iOS 音频的录制、播放及音频文件管理

[TOC]

音频会话

在使用Apple设备时,我们注意到有些应用打开音频播放时,其他音频就会终止,而有些应用却可以同时使用音频,这就出现了多种声音的情况,这些音频环境就涉及了音频会话内容:iOS中,每个应用都有一个音频会话,及AVAudioSession,它属于AVFoundation框架中,是一种单例模式。

会话类型 说明 是否要求输入 是否需要输出 是否遵从静音键
AVAudioSessionCategoryAmbient 混音播放,可以与其他音频应用同时播放
AVAudioSessionCategorySoloAmbient 独占播放
AVAudioSessionCategoryPlayback 后台播放,也是独占的
AVAudioSessionCategoryRecord 录音模式,用于录音时使用
AVAudioSessionCategoryPlayAndRecord 播放和录音,此时可以录音也可以播放
AVAudioSessionCategoryAudioProcessing 硬件解码音频,此时不能播放和录制
AVAudioSessionCategoryMultiRoute 多种输入输出,例如可以耳机、USB设备同时播放

注意:是否遵循静音键表示在播放过程中如果用户通过硬件设置为静音是否能关闭声音

例:设置录音环境

NSError *sessionError;
// 设置音频会话为录音环境
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:&sessionError];
if (sessionError){
    NSLog(@"Error creating session: %@",[sessionError description]);
}else{
    // 启动该会话
    [[AVAudioSession sharedInstance] setActive:YES error:&sessionError];
}

说明:假如我们设置的音频会话为独占的话(如:后台播放),其他正在播放音频的应用就会被终止播放


音频的播放

音频的播放分为音效播放和音乐播放。前者是一些短音频播放,不需要进度、循环次数控制。后者是一些较长的音频,需要对进度、循环次数做精确控制。在iOS中,分别使用AudioToolbox.framework和AVFoundation.framework来完成音效和音频的播放

[TOC]

音效播放

AudioToolbox.framework是一套基于C语言的框架,使用它来播放音效其本质是将短音频注册到系统声音服务(System Sound Service)。System Sound Service是一种简单、底层的声音播放服务,但是它本身也存在着一些限制:

  1. 音频播放时间不能超过30s
  2. 数据必须是PCM或者IMA4格式
  3. 音频文件必须打包成.caf、.aif、.wav中的一种

使用步骤如下:

  1. 调用AudioServicesCreateSystemSoundID函数来获取系统声音ID
  2. 调用AudioServicesPlaySystemSound或者AudioServicesPlayAlertSound 方法播放音效(后者带有震动效果)
  3. 如果需要监听播放完成操作,则使用AudioServicesAddSystemSoundCompletion方法注册回调函数

例子:

-(void)playAudioWithName:(NSString*)soundName{
    NSURL* system_sound_url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:soundName ofType:nil]];
    //1.获取系统声音ID
    SystemSoundID system_sound_id = 0;
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)system_sound_url,&system_sound_id);
    //需要播放完成之后执行某些操作,可以调用下面方法注册一个播放完成回调函数
/*    AudioServicesAddSystemSoundCompletion(system_sound_id,
                                          NULL, // uses the main run loop
                                          NULL, // uses kCFRunLoopDefaultMode
                                          soundCompleteCallback, // the name of our custom callback function
                                          NULL // for user data, but we don't need to do that in this case, so we just pass NULL
                                          );
*/
    //2.播放音频
//    AudioServicesPlaySystemSound(system_sound_id);//播放音效
    AudioServicesPlayAlertSound(system_sound_id);//播放并震动
}

void soundCompleteCallback(SystemSoundID soundID,void * userData){
 NSLog(@"播放完成");
 //do what you want to do
 AudioServicesDisposeSystemSoundID(soundID);
}

补充:获取系统音效

_systemSounds               = [NSMutableArray array];
// 读取文件系统
NSFileManager *fileManager  = [NSFileManager defaultManager];
NSURL         *directoryURL = [NSURL URLWithString:@"/System/Library/Audio/UISounds"];
NSArray       *keys         = [NSArray arrayWithObject:NSURLIsDirectoryKey];
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtURL:directoryURL
                                      includingPropertiesForKeys:keys
                                                         options:0
                                                    errorHandler:^(NSURL *url, NSError *error) {
                                                        return YES;
                                                    }];
