11

问题

我希望从视频文件中的音轨中提取 LPCM 音频的样本准确范围。目前,我希望AVAssetReaderTrackOutput通过AVAssetTrack阅读AVURLAsset.

尽管准备并确保使用AVURLAssetPreferPreciseDurationAndTimingKeyset to初始化资产,但在资产中YES寻找样本准确的位置似乎是不准确的。

NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey : @(YES) };
_asset = [[AVURLAsset alloc] initWithURL:fileURL options:options];

这表现为例如可变比特率编码的 AAC 流。虽然我知道 VBR 音频流在准确搜索方面会带来性能开销,但我愿意支付这笔费用,前提是我收到了准确的样本。

当使用例如扩展音频文件服务和ExtAudioFileRefAPI 时,我可以实现样本精确的音频搜索和提取。与 , 类似AVAudioFile,因为它建立在ExtAudioFileRef.

然而,问题是我还想从仅音频文件 API 拒绝但AVFoundation 中通过AVURLAsset.

方法

CMTime使用和定义提取的样本准确时间范围CMTimeRange,并在 上设置AVAssetReaderTrackOutput。然后迭代地提取样本。

-(NSData *)readFromFrame:(SInt64)startFrame
      requestedFrameCount:(UInt32)frameCount
{
    NSUInteger expectedByteCount = frameCount * _bytesPerFrame;
    NSMutableData *data = [NSMutableData dataWithCapacity:expectedByteCount];
    
    //
    // Configure Output
    //

    NSDictionary *settings = @{ AVFormatIDKey               : @( kAudioFormatLinearPCM ),
                                AVLinearPCMIsNonInterleaved : @( NO ),
                                AVLinearPCMIsBigEndianKey   : @( NO ),
                                AVLinearPCMIsFloatKey       : @( YES ),
                                AVLinearPCMBitDepthKey      : @( 32 ),
                                AVNumberOfChannelsKey       : @( 2 ) };

    AVAssetReaderOutput *output = [[AVAssetReaderTrackOutput alloc] initWithTrack:_track outputSettings:settings];

    CMTime startTime    = CMTimeMake( startFrame, _sampleRate );
    CMTime durationTime = CMTimeMake( frameCount, _sampleRate );
    CMTimeRange range   = CMTimeRangeMake( startTime, durationTime );

    //
    // Configure Reader
    //

    NSError *error = nil;
    AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:_asset error:&error];

    if( !reader )
    {
        fprintf( stderr, "avf : failed to initialize reader\n" );
        fprintf( stderr, "avf : %s\n%s\n", error.localizedDescription.UTF8String, error.localizedFailureReason.UTF8String );
        exit( EXIT_FAILURE );
    }

    [reader addOutput:output];
    [reader setTimeRange:range];
    BOOL startOK = [reader startReading];

    NSAssert( startOK && reader.status == AVAssetReaderStatusReading, @"Ensure we've started reading." );

    NSAssert( _asset.providesPreciseDurationAndTiming, @"We expect the asset to provide accurate timing." );

    //
    // Start reading samples
    //

    CMSampleBufferRef sample = NULL;
    while(( sample = [output copyNextSampleBuffer] ))
    {
        CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp( sample );
        if( data.length == 0 )
        {
            // First read - we should be at the expected presentation time requested.
            int32_t comparisonResult = CMTimeCompare( presentationTime, startTime );
            NSAssert( comparisonResult == 0, @"We expect sample accurate seeking" );
        }

        CMBlockBufferRef buffer = CMSampleBufferGetDataBuffer( sample );

        if( !buffer )
        {
            fprintf( stderr, "avf : failed to obtain buffer" );
            exit( EXIT_FAILURE );
        }

        size_t lengthAtOffset = 0;
        size_t totalLength = 0;
        char *bufferData = NULL;

        if( CMBlockBufferGetDataPointer( buffer, 0, &lengthAtOffset, &totalLength, &bufferData ) != kCMBlockBufferNoErr )
        {
            fprintf( stderr, "avf : failed to get sample\n" );
            exit( EXIT_FAILURE );
        }

        if( bufferData && lengthAtOffset )
        {
            [data appendBytes:bufferData length:lengthAtOffset];
        }

        CFRelease( sample );
    }

    NSAssert( reader.status == AVAssetReaderStatusCompleted, @"Completed reading" );

    [output release];
    [reader release];

    return [NSData dataWithData:data];
}

笔记

给我的演示时间CMSampleBufferGetPresentationTimeStamp似乎与我所追求的相匹配——但由于它似乎不准确,所以我没有机会更正和对齐我检索到的样本。

关于如何做到这一点的任何想法?

或者,有没有办法适应orAVAssetTrack使用?AVAudioFileExtAudioFile

是否可以通过 访问音轨AudioFileOpenWithCallbacks

是否可以在 macOS 中以不同的方式从视频容器中获取音频流?

4

2 回答 2

3

一个可行的程序是使用 AVAssetReader 来读取压缩的 AV 文件,并结合 AVAssetWriter 来写入音频样本的新原始 LPCM 文件。然后,可以通过这个新的 PCM 文件(或内存映射数组,如果需要)快速索引,以提取精确的样本准确范围,而不会导致 VBR 每个数据包解码大小异常或依赖于无法控制的 iOS CMTimeStamp 算法。

这可能不是最节省时间或内存的过程,但它确实有效。

于 2017-11-06T15:54:46.387 回答
0

我写了另一个答案,其中我错误地声称 AVAssetReader/AVAssetReaderTrackOutput没有进行样本准确搜索,他们确实这样做了,但是当您的音轨嵌入电影文件中时,它看起来很损坏,因此您发现了一个错误。恭喜!

正如@hotpaw2 回答的评论中提到的那样,通过 pass through 转储的音轨AVAssetExportSession工作正常,即使您在非数据包边界上寻找(您碰巧在数据包边界上寻找,链接文件每个数据包有 1024 帧 -寻找数据包边界,您的差异不再为零,但它们非常非常小/听不见)。

我没有找到解决方法,所以重新考虑转储压缩轨道。有那么贵吗?如果你真的不想这样做,你可以自己解码原始数据包,方法是传递nil outputSettings:给你的并通过一个或(最好是?)AVAssetReaderOutput运行它的输出来获得 LPCM。AudioQueueAudioConverter

注意,在后一种情况下,您需要在查找时处理舍入到数据包边界。

于 2017-11-15T10:52:44.257 回答