objective c H.264 वीडियो स्ट्रीम को डिक्रॉप करने के लिए VideoToolbox का उपयोग कैसे करें




objective-c ios8 (4)

मुझे H.264 वीडियो स्ट्रीम को डिकंप्रेस करने के लिए ऐप्पल के हार्डवेयर त्वरित वीडियो फ्रेमवर्क का उपयोग करने का तरीका पता लगाने में बहुत परेशानी थी। कुछ हफ्तों के बाद मैंने इसे समझ लिया और एक व्यापक उदाहरण साझा करना चाहता था क्योंकि मुझे कोई नहीं मिला।

मेरा लक्ष्य डब्ल्यूडब्ल्यूडीसी '14 सत्र 513 में पेश किए गए वीडियो टूलबॉक्स का एक संपूर्ण, निर्देशक उदाहरण देना है। मेरा कोड संकलित या चलाएगा क्योंकि इसे प्राथमिक H.264 स्ट्रीम (फ़ाइल से पढ़ने वाले वीडियो या ऑनलाइन से स्ट्रीम किए गए वीडियो) के साथ एकीकृत करने की आवश्यकता है और विशिष्ट मामले के आधार पर tweaked की आवश्यकता है।

मुझे यह उल्लेख करना चाहिए कि इस विषय को गुमराह करते समय मैंने जो कुछ सीखा, उसे छोड़कर मुझे वीडियो एन / डिकोडिंग के साथ बहुत कम अनुभव है। मुझे वीडियो प्रारूपों, पैरामीटर संरचना इत्यादि के बारे में सभी विवरण नहीं पता हैं, इसलिए मैंने केवल इतना ही शामिल किया है जो मुझे लगता है कि आपको क्या पता होना चाहिए।

मैं एक्सकोड 6.2 का उपयोग कर रहा हूं और आईओएस डिवाइस और आईओएस डिवाइस पर तैनात हैं जो आईओएस 8.1 और 8.2 चला रहे हैं।


CMVideoFormatDescriptionCreateFromH264 पैरामीटर सेट से पहले मेमोरी लीक को हटाने के लिए CMVideoFormatDescriptionCreateFromH264ParameterSets आपको निम्न जोड़ना चाहिए:

if (_formatDesc) {
    CFRelease(_formatDesc);
    _formatDesc = NULL;
}

ऊपर VTErrors के अलावा, मैंने सोचा कि यह Livy के उदाहरण की कोशिश करते समय आप सामना कर सकते हैं कि CMFormatDescription, CMBlockBuffer, CMSampleBuffer त्रुटियों को जोड़ने लायक है।

kCMFormatDescriptionError_InvalidParameter  = -12710,
kCMFormatDescriptionError_AllocationFailed  = -12711,
kCMFormatDescriptionError_ValueNotAvailable = -12718,

kCMBlockBufferNoErr                             = 0,
kCMBlockBufferStructureAllocationFailedErr      = -12700,
kCMBlockBufferBlockAllocationFailedErr          = -12701,
kCMBlockBufferBadCustomBlockSourceErr           = -12702,
kCMBlockBufferBadOffsetParameterErr             = -12703,
kCMBlockBufferBadLengthParameterErr             = -12704,
kCMBlockBufferBadPointerParameterErr            = -12705,
kCMBlockBufferEmptyBBufErr                      = -12706,
kCMBlockBufferUnallocatedBlockErr               = -12707,
kCMBlockBufferInsufficientSpaceErr              = -12708,

