ios CMBlockBuffer से h264 निकाले




video encoding (2)

मैं अब कुछ समय के लिए इस के साथ संघर्ष कर रहा हूं, और आखिरकार मुझे सब कुछ पता चल गया है

फ़ंक्शन CMBlockBufferGetDataPointer आपको आवश्यक सभी डेटा तक पहुंच देता है, लेकिन कुछ ऐसे कुछ बहुत स्पष्ट चीजें हैं जो आपको एक प्राथमिक स्ट्रीम में कनवर्ट करने की आवश्यकता नहीं हैं।

एवीसीसी बनाम एनेक्स बी प्रारूप

सीएमबीब्लॉकबफ़र में डेटा एवीसीसी प्रारूप में जमा होता है, जबकि प्राथमिक धाराएं आमतौर पर अनुलग्नक बी विनिर्देशों का अनुसरण कर रही हैं ( यहां दो प्रारूपों का उत्कृष्ट अवलोकन है)। एवीसीसी प्रारूप में, 4 पहले बाइट्स में एनएएल यूनिट (एच 264 पैकेट के लिए एक और शब्द) की लंबाई शामिल है। आपको इस हेडर को 4 बाइट स्टार्ट कोड के साथ प्रतिस्थापित करना होगा: 0x00 0x00 0x00 0x01, जो एनेक्स बी प्राथमिक स्ट्रीम (3 बाइट संस्करण 0x00 0x00 0x01 भी ठीक काम करता है) में एनएएल इकाइयों के बीच विभाजक के रूप में कार्य करता है।

एकल सीएमब्लॉकबफर में कई एनएएल इकाइयां

अगले नहीं बहुत स्पष्ट बात यह है कि एक ही सीएमब्लॉकबफ़र कभी-कभी कई एनएएल इकाइयां रखेंगे एप्पल एक अतिरिक्त एनएएल यूनिट (एसईआई) जोड़ता है जिसमें प्रत्येक आई-फ़्रेम एनएएल इकाई (जिसे आईडीआर भी कहा जाता है) के लिए मेटाडेटा होता है। शायद यही कारण है कि आप एकल CMBlockBuffer ऑब्जेक्ट में कई बफ़र्स क्यों देख रहे हैं। हालांकि, CMBlockBufferGetDataPointer फ़ंक्शन आपको सभी डेटा तक पहुंच के साथ एक पॉइंटर प्रदान करता है। कहा जा रहा है कि, कई एनएएल इकाइयों की मौजूदगी AVCC हेडर के रूपांतरण को जटिल बनाता है। अब आपको वास्तव में अगले एनएएल यूनिट को खोजने के लिए एवीसीसी हैडर में लम्बाई के मूल्य को पढ़ना होगा, और जब तक आप बफर के अंत तक नहीं पहुंच जाते तब तक हेडर परिवर्तित करना जारी रखें।

बिग एंडियन बनाम लिटिल एंडियन

अगले नहीं बहुत स्पष्ट बात यह है कि एवीसीसी हैडर बिग एंडियन प्रारूप में संग्रहीत किया जाता है, और आईओएस मूल रूप से लिटिल एंडियन मूल रूप से है इसलिए जब आप CFSwapInt32BigToHost हेडर में निहित लंबाई मान पढ़ रहे हैं तो उसे पहले CFSwapInt32BigToHost फ़ंक्शन पर पास करें।

एसपीएस और पीपीएस एनएएल इकाइयां

अंतिम नहीं बहुत स्पष्ट बात यह है कि CMBlockBuffer के अंदर डेटा में पैरामीटर एनएएल इकाइयों एसपीएस और पीपीएस शामिल नहीं है, जिसमें प्रोफ़ाइल, स्तर, रिज़ॉल्यूशन, फ़्रेम दर जैसे डिकोडर के लिए कॉन्फ़िगरेशन पैरामीटर शामिल हैं। इन्हें नमूना बफ़र के प्रारूप विवरण में मेटाडेटा के रूप में संग्रहित किया जाता है और इसे CMVideoFormatDescriptionGetH264ParameterSetAtIndex फ़ंक्शन के माध्यम से एक्सेस किया जा सकता है। ध्यान दें कि आपको भेजने से पहले इन एनएएल इकाइयों को शुरू करना होगा एसपीएस और पीपीएस एनएएल इकाइयों को हर नए फ्रेम के साथ भेजने की ज़रूरत नहीं है। एक डिकोडर को केवल उन्हें एक बार पढ़ने की जरूरत होती है, लेकिन समय-समय पर उन्हें फिर से फिर से करना आम बात है, उदाहरण के लिए हर नई आई-फ्रेम एनएएल इकाई से पहले

कोड उदाहरण

नीचे इन सभी चीजों को ध्यान में रखते हुए एक कोड का उदाहरण दिया गया है।

