iOS开发之音频播放、录音

yqv18512 4年前

来自: http://www.cnblogs.com/wuhongxing/p/5189465.html

iOS的音频播放可以分为短音频播放(例如:音效等点缀音频)和长音频播放(例:音乐等主音频)。前者不需要对进度、循环等进行控制,而后者需要精确的控制。在iOS中播放这两种音频分别使用AudioToolbox.framewor k和AVFoundat ion.framework来完成。

短音频音效

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

  • No longer than 30 seconds in duration(播放时间小于30s)

  • In linear PCM or IMA4 (IMA/ADPCM) format(数据必须是pcm或者ima4格式的)

  • Packaged in a .caf ,  .aif , or  .wav file(音频文件必须打包成.caf,.aif,.wav文件)

使用System Sound Service 播放音效的步骤如下(封装成一个播放短音频的工具类):

+ (void)initialize {      // 加载所有音频文件      // 1.遍历所有的plane.bundle的所有的音频文件      NSFileManager *manage = [NSFileManager defaultManager];            // 2.获了plane.bundle的路径      NSString *planePath   = [[NSBundle mainBundle] pathForResource:@"plane.bundle" ofType:nil];            NSArray *contents     = [manage contentsOfDirectoryAtPath:planePath error:nil];            // 3.遍历里面的mp3文件,创建SystemSoundID;      NSMutableDictionary *soundDictM = [NSMutableDictionary dictionary];            for (NSString *soundName in contents) {          // 音频的URL          NSString *soundUrlPath = [planePath stringByAppendingPathComponent:soundName];                    NSURL *soundUrl       = [NSURL fileURLWithPath:soundUrlPath];                    SystemSoundID soundID;                    // 创建声音          AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundUrl, &soundID);                    soundDictM[soundName] = @(soundID);      }      soundDict = soundDictM;  }    - (void)playShortSoundWithName:(NSString *)soundName {      // 播放声音      AudioServicesPlaySystemSound([soundDict[soundName] unsignedIntValue]);  }

长音频播放(使用AVFoundation.framework的AVAudioPlayer实现)

AVAudioPlayer的使用就比较简单了:

  1.  初始化AVAudioPlayer对象,此时通常指定本地文件路径。
  2.  设置播放器的属性,例如重复次数、音量大小等。
  3.  调用play方法播放.
属性 说明
@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不支持加载网络媒体流,只能播放本地文件
- (instancetype)initWithData:(NSData *)data error:(NSError **)outError 使用NSData初始化播放器,注意使用此方法时必须文件格式和文件后缀一致,否则出错,所以相比此方法更推荐使用上述方法或- (instancetype)initWithData:(NSData *)data fileTypeHint:(NSString *)utiString error:(NSError **)outError方法进行初始化
- (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方法
@property(nonatomic, copy) NSArray *channelAssignments 获得或设置播放声道
代理方法 说明
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 音频播放完成
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error 音频解码发生错误

下面就使用AVAudioPlayer实现一个简单播放器,在这个播放器中实现了播放、暂停、显示播放进度功能,当然例如调节音量、设置循环模式、甚至是声波图像(通过分析音频分贝值)等功能都可以实现,这里就不再一一演示。界面效果如下:

- (void)viewDidLoad {      [super viewDidLoad];      // Do any additional setup after loading the view.        }    - (IBAction)stop:(id)sender {      /**       *  Stops playback and undoes the setup needed for playback.       */      [_player stop];            // 自己指定播放的时间      _player.currentTime = 0;  }    - (IBAction)start:(id)sender {      // 获取mp3路径      NSURL *mp3Url = [[NSBundle mainBundle] URLForResource:@"bbqne.mp3" withExtension:nil];            static dispatch_once_t onceToken;      dispatch_once(&onceToken, ^{          _player            = [[AVAudioPlayer alloc] initWithContentsOfURL:mp3Url error:nil];                    /**           *  A Boolean value that specifies whether playback rate adjustment is enabled for an audio player.           */          _player.enableRate = YES;                    /**           *  Prepares the audio player for playback by preloading its buffers.           */          [_player prepareToPlay];                    // 这里不能加全局断点,不然会崩          /**           *  The number of times a sound will return to the beginning, upon reaching the end, to repeat playback.           */          _player.numberOfLoops    = MAXFLOAT;                    _player.delegate         = self;                    _displayLink             = [CADisplayLink displayLinkWithTarget:self                                                                 selector:@selector(playMusic)];                    [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];                    AVAudioSession *session  = [AVAudioSession sharedInstance];                    [[NSNotificationCenter defaultCenter] addObserver:self                                                   selector:@selector(handleInterruption:)                                                       name:AVAudioSessionInterruptionNotification                                                     object:session];                    _timeSlider.maximumValue = _player.duration;      });            /**       *  Plays a sound asynchronously.       */      [_player play];        }  /**   *  改变进度条   */  - (void)playMusic {      _timeSlider.value = _player.currentTime;            int sec           = _timeSlider.value;        _timeLabel.text   = [NSString stringWithFormat:@"%02d",sec];              [_player updateMeters];            double lowPassResults = pow(10, (0.05 * [self.player peakPowerForChannel:0]));            float result  = 10 * (float)lowPassResults;            NSLog(@"%.2f",result);  }    - (void)handleInterruption:(NSNotification *)noti {      // 处理中断事件      NSLog(@"处理中断事件");  }    - (IBAction)pause:(id)sender {      /**       *  Pauses playback; sound remains ready to resume playback from where it left off.       */      [_player pause];  }    /**   *  指定当前播放的时间   */  - (IBAction)timeChanged:(UISlider *)sender {      _player.currentTime = sender.value;  }    /**   *  改变播放速度   */  - (IBAction)rateChange:(UISlider *)sender {      _player.rate = sender.value;  }    /**   *  改变播放音量   */  - (IBAction)volunmChanged:(UISlider *)sender {      _player.volume = sender.value;  }

录音

在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; 是否启用录音测量,如果启用录音测量可以获得录音分贝等数据信息
@property(nonatomic, copy) NSArray *channelAssignments 当前录音的通道
对象方法 说明
- (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 按指定的时长开始录音
- (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

程序的构建主要分为以下几步:

  1. 设置音频会话类型为AVAudioSessionCategoryPlayAndRecord,因为程序中牵扯到录音和播放操作。 
  2. 创建录音机AVAudioRecorder,指定录音保存的路径并且设置录音属性,注意对于一般的录音文件要求的采样率、位数并不高,需要适当设置以保证录音文件的大小和效果。 
  3. 设置录音机代理以便在录音完成后播放录音,打开录音测量保证能够实时获得录音时的声音强度。(注意声音强度范围-160到0,0代表最大输入) 
  4. 创建音频播放器AVAudioPlayer,用于在录音完成之后播放录音。 
  5. 创建一个定时器以便实时刷新录音测量值并更新录音强度到UIProgressView中显示。 
  6. 添加录音、暂停、恢复、停止操作,需要注意录音的恢复操作其实是有音频会话管理的,恢复时只要再次调用record方法即可,无需手动管理恢复时间等。

下面是主要代码