kCMSampleBufferError_AllocationFailed             = -12730,
kCMSampleBufferError_RequiredParameterMissing     = -12731,
kCMSampleBufferError_AlreadyHasDataBuffer         = -12732,
kCMSampleBufferError_BufferNotReady               = -12733,
kCMSampleBufferError_SampleIndexOutOfRange        = -12734,
kCMSampleBufferError_BufferHasNoSampleSizes       = -12735,
kCMSampleBufferError_BufferHasNoSampleTimingInfo  = -12736,
kCMSampleBufferError_ArrayTooSmall                = -12737,
kCMSampleBufferError_InvalidEntryCount            = -12738,
kCMSampleBufferError_CannotSubdivide              = -12739,
kCMSampleBufferError_SampleTimingInfoInvalid      = -12740,
kCMSampleBufferError_InvalidMediaTypeForOperation = -12741,
kCMSampleBufferError_InvalidSampleData            = -12742,
kCMSampleBufferError_InvalidMediaFormat           = -12743,
kCMSampleBufferError_Invalidated                  = -12744,
kCMSampleBufferError_DataFailed                   = -16750,
kCMSampleBufferError_DataCanceled                 = -16751,

अवधारणाओं:

एनयूएलएस: एनयूएल केवल अलग-अलग लंबाई के डेटा का एक हिस्सा है जिसमें एनयूएल स्टार्ट कोड हैडर 0x00 00 00 01 YY जहां 0x00 00 00 01 YY के पहले 5 बिट आपको बताते हैं कि यह किस प्रकार का एनयूएल है और इसलिए हेडर का किस प्रकार का डेटा है। (चूंकि आपको केवल पहले 5 बिट्स की आवश्यकता है, इसलिए मैं प्रासंगिक बिट्स प्राप्त करने के लिए YY & 0x1F का उपयोग करता हूं।) मैं सूचीबद्ध करता हूं कि ये सभी प्रकार NSString * const naluTypesStrings[] , लेकिन आपको यह जानने की आवश्यकता नहीं है कि वे क्या हैं सभी हैं।

पैरामीटर: आपके डिकोडर को पैरामीटर की आवश्यकता होती है, इसलिए यह जानता है कि H.264 वीडियो डेटा कैसे संग्रहीत किया जाता है। आपको सेट करने की आवश्यकता है अनुक्रम पैरामीटर सेट (एसपीएस) और पिक्चर पैरामीटर सेट (पीपीएस) और उनमें से प्रत्येक का अपना एनयूएल प्रकार संख्या है। आपको यह जानने की आवश्यकता नहीं है कि पैरामीटर का क्या अर्थ है, डिकोडर जानता है कि उनके साथ क्या करना है।

एच .264 स्ट्रीम प्रारूप: अधिकांश एच .264 धाराओं में, आपको पीपीएस और एसपीएस पैरामीटर के प्रारंभिक सेट के साथ एक फ्रेम (उर्फ आईडीआर फ्रेम या फ्लश फ्रेम) एनयूएल के बाद प्राप्त होगा। फिर आपको कई पी फ्रेम NALUs (शायद कुछ दर्जन या तो) प्राप्त होंगे, फिर पैरामीटर का एक और सेट (जो प्रारंभिक पैरामीटर के समान हो सकता है) और एक फ्रेम, अधिक पी फ्रेम, आदि। फ्रेम मैं फ्रेम से बहुत बड़ा है पी फ्रेम संकल्पनात्मक रूप से आप वीडियो की पूरी छवि के रूप में फ्रेम के बारे में सोच सकते हैं, और पी फ्रेम केवल उस फ्रेम में किए गए परिवर्तन हैं जिन्हें मैं फ्रेम करता हूं, जब तक आप अगली I फ्रेम प्राप्त नहीं करते।

