AVAsset读MP4中H.264与判断CMSampleBuffer是否为关键帧问题

通过AVAssetReader配合AVAssetReaderTrackOutput,只需简单的几行语句可读取MP4文件中的H.264数据(假设视频为H.264编码)。读取过程中有几份CMSampleBuffer是相对有趣的,示例代码如下。

AVAssetReaderTrackOutput.copyNextSampleBuffer

经测试,整个MP4文件的视频包(SampleBuffer)一共出现5次buffer-level attachments信息,列举如下。

1、第一个:EditBoundary(P) = true

CMSampleBuffer 0x134d85df0 retainCount: 1 allocator: 0x1a1a20150
    invalid = NO
    dataReady = YES
    makeDataReadyCallback = 0x0
    makeDataReadyRefcon = 0x0
    buffer-level attachments:
        EditBoundary(P) = true
    formatDescription = (null)
    sbufToTrackReadiness = 0x0
    numSamples = 0
    sampleTimingArray[1] = {
        {
            PTS = {0/1 = 0.000}, 
            DTS = {INVALID}, 
            duration = {0/1 = 0.000}
        },
    }
    dataBuffer = 0x0

苹果文档里并无EditBoundary相关说明。

2、第二个:ResetDecoderBeforeDecoding(P) = true

CMSampleBuffer 0x134e57320 retainCount: 1 allocator: 0x1a1a20150
    invalid = NO
    dataReady = YES
    makeDataReadyCallback = 0x0
    makeDataReadyRefcon = 0x0
    buffer-level attachments:
        ResetDecoderBeforeDecoding(P) = true
    formatDescription = //...
    sbufToTrackReadiness = 0x0
    numSamples = 1
    sampleTimingArray[1] = {
        {
            PTS = {0/60000 = 0.000}, 
            DTS = {0/60000 = 0.000}, 
            duration = {2000/60000 = 0.033}
        },
    }
    sampleSizeArray[1] = {
        sampleSize = 176328,
    }
    dataBuffer = 0x134e57550

苹果文档关于ResetDecoderBeforeDecoding的说明如下:

kCMSampleBufferAttachmentKey_ResetDecoderBeforeDecoding
Indicates whether the sample buffer should be reset before decoding (type CFBoolean, default false).

This attachment is used at run time to indicate that a sample follows a break in decode sequence and that it is appropriate to reset the decoder before decoding this sample.

This attachment is not written to media files.

因此,ResetDecoderBeforeDecoding只是AVFoundation自行添加的信息。

3、从第三个视频包开始,不再包含buffer-level attachments数据,直到视频流结束前三个视频包时才有新的buffer-level attachments信息。不过,它们有sampleAttachmentsArray信息,这是前两个视频包所没有的,示例类似如下:

CMSampleBuffer 0x134d86260 retainCount: 1 allocator: 0x1a1a20150
    invalid = NO
    dataReady = YES
    makeDataReadyCallback = 0x0
    makeDataReadyRefcon = 0x0
    formatDescription = //...
    sbufToTrackReadiness = 0x0
    numSamples = 1
    sampleTimingArray[1] = {
        {
            PTS = {2000/60000 = 0.033}, 
            DTS = {2000/60000 = 0.033}, 
            duration = {2000/60000 = 0.033}},
    }
    sampleSizeArray[1] = {
        sampleSize = 28652,
    }
    sampleAttachmentsArray[1] = {
        sample 0:
            NotSync    = true
    }
    dataBuffer = 0x134d806f0

苹果文档对sampleAttachmentsArray中NotSync的说明如下:

kCMSampleAttachmentKey_NotSync
Indicates whether the sample is a sync sample (type CFBoolean, default false).

A sync sample, also known as a key frame or IDR (Instantaneous Decoding Refresh), can be decoded without requiring any previous samples to have been decoded. Samples following a sync sample also do not require samples prior to the sync sample to have been decoded. Samples are assumed to be sync samples by default — set the value for this key to kCFBooleanTrue for samples which should not be treated as sync samples.

This attachment is read from and written to media files.

上述资料表明,NotSync为true时表示非IDR帧。这令人感到疑惑,整个视频流都没看到NotSync = false的帧。

4、倒数第三个:DrainAfterDecoding(P) = true

CMSampleBuffer 0x134e8c740 retainCount: 1 allocator: 0x1a1a20150
    invalid = NO
    dataReady = YES
    makeDataReadyCallback = 0x0
    makeDataReadyRefcon = 0x0
    buffer-level attachments:
        DrainAfterDecoding(P) = true
    formatDescription = //...
    sbufToTrackReadiness = 0x0
    numSamples = 0
    dataBuffer = 0x0

苹果文档对kCMSampleBufferAttachmentKey_DrainAfterDecoding的说明如下:

kCMSampleBufferAttachmentKey_DrainAfterDecoding
Indicates whether the sample buffer should be drained after decoding type CFBoolean, default false).