for (NSURL *url in enumerator) {
    NSError  *error;
    NSNumber *isDirectory = nil;
    if (! [url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
    } else if (![isDirectory boolValue]) {
        SystemSoundID soundID;
        AudioServicesCreateSystemSoundID((__bridge_retained CFURLRef)url, &soundID);
        // 音效的数据模型
        SoundInfomation *sound = [[SoundInfomation alloc] init];
        sound.soundID   = soundID;
        sound.soundUrl  = url;
        sound.soundName = url.lastPathComponent;
        [_systemSounds addObject:sound];
    }
}

[TOC]

音乐播放

如果播放较大的音频或者要对音频有精确的控制则System Sound Service可能就很难满足实际需求了,通常这种情况会选择使用AVFoundation.framework中的AVAudioPlayer来实现。AVAudioPlayer可以看成一个播放器,它支持多种音频格式,而且能够进行进度、音量、播放速度等控制

属性 说明
@property(readonly, getter=isPlaying) BOOL playing 是否正在播放,只读
@property(readonly) NSUInteger numberOfChannels 音频声道数,只读
@property(readonly) NSTimeInterval duration 音频时长
@property(readonly) NSURL *url 音频文件路径,只读
@property(readonly) NSData *data 音频数据,只读
@property float pan 立体声平衡,如果为-1.0则完全左声道,如果0.0则左右声道平衡,如果为1.0则完全为右声道
@property float volume 音量大小,范围0-1.0
@property BOOL enableRate 是否允许改变播放速率
@property float rate 播放速率,范围0.5-2.0,如果为1.0则正常播放,如果要修改播放速率则必须设置enableRate为YES
@property NSTimeInterval currentTime 当前播放时长
@property(readonly) NSTimeInterval deviceCurrentTime 输出设备播放音频的时间,注意如果播放中被暂停此时间也会继续累加
@property NSInteger numberOfLoops 循环播放次数,如果为0则不循环,如果小于0则无限循环,大于0则表示循环次数
@property(readonly) NSDictionary *settings 音频播放设置信息,只读
@property(getter=isMeteringEnabled) BOOL meteringEnabled 是否启用音频测量,默认为NO,一旦启用音频测量可以通过updateMeters方法更新测量值
对象方法 说明
-(instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError 使用文件URL初始化播放器,注意这个URL不能是HTTP URL,AVAudioPlayer不支持加载网络媒体流,只能播放本地文件,如果需要播放网络URL,可以尝试AVPlayer
-(BOOL)prepareToPlay 加载音频文件到缓冲区,注意即使在播放之前音频文件没有加载到缓冲区程序也会隐式调用此方法。
-(BOOL)play 播放音频文件
-(BOOL)playAtTime:(NSTimeInterval)time 在指定的时间开始播放音频
-(void)pause 暂停播放
-(void)stop 停止播放
-(void)updateMeters 更新音频测量值,注意如果要更新音频测量值必须设置meteringEnabled为YES,通过音频测量值可以即时获得音频分贝等信息
-(float)peakPowerForChannel:(NSUInteger)channelNumber 获得指定声道的分贝峰值,注意如果要获得分贝峰值必须在此之前调用updateMeters方法
-(float)averagePowerForChannel:(NSUInteger)channelNumber 获得指定声道的分贝平均值,注意如果要获得分贝平均值必须在此之前调用updateMeters方法
代理方法 说明
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 音频播放完成
-(void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error 音频解码发生错误
-(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player 播放被打断
-(void)audioPlayerEndInterruption:(AVAudioPlayer *)player 结束打断

使用步骤:

  1. 初始化AVAudioPlayer对象,此时需要指定本地文件路径
  2. 设置播放器属性,例如重复次数、音量大小等
  3. 调用play方法播放

示例:

-(void)playAudioWithUrl:(NSURL*)url{
    NSError *error=nil;
    // 1、初始化
    _audioPlayer=[[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
    // 2、设置相关属性
    _audioPlayer.numberOfLoops=0;   // 设置为0不循环
    _audioPlayer.delegate = self;
    if (error) {
        NSLog(@"创建播放器过程中发生错误,错误信息:%@",error.localizedDescription);
    }
    if (![_audioPlayer isPlaying]) {
        //解决音量小的问题
        AVAudioSession *audioSession = [AVAudioSession sharedInstance];
        NSError *err = nil;
        [audioSession setCategory:AVAudioSessionCategoryPlayback error:&err];
        // 3、播放
        [_audioPlayer play];    // 播放音频
    }
}
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    NSLog(@"音乐播放完成...");
}

[TOC]

音频录制

在AVFoundation框架中还要一个AVAudioRecorder类专门处理录音操作,它同样支持多种音频格式。与AVAudioPlayer类似,你完全可以将它看成是一个录音机控制类

属性 说明
@property(readonly, getter=isRecording) BOOL recording 是否正在录音,只读
@property(readonly) NSURL *url 录音文件地址,只读
@property(readonly) NSDictionary *settings 录音文件设置,只读
@property(readonly) NSTimeInterval currentTime 录音时长,只读,注意仅仅在录音状态可用
@property(readonly) NSTimeInterval deviceCurrentTime 输入设置的时间长度,只读,注意此属性一直可访问
@property(getter=isMeteringEnabled) BOOL meteringEnabled 是否启用录音测量,如果启用录音测量可以获得录音分贝等数据信息
对象方法 说明
-(instancetype)initWithURL:(NSURL *)url settings:(NSDictionary *)settings error:(NSError **)outError 录音机对象初始化方法,注意其中的url必须是本地文件url,settings是录音格式、编码等设置
-(BOOL)prepareToRecord 准备录音,主要用于创建缓冲区,如果不手动调用,在调用record录音时也会自动调用
-(BOOL)record 开始录音/恢复录音
-(BOOL)recordAtTime:(NSTimeInterval)time 在指定将来的某个时刻开始录音
-(BOOL)recordForDuration:(NSTimeInterval) duration 指定的时长开始录音,如60'
-(BOOL)recordAtTime:(NSTimeInterval)time forDuration:(NSTimeInterval) duration 在指定的时间开始录音,并指定录音时长
-(void)pause 暂停录音
-(void)stop 停止录音
-(BOOL)deleteRecording 删除录音,注意要删除录音此时录音机必须处于停止状态
-(void)updateMeters 更新测量数据,注意只有meteringEnabled为YES此方法才可用
-(float)peakPowerForChannel:(NSUInteger)channelNumber 指定通道的测量峰值,注意只有调用完updateMeters才有值
-(float)averagePowerForChannel:(NSUInteger)channelNumber 指定通道的测量平均值,注意只有调用完updateMeters才有值
代理方法 说明
-(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag 完成录音
-(void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError *)error 录音编码发生错误
-(void)audioRecorderBeginInterruption:(AVAudioRecorder *)recorder 录音被打断
-(void)audioRecorderEndInterruption:(AVAudioRecorder *)recorder 录音打断结束

AVAudioRecorder很多属性和方法跟AVAudioPlayer都是类似的,但是它的创建有所不同,在创建录音机时除了指定路径外还必须指定录音设置信息,因为录音机必须知道录音文件的格式、采样率、通道数、每个采样点的位数等信息,但是也并不是所有的信息都必须设置,通常只需要几个常用设置

使用步骤:

  1. 设置音频会话类型为AVAudioSessionCategoryRecord,录音模式
  2. 创建录音机AVAudioRecorder,指定录音保存的路径并且设置录音属性
  3. 调用record开始录制

示例:

-(void)setupRecorder{
    //设置音频会话
    NSError *sessionError;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:&sessionError];
    if (sessionError){
        NSLog(@"Error creating session: %@",[sessionError description]);
    }else{
        [[AVAudioSession sharedInstance] setActive:YES error:&sessionError];
    }
    //录音设置
    //创建录音文件保存路径
    NSURL *url = [self getSavePath];
    //创建录音机
    NSError *error = nil;
    _audioRecorder = [[AVAudioRecorder alloc] initWithURL:url settings:self.setting error:&error];
    if (error) {
        NSLog(@"创建录音机对象时发生错误,错误信息:%@",error.localizedDescription);
    }
    _audioRecorder.delegate = self;
    _audioRecorder.meteringEnabled = YES;//如果要监控声波则必须设置为YES
    [_audioRecorder prepareToRecord];
    if (![_audioRecorder isRecording]) {
        [_audioRecorder record];//首次使用应用时如果调用record方法会询问用户是否允许使用麦克风
    }
}

// !!!: 录音设置
-(NSDictionary *)setting{
    if (_setting==nil) {
        NSMutableDictionary *setting = [NSMutableDictionary dictionary];
        //录音格式
        [setting setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey];
        //采样率,8000/11025/22050/44100/96000(影响音频的质量),8000是电话采样率
        [setting setObject:@(22050) forKey:AVSampleRateKey];
        //通道 , 1/2
        [setting setObject:@(2) forKey:AVNumberOfChannelsKey];
        //采样点位数,分为8、16、24、32, 默认16
        [setting setObject:@(16) forKey:AVLinearPCMBitDepthKey];
        //是否使用浮点数采样
        [setting setObject:@(YES) forKey:AVLinearPCMIsFloatKey];
        // 录音质量
        [setting setObject:@(AVAudioQualityHigh) forKey:AVEncoderAudioQualityKey];
        //....其他设置等
    }
    return _setting;
}

[TOC]

音频管理

下面是笔者自己写的一个音频管理类。可以进行音频录制、播放、获取音频信息、管理音频文件等功能

.h 文件

//
//  AudioManager.h
//  ExeToExp
//
//  Created by LOLITA on 17/3/28.
//  Copyright © 2017年 LOLITA. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@protocol AudioManagerDelegate;
@interface AudioManager : NSObject
@property (nonatomic,weak) id <AudioManagerDelegate> delegate;

+(instancetype)sharedInstance;

// !!!: 开始录制
-(void)startRecord;

// !!!: 暂停录制
-(void)pauseRecord;

// !!!: 恢复录制
-(void)resumeRecord;

// !!!: 停止录制
-(void)stopRecord;

// !!!: 取消当前录制
-(void)cancelRecord;

// !!!: 播放语音
-(void)playAudioWithUrl:(NSURL*)url;

// !!!: 停止语音播放
-(void)stopPlay;

// !!!: 暂停语音播放
-(void)pausePlay;

// !!!: 恢复语音播放
-(void)resumePlay;

// !!!: 获取当前录制文件的路径
-(NSURL*)recordCurrentAudioFile;

// !!!: 获取语音时长
-(float)durationWithAudio:(NSURL *)audioUrl;

// !!!: 删除本地音频文件下所有文件
-(void)removeAllAudioFile;

// !!!: 删除本地指定音频文件
-(void)removeAudioFile:(NSURL*)url;

// !!!: 删除指定后缀的文件,如“.wav”,“.caf”
-(void)removeFileSuffixList:(NSArray<NSString*>*)suffixList filePath:(NSString*)path;
@end

@protocol AudioManagerDelegate <NSObject>
@optional
-(void)audioRecorderDidFinishRecording:(AVAudioRecorder*)recorder successfullyFlag:(BOOL)flag;  // 录制完成
-(void)audioPowerChange:(CGFloat)power; // 音量
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag; // 播放完成
@end

.m 文件

//
//  AudioManager.m
//  ExeToExp
//
//  Created by LOLITA on 17/3/28.
//  Copyright © 2017年 LOLITA. All rights reserved.
//

#define kAudioFolder @"AudioFolder" // 音频文件夹
#import "AudioManager.h"
@interface AudioManager ()<AVAudioRecorderDelegate,AVAudioPlayerDelegate>
@property (nonatomic,strong) AVAudioRecorder *audioRecorder;    // 录音机
@property (nonatomic,strong) AVAudioPlayer *audioPlayer;        // 音频播放器
@property (strong ,nonatomic) NSDictionary *setting;            // 录音机的设置
@property (copy ,nonatomic) NSString *audioDir;                 // 录音文件夹路径
@property (nonatomic,strong) NSTimer *timer;    // 录音声波监控
@property (copy ,nonatomic) NSString *filename; // 记录当前文件名
@property (assign ,nonatomic) BOOL cancelCurrentRecord;    // 取消当前录制
@end;

@implementation AudioManager
+(instancetype)sharedInstance{
    static AudioManager *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[AudioManager alloc] init];
    });
    return instance;
}
#pragma mark - <************************** 一些初始化 **************************>
// !!!: 配置录音机
-(void)setupRecorder{
    //设置音频会话
    NSError *sessionError;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:&sessionError];
    if (sessionError){
        NSLog(@"Error creating session: %@",[sessionError description]);
    }else{
        [[AVAudioSession sharedInstance] setActive:YES error:&sessionError];
    }
    //录音设置
    //创建录音文件保存路径
    NSURL *url = [self getSavePath];
    //创建录音机
    NSError *error = nil;
    _audioRecorder = [[AVAudioRecorder alloc] initWithURL:url settings:self.setting error:&error];
    _audioRecorder.delegate = self;
    _audioRecorder.meteringEnabled = YES;//如果要监控声波则必须设置为YES
    [_audioRecorder prepareToRecord];
    if (error) {
        NSLog(@"创建录音机对象时发生错误,错误信息:%@",error.localizedDescription);
    }
}


// !!!: 录音声波监
-(NSTimer *)timer{
    if (!_timer) {
        _timer=[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(powerChange) userInfo:nil repeats:YES];
    }
    return _timer;
}

// !!!: 录音设置
-(NSDictionary *)setting{
    if (_setting==nil) {
        NSMutableDictionary *setting = [NSMutableDictionary dictionary];
        //录音格式
        [setting setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey];
        //采样率,8000/11025/22050/44100/96000(影响音频的质量),8000是电话采样率
        [setting setObject:@(22050) forKey:AVSampleRateKey];
        //通道 , 1/2
        [setting setObject:@(2) forKey:AVNumberOfChannelsKey];
        //采样点位数,分为8、16、24、32, 默认16
        [setting setObject:@(16) forKey:AVLinearPCMBitDepthKey];
        //是否使用浮点数采样
        [setting setObject:@(YES) forKey:AVLinearPCMIsFloatKey];
        // 录音质量
        [setting setObject:@(AVAudioQualityHigh) forKey:AVEncoderAudioQualityKey];
        //....其他设置等
    }
    return _setting;
}

// !!!: 录音文件夹
-(NSString *)audioDir{
    if (_audioDir==nil) {
        _audioDir = NSTemporaryDirectory();
        _audioDir = [_audioDir stringByAppendingPathComponent:kAudioFolder];
        BOOL isDir = NO;
        NSFileManager *fileManager = [NSFileManager defaultManager];
        BOOL existed = [fileManager fileExistsAtPath:_audioDir isDirectory:&isDir];
        if (!(isDir == YES && existed == YES)){
            [fileManager createDirectoryAtPath:_audioDir withIntermediateDirectories:YES attributes:nil error:nil];
        }
    }
    return _audioDir;
}

#pragma mark - <************************** 事件 **************************>
// !!!: 开始录制
-(void)startRecord{
    [self setupRecorder];
    if (![self.audioRecorder isRecording]) {
        [self.audioRecorder record];//首次使用应用时如果调用record方法会询问用户是否允许使用麦克风
//        [self.audioRecorder recordForDuration:60];    // 录音时长
        self.timer.fireDate=[NSDate distantPast];
    }
}
// !!!: 暂停录制
-(void)pauseRecord{
    if ([self.audioRecorder isRecording]) {
        [self.audioRecorder pause];
        self.timer.fireDate=[NSDate distantFuture];
    }
}
// !!!: 恢复录制
-(void)resumeRecord{
    if (![self.audioRecorder isRecording]) {
        [self.audioRecorder record];
        self.timer.fireDate=[NSDate distantPast];
    }
}
// !!!: 停止录制
-(void)stopRecord{
    [self.audioRecorder stop];
    self.timer.fireDate=[NSDate distantFuture];
}

// !!!: 取消当前录制
-(void)cancelRecord{
    self.cancelCurrentRecord = YES;
    [self stopRecord];
    if ([self.audioRecorder deleteRecording]) {
        NSLog(@"删除录音文件!");
    }
}


// !!!: 播放音频文件
-(void)playAudioWithUrl:(NSURL*)url{
    //语音播放
    NSError *error=nil;
    _audioPlayer=[[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
    _audioPlayer.numberOfLoops=0;   // 设置为0不循环
    _audioPlayer.delegate = self;
    if (error) {
        NSLog(@"创建播放器过程中发生错误,错误信息:%@",error.localizedDescription);
    }
    if (![_audioPlayer isPlaying]) {
        //解决音量小的问题
        AVAudioSession *audioSession = [AVAudioSession sharedInstance];
        NSError *err = nil;
        [audioSession setCategory:AVAudioSessionCategoryPlayback error:&err];
        [_audioPlayer play];    // 播放音频
    }
}

// !!!: 停止播放语音
-(void)stopPlay{
    [self.audioPlayer stop];
}

// !!!: 暂停语音
-(void)pausePlay{
    [self.audioPlayer pause];
}

// !!!: 恢复语音
-(void)resumePlay{
    [self.audioPlayer play];
}


#pragma mark - <************************** 获取数据 **************************>
// !!!: 获取录音保存路径
-(NSURL*)getSavePath{
    self.filename = [NSString stringWithFormat:@"audio_%@.wav",[self getDateString]];
    NSString* fileUrlString = [self.audioDir stringByAppendingPathComponent:self.filename];
    NSURL *url = [NSURL fileURLWithPath:fileUrlString];
    return url;
}

// !!!: 返回音频文件地址
-(NSURL *)recordCurrentAudioFile{
    NSString* fileUrlString = [self.audioDir stringByAppendingPathComponent:self.filename];
    NSURL *url = [NSURL fileURLWithPath:fileUrlString];
    return url;
}


// !!!: 获取语音时长
-(float)durationWithAudio:(NSURL *)audioUrl{
    AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:audioUrl options:nil];
    CMTime audioDuration = audioAsset.duration;
    float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
    return audioDurationSeconds;
}


// !!!: 删除所有文件夹
-(void)removeAllAudioFile{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager removeItemAtPath:self.audioDir error:nil]) {
        NSLog(@"删除文件夹成功!!");
    }
}

