iOS实时硬解码H.264

本文介绍iOS下使用VTDecompressionSessionRef将编码格式为H.264的每一帧frame数据解码的方法。

编码H.264请参考:iOS实时硬编码H.264

DecodeH264.h
#import <Foundation/Foundation.h>
#import <VideoToolbox/VideoToolbox.h>

@protocol DecodeH264Delegate <NSObject>
- (void)displayDecodedFrame:(CVImageBufferRef)imageBuffer;
@end

@interface DecodeH264 : NSObject
- (BOOL)initH264Decoder;
- (void)decodeNalu:(uint8_t *)frame withSize:(uint32_t)frameSize;
@property (nonatomic,weak) id<DecodeH264Delegate>delegate;
@end
DecodeH264.m
#import "DecodeH264.h"

#define h264outputWidth 800
#define h264outputHeight 600

@interface DecodeH264() {
    uint8_t *sps;
    uint8_t *pps;
    int spsSize;
    int ppsSize;
    VTDecompressionSessionRef session;
    CMVideoFormatDescriptionRef description;
}
@end

@implementation DecodeH264

//解码回调函数
static void outputCallback(void *decompressionOutputRefCon,
                           void *sourceFrameRefCon,
                           OSStatus status,
                           VTDecodeInfoFlags infoFlags,
                           CVImageBufferRef pixelBuffer,
                           CMTime presentationTimeStamp,
                           CMTime presentationDuration)
{
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    *outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);
    DecodeH264 *decoder = (__bridge DecodeH264 *)decompressionOutputRefCon;
    if (decoder.delegate!=nil) {
        [decoder.delegate displayDecodedFrame:pixelBuffer];
    }
}

//创建解码器
- (BOOL)initH264Decoder {
    if(session) {
        return YES;
    }
    const uint8_t *parameterSetPointers[2] = {sps,pps};
    const size_t parameterSetSizes[2] = {spsSize,ppsSize};
    //设置参数
    OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
                                                                          2,//param count
                                                                          parameterSetPointers,
                                                                          parameterSetSizes,
                                                                          4,//nal start code size
                                                                          &description);
    if(status==noErr) {
        //设置属性
        NSDictionary *destinationPixelBufferAttributes = @{
                                                           //硬解必须为kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange或kCVPixelFormatType_420YpCbCr8Planar,因为iOS是nv12,其他是nv21
                                                           (id)kCVPixelBufferPixelFormatTypeKey:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange],
                                                           //宽高与编码相反
                                                           (id)kCVPixelBufferWidthKey:[NSNumber numberWithInt:h264outputHeight*2],
                                                           (id)kCVPixelBufferHeightKey:[NSNumber numberWithInt:h264outputWidth*2],
                                                           (id)kCVPixelBufferOpenGLCompatibilityKey:[NSNumber numberWithBool:YES]
                                                           };
        //设置回调
        VTDecompressionOutputCallbackRecord callBackRecord;
        callBackRecord.decompressionOutputCallback = outputCallback;
        callBackRecord.decompressionOutputRefCon = (__bridge void *)self;
        //创建session
        status = VTDecompressionSessionCreate(kCFAllocatorDefault,
                                              description,
                                              NULL,
                                              (__bridge CFDictionaryRef)destinationPixelBufferAttributes,
                                              &callBackRecord,
                                              &session);
        //设置属性
        VTSessionSetProperty(description, kVTDecompressionPropertyKey_ThreadCount, (__bridge CFTypeRef)[NSNumber numberWithInt:1]);
        VTSessionSetProperty(description, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
    }
    else {
        NSLog(@"创建失败,status=%d",status);
    }
    return YES;
}

//获取nalu数据
- (void)decodeNalu:(uint8_t *)frame withSize:(uint32_t)frameSize {
    int nalu_type = (frame[4] & 0x1F);//用于判断nalu类型
    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]) {
                //解码I帧
                [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:
            //P/B帧
            if([self initH264Decoder]) {
                //解码P/B帧
                [self decode:frame withSize:frameSize];
            }
            break;
    }
}

