簡(jiǎn)略描述: AVAudioRecorder 與 AVAudioPlayer(只能播放本地音頻, 遠(yuǎn)程音頻使用AVPlayer)都是使用代理的方法判斷是否停止, 錄音的時(shí)候需要設(shè)置recordSetting, 字典的形式存儲(chǔ)了幾種錄音需要的格式,例如:采樣率, 通道數(shù),錄音質(zhì)量等.還有一個(gè)AVAudioSession 是一個(gè)單例 [AVAudioSession sharedInstance] 在錄音和播放音頻的需要設(shè)置一下其模式AVAudioSessionCategoryOptions, 每個(gè)枚舉類型都有其對(duì)應(yīng)的應(yīng)用場(chǎng)景.
摘抄:
在獲得一個(gè)AVAudioSession類的實(shí)例后蹋艺,你就能通過(guò)調(diào)用音頻會(huì)話對(duì)象的setCategory:error:實(shí)例方法庶诡,來(lái)從IOS應(yīng)用可用的不同類別中作出選擇。下面列出了可供使用的音頻會(huì)話類別:
AVAudioSessionCategorySoloAmbient
這個(gè)類別非常像AVAudioSessionCategoryAmbient類別缸兔,除了會(huì)停止其他程序的音頻回放艇挨,比如iPod程序店归。當(dāng)設(shè)備被設(shè)置為靜音模式趁曼,你的音頻回放將會(huì)停止颂碘。
AVAudioSessionCategoryRecord
這會(huì)停止其他應(yīng)用的聲音(比如iPod)并讓你的應(yīng)用也不能初始化音頻回放(比如AVAudioPlayer)邀层。在這種模式下返敬,你只能進(jìn)行錄音。使用這個(gè)類別寥院,調(diào)用AVAudioPlayer的prepareToPlay會(huì)返回YES劲赠,但是調(diào)用play方法將返回NO。主UI界面會(huì)照常工作。這時(shí)凛澎,即使你的設(shè)備屏幕被用戶鎖定了泌绣,應(yīng)用的錄音仍會(huì)繼續(xù)。
AVAudioSessionCategoryPlayback
這個(gè)類別會(huì)靜止其他應(yīng)用的音頻回放(比如iPod應(yīng)用的音頻回放)预厌。你可以使用AVAudioPlayer的prepareToPlay和play方法阿迈,在你的應(yīng)用中播放聲音。主UI界面會(huì)照常工作轧叽。這時(shí)苗沧,即使屏幕被鎖定或者設(shè)備為靜音模式,音頻回放都會(huì)繼續(xù)炭晒。
AVAudioSessionCategoryPlayAndRecord
這個(gè)類別允許你的應(yīng)用中同時(shí)進(jìn)行聲音的播放和錄制待逞。當(dāng)你的聲音錄制或播放開始后,其他應(yīng)用的聲音播放將會(huì)停止网严。主UI界面會(huì)照常工作识樱。這時(shí),即使屏幕被鎖定或者設(shè)備為靜音模式震束,音頻回放和錄制都會(huì)繼續(xù)怜庸。
AVAudioSessionCategoryAudioProcessing
這個(gè)類別用于應(yīng)用中進(jìn)行音頻處理的情形,而不是音頻回放或錄制垢村。設(shè)置了這種模式割疾,你在應(yīng)用中就不能播放和錄制任何聲音。調(diào)用AVAPlayer的prepareToPlay和play方法都將返回NO嘉栓。其他應(yīng)用的音頻回放宏榕,比如iPod,也會(huì)在此模式下停止侵佃。
AVAudioSessionCategoryAmbient
這個(gè)類別不會(huì)停止其他應(yīng)用的聲音麻昼,相反,它允許你的音頻播放于其他應(yīng)用的聲音之上馋辈,比如iPod抚芦。你的應(yīng)用的主UI縣城會(huì)工作正常。調(diào)用AVAPlayer的prepareToPlay和play方法都將返回YES首有。當(dāng)用戶鎖屏?xí)r燕垃,你的應(yīng)用將停止所有正在回放的音頻枢劝。僅當(dāng)你的應(yīng)用是唯一播放該音頻文件的應(yīng)用時(shí)井联,靜音模式將停止你程序的音頻回放。如果正當(dāng)iPod播放一手歌時(shí)您旁,你開始播放音頻烙常,將設(shè)備設(shè)為靜音模式并不能停止你的音頻回放。
思路:
1.創(chuàng)建一個(gè)AudioManager統(tǒng)一管理audioRecord和audioPlay
2.先分別實(shí)現(xiàn)audioRecord和audioPlay
3.基于audioRecord和audioPlay 在AudioManager 統(tǒng)一處理
4.就是一個(gè)封裝, 花點(diǎn)時(shí)間 就能看懂
.h文件
#import <Foundation/Foundation.h>
@interface LCAudioManager : NSObject
+ (instancetype)manager;
#pragma mark - LCAudioRecord
// 判斷麥克風(fēng)是否可用
- (BOOL)checkMicrophoneAvailability;
/**
* 開始錄音
*
*/
- (void)startRecordingWithFileName:(NSString *)fileName
completion:(void(^)(NSError *error))completion;
/**
* 停止錄音
*
*/
- (void)stopRecordingWithCompletion:(void(^)(NSString *recordPath,
NSInteger aDuration,
NSError *error))completion;
/**
* 取消錄音
*/
- (void)cancelRecording;
/**
* 當(dāng)前是否正在錄音
*
*/
- (BOOL)isRecording;
#pragma mark - LCAudioPlay
/**
* 播放音頻
*
*/
- (void)playingWithRecordPath:(NSString *)recordPath
completion:(void(^)(NSError *error))completion;
/**
* 停止播放
*
*/
- (void)stopPlaying;
/**
* 當(dāng)前是否正在播放
*
*/
-(BOOL)isPlaying;
@end
.m文件
// 當(dāng)前代碼是將 錄音轉(zhuǎn)換為mp3, 如不需轉(zhuǎn)換 直接注釋此代碼, 并設(shè)置一下相應(yīng)的錄音格式,例如通道數(shù),采樣率等
//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", @"停止當(dāng)前錄音") 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]) { // 如果沒(méi)有轉(zhuǎn)化成功,嘗試再次轉(zhuǎn)換
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", @"轉(zhuǎn)換文件失敗") 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
// 使用三方庫(kù) lame // 需要將libmp3lame 拖進(jìn)項(xiàng)目
- (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 被轉(zhuǎn)換的音頻文件位置
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;
/**
* 當(dāng)前是否正在播放
*
*/
- (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;
/**
* 當(dāng)前是否正在錄音
*
*/
- (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) { // 轉(zhuǎn)換為的MP3格式
_recordSetting = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithFloat: 11025.0],AVSampleRateKey, //采樣率 44100.0
[NSNumber numberWithInt: kAudioFormatLinearPCM],AVFormatIDKey,
[NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,//采樣位數(shù) 默認(rèn) 16
[NSNumber numberWithInt: 2], AVNumberOfChannelsKey,//通道的數(shù)目
[NSNumber numberWithInt:AVAudioQualityMax], AVEncoderAudioQualityKey, // 錄音質(zhì)量
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.開始錄音
// 檢測(cè)麥克風(fēng)是否可用
[LCAudioManager manager] checkMicrophoneAvailability]
[[LCAudioManager manager] startRecordingWithFileName:[NSString recordFileName] completion:nil];
2.結(jié)束錄音
[[LCAudioManager manager] stopRecordingWithCompletion:^(NSString *recordPath, NSInteger aDuration, NSError *error) {
if (aDuration < 1) {
[MBProgressHUD showError:@"錄音時(shí)間過(guò)短"];
return ;
}
if (!error) { // 錄音成功
// 執(zhí)行下一步計(jì)劃
}
}];