iOS 오디오 및 비디오 개발 5: 오디오 디코딩

다음은 다섯 번째 기사입니다. iOS 오디오 디코딩 데모 . 이 데모에는 다음이 포함되어 있습니다.

  • 1) 오디오 디캡슐레이션 모듈을 구현합니다.

  • 2) 오디오 디코딩 모듈을 구현합니다.

  • 3) 오디오 부분의 캡슐화 해제 및 디코딩 논리를 MP4 파일로 구현하고 캡슐화 해제 및 디코딩된 데이터를 PCM 파일로 저장합니다.

  • 4) 코드 논리와 원칙을 이해하는 데 도움이 되는 자세한 코드 주석.

처음 4개의 기사:

iOS를 개발, 오디오 캡처 및 PCM 파일로 저장

iOS 오디오 및 비디오 개발 2: 오디오 인코딩, PCM 데이터 캡처 및 AAC로 인코딩

iOS 오디오 및 비디오 개발 3: M4A로 오디오 캡슐화, 캡처, 인코딩 및 캡슐화

iOS 오디오 및 비디오 개발 4: 오디오 캡슐화 해제, MP4에서 AAC 캡슐화 해제

1. 오디오 디캡슐레이션 모듈

이 데모  에서 decapsulation 모듈 KFMP4Demuxer 의 구현은  "iOS Audio Decapsulation Demo" 의 구현과 동일하며  여기에서 반복되지 않습니다. 인터페이스는 다음과 같습니다.

KFMP4Demuxer.h

#import <Foundation/Foundation.h>
#import <CoreMedia/CoreMedia.h>
#import "KFDemuxerConfig.h"

NS_ASSUME_NONNULL_BEGIN

typedef NS_ENUM(NSInteger, KFMP4DemuxerStatus) {
    KFMP4DemuxerStatusUnknown = 0,
    KFMP4DemuxerStatusRunning = 1,
    KFMP4DemuxerStatusFailed = 2,
    KFMP4DemuxerStatusCompleted = 3,
    KFMP4DemuxerStatusCancelled = 4,
};

@interface KFMP4Demuxer : NSObject
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithConfig:(KFDemuxerConfig *)config;

@property (nonatomic, strong, readonly) KFDemuxerConfig *config;
@property (nonatomic, copy) void (^errorCallBack)(NSError *error);
@property (nonatomic, assign, readonly) BOOL hasAudioTrack; // 是否包含音频数据。
@property (nonatomic, assign, readonly) BOOL hasVideoTrack; // 是否包含视频数据。
@property (nonatomic, assign, readonly) CGSize videoSize; // 视频大小。
@property (nonatomic, assign, readonly) CMTime duration; // 媒体时长。
@property (nonatomic, assign, readonly) CMVideoCodecType codecType; // 编码类型。
@property (nonatomic, assign, readonly) KFMP4DemuxerStatus demuxerStatus; // 解封装器状态。
@property (nonatomic, assign, readonly) BOOL audioEOF; // 是否音频结束。
@property (nonatomic, assign, readonly) BOOL videoEOF; // 是否视频结束。
@property (nonatomic, assign, readonly) CGAffineTransform preferredTransform; // 图像的变换信息。比如:视频图像旋转。

- (void)startReading:(void (^)(BOOL success, NSError *error))completeHandler; // 开始读取数据解封装。
- (void)cancelReading; // 取消读取。

- (BOOL)hasAudioSampleBuffer; // 是否还有音频数据。
- (CMSampleBufferRef)copyNextAudioSampleBuffer CF_RETURNS_RETAINED; // 拷贝下一份音频采样。

- (BOOL)hasVideoSampleBuffer; // 是否还有视频数据。
- (CMSampleBufferRef)copyNextVideoSampleBuffer CF_RETURNS_RETAINED; // 拷贝下一份视频采样。
@end

NS_ASSUME_NONNULL_END

2. 오디오 디코딩 모듈

