美文网首页
iOS H264解码

iOS H264解码

作者: 如意神王 | 来源:发表于2019-06-12 17:10 被阅读0次

    首先感谢 Logan_iOS 大神

    Logan_iOS

    1.头文件和参数类型

    #import <VideoToolbox/VideoToolbox.h>
    VTDecompressionSessionRef _deocderSession;               //  解码  session
    CMVideoFormatDescriptionRef _decoderFormatDescription;   //  解码 format 封装了sps和pps
    //sps & pps
    uint8_t *_sps;
    NSInteger _spsSize;
    uint8_t *_pps;
    NSInteger _ppsSize;
    

    2.调用解码方法 采集sps pps 关键帧 其他帧数据

    // 解码操作
    - (void)decodeNalu:(uint8_t *)frame size:(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);
        
        //传输的时候。关键帧不能丢数据 否则绿屏   B/P可以丢  这样会卡顿
        switch (nalu_type)
        {
            case 0x05:
                //  关键帧
                if([self initH264Decoder])
                {
                    pixelBuffer = [self decode:frame withSize:frameSize];
                }
                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;
            }
        }
    }
    

    3.初始化解码器

    注意调用次数和位置
    1.读取H264格式信息
    2.设置解码参数NSDictionary类型
    3.设置callBack 类和回调函数
    4.创建解压会话,这时候只是创建了并未开始解压工作
    5.设置解压会话属性

    // 初始化解码器
    - (BOOL)initH264Decoder {
        if(_deocderSession) {
            return YES;
        }
        const uint8_t* const parameterSetPointers[2] = { _sps, _pps };
        const size_t parameterSetSizes[2] = { _spsSize, _ppsSize };
        // 1.获取H264视频格式描述信息
        // 参数1 alloc方式
        // 参数2 参数集合数量
        // parameterSetCount This parameter must be at least 2.可能和parameterSetPointers、parameterSetSizes有关系
        // 参数3 parameterSetPointers
        // 参数4 parameterSetSizes
        // 参数5 NALU 标识StartCode的长度
        // 参数6 CMVideoFormatDescriptionRef 视频格式描述容器 H264读取出来的信息放在这里面
        OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
                                                                              2, //param count
                                                                              parameterSetPointers,
                                                                              parameterSetSizes,
                                                                              4, //nal start code size
                                                                              &_decoderFormatDescription);
        
        if(status == noErr) {
            // 2.设置解压会话参数值
            NSDictionary* destinationPixelBufferAttributes = @{
                                                               (id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange], //硬解必须是 kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 或者是kCVPixelFormatType_420YpCbCr8Planar
                                                               //这里款高和编码反的
                                                               (id)kCVPixelBufferOpenGLCompatibilityKey : [NSNumber numberWithBool:YES]
                                                               };
            
            // 3.设置回调方法
            VTDecompressionOutputCallbackRecord callBackRecord;
            // 回调函数
            callBackRecord.decompressionOutputCallback = didDecompress;
            // 输出的类
            callBackRecord.decompressionOutputRefCon = (__bridge void *)self;
            
            // 4.创建解压会话,这时候只是创建了并未开始解压工作
            // 参数1 session alloc方式
            // 参数2 CMVideoFormatDescriptionRef 视频格式描述信息 上面读取的
            // 参数3 特殊的decoder NULL-> VideoToolbox自行选择
            // 参数4 解压参数设置
            // 参数5 回调地址
            // 参数6 会话地址
            status = VTDecompressionSessionCreate(kCFAllocatorDefault,
                                                  _decoderFormatDescription,
                                                  NULL,
                                                  (__bridge CFDictionaryRef)destinationPixelBufferAttributes,
                                                  &callBackRecord,
                                                  &_deocderSession);
            // 5.设置解压属性
            VTSessionSetProperty(_deocderSession, kVTDecompressionPropertyKey_ThreadCount, (__bridge CFTypeRef)[NSNumber numberWithInt:1]);
            VTSessionSetProperty(_deocderSession, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
        } else {
            NSLog(@"IOS8VT: reset decoder session failed status=%d", (int)status);
        }
        
        return YES;
    }
    

    4.开始解码

    // 开始解压
    // 1.根据帧信息获取编码后的CMBlockBuffer
    // 2.创建 CMSampleBuffer
    // 3.解压
    // 4.返回解压后的信息CVPixelBufferRef

    - (CVPixelBufferRef)decode:(uint8_t *)frame withSize:(uint32_t)frameSize{
        CVPixelBufferRef outputPixelBuffer = NULL;
        //1.根据帧信息获取编码后的CMBlockBuffer
        // 参数1 alloc方式
        // 参数2 frame
        // 参数3 frameSize
        // 参数4 blockAllocator NULL ->default
        // 参数5
        // 参数6
        CMBlockBufferRef blockBuffer = NULL;
        OSStatus status  = CMBlockBufferCreateWithMemoryBlock(NULL,
                                                              (void *)frame,
                                                              frameSize,
                                                              kCFAllocatorNull,
                                                              NULL,
                                                              0,
                                                              frameSize,
                                                              FALSE,
                                                              &blockBuffer);
        if(status == kCMBlockBufferNoErr) {
            CMSampleBufferRef sampleBuffer = NULL;
            const size_t sampleSizeArray[] = {frameSize};
    //        2.创建 CMSampleBuffer
            // 参数1 alloc方式
            // 参数2 CMBlockBufferRef 编码后的视频数据信息
            // 参数3 视频格式描述
            
            status = CMSampleBufferCreateReady(kCFAllocatorDefault,
                                               blockBuffer,
                                               _decoderFormatDescription ,
                                               1, 0, NULL, 1, sampleSizeArray,
                                               &sampleBuffer);
            if (status == kCMBlockBufferNoErr && sampleBuffer) {
                VTDecodeFrameFlags flags = 0;
                VTDecodeInfoFlags flagOut = 0;
                OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(_deocderSession,
                                                                          sampleBuffer,
                                                                          flags,
                                                                          &outputPixelBuffer,
                                                                          &flagOut);
                
                if(decodeStatus == kVTInvalidSessionErr) {
                    NSLog(@"IOS8VT: Invalid session, reset decoder session");
                } else if(decodeStatus == kVTVideoDecoderBadDataErr) {
                    NSLog(@"IOS8VT: decode failed status=%d(Bad data)", (int)decodeStatus);
                } else if(decodeStatus != noErr) {
                    NSLog(@"IOS8VT: decode failed status=%d", (int)decodeStatus);
                }
                CFRelease(sampleBuffer);
            }
            CFRelease(blockBuffer);
        }
        return outputPixelBuffer;
    }
    

    5.解码后回调方法

    // 解码回调函数

    static void didDecompress( void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef pixelBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ){
        CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
        *outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);
        VideoH264Decoder *decoder = (__bridge VideoH264Decoder *)decompressionOutputRefCon;
        
        if ([decoder.delegate respondsToSelector:@selector(decoder:didDecodingFrame:)]) {
            [decoder.delegate decoder: decoder didDecodingFrame:pixelBuffer];
        }
    }
    

    6.解码调用

    编码成功后,模拟客户端进行解码调用

    // 获取数据进行解码
    - (void)didReadData:(NSData *)data{
        [self.h264Decoder decodeNalu:(uint8_t *)[data bytes] size:(uint32_t)data.length];
    }
    

    7.视频H264编码前后CMSampleBuffer结构示意图

    视频H264编码后-解压后数据结构图.png

    相关文章

      网友评论

          本文标题:iOS H264解码

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