web-dev-qa-db-fra.com

Comment utiliser VideoToolbox pour décompresser un flux vidéo H.264

J'ai eu beaucoup de difficulté à comprendre comment utiliser la structure vidéo à accélération matérielle d'Apple pour décompresser un flux vidéo H.264. Après quelques semaines, j'ai compris et je voulais partager un exemple exhaustif car je ne pouvais pas en trouver un.

Mon objectif est de donner un exemple complet et instructif de Video Toolbox introduit dans WWDC '14 session 51 . Mon code ne sera pas compilé ni exécuté, car il doit être intégré à un flux H.264 élémentaire (comme une vidéo lue à partir d'un fichier ou diffusé en ligne, etc.) et doit être modifié en fonction des cas.

Je dois mentionner que j'ai très peu d'expérience en vidéo en/décodage, sauf ce que j'ai appris en googlant le sujet. Je ne connais pas tous les détails concernant les formats vidéo, la structure des paramètres, etc., donc je n'ai inclus que ce que je pense que vous devez savoir.

J'utilise XCode 6.2 et je l'ai déployé sur des appareils iOS exécutant iOS 8.1 et 8.2.

60
Olivia Stork

Concepts:

NALUs: Les NALU sont simplement un bloc de données de longueur variable ayant un en-tête de code de début NALU 0x00 00 00 01 YY où les 5 premiers bits de YY vous indiquent le type de Ceci est NALU et donc quel type de données suit l'en-tête. (Puisque vous n’avez besoin que des 5 premiers bits, j’utilise YY & 0x1F pour obtenir uniquement les bits pertinents.) J’énumère tous ces types dans la méthode NSString * const naluTypesStrings[], mais vous ne le faites pas. besoin de savoir ce qu'ils sont tous.

Paramètres: Votre décodeur a besoin de paramètres pour savoir comment les données vidéo H.264 sont stockées. Les 2 que vous devez définir sont Jeu de paramètres de séquence (SPS) et Jeu de paramètres d'image (PPS) et ils ont chacun leur propre numéro de type NALU. Vous n'avez pas besoin de savoir ce que signifient les paramètres, le décodeur sait quoi en faire.

Format de flux H.264: Dans la plupart des flux H.264, vous recevrez un ensemble initial de PPS et des paramètres SPS suivis d'une trame i (ou cadre IDR ou cadre) NALU . Ensuite, vous recevrez plusieurs NALU de trames P (peut-être quelques dizaines environ), puis un autre jeu de paramètres (qui peuvent être identiques aux paramètres initiaux) et une trame i, plusieurs trames P, etc. Les trames i sont beaucoup plus grandes que P cadres. Conceptuellement, vous pouvez considérer l'image i comme une image complète de la vidéo. Les images P sont simplement les modifications qui ont été apportées à cette image i, jusqu'à ce que vous receviez l'image i suivante.

Procédure:

  1. Générez des NALU individuelles à partir de votre flux H.264. Je ne peux pas afficher de code pour cette étape car cela dépend beaucoup de la source vidéo que vous utilisez. J'ai fait ce graphique pour montrer ce avec quoi je travaillais ("données" dans le graphique est "cadre" dans mon code suivant), mais votre cas peut et sera probablement différent. What I was working with Ma méthode receivedRawVideoFrame: est appelée à chaque fois que je reçois une image (uint8_t *frame) qui était l'un des deux types. Dans le diagramme, ces 2 types de trame sont les 2 grandes boîtes violettes.

  2. Créez un CMVideoFormatDescriptionRef à partir de votre NPS SPS et PPS avec CMVideoFormatDescriptionCreateFromH264ParameterSets (). Vous ne pouvez afficher aucune image sans le faire au préalable. Le SPS et PPS peuvent ressembler à un fouillis de nombres, mais le VTD sait quoi faire avec eux. Tout ce que vous devez savoir, c’est que CMVideoFormatDescriptionRef est une description des données vidéo, telles que la largeur/hauteur, le type de format (kCMPixelFormat_32BGRA, kCMVideoCodecType_H264 etc.), les proportions , espace colorimétrique, etc. Votre décodeur conservera les paramètres jusqu’à l’arrivée d’un nouvel ensemble (des paramètres sont parfois renvoyés régulièrement, même s’ils n’ont pas été modifiés).

  3. Reconditionnez vos NALU de trame IDR et non-IDR conformément au format "AVCC". Cela signifie que vous devez supprimer les codes de début NALU et les remplacer par un en-tête de 4 octets indiquant la longueur de la NALU. Vous n'avez pas besoin de faire cela pour les NALU SPS et PPS. (Notez que l'en-tête de longueur NALU de 4 octets est en big-endian. Par conséquent, si vous avez une valeur UInt32, elle doit être remplacée par des octets avant d'être copiée dans le CMBlockBuffer à l'aide du code CFSwapInt32. Je le fais dans mon code avec l'appel de fonction htonl.)

  4. Regroupez les trames NALU IDR et non-IDR dans CMBlockBuffer. Ne procédez pas ainsi avec les NALUs du paramètre SPS PPS. Tout ce que vous devez savoir sur CMBlockBuffers, c’est qu’il s’agit d’une méthode permettant d’envelopper des blocs de données arbitraires dans le support principal. (Toutes les données vidéo compressées dans un pipeline vidéo sont encapsulées dans cela.)

  5. Paquetez le CMBlockBuffer dans CMSampleBuffer. Tout ce que vous avez besoin de savoir sur CMSampleBuffers consiste à envelopper notre CMBlockBuffers avec d'autres informations (ici ce serait le CMVideoFormatDescription et CMTime, si CMTime est utilisé).

  6. Créez un VTDecompressionSessionRef et insérez les tampons exemples dans la méthode VTDecompressionSessionDecodeFrame (). Vous pouvez également utiliser AVSampleBufferDisplayLayer et sa méthode enqueueSampleBuffer: sans avoir besoin d'utiliser VTDecompSession. . C'est plus simple à configurer, mais ne générera pas d'erreurs si quelque chose se passe mal comme VTD.

  7. Dans le rappel VTDecompSession, utilisez le CVImageBufferRef résultant pour afficher l'image vidéo. Si vous devez convertir votre CVImageBuffer en UIImage, voyez ma réponse à StackOverflow ici .

