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

音频会话

在使用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来完成音效和音频的播放

音效播放

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];
    }
}

音乐播放

如果播放较大的音频或者要对音频有精确的控制则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(@"音乐播放完成...");
}

音频录制

在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;
}

音频管理

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

.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


补充:音频队列服务

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

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

录音音频服务队列

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

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

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

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

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

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

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

音频播放缓冲队列

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

这里写图片描述

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


参考地址

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

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值