来自:http://blog.csdn.net/zengconggen/article/details/7842076
ios上音频文件合成有多种方法,一是使用AVAssetExportSession结合AVMutableComposition,二是使用AVAssetReader和AVAssetWriter. 三是转换成pcm数据后处理pcm数据合成。
方法一和方法二在处理速度上相对来说要慢。一般3分半左右音频需要30s左右,特别是合成不同采样率的音频,速度比较慢。下文提供本人自己写的一个类,采用第三种方法合成音频,采用该算法合成的音频,比网络上一般代码提供的音频在合成效果上好。并且处理速度快,ipod touch4上处理4分半音频仅需6s左右。
//
// ExtAudioFileMixer.h
// AudioManager
//
// Created by zeng songgen on 12-8-7.
// Copyright (c) 2012年 Rumtel. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface ExtAudioFileMixer : NSObject
+ (OSStatus)mixAudio:(NSString *)audioPath1
andAudio:(NSString *)audioPath2
toFile:(NSString *)outputPath
preferedSampleRate:(float)sampleRate;
@end
//
// ExtAudioFileMixer.m
// AudioManager
//
// Created by zeng songgen on 12-8-7.
// Copyright (c) 2012年 Rumtel. All rights reserved.
//
#import "ExtAudioFileMixer.h"
#import <AudioToolbox/AudioToolbox.h>
@implementation ExtAudioFileMixer
+ (OSStatus)mixAudio:(NSString *)audioPath1
andAudio:(NSString *)audioPath2
toFile:(NSString *)outputPath
preferedSampleRate:(float)sampleRate
{
OSStatus err = noErr;
AudioStreamBasicDescription inputFileFormat1;
AudioStreamBasicDescription inputFileFormat2;
AudioStreamBasicDescription converterFormat;
UInt32 thePropertySize = sizeof(inputFileFormat1);
ExtAudioFileRef inputAudioFileRef1 = NULL;
ExtAudioFileRef inputAudioFileRef2 = NULL;
ExtAudioFileRef outputAudioFileRef = NULL;
AudioStreamBasicDescription outputFileFormat;
NSURL *inURL1 = [NSURL fileURLWithPath:audioPath1];
NSURL *inURL2 = [NSURL fileURLWithPath:audioPath2];
NSURL *outURL = [NSURL fileURLWithPath:outputPath];
// Open input audio file
err = ExtAudioFileOpenURL((CFURLRef)inURL1, &inputAudioFileRef1);
if (err)
{
goto reterr;
}
assert(inputAudioFileRef1);
err = ExtAudioFileOpenURL((CFURLRef)inURL2, &inputAudioFileRef2);
if (err)
{
goto reterr;
}
assert(inputAudioFileRef2);
// Get input audio format
bzero(&inputFileFormat1, sizeof(inputFileFormat1));
err = ExtAudioFileGetProperty(inputAudioFileRef1, kExtAudioFileProperty_FileDataFormat,
&thePropertySize, &inputFileFormat1);
if (err)
{
goto reterr;
}
// only mono or stereo audio files are supported
if (inputFileFormat1.mChannelsPerFrame > 2)
{
err = kExtAudioFileError_InvalidDataFormat;
goto reterr;
}
bzero(&inputFileFormat2, sizeof(inputFileFormat2));
err = ExtAudioFileGetProperty(inputAudioFileRef2, kExtAudioFileProperty_FileDataFormat,
&thePropertySize, &inputFileFormat2);
if (err)
{
goto reterr;
}
// only mono or stereo audio files are supported
if (inputFileFormat2.mChannelsPerFrame > 2)
{
err = kExtAudioFileError_InvalidDataFormat;
goto reterr;
}
int numChannels = MAX(inputFileFormat1.mChannelsPerFrame, inputFileFormat2.mChannelsPerFrame);
// Enable an audio converter on the input audio data by setting
// the kExtAudioFileProperty_ClientDataFormat property. Each
// read from the input file returns data in linear pcm format.
AudioFileTypeID audioFileTypeID = kAudioFileCAFType;
Float64 mSampleRate = sampleRate? sampleRate : MAX(inputFileFormat1.mSampleRate, inputFileFormat2.mSampleRate);
[self _setDefaultAudioFormatFlags:&converterFormat sampleRate:mSampleRate numChannels:inputFileFormat1.mChannelsPerFrame];
err = ExtAudioFileSetProperty(inputAudioFileRef1, kExtAudioFileProperty_ClientDataFormat,
sizeof(converterFormat), &converterFormat);
if (err)
{
goto reterr;
}
[self _setDefaultAudioFormatFlags:&converterFormat sampleRate:mSampleRate numChannels:inputFileFormat2.mChannelsPerFrame];
err = ExtAudioFileSetProperty(inputAudioFileRef2, kExtAudioFileProperty_ClientDataFormat,
sizeof(converterFormat), &converterFormat);
if (err)
{
goto reterr;
}
// Handle the case of reading from a mono input file and writing to a stereo
// output file by setting up a channel map. The mono output is duplicated
// in the left and right channel.
if (inputFileFormat1.mChannelsPerFrame == 1 && numChannels == 2) {
SInt32 channelMap[2] = { 0, 0 };
// Get the underlying AudioConverterRef
AudioConverterRef convRef = NULL;
UInt32 size = sizeof(AudioConverterRef);
err = ExtAudioFileGetProperty(inputAudioFileRef1, kExtAudioFileProperty_AudioConverter, &size, &convRef);
if (err)
{
goto reterr;
}
assert(convRef);
err = AudioConverterSetProperty(convRef, kAudioConverterChannelMap, sizeof(channelMap), channelMap);
if (err)
{
goto reterr;
}
}
if (inputFileFormat2.mChannelsPerFrame == 1 && numChannels == 2) {
SInt32 channelMap[2] = { 0, 0 };
// Get the underlying AudioConverterRef
AudioConverterRef convRef = NULL;
UInt32 size = sizeof(AudioConverterRef);
err = ExtAudioFileGetProperty(inputAudioFileRef2, kExtAudioFileProperty_AudioConverter, &size, &convRef);
if (err)
{
goto reterr;
}
assert(convRef);
err = AudioConverterSetProperty(convRef, kAudioConverterChannelMap, sizeof(channelMap), channelMap);
if (err)
{
goto reterr;
}
}
// Output file is typically a caff file, but the user could emit some other
// common file types. If a file exists already, it is deleted before writing
// the new audio file.
[self _setDefaultAudioFormatFlags:&outputFileFormat sampleRate:mSampleRate numChannels:numChannels];
UInt32 flags = kAudioFileFlags_EraseFile;
err = ExtAudioFileCreateWithURL((CFURLRef)outURL, audioFileTypeID, &outputFileFormat,
NULL, flags, &outputAudioFileRef);
if (err)
{
// -48 means the file exists already
goto reterr;
}
assert(outputAudioFileRef);
// Enable converter when writing to the output file by setting the client
// data format to the pcm converter we created earlier.
err = ExtAudioFileSetProperty(outputAudioFileRef, kExtAudioFileProperty_ClientDataFormat,
sizeof(outputFileFormat), &outputFileFormat);
if (err)
{
goto reterr;
}
// Buffer to read from source file and write to dest file
UInt16 bufferSize = 8192;
AudioSampleType * buffer1 = malloc(bufferSize);
AudioSampleType * buffer2 = malloc(bufferSize);
AudioSampleType * outBuffer = malloc(bufferSize);
AudioBufferList conversionBuffer1;
conversionBuffer1.mNumberBuffers = 1;
conversionBuffer1.mBuffers[0].mNumberChannels = inputFileFormat1.mChannelsPerFrame;
conversionBuffer1.mBuffers[0].mDataByteSize = bufferSize;
conversionBuffer1.mBuffers[0].mData = buffer1;
AudioBufferList conversionBuffer2;
conversionBuffer2.mNumberBuffers = 1;
conversionBuffer2.mBuffers[0].mNumberChannels = inputFileFormat2.mChannelsPerFrame;
conversionBuffer2.mBuffers[0].mDataByteSize = bufferSize;
conversionBuffer2.mBuffers[0].mData = buffer2;
//
AudioBufferList outBufferList;
outBufferList.mNumberBuffers = 1;
outBufferList.mBuffers[0].mNumberChannels = outputFileFormat.mChannelsPerFrame;
outBufferList.mBuffers[0].mDataByteSize = bufferSize;
outBufferList.mBuffers[0].mData = outBuffer;
UInt32 numFramesToReadPerTime = INT_MAX;
UInt8 bitOffset = 8 * sizeof(AudioSampleType);
UInt64 bitMax = (UInt64) (pow(2, bitOffset));
UInt64 bitMid = bitMax/2;
while (TRUE) {
conversionBuffer1.mBuffers[0].mDataByteSize = bufferSize;
conversionBuffer2.mBuffers[0].mDataByteSize = bufferSize;
outBufferList.mBuffers[0].mDataByteSize = bufferSize;
UInt32 frameCount1 = numFramesToReadPerTime;
UInt32 frameCount2 = numFramesToReadPerTime;
if (inputFileFormat1.mBytesPerFrame)
{
frameCount1 = bufferSize/inputFileFormat1.mBytesPerFrame;
}
if (inputFileFormat2.mBytesPerFrame)
{
frameCount2 = bufferSize/inputFileFormat2.mBytesPerFrame;
}
// Read a chunk of input
err = ExtAudioFileRead(inputAudioFileRef1, &frameCount1, &conversionBuffer1);
if (err) {
goto reterr;
}
err = ExtAudioFileRead(inputAudioFileRef2, &frameCount2, &conversionBuffer2);
if (err) {
goto reterr;
}
// If no frames were returned, conversion is finished
if (frameCount1 == 0 && frameCount2 == 0)
break;
UInt32 frameCount = MAX(frameCount1, frameCount2);
UInt32 minFrames = MIN(frameCount1, frameCount2);
outBufferList.mBuffers[0].mDataByteSize = frameCount * outputFileFormat.mBytesPerFrame;
UInt32 length = frameCount * 2;
for (int j =0; j < length; j++)
{
if (j/2 < minFrames)
{
SInt32 sValue =0;
SInt16 value1 = (SInt16)*(buffer1+j); //-32768 ~ 32767
SInt16 value2 = (SInt16)*(buffer2+j); //-32768 ~ 32767
SInt8 sign1 = (value1 == 0)? 0 : abs(value1)/value1;
SInt8 sign2 = (value2== 0)? 0 : abs(value2)/value2;
if (sign1 == sign2)
{
UInt32 tmp = ((value1 * value2) >> (bitOffset -1));
sValue = value1 + value2 - sign1 * tmp;
if (abs(sValue) >= bitMid)
{
sValue = sign1 * (bitMid - 1);
}
}
else
{
SInt32 tmpValue1 = value1 + bitMid;
SInt32 tmpValue2 = value2 + bitMid;
UInt32 tmp = ((tmpValue1 * tmpValue2) >> (bitOffset -1));
if (tmpValue1 < bitMid && tmpValue2 < bitMid)
{
sValue = tmp;
}
else
{
sValue = 2 * (tmpValue1 + tmpValue2 ) - tmp - bitMax;
}
sValue -= bitMid;
}
if (abs(sValue) >= bitMid)
{
SInt8 sign = abs(sValue)/sValue;
sValue = sign * (bitMid - 1);
}
*(outBuffer +j) = sValue;
}
else{
if (frameCount == frameCount1)
{
//将buffer1中的剩余数据添加到outbuffer
*(outBuffer +j) = *(buffer1 + j);
}
else
{
//将buffer1中的剩余数据添加到outbuffer
*(outBuffer +j) = *(buffer2 + j);
}
}
}
// Write pcm data to output file
NSLog(@"frame count (%ld, %ld, %ld)", frameCount, frameCount1, frameCount2);
err = ExtAudioFileWrite(outputAudioFileRef, frameCount, &outBufferList);
if (err) {
goto reterr;
}
}
reterr:
if (buffer1)
free(buffer1);
if (buffer2)
free(buffer2);
if (outBuffer)
free(outBuffer);
if (inputAudioFileRef1)
ExtAudioFileDispose(inputAudioFileRef1);
if (inputAudioFileRef2)
ExtAudioFileDispose(inputAudioFileRef2);
if (outputAudioFileRef)
ExtAudioFileDispose(outputAudioFileRef);
return err;
}
// Set flags for default audio format on iPhone OS
+ (void) _setDefaultAudioFormatFlags:(AudioStreamBasicDescription*)audioFormatPtr
sampleRate:(Float64)sampleRate
numChannels:(NSUInteger)numChannels
{
bzero(audioFormatPtr, sizeof(AudioStreamBasicDescription));
audioFormatPtr->mFormatID = kAudioFormatLinearPCM;
audioFormatPtr->mSampleRate = sampleRate;
audioFormatPtr->mChannelsPerFrame = numChannels;
audioFormatPtr->mBytesPerPacket = 2 * numChannels;
audioFormatPtr->mFramesPerPacket = 1;
audioFormatPtr->mBytesPerFrame = 2 * numChannels;
audioFormatPtr->mBitsPerChannel = 16;
audioFormatPtr->mFormatFlags = kAudioFormatFlagsNativeEndian |
kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
}
@end