// !!!: 删除指定文件
-(void)removeAudioFile:(NSURL *)url{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager removeItemAtPath:url.path error:nil]) {
        NSLog(@"删除录音文件成功!!");
    }
}

// !!!: 删除指定后缀的文件
-(void)removeFileSuffixList:(NSArray<NSString *> *)suffixList filePath:(NSString *)path{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *contentOfFolder = [fileManager contentsOfDirectoryAtPath:path error:NULL];
    for (NSString *aPath in contentOfFolder) {
        NSString * fullPath = [path stringByAppendingPathComponent:aPath];
        BOOL isDir = NO;
        if ([fileManager fileExistsAtPath:fullPath isDirectory:&isDir]) {
            if (isDir == YES) {
                // 是文件夹,则继续遍历
                [self removeFileSuffixList:suffixList filePath:fullPath];
            }
            else{
                NSLog(@"file-:%@", aPath);
                for (NSString* suffix in suffixList) {
                    if ([aPath hasSuffix:suffix]) {
                        if ([fileManager removeItemAtPath:fullPath error:nil]) {
                            NSLog(@"删除文件成功!!");
                        }
                    }
                }
            }
        }
    }
}



#pragma mark - <************************** 代理方法 **************************>
// !!!: 录音代理事件
-(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{
    if (self.cancelCurrentRecord) {
        self.cancelCurrentRecord = NO;
        NSLog(@"取消录制!");
    }
    else{
        if (self.delegate&&[self.delegate respondsToSelector:@selector(audioRecorderDidFinishRecording:successfullyFlag:)]) {
            [self.delegate audioRecorderDidFinishRecording:recorder successfullyFlag:flag];
        }
        NSLog(@"录制完成!");
    }
}
// !!!: 播放语音代理事件
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    if (self.delegate&&[self.delegate respondsToSelector:@selector(audioPlayerDidFinishPlaying:successfully:)]) {
        [self.delegate audioPlayerDidFinishPlaying:player successfully:flag];
    }
    NSLog(@"播放完成!");
}


