Подтвердить что ты не робот

Как использовать VideoToolbox для распаковки видеопотока H.264

У меня было много проблем с выяснением того, как использовать ускоренную видеофрагму Apple Hardware для распаковки видеопотока H.264. Через несколько недель я понял это и хотел поделиться обширным примером, так как не смог найти его.

Моя цель - дать подробный, поучительный пример Video Toolbox, представленный в разделе WWDC '14 сеанс 513. Мой код не будет компилироваться или запускаться, так как он должен быть интегрирован с элементарным потоком H.264 (например, видео, прочитанным из файла или переданным из Интернета и т.д.), И его необходимо настроить в зависимости от конкретного случая.

Я должен упомянуть, что у меня очень мало опыта в видеообъявлении/декодировании, кроме того, что я узнал во время поиска темы. Я не знаю всех подробностей о форматах видео, структуре параметров и т.д., Поэтому я включил только то, что, как мне кажется, вам нужно знать.

Я использую XCode 6.2 и развернуты на устройствах iOS под управлением iOS 8.1 и 8.2.

4b9b3361

Ответ 1

Основные понятия:

NALUs: NALUs - это просто фрагмент данных переменной длины с заголовком начального кода NALU 0x00 00 00 01 YY где первые 5 бит YY указывают вам, какой тип NALU это и, следовательно, какой тип данных следует за заголовком. (Поскольку вам нужны только первые 5 бит, я использую YY & 0x1F чтобы просто получить соответствующие биты.) Я перечисляю все эти типы в методе NSString * const naluTypesStrings[], но вам не нужно знать, что они все.

Параметры: ваш декодер нуждается в параметрах, чтобы он знал, как хранятся видеоданные H.264. 2, которые вы должны установить, - это набор параметров последовательности (SPS) и набор параметров изображения (PPS), каждый из которых имеет свой номер типа NALU. Вам не нужно знать, что означают параметры, декодер знает, что с ними делать.

Формат потока H.264: в большинстве потоков H.264 вы получите исходный набор параметров PPS и SPS, за которым следует i-й кадр (aka frame IDR или флеш-кадр) NALU. Затем вы получите несколько PAL NALU (возможно, несколько десятков или около того), затем еще один набор параметров (которые могут совпадать с начальными параметрами) и i-фрейм, больше P-кадров и т.д. я -фрагменты намного больше, чем P кадров. Концептуально вы можете представить i-й кадр как целое изображение видео, а P-кадры - это только изменения, внесенные в этот i-й кадр, пока вы не получите следующий i-й кадр.

Процедура:

  1. Создайте отдельные NALU из потока H.264. Я не могу показать код для этого шага, поскольку он зависит от того, какой источник видео вы используете. Я сделал эту графику, чтобы показать, с чем я работал ("данные" на графике - "кадр" в моем следующем коде), но ваш случай может и, вероятно, будет отличаться. What I was working with Мой метод uint8_t *frame receivedRawVideoFrame: вызывается каждый раз, когда я получаю фрейм (uint8_t *frame), который был одним из двух типов. На диаграмме эти 2 типа кадров представляют собой 2 больших фиолетовых прямоугольника.

  2. Создайте CMVideoFormatDescriptionRef из ваших SPS и PPS NALU с помощью CMVideoFormatDescriptionCreateFromH264ParameterSets(). Вы не можете отображать кадры без этого. SPS и PPS могут выглядеть как беспорядок чисел, но VTD знает, что с ними делать. Все, что вам нужно знать, это то, что CMVideoFormatDescriptionRef - это описание видеоданных. Например, ширина/высота, тип формата (kCMPixelFormat_32BGRA, kCMVideoCodecType_H264 и т.д.), Соотношение сторон, цветовое пространство и т.д. Ваш декодер будет удерживать параметры до тех пор, пока не поступит новый набор (иногда параметры регулярно повторяются, даже если они не изменились).

  3. Повторно упакуйте свои NALU IDR и non-IDR в соответствии с форматом "AVCC". Это означает удаление начальных кодов NALU и их замену на 4-байтовый заголовок, который указывает длину NALU. Вам не нужно делать это для SPS и PPS NALU. (Обратите внимание, что 4-байтовый заголовок длины NALU имеет значение big-endian, поэтому, если у вас есть значение UInt32 он должен быть заменен байтом перед копированием в CMBlockBuffer с использованием CFSwapInt32. Я делаю это в своем коде с htonl функции htonl.)

  4. Пакет рамок IDR и non-IDR NALU в CMBlockBuffer. Не делайте этого с помощью NALU параметров SPS PPS. Все, что вам нужно знать о CMBlockBuffers это то, что они представляют собой способ обертывания произвольных блоков данных на основном носителе. (Все сжатые видеоданные в конвейере видео завернуты в это.)

  5. Поместите CMBlockBuffer в CMSampleBuffer. Все, что вам нужно знать о CMSampleBuffers это то, что они завершают нашу CMBlockBuffers другой информацией (здесь это будет CMVideoFormatDescription и CMTime, если используется CMTime).

  6. Создайте VTDecompressionSessionRef и подайте пробные буферы в VTDecompressionSessionDecodeFrame(). Кроме того, вы можете использовать AVSampleBufferDisplayLayer и его метод enqueueSampleBuffer: и вам не нужно будет использовать VTDecompSession. Это проще настроить, но не будет бросать ошибки, если что-то пойдет не так, как VTD.

  7. В обратном вызове VTDecompSession используйте полученный CVImageBufferRef для отображения видеокадра. Если вам нужно преобразовать CVImageBuffer в UIImage, см. Мой ответ StackOverflow здесь.

