藤原とうふ店(自家用)

iOS录制MP3音频,完整代码

2023-12-04  本文已影响0人  _菩提本无树_

代码地址

目前仅支持caf和MP3
demo项目

//
//  DSAudioRecordManager.m
//  DSCapacityEvaluation
//
//  Created by DDS on 2023/12/4.
//  Copyright © 2023 DDS. All rights reserved.
//

#import "DSAudioRecordManager.h"
#import "DSCommonTool.h"
#import <AVFoundation/AVFoundation.h>
#import "lame.h"
@interface DSAudioRecordManager()<AVAudioRecorderDelegate>
/// 音频文件地址
@property (nonatomic, copy) NSString *recordPathDetails;

/// 音频文件所在文件夹
@property (nonatomic, copy) NSString *recordPath;

/// Audio参数
@property (nonatomic, strong) NSDictionary *settingParams;

/// 生成的音频格式
@property (nonatomic, assign) DSAudioRecordFileType recordFileType;

/// 音频文件
@property (nonatomic, strong) NSData *recordData;

/// 文件名称
@property (nonatomic, copy) NSString *fileName;

@end

@implementation DSAudioRecordManager
{
    NSInteger _startTime;
    BOOL _cancel;
}

+ (instancetype)sharedManager {
    static DSAudioRecordManager *_sharedManager = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _sharedManager = [[self alloc] init];
    });
    return _sharedManager;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        _recordPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
        _settingParams = [self getAudioSetting];
        _recordFileType = DSAudioRecordFileCAFType;
        _cancel = NO;
    }
    return self;
}