#pragma mark - <************************** 私有方法 **************************>
// !!!: 获取时刻名称
-(NSString*)getDateString{
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSInteger unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekday |
    NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
    NSDateComponents *comps  = [calendar components:unitFlags fromDate:[NSDate date]];
    NSInteger year = [comps year];
    NSInteger month = [comps month];
    NSInteger day = [comps day];
    NSInteger hour = [comps hour];
    NSInteger min = [comps minute];
    NSInteger sec = [comps second];
    NSString* formatString = @"%d%02d%02d%02d%02d%02d";
    return [NSString stringWithFormat:formatString, year, month, day, hour, min, sec];
}

// !!!: 录音声波状态设置
-(void)powerChange{
    [self.audioRecorder updateMeters];//更新测量值
    float power = [self.audioRecorder averagePowerForChannel:0];//取得第一个通道的音频,注意音频强度范围时-160到0
    CGFloat progress = power+160.0;
    NSLog(@"音频强度:%f",power);
    if (self.delegate&&[self.delegate respondsToSelector:@selector(audioPowerChange:)]) {
        [self.delegate audioPowerChange:progress];
    }
}

-(void)dealloc{
    [self removeAllAudioFile];
}

@end


[TOC]

补充:音频队列服务

音频队列服务Audio Queue Services属于AudioToolbox框架,使用音频队列服务可以做到音频流式播放和录制

