iOS学习笔记23-音效与音乐

一、音频

在iOS中,音频播放从形式上可以分为音效播放和音乐播放。

  • 音效:
  • 主要指一些短音频的播放,这类音频一般不需要进行进度、循环等控制。
  • 在iOS中,音效我们是使用AudioToolbox.framework框架实现。
  • 音乐:
  • 主要指一些较长的音频,通常需要对播放进行精确控制。
  • 在iOS中,音乐我们是使用AVFoundation.framework框架实现。

二、音效

AudioToolbox.framework框架是一套基于C语言的框架
它的实现原理是将短音频注册到系统声音服务(SystemSoundService)

系统声音服务的一些限制:
  1. 音频播放时间不能超过30s
  1. 数据必须是PCM或者IMA4格式,现在的基本都符合
  2. 音频文件必须是CAF、AIF、WAV的一种,实际上有些MP3格式也可以播放。
使用步骤:
  1. 导入AudioToolbox.framework框架,添加头文件:
#import <AudioToolbox/AudioToolbox.h>
  1. 获取音效文件的路径URL
  2. 添加进系统声音服务,获得系统声音ID,ID是区分不同音效的唯一标示
void AudioServicesCreateSystemSoundID(
          CFURLRef inFileURL,    /* 音效文件URL,需要把NSURL桥接成CGURLRef */
          SystemSoundID *outSystemSoundID /* 返回音效唯一标示ID */
);
  1. 如果需要监听音效播放完成,需要绑定回调函数
void AudioServicesAddSystemSoundCompletion(  
        SystemSoundID inSystemSoundID, /* 音效ID */
        CFRunLoopRef inRunLoop, /* 所在循环,一般为NULL */
        CFStringRef inRunLoopMode,/* 循环模式,一般为NULL */
        void (*)(SystemSoundID,void*) inCompletionRoutine,/* 回调C语言函数指针 */
        void *inClientData /* 回调函数的参数 */
);
  1. 开始播放,有两种播放模式:
/* 开始播放音效 */
void AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID);
/* 开始播放音效并带震动 */
void AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID);
下面是使用实例:
- (void)viewDidLoad{
    [super viewDidLoad];
    [self playSoundEffect:@"bellTone.wav"];
}
#pragma mark -音效
/* 音效播放完成的回调函数,这个是C语言函数,第一个参数是音效ID,第二个是万能参数 */
void soundCompleteCallBack(SystemSoundID soundID, void *clientData)
{
    NSLog(@"播放完成");
}
- (void)playSoundEffect:(NSString *)name {
    //获取音效文件路径
    NSString *filePath = [[NSBundle mainBundle] pathForResource:name ofType:nil];
    //创建音效文件URL
    NSURL *fileUrl = [NSURL URLWithString:filePath];
    //音效声音的唯一标示ID
    SystemSoundID soundID = 0;
    //将音效加入到系统音效服务中,NSURL需要桥接成CFURLRef,会返回一个长整形ID,用来做音效的唯一标示
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
    //设置音效播放完成后的回调C语言函数
    AudioServicesAddSystemSoundCompletion(soundID,NULL,NULL,soundCompleteCallBack,NULL);
    //开始播放音效
    AudioServicesPlaySystemSound(soundID);
}

三、音乐

如果播放较大的音频,需要对其进行精确控制,我们需要使用到另外一个框架,即:
AVFoundation.framework框架,它支持多种音频格式,可以进行精确控制。
音乐播放功能我们使用到的是AVFoundation.frameworkAVAudioPlayer音乐播放器来实现。

下面有一些AVAudioPlayer类的常用属性:
@property (readonly, getter=isPlaying) BOOL playing;//是否正在播放
@property NSTimeInterval currentTime;//当前已经播放的时间
@property (readonly) NSTimeInterval duration;//播放的总时间
@property float volume;//音量大小
@property float rate;//播放速率,默认为1.0
@property (readonly) NSURL *url;//音乐文件的URL
下面是常用的对象方法:
/* 初始化方法 */
- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError;
- (BOOL)prepareToPlay;/* 把音乐文件加载到缓存区 */
- (BOOL)play;/* 开始播放音乐 */
- (BOOL)playAtTime:(NSTimeInterval)time;/* 在指定时间开始播放音乐 */
- (void)pause;/* 中断音乐,可以通过调用play方法继续播放 */
- (void)stop;/* 终止音乐,无法继续播放 */
常用的代理方法:
/* 播放完成时调用 */
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
/* 音频解码发生错误时调用 */
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error;
使用步骤:
  1. 导入AVFoundation.framework框架,添加头文件:
#import <AVFoundation/AVFoundation.h>
  1. 获取音乐文件的本地文件路径URL,不能使用网络URL
  2. 创建音乐播放器对象AVAudioPlayer,并设置属性和代理。
  3. 调用perpareToPlay方法加载音乐文件到缓冲区
  4. 调用play方法播放,调用pause中断播放
  5. 实现AVAudioPlayerDelegate方法,监听播放完成。
我们来做个简易的音乐播放器吧:
  1. 可以循环播放音乐
  1. 可以中断音乐
  2. 可以切换不同的音乐
  3. 可以知道播放进度
下面是具体的简易音乐播放器项目代码:
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController () <AVAudioPlayerDelegate>

