首先介绍一下常用的音频文件格式
PCM格式:
PCM属于编码格式,PCM是经过话筒后直接得到的未经压缩的数据流
数据的大小 = 采样率 * 采样位数 * 声道 * 秒数 / 8
采样率一般是:22K或者是44K
位数一般是:8位或者16位
声道一般是:双声道或者单声道
PCM是一串由多个样本值组成的数据流,本身没有任何头信息或者帧的概念。只有一段PCM数据是没有办法知道它的采样率的信息的。
WAV格式:
WAV是封装格式,里面本身可以存放多种编码格式,不过一般都存放PCM数据。
WAV文件是由“WAV文件头”和“WAV文件体”组成。前44字节存放采样率,通道数,数据部分的标识符等头信息,后面就是存放数据部分。很显然WAV的头信息一旦损坏了,播放就会产生问题。
MP3格式:
MP3是封装格式,里面存放的数据使用的编码方式为:MPEG1 Layer-3
MP3是由TAG_V2结构体,TAG_V1结构体,和一组Frame组成。头部和尾部的TAG结构体不一定存在,需要判断。中间的Frame,每一个Frame都是由帧头和数据实体组成,帧头记录了MP3的位率,采样率,版本等信息。每个帧之间相互独立,也就是说即使前面的帧损坏了,后面的也可以播放。
AMR格式:
AMR是封装格式。
AMR文件包含一个文件头,后面就是一帧一帧的AMR帧。
文件头里面的值就固定值,用于标记文件为AMR文件。
每个帧分为帧头和数据部分,帧头里面包含编发方式和辅助信息。AMR的采样率跟编码方式的不同而不同。
AAC格式:
AAC文件可以没有文件头,全部由帧序列组成。
每个帧包含帧头和数据部分。
帧头包含采样率,声道数,帧长度等有点类似MP3格式。
CAF格式:
CAF是苹果的一种音频封装格式,与WAV差不多,里面可以存放LPCM,MP3等多种编码方式。
M4A格式:
M4A:M4A是MPEG4音频标准的文件的扩展名。在MPEG4标准中提到,普通的MPEG4文件扩搜索展名是.mp4,但自从苹果开始在它的iTunes以及iPod中使用m4a格式音频文件以区别MPEG4的视频和音频文件以来,.m4a这个扩展名变得流行了。
一、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.大家可以百度下载这个库,主要使用以下两个文件,找到这两个文件之后,直接拖进项目即可,拖进去之后,在需要使用的文件里面加入lame.h的头文件,即可使用:

+ (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转mp3
这里也是需要lame文件的
- (void)audio_PCMtoMP3
{
NSString*tempDir =NSTemporaryDirectory();
NSString*urlPatch = [tempDir stringByAppendingString:@"/ebemate_record.caf"];//原录音保存位置
NSString *mp3FilePath = [tempDir stringByAppendingPathComponent:@"/ebemate_record.mp3"];//转码mp3格式保存位置
@try {
int read, write;
FILE *pcm = fopen([urlPatch cStringUsingEncoding:1], "rb"); //source 被转换的音频文件位置
if(pcm == NULL){
NSLog(@"file not found");
return;
}
fseek(pcm, 4*1024, SEEK_CUR); //skip file header
FILE *mp3 = fopen([mp3FilePath cStringUsingEncoding:1], "wb"); //output 输出生成的Mp3文件位置
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, 8000);//这里的值必须要和上面s录音的时候设置的一直,否则转码出来的mp3格式播放会变音
lame_set_VBR(lame, vbr_default);
lame_set_brate(lame,0);
lame_set_mode(lame,3);
lame_set_quality(lame,2); /* 2=high 5 = medium 7=low 音质*/
lame_set_out_samplerate(lame, 44100);
lame_init_params(lame);
do {
read = (int)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(@"audio_PCMtoMP3audio_PCMtoMP3%@",[exception description]);
}
@finally {
NSString*tempDir =NSTemporaryDirectory();
NSString *mp3FilePath = [tempDir stringByAppendingPathComponent:@"/ebemate_record.mp3"];
NSURL *mp3_url=[NSURL fileURLWithPath:mp3FilePath];
[[NSUserDefaults standardUserDefaults]setObject:mp3FilePath forKey:file_name];
NSLog(@"MP3生成成功: %@ mp3二进制文件=%@",mp3_url,[NSData dataWithContentsOfURL:mp3_url]);
}
}
需要下载lame库的同学,可以直接下载下面👇这个录音demo,里面包含了lame需要的两个主要文件,可直接拖到项目中去:
录音demo
五、caf和amr的互转
这两者的互相转换,我用到了一个三方库,libopencore-amrnb,百度可以搜到。
下载完拖到项目中,主要文件如下:

转换需要的主要代码为amrFileCodec.m文件中的:
//此处将一个录制的caf直接转换为amr格式
//调用方式为 EncodeWAVEToAMR(pcmData,1,16);
NSData* EncodeWAVEToAMR(NSData* data, int nChannels, int nBitsPerSample)
{
// NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// NSString *documentPath = [paths objectAtIndex:0];
// NSString *wavFile = [documentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"11.caf"]];
// NSLog(@"documentPath=%@", documentPath);
if (data==nil){
//data = [NSData dataWithContentsOfFile:wavFile];
return nil;
}
int nPos = 0;
char* buf =(char *) [data bytes];
int maxLen = [data length];
nPos += SkipCaffHead(buf);
if (nPos>=maxLen) {
return nil;
}
//这时取出来的是纯pcm数据
buf += nPos;
return EncodePCMToAMR(buf,maxLen- nPos,nChannels,nBitsPerSample);
}
转换的过程为:
* 写入文件开始转换
*/
//tmpFileUrl 为录音文件的路径
- (void)writeAuToAmrFile:(NSURL*)tmpFileUrl callback:(writeSuccessBlock)block{
if ([tmpFileUrl isKindOfClass:[NSURL class]]) {
NSData *amrData = [self encodeWAVEToAMROfFile:tmpFileUrl]; //开始转换,这里调用的就是上面说的主要的方法
[self writeToAmrFile:tmpFileUrl amrData:amrData call:block];//得到转换为amr格式的data文件,在保存下来
}
}
//重点:根据录音文件(caf格式)的url转换为amr格式
- (NSData*)encodeWAVEToAMROfFile:(NSURL*)cafFileUrl{
NSData *data = EncodeWAVEToAMR([NSData dataWithContentsOfURL:cafFileUrl], 1, 16);
return data;
}
//保存转换好的amr格式的文件
- (void)writeToAmrFile:(NSURL*)tempFile0 amrData:(NSData*)curAudioData call:(writeSuccessBlock)block{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = [paths objectAtIndex:0];
NSString *amrName = [[[tempFile0 lastPathComponent] componentsSeparatedByString:@"."] firstObject];
NSString *amrFile = [documentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.amr",amrName]];
NSLog(@"转换之后的amr文件=%@",amrFile);
BOOL ist = [curAudioData writeToFile:amrFile atomically:YES];
if (block) {
block(ist,curAudioData);
}
}
网友评论