实现支持 MJPEG 的播放器

MJPEG 简介

Motion JPEGM-JPEGMJPEG,Motion Joint Photographic Experts Group,FourCC:MJPG)是一种影像压缩格式,其中每一图像都分别使用JPEG编码。M-JPEG常用在数码相机摄像头之类的图像采集设备上,非线性剪辑系统也常用这种格式。QuickTime播放器和包括Mozilla FirefoxGoogle ChromeSafari在内许多网页浏览器原生支持M-JPEG。

—— https://zh.m.wikipedia.org/zh-hans/Motion_JPEG

对于网络摄像头时,使用 MJPEG 是一个比较低成本的方案,并且非常时候局域网配置。因为不需要很高的压缩效率,替换 H264、H265 会省下专利费和芯片成本。

MJPEG 流没有统一的规范,微软使用很老的 AVI 格式封装,Mac 平台则用 Mp4 封装。对于流式传输,则是各个摄像头厂商自己定义协议了,所以需要实自己现播放。下面介绍各个播放框架中,怎么实现 MJPEG 的播放。

DirectShow 框架

实现一个 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” 就无法连接。

VideoToolBox 解码

在 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;
    });
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting Horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值