KFAudioDecoder다음으로, 디캡슐화된 인코딩된 데이터가 입력되고 디코딩된 데이터가 출력 되는 오디오 디코딩 모듈을 구현해 보겠습니다  .

KFAudioDecoder.h

#import <Foundation/Foundation.h>
#import <CoreMedia/CoreMedia.h>

NS_ASSUME_NONNULL_BEGIN

@interface KFAudioDecoder : NSObject
@property (nonatomic, copy) void (^sampleBufferOutputCallBack)(CMSampleBufferRef sample); // 解码器数据回调。
@property (nonatomic, copy) void (^errorCallBack)(NSError *error); // 解码器错误回调。

- (void)decodeSampleBuffer:(CMSampleBufferRef)sampleBuffer; // 解码。
@end

NS_ASSUME_NONNULL_END

위는  KFAudioDecoder 주로 오디오 디코딩 数据回调错误回调인터페이스를 포함한 인터페이스 디자인이며 다른 하나는 解码인터페이스입니다.

위의 解码인터페이스와 解码器数据回调인터페이스에서 우리는 여전히 CMSampleBufferRef[1]을 매개변수 또는 반환 값 유형으로 사용합니다.

解码인터페이스에서 우리는  캡슐화 CMSampleBufferRef 해제 후 얻은 AAC 인코딩 데이터를 패키징합니다.

인터페이스 에서 解码器数据回调우리가  CMSampleBufferRef 포장하는 것은 AAC를 디코딩한 후 얻은 오디오 PCM 데이터입니다.

( C/C++ , Linux 서버 개발 , FFmpeg , webRTC , rtmp , hls , rtsp , ffplay , srs ) 를 포함한 최신의 가장 완전한 C++ 오디오 및 비디오 학습 및 개선 자료 를 받으려면 CSDN 스테이션에서 개인 메시지를 보내주십시오.

KFAudioDecoder.m

#import "KFAudioDecoder.h"
#import <AudioToolbox/AudioToolbox.h>

// 自定义数据,用于封装音频解码回调中用到的数据。
typedef struct KFAudioUserData {
    UInt32 mChannels;
    UInt32 mDataSize;
    void *mData;
    AudioStreamPacketDescription mPacketDesc;
} KFAudioUserData;

@interface KFAudioDecoder () {
    UInt8 *_pcmBuffer; // 解码缓冲区。
}
@property (nonatomic, assign) AudioConverterRef audioDecoderInstance; // 音频解码器实例。
@property (nonatomic, assign) CMFormatDescriptionRef pcmFormat; // 音频解码参数。
@property (nonatomic, strong) dispatch_queue_t decoderQueue;
@property (nonatomic, assign) BOOL isError;
@end

@implementation KFAudioDecoder
#pragma mark - Lifecycle
- (instancetype)init {
    self = [super init];
    if (self) {
        _decoderQueue = dispatch_queue_create("com.KeyFrameKit.audioDecoder", DISPATCH_QUEUE_SERIAL);
    }
    
    return self;
}

- (void)dealloc {
    // 清理解码器。
    if (_audioDecoderInstance) {
        AudioConverterDispose(_audioDecoderInstance);
        _audioDecoderInstance = nil;
    }
    if (_pcmFormat) {
        CFRelease(_pcmFormat);
        _pcmFormat = NULL;
    }
    
    // 清理缓冲区。
    if (_pcmBuffer) {
        free(_pcmBuffer);
        _pcmBuffer = NULL;
    }
}

#pragma mark - Public Method
- (void)decodeSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    if (!sampleBuffer || !CMSampleBufferGetDataBuffer(sampleBuffer) || self.isError) {
        return;
    }
    
    // 异步处理,防止主线程卡顿。
    __weak typeof(self) weakSelf = self;
    CFRetain(sampleBuffer);
    dispatch_async(_decoderQueue, ^{
        [weakSelf _decodeSampleBuffer:sampleBuffer];
        CFRelease(sampleBuffer);
    });
}

