前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用VideoToolbox硬解码H.264

使用VideoToolbox硬解码H.264

作者头像
落影
发布2018-04-27 16:48:40
2.4K0
发布2018-04-27 16:48:40
举报
文章被收录于专栏:落影的专栏落影的专栏

前言

使用VideoToolbox硬编码H.264

在上一篇的硬编码简单介绍了H.264和VideoToolbox以及如何使用VideoToolbox硬编码从摄像头采集到的数据为H.264文件,这次使用VideoToolbox硬解码上一篇生成的H.264文件并渲染显示到屏幕。

概念介绍

  • CVPixelBuffer

包含未压缩的像素数据,包括图像宽度、高度等;

  • CVPixelBufferPool

CVPixelBuffer的缓冲池,因为CVPixelBuffer的创建和销毁代价很大;

  • pixelBufferAttributes

CFDictionary包括宽高、像素格式(RGBA、YUV)、使用场景(OpenGL ES、Core Animation)

  • CMTime

64位的value,32位的scale,media的时间格式;

  • CMVideoFormatDescription

video的格式,包括宽高、颜色空间、编码格式等;对于H.264的视频,PPS和SPS的数据也在这里;

  • CMBlockBuffer

未压缩的图像数据;

  • CMSampleBuffer

存放一个或者多个压缩或未压缩的媒体文件;

  • CMClock

时间源:A timing source object.

  • CMTimebase

时间控制器,可以设置rate和time:A timebase represents a timeline that clients can control by setting the rate and time. Each timebase has either a master clock or a master timebase. The rate of the timebase is expressed relative to its master.

核心思路

用NSInputStream读入原始H.264码流,用CADisplayLink控制显示速率,用NALU的前四个字节识别SPS和PPS并存储,当读入IDR帧的时候初始化VideoToolbox,并开始同步解码;解码得到的CVPixelBufferRef会传入OpenGL ES类进行解析渲染。

效果展示

H.264的清晰度受码率和关键帧间隔影响,GIF清晰度有限。

全文仅此GIF

具体细节

1、把原始码流包装成CMSampleBuffer
  • 1、替换头字节长度;
代码语言:javascript
复制
            uint32_t nalSize = (uint32_t)(packetSize - 4);
            uint32_t *pNalSize = (uint32_t *)packetBuffer;
            *pNalSize = CFSwapInt32HostToBig(nalSize);
  • 2、用CMBlockBuffer把NALUnit包装起来;
代码语言:javascript
复制
        CMBlockBufferRef blockBuffer = NULL;
        OSStatus status  = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,
                                                              (void*)packetBuffer, packetSize,
                                                              kCFAllocatorNull,
                                                              NULL, 0, packetSize,
                                                              0, &blockBuffer);
  • 3、把SPS和PPS包装成CMVideoFormatDescription;
代码语言:javascript
复制
        const uint8_t* parameterSetPointers[2] = {mSPS, mPPS};
        const size_t parameterSetSizes[2] = {mSPSSize, mPPSSize};
        OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
                                                                              2, //param count
                                                                              parameterSetPointers,
                                                                              parameterSetSizes,
                                                                              4, //nal start code size
                                                                              &mFormatDescription);
  • 4、添加CMTime时间;

(WWDC视频上说有,但是我在实现过程没有找到添加的地方,可能是我遗漏了)

  • 5、创建CMSampleBuffer;
代码语言:javascript
复制
            CMSampleBufferRef sampleBuffer = NULL;
            const size_t sampleSizeArray[] = {packetSize};
            status = CMSampleBufferCreateReady(kCFAllocatorDefault,
                                               blockBuffer,
                                               mFormatDescription,
                                               1, 0, NULL, 1, sampleSizeArray,
                                               &sampleBuffer);
2、解码并显示
  • 1、传入CMSampleBuffer
代码语言:javascript
复制
                VTDecodeFrameFlags flags = 0;
                VTDecodeInfoFlags flagOut = 0;
                // 默认是同步操作。
                // 调用didDecompress,返回后再回调
                OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(mDecodeSession,
                                                                          sampleBuffer,
                                                                          flags,
                                                                          &outputPixelBuffer,
                                                                          &flagOut);
  • 2、回调didDecompress
代码语言:javascript
复制
void didDecompress(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef pixelBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ){
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    *outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);
}
  • 3、显示解码的结果
代码语言:javascript
复制
[self.mOpenGLView displayPixelBuffer:pixelBuffer];

仔细对比硬编码和硬解码的图像,会发现硬编码的图像被水平镜像过。

当遇到IDR帧时,更合适的做法是通过 VTDecompressionSessionCanAcceptFormatDescription判断原来的session是否能接受新的SPS和PPS,如果不能再新建session。

总结

WWDC的视频适合先学再看(个人体悟),并不是很适合没基础的时候看。在写完硬编码和硬解码的demo之后,再完整的看一遍WWDC的视频,对VideoToolbox的印象更加深刻,同时明白MPEG-4格式下的H.264码流与原始H.264码流的不同。

如果有不了解的,可以查看代码

对OpenGL ES有兴趣的,看看的OpenGL ES文集

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.09.05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 概念介绍
  • 核心思路
  • 效果展示
  • 具体细节
    • 1、把原始码流包装成CMSampleBuffer
      • 2、解码并显示
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档