LCWechat -- 封装录音与播放
2016-05-31 本文已影响434人
南镇s
简略描述: 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) { // 录音成功
// 执行下一步计划
}
}];