//解码
- (CVPixelBufferRef)decode:(uint8_t *)frame withSize:(uint32_t)frameSize {
    CVPixelBufferRef outputPixelBuffer = NULL;
    CMBlockBufferRef blockBuffer = NULL;
    //创建blockBuffer
    OSStatus status = CMBlockBufferCreateWithMemoryBlock(NULL,
                                                        (void *)frame,
                                                        frameSize,
                                                        kCFAllocatorNull,
                                                        NULL,
                                                        0,
                                                        frameSize,
                                                        FALSE,
                                                        &blockBuffer);
    if(status==kCMBlockBufferNoErr) {
        CMSampleBufferRef sampleBuffer = NULL;
        const size_t sampleSizeArray[] = {frameSize};
        //创建sampleBuffer
        status = CMSampleBufferCreateReady(kCFAllocatorDefault,
                                           blockBuffer,
                                           description,
                                           1,
                                           0,
                                           NULL,
                                           1,
                                           sampleSizeArray,
                                           &sampleBuffer);
        if (status==kCMBlockBufferNoErr && sampleBuffer) {
            VTDecodeFrameFlags flags = 0;
            VTDecodeInfoFlags flagOut = 0;
            //解码
            OSStatus status = VTDecompressionSessionDecodeFrame(session,
                                                                sampleBuffer,
                                                                flags,
                                                                &outputPixelBuffer,
                                                                &flagOut);
            if (status==kVTInvalidSessionErr) {
                NSLog(@"无效session");
            }
            else if (status==kVTVideoDecoderBadDataErr) {
                NSLog(@"解码失败(Bad data),status=%d",status);
            }
            else if (status!=noErr) {
                NSLog(@"解码失败,status=%d",status);
            }
            CFRelease(sampleBuffer);
        }
        CFRelease(blockBuffer);
    }
    return outputPixelBuffer;
}

@end
ViewController.m
    //创建解码对象
    _decoder = [[DecodeH264 alloc] init];
    _decoder.delegate = self;
//编码回调
- (void)gotSpsPps:(NSData *)sps pps:(NSData *)pps {
    const char bytes[] = "\x00\x00\x00\x01";//起始码
    size_t length = (sizeof bytes) - 1;
    NSData *ByteHeader = [NSData dataWithBytes:bytes length:length];
    //sps
    NSMutableData *h264Data = [[NSMutableData alloc] init];
    [h264Data appendData:ByteHeader];
    [h264Data appendData:sps];
    [_decoder decodeNalu:(uint8_t *)[h264Data bytes] withSize:(uint32_t)h264Data.length];
    //pps
    [h264Data resetBytesInRange:NSMakeRange(0, [h264Data length])];
    [h264Data setLength:0];
    [h264Data appendData:ByteHeader];
    [h264Data appendData:pps];
    [_decoder decodeNalu:(uint8_t *)[h264Data bytes] withSize:(uint32_t)h264Data.length];
}

//编码回调
- (void)gotEncodedData:(NSData *)data {
    const char bytes[] = "\x00\x00\x00\x01";//起始码
    size_t length = (sizeof bytes) - 1;
    NSData *ByteHeader = [NSData dataWithBytes:bytes length:length];
    NSMutableData *h264Data = [[NSMutableData alloc] init];
    [h264Data appendData:ByteHeader];
    [h264Data appendData:data];
    [_decoder decodeNalu:(uint8_t *)[h264Data bytes] withSize:(uint32_t)h264Data.length];
}

//解码回调
- (void)displayDecodedFrame:(CVImageBufferRef)imageBuffer {
    NSLog(@"decode success");
    CVPixelBufferRelease(imageBuffer);
}

解码后的CVImageBufferRef可以通过OpenGLES处理渲染。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容

  • 用到的组件 1、通过CocoaPods安装 2、第三方类库安装 3、第三方服务 友盟社会化分享组件 友盟用户反馈 ...
    SunnyLeong阅读 14,621评论 1 180
  • 在保证视频图像质量的前提下,HEVC通过增加一定的计算复杂度,可以实现码流在H.264/AVC的基础上降低50%。...
    加刘景长阅读 7,881评论 0 6
  • 有一种喜欢,叫我爱你。 认识你八年了哇(没想到这么久了),初见你于微时,那时方知道了什么是明目皓齿灿若星辰,什么是...
    小爪纸阅读 293评论 0 1
  • Runloop学习 | 目录 ||: ------------- || 1 什么是...
    不多满阅读 293评论 0 0
  • 亲爱的秀秀,当你读到这封信的时候,请不要惊讶,我是来自七年后的你。或许你会觉得有点不可思议,但请你认真阅读此信,因...
    一十七掌阅读 464评论 0 0