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的代码。。。

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值