#pragma mark - Private Method
- (void)_setupAudioDecoderInstanceWithInputAudioFormat:(AudioStreamBasicDescription)inputFormat error:(NSError **)error{
    if (_audioDecoderInstance != nil) {
        return;
    }
    
    // 1、设置音频解码器输出参数。其中一些参数与输入的音频数据参数一致。
    AudioStreamBasicDescription outputFormat = {0};
    outputFormat.mSampleRate = inputFormat.mSampleRate; // 输出采样率与输入一致。
    outputFormat.mFormatID = kAudioFormatLinearPCM; // 输出的 PCM 格式。
    outputFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
    outputFormat.mChannelsPerFrame = (UInt32) inputFormat.mChannelsPerFrame; // 输出声道数与输入一致。
    outputFormat.mFramesPerPacket = 1; // 每个包的帧数。对于 PCM 这样的非压缩音频数据,设置为 1。
    outputFormat.mBitsPerChannel = 16; // 对于 PCM,表示采样位深。
    outputFormat.mBytesPerFrame = outputFormat.mChannelsPerFrame * outputFormat.mBitsPerChannel / 8; // 每帧字节数 (byte = bit / 8)。
    outputFormat.mBytesPerPacket = outputFormat.mFramesPerPacket * outputFormat.mBytesPerFrame; // 每个包的字节数。
    outputFormat.mReserved = 0; // 对齐方式,0 表示 8 字节对齐。

    // 2、基于音频输入和输出参数创建音频解码器。
    OSStatus status = AudioConverterNew(&inputFormat, &outputFormat, &_audioDecoderInstance);
    if (status != 0) {
        *error = [NSError errorWithDomain:NSStringFromClass(self.class) code:status userInfo:nil];
    }
    
    // 3、创建编码格式信息 _pcmFormat。
    OSStatus result = CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &outputFormat, 0, NULL, 0, NULL, NULL, &_pcmFormat);
    if (result != noErr) {
        *error = [NSError errorWithDomain:NSStringFromClass(self.class) code:result userInfo:nil];
        return;
    }
}

