基于iOS的网络音视频实时传输系统(五)- 使用VideoToolbox硬解码H264


下载


GitHub:

client 端:https://github.com/AmoAmoAmo/Smart_Device_Client

server端:https://github.com/AmoAmoAmo/Smart_Device_Server

另还写了一份macOS版的server,但是目前还有一些问题,有兴趣的去看看吧:https://github.com/AmoAmoAmo/Server_Mac



使用VideoToolbox硬解码H264。

关于这一部分,由于是第一次使用,便不班门弄斧,

在参考文章中列举了不少说得很详细的博客。

关于最终的效果,可以参考第一篇的文章



部分过程


初始化编码器session

- (void)initVideoToolBox {
    if (!mDecodeSession) {
        // 把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);
        if(status == noErr) {
            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);
            
            VTDecompressionOutputCallbackRecord callBackRecord;
            callBackRecord.decompressionOutputCallback = didDecompress;
            callBackRecord.decompressionOutputRefCon = NULL;
            
            status = VTDecompressionSessionCreate(kCFAllocatorDefault,
                                                  mFormatDescription,
                                                  NULL, attrs,
                                                  &callBackRecord,
                                                  &mDecodeSession);
            CFRelease(attrs);
        } else {
            NSLog(@"IOS8VT: reset decoder session failed status = %d", (int)status);
        } 
    }
}

回调函数

void didDecompress(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef pixelBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ){
    
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    *outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);
}


获取H.264开始码,并开始相应的工作

// 一收到数据就调用这个方法,用来刷新屏幕
- (void)updateFrame
{
    // 同步 --》 顺序执行
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        // 替换头字节长度
        uint32_t nalSize = (uint32_t)(packetSize - 4);
        uint32_t *pNalSize = (uint32_t *)packetBuffer;
        *pNalSize = CFSwapInt32HostToBig(nalSize);
        
        // 在buffer的前面填入代表长度的int    // 以00 00 00 01分割之后的下一个字节就是--NALU类型--
        CVPixelBufferRef pixelBuffer = NULL;
        int nalType = packetBuffer[4] & 0x1F;  // NALU类型  & 0001  1111
        switch (nalType) {
            case 0x05:
//                NSLog(@"*********** IDR frame, I帧");
                [self initVideoToolBox]; // 当读入IDR帧的时候初始化VideoToolbox,并开始同步解码
                pixelBuffer = [self decode]; // 解码得到的CVPixelBufferRef会传入OpenGL ES类进行解析渲染
                break;
            case 0x07:
//                NSLog(@"*********** SPS");
                mSPSSize = packetSize - 4;
                mSPS = malloc(mSPSSize);
                memcpy(mSPS, packetBuffer + 4, mSPSSize);
                break;
            case 0x08:
//                NSLog(@"*********** PPS");
                mPPSSize = packetSize - 4;
                mPPS = malloc(mPPSSize);
                memcpy(mPPS, packetBuffer + 4, mPPSSize);
                break;
            default:
//                NSLog(@"*********** B/P frame"); // P帧?
                pixelBuffer = [self decode];
                
                break;
        }
        
        if(pixelBuffer) {
            // 把解码后的数据block传给viewController
            self.returnDataBlock(pixelBuffer);
            CVPixelBufferRelease(pixelBuffer);
            
        }
    });
}

用CMBlockBuffer(未压缩的图像数据) 把NALUnit包装起来

CMBlockBufferRef blockBuffer = NULL;
        OSStatus status  = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,
                                                              (void*)packetBuffer, packetSize,
                                                              kCFAllocatorNull,
                                                              NULL, 0, packetSize,
                                                              0, &blockBuffer);

创建CMSampleBuffer

把原始码流包装成CMSampleBuffer(存放一个或者多个压缩或未压缩的媒体文件)

CMSampleBufferRef sampleBuffer = NULL;
            const size_t sampleSizeArray[] = {packetSize};
            status = CMSampleBufferCreateReady(kCFAllocatorDefault,
                                               blockBuffer,        // 用CMBlockBuffer把NALUnit包装起来
                                               mFormatDescription, // 把SPS和PPS包装成CMVideoFormatDescription
                                               1, 0, NULL, 1, sampleSizeArray,
                                               &sampleBuffer);

解码并显示

VTDecodeFrameFlags flags = 0;
                VTDecodeInfoFlags flagOut = 0;
                // 默认是同步操作。
                // 调用didDecompress,返回后再回调
                OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(mDecodeSession,
                                                                          sampleBuffer,
                                                                          flags,
                                                                          &outputPixelBuffer,
                                                                          &flagOut);

上一步即可获得编码后的数据 outputPixelBuffer,再将它传给OpenGL渲染显示解码的结果。

关于OpenGL渲染显示在下一篇文章里。



参考文章:


1.  http://www.jianshu.com/p/6dfe49b5dab8

2.  http://www.jianshu.com/p/da18b979aeec




相关文章


基于iOS的网络音视频实时传输系统(一)- 前言

基于iOS的网络音视频实时传输系统(二)- 捕获音视频数据

基于iOS的网络音视频实时传输系统(三)- VideoToolbox编码音视频数据为H264、AAC

基于iOS的网络音视频实时传输系统(四)- 自定义socket协议(TCP、UDP)

基于iOS的网络音视频实时传输系统(五)- 使用VideoToolbox硬解码H264

基于iOS的网络音视频实时传输系统(六)- AudioQueue播放音频,OpenGL渲染显示图像


猜你喜欢

转载自blog.csdn.net/a997013919/article/details/78215544