प्रक्रिया:

  1. अपनी H.264 स्ट्रीम से अलग-अलग एनयूएल उत्पन्न करें। मैं इस चरण के लिए कोड नहीं दिखा सकता क्योंकि यह आपके द्वारा उपयोग किए जा रहे वीडियो स्रोत पर बहुत निर्भर करता है। मैंने यह ग्राफ़िक दिखाया कि मैं किसके साथ काम कर रहा था (ग्राफ़िक में "डेटा" मेरे निम्नलिखित कोड में "फ्रेम" है), लेकिन आपका मामला शायद और अलग हो सकता है। मेरी विधि receivedRawVideoFrame: uint8_t *frame receivedRawVideoFrame: हर बार मुझे फ्रेम ( uint8_t *frame ) प्राप्त होता है जो कि 2 प्रकारों में से एक था। आरेख में, वे 2 फ्रेम प्रकार 2 बड़े बैंगनी बक्से हैं।

  2. CMVideoFormatDescriptionCreateFromH264ParameterSets () के साथ अपने एसपीएस और पीपीएस NALUs से एक CMVideoFormatDescriptionRef बनाएं । आप इसे पहले किए बिना किसी भी फ्रेम प्रदर्शित नहीं कर सकते हैं। एसपीएस और पीपीएस संख्याओं की झटके की तरह लग सकते हैं, लेकिन वीटीडी जानता है कि उनके साथ क्या करना है। आपको केवल यह जानने की ज़रूरत है कि CMVideoFormatDescriptionRef डिस्क्रिप्शन CMVideoFormatDescriptionRef वीडियो डेटा का वर्णन है, जैसे चौड़ाई / ऊंचाई, प्रारूप प्रकार ( kCMPixelFormat_32BGRA , kCMVideoCodecType_H264 इत्यादि), पहलू अनुपात, रंग स्थान इत्यादि। आपका डिकोडर पैरामीटर पर तब तक रखेगा जब तक कोई नया सेट नहीं आ जाता (कभी-कभी पैरामीटर नियमित रूप से परेशान होते हैं, भले ही वे नहीं बदला हो)।

  3. "एवीसीसी" प्रारूप के अनुसार अपने आईडीआर और गैर-आईडीआर फ्रेम एनयूएल को दोबारा पैकेज करें। इसका मतलब है कि एनयूएल स्टार्ट कोड को हटा देना और उन्हें 4-बाइट हेडर के साथ बदलना जो एनयूएल की लंबाई बताता है। आपको एसपीएस और पीपीएस एनयूएल के लिए ऐसा करने की आवश्यकता नहीं है। (ध्यान दें कि 4-बाइट एनयूएल लम्बाई हेडर बड़े-एंडियन में है, इसलिए यदि आपके पास UInt32 मान है तो इसे CMBlockBuffer का उपयोग करके CFSwapInt32 कॉपी करने से पहले बाइट-स्वैप होना चाहिए। मैं इसे अपने कोड में htonl फ़ंक्शन कॉल के साथ करता htonl ।)

  4. CMBlockBuffer में आईडीआर और गैर-आईडीआर NALU फ्रेम पैकेज करें। एसपीएस पीपीएस पैरामीटर NALUs के साथ ऐसा मत करो। आपको केवल CMBlockBuffers बारे में जानने की आवश्यकता है कि वे कोर मीडिया में डेटा के मनमानी ब्लॉक को लपेटने का एक तरीका हैं। (एक वीडियो पाइपलाइन में कोई संपीड़ित वीडियो डेटा इसमें लपेटा गया है।)

  5. CMSampleBuffer को CMSampleBuffer में पैकेज करें। आपको CMSampleBuffers बारे में जानने की जरूरत है कि वे हमारे CMBlockBuffers को अन्य जानकारी के साथ CMBlockBuffers हैं (यहां CMVideoFormatDescription और CMTime , अगर CMTime का उपयोग किया जाता है)।

  6. VTDecompressionSessionRef बनाएं और नमूना बफर को VTDecompressionSessionDecodeFrame () में फ़ीड करें। वैकल्पिक रूप से, आप AVSampleBufferDisplayLayer और इसके enqueueSampleBuffer: उपयोग कर सकते हैं enqueueSampleBuffer: विधि और आपको VTDecompSession का उपयोग करने की आवश्यकता नहीं होगी। सेट अप करना आसान है, लेकिन अगर वीटीडी की तरह कुछ गलत हो जाता है तो त्रुटियों को फेंक नहीं देगा।

  7. VTDecompSession कॉलबैक में, वीडियो फ्रेम प्रदर्शित करने के लिए परिणामी CVImageBufferRef का उपयोग करें। यदि आपको अपने CVImageBuffer को UIImage कनवर्ट करने की आवश्यकता है, तो मेरे उत्तर को here ।