- (void)_decodeSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    // 1、从输入数据中获取音频格式信息。
    CMAudioFormatDescriptionRef audioFormatRef = CMSampleBufferGetFormatDescription(sampleBuffer);
    if (!audioFormatRef) {
        return;
    }
    // 获取音频参数信息,AudioStreamBasicDescription 包含了音频的数据格式、声道数、采样位深、采样率等参数。
    AudioStreamBasicDescription audioFormat = *CMAudioFormatDescriptionGetStreamBasicDescription(audioFormatRef);
    
    // 2、根据音频参数创建解码器实例。
    NSError *error = nil;
    // 第一次解码时创建解码器。
    if (!_audioDecoderInstance) {
        [self _setupAudioDecoderInstanceWithInputAudioFormat:audioFormat error:&error];
        if (error) {
            [self _callBackError:error];
            return;
        }
        if (!_audioDecoderInstance) {
            return;
        }
    }
    
    // 3、获取输入数据中的 AAC 编码数据。
    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    size_t audioLength;
    char *dataPointer = NULL;
    CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &audioLength, &dataPointer);
    if (audioLength == 0 || !dataPointer) {
        return;
    }
    
    // 4、创建解码回调中要用到的自定义数据。
    KFAudioUserData userData = {0};
    userData.mChannels = (UInt32) audioFormat.mChannelsPerFrame;
    userData.mDataSize = (UInt32) audioLength;
    userData.mData = (void *) dataPointer; // 绑定 AAC 编码数据。
    userData.mPacketDesc.mDataByteSize = (UInt32) audioLength;
    userData.mPacketDesc.mStartOffset = 0;
    userData.mPacketDesc.mVariableFramesInPacket = 0;

    // 5、创建解码输出数据缓冲区内存空间。
    // AAC 编码的每个包有 1024 帧。
    UInt32 pcmDataPacketSize = 1024;
    // 缓冲区长度:pcmDataPacketSize * 2(16 bit 采样深度) * 声道数量。
    UInt32 pcmBufferSize = (UInt32) (pcmDataPacketSize * 2 * audioFormat.mChannelsPerFrame);
    if (!_pcmBuffer) {
        _pcmBuffer = malloc(pcmBufferSize);
    }
    memset(_pcmBuffer, 0, pcmBufferSize);
    
    // 6、创建解码器接口对应的解码缓冲区 AudioBufferList,绑定缓冲区的内存空间。
    AudioBufferList outAudioBufferList = {0};
    outAudioBufferList.mNumberBuffers = 1;
    outAudioBufferList.mBuffers[0].mNumberChannels = (UInt32) audioFormat.mChannelsPerFrame;
    outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32) pcmBufferSize; // 设置解码缓冲区大小。
    outAudioBufferList.mBuffers[0].mData = _pcmBuffer; // 绑定缓冲区空间。

    // 7、输出数据描述。
    AudioStreamPacketDescription outputPacketDesc = {0};
    
    // 9、解码。
    OSStatus status = AudioConverterFillComplexBuffer(self.audioDecoderInstance, inputDataProcess, &userData, &pcmDataPacketSize, &outAudioBufferList, &outputPacketDesc);
    if (status != noErr) {
        [self _callBackError:[NSError errorWithDomain:NSStringFromClass(self.class) code:status userInfo:nil]];
        return;
    }
    
    if (outAudioBufferList.mBuffers[0].mDataByteSize > 0) {
        // 10、获取解码后的 PCM 数据并进行封装。
        // 把解码后的 PCM 数据先封装到 CMBlockBuffer 中。
        CMBlockBufferRef pcmBlockBuffer;
        size_t pcmBlockBufferSize = outAudioBufferList.mBuffers[0].mDataByteSize;
        char *pcmBlockBufferDataPointer = malloc(pcmBlockBufferSize);
        memcpy(pcmBlockBufferDataPointer, outAudioBufferList.mBuffers[0].mData, pcmBlockBufferSize);
        OSStatus status  = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,
                                                              pcmBlockBufferDataPointer,
                                                              pcmBlockBufferSize,
                                                              NULL,
                                                              NULL,
                                                              0,
                                                              pcmBlockBufferSize,
                                                              0,
                                                              &pcmBlockBuffer);
        if (status != noErr) {
            return;
        }
        
        // 把 PCM 数据所在的 CMBlockBuffer 封装到 CMSampleBuffer 中。
        CMSampleBufferRef pcmSampleBuffer = NULL;
        CMSampleTimingInfo timingInfo = {CMTimeMake(1, audioFormat.mSampleRate), CMSampleBufferGetPresentationTimeStamp(sampleBuffer), kCMTimeInvalid };
        status = CMSampleBufferCreateReady(kCFAllocatorDefault,
                                           pcmBlockBuffer,
                                           _pcmFormat,
                                           pcmDataPacketSize,
                                           1,
                                           &timingInfo,
                                           0,
                                           NULL,
                                           &pcmSampleBuffer);
        CFRelease(pcmBlockBuffer);
        
        // 11、回调解码数据。
        if (pcmSampleBuffer) {
            if (self.sampleBufferOutputCallBack) {
                self.sampleBufferOutputCallBack(pcmSampleBuffer);
            }
            CFRelease(pcmSampleBuffer);
        }
        
    }
}

- (void)_callBackError:(NSError*)error {
    self.isError = YES;
    if (error && self.errorCallBack) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.errorCallBack(error);
        });
    }
}