This attachment is used at run time to indicate that a sample precedes a break in decode sequence and that it is appropriate to drain the decoder after decoding this sample.

This attachment is not written to media files.

5、倒数第二个:PostNotificationWhenConsumed(P)

CMSampleBuffer 0x134e8c940 retainCount: 1 allocator: 0x1a1a20150
    invalid = NO
    dataReady = YES
    makeDataReadyCallback = 0x0
    makeDataReadyRefcon = 0x0
    buffer-level attachments:
        PostNotificationWhenConsumed(P) = {type = mutable dict, count = 2,
            entries =>
                0 : {contents = "StartPresentationTimesStamp"} = {type = mutable dict, count = 4,
                    entries =>
                        0 : {contents = "flags"} = {value = +1, type = kCFNumberSInt32Type}
                        1 : {contents = "value"} = {value = +1334000, type = kCFNumberSInt64Type}
                        4 : {contents = "timescale"} = {value = +60000, type = kCFNumberSInt32Type}
                        5 : {contents = "epoch"} = {value = +0, type = kCFNumberSInt64Type}
                    }

            1 : {contents = "EndPresentationTimesStamp"} = {type = mutable dict, count = 4,
                entries =>
                    0 : {contents = "flags"} = {value = +5, type = kCFNumberSInt32Type}
                    1 : {contents = "value"} = {value = +0, type = kCFNumberSInt64Type}
                    4 : {contents = "timescale"} = {value = +0, type = kCFNumberSInt32Type}
                    5 : {contents = "epoch"} = {value = +0, type = kCFNumberSInt64Type}
                }

    }
    formatDescription = //... 
    sbufToTrackReadiness = 0x0
    numSamples = 0
    dataBuffer = 0x0

整理PostNotificationWhenConsumed(P)得

@{
    @"StartPresentationTimesStamp": @{
        @"flags":       @(1),
        @"value":       @(1334000),
        @"timescale":   @(60000),
        @"epoch":       @(0)
    }
    @"EndPresentationTimesStamp": @{
        @"flags":       @(5),
        @"value":       @(0),
        @"timescale":   @(0),
        @"epoch":       @(0)
    }
}

苹果文档对kCMSampleBufferAttachmentKey_PostNotificationWhenConsumed的说明如下:

kCMSampleBufferAttachmentKey_PostNotificationWhenConsumed
If present, indicates that decode pipelines should post a notification when consuming the sample buffer(type CFDictionary).

This attachment is used at run time to request that a decode pipeline post a kCMSampleBufferConsumerNotification_BufferConsumed notification when this sample buffer is consumed. The value for this key is used as the userInfo dictionary in the notification.

This attachment is not written to media files.

6、最后一个:EmptyMedia(P) = true、PermanentEmptyMedia(P) = true

CMSampleBuffer 0x134e8cb60 retainCount: 1 allocator: 0x1a1a20150
    invalid = NO
    dataReady = YES
    makeDataReadyCallback = 0x0
    makeDataReadyRefcon = 0x0
    buffer-level attachments:
        EmptyMedia(P) = true
        PermanentEmptyMedia(P) = true
    formatDescription = //...
    sbufToTrackReadiness = 0x0
    numSamples = 0
    sampleTimingArray[1] = {
        {
            PTS = {1334000/60000 = 22.233}, 
            DTS = {INVALID}, 
            duration = {0/1 = 0.000}},
    }
    dataBuffer = 0x0

苹果文档对kCMSampleBufferAttachmentKey_EmptyMedia的说明如下:

Marks an intentionally empty interval in the sequence of samples (type CFBoolean, default false).

The sample buffer's output presentation timestamp indicates when the empty interval begins. Marker sample buffers with this attachment are used to announce the arrival of empty edits.

苹果文档对kCMSampleBufferAttachmentKey_PermanentEmptyMedia的说明如下:

Marks the end of the sequence of samples (type CFBoolean, default false).

Marker sample buffers with this attachment in addition to kCMSampleBufferAttachmentKey_EmptyMedia are used to indicate that no further samples are expected.

总结
1、在读取过程中,CMSampleBuffer无直接信息表示自身是否为关键帧,使用sampleSizeArray判断也不方便,虽然IDR帧显然比P、B帧数量会大,但是不同IDR之间的大小并不相同。
2、经多份数据样本测试,DTS = {INVALID}时,dataBuffer = 0x0。
3、由于AVAssetReader自行添加了三个额外的kCMSampleBufferAttachmentKey:

  • ResetDecoderBeforeDecoding
  • DrainAfterDecoding
  • PostNotificationWhenConsumed

故使用媒体文件(如MP4)的持续时间(AVURLAsset.duration)与平均帧率(AVAssetTrack.nominalFrameRate)的乘积并不能完善地保证视频的总帧数,表示读取进度时需考虑这几个因素,才不会让读取进度超过100%。

你可能感兴趣的:(AVAsset读MP4中H.264与判断CMSampleBuffer是否为关键帧问题)