在音视频-视频编/解码 实战文章中已经知道,我们音视频采集后的数据格式为CMSampleBufferRef
类型。如果不需要对音频的编解码操作,可以直接播放该PCM
音频数据。
一、直接播放采集到的CMSampleBufferRef
音频数据
1.1 创建PCM
音频播放器
- (instancetype)initWithConfigure:(CQAudioCoderConfigure *)configure {
self = [super init];
if (self) {
//1、设置AudioStreamBasicDescription
self.configure = configure;
AudioStreamBasicDescription audioDescription = {0};
audioDescription.mSampleRate = (Float64)self.configure.sampleRate;//采样率
audioDescription.mChannelsPerFrame = (UInt32)self.configure.channelCount;//输出声道
audioDescription.mFormatID = kAudioFormatLinearPCM;//输出格式
audioDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;//编码 12
audioDescription.mFramesPerPacket = 1;//每个包的帧数
audioDescription.mBitsPerChannel = 16;//数据帧中每个通道的采样位数
audioDescription.mBytesPerFrame = audioDescription.mBitsPerChannel / 8 * audioDescription.mChannelsPerFrame;//每一帧的大小(采样位数 / 8 * 声道数)
audioDescription.mBytesPerPacket = audioDescription.mBytesPerFrame * audioDescription.mFramesPerPacket;//每个packet的大小(每帧的大小 * 帧数)
audioDescription.mReserved = 0;//是否倒置
CQAudioPlayerState playerState = {0};
playerState.audioDescription = audioDescription;
self.playerState = playerState;
//2、设置session
NSError *error;
//注意:该操作为同步操作,会阻塞线程。
[[AVAudioSession sharedInstance] setActive:YES error:&error];
if (error) {
NSLog(@"AVAudioSession setActive error: %@", error);
}
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
if (error) {
NSLog(@"AVAudioSession setCategory error: %@", error);
}
//3、创建播放队列
OSStatus status = AudioQueueNewOutput(&_playerState.audioDescription, CQAudioQueueOutputCallback, NULL, NULL, NULL, 0, &_playerState.audioQueue);
if (status != noErr) {
error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
NSLog(@"AudioQueue create error: %@", error);
return self;
}
//4、设置播放音频队列参数值
AudioQueueSetParameter(_playerState.audioQueue, kAudioQueueParam_Volume, 1);
self.isPlaying = false;
}
return self;
}
static void CQAudioQueueOutputCallback(void * __nullable inUserData, AudioQueueRef audioQueue, AudioQueueBufferRef audioQueueBuffer) {
AudioQueueFreeBuffer(audioQueue, audioQueueBuffer);
}
- 1、设置
AudioStreamBasicDescription
(音频流的基本信息描述) - 2、激活
AVAudioSession
(同步操作,会阻塞线程),设置会话类别。 - 3、
AudioQueueNewOutput
创建音频队列,用于播放音频数据。
参数1:音频流基本信息描述的结构指针。
参数2:播放完缓冲区后要调用的回调函数的指针。
参数3:指向指定要传递给回调函数的数据的值或指针。
参数4:事件循环,指定NULL,则在音频队列的一个内部线程上调用回调。
参数5:循环模式。
参数6:0
参数7:指向新创建的播放音频队列对象的指针 - 4、设置播放音频队列参数值
参数1:要开始的音频队列
参数2:属性(kAudioQueueParam_Volume
音量)
参数3:对应的属性值
1.2 将CMSampleBufferRef
转换为NSData
- (NSData *)convertToPcmData:(CMSampleBufferRef)sampleBuffer {
size_t pcmSize = CMSampleBufferGetTotalSampleSize(sampleBuffer);
int8_t *audioData = (int8_t *)malloc(pcmSize);
memset(audioData, 0, pcmSize);
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
CMBlockBufferCopyDataBytes(blockBuffer, 0, pcmSize, audioData);
NSData *data = [NSData dataWithBytes:audioData length:pcmSize];
free(audioData);
return data;
}
- 1、
CMSampleBufferGetTotalSampleSize
:获取PCM
数据大小pcmSize
。 - 2、为音频数据开辟内存空间
audioData
。 - 3、
CMSampleBufferGetDataBuffer
:获取CMBlockBuffer
, 这里面保存了PCM
数据。 - 4、
CMBlockBufferCopyDataBytes
:将音频数据拷贝到audioData
1.3 播放NSData
格式的音频数据
- (void)startPlayPCMData:(NSData *)data {
AudioQueueBufferRef audioQueueBuffer;
AudioQueueAllocateBuffer(_playerState.audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffer);
memcpy(audioQueueBuffer->mAudioData, data.bytes, data.length);
audioQueueBuffer->mAudioDataByteSize = (UInt32)data.length;
//将缓存区添加到录制或播放音频队列的缓冲区队列
OSStatus status = AudioQueueEnqueueBuffer(_playerState.audioQueue, audioQueueBuffer, 0, NULL);
if (status != noErr) {
NSLog(@"Error: audio queue palyer enqueue error: %d",(int)status);
}
QueueStart(_playerState.audioQueue, NULL);
}
- 1、
AudioQueueAllocateBuffer
使用 音频队列对象 分配 音频队列缓冲区对象
参数1:要分配缓冲区的音频队列
参数2:新缓冲区所需的字节大小(MIN_SIZE_PER_FRAME
每一帧使用最小字节大小)
参数3:指向新分配的 音频队列缓冲区对象 - 2、
memcpy
:将data
中的数据拷贝到audioQueueBuffer->mAudioData
。 - 3、
AudioQueueEnqueueBuffer
为录音或回放的音频队列分配缓冲区。 - 4、开始播放或录制音频 。
二、对采集到的CMSampleBufferRef
音频数据进行编码
2.1 创建音频转换器
- (void)createAudioConverter:(CMSampleBufferRef)sampleBuffer {
//1、获取输入AudioStreamBasicDescription
CMFormatDescriptionRef description = CMSampleBufferGetFormatDescription(sampleBuffer);
AudioStreamBasicDescription inAudioDescription = *CMAudioFormatDescriptionGetStreamBasicDescription(description);
//2、设置输出AudioStreamBasicDescription
AudioStreamBasicDescription outAudioDescription = {0};
outAudioDescription.mSampleRate = (Float64)self.configure.sampleRate;//采样率
outAudioDescription.mFormatID = kAudioFormatMPEG4AAC; //输出格式
outAudioDescription.mFormatFlags = kMPEG4Object_AAC_LC; //如果设为0 代表无损编码
outAudioDescription.mBytesPerPacket = 0; //自己确定每个packet 大小
outAudioDescription.mFramesPerPacket = 1024; //每一个packet帧数 AAC-1024;
outAudioDescription.mBytesPerFrame = 0; //每一帧大小
outAudioDescription.mChannelsPerFrame = (uint32_t)self.configure.channelCount;//输出声道数
outAudioDescription.mBitsPerChannel = 0; //数据帧中每个通道的采样位数。
outAudioDescription.mReserved = 0; //对其方式 0(8字节对齐)
//填充输出相关信息
UInt32 outSize = sizeof(outAudioDescription);
OSStatus status = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outSize, &outAudioDescription);
if (status != noErr) {
NSLog(@"AudioFormatGetProperty status : %d", status);
}
//3、获取编码器类型描述(只能传入software)
AudioClassDescription *audioClassDesc = [self getAudioClassDescriptionWithFormatID:outAudioDescription.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
// 4、创建音频转换器
status = AudioConverterNewSpecific(&inAudioDescription, &outAudioDescription, 1, audioClassDesc, &_audioConverter);
if (status != noErr) {
NSLog(@"音频转换器创建失败 status:%d", (int)status);
return;
}
// 5、设置音频转换器的属性
//编解码质量
UInt32 temp = kAudioConverterQuality_High;
AudioConverterSetProperty(self.audioConverter, kAudioConverterCodecQuality, sizeof(temp), &temp);
//设置比特率
uint32_t bitrate = (uint32_t)self.configure.bitrate;
uint32_t bitrateSize = sizeof(bitrate);
status = AudioConverterSetProperty(self.audioConverter, kAudioConverterCodecQuality, bitrateSize, &bitrate);
if (status != noErr) {
NSLog(@"设置比特率失败 status:%d", (int)status);
}
}
- 1、获取 音频流数据基本描述 的输入对象。
- 2、设置 音频流数据基本描述 的输出对象。
- 3、获取编码器类型描述
AudioClassDescription
。 - 4、
AudioConverterNewSpecific
创建音频转换器
参数1:输入音频流数据基本描述
参数2:输出音频流数据基本描述
参数3:编码器类型描述AudioClassDescription
的数量
参数4:编码器类型描述AudioClassDescription
参数5:创建的音频转换器 - 5、
AudioConverterSetProperty
设置音频转换器的属性。
看下第三步:获取编码器类型描述AudioClassDescription
:
- (AudioClassDescription *)getAudioClassDescriptionWithFormatID:(AudioFormatID)formatID fromManufacture: (uint32_t)manufacturer {
//1、
UInt32 encoderSpecifier = formatID;
UInt32 size;//获取满足AAC编码器的总大小
OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(encoderSpecifier), &encoderSpecifier, &size);
if (status != noErr) {
NSLog(@"AudioFormatGetPropertyInfo error status: %d", (int)status);
return nil;
}
unsigned int count = size / sizeof(AudioClassDescription);//aac编码器的个数
AudioClassDescription description[count];//编码器的数组
//2、将编码器的信息写入数组description
status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(encoderSpecifier), &encoderSpecifier, &size, &description);
if (status != noErr) {
NSLog(@"AudioFormatGetProperty error status: %d", (int)status);
return nil;
}
//3、
static AudioClassDescription audioClassDescription;
for (unsigned int i = 0; i < count; i++) {
if (formatID == description[i].mSubType && manufacturer == description[i].mManufacturer) {
audioClassDescription = description[i];
return &audioClassDescription;
}
}
return nil;
}
- 1、
AudioFormatGetPropertyInfo
获取编码器的属性信息。
参数1:编码器类型
参数2:类型描述大小
参数3:类型描述
参数4:编码器的总大小 - 2、
AudioFormatGetProperty
将满足AAC编码的编码器的信息写入数组。 - 3、循环查询符合类型的编码器类型描述。
2.2 编码
- (void)encodeSamepleBuffer:(CMSampleBufferRef)sampleBuffer {
CFRetain(sampleBuffer);
//1.创建音频转换器。
if (!self.audioConverter) {
[self createAudioConverter:sampleBuffer];
}
dispatch_async(self.encoderQueue, ^{
//2.获取blockBuffer中音频数据大小以及音频数据地址
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
CFRetain(blockBuffer);
OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &self->_pcmBufferSize, &self->_pcmBuffer);
if (status != kCMBlockBufferNoErr) {
NSLog(@"CMBlockBufferGetDataPointer error status: %d", (int)status);
return;
}
uint8_t *pcmBuffer = malloc(self->_pcmBufferSize); //为pcmBuffer开辟内存空间
memset(pcmBuffer, 0, self->_pcmBufferSize);//将pcmBuffer数据set为0
//3.将pcmBuffer数据填充到outBufferList 对象中
AudioBufferList outBufferList = {0};
outBufferList.mNumberBuffers = 1;
outBufferList.mBuffers[0].mNumberChannels = (uint32_t)self.configure.channelCount;
outBufferList.mBuffers[0].mDataByteSize = (UInt32)self->_pcmBufferSize;
outBufferList.mBuffers[0].mData = pcmBuffer;
//4.实时音频压缩
UInt32 outPacketSize = 1;//输出包大小为1
//转换由输入回调函数提供的数据
status = AudioConverterFillComplexBuffer(self.audioConverter, inputEncodeDataProc, (__bridge void * _Nullable)(self), &outPacketSize, &outBufferList, NULL);
if (status == noErr) {
//5、获取数据
NSData *rawAAC = [NSData dataWithBytes: outBufferList.mBuffers[0].mData length:outBufferList.mBuffers[0].mDataByteSize];
free(pcmBuffer);
//6、添加`ADTS`头。想要获取裸流时,不用添加`ADTS`头。写入文件时,必须添加。
// NSData *adtsData = [self setADTSDataForPacketLength:encodedAudioData.length];
// NSMutableData *entireData = [NSMutableData dataWithCapacity:adtsData.length + encodedAudioData.length];
// [entireData appendData:adtsData];
// [entireData appendData:encodedAudioData];
//7、将数据传递到回调队列中
dispatch_async(self->_callbackQueue, ^{
[self->_delegate audioEncodeCallback:rawAAC];
});
} else {
NSLog(@"AudioConverterFillComplexBuffer error status: %d", (int)status);
}
//释放
CFRelease(blockBuffer);
CFRelease(sampleBuffer);
});
}
- 1、创建音频转换器。
- 2、获取
CMBlockBufferRef
,CMBlockBufferRef
中音频数据大小以及音频数据地址。 - 3、创建
AudioBufferList
对象。 - 4、
AudioConverterFillComplexBuffer
实时音频压缩,转换由输入回调函数提供的数据:
参数1: 音频转换器
参数2:inputEncodeDataProc
回调函数,提供要转换的音频数据的回调函数。当转换器准备好接受新的输入数据时,会重复调用此回调。
参数3: 回调函数中用到的值,一般情况下将我们自定义的编码器对象传过去,这里也就是self
。
参数4: 输出包的大小
参数5: 存储pcmBuffer
数据的AudioBufferList
对象地址
参数6:outPacketDescription
输出包信息 - 5、获取压缩后的音频数据
outBufferList.mBuffers[0].mData
。 - 6、添加
ADTS
头。想要获取裸流时,不用添加ADTS
头。写入文件时,必须添加。 - 7、将压缩后的数据传递到回调队列中。
- 8、手动释放
blockBuffer
、sampleBuffer
。
看下inputEncodeDataProc
输入回调函数:
static OSStatus inputEncodeDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
CQAudioEncoder *audioEncoder = (__bridge CQAudioEncoder *)(inUserData);
//判断pcmBuffsize大小
if (!audioEncoder.pcmBufferSize) {
*ioNumberDataPackets = 0;
return -1;
}
//填充数据
ioData->mBuffers[0].mData = audioEncoder.pcmBuffer;
ioData->mBuffers[0].mDataByteSize = (uint32_t)audioEncoder.pcmBufferSize;
ioData->mBuffers[0].mNumberChannels = (uint32_t)audioEncoder.configure.channelCount;
//清空数据
audioEncoder.pcmBufferSize = 0;
*ioNumberDataPackets = 1;
return noErr;
}
- 该函数的核心功能就是 提供要转换的音频数据。当转换器准备好接受新的输入数据时,会重复调用此回调。
添加ADTS
头:
- (NSData*)setADTSDataForPacketLength:(NSUInteger)packetLength {
int adtsLength = 7;
char *packet = malloc(sizeof(char) * adtsLength);
int profile = 2;
int freqIdx = 4;
int chanCfg = 1;
NSUInteger fullLength = adtsLength + packetLength;
packet[0] = (char)0xFF;
packet[1] = (char)0xF9;
packet[2] = (char)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
packet[3] = (char)(((chanCfg&3)<<6) + (fullLength>>11));
packet[4] = (char)((fullLength&0x7FF) >> 3);
packet[5] = (char)(((fullLength&7)<<5) + 0x1F);
packet[6] = (char)0xFC;
NSData *data = [NSData dataWithBytesNoCopy:packet length:adtsLength freeWhenDone:YES];
return data;
}
- 了解即可,可参考相关 ADTS文章。
三、解码
解码跟编码流程很像,这里就不详细介绍了,看核心代码:
3.1 创建音频转换器
- (void)createAudioConverter {
//输出参数
AudioStreamBasicDescription outAudioDes = {0};
outAudioDes.mSampleRate = (Float64)self.configure.sampleRate; //采样率
outAudioDes.mChannelsPerFrame = (UInt32)self.configure.channelCount; //输出声道数
outAudioDes.mFormatID = kAudioFormatLinearPCM; //输出格式
outAudioDes.mFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); //编码 12
outAudioDes.mFramesPerPacket = 1; //每一个packet帧数 ;
outAudioDes.mBitsPerChannel = 16; //数据帧中每个通道的采样位数。
outAudioDes.mBytesPerFrame = outAudioDes.mBitsPerChannel / 8 *outAudioDes.mChannelsPerFrame; //每一帧大小(采样位数 / 8 *声道数)
outAudioDes.mBytesPerPacket = outAudioDes.mBytesPerFrame * outAudioDes.mFramesPerPacket; //每个packet大小(帧大小 * 帧数)
outAudioDes.mReserved = 0; //对其方式 0(8字节对齐)
//输入参数
AudioStreamBasicDescription inAduioDes = {0};
inAduioDes.mSampleRate = (Float64)self.configure.sampleRate;
inAduioDes.mFormatID = kAudioFormatMPEG4AAC;
inAduioDes.mFormatFlags = kMPEG4Object_AAC_LC;
inAduioDes.mFramesPerPacket = 1024;
inAduioDes.mChannelsPerFrame = (UInt32)self.configure.channelCount;
//填充输出相关信息
UInt32 inDesSize = sizeof(inAduioDes);
AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &inDesSize, &inAduioDes);
//获取编码器类型描述(只能传入software)
AudioClassDescription *audioClassDesc = [self getAudioClassDescriptionWithFormatID:outAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer ];
OSStatus status = AudioConverterNewSpecific(&inAduioDes, &outAudioDes, 1, audioClassDesc, &_audioConverter);
if (status != noErr) {
NSLog(@"AudioConverterNewSpecific error status: %d", (int)status);
return;
}
}
**获取编码器类型描述AudioClassDescription: **
(AudioClassDescription *)getAudioClassDescriptionWithFormatID:(AudioFormatID)formatID fromManufacture: (uint32_t)manufacturer {
static AudioClassDescription desc;
UInt32 decoderSpecific = formatID;
UInt32 size;//获取满足AAC编码器的总大小
OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Decoders, sizeof(decoderSpecific), &decoderSpecific, &size);
if (status != noErr) {
NSLog(@"AudioFormatGetPropertyInfo Decoder error status= %d", (int)status);
return nil;
}
unsigned int count = size / sizeof(AudioClassDescription);
AudioClassDescription description[count];
//将满足aac解码的解码器的信息写入数组
status = AudioFormatGetProperty(kAudioFormatProperty_Decoders, sizeof(decoderSpecific), &decoderSpecific, &size, &description);
if (status != noErr) {
NSLog(@"AudioFormatGetProperty Decoder error status= %d", (int)status);
return nil;
}
for (unsigned int i = 0; i < count; i++) {
if (formatID == description[i].mSubType && manufacturer == description[i].mManufacturer) {
desc = description[i];
return &desc;
}
}
return nil;
}
3.2解码
- (void)audioDecode:(NSData *)data {
if (!_audioConverter) { return; }
dispatch_async(self.decoderQueue, ^{
//记录需要解码的数据, 作为参数传入解码回调函数
CQAudioUserData userData = {0};
userData.channelCount = (UInt32)self.configure.channelCount;
userData.data = (char *)[data bytes];
userData.size = (UInt32)data.length;
userData.packetDesc.mDataByteSize = (UInt32)data.length;
userData.packetDesc.mStartOffset = 0;
userData.packetDesc.mVariableFramesInPacket = 0;
//输出大小和packet个数
UInt32 pcmBufferSize = (UInt32)(2048 * self.configure.channelCount);
UInt32 pcmDataPacketSize = 1024;
//创建临时容器pcm
uint8_t *pcmBuffer = malloc(pcmBufferSize);
memset(pcmBuffer, 0, pcmBufferSize);
//输出outBufferList
AudioBufferList outBufferList = {0};
outBufferList.mNumberBuffers = 1;
outBufferList.mBuffers[0].mNumberChannels = (uint32_t)self.configure.channelCount;
outBufferList.mBuffers[0].mDataByteSize = (UInt32)pcmBufferSize;
outBufferList.mBuffers[0].mData = pcmBuffer;
AudioStreamPacketDescription outPacketDescription = {0};
//配置填充函数,获取输出数据
OSStatus status = AudioConverterFillComplexBuffer(self->_audioConverter, &outDecodeDataProc, &userData, &pcmDataPacketSize, &outBufferList, &outPacketDescription);
if (status != noErr) {
NSLog(@"AAC Decoder error status: %d",(int)status);
return;
}
//如果获取到数据
if (outBufferList.mBuffers[0].mDataByteSize > 0) {
NSData *decodedData = [NSData dataWithBytes:outBufferList.mBuffers[0].mData length:outBufferList.mBuffers[0].mDataByteSize];
dispatch_async(self->_callbackQueue, ^{
[self->_delegate audioDecodeCallback:decodedData];
});
}
free(pcmBuffer);
});
}
解码器回调函数
static OSStatus outDecodeDataProc( AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
CQAudioUserData *audioDecoder = (CQAudioUserData *)(inUserData);
if (audioDecoder->size <= 0) {
ioNumberDataPackets = 0;
return -1;
}
//传入数据
*outDataPacketDescription = &audioDecoder->packetDesc;
(*outDataPacketDescription)[0].mStartOffset = 0;
(*outDataPacketDescription)[0].mDataByteSize = audioDecoder->size;
(*outDataPacketDescription)[0].mVariableFramesInPacket = 0;
ioData->mBuffers[0].mData = audioDecoder->data;
ioData->mBuffers[0].mDataByteSize = audioDecoder->size;
ioData->mBuffers[0].mNumberChannels = audioDecoder->channelCount;
return noErr;
}
最后,解码后的数据直接使用我们上面创建的PCM
播放器去播放即可。