#pragma mark - Decoder CallBack
static OSStatus inputDataProcess(AudioConverterRef inConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
    KFAudioUserData *userData = (KFAudioUserData *) inUserData;
    if (userData->mDataSize <= 0) {
        ioNumberDataPackets = 0;
        return -1;
    }
    
    // 设置解码输出数据格式信息。
    *outDataPacketDescription = &userData->mPacketDesc;
    (*outDataPacketDescription)[0].mStartOffset = 0;
    (*outDataPacketDescription)[0].mDataByteSize = userData->mDataSize;
    (*outDataPacketDescription)[0].mVariableFramesInPacket = 0;

    // 将待解码的数据拷贝到解码器的缓冲区的对应位置进行解码。
    ioData->mBuffers[0].mData = userData->mData;
    ioData->mBuffers[0].mDataByteSize = userData->mDataSize;
    ioData->mBuffers[0].mNumberChannels = userData->mChannels;
    
    return noErr;
}

@end

위는  KFAudioDecoder 구현입니다.코드에서 주로 다음과 같은 부분이 있음을 알 수 있습니다.

  • 1) 오디오 디코딩 인스턴스를 만듭니다. -decodeSampleBuffer: →  에 대한 첫 번째 호출  -_decodeSampleBuffer: 은 오디오 디코딩 인스턴스를 생성합니다.

    • 방식으로 구현 된다  -_setupAudioDecoderInstanceWithInputAudioFormat:error: .

  • 2) 오디오 디코딩 로직을 구현하고 데이터를  CMSampleBufferRef 구조로 캡슐화하고 KFAudioDecoder의 외부 데이터 콜백 인터페이스에 던집니다.

    •  디코딩 프로세스는 디코딩할 버퍼와 디코딩 버퍼의 관리를 포함하는  -decodeSampleBuffer: →  에서 구현 되며  , 마지막으로  콜백에서 디코딩할 데이터가 디코딩을 위해 디코더의 버퍼에 복사되고 해당 디코딩 데이터 형식이 설정됩니다.-_decodeSampleBuffer:inputDataProcess(...)

  • 3) 오디오 디코딩 프로세스에서 오류를 캡처하고 KFAudioDecoder의 외부 오류 콜백 인터페이스에 던집니다.

    • -_decodeSampleBuffer: 메서드 에서 오류를 포착하고  메서드  -_callBackError: 외부에서 다시 호출합니다.

  • 4) 오디오 디코더 인스턴스와 디코딩 버퍼를 정리합니다.

    • 방식으로 구현 된다  -dealloc .

자세한 내용은 위의 코드와 주석을 참조하십시오.

3. MP4 파일의 오디오 부분을 캡슐화 해제하고 디코딩하여 PCM 파일로 저장

ViewController에서 오디오 디캡슐화 및 디코딩 로직을 구현하고 디코딩된 데이터를 PCM 파일로 저장합니다.

 

#import "KFAudioDecoderViewController.h"
#import "KFMP4Demuxer.h"
#import "KFAudioDecoder.h"

@interface KFAudioDecoderViewController ()
@property (nonatomic, strong) KFDemuxerConfig *demuxerConfig;
@property (nonatomic, strong) KFMP4Demuxer *demuxer;
@property (nonatomic, strong) KFAudioDecoder *decoder;
@property (nonatomic, strong) NSFileHandle *fileHandle;
@end

@implementation KFAudioDecoderViewController
#pragma mark - Property
- (KFDemuxerConfig *)demuxerConfig {
    if (!_demuxerConfig) {
        _demuxerConfig = [[KFDemuxerConfig alloc] init];
        _demuxerConfig.demuxerType = KFMediaAudio;
        NSString *videoPath = [[NSBundle mainBundle] pathForResource:@"input" ofType:@"mp4"];
        _demuxerConfig.asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:videoPath]];
    }
    
    return _demuxerConfig;
}

- (KFMP4Demuxer *)demuxer {
    if (!_demuxer) {
        _demuxer = [[KFMP4Demuxer alloc] initWithConfig:self.demuxerConfig];
        _demuxer.errorCallBack = ^(NSError *error) {
            NSLog(@"KFMP4Demuxer error:%zi %@", error.code, error.localizedDescription);
        };
    }
    
    return _demuxer;
}

