Motion JPEG(M-JPEG或MJPEG,Motion Joint Photographic Experts Group,FourCC:MJPG)是一种影像压缩格式,其中每一帧图像都分别使用JPEG编码。M-JPEG常用在数码相机和摄像头之类的图像采集设备上,非线性剪辑系统也常用这种格式。QuickTime播放器和包括Mozilla Firefox,Google Chrome,Safari在内许多网页浏览器原生支持M-JPEG。
—— https://zh.m.wikipedia.org/zh-hans/Motion_JPEG
对于网络摄像头时,使用 MJPEG 是一个比较低成本的方案,并且非常时候局域网配置。因为不需要很高的压缩效率,替换 H264、H265 会省下专利费和芯片成本。
MJPEG 流没有统一的规范,微软使用很老的 AVI 格式封装,Mac 平台则用 Mp4 封装。对于流式传输,则是各个摄像头厂商自己定义协议了,所以需要实自己现播放。下面介绍各个播放框架中,怎么实现 MJPEG 的播放。
实现一个 SourceFilter,输出 MJPEG 格式的压缩视频。

关键的配置代码如下:
- static HRESULT FillMJPG(CMediaType* pMediaType, Bambu_StreamInfo const* m_info)
- {
- VIDEOINFOHEADER* pvi =
- (VIDEOINFOHEADER*)pMediaType->AllocFormatBuffer(sizeof(VIDEOINFOHEADER));
- if (pvi == 0)
- return(E_OUTOFMEMORY);
- ZeroMemory(pvi, pMediaType->cbFormat);
-
- int sizeImage = m_info->format.video.width * m_info->format.video.height * 3;
-
- pMediaType->SetSubtype(&MEDIASUBTYPE_MJPG);
- pMediaType->SetFormatType(&FORMAT_VideoInfo);
- pMediaType->SetTemporalCompression(FALSE);
- pMediaType->SetSampleSize(sizeImage);
-
- SetRect(&(pvi->rcSource), 0, 0, m_info->format.video.width, m_info->format.video.height);
- CopyRect(&(pvi->rcTarget), &(pvi->rcSource));
- pvi->AvgTimePerFrame = 0;//UNITS / m_info->video_format.frame_rate;
-
- BITMAPINFOHEADER* bmi = &pvi->bmiHeader;
- bmi->biSize = sizeof(BITMAPINFOHEADER);
- bmi->biPlanes = 1;
- bmi->biClrImportant = 0;
- bmi->biClrUsed = 0;
- bmi->biWidth = m_info->format.video.width;
- bmi->biHeight = m_info->format.video.height;
- bmi->biSizeImage = sizeImage;
- bmi->biBitCount = 24;
- bmi->biCompression = 'GPJM'; // MJPG
-
- return S_OK;
- }
其中 biBitCount 一定要设置,因为 jpg 没有 alpha 通道,所以一般是 24 bit 深度。我一开始没有设置,与 “MJPEG Decompressor” 就无法连接。
在 Mac 平台,使用 VideoToolBox 解码 MJPG 格式的视频。
关键的配置代码如下:
- status = CMVideoFormatDescriptionCreate(kCFAllocatorDefault, kCMVideoCodecType_JPEG,
- stream_info.format.video.width, stream_info.format.video.height,
- NULL, &formatDescription);
另外发现一个诡异的问题:图像显示顺序是乱的,会显示一张新图像,再显示一张之前的图像,如此反复。
后来发现,在解码回调函数里面拿到 IOSurfaceRef 图像,必须马上使用,post 到 ui 线程处理时,要等待 ui 线程处理完成。但是同样的代码,对于 H264 解码器,是正常的。
改成同步等待(用 dispatch_sync 代替 dispatch_async),就 OK 了。
- - (void) didDecompress: (OSStatus) status with:(CVImageBufferRef) imageBuffer {
- IOSurfaceRef surface = CVPixelBufferGetIOSurface(imageBuffer);
- dispatch_sync(dispatch_get_main_queue(), ^{
- if (self->playing && surface != nil)
- view.layer.contents = (__bridge id) surface;
- });
- }