1. 程式人生 > >libVLC提取視訊幀及自定義讀取媒體檔案

libVLC提取視訊幀及自定義讀取媒體檔案

hello誒喂八滴跟我一起嗨嗨嗨。。。,阿循今天給大家分享一下最近在學習的開源視訊播放器vlcplayer的一些心得,我這邊是要把這個弄到Unity裡去用,因此提取視訊幀和自定義讀取是很關鍵的功能點,前者可以拿到資料給unity渲染,後者可以在C#層面去做資料功能模組,開發效率美滋滋。

libvlc是vlc的開發者庫,它的播放器也是通過libvlc實現的,我們這裡用到了C++,不過也是有C#繫結的版本的,這裡就不討論了。

首先,含SDK目錄的程式可以在http://download.videolan.org/pub/videolan/vlc/找到,官網上的版本里並不包含;配置這些按網上教 程來就好,也很多的。ps:看到SDK不得不吐槽,居然有40多M。或許,一些裁剪可以解決這個問題。然則,終究,還是花時間,先這樣吧。

關於這幾個功能,網上也是語焉不詳,我既然花了時間去跑通了,正宜分享給大家。

1、提取幀,可以用來開發自定義渲染的功能,vlcplayer雖然說可在指定窗體播放,但是靈活性不是很高,要做一些定製性的渲染,就需要通過獲取它的視訊幀的資料,然後再通過一些手段顯示,如dx,gdi等等,其它平臺也是一樣的思路。

下面的例子我每隔30幀抽取一張圖,並用opencv儲存成jpg:

#pragma region 提取幀回撥
// 回撥
const int w = 1920; 
const int h = 960;
uchar buffer[w*h * 4];
int idx = -1;
Mat* mat = new Mat(h, w, CV_8UC3);

void* VideoLockCallback(void *opaque, void **planes) 
{
	memset(buffer, 0, w*h * 4);
	*planes = buffer;
	return buffer;
}
void VideoUnlockCallback(void *opaque, void *picture, void *const*planes) {}
void VideoDisplayCallback(void *opaque,void *picture) 
{
	idx++;
	if (idx % 30 != 0)
	{
		return;
	}
	int j = idx;
	// 設定資料
	int nl = mat->rows;
	int nc = mat->cols;
	/*if (mat->isContinuous())
	{
	nc = nc*nl;
	nl = 1;
	}*/
	for (size_t j = 0; j < nl; j++)
	{
		uchar *data = mat->ptr<uchar>(j);
		for (size_t i = 0; i < nc; i++)
		{
			// buffer->RGBA
			// opencv->BGR
			*data++ = buffer[j*nc * 4 + i * 4 + 2];
			*data++ = buffer[j*nc * 4 + i * 4 + 1];
			*data++ = buffer[j*nc * 4 + i * 4];
		}
	}
	//
	vector<int>compression_params;
	compression_params.push_back(IMWRITE_JPEG_QUALITY);
	compression_params.push_back(50);
	imwrite("frame/" + to_string(j) + ".jpg", *mat, compression_params);
}
#pragma endregion 提取幀回撥
void GetFrameTest(const char* fileName)
{
	libvlc_instance_t* vlcInst = libvlc_new(0, 0);
	if (!vlcInst)
		cout << "libvlc_new error\n";
	libvlc_media_t* vlc_media = libvlc_media_new_path(vlcInst, fileName);
	if (!vlc_media)
		cout << "libvlc_media_new_path error\n";
	libvlc_media_player_t* vlc_player = libvlc_media_player_new_from_media(vlc_media);
	libvlc_media_release(vlc_media);
	if (!vlc_player)
		cout << "libvlc_media_player_new_from_media error\n";

	libvlc_video_set_callbacks(vlc_player, VideoLockCallback, VideoUnlockCallback, VideoDisplayCallback, 0);
	libvlc_video_set_format(vlc_player, "RGBA", w, h, w * 4);
	libvlc_media_player_play(vlc_player);
}