अन्य नोट:

  • एच .264 धाराएं बहुत भिन्न हो सकती हैं। जो मैंने सीखा, उससे एनयूएल कोड कोड हेडर कभी-कभी 3 बाइट्स ( 0x00 00 01 ) और कभी-कभी 4 ( 0x00 00 00 01 ) शुरू होता है। मेरा कोड 4 बाइट्स के लिए काम करता है; यदि आप 3 के साथ काम कर रहे हैं तो आपको कुछ चीजों को बदलने की आवश्यकता होगी।

  • यदि आप एनयूएल के बारे में और जानना चाहते हैं, तो मुझे यह जवाब बहुत उपयोगी साबित हुआ। मेरे मामले में, मैंने पाया कि मुझे वर्णित "अनुकरण रोकथाम" बाइट्स को अनदेखा करने की आवश्यकता नहीं थी, इसलिए मैंने व्यक्तिगत रूप से उस चरण को छोड़ दिया लेकिन आपको इसके बारे में जानने की आवश्यकता हो सकती है।

  • यदि आपका VTDecompression सत्र एक त्रुटि संख्या आउटपुट करता है (जैसे -12 9 0 9 ) आपके एक्सकोड प्रोजेक्ट में त्रुटि कोड देखें। अपने प्रोजेक्ट नेविगेटर में VideoToolbox ढांचे को ढूंढें, इसे खोलें और हेडर VTErrors.h ढूंढें। यदि आप इसे नहीं ढूंढ पा रहे हैं, तो मैंने नीचे दिए गए सभी त्रुटि कोडों को किसी अन्य उत्तर में भी शामिल किया है।

कोड उदाहरण:

तो आइए कुछ वैश्विक चर घोषित करके और वीटी फ्रेमवर्क (वीटी = वीडियो टूलबॉक्स) सहित शुरू करें।

#import <VideoToolbox/VideoToolbox.h>

@property (nonatomic, assign) CMVideoFormatDescriptionRef formatDesc;
@property (nonatomic, assign) VTDecompressionSessionRef decompressionSession;
@property (nonatomic, retain) AVSampleBufferDisplayLayer *videoLayer;
@property (nonatomic, assign) int spsSize;
@property (nonatomic, assign) int ppsSize;

निम्नलिखित सरणी का उपयोग केवल इसलिए किया जाता है ताकि आप प्रिंट कर सकें कि आप किस प्रकार के एनयूएल फ्रेम प्राप्त कर रहे हैं। यदि आप जानते हैं कि इन सभी प्रकारों का क्या मतलब है, आपके लिए अच्छा है, तो आप मुझसे H.264 के बारे में अधिक जानते हैं :) मेरा कोड केवल 1, 5, 7 और 8 को संभालता है।

NSString * const naluTypesStrings[] =
{
    @"0: Unspecified (non-VCL)",
    @"1: Coded slice of a non-IDR picture (VCL)",    // P frame
    @"2: Coded slice data partition A (VCL)",
    @"3: Coded slice data partition B (VCL)",
    @"4: Coded slice data partition C (VCL)",
    @"5: Coded slice of an IDR picture (VCL)",      // I frame
    @"6: Supplemental enhancement information (SEI) (non-VCL)",
    @"7: Sequence parameter set (non-VCL)",         // SPS parameter
    @"8: Picture parameter set (non-VCL)",          // PPS parameter
    @"9: Access unit delimiter (non-VCL)",
    @"10: End of sequence (non-VCL)",
    @"11: End of stream (non-VCL)",
    @"12: Filler data (non-VCL)",
    @"13: Sequence parameter set extension (non-VCL)",
    @"14: Prefix NAL unit (non-VCL)",
    @"15: Subset sequence parameter set (non-VCL)",
    @"16: Reserved (non-VCL)",
    @"17: Reserved (non-VCL)",
    @"18: Reserved (non-VCL)",
    @"19: Coded slice of an auxiliary coded picture without partitioning (non-VCL)",
    @"20: Coded slice extension (non-VCL)",
    @"21: Coded slice extension for depth view components (non-VCL)",
    @"22: Reserved (non-VCL)",
    @"23: Reserved (non-VCL)",
    @"24: STAP-A Single-time aggregation packet (non-VCL)",
    @"25: STAP-B Single-time aggregation packet (non-VCL)",
    @"26: MTAP16 Multi-time aggregation packet (non-VCL)",
    @"27: MTAP24 Multi-time aggregation packet (non-VCL)",
    @"28: FU-A Fragmentation unit (non-VCL)",
    @"29: FU-B Fragmentation unit (non-VCL)",
    @"30: Unspecified (non-VCL)",
    @"31: Unspecified (non-VCL)",
};