- (void)settingAudioRecordFilePath:(NSString *_Nullable)path
                       andFileType:(DSAudioRecordFileType)fileType
                   andAudioSetting:(NSDictionary *_Nullable)audioSetting{
    /// 设置存储目录
    if (path && path.length > 0) {
        BOOL isDir = YES;
        if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
            if (![[NSFileManager defaultManager] isWritableFileAtPath:path] || ![[NSFileManager defaultManager] isReadableFileAtPath:path]) {
                NSLog(@"该文件夹不可读/不可写");
            } else {
                _recordPath = path;
            }
        } else {
            BOOL isSuccess = [[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil];
            if (isSuccess) {
                if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
                    _recordPath = path;
                }
            }
        }
    }
    /// 音频格式
    _recordFileType = fileType;
    /// 设置参数
    if (audioSetting && audioSetting.allKeys.count > 0) {
        _settingParams = audioSetting;
    }

}
#pragma mark Fun
/// 开始录音
- (void)startRecordFileName:(nonnull NSString *)fileName {
    _cancel = NO;
    if (!fileName || fileName.length == 0) {
        fileName = [NSString stringWithFormat:@"%ld",[DSCommonTool getCurrentTime]];
    }
    _fileName = fileName;
    if ([self.audioRecorder isRecording]) {
        [self.audioRecorder stop];
    }

    //创建音频会话对象
    AVAudioSession *audioSession=[AVAudioSession sharedInstance];
    //设置category
    [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
    if (![self.audioRecorder isRecording]){
        // 首次使用应用时如果调用record方法会询问用户是否允许使用麦克风
        _startTime = [DSCommonTool getCurrentTime];
        [self.audioRecorder record];
    } else {
        if (self.delegate && [self.delegate respondsToSelector:@selector(onRecordOverPath:andErrorType:)]) {
            [self.delegate onRecordOverPath:nil andErrorType:DSAudioRecordErrorRecordFailType];
        }
    }
}
/// 结束录音
- (void)stopRecord{
    if (self.audioRecorder.recording) {
        [self.audioRecorder stop];
    } else {
        if (self.delegate && [self.delegate respondsToSelector:@selector(onRecordOverPath:andErrorType:)]) {
            [self.delegate onRecordOverPath:nil andErrorType:DSAudioRecordErrorNoRecordType];
        }
    }
}
/// 取消录音
- (void)cancelRecord {
    _cancel = YES;
    if (self.audioRecorder.recording) {
        [self.audioRecorder stop];
    }
    if (self.delegate && [self.delegate respondsToSelector:@selector(onRecordOverPath:andErrorType:)]) {
        [self.delegate onRecordOverPath:nil andErrorType:DSAudioRecordErrorCancelType];
    }
}

#pragma mark other

/**
 *  取得录音文件设置,默认格式
 *  @return 录音设置
 */
-(NSDictionary *)getAudioSetting{
    //LinearPCM 是iOS的一种无损编码格式,但是体积较为庞大
    //录音设置
    NSMutableDictionary *recordSettings = [[NSMutableDictionary alloc] init];
    //录音格式
    [recordSettings setValue :[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey: AVFormatIDKey];
    //采样率
    [recordSettings setValue :[NSNumber numberWithFloat:11025.0] forKey: AVSampleRateKey];//44100.0
    //通道数
    [recordSettings setValue :[NSNumber numberWithInt:2] forKey: AVNumberOfChannelsKey];
    //音频质量,采样质量
    [recordSettings setValue:[NSNumber numberWithInt:AVAudioQualityMedium] forKey:AVEncoderAudioQualityKey];
    return recordSettings;
}

- (BOOL)isRecording{
    return self.audioRecorder.recording;
}

/// 文件转MP3
/// - Parameter tmpUrl: tmpUrl description
-(void)conventToMp3WithPath:(NSURL *)tmpUrl {
   //tmpUrl是caf文件的路径,并转换成字符串
   NSString *cafFilePath = [tmpUrl absoluteString];
    
   //存储mp3文件的路径
   NSString *mp3FilePath = [NSString stringWithFormat:@"%@/%@.mp3",_recordPath,_fileName];
   @try {
       int read, write;

       FILE *pcm = fopen([cafFilePath 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;
       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_in_samplerate(lame, 11025.0);
       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);
       if (self.delegate && [self.delegate respondsToSelector:@selector(onRecordOverPath:andErrorType:)]) {
           [self.delegate onRecordOverPath:mp3FilePath andErrorType:(DSAudioRecordSuccessType)];
       }
   }
   @catch (NSException *exception) {
       if (self.delegate && [self.delegate respondsToSelector:@selector(onRecordOverPath:andErrorType:)]) {
           [self.delegate onRecordOverPath:nil andErrorType:(DSAudioRecordErrorConventType)];
       }
   }
   @finally {
   }
}
#pragma mark AVAudioRecorderDelegate
/*!
    @method audioRecorderDidFinishRecording:successfully:
    @abstract This callback method is called when a recording has been finished or stopped.
    @discussion This method is NOT called if the recorder is stopped due to an interruption.
 */
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{
    if (_cancel) {
        return;
    }
    if (flag) {
        if (self.delegate && [self.delegate respondsToSelector:@selector(onRecordOverPath:andErrorType:)]) {
            if (self.recordFileType == DSAudioRecordFileMP3Type) {
                [self conventToMp3WithPath:[NSURL URLWithString:[_recordPath stringByAppendingPathComponent:@"myRecord.caf"]]];
            } else {
                [self.delegate onRecordOverPath:_recordPathDetails andErrorType:(DSAudioRecordSuccessType)];
            }
        }
    } else {
        if (self.delegate && [self.delegate respondsToSelector:@selector(onRecordOverPath:andErrorType:)]) {
            [self.delegate onRecordOverPath:nil andErrorType:(DSAudioRecordErrorSystemType)];
        }
    }
}

- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError *)error{
    if (self.delegate && [self.delegate respondsToSelector:@selector(onRecordOverPath:andErrorType:)]) {
        [self.delegate onRecordOverPath:nil andErrorType:(DSAudioRecordErrorSystemType)];
    }
}
#pragma mark Lazy
/**
 *  获得录音机对象
 *
 *  @return 录音机对象
 */
- (AVAudioRecorder *)audioRecorder{
    if (!_audioRecorder) {
        //创建录音文件保存路径
        self.recordPathDetails = [_recordPath stringByAppendingPathComponent:@"myRecord.caf"];
        NSURL *url = [NSURL URLWithString: self.recordPathDetails];
        //创建录音格式设置,详见下文
        NSDictionary *setting = [self getAudioSetting];
        //创建录音机
        NSError *error = nil;
        _audioRecorder=[[AVAudioRecorder alloc]initWithURL:url settings:setting error:&error];
        //设置委托代理
        _audioRecorder.delegate = self;
        //如果要监控声波则必须设置为YES
        _audioRecorder.meteringEnabled = YES;
        if (error) {
            if (self.delegate && [self.delegate respondsToSelector:@selector(onRecordOverPath:andErrorType:)]) {
                [self.delegate onRecordOverPath:nil andErrorType:(DSAudioRecordErrorSystemType)];
            }
            NSLog(@"创建录音机对象时发生错误,错误信息:%@",error.localizedDescription);
            return nil;
        }
    }
    return _audioRecorder;
}


@end
上一篇下一篇

猜你喜欢

热点阅读