600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > iOS 音频的录制 播放及音频文件管理

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

时间:2022-01-15 08:05:54

相关推荐

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

文章目录

音频会话音效播放音乐播放音频录制音频管理补充:音频队列服务参考地址

音频会话

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

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

例:设置录音环境

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是一种简单、底层的声音播放服务,但是它本身也存在着一些限制:

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

使用步骤如下:

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

例子:

-(void)playAudioWithName:(NSString*)soundName{NSURL* system_sound_url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:soundName ofType:nil]];//1.获取系统声音IDSystemSoundID system_sound_id = 0;AudioServicesCreateSystemSoundID((__bridge CFURLRef)system_sound_url,&system_sound_id);//需要播放完成之后执行某些操作,可以调用下面方法注册一个播放完成回调函数/* AudioServicesAddSystemSoundCompletion(system_sound_id,NULL, // uses the main run loopNULL, // uses kCFRunLoopDefaultModesoundCompleteCallback, // the name of our custom callback functionNULL // 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 doAudioServicesDisposeSystemSoundID(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:directoryURLincludingPropertiesForKeys:keysoptions:0errorHandler:^(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可以看成一个播放器,它支持多种音频格式,而且能够进行进度、音量、播放速度等控制

使用步骤:

初始化AVAudioPlayer对象,此时需要指定本地文件路径设置播放器属性,例如重复次数、音量大小等调用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类似,你完全可以将它看成是一个录音机控制类

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

使用步骤:

设置音频会话类型为AVAudioSessionCategoryRecord,录音模式创建录音机AVAudioRecorder,指定录音保存的路径并且设置录音属性调用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 © 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 © 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到0CGFloat 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开发系列–音频播放、录音、视频播放、拍照、视频录制

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。