अब यह वह जगह है जहां सभी जादू होता है।

-(void) receivedRawVideoFrame:(uint8_t *)frame withSize:(uint32_t)frameSize isIFrame:(int)isIFrame
{
    OSStatus status;

    uint8_t *data = NULL;
    uint8_t *pps = NULL;
    uint8_t *sps = NULL;

    // I know what my H.264 data source's NALUs look like so I know start code index is always 0.
    // if you don't know where it starts, you can use a for loop similar to how i find the 2nd and 3rd start codes
    int startCodeIndex = 0;
    int secondStartCodeIndex = 0;
    int thirdStartCodeIndex = 0;

    long blockLength = 0;

    CMSampleBufferRef sampleBuffer = NULL;
    CMBlockBufferRef blockBuffer = NULL;

    int nalu_type = (frame[startCodeIndex + 4] & 0x1F);
    NSLog(@"~~~~~~~ Received NALU Type \"%@\" ~~~~~~~~", naluTypesStrings[nalu_type]);

    // if we havent already set up our format description with our SPS PPS parameters, we
    // can't process any frames except type 7 that has our parameters
    if (nalu_type != 7 && _formatDesc == NULL)
    {
        NSLog(@"Video error: Frame is not an I Frame and format description is null");
        return;
    }

    // NALU type 7 is the SPS parameter NALU
    if (nalu_type == 7)
    {
        // find where the second PPS start code begins, (the 0x00 00 00 01 code)
        // from which we also get the length of the first SPS code
        for (int i = startCodeIndex + 4; i < startCodeIndex + 40; i++)
        {
            if (frame[i] == 0x00 && frame[i+1] == 0x00 && frame[i+2] == 0x00 && frame[i+3] == 0x01)
            {
                secondStartCodeIndex = i;
                _spsSize = secondStartCodeIndex;   // includes the header in the size
                break;
            }
        }

        // find what the second NALU type is
        nalu_type = (frame[secondStartCodeIndex + 4] & 0x1F);
        NSLog(@"~~~~~~~ Received NALU Type \"%@\" ~~~~~~~~", naluTypesStrings[nalu_type]);
    }

    // type 8 is the PPS parameter NALU
    if(nalu_type == 8)
    {
        // find where the NALU after this one starts so we know how long the PPS parameter is
        for (int i = _spsSize + 4; i < _spsSize + 30; i++)
        {
            if (frame[i] == 0x00 && frame[i+1] == 0x00 && frame[i+2] == 0x00 && frame[i+3] == 0x01)
            {
                thirdStartCodeIndex = i;
                _ppsSize = thirdStartCodeIndex - _spsSize;
                break;
            }
        }

        // allocate enough data to fit the SPS and PPS parameters into our data objects.
        // VTD doesn't want you to include the start code header (4 bytes long) so we add the - 4 here
        sps = malloc(_spsSize - 4);
        pps = malloc(_ppsSize - 4);

        // copy in the actual sps and pps values, again ignoring the 4 byte header
        memcpy (sps, &frame[4], _spsSize-4);
        memcpy (pps, &frame[_spsSize+4], _ppsSize-4);

        // now we set our H264 parameters
        uint8_t*  parameterSetPointers[2] = {sps, pps};
        size_t parameterSetSizes[2] = {_spsSize-4, _ppsSize-4};

        status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, 
                                                (const uint8_t *const*)parameterSetPointers, 
                                                parameterSetSizes, 4, 
                                                &_formatDesc);

        NSLog(@"\t\t Creation of CMVideoFormatDescription: %@", (status == noErr) ? @"successful!" : @"failed...");
        if(status != noErr) NSLog(@"\t\t Format Description ERROR type: %d", (int)status);

        // See if decomp session can convert from previous format description 
        // to the new one, if not we need to remake the decomp session.
        // This snippet was not necessary for my applications but it could be for yours
        /*BOOL needNewDecompSession = (VTDecompressionSessionCanAcceptFormatDescription(_decompressionSession, _formatDesc) == NO);
         if(needNewDecompSession)
         {
             [self createDecompSession];
         }*/

        // now lets handle the IDR frame that (should) come after the parameter sets
        // I say "should" because that's how I expect my H264 stream to work, YMMV
        nalu_type = (frame[thirdStartCodeIndex + 4] & 0x1F);
        NSLog(@"~~~~~~~ Received NALU Type \"%@\" ~~~~~~~~", naluTypesStrings[nalu_type]);
    }

    // create our VTDecompressionSession.  This isnt neccessary if you choose to use AVSampleBufferDisplayLayer
    if((status == noErr) && (_decompressionSession == NULL))
    {
        [self createDecompSession];
    }

    // type 5 is an IDR frame NALU.  The SPS and PPS NALUs should always be followed by an IDR (or IFrame) NALU, as far as I know
    if(nalu_type == 5)
    {
        // find the offset, or where the SPS and PPS NALUs end and the IDR frame NALU begins
        int offset = _spsSize + _ppsSize;
        blockLength = frameSize - offset;
        data = malloc(blockLength);
        data = memcpy(data, &frame[offset], blockLength);

        // replace the start code header on this NALU with its size.
        // AVCC format requires that you do this.  
        // htonl converts the unsigned int from host to network byte order
        uint32_t dataLength32 = htonl (blockLength - 4);
        memcpy (data, &dataLength32, sizeof (uint32_t));

        // create a block buffer from the IDR NALU
        status = CMBlockBufferCreateWithMemoryBlock(NULL, data,  // memoryBlock to hold buffered data
                                                    blockLength,  // block length of the mem block in bytes.
                                                    kCFAllocatorNull, NULL,
                                                    0, // offsetToData
                                                    blockLength,   // dataLength of relevant bytes, starting at offsetToData
                                                    0, &blockBuffer);

        NSLog(@"\t\t BlockBufferCreation: \t %@", (status == kCMBlockBufferNoErr) ? @"successful!" : @"failed...");
    }

    // NALU type 1 is non-IDR (or PFrame) picture
    if (nalu_type == 1)
    {
        // non-IDR frames do not have an offset due to SPS and PSS, so the approach
        // is similar to the IDR frames just without the offset
        blockLength = frameSize;
        data = malloc(blockLength);
        data = memcpy(data, &frame[0], blockLength);

        // again, replace the start header with the size of the NALU
        uint32_t dataLength32 = htonl (blockLength - 4);
        memcpy (data, &dataLength32, sizeof (uint32_t));

        status = CMBlockBufferCreateWithMemoryBlock(NULL, data,  // memoryBlock to hold data. If NULL, block will be alloc when needed
                                                    blockLength,  // overall length of the mem block in bytes
                                                    kCFAllocatorNull, NULL,
                                                    0,     // offsetToData
                                                    blockLength,  // dataLength of relevant data bytes, starting at offsetToData
                                                    0, &blockBuffer);

        NSLog(@"\t\t BlockBufferCreation: \t %@", (status == kCMBlockBufferNoErr) ? @"successful!" : @"failed...");
    }

    // now create our sample buffer from the block buffer,
    if(status == noErr)
    {
        // here I'm not bothering with any timing specifics since in my case we displayed all frames immediately
        const size_t sampleSize = blockLength;
        status = CMSampleBufferCreate(kCFAllocatorDefault,
                                      blockBuffer, true, NULL, NULL,
                                      _formatDesc, 1, 0, NULL, 1,
                                      &sampleSize, &sampleBuffer);

        NSLog(@"\t\t SampleBufferCreate: \t %@", (status == noErr) ? @"successful!" : @"failed...");
    }

    if(status == noErr)
    {
        // set some values of the sample buffer's attachments
        CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
        CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
        CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);

        // either send the samplebuffer to a VTDecompressionSession or to an AVSampleBufferDisplayLayer
        [self render:sampleBuffer];
    }

    // free memory to avoid a memory leak, do the same for sps, pps and blockbuffer
    if (NULL != data)
    {
        free (data);
        data = NULL;
    }
}

