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

J'ai eu beaucoup de mal à comprendre comment utiliser le framework vidéo accéléré par le matériel D'Apple pour décompresser un flux vidéo H. 264. Après quelques semaines, je l'ai compris et je voulais partager un exemple complet car je ne pouvais pas en trouver un.

Mon but est de donner un exemple complet et instructif de Video Toolbox introduit dans WWDC ' 14 session 513 . Mon code ne sera pas compilé ou exécuté car il doit être intégré à un flux H. 264 élémentaire (comme une vidéo lue à partir d'un fichier ou streamed à partir de online etc) et doit être modifié en fonction du cas spécifique.

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

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

55
demandé sur Bobjt 2015-04-08 23:44:16

5 réponses

Concepts:

NALUs: NALUs sont simplement un morceau de données de longueur variable qui a un en-tête de code de démarrage NALU 0x00 00 00 01 YY où les 5 premiers bits de YY vous indiquent quel type de Nalu il s'agit 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 simplement les bits pertinents.) Je liste ce que tous ces types sont dans la méthode NSString * const naluTypesStrings[], mais vous n'avez pas besoin de savoir ce qu'ils sont tous.

Paramètres: Votre le 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 Sequence Parameter Set (SPS) et Picture Parameter Set (PPS) et ils ont chacun leur propre numéro de type NALU. Vous n'avez pas besoin de savoir ce que les paramètres signifient, le décodeur sait quoi faire avec eux.

Format de flux H. 264: dans la plupart des flux H. 264, vous recevrez avec un ensemble initial de paramètres PPS et SPS suivi d'un cadre i (alias cadre IDR ou cadre affleurant) NALU. Ensuite, vous recevrez plusieurs NALUs de trame P (peut - être quelques dizaines), puis un autre ensemble de paramètres (qui peuvent être les mêmes que les paramètres initiaux) et un cadre i, plus de cadres P, etc. les cadres i sont beaucoup plus grands que les cadres P. Conceptuellement, vous pouvez penser à l'image i comme une image entière de la vidéo, et les images P ne sont que les modifications qui ont été apportées à cette image i, jusqu'à ce que vous receviez le prochain i cadre.