@property (nonatomic, strong) NSArray *musicArray;/*< 音乐列表 */
@property (nonatomic, strong) IBOutlet UIProgressView *progress;/*< 进度条 */
@property (nonatomic, strong) AVAudioPlayer *audioPlayer;/*< 音乐播放器对象 */
@property (nonatomic, strong) NSTimer *timer;/* 定时器 */
@property (nonatomic, strong) IBOutlet UILabel *titleLabel;/* 显示标题 */
@property (nonatomic, assign) NSInteger selectMusic;/* 当前播放音乐的索引 */

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //音乐文件列表
    self.musicArray = @[@"大梦想家",@"江南",@"酒干倘卖无",
                        @"泡沫",@"夏洛特烦恼",@"演员"];
    //创建定时器,用于不断更新进度条
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 
                                                  target:self 
                                                selector:@selector(updataProgress) 
                                                userInfo:nil 
                                                 repeats:YES];
    //创建播放器
    [self initAudioPlayerWithNumber:0];
    //播放器开始播放音乐
    [self.audioPlayer play];
}
/* 初始化播放器 */
- (void)initAudioPlayerWithNumber:(NSInteger)num{
    if (num >= self.musicArray.count) {
        return;
    }
    //获取音乐文件路径
    NSString *name = self.musicArray[num];
    NSString *filePath = [[NSBundle mainBundle] pathForResource:name 
                                                         ofType:@"mp3"];
    //创建音乐文件URL,必须是fileURLWithPath,AVAudioPlayer不支持HTTP的URL
    NSURL *fileUrl = [NSURL fileURLWithPath:filePath];
    //创建播放器
    NSError *error = nil;
    AVAudioPlayer *audioPlayer =
            [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:&error];
    if (error) {
        NSLog(@"初始化播放器错误,错误:%@",error.localizedDescription);
        return;
    }
    audioPlayer.numberOfLoops = 0;//设置循环次数,0表示不循环
    audioPlayer.delegate = self;//设置代理
    [audioPlayer prepareToPlay];//加载音乐文件到缓存,还不会播放
    
    self.selectMusic = num;//保存当前的音乐索引
    self.audioPlayer = audioPlayer;//保存当前播放器
    self.titleLabel.text = name;//设置标题
}
/* 进度条更新方法,每0.5秒更新一次 */
- (void)updataProgress{
    //currentTime是当前播放时间,duration是播放总时长
    CGFloat progress = self.audioPlayer.currentTime / self.audioPlayer.duration;
    [self.progress setProgress:progress animated:YES];
}
/* 点击播放按钮 */
- (IBAction)musicPlay:(id)sender {
    //如果播放器没有在播放,才需要调用播放方法
    if (![self.audioPlayer isPlaying]) {
        [self.audioPlayer play];
        //恢复定时器
        self.timer.fireDate = [NSDate distantPast];
    }
}
/* 点击暂停按钮 */
- (IBAction)musicPause:(id)sender {
    //如果播放器在播放,才需要调用暂停方法
    if ([self.audioPlayer isPlaying]) {
        [self.audioPlayer pause];
        //停止定时器
        self.timer.fireDate = [NSDate distantFuture];
    }
}
/* 点击下一首歌按钮 */
- (IBAction)next:(id)sender {
    //音乐索引循环递增
    self.selectMusic = (self.selectMusic + 1)%self.musicArray.count;
    //重新设置播放器
    [self initAudioPlayerWithNumber:self.selectMusic];
    //播放器播放
    [self.audioPlayer play];
}
#pragma mark - AVAudioPlayerDelegate代理方法
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
    //自动跳转播放下一首歌
    [self next:nil];
}
效果图

(⊙﹏⊙)b,也是够简陋的,你可以做精美一点,比如如果每首歌对应一个图片,到时候切歌的时候,图片显示在背景,一定赏心悦目多了,O(∩_∩)O~,我没有找到好的图片,就这样吧。

四、后台播放音乐

事实上上面的播放器有一点问题,那就是一旦进入后台,我们的音乐器就自动暂停了,不可以/(ㄒoㄒ)/~~!那该怎么办呢?

1. 设置后台运行模式

设置info.plist文件,添加字段Required background modes
并设置App plays audio or streams audio/video using AirPlay

2. 使用音频会话,设置后台运行类型
//获取音频会话单例对象
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
//设置会话为后台播放
[audioSession setCategory:AVAudioSessionCategoryPlayback error:NULL];
//激活修改,启动会话
[audioSession setActive:YES error:NULL];

音频会话除了后台播放外,还可以有以下的播放模式:


音频会话播放类型列表
上面的代码只需修改初始化播放器方法即可实现后台播放:
/* 初始化播放器 */
- (void)initAudioPlayerWithNumber:(NSInteger)num{
    if (num >= self.musicArray.count) {
        return;
    }
    //获取音乐文件路径
    NSString *name = self.musicArray[num];
    NSString *filePath = [[NSBundle mainBundle] pathForResource:name 
                                                         ofType:@"mp3"];
    //创建音乐文件URL,必须是fileURLWithPath,AVAudioPlayer不支持HTTP的URL
    NSURL *fileUrl = [NSURL fileURLWithPath:filePath];
    //创建播放器
    NSError *error = nil;
    AVAudioPlayer *audioPlayer =
            [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:&error];
    if (error) {
        NSLog(@"初始化播放器错误,错误:%@",error.localizedDescription);
        return;
    }
    audioPlayer.numberOfLoops = 0;//设置循环次数,0表示不循环
    audioPlayer.delegate = self;//设置代理
    [audioPlayer prepareToPlay];//加载音乐文件到缓存,还不会播放
    
    //获取音频会话单例对象
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    //设置会话为后台播放
    [audioSession setCategory:AVAudioSessionCategoryPlayback error:NULL];
    //激活修改,启动会话
    [audioSession setActive:YES error:NULL];

    self.selectMusic = num;//保存当前的音乐索引
    self.audioPlayer = audioPlayer;//保存当前播放器
    self.titleLabel.text = name;//设置标题
}

这样,我们就可以后台播放了,(o)/~。

有什么问题可以在下方评论区出去,好意见我会采纳的,打造一个大家都满意的学习笔记,O(∩_∩)O哈!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容