- (KFAudioDecoder *)decoder {
    if (!_decoder) {
        __weak typeof(self) weakSelf = self;
        _decoder = [[KFAudioDecoder alloc] init];
        _decoder.errorCallBack = ^(NSError *error) {
            NSLog(@"KFAudioDecoder error:%zi %@", error.code, error.localizedDescription);
        };
        // 解码数据回调。在这里把解码后的音频 PCM 数据存储为文件。
        _decoder.sampleBufferOutputCallBack = ^(CMSampleBufferRef sampleBuffer) {
            if (sampleBuffer) {
                CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
                size_t totolLength;
                char *dataPointer = NULL;
                CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &totolLength, &dataPointer);
                if (totolLength == 0 || !dataPointer) {
                    return;
                }
                
                [weakSelf.fileHandle writeData:[NSData dataWithBytes:dataPointer length:totolLength]];
            }
        };
    }
    
    return _decoder;
}

- (NSFileHandle *)fileHandle {
    if (!_fileHandle) {
        NSString *videoPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"output.pcm"];
        [[NSFileManager defaultManager] removeItemAtPath:videoPath error:nil];
        [[NSFileManager defaultManager] createFileAtPath:videoPath contents:nil attributes:nil];
        _fileHandle = [NSFileHandle fileHandleForWritingAtPath:videoPath];
    }

    return _fileHandle;
}

#pragma mark - Lifecycle
- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupUI];
    
    // 完成音频解码后,可以将 App Document 文件夹下面的 output.pcm 文件拷贝到电脑上,使用 ffplay 播放:
    // ffplay -ar 44100 -channels 1 -f s16le -i output.pcm
}

- (void)dealloc {
    if (_fileHandle) {
        [_fileHandle closeFile];
        _fileHandle = nil;
    }
}

#pragma mark - Setup
- (void)setupUI {
    self.edgesForExtendedLayout = UIRectEdgeAll;
    self.extendedLayoutIncludesOpaqueBars = YES;
    self.title = @"Audio Decoder";
    self.view.backgroundColor = [UIColor whiteColor];
    
    // Navigation item.
    UIBarButtonItem *startBarButton = [[UIBarButtonItem alloc] initWithTitle:@"Start" style:UIBarButtonItemStylePlain target:self action:@selector(start)];
    self.navigationItem.rightBarButtonItems = @[startBarButton];
}

#pragma mark - Action
- (void)start {
    __weak typeof(self) weakSelf = self;
    NSLog(@"KFMP4Demuxer start");
    [self.demuxer startReading:^(BOOL success, NSError * _Nonnull error) {
        if (success) {
            // Demuxer 启动成功后,就可以从它里面获取解封装后的数据了。
            [weakSelf fetchAndDecodeDemuxedData];
        } else {
            NSLog(@"KFMP4Demuxer error:%zi %@", error.code, error.localizedDescription);
        }
    }];
}

#pragma mark - Utility
- (void)fetchAndDecodeDemuxedData {
    // 异步地从 Demuxer 获取解封装后的 AAC 编码数据,送给解码器进行解码。
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (weakSelf.demuxer.hasAudioSampleBuffer) {
            CMSampleBufferRef audioBuffer = [weakSelf.demuxer copyNextAudioSampleBuffer];
            if (audioBuffer) {
                [weakSelf decodeSampleBuffer:audioBuffer];
                CFRelease(audioBuffer);
            }
        }
        if (weakSelf.demuxer.demuxerStatus == KFMP4DemuxerStatusCompleted) {
            NSLog(@"KFMP4Demuxer complete");
        }
    });
}

