从内存缓冲区处理视频流

14

我需要解析来自私有网络协议的视频流(MPEG TS),我已经知道如何做到这一点。然后,我想使用OpenCV将视频流处理为帧。我知道如何使用cv :: VideoCapture从文件或标准URL中读取,但我想设置OpenCV以从内存中的缓冲区读取数据,直到需要它。是否可以设置回调方法(或任何其他接口),以便我仍然可以使用cv :: VideoCapture对象?是否有更好的方法在不将其写入文件并重新读取它的情况下完成视频处理?如果这是更好的选择,我也可以考虑直接使用FFMPEG。我认为如果需要,我可以将AVFrames转换为Mat。


你如何读取和解码缓冲区?我曾经通过初始化IplImage对象,分配内存并使用memcpy将帧从流复制到IplImage中,将OpenCV与专有流集成。在我的情况下,我很幸运地从流中获得了适当的原始图像。 - lfagundes
@lfagundes,一旦我从专有封装中提取出视频流,它就变成了普通的MPEG TS流。之后,我想使用现有的工具(最好是OpenCV)来解码传输流和视频帧。 - Dan
以下是如何使用OpenCV捕获视频流,将其编码为MPEG-4缓冲区,然后从缓冲区解码的示例:http://dimitri-christodoulou.blogspot.com/2012/02/encode-and-decode-video-from-memory.html - Dimitri
2个回答

20
我最近也有类似的需求。我正在寻找一种在OpenCV中播放已经存在于内存中的视频的方法,但是不需要将视频文件写入磁盘。我发现FFMPEG接口已经支持这一点,通过使用av_open_input_stream。与OpenCV用于打开文件的av_open_input_file调用相比,需要进行更多的准备工作。
通过以下两个网站之间的交流,我能够组合出一个使用ffmpeg调用的可行解决方案。请参考这些网站上的信息以获取更多详细信息:

http://ffmpeg.arrozcru.org/forum/viewtopic.php?f=8&t=1170

http://cdry.wordpress.com/2009/09/09/using-custom-io-callbacks-with-ffmpeg/

为了在OpenCV中使其正常工作,我最终向CvCapture_FFMPEG类添加了一个新的函数:
virtual bool openBuffer( unsigned char* pBuffer, unsigned int bufLen );

我在highgui DLL中提供了一个新的API调用来访问它,类似于cvCreateFileCapture。新的openBuffer函数基本上与open(const char *_filename)函数相同,唯一的区别是:

err = av_open_input_file(&ic, _filename, NULL, 0, NULL);

被替换为:

ic = avformat_alloc_context();
ic->pb = avio_alloc_context(pBuffer, bufLen, 0, pBuffer, read_buffer, NULL, NULL);

if(!ic->pb) {
    // handle error
}

// Need to probe buffer for input format unless you already know it
AVProbeData probe_data;
probe_data.buf_size = (bufLen < 4096) ? bufLen : 4096;
probe_data.filename = "stream";
probe_data.buf = (unsigned char *) malloc(probe_data.buf_size);
memcpy(probe_data.buf, pBuffer, probe_data.buf_size);

AVInputFormat *pAVInputFormat = av_probe_input_format(&probe_data, 1);

if(!pAVInputFormat)
    pAVInputFormat = av_probe_input_format(&probe_data, 0);

// cleanup
free(probe_data.buf);
probe_data.buf = NULL;

if(!pAVInputFormat) {
    // handle error
}

pAVInputFormat->flags |= AVFMT_NOFILE;

err = av_open_input_stream(&ic , ic->pb, "stream", pAVInputFormat, NULL);

此外,在CvCapture_FFMPEG::close()函数中,请确保调用av_close_input_stream而不是av_close_input_file

现在,我将read_buffer回调函数定义为传递给avio_alloc_context的:

static int read_buffer(void *opaque, uint8_t *buf, int buf_size)
{
    // This function must fill the buffer with data and return number of bytes copied.
    // opaque is the pointer to private_data in the call to avio_alloc_context (4th param)
    
    memcpy(buf, opaque, buf_size);
    return buf_size;
}

这个解决方案假设整个视频都包含在内存缓冲区中,并且可能需要进行调整才能与流数据一起使用。
就是这样!顺便说一下,我正在使用 OpenCV 版本 2.1,所以你的情况可能会有所不同。

我已经解决了这个问题,但是忘记发布我的解决方案。这确实是我所采用的方法。 - Dan
4
注意事项:memcpy(probe_data.buf, data, 4096) 应该使用 probe_data.buf_size 而不是 4096(否则对于小于 4096 的输入会崩溃)。av_alloc_put_byte 已被重命名为 avio_alloc_context - Yaur

1

以下是针对opencv 4.2.0的类似上述操作的代码:

https://github.com/jcdutton/opencv 分支:4.2.0-jcd1

将整个文件加载到大小为buffer_size的缓冲区中,指向RAM的buffer指针。 示例代码:

VideoCapture d_reader1;
d_reader1.open_buffer(buffer, buffer_size);
d_reader1.read(input1);

以上代码读取视频的第一帧。

如果有人费心添加了一个有用的功能,并创建拉取请求将其上游,那就太棒了。现在这个功能停留在旧版本上而没有得到上游维护。 - Marko Kohtala

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接