美文网首页iOS基本功
iOS音频格式m4a、caf、amr的相互转换

iOS音频格式m4a、caf、amr的相互转换

作者: 凡几多 | 来源:发表于2018-11-14 14:16 被阅读329次

这几年做了许多涉及到音视频的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,百度可以搜到。

相关文章

网友评论

    本文标题:iOS音频格式m4a、caf、amr的相互转换

    本文链接:https://www.haomeiwen.com/subject/xoxjfqtx.html