标签:
在iOS中音频播放从形式上可以分为音效播放和音乐播放。前者主要指的是一些短音频播放,通常作为点缀音频,对于这类音频不需要进行进度、循环等控制。后者指的是一些较长的音频,通常是主音频,对于这些音频的播放通常需要进行精确的控制。在iOS中播放两类音频分别使用AudioToolbox.framework和AVFoundation.framework来完成音效和音乐播放。
AudioToolbox.framework是一套基于C语言的框架,使用它来播放音效其本质是将短音频注册到系统声音服务(System Sound Service)。System Sound Service是一种简单、底层的声音播放服务,但是它本身也存在着一些限制:
使用System Sound Service 播放音效的步骤如下:
如果播放较大的音频或者要对音频有精确的控制则System Sound Service可能就很难满足实际需求了,通常这种情况会选择使用AVFoundation.framework中的AVAudioPlayer来实现。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不支持加载网络媒体流,只能播放本地文件 |
- (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 | 音频解码发生错误 |
事实上上面的播放器还存在一些问题,例如通常我们看到的播放器即使退出到后台也是可以播放的,而这个播放器如果退出到后台它会自动暂停。如果要支持后台播放需要做下面几件事情:
事实上上面的播放器还存在一些问题,例如通常我们看到的播放器即使退出到后台也是可以播放的,而这个播放器如果退出到后台它会自动暂停。如果要支持后台播放需要做下面几件事情:
1.设置后台运行模式:在plist文件中添加Required background modes,并且设置item 0=App plays audio or streams audio/video using AirPlay(其实可以直接通过Xcode在Project Targets-Capabilities-Background Modes中设置)
2.设置AVAudioSession的类型为AVAudioSessionCategoryPlayback并且调用setActive::方法启动会话。
3.为了能够让应用退到后台之后支持耳机控制,建议添加远程控制事件(这一步不是后台播放必须的)
前两步是后台播放所必须设置的,第三步主要用于接收远程事件,这部分内容之前的文章中有详细介绍,如果这一步不设置虽让也能够在后台播放,但是无法获得音频控制权(如果在使用当前应用之前使用其他播放器播放音乐的话,此时如果按耳机播放键或者控制中心的播放按钮则会播放前一个应用的音频),并且不能使用耳机进行音频控制。第一步操作相信大家都很容易理解,如果应用程序要允许运行到后台必须设置,正常情况下应用如果进入后台会被挂起,通过该设置可以上应用程序继续在后台运行。但是第二步使用的AVAudioSession有必要进行一下详细的说明。
在iOS中每个应用都有一个音频会话,这个会话就通过AVAudioSession来表示。AVAudioSession同样存在于AVFoundation框架中,它是单例模式设计,通过sharedInstance进行访问。在使用Apple设备时大家会发现有些应用只要打开其他音频播放就会终止,而有些应用却可以和其他应用同时播放,在多种音频环境中如何去控制播放的方式就是通过音频会话来完成的。下面是音频会话的几种会话模式:
会话类型 | 说明 | 是否要求输入 | 是否要求输出 | 是否遵从静音键 |
AVAudioSessionCategoryAmbient | 混音播放,可以与其他音频应用同时播放 | 否 | 是 | 是 |
AVAudioSessionCategorySoloAmbient | 独占播放 | 否 | 是 | 是 |
AVAudioSessionCategoryPlayback | 后台播放,也是独占的 | 否 | 是 | 否 |
AVAudioSessionCategoryRecord | 录音模式,用于录音时使用 | 是 | 否 | 否 |
AVAudioSessionCategoryPlayAndRecord | 播放和录音,此时可以录音也可以播放 | 是 | 是 | 否 |
AVAudioSessionCategoryAudioProcessing | 硬件解码音频,此时不能播放和录制 | 否 | 否 | 否 |
AVAudioSessionCategoryMultiRoute | 多种输入输出,例如可以耳机、USB设备同时播放 | 是 | 是 | 否 |
注意:是否遵循静音键表示在播放过程中如果用户通过硬件设置为静音是否能关闭声音。
好了,基本的特点和属性已经介绍的差不多了,为此,我封装了一个工具类,专门用来播放音频文件,代码如下:
//
// AudioTool.h
// 01-音效
//
// Created by ma c on 15/12/26.
// Copyright (c) 2015年 XYQ. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@interface AudioTool : NSObject
//播放音效
/**
@param fileName : 音效文件名
*/
+(void)playSound:(NSString *)fileName;
//销毁音效
/**
@param fileName : 音效文件名
*/
+(void)disposeSound:(NSString *)fileName;
//播放音乐
/**
@param fileName : 音乐文件名
*/
+(AVAudioPlayer*)playMusic:(NSString *)fileName;
//暂停播放音乐
/**
@param fileName : 音乐文件名
*/
+(void)pauseMusic:(NSString *)fileName;
//停止播放音乐
/**
@param fileName : 音乐文件名
*/
+(void)stopMusic:(NSString *)fileName;
//返回当前进度下的播放器
+(AVAudioPlayer *)currentPlayingAudioPlayer;
@end
//
// AudioTool.m
// 01-音效
//
// Created by ma c on 15/12/26.
// Copyright (c) 2015年 XYQ. All rights reserved.
//
/**
音频格式转换可以使用终端的命令行进行转换,基本使用:afconvert -f [格式] -d [fileName]
具体使用可以使用help查看:afconvert -help
afconvert: audio format convert 音频格式转换
*/
#import "AudioTool.h"
#import <AVFoundation/AVFoundation.h>
static NSMutableDictionary *_soundDict;
static NSMutableDictionary *_musicDict;
@implementation AudioTool
#pragma mark 初始化字典
+(void)initialize
{
//存放所有的音频ID,fileName作为key,SoundID作为value
_soundDict = [NSMutableDictionary dictionary];
//存放所有的音乐播放器,fileName作为key,audioPlayer作为value
_musicDict = [NSMutableDictionary dictionary];
//设置音频会话类型
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategorySoloAmbient error:nil];
[session setActive:YES error:nil];
}
#pragma mark 播放音效
+(void)playSound:(NSString *)fileName
{
//判断文件名是否为空
if (!fileName) return;
//加载音效文件(短音频) 记住:每一个音效对应一个ID
SystemSoundID soundID = [_soundDict[fileName] unsignedIntValue];
if (!soundID) {
NSURL *url = [[NSBundle mainBundle]URLForResource:fileName withExtension:nil];
if (!url) return;
//创建音效sound ID
AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &soundID);
//存入字典
[_soundDict setObject:[NSNumber numberWithUnsignedInt:soundID] forKey:fileName];
//使用sound ID播放
AudioServicesPlaySystemSound(soundID);
//AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID) //播放时手机会震动
}
}
#pragma mark 销毁音效
+(void)disposeSound:(NSString *)fileName
{
//判断文件名是否为空
if (!fileName) return;
//从字典中取出ID
SystemSoundID soundID = (SystemSoundID)[[_soundDict objectForKey:fileName] unsignedIntValue];
//释放音效资源
if (soundID) {
AudioServicesDisposeSystemSoundID(soundID);
[_soundDict removeObjectForKey:fileName];
}
}
#pragma mark 播放音乐
+(AVAudioPlayer *)playMusic:(NSString *)fileName
{
//判断文件名是否为空
if (!fileName) return nil;
//从字典中取出播放器
AVAudioPlayer *audioPlayer = _musicDict[fileName];
if (!audioPlayer){
//加载音乐文件
NSError *error = nil;
NSURL *url = [[NSBundle mainBundle]URLForResource:fileName withExtension:nil];
if (!url) return nil;
//创建播放器
audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];
//播放速率、音量、当前时间等
//audioPlayer.enableRate = YES;
//audioPlayer.rate = 5.0f;
//audioPlayer.volume = 20.0f;
//audioPlayer.currentTime = 100.0f;
if (error) return nil;
//将播放器存入字典中
[_musicDict setObject:audioPlayer forKey:fileName];
//创建缓冲(以便后面的播放流畅)
[audioPlayer prepareToPlay];
//开始播放
[audioPlayer play];
}
if (!audioPlayer.isPlaying)
{
//开始播放
[audioPlayer play];
}
return audioPlayer;
}
#pragma mark 暂停音乐
+(void)pauseMusic:(NSString *)fileName
{
//判断文件名是否为空
if (!fileName) return;
//从字典中取出播放器
AVAudioPlayer *audioPlayer = _musicDict[fileName];
//暂停
if (audioPlayer && audioPlayer.isPlaying) {
[audioPlayer pause];
}
}
#pragma mark 停止音乐
+(void)stopMusic:(NSString *)fileName
{
//判断文件名是否为空
if (!fileName) return;
//从字典中取出播放器
AVAudioPlayer *audioPlayer = _musicDict[fileName];
//停止并移除播放器
if (audioPlayer && audioPlayer.isPlaying)
{
[audioPlayer stop];
[_musicDict removeObjectForKey:fileName];
}
}
#pragma mark 返回当前进度下的播放器
+(AVAudioPlayer *)currentPlayingAudioPlayer
{
for(NSString *fileName in _musicDict) {
AVAudioPlayer *audioplayer = _musicDict[fileName];
if (audioplayer.isPlaying) {
return audioplayer;
}
}
return nil;
}
@end
标签:
原文地址:http://www.cnblogs.com/XYQ-208910/p/5662655.html