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



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



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



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



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

880   5  

5 ответов:

концепции:

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 IDR frame или flush frame) NALU. Затем вы получите несколько P-кадров NALUs (возможно, несколько десятков или около того), затем другой набор параметров (которые могут быть такими же, как и начальные параметры) и I-кадр, больше P-кадров и т. д. I кадры намного больше, чем P кадров. Концептуально вы можете думать о i-кадре как о целом изображении видео, а P-кадры-это только изменения, внесенные в этот I-кадр, пока вы не получите на следующий кадр.

процедура:

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

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

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

  4. упакуйте кадры IDR и non-IDR NALU в CMBlockBuffer. не делайте этого с параметром SPS PPS NALUs. Все, что вам нужно знать о 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.

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

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

Пример Кода:

Итак, давайте начнем с объявления некоторых глобальных переменных и включая фреймворк 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'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};

        // suggestion from @Kris Dude's 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'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;
    }
}

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

если вы хотите установить атрибуты для назначения CVPixelBuffer прочесть о CoreVideo PixelBufferAttributes values и положить их в 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 в ВТД, которая будет декодироваться.

- (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, обязательно инициализируйте слой таким образом, в viewDidLoad или внутри какого-либо другого метода инициализации.

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

Если вы не можете найти коды ошибок VTD в рамках, я решил просто включить их здесь. (Опять же, все эти ошибки и многое другое можно найти внутри VideoToolbox.framework сам в навигаторе проекта, в файле VTErrors.h.)

вы получите один из этих кодов ошибок либо в обратном вызове vtd decode frame, либо при создании сеанса 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,

хороший быстрый пример многого из этого можно найти в библиотеке Avios Джоша Бейкера:https://github.com/tidwall/Avios

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

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

в дополнение к VTErrors выше, я думал, что стоит добавить 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,

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

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

Comments

    Ничего не найдено.