美文网首页iOS语音、视频处理
关于iOS下音频操作(音频合并,裁切,转码)

关于iOS下音频操作(音频合并,裁切,转码)

作者: 牧羊人Q | 来源:发表于2019-07-17 15:31 被阅读0次

    前段时间在做音频的相关技术,涉及到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);
            }
        }];
    }
    

    相关文章

      网友评论

        本文标题:关于iOS下音频操作(音频合并,裁切,转码)

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