• 实现支持 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 格式的压缩视频。

      关键的配置代码如下:

    1. static HRESULT FillMJPG(CMediaType* pMediaType, Bambu_StreamInfo const* m_info)
    2. {
    3. VIDEOINFOHEADER* pvi =
    4. (VIDEOINFOHEADER*)pMediaType->AllocFormatBuffer(sizeof(VIDEOINFOHEADER));
    5. if (pvi == 0)
    6. return(E_OUTOFMEMORY);
    7. ZeroMemory(pvi, pMediaType->cbFormat);
    8. int sizeImage = m_info->format.video.width * m_info->format.video.height * 3;
    9. pMediaType->SetSubtype(&MEDIASUBTYPE_MJPG);
    10. pMediaType->SetFormatType(&FORMAT_VideoInfo);
    11. pMediaType->SetTemporalCompression(FALSE);
    12. pMediaType->SetSampleSize(sizeImage);
    13. SetRect(&(pvi->rcSource), 0, 0, m_info->format.video.width, m_info->format.video.height);
    14. CopyRect(&(pvi->rcTarget), &(pvi->rcSource));
    15. pvi->AvgTimePerFrame = 0;//UNITS / m_info->video_format.frame_rate;
    16. BITMAPINFOHEADER* bmi = &pvi->bmiHeader;
    17. bmi->biSize = sizeof(BITMAPINFOHEADER);
    18. bmi->biPlanes = 1;
    19. bmi->biClrImportant = 0;
    20. bmi->biClrUsed = 0;
    21. bmi->biWidth = m_info->format.video.width;
    22. bmi->biHeight = m_info->format.video.height;
    23. bmi->biSizeImage = sizeImage;
    24. bmi->biBitCount = 24;
    25. bmi->biCompression = 'GPJM'; // MJPG
    26. return S_OK;
    27. }

    其中 biBitCount 一定要设置,因为 jpg 没有 alpha 通道,所以一般是 24 bit 深度。我一开始没有设置,与 “MJPEG Decompressor” 就无法连接。

    VideoToolBox 解码

    在 Mac 平台,使用 VideoToolBox 解码 MJPG 格式的视频。

    关键的配置代码如下:

    1. status = CMVideoFormatDescriptionCreate(kCFAllocatorDefault, kCMVideoCodecType_JPEG,
    2. stream_info.format.video.width, stream_info.format.video.height,
    3. NULL, &formatDescription);

    另外发现一个诡异的问题:图像显示顺序是乱的,会显示一张新图像,再显示一张之前的图像,如此反复。

    后来发现,在解码回调函数里面拿到 IOSurfaceRef 图像,必须马上使用,post 到 ui 线程处理时,要等待 ui 线程处理完成。但是同样的代码,对于 H264 解码器,是正常的。

    改成同步等待(用 dispatch_sync 代替 dispatch_async),就 OK 了。

    1. - (void) didDecompress: (OSStatus) status with:(CVImageBufferRef) imageBuffer {
    2. IOSurfaceRef surface = CVPixelBufferGetIOSurface(imageBuffer);
    3. dispatch_sync(dispatch_get_main_queue(), ^{
    4. if (self->playing && surface != nil)
    5. view.layer.contents = (__bridge id) surface;
    6. });
    7. }

  • 相关阅读:
    Redis入门完整教程:客户端管理
    Java&Springboot&MYSQL装修选购网站99192-计算机毕业设计项目选题推荐(附源码)
    Python内置库pathlib
    《活着》思维导图
    PAT 甲级 A1087 All Roads Lead to Rome
    【集合】双列集合
    计算机毕业设计SSM“花点时间”在线图书超市【附源码数据库】
    论文解读(AGE)《Adaptive Graph Encoder for Attributed Graph Embedding》
    Android Apk 编译打包流程,了解一下~
    矩阵微积分
  • 原文地址:https://blog.csdn.net/luansxx/article/details/126439279