- (void)decodeSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    // 获取解封装后的 AAC 编码裸数据。
    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    size_t totolLength;
    char *dataPointer = NULL;
    CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &totolLength, &dataPointer);
    if (totolLength == 0 || !dataPointer) {
        return;
    }
    
    // 目前 AudioDecoder 的解码接口实现的是单包(packet,1 packet 有 1024 帧)解码。而从 Demuxer 获取的一个 CMSampleBuffer 可能包含多个包,所以这里要拆一下包,再送给解码器。
    NSLog(@"SampleNum: %ld", CMSampleBufferGetNumSamples(sampleBuffer));
    for (NSInteger index = 0; index < CMSampleBufferGetNumSamples(sampleBuffer); index++) {
        // 1、获取一个包的数据。
        size_t sampleSize = CMSampleBufferGetSampleSize(sampleBuffer, index);
        CMSampleTimingInfo timingInfo;
        CMSampleBufferGetSampleTimingInfo(sampleBuffer, index, &timingInfo);
        char *sampleDataPointer = malloc(sampleSize);
        memcpy(sampleDataPointer, dataPointer, sampleSize);
        
        // 2、将数据封装到 CMBlockBuffer 中。
        CMBlockBufferRef packetBlockBuffer;
        OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,
                                                              sampleDataPointer,
                                                              sampleSize,
                                                              NULL,
                                                              NULL,
                                                              0,
                                                              sampleSize,
                                                              0,
                                                              &packetBlockBuffer);
        
        if (status == noErr) {
            // 3、将 CMBlockBuffer 封装到 CMSampleBuffer 中。
            CMSampleBufferRef frameSampleBuffer = NULL;
            const size_t sampleSizeArray[] = {sampleSize};
            status = CMSampleBufferCreateReady(kCFAllocatorDefault,
                                               packetBlockBuffer,
                                               CMSampleBufferGetFormatDescription(sampleBuffer),
                                               1,
                                               1,
                                               &timingInfo,
                                               1,
                                               sampleSizeArray,
                                               &frameSampleBuffer);
            CFRelease(packetBlockBuffer);
            
            // 4、解码这个包的数据。
            if (frameSampleBuffer) {
                [self.decoder decodeSampleBuffer:frameSampleBuffer];
                CFRelease(frameSampleBuffer);
            }
        }
        dataPointer += sampleSize;
    }
}

@end

위는  KFAudioDecoderViewController 주로 다음 부분을 포함하는 구현입니다.

  • 1) 오디오 캡슐화 해제를 시작하여 전체 캡슐화 해제 및 디코딩 프로세스를 구동합니다.

    • -start 에서 시작 작업을 구현합니다

  • 2) 캡슐화 해제 모듈  KFMP4Demuxer 이 성공적으로 시작된 후 캡슐화 해제 데이터 읽기를 시작하고 디코딩을 시작합니다.

    • 방식으로 구현 된다  -fetchAndDecodeDemuxedData .

  • 3) 압축 해제된 데이터의 압축을 풀고 패킷 단위로 캡슐화한  다음 CMSampleBuffer 디코딩을 위해 디코더로 보냅니다.

    • 방식으로 구현 된다  -decodeSampleBuffer: .

  • 4)  KFAudioDecoder 디코딩 모듈의 데이터 콜백에서 디코딩된 PCM 데이터를 가져와 파일로 저장합니다.

    • KFAudioDecoder 의  sampleBufferOutputCallBack 콜백 에서 구현 되었습니다  .

4. 도구로 PCM 파일 재생

오디오 디코딩이 완료되면 App Document 폴더 아래에 있는 파일을  output.pcm 컴퓨터로  복사하고 ffplay 재생을 사용하여 오디오 캡처가 예상한 대로인지 확인할 수 있습니다.

$ ffplay -ar 44100 -channels 1 -f s16le -i output.pcm

 여기에서 매개변수는 프로젝트에 있는 입력 비디오 소스의 , , , 와 정렬 采样率되어야 声道数합니다 采样位深. 예를 들어 데모에서 입력 비디오 소스의 채널 번호는 1이므로 위의 채널 번호를 1로 설정해야 정상적인 사운드를 재생할 수 있습니다.

추천

출처blog.csdn.net/m0_60259116/article/details/124770941