1.memset: 原型: void * memset(void * __b, int __c, size_t __len); 解释:将s中当前位置后面的n个字节(typedef unsigned int size_t) 用ch替换并返回s 做用:在一段内存块中填充某个特定的值,它是对较大的结构体或数组进行清零操做的一种最快方法。html
2.memcpy: 原型: void * memcpy(void * dest, const void * src, size_t n); 解释:从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中ios
3.void free(void *); 解释:释放内存,须要将malloc出来的内存通通释放掉,对于结构体要先将结构体中malloc出来的释放掉最后再释放掉结构体自己。git
1.OSStaus:状态码,若是没有错误返回0:(即noErr)github
2.AudioFormatGetPropertyInfo:express
原型:
AudioFormatGetPropertyInfo(
AudioFormatPropertyID inPropertyID,
UInt32 inSpecifierSize,
const void * __nullable inSpecifier,
UInt32 * outPropertyDataSize);
* 做用:检索给定属性的信息,好比编码器目标格式的size等
复制代码
3.AudioSessionGetProperty:编程
原型:
extern OSStatus
AudioSessionGetProperty(
AudioSessionPropertyID inID,
UInt32 *ioDataSize,
void *outData);
* 做用:获取指定AudioSession对象的inID属性的值(好比采样率,声道数等等)
复制代码
4.AudioUnitSetProperty数组
extern OSStatus
AudioUnitSetProperty( AudioUnit inUnit,
AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement,
const void * __nullable inData,
UInt32 inDataSize)
* 做用:设置AudioUnit特定属性的值,其中scope,element不理解可参考下文audio unit概念部分,这里能够设置音频流的各类参数,好比采样频率、量化位数、通道个数、每包中帧的个数等等
复制代码
AVFoundation框架中的AVAudioPlayer和AVAudioRecorder类,用法简单,可是不支持流式,也就意味着在播放音频前,必须等到整个音频加载完成后,才能开始播放音频;录音时,也必须等到录音结束后才能得到录音数据。缓存
在iOS和Mac OS X中,音频队列Audio Queues是一个用来录制和播放音频的软件对象,也就是说,能够用来录音和播放,录音可以获取实时的PCM原始音频数据。bash
数据介绍cookie
(1)In CBR (constant bit rate) formats, such as linear PCM and IMA/ADPCM, all packets are the same size.
(2)In VBR (variable bit rate) formats, such as AAC, Apple Lossless, and MP3, all packets have the same number of frames but the number of bits in each sample value can vary.
(3)In VFR (variable frame rate) formats, packets have a varying number of frames. There are no commonly used formats of this type.
(1)音频文件的组成:文件格式(或者音频容器)+数据格式(或者音频编码)
知识点:
(2).音频文件计算大小 简述:声卡对声音的处理质量能够用三个基本参数来衡量,即采样频率,采样位数和声道数。
知识点:
采样频率:单位时间内采样次数。采样频率越大,采样点之间的间隔就越小,数字化后获得的声音就越逼真,但相应的数据量就越大,声卡通常提供11.025kHz,22.05kHz和44.1kHz等不一样的采样频率。
采样位数:记录每次采样值数值大小的位数。采样位数一般有8bits或16bits两种,采样位数越大,所能记录的声音变化度就越细腻,相应的数据量就越大。
声道数:处理的声音是单声道仍是立体声。单声道在声音处理过程当中只有单数据流,而立体声则须要左右声道的两个数据流。显然,立体声的效果要好,但相应数据量要比单声道数据量加倍。
声音数据量的计算公式:数据量(字节 / 秒)=(采样频率(Hz)* 采样位数(bit)* 声道数)/ 8 单声道的声道数为1,立体声的声道数为2. 字节B,1MB=1024KB = 1024*1024B
(3)
(2).Audio Data Formats:经过设置一组属性代码能够和操做系统支持的任何格式一块儿工做。(包括采样率,比特率),对于AudioQueue与AudioUnit设置略有不一样。
struct AudioStreamBasicDescription
{
Float64 mSampleRate; // 采样率 :Hz
AudioFormatID mFormatID; // 采样数据的类型,PCM,AAC等
AudioFormatFlags mFormatFlags; // 每种格式特定的标志,无损编码 ,0表示没有
UInt32 mBytesPerPacket; // 一个数据包中的字节数
UInt32 mFramesPerPacket; // 一个数据包中的帧数,每一个packet的帧数。若是是未压缩的音频数据,值是1。动态帧率格式,这个值是一个较大的固定数字,好比说AAC的1024。若是是动态大小帧数(好比Ogg格式)设置为0。
UInt32 mBytesPerFrame; // 每一帧中的字节数
UInt32 mChannelsPerFrame; // 每一帧数据中的通道数,单声道为1,立体声为2
UInt32 mBitsPerChannel; // 每一个通道中的位数,1byte = 8bit
UInt32 mReserved; // 8字节对齐,填0
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
复制代码
###---------------------------- Audio Queue ---------------------------
.音频队列 — 详细请参考 Audio Queue,该文章中已有详细描述,再也不重复介绍,不懂请参考。
你能够将音频队列配合其余Core Audio的接口使用,再加上相对少许的自定义代码就能够在你的应用程序中建立一套完整的数字音频录制或播放解决方案。
一组音频队列缓冲区(audio queue buffers),每一个音频队列缓冲区都是一个存储音频数据的临时仓库
一个缓冲区队列(buffer queue),一个包含音频队列缓冲区的有序列表
一个你本身编写的音频队列回调函数(audio queue callback)
本例中涉及的一些宏定义,具体能够下载代码详细看
#define kBufferDurationSeconds .5
#define kXDXRecoderAudioBytesPerPacket 2
#define kXDXRecoderAACFramesPerPacket 1024
#define kXDXRecoderPCMTotalPacket 512
#define kXDXRecoderPCMFramesPerPacket 1
#define kXDXRecoderConverterEncodeBitRate 64000
#define kXDXAudioSampleRate 48000.0
复制代码
-(void)startRecorder {
// Reset pcm_buffer to save convert handle, 每次开始音频会话前初始化pcm_buffer, pcm_buffer用来在捕捉声音的回调中存储累加的PCM原始数据
memset(pcm_buffer, 0, pcm_buffer_size);
pcm_buffer_size = 0;
frameCount = 0;
// 是否正在录制
if (isRunning) {
// log4cplus_info("pcm", "Start recorder repeat");
return;
}
// 本例中采用log4打印log信息,若你没有能够不用,删除有关Log4的语句
// log4cplus_info("pcm", "starup PCM audio encoder");
// 设置采集的数据的类型为PCM
[self setUpRecoderWithFormatID:kAudioFormatLinearPCM];
OSStatus status = 0;
UInt32 size = sizeof(dataFormat);
// 编码器转码设置
[self convertBasicSetting];
// 这个if语句用来检测是否初始化本例对象成功,若是不成功重启三次,三次后若是失败能够进行其余处理
if (err != nil) {
NSString *error = nil;
for (int i = 0; i < 3; i++) {
usleep(100*1000);
error = [self convertBasicSetting];
if (error == nil) break;
}
// if init this class failed then restart three times , if failed again,can handle at there
// [self exitWithErr:error];
}
// 新建一个队列,第二个参数注册回调函数,第三个防止内存泄露
status = AudioQueueNewInput(&dataFormat, inputBufferHandler, (__bridge void *)(self), NULL, NULL, 0, &mQueue);
// log4cplus_info("pcm","AudioQueueNewInput status:%d",(int)status);
// 获取队列属性
status = AudioQueueGetProperty(mQueue, kAudioQueueProperty_StreamDescription, &dataFormat, &size);
// log4cplus_info("pcm","AudioQueueNewInput status:%u",(unsigned int)dataFormat.mFormatID);
// 这里将头信息添加到写入文件中,若文件数据为CBR,不须要添加,为VBR须要添加
[self copyEncoderCookieToFile];
// 能够计算得到,在这里使用的是固定大小
// bufferByteSize = [self computeRecordBufferSizeFrom:&dataFormat andDuration:kBufferDurationSeconds];
// log4cplus_info("pcm","pcm raw data buff number:%d, channel number:%u",
kNumberQueueBuffers,
dataFormat.mChannelsPerFrame);
// 设置三个音频队列缓冲区
for (int i = 0; i != kNumberQueueBuffers; i++) {
// 注意:为每一个缓冲区分配大小,可根据具体需求进行修改,可是必定要注意必须知足转换器的需求,转换器只有每次给1024帧数据才会完成一次转换,若是需求为采集数据量较少则用本例提供的pcm_buffer对数据进行累加后再处理
status = AudioQueueAllocateBuffer(mQueue, kXDXRecoderPCMTotalPacket*kXDXRecoderAudioBytesPerPacket*dataFormat.mChannelsPerFrame, &mBuffers[i]);
// 入队
status = AudioQueueEnqueueBuffer(mQueue, mBuffers[i], 0, NULL);
}
isRunning = YES;
hostTime = 0;
status = AudioQueueStart(mQueue, NULL);
log4cplus_info("pcm","AudioQueueStart status:%d",(int)status);
}
复制代码
初始化输出流的结构体描述
struct AudioStreamBasicDescription
{
Float64 mSampleRate; // 采样率 :Hz
AudioFormatID mFormatID; // 采样数据的类型,PCM,AAC等
AudioFormatFlags mFormatFlags; // 每种格式特定的标志,无损编码 ,0表示没有
UInt32 mBytesPerPacket; // 一个数据包中的字节数
UInt32 mFramesPerPacket; // 一个数据包中的帧数,每一个packet的帧数。若是是未压缩的音频数据,值是1。动态帧率格式,这个值是一个较大的固定数字,好比说AAC的1024。若是是动态大小帧数(好比Ogg格式)设置为0。
UInt32 mBytesPerFrame; // 每一帧中的字节数
UInt32 mChannelsPerFrame; // 每一帧数据中的通道数,单声道为1,立体声为2
UInt32 mBitsPerChannel; // 每一个通道中的位数,1byte = 8bit
UInt32 mReserved; // 8字节对齐,填0
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
复制代码
注意: kNumberQueueBuffers,音频队列可使用任意数量的缓冲区。你的应用程序制定它的数量。通常状况下这个数字是3。这样就可让给一个忙于将数据写入磁盘,同时另外一个在填充新的音频数据,第三个缓冲区在须要作磁盘I/O延迟补偿的时候可用
如何使用AudioQueue:
AudioQueueNewInput
// 做用:建立一个音频队列为了录制音频数据
原型:extern OSStatus
AudioQueueNewInput( const AudioStreamBasicDescription *inFormat, 同上
AudioQueueInputCallback inCallbackProc, // 注册回调函数
void * __nullable inUserData,
CFRunLoopRef __nullable inCallbackRunLoop,
CFStringRef __nullable inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef __nullable * __nonnull outAQ);
// 这个函数的第四个和第五个参数是有关于线程的,我设置成null,表明它默认使用内部线程去录音,并且仍是异步的
复制代码
-(void)setUpRecoderWithFormatID:(UInt32)formatID {
// Notice : The settings here are official recommended settings,can be changed according to specific requirements. 此处的设置为官方推荐设置,可根据具体需求修改部分设置
//setup auido sample rate, channel number, and format ID
memset(&dataFormat, 0, sizeof(dataFormat));
UInt32 size = sizeof(dataFormat.mSampleRate);
AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate,
&size,
&dataFormat.mSampleRate);
dataFormat.mSampleRate = kXDXAudioSampleRate; // 设置采样率
size = sizeof(dataFormat.mChannelsPerFrame);
AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareInputNumberChannels,
&size,
&dataFormat.mChannelsPerFrame);
dataFormat.mFormatID = formatID;
// 关于采集PCM数据是根据苹果官方文档给出的Demo设置,至于为何这么设置可能与采集回调函数内部实现有关,修改的话请谨慎
if (formatID == kAudioFormatLinearPCM)
{
/*
为保存音频数据的方式的说明,如能够根据大端字节序或小端字节序,
浮点数或整数以及不一样体位去保存数据
例如对PCM格式一般咱们以下设置:kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked等
*/
dataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
// 每一个通道里,一帧采集的bit数目
dataFormat.mBitsPerChannel = 16;
// 8bit为1byte,即为1个通道里1帧须要采集2byte数据,再*通道数,即为全部通道采集的byte数目
dataFormat.mBytesPerPacket = dataFormat.mBytesPerFrame = (dataFormat.mBitsPerChannel / 8) * dataFormat.mChannelsPerFrame;
// 每一个包中的帧数,采集PCM数据须要将dataFormat.mFramesPerPacket设置为1,不然回调不成功
dataFormat.mFramesPerPacket = kXDXRecoderPCMFramesPerPacket;
}
}
复制代码
-(NSString *)convertBasicSetting {
// 此处目标格式其余参数均为默认,系统会自动计算,不然没法进入encodeConverterComplexInputDataProc回调
AudioStreamBasicDescription sourceDes = dataFormat; // 原始格式
AudioStreamBasicDescription targetDes; // 转码后格式
// 设置目标格式及基本信息
memset(&targetDes, 0, sizeof(targetDes));
targetDes.mFormatID = kAudioFormatMPEG4AAC;
targetDes.mSampleRate = kXDXAudioSampleRate;
targetDes.mChannelsPerFrame = dataFormat.mChannelsPerFrame;
targetDes.mFramesPerPacket = kXDXRecoderAACFramesPerPacket; // 采集的为AAC须要将targetDes.mFramesPerPacket设置为1024,AAC软编码须要喂给转换器1024个样点才开始编码,这与回调函数中inNumPackets有关,不可随意更改
OSStatus status = 0;
UInt32 targetSize = sizeof(targetDes);
status = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &targetSize, &targetDes);
// log4cplus_info("pcm", "create target data format status:%d",(int)status);
memset(&_targetDes, 0, sizeof(_targetDes));
// 赋给全局变量
memcpy(&_targetDes, &targetDes, targetSize);
// 选择软件编码
AudioClassDescription audioClassDes;
status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders,
sizeof(targetDes.mFormatID),
&targetDes.mFormatID,
&targetSize);
// log4cplus_info("pcm","get kAudioFormatProperty_Encoders status:%d",(int)status);
// 计算编码器容量
UInt32 numEncoders = targetSize/sizeof(AudioClassDescription);
// 用数组存放编码器内容
AudioClassDescription audioClassArr[numEncoders];
// 将编码器属性赋给数组
AudioFormatGetProperty(kAudioFormatProperty_Encoders,
sizeof(targetDes.mFormatID),
&targetDes.mFormatID,
&targetSize,
audioClassArr);
// log4cplus_info("pcm","wrirte audioClassArr status:%d",(int)status);
// 遍历数组,设置软编
for (int i = 0; i < numEncoders; i++) {
if (audioClassArr[i].mSubType == kAudioFormatMPEG4AAC && audioClassArr[i].mManufacturer == kAppleSoftwareAudioCodecManufacturer) {
memcpy(&audioClassDes, &audioClassArr[i], sizeof(AudioClassDescription));
break;
}
}
// 防止内存泄露
if (_encodeConvertRef == NULL) {
// 新建一个编码对象,设置原,目标格式
status = AudioConverterNewSpecific(&sourceDes, &targetDes, 1,
&audioClassDes, &_encodeConvertRef);
if (status != noErr) {
// log4cplus_info("Audio Recoder","new convertRef failed status:%d \n",(int)status);
return @"Error : New convertRef failed \n";
}
}
// 获取原始格式大小
targetSize = sizeof(sourceDes);
status = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCurrentInputStreamDescription, &targetSize, &sourceDes);
// log4cplus_info("pcm","get sourceDes status:%d",(int)status);
// 获取目标格式大小
targetSize = sizeof(targetDes);
status = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCurrentOutputStreamDescription, &targetSize, &targetDes);;
// log4cplus_info("pcm","get targetDes status:%d",(int)status);
// 设置码率,须要和采样率对应
UInt32 bitRate = kXDXRecoderConverterEncodeBitRate;
targetSize = sizeof(bitRate);
status = AudioConverterSetProperty(_encodeConvertRef,
kAudioConverterEncodeBitRate,
targetSize, &bitRate);
// log4cplus_info("pcm","set covert property bit rate status:%d",(int)status);
if (status != noErr) {
// log4cplus_info("Audio Recoder","set covert property bit rate status:%d",(int)status);
return @"Error : Set covert property bit rate failed";
}
return nil;
}
复制代码
AudioFormatGetProperty:
原型:
extern OSStatus
AudioFormatGetProperty( AudioFormatPropertyID inPropertyID,
UInt32 inSpecifierSize,
const void * __nullable inSpecifier,
UInt32 * __nullable ioPropertyDataSize,
void * __nullabl outPropertyData);
做用:检索某个属性的值
复制代码
AudioClassDescription:
指的是一个可以对一个信号或者一个数据流进行变换的设备或者程序。这里指的变换既包括将 信号或者数据流进行编码(一般是为了传输、存储或者加密)或者提取获得一个编码流的操做,也包括为了观察或者处理从这个编码流中恢复适合观察或操做的形式的操做。编解码器常常用在视频会议和流媒体等应用中。
默认状况下,Apple会建立一个硬件编码器,若是硬件不可用,会建立软件编码器。
通过个人测试,硬件AAC编码器的编码时延很高,须要buffer大约2秒的数据才会开始编码。而软件编码器的编码时延就是正常的,只要喂给1024个样点,就会开始编码。
AudioConverterNewSpecific:
原型: extern OSStatus
AudioConverterNewSpecific( const AudioStreamBasicDescription * inSourceFormat,
const AudioStreamBasicDescription * inDestinationFormat,
UInt32 inNumberClassDescriptions,
const AudioClassDescription * inClassDescriptions,
AudioConverterRef __nullable * __nonnull outAudioConverter);
解释:建立一个转换器
做用:设置一些转码基本信息
复制代码
AudioConverterSetProperty:
原型:extern OSStatus
AudioConverterSetProperty( AudioConverterRef inAudioConverter,
AudioConverterPropertyID inPropertyID,
UInt32 inPropertyDataSize,
const void * inPropertyData);
做用:设置码率,须要注意,AAC并非随便的码率均可以支持。好比若是PCM采样率是44100KHz,那么码率能够设置64000bps,若是是16K,能够设置为32000bps。
复制代码
-(void)copyEncoderCookieToFile
{
// Grab the cookie from the converter and write it to the destination file.
UInt32 cookieSize = 0;
OSStatus error = AudioConverterGetPropertyInfo(_encodeConvertRef, kAudioConverterCompressionMagicCookie, &cookieSize, NULL);
// If there is an error here, then the format doesn't have a cookie - this is perfectly fine as som formats do not. // log4cplus_info("cookie","cookie status:%d %d",(int)error, cookieSize); if (error == noErr && cookieSize != 0) { char *cookie = (char *)malloc(cookieSize * sizeof(char)); // UInt32 *cookie = (UInt32 *)malloc(cookieSize * sizeof(UInt32)); error = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCompressionMagicCookie, &cookieSize, cookie); // log4cplus_info("cookie","cookie size status:%d",(int)error); if (error == noErr) { error = AudioFileSetProperty(mRecordFile, kAudioFilePropertyMagicCookieData, cookieSize, cookie); // log4cplus_info("cookie","set cookie status:%d ",(int)error); if (error == noErr) { UInt32 willEatTheCookie = false; error = AudioFileGetPropertyInfo(mRecordFile, kAudioFilePropertyMagicCookieData, NULL, &willEatTheCookie); printf("Writing magic cookie to destination file: %u\n cookie:%d \n", (unsigned int)cookieSize, willEatTheCookie); } else { printf("Even though some formats have cookies, some files don't take them and that's OK\n"); } } else { printf("Could not Get kAudioConverterCompressionMagicCookie from Audio Converter!\n"); } free(cookie); } } 复制代码
Magic cookie 是一种不透明的数据格式,它和压缩数据文件与流联系密切,若是文件数据为CBR格式(无损),则不须要添加头部信息,若是为VBR须要添加,// if collect CBR needn't set magic cookie , if collect VBR should set magic cookie, if needn't to convert format that can be setting by audio queue directly.
// AudioQueue中注册的回调函数
static void inputBufferHandler(void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp * inStartTime,
UInt32 inNumPackets,
const AudioStreamPacketDescription* inPacketDesc) {
// 至关于本类对象实例
TVURecorder *recoder = (TVURecorder *)inUserData;
/*
inNumPackets 总包数:音频队列缓冲区大小 (在先前估算缓存区大小为kXDXRecoderAACFramesPerPacket*2)/ (dataFormat.mFramesPerPacket (采集数据每一个包中有多少帧,此处在初始化设置中为1) * dataFormat.mBytesPerFrame(每一帧中有多少个字节,此处在初始化设置中为每一帧中两个字节)),因此能够根据该公式计算捕捉PCM数据时inNumPackets。
注意:若是采集的数据是PCM须要将dataFormat.mFramesPerPacket设置为1,而本例中最终要的数据为AAC,由于本例中使用的转换器只有每次传入1024帧才能开始工做,因此在AAC格式下须要将mFramesPerPacket设置为1024.也就是采集到的inNumPackets为1,在转换器中传入的inNumPackets应该为AAC格式下默认的1,在此后写入文件中也应该传的是转换好的inNumPackets,若是有特殊需求须要将采集的数据量小于1024,那么须要将每次捕捉到的数据先预先存储在一个buffer中,等到攒够1024帧再进行转换。
*/
// collect pcm data,能够在此存储
// First case : collect data not is 1024 frame, if collect data not is 1024 frame, we need to save data to pcm_buffer untill 1024 frame
memcpy(pcm_buffer+pcm_buffer_size, inBuffer->mAudioData, inBuffer->mAudioDataByteSize);
pcm_buffer_size = pcm_buffer_size + inBuffer->mAudioDataByteSize;
if(inBuffer->mAudioDataByteSize != kXDXRecoderAACFramesPerPacket*2)
NSLog(@"write pcm buffer size:%d, totoal buff size:%d", inBuffer->mAudioDataByteSize, pcm_buffer_size);
frameCount++;
// Second case : If the size of the data collection is not required, we can let mic collect 1024 frame so that don't need to write firtst case, but it is recommended to write the above code because of agility // if collect data is added to 1024 frame if(frameCount == totalFrames) { AudioBufferList *bufferList = convertPCMToAAC(recoder); pcm_buffer_size = 0; frameCount = 0; // free memory free(bufferList->mBuffers[0].mData); free(bufferList); // begin write audio data for record audio only // 出队 AudioQueueRef queue = recoder.mQueue; if (recoder.isRunning) { AudioQueueEnqueueBuffer(queue, inBuffer, 0, NULL); } } } 复制代码
解析回调函数:至关于中断服务函数,每次录取到音频数据就进入这个函数
注意:inNumPackets 总包数:音频队列缓冲区大小 (在先前估算缓存区大小为2048)/ (dataFormat.mFramesPerPacket (采集数据每一个包中有多少帧,此处在初始化设置中为1) * dataFormat.mBytesPerFrame(每一帧中有多少个字节,此处在初始化设置中为每一帧中两个字节))
// PCM -> AAC
AudioBufferList* convertPCMToAAC (AudioQueueBufferRef inBuffer, XDXRecorder *recoder) {
UInt32 maxPacketSize = 0;
UInt32 size = sizeof(maxPacketSize);
OSStatus status;
status = AudioConverterGetProperty(_encodeConvertRef,
kAudioConverterPropertyMaximumOutputPacketSize,
&size,
&maxPacketSize);
// log4cplus_info("AudioConverter","kAudioConverterPropertyMaximumOutputPacketSize status:%d \n",(int)status);
// 初始化一个bufferList存储数据
AudioBufferList *bufferList = (AudioBufferList *)malloc(sizeof(AudioBufferList));
bufferList->mNumberBuffers = 1;
bufferList->mBuffers[0].mNumberChannels = _targetDes.mChannelsPerFrame;
bufferList->mBuffers[0].mData = malloc(maxPacketSize);
bufferList->mBuffers[0].mDataByteSize = pcm_buffer_size;
AudioStreamPacketDescription outputPacketDescriptions;
/*
inNumPackets设置为1表示编码产生1帧数据即返回,官方:On entry, the capacity of outOutputData expressed in packets in the converter's output format. On exit, the number of packets of converted data that were written to outOutputData. 在输入表示输出数据的最大容纳能力 在转换器的输出格式上,在转换完成时表示多少个包被写入 */ UInt32 inNumPackets = 1; status = AudioConverterFillComplexBuffer(_encodeConvertRef, encodeConverterComplexInputDataProc, // 填充数据的回调函数 pcm_buffer, // 音频队列缓冲区中数据 &inNumPackets, bufferList, // 成功后将值赋给bufferList &outputPacketDescriptions); // 输出包包含的一些信息 log4cplus_info("AudioConverter","set AudioConverterFillComplexBuffer status:%d",(int)status); if (recoder.needsVoiceDemo) { // if inNumPackets set not correct, file will not normally play. 将转换器转换出来的包写入文件中,inNumPackets表示写入文件的起始位置 OSStatus status = AudioFileWritePackets(recoder.mRecordFile, FALSE, bufferList->mBuffers[0].mDataByteSize, &outputPacketDescriptions, recoder.mRecordPacket, &inNumPackets, bufferList->mBuffers[0].mData); // log4cplus_info("write file","write file status = %d",(int)status); recoder.mRecordPacket += inNumPackets; // Used to record the location of the write file,用于记录写入文件的位置 } return bufferList; } 复制代码
解析
outputPacketDescriptions数组是每次转换的AAC编码后各个包的描述,但这里每次只转换一包数据(由传入的packetSize决定)。调用AudioConverterFillComplexBuffer触发转码,他的第二个参数是填充原始音频数据的回调。转码完成后,会将转码的数据存放在它的第五个参数中(bufferList).
// 录制声音功能
-(void)startVoiceDemo
{
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = [[searchPaths objectAtIndex:0] stringByAppendingPathComponent:@"VoiceDemo"];
OSStatus status;
// Get the full path to our file.
NSString *fullFileName = [NSString stringWithFormat:@"%@.%@",[[XDXDateTool shareXDXDateTool] getDateWithFormat_yyyy_MM_dd_HH_mm_ss],@"caf"];
NSString *filePath = [documentPath stringByAppendingPathComponent:fullFileName];
[mRecordFilePath release];
mRecordFilePath = [filePath copy];;
CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)filePath, NULL);
// create the audio file
status = AudioFileCreateWithURL(url, kAudioFileMPEG4Type, &_targetDes, kAudioFileFlags_EraseFile, &mRecordFile);
if (status != noErr) {
// log4cplus_info("Audio Recoder","AudioFileCreateWithURL Failed, status:%d",(int)status);
}
CFRelease(url);
// add magic cookie contain header file info for VBR data
[self copyEncoderCookieToFile];
mNeedsVoiceDemo = YES;
NSLog(@"%s",__FUNCTION__);
}
复制代码
##--------------------------- Audio Unit -----------------------------
1). AudioUnit是 iOS提供的为了支持混音,均衡,格式转换,实时输入输出用于录制,回放,离线渲染和实时回话(VOIP),这让咱们能够动态加载和使用,即从iOS应用程序中接收这些强大而灵活的插件。它是iOS音频中最低层,因此除非你须要合成声音的实时播放,低延迟的I/O,或特定声音的特定特色。 Audio unit scopes and elements :
上图是一个AudioUnit的组成结构,A scope 主要使用到的输入kAudioUnitScope_Input和输出kAudioUnitScope_Output。Element 是嵌套在audio unit scope的编程上下文。
AudioUnit 的Remote IO有2个element,大部分代码和文献都用bus代替element,二者同义,bus0就是输出,bus 1表明输入,播放音频文件就是在bus 0传送数据,bus 1输入在Remote IO 默认是关闭的,在录音的状态下 须要把bus 1设置成开启状态。
咱们能使用(kAudioOutputUnitProperty_EnableIO)属性独立地开启或禁用每一个element,Element 1 直接与音频输入硬件相连(麦克风),Element 1 的input scope对咱们是不透明的,来自输入硬件的音频数据只能在Element 1的output scope中访问。
一样的element 0直接和输出硬件相连(扬声器),咱们能够将audio数据传输到element 0的input scope中,可是output scope对咱们是不透明的。
注意:每一个element自己都有一个输入范围和输出范围,所以在代码中若是不理解可能会比较懵逼,好比你从input element的 output scope 中受到音频,并将音频发送到output element的intput scope中,若是代码中不理解,能够再看看上图。
2 - 1. I/O Units : iOS提供了3种I/O Units.
1). 导入所需动态库与头文件(At runtime, obtain a reference to the dynamically-linkable library that defines an audio unit you want to use.)
2). 实例化audio unit(Instantiate the audio unit.)
3). 配置audioUnit的类型去完成特定的需求(Configure the audio unit as required for its type and to accomodate the intent of your app.)
4). 初始化uandio unit(Initialize the audio unit to prepare it to handle audio. )
5). 开始audio flow(Start audio flow.)
6). 控制audio unit(Control the audio unit.)
7). 结束后回收audio unit(When finished, deallocate the audio unit.)
- (void)initAudioComponent {
OSStatus status;
// 配置AudioUnit基本信息
AudioComponentDescription audioDesc;
audioDesc.componentType = kAudioUnitType_Output;
// 若是你的应用程序须要去除回声将componentSubType设置为kAudioUnitSubType_VoiceProcessingIO,不然根据需求设置为其余,在博客中有介绍
audioDesc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;//kAudioUnitSubType_VoiceProcessingIO;
// 苹果本身的标志
audioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
audioDesc.componentFlags = 0;
audioDesc.componentFlagsMask = 0;
AudioComponent inputComponent = AudioComponentFindNext(NULL, &audioDesc);
// 新建一个AudioComponent对象,只有这步完成才能进行后续步骤,因此顺序不可颠倒
status = AudioComponentInstanceNew(inputComponent, &_audioUnit);
if (status != noErr) {
_audioUnit = NULL;
// log4cplus_info("Audio Recoder", "couldn't create a new instance of AURemoteIO, status : %d \n",status);
}
}
复制代码
解析
- (void)initBuffer {
// 禁用AudioUnit默认的buffer而使用咱们本身写的全局BUFFER,用来接收每次采集的PCM数据,Disable AU buffer allocation for the recorder, we allocate our own.
UInt32 flag = 0;
OSStatus status = AudioUnitSetProperty(_audioUnit,
kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output,
INPUT_BUS,
&flag,
sizeof(flag));
if (status != noErr) {
// log4cplus_info("Audio Recoder", "couldn't AllocateBuffer of AudioUnitCallBack, status : %d \n",status);
}
_buffList = (AudioBufferList*)malloc(sizeof(AudioBufferList));
_buffList->mNumberBuffers = 1;
_buffList->mBuffers[0].mNumberChannels = dataFormat.mChannelsPerFrame;
_buffList->mBuffers[0].mDataByteSize = kTVURecoderPCMMaxBuffSize * sizeof(short);
_buffList->mBuffers[0].mData = (short *)malloc(sizeof(short) * kTVURecoderPCMMaxBuffSize);
}
复制代码
解析
本例经过禁用AudioUnit默认的buffer而使用咱们本身写的全局BUFFER,用来接收每次采集的PCM数据,Disable AU buffer allocation for the recorder, we allocate our own.还有一种写法是可使用回调中提供的ioData存储采集的数据,这里使用全局的buff是为了供其余地方使用,可根据须要自行决定采用哪一种方式,若不采用全局buffer则不可采用上述禁用操做。
// 由于本例只作录音功能,未实现播放功能,因此没有设置播放相关设置。
- (void)setAudioUnitPropertyAndFormat {
OSStatus status;
[self setUpRecoderWithFormatID:kAudioFormatLinearPCM];
// 应用audioUnit设置的格式
status = AudioUnitSetProperty(_audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
INPUT_BUS,
&dataFormat,
sizeof(dataFormat));
if (status != noErr) {
// log4cplus_info("Audio Recoder", "couldn't set the input client format on AURemoteIO, status : %d \n",status);
}
// 去除回声开关
UInt32 echoCancellation;
AudioUnitSetProperty(_audioUnit,
kAUVoiceIOProperty_BypassVoiceProcessing,
kAudioUnitScope_Global,
0,
&echoCancellation,
sizeof(echoCancellation));
// AudioUnit输入端默认是关闭,须要将他打开
UInt32 flag = 1;
status = AudioUnitSetProperty(_audioUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
INPUT_BUS,
&flag,
sizeof(flag));
if (status != noErr) {
// log4cplus_info("Audio Recoder", "could not enable input on AURemoteIO, status : %d \n",status);
}
}
-(void)setUpRecoderWithFormatID:(UInt32)formatID {
// Notice : The settings here are official recommended settings,can be changed according to specific requirements. 此处的设置为官方推荐设置,可根据具体需求修改部分设置
//setup auido sample rate, channel number, and format ID
memset(&dataFormat, 0, sizeof(dataFormat));
UInt32 size = sizeof(dataFormat.mSampleRate);
AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate,
&size,
&dataFormat.mSampleRate);
dataFormat.mSampleRate = kXDXAudioSampleRate;
size = sizeof(dataFormat.mChannelsPerFrame);
AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareInputNumberChannels,
&size,
&dataFormat.mChannelsPerFrame);
dataFormat.mFormatID = formatID;
dataFormat.mChannelsPerFrame = 1;
if (formatID == kAudioFormatLinearPCM) {
if (self.releaseMethod == XDXRecorderReleaseMethodAudioQueue) {
dataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
}else if (self.releaseMethod == XDXRecorderReleaseMethodAudioQueue) {
dataFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
}
dataFormat.mBitsPerChannel = 16;
dataFormat.mBytesPerPacket = dataFormat.mBytesPerFrame = (dataFormat.mBitsPerChannel / 8) * dataFormat.mChannelsPerFrame;
dataFormat.mFramesPerPacket = kXDXRecoderPCMFramesPerPacket; // 用AudioQueue采集pcm须要这么设置
}
}
复制代码
解析
上述操做针对录音功能须要对Audio Unit作出对应设置,首先设置ASBD采集数据为PCM的格式,须要注意的是若是是使用AudioQueue与AudioUnit的dataFormat.mFormatFlags设置略有不一样,经测试必须这样设置,缘由暂不详,设置完后使用AudioUnitSetProperty应用设置,这里只作录音,因此对kAudioOutputUnitProperty_EnableIO 的 kAudioUnitScope_Input 开启,而对kAudioUnitScope_Output 输入端输出的音频格式进行设置,若是不理解可参照1中概念解析进行理解,kAUVoiceIOProperty_BypassVoiceProcessing则是回声的开关。
-(NSString *)convertBasicSetting {
// 此处目标格式其余参数均为默认,系统会自动计算,不然没法进入encodeConverterComplexInputDataProc回调
AudioStreamBasicDescription sourceDes = dataFormat; // 原始格式
AudioStreamBasicDescription targetDes; // 转码后格式
// 设置目标格式及基本信息
memset(&targetDes, 0, sizeof(targetDes));
targetDes.mFormatID = kAudioFormatMPEG4AAC;
targetDes.mSampleRate = kXDXAudioSampleRate;
targetDes.mChannelsPerFrame = dataFormat.mChannelsPerFrame;
targetDes.mFramesPerPacket = kXDXRecoderAACFramesPerPacket; // 采集的为AAC须要将targetDes.mFramesPerPacket设置为1024,AAC软编码须要喂给转换器1024个样点才开始编码,这与回调函数中inNumPackets有关,不可随意更改
OSStatus status = 0;
UInt32 targetSize = sizeof(targetDes);
status = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &targetSize, &targetDes);
// log4cplus_info("pcm", "create target data format status:%d",(int)status);
memset(&_targetDes, 0, sizeof(_targetDes));
// 赋给全局变量
memcpy(&_targetDes, &targetDes, targetSize);
// 选择软件编码
AudioClassDescription audioClassDes;
status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders,
sizeof(targetDes.mFormatID),
&targetDes.mFormatID,
&targetSize);
// log4cplus_info("pcm","get kAudioFormatProperty_Encoders status:%d",(int)status);
// 计算编码器容量
UInt32 numEncoders = targetSize/sizeof(AudioClassDescription);
// 用数组存放编码器内容
AudioClassDescription audioClassArr[numEncoders];
// 将编码器属性赋给数组
AudioFormatGetProperty(kAudioFormatProperty_Encoders,
sizeof(targetDes.mFormatID),
&targetDes.mFormatID,
&targetSize,
audioClassArr);
// log4cplus_info("pcm","wrirte audioClassArr status:%d",(int)status);
// 遍历数组,设置软编
for (int i = 0; i < numEncoders; i++) {
if (audioClassArr[i].mSubType == kAudioFormatMPEG4AAC && audioClassArr[i].mManufacturer == kAppleSoftwareAudioCodecManufacturer) {
memcpy(&audioClassDes, &audioClassArr[i], sizeof(AudioClassDescription));
break;
}
}
// 防止内存泄露
if (_encodeConvertRef == NULL) {
// 新建一个编码对象,设置原,目标格式
status = AudioConverterNewSpecific(&sourceDes, &targetDes, 1,
&audioClassDes, &_encodeConvertRef);
if (status != noErr) {
// log4cplus_info("Audio Recoder","new convertRef failed status:%d \n",(int)status);
return @"Error : New convertRef failed \n";
}
}
// 获取原始格式大小
targetSize = sizeof(sourceDes);
status = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCurrentInputStreamDescription, &targetSize, &sourceDes);
// log4cplus_info("pcm","get sourceDes status:%d",(int)status);
// 获取目标格式大小
targetSize = sizeof(targetDes);
status = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCurrentOutputStreamDescription, &targetSize, &targetDes);;
// log4cplus_info("pcm","get targetDes status:%d",(int)status);
// 设置码率,须要和采样率对应
UInt32 bitRate = kXDXRecoderConverterEncodeBitRate;
targetSize = sizeof(bitRate);
status = AudioConverterSetProperty(_encodeConvertRef,
kAudioConverterEncodeBitRate,
targetSize, &bitRate);
// log4cplus_info("pcm","set covert property bit rate status:%d",(int)status);
if (status != noErr) {
// log4cplus_info("Audio Recoder","set covert property bit rate status:%d",(int)status);
return @"Error : Set covert property bit rate failed";
}
return nil;
}
复制代码
解析
设置原格式与转码格式并建立_encodeConvertRef转码器对象完成相关初始化操做,值得注意的是targetDes.mFramesPerPacket设置为1024,AAC软编码须要喂给转换器1024个样点才开始编码,不可随意更改,缘由以下图,由AAC编码器决定。
- (void)initRecordeCallback {
// 设置回调,有两种方式,一种是采集pcm的BUFFER使用系统回调中的参数,另外一种是使用咱们本身的,本例中使用的是本身的,因此回调中的ioData为空。
// 方法1:
AURenderCallbackStruct recordCallback;
recordCallback.inputProc = RecordCallback;
recordCallback.inputProcRefCon = (__bridge void *)self;
OSStatus status = AudioUnitSetProperty(_audioUnit,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
INPUT_BUS,
&recordCallback,
sizeof(recordCallback));
// 方法2:
AURenderCallbackStruct renderCallback;
renderCallback.inputProc = RecordCallback;
renderCallback.inputProcRefCon = (__bridge void *)self;
AudioUnitSetProperty(_rioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, & RecordCallback, sizeof(RecordCallback));
if (status != noErr) {
// log4cplus_info("Audio Recoder", "Audio Unit set record Callback failed, status : %d \n",status);
}
}
复制代码
解析
以上为设置采集回调,有两种方式,1种为使用咱们本身的buffer,这样须要先在上述initBuffer中禁用系统的buffer,则回调函数中每次渲染的为咱们本身的buffer,另外一种则是使用系统的buffer,对应须要在回调函数中将ioData放进渲染的函数中。
static OSStatus RecordCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
/*
注意:若是采集的数据是PCM须要将dataFormat.mFramesPerPacket设置为1,而本例中最终要的数据为AAC,由于本例中使用的转换器只有每次传入1024帧才能开始工做,因此在AAC格式下须要将mFramesPerPacket设置为1024.也就是采集到的inNumPackets为1,在转换器中传入的inNumPackets应该为AAC格式下默认的1,在此后写入文件中也应该传的是转换好的inNumPackets,若是有特殊需求须要将采集的数据量小于1024,那么须要将每次捕捉到的数据先预先存储在一个buffer中,等到攒够1024帧再进行转换。
*/
XDXRecorder *recorder = (XDXRecorder *)inRefCon;
// 将回调数据传给_buffList
AudioUnitRender(recorder->_audioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, recorder->_buffList);
void *bufferData = recorder->_buffList->mBuffers[0].mData;
UInt32 bufferSize = recorder->_buffList->mBuffers[0].mDataByteSize;
// printf("Audio Recoder Render dataSize : %d \n",bufferSize);
// 因为PCM转成AAC的转换器每次须要有1024个采样点(每一帧2个字节)才能完成一次转换,因此每次须要2048大小的数据,这里定义的pcm_buffer用来累加每次存储的bufferData
memcpy(pcm_buffer+pcm_buffer_size, bufferData, bufferSize);
pcm_buffer_size = pcm_buffer_size + bufferSize;
if(pcm_buffer_size >= kTVURecoderPCMMaxBuffSize) {
AudioBufferList *bufferList = convertPCMToAAC(recorder);
// 由于采样不可能每次都精准的采集到1024个样点,因此若是大于2048大小就先填满2048,剩下的跟着下一次采集一块儿送给转换器
memcpy(pcm_buffer, pcm_buffer + kTVURecoderPCMMaxBuffSize, pcm_buffer_size - kTVURecoderPCMMaxBuffSize);
pcm_buffer_size = pcm_buffer_size - kTVURecoderPCMMaxBuffSize;
// free memory
if(bufferList) {
free(bufferList->mBuffers[0].mData);
free(bufferList);
}
}
return noErr;
}
复制代码
解析
在该回调中若是采用咱们本身定义的全局buffer,则回调函数参数中的ioData为NULL,再也不使用,若是想使用ioData按照上述设置并将其放入AudioUnitRender函数中进行渲染,回调函数中采用pcm_buffer存储满2048个字节的数组传给转换器,这是编码器的特性,因此若是采集的数据小于2048先取pcm_buffer的前2048个字节,后面的数据与下次采集的PCM数据累加在一块儿。上述转换过程在AudioQueue中已经有介绍,逻辑彻底相同,可在上文中阅读。
-(void)copyEncoderCookieToFile
{
// Grab the cookie from the converter and write it to the destination file.
UInt32 cookieSize = 0;
OSStatus error = AudioConverterGetPropertyInfo(_encodeConvertRef, kAudioConverterCompressionMagicCookie, &cookieSize, NULL);
// If there is an error here, then the format doesn't have a cookie - this is perfectly fine as som formats do not. // log4cplus_info("cookie","cookie status:%d %d",(int)error, cookieSize); if (error == noErr && cookieSize != 0) { char *cookie = (char *)malloc(cookieSize * sizeof(char)); // UInt32 *cookie = (UInt32 *)malloc(cookieSize * sizeof(UInt32)); error = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCompressionMagicCookie, &cookieSize, cookie); // log4cplus_info("cookie","cookie size status:%d",(int)error); if (error == noErr) { error = AudioFileSetProperty(mRecordFile, kAudioFilePropertyMagicCookieData, cookieSize, cookie); // log4cplus_info("cookie","set cookie status:%d ",(int)error); if (error == noErr) { UInt32 willEatTheCookie = false; error = AudioFileGetPropertyInfo(mRecordFile, kAudioFilePropertyMagicCookieData, NULL, &willEatTheCookie); printf("Writing magic cookie to destination file: %u\n cookie:%d \n", (unsigned int)cookieSize, willEatTheCookie); } else { printf("Even though some formats have cookies, some files don't take them and that's OK\n"); } } else { printf("Could not Get kAudioConverterCompressionMagicCookie from Audio Converter!\n"); } free(cookie); } } 复制代码
解析
Magic cookie 是一种不透明的数据格式,它和压缩数据文件与流联系密切,若是文件数据为CBR格式(无损),则不须要添加头部信息,若是为VBR须要添加,// if collect CBR needn't set magic cookie , if collect VBR should set magic cookie, if needn't to convert format that can be setting by audio queue directly.
- (void)startAudioUnitRecorder {
OSStatus status;
if (isRunning) {
// log4cplus_info("Audio Recoder", "Start recorder repeat \n");
return;
}
[self initGlobalVar];
// log4cplus_info("Audio Recoder", "starup PCM audio encoder \n");
status = AudioOutputUnitStart(_audioUnit);
// log4cplus_info("Audio Recoder", "AudioOutputUnitStart status : %d \n",status);
if (status == noErr) {
isRunning = YES;
hostTime = 0;
}
}
-(void)stopAudioUnitRecorder {
if (isRunning == NO) {
// log4cplus_info("Audio Recoder", "Stop recorder repeat \n");
return;
}
// log4cplus_info("Audio Recoder","stop pcm encoder \n");
isRunning = NO;
[self copyEncoderCookieToFile];
OSStatus status = AudioOutputUnitStop(_audioUnit);
if (status != noErr){
// log4cplus_info("Audio Recoder", "stop AudioUnit failed. \n");
}
AudioFileClose(mRecordFile);
}
复制代码
解析
因为AudioUnit的初始化在本类中初始化方法中完成,因此只须要调用start,stop方法便可控制录制转码过程。切记不可在start方法中完成audio unit对象的建立和初始化,不然会发生异常。