前段时间在做音频的相关技术,涉及到iOS录音转码的一些知识,这里在这里记录下。
一、使用iOS自带的AVAudioRecorder录音时录音的格式可以是.caf,wav,但有时候可能需要需要把这些格式转成其他的格式,比如从caf转为mp3,从wav转mp3,m4a转wav再转mp3(因为有时候在对音频做合并和剪切的时候生成的音频格式是m4a的)这里就说一说音频格式的集中转码操作。
caf音频格式转mp3
caf转mp3音频格式需要用到一个c语言的第三方库lame,这个库可以把caf和wav的成功转为mp3格式(其他格式的没有测试)。只需设置一些参数就可以了,详见代码:
//转换为mp3
+ (void)convenrtToMp3WithResult:(NSString *)originalPath outPath:(NSString *)outPath success:(BaseIdBlock)successBlock{
[[NSFileManager defaultManager] removeItemAtPath:outPath error:nil];
@try {
int read, write;
FILE *pcm = fopen([originalPath cStringUsingEncoding:1], "rb");//被转换的文件
fseek(pcm, 4*1024, SEEK_CUR); //skip file header
FILE *mp3 = fopen([outPath cStringUsingEncoding:1], "wb");//转换后文件的存放位置
const int PCM_SIZE = 8192;
const int MP3_SIZE = 8192;
short int pcm_buffer[PCM_SIZE*2];
unsigned char mp3_buffer[MP3_SIZE];
lame_t lame = lame_init();
lame_set_num_channels (lame, 2 ); // 设置 1 为单通道,默认为 2 双通道
lame_set_in_samplerate(lame, 44100);//
lame_set_brate (lame, 8);
lame_set_mode (lame, 3);
lame_set_VBR(lame, vbr_default);
lame_set_quality (lame, 2); /* 2=high 5 = medium 7=low 音 质 */
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) {
// NSLog(@"%@",[exception description]);
}
@finally {
successBlock(outPath);
}
}
m4a格式转wav格式
因为在对音频做合并或者裁切的时候生成的音频格式是m4a的,但是m4a转成mp3会损坏音频格式,所以我当时采用先把m4a转为wav,再用wav转成mp3。以下粘出代码:
+ (void)convertM4aToWav:(NSString *)originalPath outPath:(NSString *)outPath success:(BaseIdBlock)block{
if ([FileUitl isExist:outPath]) {
[FileUitl removeFile:outPath];
}
NSURL *originalUrl = [NSURL fileURLWithPath:originalPath];
NSURL *outPutUrl = [NSURL fileURLWithPath:outPath];
AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:originalUrl options:nil]; //读取原始文件信息
NSError *error = nil;
AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:songAsset error:&error];
if (error) {
NSLog (@"error: %@", error);
return;
}
AVAssetReaderOutput *assetReaderOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:songAsset.tracks audioSettings: nil];
if (![assetReader canAddOutput:assetReaderOutput]) {
NSLog (@"can't add reader output... die!");
return;
}
[assetReader addOutput:assetReaderOutput];
AVAssetWriter *assetWriter = [AVAssetWriter assetWriterWithURL:outPutUrl fileType:AVFileTypeCoreAudioFormat error:&error];
if (error) {
NSLog (@"error: %@", error);
return;
}
AudioChannelLayout channelLayout;
memset(&channelLayout, 0, sizeof(AudioChannelLayout));
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
/** 配置音频参数 */
NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey, [NSNumber numberWithFloat:44100.0], AVSampleRateKey, [NSNumber numberWithInt:2], AVNumberOfChannelsKey, [NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)], AVChannelLayoutKey,[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 {
NSLog (@"can't add asset writer input... die!");
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];
convertedByteCount += CMSampleBufferGetTotalSampleSize (nextBuffer);
} else {
[assetWriterInput markAsFinished];
[assetWriter finishWritingWithCompletionHandler:^{
}];
[assetReader cancelReading];
NSDictionary *outputFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[outPutUrl path] error:nil];
NSLog (@"FlyElephant %lld",[outputFileAttributes fileSize]);
if ([FileUitl isExist:originalPath]) {
[FileUitl removeFile:originalPath];
}
block(outPath);
break;
}
}
}];
}
简单的来说用AVAssetReader和AVAssetWrite,AVAssetReader用于从AVAsset资源读取媒体样本,AVAssetWrite用于对媒体资源进行编码并写入到新的文件中。然后wav转mp3就按照第一步的那样做就可以了
音频合并
音频合并和裁切用的AVFoundation框架下一个多媒体的载体类:AVAsset。它提供了一系列的接口来处理多媒体,只需要我们写很少的代码就能对音频,视频做出来。下面贴出代码:
+(void)jointAudioPath:(NSString *)audio1 withPath:(NSString *)audio2 outPath:(NSString *)outPath success:(BaseIdBlock)block
{
AVURLAsset *audioAsset1 = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:audio2]];
AVURLAsset *audioAsset2 = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:audio1]];
AVMutableComposition *composition = [AVMutableComposition composition];
// 音频通道
AVMutableCompositionTrack *audioTrack1 = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:0];
AVMutableCompositionTrack *audioTrack2 = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:0];
// 音频采集通道
AVAssetTrack *audioAssetTrack1 = [[audioAsset1 tracksWithMediaType:AVMediaTypeAudio] firstObject];
AVAssetTrack *audioAssetTrack2 = [[audioAsset2 tracksWithMediaType:AVMediaTypeAudio] firstObject];
// 音频合并 - 插入音轨文件
[audioTrack1 insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset1.duration) ofTrack:audioAssetTrack1 atTime:kCMTimeZero error:nil];
// `startTime`参数要设置为第一段音频的时长,即`audioAsset1.duration`, 表示将第二段音频插入到第一段音频的尾部。
[audioTrack2 insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset2.duration) ofTrack:audioAssetTrack2 atTime:audioAsset1.duration error:nil];
// 合并后的文件导出 - `presetName`要和之后的`session.outputFileType`相对应。
AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetAppleM4A];
NSString *outPutFilePath = [self m4aRecordPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:outPutFilePath]) {
[[NSFileManager defaultManager] removeItemAtPath:outPutFilePath error:nil];
}
// 查看当前session支持的fileType类型
NSLog(@"---%@",[session supportedFileTypes]);
session.outputURL = [NSURL fileURLWithPath:outPutFilePath];
session.outputFileType = AVFileTypeAppleM4A; //与上述的`present`相对应
session.shouldOptimizeForNetworkUse = YES; //优化网络
WeakSelf(self);
[session exportAsynchronouslyWithCompletionHandler:^{
if (session.status == AVAssetExportSessionStatusCompleted) {
NSLog(@"合并成功----%@", outPutFilePath);
if ([FileUitl isExist:audio1]) {
[FileUitl removeFile:audio1];
[FileUitl removeFile:audio2];
}
[weakself convertM4aToWav:[self m4aRecordPath] outPath:outPath success:^(id parameter) {
block(parameter);
}];
}else if (session.status == AVAssetExportSessionStatusFailed){
NSLog(@"合并失败!");
}
}];
}
音频裁剪
+(void)cutAudioStartTime:(CGFloat)source endTime:(CGFloat)end withPath:(NSString *)path withBlock:(BaseIdBlock)block{
NSString *m4aOutPath = [AudioFileManager m4aRecordName:kAuditionRecord];
NSString *wavOutPath = [AudioFileManager wavRecordName:kAuditionRecord];
//音频输出会话
AVURLAsset *videoAsset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:path]];
//音频输出会话
//AVAssetExportPresetAppleM4A:(输出音频,并且是.m4a格式)
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:videoAsset presetName:AVAssetExportPresetAppleM4A];
exportSession.outputURL = [NSURL fileURLWithPath:m4aOutPath];
exportSession.outputFileType = AVFileTypeAppleM4A;
exportSession.timeRange = CMTimeRangeFromTimeToTime(CMTimeMake(source, 1), CMTimeMake(end, 1));
[exportSession exportAsynchronouslyWithCompletionHandler:^{
//exporeSession.status
if (AVAssetExportSessionStatusCompleted == exportSession.status) {
[self convertM4aToWav:m4aOutPath outPath:wavOutPath success:^(id parameter) {
block(parameter);
}];
} else if (AVAssetExportSessionStatusFailed == exportSession.status) {
NSLog(@"剪切失败!");
}else{
NSLog(@"Export Session Status: %ld", (long)exportSession.status);
}
}];
}