博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
H.264视频流硬件解码实现
阅读量:5255 次
发布时间:2019-06-14

本文共 4396 字,大约阅读时间需要 14 分钟。

参考知乎:http://www.zhihu.com/question/20692215

http://blog.csdn.net/leonpengweicn/article/details/46880833

        Demo实例:

           https://github.com/stevenyao/iOSHardwareDecoder

           https://github.com/adison/-VideoToolboxDemo

从iPhone4开始,苹果就是支持硬件解码了,但是硬解码API一直是私有API,不开放给开发者使用,只有越狱才能使用,正常的App如果想提交到AppStore是不允许使用私有API的。

从iOS8开始,可能是苹果想通了,开放了硬解码和硬编码API,就是名为 VideoToolbox.framework的API,需要用iOS 8以后才能使用,iOS 7.x上还不行。
这套硬解码API是几个纯C函数,在任何OC或者 C++代码里都可以使用。
首先要把 VideoToolbox.framework 添加到工程里,并且包含以下头文件。

#include <VideoToolbox/VideoToolbox.h>

 

解码主要需要以下三个函数

VTDecompressionSessionCreate 创建解码 session

VTDecompressionSessionDecodeFrame 解码一个frame

VTDecompressionSessionInvalidate 销毁解码 session

 

首先要创建 decode session,方法如下:

OSStatus status = VTDecompressionSessionCreate(kCFAllocatorDefault, decoderFormatDescription, NULL, attrs, &callBackRecord, &deocderSession);

 

其中 decoderFormatDescription 是 CMVideoFormatDescriptionRef 类型的视频格式描述,这个需要用H.264的 sps 和 pps数据来创建,调用以下函数创建 decoderFormatDescription

CMVideoFormatDescriptionCreateFromH264ParameterSets

需要注意的是,这里用的 sps和pps数据是不包含“00 00 00 01”的start code的。

 

attr是传递给decode session的属性词典

CFDictionaryRef attrs = NULL; const void *keys[] = { kCVPixelBufferPixelFormatTypeKey }; // kCVPixelFormatType_420YpCbCr8Planar is YUV420 // kCVPixelFormatType_420YpCbCr8BiPlanarFullRange is NV12 uint32_t v = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange; const void *values[] = { CFNumberCreate(NULL, kCFNumberSInt32Type, &v) }; attrs = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);

其中重要的属性就一个,kCVPixelBufferPixelFormatTypeKey,指定解码后的图像格式,必须指定成NV12,苹果的硬解码器只支持NV12。

callBackRecord 是用来指定回调函数的,解码器支持异步模式,解码后会调用这里的回调函数。
如果 decoderSession创建成功就可以开始解码了。

VTDecodeFrameFlags flags = 0; //kVTDecodeFrame_EnableTemporalProcessing | kVTDecodeFrame_EnableAsynchronousDecompression; VTDecodeInfoFlags flagOut = 0; CVPixelBufferRef outputPixelBuffer = NULL; OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(deocderSession, sampleBuffer, flags, &outputPixelBuffer, &flagOut);

其中 flags 用0 表示使用同步解码,这样比较简单。

其中 sampleBuffer是输入的H.264视频数据,每次输入一个frame。
先用CMBlockBufferCreateWithMemoryBlock 从H.264数据创建一个CMBlockBufferRef实例。
然后用 CMSampleBufferCreateReady创建CMSampleBufferRef实例。
这里要注意的是,传入的H.264数据需要Mp4风格的,就是开始的四个字节是数据的长度而不是“00 00 00 01”的start code,四个字节的长度是big-endian的。
一般来说从 视频里读出的数据都是 “00 00 00 01”开头的,这里需要自己转换下。
解码成功之后,outputPixelBuffer里就是一帧 NV12格式的YUV图像了。
如果想获取YUV的数据可以通过

CVPixelBufferLockBaseAddress(outputPixelBuffer, 0); void *baseAddress = CVPixelBufferGetBaseAddress(outputPixelBuffer);

获得图像数据的指针,需要说明baseAddress并不是指向YUV数据,而是指向一个CVPlanarPixelBufferInfo_YCbCrBiPlanar结构体,结构体里记录了两个plane的offset和pitch。

 

