다음은 다섯 번째 기사입니다. iOS 오디오 디코딩 데모 . 이 데모에는 다음이 포함되어 있습니다.
-
1) 오디오 디캡슐레이션 모듈을 구현합니다.
-
2) 오디오 디코딩 모듈을 구현합니다.
-
3) 오디오 부분의 캡슐화 해제 및 디코딩 논리를 MP4 파일로 구현하고 캡슐화 해제 및 디코딩된 데이터를 PCM 파일로 저장합니다.
-
4) 코드 논리와 원칙을 이해하는 데 도움이 되는 자세한 코드 주석.
처음 4개의 기사:
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로 설정해야 정상적인 사운드를 재생할 수 있습니다.