美文网首页iOS技术资料音视频iOS进阶
使用VideoToolbox硬解码H.264

使用VideoToolbox硬解码H.264

作者: 落影loyinglin | 来源:发表于2016-09-05 09:56 被阅读5284次

前言

使用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、替换头字节长度;
            uint32_t nalSize = (uint32_t)(packetSize - 4);
            uint32_t *pNalSize = (uint32_t *)packetBuffer;
            *pNalSize = CFSwapInt32HostToBig(nalSize);
  • 2、用CMBlockBuffer把NALUnit包装起来;
        CMBlockBufferRef blockBuffer = NULL;
        OSStatus status  = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,
                                                              (void*)packetBuffer, packetSize,
                                                              kCFAllocatorNull,
                                                              NULL, 0, packetSize,
                                                              0, &blockBuffer);
  • 3、把SPS和PPS包装成CMVideoFormatDescription;

        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;
            CMSampleBufferRef sampleBuffer = NULL;
            const size_t sampleSizeArray[] = {packetSize};
            status = CMSampleBufferCreateReady(kCFAllocatorDefault,
                                               blockBuffer,
                                               mFormatDescription,
                                               1, 0, NULL, 1, sampleSizeArray,
                                               &sampleBuffer);

2、解码并显示

  • 1、传入CMSampleBuffer
                VTDecodeFrameFlags flags = 0;
                VTDecodeInfoFlags flagOut = 0;
                // 默认是同步操作。
                // 调用didDecompress,返回后再回调
                OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(mDecodeSession,
                                                                          sampleBuffer,
                                                                          flags,
                                                                          &outputPixelBuffer,
                                                                          &flagOut);
  • 2、回调didDecompress
void didDecompress(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef pixelBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ){
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    *outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);
}
  • 3、显示解码的结果
[self.mOpenGLView displayPixelBuffer:pixelBuffer];

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

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

总结

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

相关文章

网友评论

  • Liusr:兄弟,今天遇到硬解码绿屏怎么破?感觉这个videotoolbox兼容性不好,还是说我显示有问题。
  • S__L:不使用OpenGL显示不可以么,不是有一个AVSampleBufferDisplayLayer么,用这个不可以显示么?懵懵的;
    落影loyinglin:@S__L 可以的。这个是为了学习
  • 806349745123:uint32_t nalSize = (uint32_t)(packetSize - 4);
    uint32_t *pNalSize = (uint32_t *)packetBuffer;
    *pNalSize = CFSwapInt32HostToBig(nalSize);

    uint32_t *pNalSize = (uint32_t *)packetBuffer; pNalSize指向packetBuffer

    *pNalSize = CFSwapInt32HostToBig(nalSize);后面进行大端转换,参数是nalSize,那上面uint32_t *pNalSize = (uint32_t *)packetBuffer;那段行数有什么意义
  • 806349745123://这里使用一种简略的方式来获取这一帧的长度:通过查找下一个0x00000001来确定。
    如果第一次截取字节流的时候不能截取下一个0x00000001,只能截取到0x0000,那如何确定一个NALU的长度?
    657d8a15c6d8:如何截取不到下一个0x00000001,end = buffer.size
  • f5890fa5c87d:编码部分,创建session时,设置width = 640, height = 480,使用VLC可以播放,但是在解码的部分 解析出来的画面绿屏了,请问要在哪里修改参数吗?
    落影loyinglin:@阿么阿么_8efa 有不同矩阵的
    f5890fa5c87d:@KeyboardLife 我的问题还没有解决,貌似我的问题在于摄像头获取到的原始数据格式为YUV422,与YUV420至RGB的转换并不一样
    KeyboardLife:服务器之前是1280*720,现在改成640*480,出现绿屏了,请问改参数再哪儿,你解决了吗
  • f5890fa5c87d:感谢楼主的无私分享,解决了我的燃眉之急。

    GLKView* view = (GLKView *)self.view;
    上面这句是把self.view强转成(GLKView *)类型的意思吗?为什么改成将自定义的customView强转成(GLKView *)类型就不行了呢?那如果想要把视频渲染到自定义的一个View里面,而不是整个屏幕,有没有什么思路?

    还有一个问题是,如果想要加上播放器的功能,楼主有没有思路可以指点一下
  • 飛天江郎:我这边有个问题,如果我已经能从第三方设备上获取到h264裸流以及pcm数据的裸流,那么我在播放的时候怎么确保音视频能同步呢?
  • Kira丶陈:楼主,请教下,我硬解码h264数据时,在模拟器上运行正常,但是在设备上调试的时候,总是提示12909,bad data err?有解码?
  • 899e2d74bdeb:请问,你这里录制的视频宽度480高度是640,如果我把宽度和高度对换,播发出来的视频就变形了(压缩了),请问有什么方法解决,尝试了很多方法都不行。
    落影loyinglin:@Kira丶陈 你用这个demo吗
    Kira丶陈:楼主,请教下,我硬解码h264数据时,在模拟器上运行正常,但是在设备上调试的时候,总是提示12909,bad data err?有解码?
    899e2d74bdeb:int width = 480, height = 640;
    OSStatus status = VTCompressionSessionCreate(NULL, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, didCompressH264, (__bridge void *)(self), &EncodingSession);
    也就是把宽度和高度对换就发生了变形?请问这个问题怎么解决
  • lly_0620:nice啊~~~
  • cjy027:写的不错,已打赏,谢谢你的文章
    落影loyinglin:@cjy027 多谢支持。
    一起学习,一起交流。
  • 蚂蚱Damon:落影大兄弟,想咨询你一下,如果我这边是通过Tcp,socket获取的H264的码流,要怎么解码呢?盼回复
  • 899e2d74bdeb:请问,关于替换头字节长度那里,不太明白,请教一下?
    落影loyinglin:@无忌不悔 https://developer.apple.com/videos/play/wwdc2014/513/
    无忌不悔:@落影lying-in 请问是哪个WWDC视频?想学习一下~
    落影loyinglin:@哎疯 这个可以看下WWDC视频,讲的比较清楚
  • 58b1bd5d1d45:楼主,请问使用ffmpeg下载网络直播流,然后用硬解码来解码,最开始会在decodeframe函数返回12909错误一段时间,之后正常,有无遇到?:pray:
  • Jovins:我想问一下+ (Class)layerClass 这方法调不了, 是不是要一些设置?
    Jovins:@落影loyinglin 找到原因了(Do any additional setup after loading the view, typically from a nib.)就是这句没留意,你写的资料很好,帮助很大!
    落影loyinglin:@EvenCoder 调不了是什么意思
  • 落影loyinglin:镜像的原因在于OpenGL ES的纹理坐标的X坐标写反了,把0、1对调即可。
    c1e0f39af20a:@落影loyinglin 请教下。我现在接受的是一个yuv420p的h264视频频流,是不是说这种硬解就直接可以放弃了呢,这种只能用ffmpeg来做是吗,因为我有看到说苹果只支持nv12也就是yuv420sp这种硬解码。

本文标题:使用VideoToolbox硬解码H.264

本文链接:https://www.haomeiwen.com/subject/mvkrettx.html