但是如果想把视频播放出来是不需要去读取YUV数据的,因为CVPixelBufferRef是可以直接转换成OpenGL的Texture或者UIImage的。

调用CVOpenGLESTextureCacheCreateTextureFromImage,可以直接创建OpenGL Texture

 

从 CVPixelBufferRef 创建 UIImage

CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer]; UIImage *uiImage = [UIImage imageWithCIImage:ciImage];

 

解码完成后销毁 decoder session

VTDecompressionSessionInvalidate(deocderSession)

 

硬解码的基本流程就是这样了,如果需要成功解码播放视频还需要一些H.264视频格式,YUV图像格式,OpenGL等基础知识。

 

还是有很多小细节要处理的,无法在这里一一说明了,有人有问题可以在评论里讨论。

从解码到播放,大约1000行代码左右,主要是OpenGL渲染的代码比较多。

----------------------------------------------------------------------------------------------------------------------------

实际场景。对方传来的流 有可能是连续帧。这种时候 你要自己写抓单独的帧。其实很简单。就是遍历。遇见00000001开始。遇见下一个0000001结束。。中间的就是需要的数据了。

花屏的原因只有一个。就是I帧数据不符合sps pps的描述。 或者是丢包少了数据。或者是多了数据。 

猜测你花屏的原因是。  解码器收到sps pps。然后收到的I帧实际是  I帧+B/P帧。     或者是  I帧数据丢包了。 或者是合包的时候位置错了
只要能出图像。方向就对了。剩下的就是分析数据

- (void)decodeNalu:(uint8_t *)frame withSize:(uint32_t)frameSize

{
    int nalu_type = (frame[4] & 0x1F);
    CVPixelBufferRef pixelBuffer = NULL;
    uint32_t nalSize = (uint32_t)(frameSize - 4);
    uint8_t *pNalSize = (uint8_t*)(&nalSize);
    frame[0] = *(pNalSize + 3);
    frame[1] = *(pNalSize + 2);
    frame[2] = *(pNalSize + 1);
    frame[3] = *(pNalSize);
    
    //传输的时候:关键帧(I)不能丢数据,否则绿屏;B/P帧可以丢但会卡顿
    switch (nalu_type) {
        case 0x05: { // I(关键帧)
            if([self initH264Decoder]) {
                pixelBuffer = [self decode:frame withSize:frameSize];
            }
            
            break;
        }
        case 0x06: { // SEI
            break;
        }
        case 0x07: { // sps
            _spsSize = frameSize - 4;
            _sps = malloc(_spsSize);
            memcpy(_sps, &frame[4], _spsSize);
            break;
        }
        case 0x08: { // pps
            _ppsSize = frameSize - 4;
            _pps = malloc(_ppsSize);
            memcpy(_pps, &frame[4], _ppsSize);
            break;
        }
        default: { // B/P(帧)
            if([self initH264Decoder]) {
                pixelBuffer = [self decode:frame withSize:frameSize];
            }
            
            break;
        }
    }
}

 

转载于:https://www.cnblogs.com/chims-liu-touch/p/7977330.html

你可能感兴趣的文章
electron入门心得
查看>>
格而知之2:UIView的autoresizingMask属性探究
查看>>
我的Hook学习笔记
查看>>
js中的try/catch
查看>>
寄Android开发Gradle你需要知道的知识
查看>>
简述spring中常有的几种advice?
查看>>
给你的网站404页面加上“宝贝寻亲”公益页面
查看>>
整理推荐的CSS属性书写顺序
查看>>
ServerSocket和Socket通信
查看>>
css & input type & search icon
查看>>
源代码的下载和编译读后感
查看>>
Kafka学习笔记
查看>>
Octotree Chrome安装与使用方法
查看>>
Windows 环境下基于 Redis 的 Celery 任务调度模块的实现
查看>>
趣谈Java变量的可见性问题
查看>>
C# 强制关闭当前程序进程(完全Kill掉不留痕迹)
查看>>
ssm框架之将数据库的数据导入导出为excel文件
查看>>
语音识别中的MFCC的提取原理和MATLAB实现
查看>>
验证组件FluentValidation的使用示例
查看>>
0320-学习进度条
查看>>