Другие примечания:

  • Потоки H.264 могут сильно различаться. Из того, что я узнал, стартовые заголовки NALU иногда имеют 3 байта (0x00 00 01), а иногда 4 (0x00 00 00 01). Мой код работает на 4 байта; вам нужно будет изменить несколько вещей, если вы работаете с 3.

  • Если вы хотите узнать больше о NALU, я нашел этот ответ очень полезным. В моем случае я обнаружил, что мне не нужно игнорировать байты предотвращения эмуляции, как описано, поэтому я лично пропустил этот шаг, но вам может понадобиться знать об этом.

  • Если ваш VTDecompressionSession выводит номер ошибки (например, -12909), найдите код ошибки в своем проекте XCode. Найдите инфраструктуру VideoToolbox в навигаторе проекта, откройте ее и найдите заголовок VTErrors.h. Если вы не можете найти его, я также включил все коды ошибок ниже в другой ответ.

Пример кода:

Поэтому начнем с объявления некоторых глобальных переменных и включая структуру VT (VT = Video Toolbox).

#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;

Следующий массив используется только для того, чтобы вы могли распечатать, какой тип кадра NALU вы получаете. Если вы знаете, что означают все эти типы, хорошо для вас, вы знаете больше о 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 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};

        // suggestion from @Kris Dude answer below
        if (_formatDesc) 
        {
            CFRelease(_formatDesc);
            _formatDesc = NULL;
        }

        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 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 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;
    }
}

Следующий метод создает сеанс VTD. Повторно создавайте его, когда вы получаете новые параметры. (Вам не нужно воссоздавать его каждый раз, когда вы получаете параметры, довольно уверен.)

Если вы хотите установить атрибуты для целевого 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);
}

Теперь этот метод вызывается каждый раз, когда VTD выполняется при распаковке любого кадра, который вы ему отправили. Этот метод вызывается, даже если есть ошибка или если кадр отбрасывается.

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];
    }
}

Здесь мы фактически отправляем sampleBuffer на VTD, который должен быть декодирован.

- (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 такой слой, в viewDidLoad или внутри какого-либо другого метода 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];
}

Ответ 2

Если вы не можете найти коды ошибок VTD в рамках, я решил просто включить их здесь. (Опять же, все эти ошибки и многое другое можно найти внутри самой 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,

Ответ 3

Хороший пример Swift из этого можно найти в библиотеке Josh Baker Avios: https://github.com/tidwall/Avios

Обратите внимание, что Avios в настоящее время ожидает, что пользователь будет обрабатывать данные канала в стартовых кодах NAL, но обрабатывает декодирование данных с этой точки вперед.

Также стоит посмотреть основанную на Swift библиотеку RTMP HaishinKit (ранее "LF" ), которая имеет свою собственную реализацию декодирования, включая более надежный разбор NALU: https://github.com/shogo4405/lf.swift

Ответ 4

В дополнение к VTErrors выше, я подумал, что стоит добавить ошибки CMFormatDescription, CMBlockBuffer, CMSampleBuffer, которые могут возникнуть при попытке использования примера Livy.

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,

Ответ 5

@Livy для удаления утечек памяти перед установкой CMVideoFormatDescriptionCreateFromH264ParameterSets необходимо добавить следующее:

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