LCWechat -- 封装录音与播放

作者: 南镇s | 来源:发表于2016-05-31 13:36 被阅读434次

    简略描述: AVAudioRecorder 与 AVAudioPlayer(只能播放本地音频, 远程音频使用AVPlayer)都是使用代理的方法判断是否停止, 录音的时候需要设置recordSetting, 字典的形式存储了几种录音需要的格式,例如:采样率, 通道数,录音质量等.还有一个AVAudioSession 是一个单例 [AVAudioSession sharedInstance] 在录音和播放音频的需要设置一下其模式AVAudioSessionCategoryOptions, 每个枚举类型都有其对应的应用场景.

    摘抄:
    在获得一个AVAudioSession类的实例后,你就能通过调用音频会话对象的setCategory:error:实例方法,来从IOS应用可用的不同类别中作出选择。下面列出了可供使用的音频会话类别:
    AVAudioSessionCategorySoloAmbient
    
    这个类别非常像AVAudioSessionCategoryAmbient类别,除了会停止其他程序的音频回放,比如iPod程序。当设备被设置为静音模式,你的音频回放将会停止。
    
    AVAudioSessionCategoryRecord
    这会停止其他应用的声音(比如iPod)并让你的应用也不能初始化音频回放(比如AVAudioPlayer)。在这种模式下,你只能进行录音。使用这个类别,调用AVAudioPlayer的prepareToPlay会返回YES,但是调用play方法将返回NO。主UI界面会照常工作。这时,即使你的设备屏幕被用户锁定了,应用的录音仍会继续。
    
    AVAudioSessionCategoryPlayback
    这个类别会静止其他应用的音频回放(比如iPod应用的音频回放)。你可以使用AVAudioPlayer的prepareToPlay和play方法,在你的应用中播放声音。主UI界面会照常工作。这时,即使屏幕被锁定或者设备为静音模式,音频回放都会继续。
    
    AVAudioSessionCategoryPlayAndRecord
    这个类别允许你的应用中同时进行声音的播放和录制。当你的声音录制或播放开始后,其他应用的声音播放将会停止。主UI界面会照常工作。这时,即使屏幕被锁定或者设备为静音模式,音频回放和录制都会继续。
    
    AVAudioSessionCategoryAudioProcessing
    这个类别用于应用中进行音频处理的情形,而不是音频回放或录制。设置了这种模式,你在应用中就不能播放和录制任何声音。调用AVAPlayer的prepareToPlay和play方法都将返回NO。其他应用的音频回放,比如iPod,也会在此模式下停止。
    
    AVAudioSessionCategoryAmbient
    这个类别不会停止其他应用的声音,相反,它允许你的音频播放于其他应用的声音之上,比如iPod。你的应用的主UI县城会工作正常。调用AVAPlayer的prepareToPlay和play方法都将返回YES。当用户锁屏时,你的应用将停止所有正在回放的音频。仅当你的应用是唯一播放该音频文件的应用时,静音模式将停止你程序的音频回放。如果正当iPod播放一手歌时,你开始播放音频,将设备设为静音模式并不能停止你的音频回放。
    

    思路:

    1.创建一个AudioManager统一管理audioRecord和audioPlay
    2.先分别实现audioRecord和audioPlay
    3.基于audioRecord和audioPlay 在AudioManager 统一处理
    4.就是一个封装, 花点时间 就能看懂

    .h文件
    #import <Foundation/Foundation.h>
    
    @interface LCAudioManager : NSObject
    
    + (instancetype)manager;
    
    #pragma mark - LCAudioRecord
    // 判断麦克风是否可用
    - (BOOL)checkMicrophoneAvailability;
     
    /**
     *  开始录音
     *
     */
    - (void)startRecordingWithFileName:(NSString *)fileName
                                 completion:(void(^)(NSError *error))completion;
    
    /**
     *  停止录音
     *
     */
    - (void)stopRecordingWithCompletion:(void(^)(NSString *recordPath,
                                                     NSInteger aDuration,
                                                     NSError *error))completion;
    /**
     *  取消录音
     */
    - (void)cancelRecording;
    
    /**
     *  当前是否正在录音
     *
     */
    - (BOOL)isRecording;
    
    
    #pragma mark - LCAudioPlay
    /**
     *  播放音频
     *
     */
    - (void)playingWithRecordPath:(NSString *)recordPath
                      completion:(void(^)(NSError *error))completion;
    
    /**
     *  停止播放
     *
     */
    - (void)stopPlaying;
    
    /**
     *  当前是否正在播放
     *
     */
    -(BOOL)isPlaying;
    @end
    
    
    .m文件
    // 当前代码是将 录音转换为mp3, 如不需转换 直接注释此代码, 并设置一下相应的录音格式,例如通道数,采样率等
    //BOOL convertResult = [self convertWAV:recordPath toMP3:mp3FilePath];
    #import "LCAudioManager.h"
    #import "LCAudioPlay.h"
    #import "LCAudioRecord.h"
    #import <AVFoundation/AVFoundation.h>
    #import "lame.h"
    
    #define audioRecordDurationTooShort -100
    #define audioRecordStoping -101
    #define audioRecordNotStarted -102
    #define audioRecordConvertionFailure -103
    #define audioRecordPathNotFound -104
    
    #define recordMinDuration 1.0
    
    typedef NS_ENUM(NSInteger, audioSession){
        audioSessionDefault = 0,
        audioSessionAudioRecord = 1,
        audioSessionPlay = 2
    };
    
    @interface LCAudioManager ()
    
    @property (strong, nonatomic) NSDate *recordStartDate;
    @property (strong, nonatomic) NSDate *recordEndDate;
    @property (copy, nonatomic) NSString *audioSessionCategory;
    @property (assign, nonatomic) BOOL currentActive;
    
    @end
    
    @implementation LCAudioManager
    
    + (instancetype)manager
    {
        static LCAudioManager *audioManager = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            audioManager = [[self alloc] init];
        });
        
        return audioManager;
    }
    
    
    - (BOOL)checkMicrophoneAvailability{
        __block BOOL open = NO;
        AVAudioSession *session = [AVAudioSession sharedInstance];
        if ([session respondsToSelector:@selector(requestRecordPermission:)]) {
            [session performSelector:@selector(requestRecordPermission:) withObject:^(BOOL status) {
                open = status;
            }];
        } else {
            open = YES;
        }
        
        return open;
    }
    
    #pragma mark - LCAudioRecord
    - (void)startRecordingWithFileName:(NSString *)fileName completion:(void (^)(NSError *))completion
    {
        if ([self isRecording]) {
            [self cancelRecording];
            if (completion) completion([NSError errorWithDomain:NSLocalizedString(@"LCAudio.recordStop", @"停止当前录音") code:audioRecordStoping userInfo:nil]);
            
            return;
        }
        
        if (!fileName || fileName.length == 0) {
            if (completion) completion([NSError errorWithDomain:NSLocalizedString(@"LCAudio.recordPathNotFound", @"尚未找到文件") code:audioRecordPathNotFound userInfo:nil]);
            return;
        }
        
        [self setCategory:audioSessionAudioRecord isActive:YES];
        [[LCAudioRecord sharedInstance] startRecordingWithRecordPath:[self recordPathWithfileName:fileName] completion:completion];
        
        self.recordStartDate = [NSDate date];
    }
    
    - (void)stopRecordingWithCompletion:(void (^)(NSString *, NSInteger, NSError *))completion
    {
        if (![self isRecording]) {
            if (completion) completion(nil, 0, [NSError errorWithDomain:NSLocalizedString(@"LCAudio.recordNotStart", @"未有录音") code:audioRecordNotStarted userInfo:nil]);
            
            return;
        }
        
        self.recordEndDate = [NSDate date];
        __weak typeof(self) weakSelf = self;
        NSTimeInterval duration = [self.recordEndDate timeIntervalSinceDate:self.recordStartDate];
        if (duration < recordMinDuration) {
            if (completion) completion(nil, 0, [NSError errorWithDomain:NSLocalizedString(@"LCAudio.recordTimeTooShort", @"录音小于1秒") code:audioRecordDurationTooShort userInfo:nil]);
            
            
            [[LCAudioRecord sharedInstance] stopRecordingWithCompletion:^(NSString *recordPath) {
                [weakSelf setCategory:audioSessionDefault isActive:NO];
            }];
            
            return;
        }
        
        [[LCAudioRecord sharedInstance] stopRecordingWithCompletion:^(NSString *recordPath) {
            if (completion) {
                NSString *mp3FilePath = [self MP3FilePath:recordPath];
                BOOL convertResult = [self convertWAV:recordPath toMP3:mp3FilePath];
                if (convertResult) {
                    // 删除录的wav
                    [[NSFileManager defaultManager] removeItemAtPath:recordPath error:nil];
                }
                
                completion(mp3FilePath,(NSInteger)duration, nil);
            }
            
            [weakSelf setCategory:audioSessionDefault isActive:NO];
        }];
    }
    
    - (void)cancelRecording
    {
        [[LCAudioRecord sharedInstance] cancelRecording];
    }
    
    - (BOOL)isRecording
    {
        return [[LCAudioRecord sharedInstance] isRecording];
    }
    
    #pragma mark - LCAudioPlay
    
    - (void)playingWithRecordPath:(NSString *)recordPath completion:(void (^)(NSError *))completion
    {
        if ([self isPlaying]) [self stopPlaying];
        
        [self setCategory:audioSessionPlay isActive:YES];
        
        NSString *mp3FilePath = [self MP3FilePath:recordPath];
        
        if (![[NSFileManager defaultManager] fileExistsAtPath:mp3FilePath]) { // 如果没有转化成功,尝试再次转换
            BOOL convertResult = [self convertWAV:recordPath toMP3:mp3FilePath];
            if (convertResult) {
                // 删除录的wav
                [[NSFileManager defaultManager] removeItemAtPath:recordPath error:nil];
            } else {
                if (completion) completion([NSError errorWithDomain:NSLocalizedString(@"LCAudio.recordConvertionFailure", @"转换文件失败") code:audioRecordConvertionFailure userInfo:nil]);
                
                return;
            }
        }
        
        [[LCAudioPlay sharedInstance] playingWithPath:[self MP3FilePath:recordPath] completion:^(NSError *error) {
            [self setCategory:audioSessionDefault isActive:NO];
            if (completion) completion(error);
        }];
    }
    
    - (void)stopPlaying
    {
        [[LCAudioPlay sharedInstance] stopPlaying];
        [self setCategory:audioSessionDefault isActive:NO];
    }
    
    - (BOOL)isPlaying
    {
        return [[LCAudioPlay sharedInstance] isPlaying];
    }
    
    #pragma mark - setCategory && setActive
    - (void)setCategory:(audioSession)session isActive:(BOOL)active
    {
        NSError *error = nil;
        NSString *category = nil;
        AVAudioSession *audioSession = [AVAudioSession sharedInstance];
        
        switch (session) {
            case audioSessionAudioRecord:
                category = AVAudioSessionCategoryRecord;
                break;
            case audioSessionPlay:
                category = AVAudioSessionCategoryPlayback;
                break;
            default:
                category = AVAudioSessionCategoryAmbient;
                break;
        }
        
        if (![self.audioSessionCategory isEqualToString:category]) [audioSession setCategory:category error:nil];
        
        
        if (active != self.currentActive) {
            self.currentActive = active;
            BOOL success = [audioSession setActive:active withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
            if (!success || error) return ;
        }
        
        self.audioSessionCategory = category;
        
    }
    
    #pragma mark - path
    - (NSString *)recordPathWithfileName:(NSString *)fileName
    {
        NSString *recordPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        recordPath = [NSString stringWithFormat:@"%@/records/",recordPath];
        recordPath = [recordPath stringByAppendingPathComponent:fileName];
        if(![[NSFileManager defaultManager] fileExistsAtPath:[recordPath stringByDeletingLastPathComponent]]){
            [[NSFileManager defaultManager] createDirectoryAtPath:[recordPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
        }
        
        return recordPath;
    }
    
    - (NSString *)MP3FilePath:(NSString *)aFilePath
    {
        return [[aFilePath stringByDeletingPathExtension] stringByAppendingPathExtension:@"mp3"];
    }
    #pragma mark - Convert
    // 使用三方库 lame // 需要将libmp3lame 拖进项目
    - (BOOL)convertWAV:(NSString *)wavFilePath toMP3:(NSString *)mp3FilePath{
        BOOL ret = NO;
        BOOL isFileExists = [[NSFileManager defaultManager] fileExistsAtPath:wavFilePath];
        if (isFileExists) {
            int read, write;
            
            FILE *pcm = fopen([wavFilePath 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);
            isFileExists = [[NSFileManager defaultManager] fileExistsAtPath:mp3FilePath];
            if (isFileExists) {
                ret = YES;
            }
        }
        
        return ret;
    }
    @end
    

    audioPlay

    .h文件
    #import <Foundation/Foundation.h>
    
    @interface LCAudioPlay : NSObject
    
    + (instancetype)sharedInstance;
    
    /**
     *  当前是否正在播放
     *
     */
    - (BOOL)isPlaying;
    
    /**
     *  播放音频
     *
     */
    - (void)playingWithPath:(NSString *)recordPath
                      completion:(void(^)(NSError *error))completion;
    
    /**
     *  停止播放
     *
     */
    - (void)stopPlaying;
    @end
    
    
    .m文件
    #import "LCAudioPlay.h"
    #import <AVFoundation/AVFoundation.h>
    
    #define audioFileNotFound -106
    #define audioPlayerInitFilure -107
    
    @interface LCAudioPlay ()<AVAudioPlayerDelegate>
    
    @property (strong, nonatomic) AVAudioPlayer *player;
    @property (copy, nonatomic) void(^completion)(NSError *);
    
    @end
    
    @implementation LCAudioPlay
    + (instancetype)sharedInstance
    {
        static LCAudioPlay *player = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            player = [[self alloc] init];
        });
        
        return player;
    }
    
    - (void)playingWithPath:(NSString *)recordPath completion:(void (^)(NSError *))completion
    {
        self.completion = completion;
        
        NSError *error = nil;
        
        if (![[NSFileManager defaultManager] fileExistsAtPath:recordPath]) {
            error = [NSError errorWithDomain:NSLocalizedString(@"LCAudio.fileNotFound", @"未找到文件") code:audioFileNotFound userInfo:nil];
            
            if (self.completion) self.completion(error);
            
            self.completion = nil;
            
            return;
        }
        
        NSURL *mp3URL = [NSURL fileURLWithPath:recordPath];
        
        self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:mp3URL error:&error];
        
        if (!self.player || error) {
            error = [NSError errorWithDomain:NSLocalizedString(@"LCAudio.audioPlayerInitFilure", @"初始化播放器失败") code:audioPlayerInitFilure userInfo:nil];
            
            self.player = nil;
            
            if (self.completion) self.completion(error);
            
            self.completion = nil;
            
            return;
        }
        
        self.player.delegate = self;
        [self.player prepareToPlay];
        [self.player play];
        
    }
    
    - (BOOL)isPlaying
    {
        return !!self.player;
    }
    
    - (void)stopPlaying
    {
        if (self.player) {
            self.player.delegate = nil;
            [self.player stop];
            self.player = nil;
        }
        
        if (self.completion) self.completion = nil;
    }
    
    - (void)dealloc
    {
        [self stopPlaying];
    }
    #pragma mark - AVAudioPlayerDelegate
    - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
    {
        if (self.completion) self.completion(nil);
        [self stopPlaying];
    }
    
    @end
    

    audioRecord

    .h文件
    #import <Foundation/Foundation.h>
    
    @interface LCAudioRecord : NSObject
    
    + (instancetype)sharedInstance;
    
    /**
     *  开始录音
     *
     */
    - (void)startRecordingWithRecordPath:(NSString *)recordPath
                                 completion:(void(^)(NSError *error))completion;
    
    /**
     *  停止录音
     *
     */
    - (void)stopRecordingWithCompletion:(void(^)(NSString *recordPath))completion;
    
    /**
     *  取消录音
     */
    - (void)cancelRecording;
    
    /**
     *  当前是否正在录音
     *
     */
    - (BOOL)isRecording;
    @end
    
    
    .m文件
    #import "LCAudioRecord.h"
    #import <AVFoundation/AVFoundation.h>
    
    #define recorderInitFailure -105
    
    @interface LCAudioRecord ()<AVAudioRecorderDelegate>
    
    @property (nonatomic, strong) AVAudioRecorder *recorder;
    @property (nonatomic, strong) NSDictionary *recordSetting;
    @property (copy, nonatomic) void(^completion)(NSString *recordPath);
    
    @end
    
    @implementation LCAudioRecord
    
    + (instancetype)sharedInstance
    {
        static LCAudioRecord *audioRecord = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            audioRecord = [[self alloc] init];
        });
        
        return audioRecord;
    }   
    
    - (NSDictionary *)recordSetting
    {
        if (!_recordSetting) { // 转换为的MP3格式
            _recordSetting = [[NSDictionary alloc] initWithObjectsAndKeys:
                              [NSNumber numberWithFloat: 11025.0],AVSampleRateKey, //采样率 44100.0
                              [NSNumber numberWithInt: kAudioFormatLinearPCM],AVFormatIDKey,
                              [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,//采样位数 默认 16
                              [NSNumber numberWithInt: 2], AVNumberOfChannelsKey,//通道的数目
                              [NSNumber numberWithInt:AVAudioQualityMax], AVEncoderAudioQualityKey, // 录音质量
                              nil];
        }
        
        return _recordSetting;
    }
    
    - (void)startRecordingWithRecordPath:(NSString *)recordPath completion:(void (^)(NSError *))completion
    {
        NSError *error = nil;
        NSURL *wavURL = [NSURL fileURLWithPath:[[recordPath stringByDeletingPathExtension] stringByAppendingPathExtension:@"wav"]];
        self.recorder = [[AVAudioRecorder alloc] initWithURL:wavURL settings:self.recordSetting error:&error];
        if (!self.recorder || error) {
            if (completion) {
                error = [NSError errorWithDomain:NSLocalizedString(@"LCAudio.recorderInitFailure", @"初始化失败") code:recorderInitFailure userInfo:nil];
                completion(error);
            }
            self.recorder = nil;
            return;
        }
        
        self.recorder.meteringEnabled = YES;
        self.recorder.delegate = self;
        [self.recorder record];
        
        if (completion) completion(error);
        
    }
    
    - (void)stopRecordingWithCompletion:(void(^)(NSString *recordPath))completion
    {
        self.completion = completion;
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self.recorder stop];
        });
    }
    
    - (BOOL)isRecording
    {
        return !!self.recorder;
    }
    
    - (void)cancelRecording
    {
        self.recorder.delegate = nil;
        if (self.recorder) [self.recorder stop];
        self.recorder = nil;
        
    }
    
    - (void)dealloc
    {
        if (self.recorder) {
            self.recorder.delegate = nil;
            [self.recorder stop];
            [self.recorder deleteRecording];
            self.recorder = nil;
        }
    }
    
    - (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag
    {
        NSString *recordPath = [[_recorder url] path];
        if (self.completion) {
            if (!flag) recordPath = nil;
            self.completion(recordPath);
        }
        
        self.recorder = nil;
        self.completion = nil;
    }
    
    @end
    
    

    如何使用:

    1.开始录音
    // 检测麦克风是否可用
    [LCAudioManager manager] checkMicrophoneAvailability]
    [[LCAudioManager manager] startRecordingWithFileName:[NSString recordFileName] completion:nil];
    
    2.结束录音
    [[LCAudioManager manager] stopRecordingWithCompletion:^(NSString *recordPath, NSInteger aDuration, NSError *error) {
            if (aDuration < 1) { 
                [MBProgressHUD showError:@"录音时间过短"];
                return ;
            }
            if (!error) { // 录音成功
              // 执行下一步计划
            }
        }];
    
    

    相关文章

      网友评论

      本文标题:LCWechat -- 封装录音与播放

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