Autres notes:

  • Les flux H.264 peuvent varier beaucoup. D'après ce que j'ai appris, Les en-têtes de code de début NALU sont parfois de 3 octets (0x00 00 01) et parfois 4 (0x00 00 00 01). Mon code fonctionne pour 4 octets; vous devrez changer quelques petites choses si vous travaillez avec 3.

  • Si vous voulez en savoir plus sur les NAL, j’ai trouvé cette réponse très utile. Dans mon cas, j'ai constaté que je n'avais pas besoin d'ignorer les octets de "prévention de l'émulation" décrits, alors j'ai personnellement sauté cette étape, mais vous devrez peut-être en être informé.

  • Si votre VTDecompressionSession génère un numéro d'erreur (comme -12909), recherchez le code d'erreur dans votre projet XCode. Recherchez le framework VideoToolbox dans votre navigateur de projet, ouvrez-le et recherchez l'en-tête VTErrors.h. Si vous ne le trouvez pas, j'ai également inclus tous les codes d'erreur ci-dessous dans une autre réponse.

Exemple de code:

Commençons donc par déclarer certaines variables globales et en incluant le framework 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;

Le tableau suivant est uniquement utilisé pour vous permettre d’imprimer le type de cadre NALU que vous recevez. Si vous savez ce que tous ces types signifient, bon pour vous, vous en savez plus sur le H.264 que moi :) Mon code ne traite que les types 1, 5, 7 et 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)",
};

Maintenant c'est là que toute la magie se produit.

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

La méthode suivante crée votre session VTD. Recréez-le chaque fois que vous recevez nouveau paramètres. (Vous n'êtes pas obligé de le recréer tous les fois vous recevez les paramètres, à peu près sûr.)

Si vous souhaitez définir des attributs pour la destination CVPixelBuffer, lisez-le sur valeurs CoreVideo PixelBufferAttributes et mettez-les dans 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);
}

Désormais, cette méthode est appelée chaque fois que VTD décompresse une image que vous lui avez envoyée. Cette méthode est appelée même s’il ya une erreur ou si le cadre est supprimé.

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

C’est là que nous envoyons effectivement sampleBuffer au VTD pour qu’il soit décodé.

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

Si vous utilisez AVSampleBufferDisplayLayer, veillez à initialiser le calque de cette manière, dans viewDidLoad ou dans une autre méthode 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];
}
160
Olivia Stork

Si vous ne trouvez pas les codes d'erreur VTD dans la structure, j'ai décidé de les inclure simplement ici. (Encore une fois, toutes ces erreurs et d’autres peuvent être trouvées à l’intérieur du VideoToolbox.framework lui-même dans le navigateur de projet, dans le fichier VTErrors.h.)

Vous obtiendrez l'un de ces codes d'erreur dans le rappel de trame de décodage VTD ou lors de la création de votre session VTD si vous avez fait quelque chose de mal.

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,
18
Olivia Stork

Un bon exemple Swift peut être trouvé dans la bibliothèque Avios de Josh Baker: https://github.com/tidwall/Avios

Notez qu'Avios attend actuellement de l'utilisateur qu'il gère les données de segmentation aux codes de début NAL, mais gère le décodage des données à partir de ce moment.

La bibliothèque RTMP basée sur Swift _ HaishinKit (anciennement "LF"), qui possède sa propre implémentation de décodage, comprend une analyse NALU plus robuste: https://github.com/shogo4405 /lf.Swift

10
leppert

En plus de VTErrors ci-dessus, j'ai pensé qu'il valait la peine d'ajouter les erreurs CMFormatDescription, CMBlockBuffer, CMSampleBuffer que vous pourriez rencontrer en essayant l'exemple de 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,
4
Jetdog

@Livy pour supprimer les fuites de mémoire avant CMVideoFormatDescriptionCreateFromH264ParameterSets vous devriez ajouter ce qui suit:

if (_formatDesc) {
    CFRelease(_formatDesc);
    _formatDesc = NULL;
}
1
Kris Dude