static void videoFrameFinishedEncoding(void *outputCallbackRefCon,
                                       void *sourceFrameRefCon,
                                       OSStatus status,
                                       VTEncodeInfoFlags infoFlags,
                                       CMSampleBufferRef sampleBuffer) {
    // Check if there were any errors encoding
    if (status != noErr) {
        NSLog(@"Error encoding video, err=%lld", (int64_t)status);
        return;
    }

    // In this example we will use a NSMutableData object to store the
    // elementary stream.
    NSMutableData *elementaryStream = [NSMutableData data];


    // Find out if the sample buffer contains an I-Frame.
    // If so we will write the SPS and PPS NAL units to the elementary stream.
    BOOL isIFrame = NO;
    CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, 0);
    if (CFArrayGetCount(attachmentsArray)) {
        CFBooleanRef notSync;
        CFDictionaryRef dict = CFArrayGetValueAtIndex(attachmentsArray, 0);
        BOOL keyExists = CFDictionaryGetValueIfPresent(dict,
                                                       kCMSampleAttachmentKey_NotSync,
                                                       (const void **)&notSync);
        // An I-Frame is a sync frame
        isIFrame = !keyExists || !CFBooleanGetValue(notSync);
    }

    // This is the start code that we will write to
    // the elementary stream before every NAL unit
    static const size_t startCodeLength = 4;
    static const uint8_t startCode[] = {0x00, 0x00, 0x00, 0x01};

    // Write the SPS and PPS NAL units to the elementary stream before every I-Frame
    if (isIFrame) {
        CMFormatDescriptionRef description = CMSampleBufferGetFormatDescription(sampleBuffer);

        // Find out how many parameter sets there are
        size_t numberOfParameterSets;
        CMVideoFormatDescriptionGetH264ParameterSetAtIndex(description,
                                                           0, NULL, NULL,
                                                           &numberOfParameterSets,
                                                           NULL);

        // Write each parameter set to the elementary stream
        for (int i = 0; i < numberOfParameterSets; i++) {
            const uint8_t *parameterSetPointer;
            size_t parameterSetLength;
            CMVideoFormatDescriptionGetH264ParameterSetAtIndex(description,
                                                               i,
                                                               &parameterSetPointer,
                                                               &parameterSetLength,
                                                               NULL, NULL);

            // Write the parameter set to the elementary stream
            [elementaryStream appendBytes:startCode length:startCodeLength];
            [elementaryStream appendBytes:parameterSetPointer length:parameterSetLength];
        }
    }

    // Get a pointer to the raw AVCC NAL unit data in the sample buffer
    size_t blockBufferLength;
    uint8_t *bufferDataPointer = NULL;
    CMBlockBufferGetDataPointer(CMSampleBufferGetDataBuffer(sampleBuffer),
                                0,
                                NULL,
                                &blockBufferLength,
                                (char **)&bufferDataPointer);

    // Loop through all the NAL units in the block buffer
    // and write them to the elementary stream with
    // start codes instead of AVCC length headers
    size_t bufferOffset = 0;
    static const int AVCCHeaderLength = 4;
    while (bufferOffset < blockBufferLength - AVCCHeaderLength) {
        // Read the NAL unit length
        uint32_t NALUnitLength = 0;
        memcpy(&NALUnitLength, bufferDataPointer + bufferOffset, AVCCHeaderLength);
        // Convert the length value from Big-endian to Little-endian
        NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);
        // Write start code to the elementary stream
        [elementaryStream appendBytes:startCode length:startCodeLength];
        // Write the NAL unit without the AVCC length header to the elementary stream
        [elementaryStream appendBytes:bufferDataPointer + bufferOffset + AVCCHeaderLength
                               length:NALUnitLength];
        // Move to the next NAL unit in the block buffer
        bufferOffset += AVCCHeaderLength + NALUnitLength;
    }
}   

मैं डिवाइस कैमरा द्वारा कब्जा किए गए कचरे फ्रेमों को संपीड़ित करने के लिए एप्पल वीडियो टूल बॉक्स (आईओएस) का उपयोग कर रहा हूं।

मेरे कॉलबैक को CMSampleBufferRef ऑब्जेक्ट के साथ कॉल किया जा रहा है जिसमें CMBlockBuffer है।

CMBlockBuffer ऑब्जेक्ट में H264 प्राथमिक स्ट्रीम होता है लेकिन मुझे प्राथमिक धारा में एक सूचक प्राप्त करने का कोई तरीका नहीं मिला।

जब मैंने CMSampleBufferRef ऑब्जेक्ट को कंसोल में मुद्रित किया I:

(lldb) po blockBufferRef
CMBlockBuffer 0x1701193e0 totalDataLength: 4264 retainCount: 1 allocator: 0x1957c2c80 subBlockCapacity: 2
 [0] 4264 bytes @ offset 128 Buffer Reference:
    CMBlockBuffer 0x170119350 totalDataLength: 4632 retainCount: 1 allocator: 0x1957c2c80 subBlockCapacity: 2
     [0] 4632 bytes @ offset 0 Memory Block 0x10295c000, 4632 bytes (custom V=0 A=0x0 F=0x18498bb44 R=0x0)

ऐसा लगता है कि CMBlockBuffer ऑब्जेक्ट को मैं पॉइंट को प्राप्त करने में कामयाब रहा हूँ और एक और CMBlockBuferRef (4632 बाइट्स) contaning है जो पहुंच योग्य नहीं है

क्या कोई भी पोस्ट कर सकता है कि कैसे H264 ग्यारहवीं धारा का उपयोग करें?

धन्यवाद!


एक उत्कृष्ट जवाब के लिए एंटोन धन्यवाद! अपने स्विफ्ट-आधारित परियोजनाओं में सीधे यहां चर्चा की गई अवधारणाओं का उपयोग करने में रुचि रखने वाले लोगों के लिए आपके समाधान का एक सरल स्विफ्ट-पोर्ट लगा रहा हूं।

public func didEncodeFrame(frame: CMSampleBuffer)
{
    print ("Received encoded frame in delegate...")

    //----AVCC to Elem stream-----//
    var elementaryStream = NSMutableData()

    //1. check if CMBuffer had I-frame
    var isIFrame:Bool = false
    let attachmentsArray:CFArray = CMSampleBufferGetSampleAttachmentsArray(frame, false)!
    //check how many attachments
    if ( CFArrayGetCount(attachmentsArray) > 0 ) {
        let dict = CFArrayGetValueAtIndex(attachmentsArray, 0)
        let dictRef:CFDictionaryRef = unsafeBitCast(dict, CFDictionaryRef.self)
        //get value
        let value = CFDictionaryGetValue(dictRef, unsafeBitCast(kCMSampleAttachmentKey_NotSync, UnsafePointer<Void>.self))
        if ( value != nil ){
            print ("IFrame found...")
            isIFrame = true
        }
    }

    //2. define the start code
    let nStartCodeLength:size_t = 4
    let nStartCode:[UInt8] = [0x00, 0x00, 0x00, 0x01]

    //3. write the SPS and PPS before I-frame
    if ( isIFrame == true ){
        let description:CMFormatDescriptionRef = CMSampleBufferGetFormatDescription(frame)!
        //how many params
        var numParams:size_t = 0
        CMVideoFormatDescriptionGetH264ParameterSetAtIndex(description, 0, nil, nil, &numParams, nil)

        //write each param-set to elementary stream
        print("Write param to elementaryStream ", numParams)
        for i in 0..<numParams {
            var parameterSetPointer:UnsafePointer<UInt8> = nil
            var parameterSetLength:size_t = 0
            CMVideoFormatDescriptionGetH264ParameterSetAtIndex(description, i, &parameterSetPointer, &parameterSetLength, nil, nil)
            elementaryStream.appendBytes(nStartCode, length: nStartCodeLength)
            elementaryStream.appendBytes(parameterSetPointer, length: unsafeBitCast(parameterSetLength, Int.self))
        }
    }

    //4. Get a pointer to the raw AVCC NAL unit data in the sample buffer
    var blockBufferLength:size_t = 0
    var bufferDataPointer: UnsafeMutablePointer<Int8> = nil
    CMBlockBufferGetDataPointer(CMSampleBufferGetDataBuffer(frame)!, 0, nil, &blockBufferLength, &bufferDataPointer)
    print ("Block length = ", blockBufferLength)

    //5. Loop through all the NAL units in the block buffer
    var bufferOffset:size_t = 0
    let AVCCHeaderLength:Int = 4
    while (bufferOffset < (blockBufferLength - AVCCHeaderLength) ) {
        // Read the NAL unit length
        var NALUnitLength:UInt32 =  0
        memcpy(&NALUnitLength, bufferDataPointer + bufferOffset, AVCCHeaderLength)
        //Big-Endian to Little-Endian
        NALUnitLength = CFSwapInt32(NALUnitLength)
        if ( NALUnitLength > 0 ){
            print ( "NALUnitLen = ", NALUnitLength)
            // Write start code to the elementary stream
            elementaryStream.appendBytes(nStartCode, length: nStartCodeLength)
            // Write the NAL unit without the AVCC length header to the elementary stream
            elementaryStream.appendBytes(bufferDataPointer + bufferOffset + AVCCHeaderLength, length: Int(NALUnitLength))
            // Move to the next NAL unit in the block buffer
            bufferOffset += AVCCHeaderLength + size_t(NALUnitLength);
            print("Moving to next NALU...")
        }
    }
    print("Read completed...")
}






h.264