不同格式音频间的转换。
iOS平台提供了AudioConverter这个接口来实现转换,这个接口在内存实现转换,不需要写文件(ExtAudioFile是对文件的操作,内部使用audioconverter来转换)。
下面的例子演示从pcm转aac的实现(比如录音出来的数据保存成aac的需求)。
adts aac每一个packet都是独立可播放的包(同mp3),每一个packet带packet信息(包括packet长度、采样率、声道数等),整个文件不带文件头信息。
m4a格式:{packet-table}{audio_data}{trailer},头信息之后就是音频裸数据,不带packet信息。
audioconverter转换出来的都是音频裸数据,至于组合成adts-aac还是苹果的m4a(ipod容器),由程序决定。
typedef struct
{
void *source;
UInt32 sourceSize;
UInt32 channelCount;
AudioStreamPacketDescription *packetDescriptions;
}FillComplexInputParam;
OSStatus audioConverterComplexInputDataProc( AudioConverterRef inAudioConverter,
UInt32* ioNumberDataPackets,
AudioBufferList* ioData,
AudioStreamPacketDescription** outDataPacketDescription,
void* inUserData)
{
FillComplexInputParam* param = (FillComplexInputParam*)inUserData;
if (param->sourceSize <= 0) {
*ioNumberDataPackets = 0;
return -1;
}
ioData->mBuffers[0].mData = param->source;
ioData->mBuffers[0].mNumberChannels = param->channelCount;
ioData->mBuffers[0].mDataByteSize = param->sourceSize;
*ioNumberDataPackets = 1;
param->sourceSize = 0;
param->source = NULL;
return noErr;
}
typedef struct _tagConvertContext {
AudioConverterRef converter;
int samplerate;
int channels;
}ConvertContext;
// init
void* convert_init(int sample_rate, int channel_count)
{
AudioStreamBasicDescription sourceDes;
memset(&sourceDes, 0, sizeof(sourceDes));
sourceDes.mSampleRate = sample_rate;
sourceDes.mFormatID = kAudioFormatLinearPCM;
sourceDes.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger;
sourceDes.mChannelsPerFrame = channel_count;
sourceDes.mBitsPerChannel = 16;
sourceDes.mBytesPerFrame = sourceDes.mBitsPerChannel/8*sourceDes.mChannelsPerFrame;
sourceDes.mBytesPerPacket = sourceDes.mBytesPerFrame;
sourceDes.mFramesPerPacket = 1;
sourceDes.mReserved = 0;
AudioStreamBasicDescription targetDes;
memset(&targetDes, 0, sizeof(targetDes));
targetDes.mFormatID = kAudioFormatMPEG4AAC;
targetDes.mSampleRate = sample_rate;
targetDes.mChannelsPerFrame = channel_count;
UInt32 size = sizeof(targetDes);
AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &size, &targetDes);
AudioClassDescription audioClassDes;
memset(&audioClassDes, 0, sizeof(AudioClassDescription));
AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(targetDes.mFormatID), &targetDes.mFormatID, &size);
int encoderCount = size / sizeof(AudioClassDescription);
AudioClassDescription descriptions[encoderCount];
AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(targetDes.mFormatID), &targetDes.mFormatID, &size, descriptions);
for (int pos = 0; pos < encoderCount; pos ++) {
if (targetDes.mFormatID == descriptions[pos].mSubType && descriptions[pos].mManufacturer == kAppleSoftwareAudioCodecManufacturer) {
memcpy(&audioClassDes, &descriptions[pos], sizeof(AudioClassDescription));
break;
}
}
ConvertContext *convertContex = malloc(sizeof(ConvertContext));
OSStatus ret = AudioConverterNewSpecific(&sourceDes, &targetDes, 1, &audioClassDes, &convertContex->converter);
if (ret == noErr) {
AudioConverterRef converter = convertContex->converter;
tmp = kAudioConverterQuality_High;
AudioConverterSetProperty(converter, kAudioConverterCodecQuality, sizeof(tmp), &tmp);
UInt32 bitRate = 96000;
UInt32 size = sizeof(bitRate);
ret = AudioConverterSetProperty(converter, kAudioConverterEncodeBitRate, size, &bitRate);
}
else {
free(convertContex);
convertContex = NULL;
}
return convertContex;
}
// converting
void convert(void* convertContext, void* srcdata, int srclen, void** outdata, int* outlen)
{
ConvertContext* convertCxt = (ConvertContext*)convertContext;
if (convertCxt && convertCxt->converter) {
UInt32 theOuputBufSize = srclen;
UInt32 packetSize = 1;
void *outBuffer = malloc(theOuputBufSize);
memset(outBuffer, 0, theOuputBufSize);
AudioStreamPacketDescription *outputPacketDescriptions = NULL;
outputPacketDescriptions = (AudioStreamPacketDescription*)malloc(sizeof(AudioStreamPacketDescription) * packetSize);
FillComplexInputParam userParam;
userParam.source = srcdata;
userParam.sourceSize = srclen;
userParam.channelCount = convertCxt->channels;
userParam.packetDescriptions = NULL;
OSStatus ret = noErr;
AudioBufferList* bufferList = malloc(sizeof(AudioBufferList));
AudioBufferList outputBuffers = *bufferList;
outputBuffers.mNumberBuffers = 1;
outputBuffers.mBuffers[0].mNumberChannels = convertCxt->channels;
outputBuffers.mBuffers[0].mData = outBuffer;
outputBuffers.mBuffers[0].mDataByteSize = theOuputBufSize;
ret = AudioConverterFillComplexBuffer(convertCxt->converter, audioConverterComplexInputDataProc, &userParam, &packetSize, &outputBuffers, outputPacketDescriptions);
if (ret == noErr) {
if (outputBuffers.mBuffers[0].mDataByteSize > 0) {
NSData* rawAAC = [NSData dataWithBytes:outputBuffers.mBuffers[0].mData length:outputBuffers.mBuffers[0].mDataByteSize];
*outdata = malloc([rawAAC length]);
memcpy(*outdata, [rawAAC bytes], [rawAAC length]);
*outlen = (int)[rawAAC length];
#if 1
int headerLength = 0;
char* packetHeader = newAdtsDataForPacketLength((int)[rawAAC length], convertCxt->samplerate, convertCxt->channels, &headerLength);
NSData* adtsPacketHeader = [NSData dataWithBytes:packetHeader length:headerLength];
free(packetHeader);
NSMutableData* fullData = [NSMutableData dataWithData:adtsPacketHeader];
[fullData appendData:rawAAC];
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *filepath = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/test%p.aac", convertCxt->converter];
NSFileHandle *file = nil;
if (![fileMgr fileExistsAtPath:filepath]) {
[fileMgr createFileAtPath:filepath contents:nil attributes:nil];
}
file = [NSFileHandle fileHandleForWritingAtPath:filepath];
[file seekToEndOfFile];
[file writeData:fullData];
[file closeFile];
#endif
}
}
free(outBuffer);
if (outputPacketDescriptions) {
free(outputPacketDescriptions);
}
}
}
// uninit
....
/**
* Add ADTS header at the beginning of each and every AAC packet.
* This is needed as MediaCodec encoder generates a packet of raw
* AAC data.
*
* Note the packetLen must count in the ADTS header itself.
* See: http://wiki.multimedia.cx/index.php?title=ADTS
* Also: http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Channel_Configurations
**/
//- (NSData*) adtsDataForPacketLength:(NSUInteger)packetLength {
char* newAdtsDataForPacketLength(int packetLength, int samplerate, int channelCount, int* ioHeaderLen) {
int adtsLength = 7;
char *packet = malloc(sizeof(char) * adtsLength);
// Variables Recycled by addADTStoPacket
int profile = 2; //AAC LC
//39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
int freqIdx = freqIdxForAdtsHeader(samplerate);
int chanCfg = channelIdxForAdtsHeader(channelCount); //MPEG-4 Audio Channel Configuration.
NSUInteger fullLength = adtsLength + packetLength;
// fill in ADTS data
packet[0] = (char)0xFF; // 11111111 = syncword
packet[1] = (char)0xF9; // 1111 1 00 1 = syncword MPEG-2 Layer CRC
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;
*ioHeaderLen = adtsLength;
return packet;
}