提取資料,關鍵在於libvlc_video_set_callbacks和libvlc_video_set_format,前者通過幾個回撥,攔截了vlc自己的視訊顯示元件,資料將傳遞到回撥裡,因此我們可以通過它來拿到資料,在上面的例子中,寬度和高度是可以自己設定的,並不需要等於視訊的寬高,vlc會自己縮放到你指定的寬高(通過libvlc_video_set_format指定的)。需要注意的是vlc生成的資料是RGBA的排列順序,當你拿到資料作它用的時候,要考慮下你的目標的顏色分量的順序。

2、自定義讀取,用處是很多的,比如視訊資料是加過密的,或者想播放一個網路視訊,自己處理網路緩衝部分,因此它自帶的播放介面就不一定能滿足要求了。在vlc2.x版本,可以通過libvlc_media_new_location來完成,因為這個方法的url引數支援很多協議,通過記憶體資料協議("imem://")即可達到目標,不過3.x對此做了封裝,提供了新的介面libvlc_media_new_callbacks,在這裡我們只講新介面下的實現。

在例子中,通過自定義資料讀取,我使用檔案流對本地檔案進行讀取,並通過自定義資料讀取的回撥把資料傳給了vlc。同樣的,你可以從http或者其它哪來獲取資料並傳給它:

#pragma region 自定義讀資料回撥
ifstream file;

int		MediaOpen(void *opaque, void **datap,uint64_t *sizep) 
{
	file.open((char*)opaque, ios::binary | ios::in);
	file.seekg(0, ios::end);
	int len = file.tellg();
	file.seekg(0);
	*sizep = len;
	*datap = &file;
	return 0;
}
size_t	MediaRead(void *opaque, unsigned char *buf, size_t len) 
{
	ifstream* in = (ifstream*)opaque;
	in->read((char*)buf, len);
	auto s = in->gcount();
	if (s == 0)
	{
		if (in->eof())
		{
			return 0;
		}
		else
		{
			return -1;
		}
	}
	return s;
}
int		MediaSeek(void *opaque, uint64_t offset) 
{ 
	ifstream* in = (ifstream*)opaque;
	in->clear();
	in->seekg(offset);
	return 0; 
}
void	MediaClose(void *opaque) 
{
	ifstream* in = (ifstream*)opaque;
	in->close();
	cout << "close\n";
}


#pragma endregion 自定義讀資料回撥
void CustomReadTest(const char* fileName)
{
	libvlc_instance_t* vlcInst = libvlc_new(0, 0);
	if (!vlcInst)
		cout << "libvlc_new error\n";
	libvlc_media_t* vlc_media = libvlc_media_new_callbacks(
		vlcInst, MediaOpen, MediaRead, MediaSeek, MediaClose, (void*)fileName);
	if (!vlc_media)
		cout << "libvlc_media_new_path error\n";
	libvlc_media_player_t* vlc_player = libvlc_media_player_new_from_media(vlc_media);
	libvlc_media_release(vlc_media);
	if (!vlc_player)
		cout << "libvlc_media_player_new_from_media error\n";
	libvlc_media_player_play(vlc_player);
	Sleep(5000);
	auto time = libvlc_media_player_get_length(vlc_player);
	Sleep(time);
}

有幾個關鍵點要提一下

1、open回撥的第一個引數opaque是libvlc_media_new_callbacks的最後一個引數傳入的,而read,seek,close回撥的opaque引數則是open回撥中的datap,名字一樣,可不是同一個資料,因為人家是c庫嘛,沒得類,有些東西就通過方法引數傳遞了。

2、我在使用ifstream的時候,因為一個坑也花了半天時間,seekg方法在達到檔案尾之後再呼叫不起作用,要先呼叫clear方法,我之前一直以為是其它地方有問題,花了很多時間去後vlc的程式碼。。。