首先看一下录音音频服务队列:

录音音频服务队列

一个音频服务队列Audio Queue有三部分组成:

三个缓冲器Buffers:每个缓冲器都是一个存储音频数据的临时仓库。

一个缓冲队列Buffer Queue:一个包含音频缓冲器的有序队列。

一个回调Callback:一个自定义的队列回调函数。

声音通过输入设备进入缓冲队列中,首先填充第一个缓冲器;当第一个缓冲器填充满之后自动填充下一个缓冲器,同时会调用回调函数;在回调函数中需要将缓冲器中的音频数据写入磁盘,同时将缓冲器放回到缓冲队列中以便重用。下面是Apple官方关于音频队列服务的流程示意图:

音频队列服务的流程示意图

类似的,看一下音频播放缓冲队列,其组成部分和录音缓冲队列类似。

音频播放缓冲队列

但是在音频播放缓冲队列中,回调函数调用的时机不同于音频录制缓冲队列,流程刚好相反。将音频读取到缓冲器中,一旦一个缓冲器填充满之后就放到缓冲队列中,然后继续填充其他缓冲器;当开始播放时,则从第一个缓冲器中读取音频进行播放;一旦播放完之后就会触发回调函数,开始播放下一个缓冲器中的音频,同时填充第一个缓冲器放;填充满之后再次放回到缓冲队列。下面是详细的流程:

这里写图片描述

当然,要明白音频队列服务的原理并不难,问题是如何实现这个自定义的回调函数,这其中我们有大量的工作要做,控制播放状态、处理异常中断、进行音频编码等等。由于牵扯内容过多,而且不是本文目的,如果以后有时间将另开一篇文章重点介绍,目前有很多第三方优秀框架可以直接使用,例如AudioStreamer、FreeStreamer。(前者当前只有非ARC版本)


[TOC]

参考地址

iOS开发系列--音频播放、录音、视频播放、拍照、视频录制

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

推荐阅读更多精彩内容