निम्न विधि आपके वीटीडी सत्र बनाता है। जब भी आपको नए पैरामीटर मिलते हैं तो इसे मनोरंजन करें। (आपको पैरामीटर प्राप्त होने पर हर बार इसे फिर से बनाना नहीं है, निश्चित रूप से।)

यदि आप गंतव्य CVPixelBuffer लिए विशेषताओं को सेट करना चाहते हैं, तो CoreVideo PixelBufferAttributes मानों पर पढ़ें और उन्हें NSDictionary *destinationImageBufferAttributes

-(void) createDecompSession
{
    // make sure to destroy the old VTD session
    _decompressionSession = NULL;
    VTDecompressionOutputCallbackRecord callBackRecord;
    callBackRecord.decompressionOutputCallback = decompressionSessionDecodeFrameCallback;

    // this is necessary if you need to make calls to Objective C "self" from within in the callback method.
    callBackRecord.decompressionOutputRefCon = (__bridge void *)self;

    // you can set some desired attributes for the destination pixel buffer.  I didn't use this but you may
    // if you need to set some attributes, be sure to uncomment the dictionary in VTDecompressionSessionCreate
    NSDictionary *destinationImageBufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                                      [NSNumber numberWithBool:YES],
                                                      (id)kCVPixelBufferOpenGLESCompatibilityKey,
                                                      nil];

    OSStatus status =  VTDecompressionSessionCreate(NULL, _formatDesc, NULL,
                                                    NULL, // (__bridge CFDictionaryRef)(destinationImageBufferAttributes)
                                                    &callBackRecord, &_decompressionSession);
    NSLog(@"Video Decompression Session Create: \t %@", (status == noErr) ? @"successful!" : @"failed...");
    if(status != noErr) NSLog(@"\t\t VTD ERROR type: %d", (int)status);
}