Procédure:

  1. Générez des NALUs individuels à partir de votre flux H. 264. Je ne peux pas afficher le 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 ("data" dans le graphique est "frame" dans mon code suivant), mais votre cas peut et sera probablement différent. Avec quoi je travaillais Ma méthode receivedRawVideoFrame: est appelée à chaque fois que je reçois un cadre (uint8_t *frame) qui fut l'un des 2 types. Dans le diagramme, ceux 2 types de cadre sont les 2 grandes boîtes violettes.

  2. Créez un CMVideoFormatDescriptionRef à partir de vos SPS et PPS NALUs avec CMVideoFormatDescriptionCreateFromh264parametersets () . Vous ne pouvez pas afficher d'images sans le faire en premier. Le SPS et le PPS peuvent ressembler à un fouillis de nombres, mais VTD sait quoi faire avec eux. Tout ce que vous devez savoir est que CMVideoFormatDescriptionRef est une description des données vidéo., comme Largeur / Hauteur, type de format(kCMPixelFormat_32BGRA, kCMVideoCodecType_H264 etc.), rapport d'aspect, espace colorimétrique etc. Votre décodeur conservera les paramètres jusqu'à ce qu'un nouvel ensemble arrive (parfois les paramètres sont renvoyés régulièrement même s'ils n'ont pas changé).

  3. Re-package votre IDR et non-IDR cadre NALUs selon le format" AVCC".{[39] } cela signifie supprimer les codes de démarrage NALU et les remplacer par un en-tête de 4 octets qui indique la longueur du Nalu. Vous n'avez pas besoin de le faire pour le NALUs SPS et PPS. (Notez que l'en-tête de longueur NALU de 4 octets est en big-endian, donc si vous avez une valeur UInt32 Elle doit être échangée en octets avant de copier dans le CMBlockBuffer en utilisant CFSwapInt32. Je le fais dans mon code avec l'appel de fonction htonl.)

  4. Empaquetez les trames IDR et non-IDR Nalu dans CMBlockBuffer.{[39] } ne le faites pas avec le paramètre SPS PPS NALUs. Tout ce que vous devez savoir à propos de CMBlockBuffers est qu'ils sont une méthode pour envelopper des blocs arbitraires de données dans les médias de base. (Toutes les données vidéo compressées dans un pipeline vidéo sont encapsulées ce.)

  5. Empaquetez le CMBlockBuffer dans CMSampleBuffer. Tout ce que vous devez savoir à propos de CMSampleBuffers, c'est qu'ils enveloppent notre CMBlockBuffers, avec d'autres informations (ici, ce serait la CMVideoFormatDescription et CMTime, si CMTime est utilisé).

  6. Créez un vtdecompressionsessionref et alimentez les tampons d'échantillons dans VTDecompressionSessionDecodeFrame (). Vous pouvez également utiliser AVSampleBufferDisplayLayer et sa méthode enqueueSampleBuffer: et vous n'aurez pas besoin D'utiliser VTDecompSession. Il est plus simple de mis en place, mais ne lancera pas d'erreurs si quelque chose ne va pas comme VTD.

  7. Dans le rappel VTDecompSession, utilisez le CVImageBufferRef résultant pour afficher l'image vidéo. si vous avez besoin de convertir votre CVImageBuffer en UIImage, consultez 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émarrage 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 choses si vous travaillez avec 3.

  • Si vous voulez savoir plus sur les NALUs, j'ai trouvé cette réponse pour être très utile. Dans mon cas, j'ai trouvé que je n'avais pas besoin d'ignorer les octets "prévention de l'émulation" comme décrit, donc j'ai personnellement sauté cette étape mais vous devrez peut-être savoir à ce sujet.

  • Si votre VTDecompressionSession génère une erreur numéro (comme -12909) recherchez le code d'erreur dans votre projet XCode. Trouvez le framework VideoToolbox dans votre navigateur de projet, ouvrez-le et trouvez les vterrors d'en-tête.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 inclure 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 que vous puissiez imprimer quel type de cadre NALU vous recevez. Si vous savez ce que tous ces types signifient, bon pour vous, vous en savez plus sur H. 264 que moi:) mon code ne gère 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 nouveaux paramètres. (Vous n'avez pas besoin de le recréer chaque {[140] } fois que vous recevez des paramètres, à peu près sûr.)

Si vous voulez définir attributs pour la destination CVPixelBuffer, lire sur CoreVideo PixelBufferAttributes valeurs et les mettre 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);
}

Maintenant, cette méthode est appelée chaque fois que VTD décompresse n'importe quelle image que vous lui avez envoyée. Cette méthode est appelée même s'il y a 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 le sampleBuffer au VTD pour être 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, assurez - vous d'initialiser le calque comme ceci, 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];
}
150
répondu Olivia Stork 2018-09-11 14:19:15

Si vous ne trouvez pas les codes D'erreur VTD dans le framework, j'ai décidé de les inclure ici. (Encore une fois, toutes ces erreurs et plus peuvent être trouvées dans le VideoToolbox.framework lui-même dans le navigateur de projet, dans le fichier VTErrors.h.)

Vous obtiendrez l'un de ces codes d'erreur soit dans le rappel de trame de décodage VTD, soit lorsque vous créez votre session VTD si vous avez fait quelque chose de incorrect.

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,
14
répondu Olivia Stork 2018-09-11 14:16:12

Un bon exemple rapide d'une grande partie de cela peut être trouvé dans la bibliothèque Avios de Josh Baker: https://github.com/tidwall/Avios

Notez Qu'Avios s'attend actuellement à ce que L'utilisateur gère les données de chunking aux codes de démarrage NAL, mais gère le décodage des données à partir de ce point.

La bibliothèque RTMP basée sur Swift HaishinKit (anciennement "LF"), qui a sa propre implémentation de décodage, y compris une analyse Nalu plus robuste, vaut également le détour: https://github.com/shogo4405/lf.swift

9
répondu leppert 2017-03-31 15:36:31

En plus de VTErrors ci-dessus, j'ai pensé qu'il valait la peine d'ajouter des erreurs CMFormatDescription, CMBlockBuffer, CMSampleBuffer que vous pouvez 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
répondu Jetdog 2015-06-08 17:49:17

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

if (_formatDesc) {
    CFRelease(_formatDesc);
    _formatDesc = NULL;
}
1
répondu Kris Dude 2018-01-30 03:07:23