Macos 使用Apple视频工具包进行H264解码

Macos 使用Apple视频工具包进行H264解码,macos,video-streaming,h.264,video-toolbox,Macos,Video Streaming,H.264,Video Toolbox,我正在尝试使用Apple视频工具箱和OpenH264的组合,让H264流媒体应用程序在各种平台上运行。有一个用例不起作用,我找不到任何解决方案。当源在运行MacOS High Sierra的2011 iMac上使用视频工具箱时,接收器是运行Big Sur的MacBook pro 在接收器上,解码图像约为3/4绿色。如果我在编码之前将图像缩小到原始图像的1/8左右,那么效果很好。如果我在MacBook上捕获帧,然后在iMac上的测试程序中运行完全相同的解码软件,那么它可以很好地解码。在Macboo

我正在尝试使用Apple视频工具箱和OpenH264的组合,让H264流媒体应用程序在各种平台上运行。有一个用例不起作用,我找不到任何解决方案。当源在运行MacOS High Sierra的2011 iMac上使用视频工具箱时,接收器是运行Big Sur的MacBook pro

在接收器上,解码图像约为3/4绿色。如果我在编码之前将图像缩小到原始图像的1/8左右,那么效果很好。如果我在MacBook上捕获帧,然后在iMac上的测试程序中运行完全相同的解码软件,那么它可以很好地解码。在Macbook上做同样的操作(测试程序的相同图像)再次显示3/4绿色。在速度较慢的Windows机器上接收OpenH264编码器时,我也遇到类似的问题。我怀疑这与时间处理有关,但我对H264的理解还不够透彻,无法解决这个问题。我注意到的一件事是解码调用返回时没有错误代码,但有70%的时间是空像素缓冲区

解码部分的“胆量”如下所示(根据GitHub上的演示修改)

注意:NAL块没有00 01分隔符,因为它们在具有显式长度字段的块中流动


解码在所有平台上都能很好地工作,编码流在OpenH264上也能很好地解码。

我终于找到了答案,所以我将把它留给子孙后代。事实证明,Video Toolkit decode函数期望将所有属于同一帧的NAL块复制到单个SampleBuffer中。较旧的Mac为应用程序提供了单个关键帧,这些关键帧被分割成单独的NAL块,然后应用程序通过网络单独发送。不幸的是,这意味着第一个NAL块将被处理,在可能的情况下不到图片的四分之一,其余的将被丢弃。您需要做的是找出哪些NAL是同一框架的一部分,并将它们捆绑在一起。不幸的是,这需要部分解析PPS和帧本身,这并不简单。非常感谢这里的帖子,它让我走上了正确的道路。

是的,可怕的绿色黏液。这是因为缺少色度数据。
void didDecompress(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef pixelBuffer, CMTime presentationTimeStamp, CMTime presentationDuration )
{
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    *outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);
}

 void initVideoDecodeToolBox ()
    {
        if (!decodeSession)
        {
            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
                                                                                  &formatDescription);
            if(status == noErr)
            {
                CFDictionaryRef attrs = NULL;
                const void *keys[] = { kCVPixelBufferPixelFormatTypeKey, kVTDecompressionPropertyKey_RealTime };
                uint32_t v = kCVPixelFormatType_32BGRA;
                const void *values[] = { CFNumberCreate(NULL, kCFNumberSInt32Type, &v), kCFBooleanTrue };
                attrs = CFDictionaryCreate(NULL, keys, values, 2, NULL, NULL);
                VTDecompressionOutputCallbackRecord callBackRecord;
                callBackRecord.decompressionOutputCallback = didDecompress;
                callBackRecord.decompressionOutputRefCon = NULL;
                status = VTDecompressionSessionCreate(kCFAllocatorDefault, formatDescription, NULL, attrs, &callBackRecord, &decodeSession);
                CFRelease(attrs);
            }
            else
            {
                NSLog(@"IOS8VT: reset decoder session failed status=%d", status);
            }
        }
    }

CVPixelBufferRef decode ( const char *NALBuffer, size_t NALSize )
    {
        CVPixelBufferRef outputPixelBuffer = NULL;
        if (decodeSession && formatDescription )
        {
            // The NAL buffer has been stripped of the NAL length data, so this has to be put back in
            MemoryBlock buf ( NALSize + 4);
            memcpy ( (char*)buf.getData()+4, NALBuffer, NALSize );
            *((uint32*)buf.getData()) = CFSwapInt32HostToBig ((uint32)NALSize);
            
            CMBlockBufferRef blockBuffer = NULL;
            OSStatus status  = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, buf.getData(), NALSize+4,kCFAllocatorNull,NULL, 0, NALSize+4, 0, &blockBuffer);
            
            if(status == kCMBlockBufferNoErr)
            {
                CMSampleBufferRef sampleBuffer = NULL;
                const size_t sampleSizeArray[] = {NALSize + 4};
                status = CMSampleBufferCreateReady(kCFAllocatorDefault,blockBuffer,formatDescription,1, 0, NULL, 1, sampleSizeArray,&sampleBuffer);
                
                if (status == kCMBlockBufferNoErr && sampleBuffer)
                {
                    VTDecodeFrameFlags flags = 0;VTDecodeInfoFlags flagOut = 0;
                    
                    // The default is synchronous operation.
                    // Call didDecompress and call back after returning.
                    OSStatus decodeStatus = VTDecompressionSessionDecodeFrame ( decodeSession, sampleBuffer, flags, &outputPixelBuffer, &flagOut );

                    if(decodeStatus != noErr)
                    {
                        DBG ( "decode failed status=" + String ( decodeStatus) );
                    }
                    CFRelease(sampleBuffer);
                }
                CFRelease(blockBuffer);
            }
        }
        return outputPixelBuffer;
    }