अब जब भी वीटीडी आपके द्वारा भेजे गए किसी भी फ्रेम को डिकंप्रेस कर देता है तो इस विधि को बुलाया जाता है। यदि कोई त्रुटि है या फ्रेम गिरा दिया गया है तो भी यह विधि कॉल हो जाती है।

void decompressionSessionDecodeFrameCallback(void *decompressionOutputRefCon,
                                             void *sourceFrameRefCon,
                                             OSStatus status,
                                             VTDecodeInfoFlags infoFlags,
                                             CVImageBufferRef imageBuffer,
                                             CMTime presentationTimeStamp,
                                             CMTime presentationDuration)
{
    THISCLASSNAME *streamManager = (__bridge THISCLASSNAME *)decompressionOutputRefCon;

    if (status != noErr)
    {
        NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
        NSLog(@"Decompressed error: %@", error);
    }
    else
    {
        NSLog(@"Decompressed sucessfully");

        // do something with your resulting CVImageBufferRef that is your decompressed frame
        [streamManager displayDecodedFrame:imageBuffer];
    }
}

यह वह जगह है जहां हम वास्तव में वीडीडी को डीकोड करने के लिए नमूना बफर भेजते हैं।

- (void) render:(CMSampleBufferRef)sampleBuffer
{
    VTDecodeFrameFlags flags = kVTDecodeFrame_EnableAsynchronousDecompression;
    VTDecodeInfoFlags flagOut;
    NSDate* currentTime = [NSDate date];
    VTDecompressionSessionDecodeFrame(_decompressionSession, sampleBuffer, flags,
                                      (void*)CFBridgingRetain(currentTime), &flagOut);

    CFRelease(sampleBuffer);

    // if you're using AVSampleBufferDisplayLayer, you only need to use this line of code
    // [videoLayer enqueueSampleBuffer:sampleBuffer];
}

