一、音频
在iOS中音频播放从形式上可以分为音效播放和音乐播放。前者主要指一些段音频播放,通常作为点缀音频,对于这类音频不需要进行进度、循环控制。后者指的是一些较长的音频,通常是主音频,对于这些音频,播放通常需要精确的控制。在iOS中播放两类视频分别使用AudioToolbox.framework 和 AVFoundation.framework 来完成音效和音乐播放。
1、音效
AudioToolbox.framework 是一套基于C语言的框架,使用它来播放音效其本质是将短音频注册到系统声音服务(System Sound Service)。System Sound Service 是一种简单、底层的声音播放服务,但它本身存在一些限制:
- 音频播放时间不能够超过30s
- 数据必须是PCM或者IMA4格式
- 音频文件必须打包成.caf、.aif、.wav 中的一种(这是官方说法,实际测试中一些.mp3 也可以播放)
使用System Sound Service 播放音效的步骤如下:
I:调用AudioServicesCreateSystemSoundID(CFURLRef inFileURL, SystemSoundID *outSystemSoundID)函数获得系统声音ID。
II:如果需要监听播放完成操作,则使用AudioServicesAddSystemSoundCompletion(SystemSoundID inSystemSoundID, CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, void *inClientData)方法注册回调函数。
III:调用AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID)或者AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID) 方法播放音效(后者带有震动效果)。
下面是一个简单的示例程序:
- (void)playSoundEffect:(NSString *)name
{
NSString audioFile = [[NSBundle mainBundle]pathForResource:name ofType:nil];
NSURL fileUrl = [NSURL fileURLWithPath:audioFile];
// 获得系统声音ID
SystemSoundID soundID = 0;
/- inFileUrl:音频文件url
- outSystemSoundID: 声音ID(此函数会将音效文件加入到系统音频服务中并返回一个ID)
*/
AudioServicesCreateSystemSoundID((__bridge CFURLRef)fileUrl, &soundID);
// 如果播放完需要进行某些操作,可以调用如下方法注册一个播放完的回调函数
AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);
// 播放音频
AudioServicesPlaySystemSound(soundID);
//AudioServicesPlayAlertSound(soundID); // 带震动
}
pragma mark 播放完的回调函数 这是一个C函数
void soundCompleteCallback(SystemSoundID soundID, void *clientData) {
NSLog(@"播放完成。。。。");
}
2、音乐
(1)概述
如果播放较大的音频或者要对音频有精确的控制System Sound Service 可能就很难满足实际需求了,通常这种情况会选择使用AVFoundation.framework中的AVAudioPlayer来实现。AVAudioPlayer可以看成一个播放器,它支持多种音频格式,而且能够进行进度、音量、播放速度等控制。首先简单看一下AVAudioPlayer常用的属性和方法:
属性
属性 | 说明 |
---|---|
@property(readonly, getter=isPlaying) BOOL playing | 是否正在播放,只读 |
@property(readonly) NSUInteger numberOfChannels | 音频声道数,只读 |
@property(readonly) NSTimerInterval 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() currentTime | 当前播放时长 |
@property(readonly) NSTimeInterval diviceCurrentTime | 输出设备播放音频的时间,如果播放暂停,此时间也会继续累加 |
@property() NSInteger numberOfLoops | 循环播放次数,如果为0 则不循环,小于0则无限循环,大于0为循环次数 |
@property(readonly) NSDictionary *settings | 音频播放设置信息,只读 |
@property(getter=isMeteringEnabled BOOL meteringEnabled) | 是否启用音频测量,默认为NO,一档医用音频测量可以通过updateMeters方法更新测量值 |
@property() NSArray *channelAssignments | 获得或设置播放声道 |
方法
对象方法 | 说明 |
---|---|
- (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 | 更新音频测量值,注意如果要跟新音频测量值,必须设置meteringEnable 为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 | 音频解码发生错误 |
(2)AVAudioPlayer的使用:
- 初始化AVAudioPlayer对象,此时通常指定本地文件路径。
- 设置播放属性,例如重复此时、音量大小等。
- 调用play方法播放。
下面就是要AVAudioPlayer实现一个简单播放器,在这个播放器中实现了播放、暂停、显示播放进度、调节音频播放状态功能,当然例如调节音量、设置循环模式。甚至是声波图像等功能都可以实现,这里不再一一演示。界面效果如下:
当然由于AVAudioPlayer一次只能播放一个音频文件,所有上一曲、下一曲其实可以通过创建多个播放器对象来完成,这里暂不实现。如果下一曲可以播放的话,通常下一曲功能可以在代理方法中触发。为了不使其他关于铺设界面的代码干扰AVAudioPlayer的理解,下面只有核心代码:
#import "ViewController.h"
#import "PlayToolBar.h"
#import "Masonry.h"
#import "UIImage+Common.h"
#import
@interface ViewController ()
@property (nonatomic, strong) UILabel *songWordLabel;
@property (nonatomic, strong) UISlider *slider;
@property (nonatomic, strong) UIButton *playBtn;
@property (nonatomic, strong) UILabel *currentTimeLabel;
@property (nonatomic, strong) UILabel *maxTimeLabel;
@property (nonatomic, strong) AVAudioPlayer *audioPlayer;
@property (nonatomic, assign) NSTimer *timer;
@property (nonatomic, strong) NSMutableArray *songWordArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self handleData];
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
self.navigationController.navigationBar.shadowImage = [UIImage new];
[self createSubviews];
[self layoutSubviewsByMasonry];
}
#pragma mark - button slider click
- (void)playAction:(UIButton *)sender
{
if (sender.tag == 100) {
[sender setBackgroundImage:[[UIImage imageNamed:@"mymusic_guess_like_pause"] imageWithColor:[UIColor colorWithRed:0.137 green:0.756 blue:0.49 alpha:1]] forState:UIControlStateNormal];
[self play];
sender.tag = 200;
} else {
[sender setBackgroundImage:[[UIImage imageNamed:@"play"] imageWithColor:[UIColor colorWithRed:0.137 green:0.756 blue:0.49 alpha:1]] forState:UIControlStateNormal];
[self pause];
sender.tag = 100;
}
}
/** slider 的点击开始 */
- (void)sliderTouchBegin
{
if ([self.audioPlayer isPlaying]) {
_timer.fireDate = [NSDate distantFuture];
}
}
/** slider 的点击结束 */
- (void)sliderSwipChangeValue
{
if ([self.audioPlayer isPlaying]) {
_audioPlayer.currentTime = _slider.value;
_timer.fireDate = [NSDate distantPast];
} else {
[_slider setValue:0 animated:YES];
}
}
#pragma mark - 定时器(懒加载)
- (NSTimer *)timer
{
if (!_timer) {
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:YES];
}
return _timer;
}
- (void)updateProgress
{
CGFloat progress = _audioPlayer.currentTime / _audioPlayer.duration;
_currentTimeLabel.text = [self timeFormatted:self.audioPlayer.currentTime];
[_slider setValue:progress * _slider.maximumValue animated:YES];
NSInteger index = [_songWordArray indexOfObject:_currentTimeLabel.text];
if (index >= 0 && index < _songWordArray.count) {
_songWordLabel.text = _songWordArray[index + 1];
}
}
#pragma mark - 播放器
/** audio�Player 创建(懒加载) */
- (AVAudioPlayer *)audioPlayer
{
if (!_audioPlayer) {
NSString *path = [[NSBundle mainBundle]pathForResource:@"周杰伦 - 稻香" ofType:@"mp3"];
NSURL *url = [NSURL fileURLWithPath:path];
_audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];
_audioPlayer.delegate = self;
_maxTimeLabel.text = [self timeFormatted:_audioPlayer.duration];
_slider.maximumValue = _audioPlayer.duration;
// 设置播放属性
_audioPlayer.numberOfLoops = 0; // 不循环
[_audioPlayer prepareToPlay]; // 准备播放,加载音频文件到缓存
}
return _audioPlayer;
}
- (NSString *)timeFormatted:(float)timeInterval
{
int time = (int)timeInterval;
int seconds = time % 60;
int minutes = (time / 60) % 60;
int hours = time / 3600;
if (hours == 0) {
return [NSString stringWithFormat:@"%02d:%02d", minutes, seconds];
}
return [NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds];
}
/** 播放 */
- (void)play
{
if (![self.audioPlayer isPlaying]) {
[self.audioPlayer play];
self.timer.fireDate = [NSDate distantPast];//回复定时器
}
}
- (void)pause
{
if ([self.audioPlayer isPlaying]) {
[self.audioPlayer pause];
self.timer.fireDate = [NSDate distantFuture];
}
}
/** audioPlayer delegate */
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
[_playBtn setBackgroundImage:[[UIImage imageNamed:@"play"] imageWithColor:[UIColor colorWithRed:0.137 green:0.756 blue:0.49 alpha:1]] forState:UIControlStateNormal];
[_timer finalize];
}
参照博文链接这里有更加详细的介绍!