这几年做了许多涉及到音视频的app,里面有很多时候需要要转换格式,比如在合成拼接的时候,或者与服务器所需格式不一致的时候。我在此总结了几种常见格式间的互相转换。希望能帮到广告开发同行们。
一、m4a格式转caf格式
/**
把.m4a转为.caf格式
@param originalUrlStr .m4a文件路径
@param destUrlStr .caf文件路径
@param completed 转化完成的block
*/
+ (void)convetM4aToWav:(NSString *)originalUrlStr
destUrl:(NSString *)destUrlStr
completed:(void (^)(NSError *error)) completed {
if ([[NSFileManager defaultManager] fileExistsAtPath:destUrlStr]) {
[[NSFileManager defaultManager] removeItemAtPath:destUrlStr error:nil];
}
NSURL *originalUrl = [NSURL fileURLWithPath:originalUrlStr];
NSURL *destUrl = [NSURL fileURLWithPath:destUrlStr];
AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:originalUrl options:nil];
//读取原始文件信息
NSError *error = nil;
AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:songAsset error:&error];
if (error) {
DebugLog (@"error: %@", error);
completed(error);
return;
}
AVAssetReaderOutput *assetReaderOutput = [AVAssetReaderAudioMixOutput
assetReaderAudioMixOutputWithAudioTracks:songAsset.tracks
audioSettings: nil];
if (![assetReader canAddOutput:assetReaderOutput]) {
DebugLog (@"can't add reader output... die!");
completed(error);
return;
}
[assetReader addOutput:assetReaderOutput];
AVAssetWriter *assetWriter = [AVAssetWriter assetWriterWithURL:destUrl
fileType:AVFileTypeCoreAudioFormat
error:&error];
if (error) {
DebugLog (@"error: %@", error);
completed(error);
return;
}
AudioChannelLayout channelLayout;
memset(&channelLayout, 0, sizeof(AudioChannelLayout));
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey,
[NSNumber numberWithFloat:44100], AVSampleRateKey,
[NSNumber numberWithInt:2], AVNumberOfChannelsKey,
[NSNumber numberWithInt:16], AVLinearPCMBitDepthKey,
[NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved,
[NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
[NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey,
nil];
AVAssetWriterInput *assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
outputSettings:outputSettings];
if ([assetWriter canAddInput:assetWriterInput]) {
[assetWriter addInput:assetWriterInput];
} else {
DebugLog (@"can't add asset writer input... die!");
completed(error);
return;
}
assetWriterInput.expectsMediaDataInRealTime = NO;
[assetWriter startWriting];
[assetReader startReading];
AVAssetTrack *soundTrack = [songAsset.tracks objectAtIndex:0];
CMTime startTime = CMTimeMake (0, soundTrack.naturalTimeScale);
[assetWriter startSessionAtSourceTime:startTime];
__block UInt64 convertedByteCount = 0;
dispatch_queue_t mediaInputQueue = dispatch_queue_create("mediaInputQueue", NULL);
[assetWriterInput requestMediaDataWhenReadyOnQueue:mediaInputQueue
usingBlock: ^
{
while (assetWriterInput.readyForMoreMediaData) {
CMSampleBufferRef nextBuffer = [assetReaderOutput copyNextSampleBuffer];
if (nextBuffer) {
// append buffer
[assetWriterInput appendSampleBuffer: nextBuffer];
DebugLog (@"appended a buffer (%zu bytes)",
CMSampleBufferGetTotalSampleSize (nextBuffer));
convertedByteCount += CMSampleBufferGetTotalSampleSize (nextBuffer);
} else {
[assetWriterInput markAsFinished];
[assetWriter finishWritingWithCompletionHandler:^{
}];
[assetReader cancelReading];
NSDictionary *outputFileAttributes = [[NSFileManager defaultManager]
attributesOfItemAtPath:[destUrl path]
error:nil];
DebugLog (@"FlyElephant %lld",[outputFileAttributes fileSize]);
break;
}
}
DebugLog(@"转换结束");
// 删除临时temprecordAudio.m4a文件
NSError *removeError = nil;
if ([[NSFileManager defaultManager] fileExistsAtPath:originalUrlStr]) {
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:originalUrlStr error:&removeError];
if (!success) {
DebugLog(@"删除临时temprecordAudio.m4a文件失败:%@",removeError);
completed(removeError);
}else{
DebugLog(@"删除临时temprecordAudio.m4a文件:%@成功",originalUrlStr);
completed(removeError);
}
}
}];
}
二、caf格式转m4a格式
/**
把.caf转为.m4a格式
@param cafUrlStr .m4a文件路径
@param m4aUrlStr .caf文件路径
@param completed 转化完成的block
*/
+ (void)convetCafToM4a:(NSString *)cafUrlStr
destUrl:(NSString *)m4aUrlStr
completed:(void (^)(NSError *error)) completed {
AVMutableComposition* mixComposition = [AVMutableComposition composition];
// 音频插入的开始时间
CMTime beginTime = kCMTimeZero;
// 获取音频合并音轨
AVMutableCompositionTrack *compositionAudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// 用于记录错误的对象
NSError *error = nil;
// 音频原文件资源
AVURLAsset *cafAsset = [[AVURLAsset alloc]initWithURL:[NSURL fileURLWithPath:cafUrlStr] options:nil];
// 原音频需要合并的音频文件的区间
CMTimeRange audio_timeRange = CMTimeRangeMake(kCMTimeZero, cafAsset.duration);
BOOL success = [compositionAudioTrack insertTimeRange:audio_timeRange ofTrack:[[cafAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:beginTime error:&error];
if (!success) {
DebugLog(@"插入原音频失败: %@",error);
}else {
DebugLog(@"插入原音频成功");
}
// 创建一个导入M4A格式的音频的导出对象
AVAssetExportSession* assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetAppleM4A];
// 导入音视频的URL
assetExport.outputURL = [NSURL fileURLWithPath:m4aUrlStr];
// 导出音视频的文件格式
assetExport.outputFileType = @"com.apple.m4a-audio";
[assetExport exportAsynchronouslyWithCompletionHandler:^{
// 分发到主线程
dispatch_async(dispatch_get_main_queue(), ^{
int exportStatus = assetExport.status;
if (exportStatus == AVAssetExportSessionStatusCompleted) {
// 合成成功
completed(nil);
NSError *removeError = nil;
if([cafUrlStr hasSuffix:@"caf"]) {
// 删除老录音caf文件
if ([[NSFileManager defaultManager] fileExistsAtPath:cafUrlStr]) {
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:cafUrlStr error:&removeError];
if (!success) {
DebugLog(@"删除老录音caf文件失败:%@",removeError);
}else{
DebugLog(@"删除老录音caf文件:%@成功",cafUrlStr);
}
}
}
}else {
completed(assetExport.error);
}
});
}];
}
三、caf或m4a转为aac或mp3
这个的转换需要引入一个第三方库,就是lame.
+ (BOOL)audio_PCMtoMP3WithPCMUrl:(NSString *)pcmUrl andTimesTamps:(NSString *)timesTamps{
// 导出aac的地址
NSString *timestampsPath = [kRecorderPath stringByAppendingPathComponent: timesTamps];
NSString *mp3FilePath = [timestampsPath stringByAppendingPathComponent: kRecordAACSaveName];
NSError *removeError = nil;
if ([[NSFileManager defaultManager] fileExistsAtPath:mp3FilePath]) {
// 如果有旧文件则删除
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:mp3FilePath error:&removeError];
if (!success) {
DebugLog(@"删除老aac文件失败:%@",removeError);
}else{
DebugLog(@"删除老aac文件:%@成功",mp3FilePath);
}
}
@try {
int read, write;
FILE *pcm = fopen([pcmUrl cStringUsingEncoding:1], "rb"); //source 被转换的音频文件位置
fseek(pcm, 4*1024, SEEK_CUR); //skip file header
FILE *mp3 = fopen([mp3FilePath cStringUsingEncoding:1], "wb"); //output 输出生成的Mp3文件位置
const int PCM_SIZE = 8192;//8192
const int MP3_SIZE = 8192;//8192
short int pcm_buffer[PCM_SIZE*2];
unsigned char mp3_buffer[MP3_SIZE];
lame_t lame = lame_init();
lame_set_in_samplerate(lame, 44100);//采样播音速度,值越大播报速度越快,反之。
lame_set_VBR(lame, vbr_default);
lame_init_params(lame);
do {
read = fread(pcm_buffer, 2*sizeof(short int), PCM_SIZE, pcm);
if (read == 0) {
write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
} else {
write = lame_encode_buffer_interleaved(lame, pcm_buffer, read, mp3_buffer, MP3_SIZE);
}
fwrite(mp3_buffer, write, 1, mp3);
} while (read != 0);
lame_close(lame);
fclose(mp3);
fclose(pcm);
}
@catch (NSException *exception) {
DebugLog(@"CAF转AAC失败:%@",[exception description]);
return NO;
}
@finally {
DebugLog(@"CAF转AAC成功!");
return YES;
}
}
四、caf和amr的互转
这两者的互相转换,我用到了一个三方库,libopencore-amrnb,百度可以搜到。