MPEG4 流的 CMVideoFormatDescription 扩展

CMVideoFormatDescription extensions for MPEG4 streams

我已经成功解码和播放 H264 视频,但是我在处理 MPEG4 视频时遇到困难。

它需要什么 CMVideoFormatDescription 扩展?尝试创建 VTDecompressionSession 时出现 -8971 错误 (codecExtensionNotFoundErr)。

这就是我创建 VideoFormatDescription 的方式

OSStatus success = CMVideoFormatDescriptionCreate(kCFAllocatorDefault,
                                                  self.mediaCodec,
                                                  message.frameSize.width,
                                                  message.frameSize.height,
                                                  NULL,
                                                  &mediaDescriptor);

我假设我需要指定一个 CFDictionaryRef,而不是那个 NULL,但是我不知道它应该包含什么。有什么想法吗?

经过许多痛苦和折磨,我终于成功了。

我需要提供至少包含 kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms 键值的 CFDictionaryRef。此键的值也必须是 CFDictionaryRef。对于 H264 类型,这是在 CMVideoFormatDescriptionCreateFromH264ParameterSets 内部创建的,看起来像这样:

avcC = <014d401e ffe10016 674d401e 9a660a0f ff350101 01400000 fa000013 88010100 0468ee3c 80>

但是对于MPEG4类型,您需要自己创建。最终结果应如下所示:

esds = <00000000 038081e6 00000003 8081e611 00000000 00000000 058081e5 060102>

现在创建它的方法对我来说仍然很模糊,但是它以某种方式起作用了。我受到 this link 的启发。这是代码:

- (CMFormatDescriptionRef)createFormatDescriptorFromMPEG4Message:(MessageContainer *)message {
    CMVideoFormatDescriptionRef mediaDescriptor = NULL;
    NSData *esdsData = [self newESDSFromData:message.frameData];

    CFMutableDictionaryRef esdsDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 1,
                                                                      &kCFTypeDictionaryKeyCallBacks,
                                                                      &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(esdsDictionary, CFSTR("esds"), (__bridge const void *)(esdsData));

    NSDictionary *dictionary = @{(__bridge NSString *)kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms : (__bridge NSDictionary *)esdsDictionary};

    OSStatus status = CMVideoFormatDescriptionCreate(kCFAllocatorDefault,
                                                     self.mediaCodec,
                                                     message.frameSize.width,
                                                     message.frameSize.height,
                                                     (__bridge CFDictionaryRef)dictionary,
                                                     &mediaDescriptor);
    if (status) {
        NSLog(@"CMVideoFormatDesciprionCreate failed with %zd", status);
    }

    return mediaDescriptor;
}


- (NSData *)newESDSFromData:(NSData *)data {
    NSInteger dataLength = data.length;

    int full_size = 3 + 5 + 13 + 5 + dataLength + 3;

    // ES_DescrTag data + DecoderConfigDescrTag + data + DecSpecificInfoTag + size + SLConfigDescriptor
    int config_size = 13 + 5 + dataLength;
    int padding = 12;

    int8_t *esdsInfo = calloc(full_size + padding, sizeof(int8_t));

    //Version
    esdsInfo[0] = 0;

    //Flags
    esdsInfo[1] = 0;
    esdsInfo[2] = 0;
    esdsInfo[3] = 0;

    //ES_DescrTag
    esdsInfo[4] |= 0x03;
    [self addMPEG4DescriptionLength:full_size
                          toPointer:esdsInfo + 5];

    //esid
    esdsInfo[8] = 0;
    esdsInfo[9] = 0;

    //Stream priority
    esdsInfo[10] = 0;

    //DecoderConfigDescrTag
    esdsInfo[11] = 0x03;

    [self addMPEG4DescriptionLength:config_size
                          toPointer:esdsInfo + 12];

    //Stream Type
    esdsInfo[15] = 0x11;

    //Buffer Size
    esdsInfo[16] = 0;
    esdsInfo[17] = 0;

    //Max bitrate
    esdsInfo[18] = 0;
    esdsInfo[19] = 0;
    esdsInfo[20] = 0;

    //Avg bitrate
    esdsInfo[21] = 0;
    esdsInfo[22] = 0;
    esdsInfo[23] = 0;

    //< DecSpecificInfoTag
    esdsInfo[24] |= 0x05;

    [self addMPEG4DescriptionLength:dataLength
                          toPointer:esdsInfo + 25];

    //SLConfigDescrTag
    esdsInfo[28] = 0x06;

    //Length
    esdsInfo[29] = 0x01;

    esdsInfo[30] = 0x02;

    NSData *esdsData = [NSData dataWithBytes:esdsInfo length:31 * sizeof(int8_t)];

    free(esdsInfo);
    return esdsData;
}

- (void)addMPEG4DescriptionLength:(NSInteger)length
                        toPointer:(int8_t *)ptr {
    for (int i = 3; i >= 0; i--) {
        uint8_t b = (length >> (i * 7)) & 0x7F;
        if (i != 0) {
            b |= 0x80;
        }

        ptr[3 - i] = b;
    }
}

消息容器是从服务器接收到的数据的简单包装器:

@interface MessageContainer : NSObject

@property (nonatomic) CGSize frameSize;
@property (nonatomic) NSData *frameData;

@end

其中 frameSize 是帧的大小(从服务器单独接收),frameData 是数据本身。