यदि आप AVSampleBufferDisplayLayer का उपयोग कर रहे हैं, तो इस तरह की परत को, AVSampleBufferDisplayLayer या किसी अन्य init विधि में देखने के लिए सुनिश्चित करें।

-(void) viewDidLoad
{
    // create our AVSampleBufferDisplayLayer and add it to the view
    videoLayer = [[AVSampleBufferDisplayLayer alloc] init];
    videoLayer.frame = self.view.frame;
    videoLayer.bounds = self.view.bounds;
    videoLayer.videoGravity = AVLayerVideoGravityResizeAspect;

    // set Timebase, you may need this if you need to display frames at specific times
    // I didn't need it so I haven't verified that the timebase is working
    CMTimebaseRef controlTimebase;
    CMTimebaseCreateWithMasterClock(CFAllocatorGetDefault(), CMClockGetHostTimeClock(), &controlTimebase);

    //videoLayer.controlTimebase = controlTimebase;
    CMTimebaseSetTime(self.videoLayer.controlTimebase, kCMTimeZero);
    CMTimebaseSetRate(self.videoLayer.controlTimebase, 1.0);

    [[self.view layer] addSublayer:videoLayer];
}

यदि आप ढांचे में वीटीडी त्रुटि कोड नहीं पा रहे हैं, तो मैंने उन्हें यहां शामिल करने का निर्णय लिया है। (फिर से, इन सभी त्रुटियों और अधिक को VideoToolbox.framework के अंदर प्रोजेक्ट नेविगेटर में फ़ाइल VTErrors.h में पाया जा सकता है।)

आप इन त्रुटि कोडों में से एक को VTD डिकोड फ्रेम कॉलबैक में प्राप्त करेंगे या जब आप अपना VTD सत्र बनाते हैं तो आपने गलत तरीके से कुछ किया है।

kVTPropertyNotSupportedErr              = -12900,
kVTPropertyReadOnlyErr                  = -12901,
kVTParameterErr                         = -12902,
kVTInvalidSessionErr                    = -12903,
kVTAllocationFailedErr                  = -12904,
kVTPixelTransferNotSupportedErr         = -12905, // c.f. -8961
kVTCouldNotFindVideoDecoderErr          = -12906,
kVTCouldNotCreateInstanceErr            = -12907,
kVTCouldNotFindVideoEncoderErr          = -12908,
kVTVideoDecoderBadDataErr               = -12909, // c.f. -8969
kVTVideoDecoderUnsupportedDataFormatErr = -12910, // c.f. -8970
kVTVideoDecoderMalfunctionErr           = -12911, // c.f. -8960
kVTVideoEncoderMalfunctionErr           = -12912,
kVTVideoDecoderNotAvailableNowErr       = -12913,
kVTImageRotationNotSupportedErr         = -12914,
kVTVideoEncoderNotAvailableNowErr       = -12915,
kVTFormatDescriptionChangeNotSupportedErr   = -12916,
kVTInsufficientSourceColorDataErr       = -12917,
kVTCouldNotCreateColorCorrectionDataErr = -12918,
kVTColorSyncTransformConvertFailedErr   = -12919,
kVTVideoDecoderAuthorizationErr         = -12210,
kVTVideoEncoderAuthorizationErr         = -12211,
kVTColorCorrectionPixelTransferFailedErr    = -12212,
kVTMultiPassStorageIdentifierMismatchErr    = -12213,
kVTMultiPassStorageInvalidErr           = -12214,
kVTFrameSiloInvalidTimeStampErr         = -12215,
kVTFrameSiloInvalidTimeRangeErr         = -12216,
kVTCouldNotFindTemporalFilterErr        = -12217,
kVTPixelTransferNotPermittedErr         = -12218,




video-toolbox