LCWechat -- 封装录音与播放

简略描述: 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) { // 录音成功
          // 执行下一步计划
        }
    }];

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容