iOS开发 - 多媒体

时间:2022-05-02 13:38:34
                                    音频播放

在iOS中音频播放从形式上可以分为音效播放和音乐播放。前者主要指的是一些短音频播放,通常作为点缀音频,对于这类音频不需要进行进度、循环等控制。后者指的是一些较长的音频,通常是主音频,对于这些音频的播放通常需要进行精确的控制。在iOS中播放两类音频分别使用AudioToolbox.framework和AVFoundation.framework两个框架来完成音效和音乐播放。

一、音效

AudioToolbox.framework是一套基于C语言的框架,使用它来播放音效其本质是将短音频注册到系统声音服务(System Sound Service)。System Sound Service是一种简单、底层的声音播放服务,使用此方法只适合播放一些很小的提示或者警告音,还可以调用系统的震动功能,但是它本身也存在着一些限制:
1.音频播放时间不能超过30s
2.数据必须是PCM或者IMA4格式
3.音频文件必须打包成.caf、.aif、wav中的一种(不过需要注意的是注意这是官方文档的说法,实际测试发现部分.mp3也可以播放)
4.不能控制播放的进度
5.调用方法后立即播放声音
6.没有循环播放和立体声控制

二、音乐
如果播放较大的音频或者要对音频有精确的控制则System Sound Service可能就很难满足实际需求了,通常这种情况会选择使用AVFoundation.framework中的AVAudioPlayer来实现,我们可以把 AVAudioPlayer 看作是一个高级的播放器,它支持广泛的音频格式。

AVAudioPlayer 可以播放任意长度的音频文件、支持循环播放、可以同步播放多个音频文件、控制播放进度以及从音频文件的任意一点开始播放等,更高级的功能可以参考 AVAudioPlayer 的文档 。要使用 AVAudioPlayer 的对象播放文件,你只需为其指定一个音频文件并设定一个实现了 AVAudioPlayerDelegate 协议的 delegate 对象。

只要将 AVAudioPlayer 的 numberOfLoops 属性设为负数,音频文件就会一直循环播放直到调用 stop 方法。

AVAudioPlayer类封装了播放单个声音的能力。播放器可以用NSURL或者NSData来初始化,要注意的是NSURL不可以是网络url而必须是本地文件url,因为AVAudioPlayer不具备播放网络音频的能力。因为AVAudioPlayer只能播放一个完整的文件,并不支持流式播放,所以必须是缓冲完才能播放。一个AVAudioPlayer只能播放一个音频,如果你想混音你可以创建多个AVAudioPlayer实例,每个相当于混音板上的一个轨道,

额外进阶
1.设置后台播放模式(两个步骤缺一不可):
第一步:Required background modes
App plays audio or streams audio/video using AirPlay

第二步:设置AVAudioSession的类型为AVAudioSessionCategoryPlayback并且调用setActive::方法启动会话
AVAudioSession *audioSession=[AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
[audioSession setActive:YES error:nil];

2.处理拔出耳机后暂停功能
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil];
NSDictionary *dic=notification.userInfo;
int changeReason= [dic[AVAudioSessionRouteChangeReasonKey] intValue];
if (changeReason==AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
AVAudioSessionRouteDescription *routeDescription=dic[AVAudioSessionRouteChangePreviousRouteKey];
AVAudioSessionPortDescription *portDescription= [routeDescription.outputs firstObject];
if ([portDescription.portType isEqualToString:@”Headphones”]) {
if ([self.audioPlayer isPlaying])
{
[self.audioPlayer pause];
self.timer.fireDate=[NSDate distantFuture];
}
}

配置iOS9 HTTPS&HTTP
NSAppTransportSecurity

NSAllowsArbitraryLoads

摇一摇demo

#import "ViewController.h"
//短效音频的框架
#import <AudioToolbox/AudioToolbox.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}

-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
NSLog(@"摇动开始");
//短效音频播放
//创建系统声音ID
SystemSoundID soundID;
//创建短效音频 播放器 并且设置播放资源
NSURL * url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"shake" ofType:@"wav"]];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)(url), &soundID);


//播放短效音频的同时加入震动效果

//kSystemSoundID_Vibrate 指的就是震动效果
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);

//播放声音
AudioServicesPlaySystemSound(soundID);
}

-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
NSLog(@"摇动结束");
//一般写页面跳转
}

-(void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
NSLog(@"摇动取消");
}

音乐播放方法

#pragma mark - 设置音乐播放器
-(void)createAudioPlayer{
//初始化播放器
//第一种(本地) 用NSURL初始化 这里的URL指的不是网络url 而是本地url
NSURL * url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:self.localMusicArray[_currentIndex] ofType:@"mp3"]];
NSLog(@"%@",self.localMusicArray[_currentIndex]);
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
//
// //预播放
// [_audioPlayer prepareToPlay];
// //播放
// [_audioPlayer play];

//第二种(网络) 用NSData初始化 先缓冲到本地 然后播放本地文件

// _audioPlayer = [[AVAudioPlayer alloc ]initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.urlArray[_currentIndex]]] error:nil];

/**************设 置****************/
//设置代理
_audioPlayer.delegate = self;
//设置音量
_audioPlayer.volume = 0.5; //0.1-1.0之间
//设置播放循环次数,负数表示无限循环,0表示播放一次,正数是几就播放几次,默认播放一次
_audioPlayer.numberOfLoops = 0;

//设置开始播放的位置 单位/s
_audioPlayer.currentTime = 0; //可以指定从任意位置播放

//声道数 只读属性
/*
int channals = _audioPlayer.numberOfChannels;

//设置计数 开启音频计数
_audioPlayer.meteringEnabled = YES;
[_audioPlayer updateMeters];//更新音频读数

//获取平均电平和峰值电平
for (int i = 0; i < channals; i++) {
//平均电平
float average = [_audioPlayer averagePowerForChannel:i];
//峰值电平
float peak = [_audioPlayer peakPowerForChannel:i];
}
//获取播放持续时间
NSTimeInterval time = _audioPlayer.duration;
*/


//分配播放资源,并将其添加到内部实行队列中
[_audioPlayer prepareToPlay];
//[_audioPlayer play];
}

#pragma mark - 代理方法,处理播放过程中的突发状况

-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
NSLog(@"播放完成");
//循环播放,单曲循环...
if (flag) {
//音频文件在正常情况下播放完成
}else{
//说明音频虽然播放结束了 但是数据解码错误
}
}

-(void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error{
//数据解码错误
}

//处理音频播放过程中被中断的情况,ios8之前必须要实现这两个代理方法 ios8之后系统自动处理
//播放被中断 比如突然来电或者用户home键返回
-(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player{
//如果当前正在播放则暂停
if (_audioPlayer.isPlaying) {
[_audioPlayer pause];
}
}
//结束中断 返回程序
-(void)audioPlayerEndInterruption:(AVAudioPlayer *)player{

[_audioPlayer play];
}

后台播放 开启线控
#pragma mark - 细节处理 后台播放 和开启线控

-(void)dealWithDetail{

//设置后台播放,结合修改 plist
//初始化音频会话 多个app的音频同时播放管理
AVAudioSession * session = [[AVAudioSession alloc] init];
//设置类型为后台播放类型
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
//开启后台播放
[session setActive:YES error:nil];

//监听输出设备的状态
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil];

}

-(void)routeChange:(NSNotification *)noti{

//获取监听内容
NSDictionary * dic = noti.userInfo;
//获取状态改变的原因
int changeReason = [dic[AVAudioSessionRouteChangeReasonKey] intValue];
//当状态改变原因为检测不到输出设备
if (changeReason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
//获取状态描述
AVAudioSessionRouteDescription * description = dic[AVAudioSessionRouteChangePreviousRouteKey];
//获取端口描述
AVAudioSessionPortDescription * portDescription = [description.outputs firstObject];
//如果端口是耳机 并且音频正在播放的话 就暂停播放
if([portDescription.portType isEqualToString:@"HeadPhones"]){
if (_audioPlayer.isPlaying) {
//暂停播放器
[_audioPlayer pause];
//暂停定时器
[timer setFireDate:[NSDate distantFuture]];
}
}
}
}


录音demo

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height

#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
@interface ViewController ()<AVAudioRecorderDelegate>
{
//记录录音时间
UILabel * _timeLabel ;
AVAudioRecorder * _audioRecoder;
AVPlayer * _player;
}

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

[self createUI];

[self createAudioRecoder];

//定时器检测录音的时长
[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(culTime) userInfo:nil repeats:YES];
}
#pragma mark -定时器函数

-(void)culTime{
_timeLabel.text = [NSString stringWithFormat:@"%.2f s",_audioRecoder.currentTime];
}

#pragma mark - 创建音频录制
-(void)createAudioRecoder{

//设置存储录音文件的路径
NSString * path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/recoder.aac"];
NSLog(@"%@",path);

//设置录制音频的属性
NSDictionary * dic = @{AVFormatIDKey:@(kAudioFormatMPEG4AAC), //音频格式
AVSampleRateKey:@(4000.0), //比特率
AVNumberOfChannelsKey:@(2) //声道
};
//初始化音频录制
_audioRecoder = [[AVAudioRecorder alloc] initWithURL:[NSURL fileURLWithPath:path] settings:dic error:nil];

//设置代理 代理的作用是检测录音文件什么时候出错
_audioRecoder.delegate = self;
//预录制
[_audioRecoder prepareToRecord];
}

#pragma mark - 创建UI
- (void)createUI{
_timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(150, 100, 100, 40)];
_timeLabel.backgroundColor = [UIColor redColor];
[self.view addSubview: _timeLabel];

//控制按钮
NSArray * arr = @[@"录音",@"暂停",@"停止",@"播放"];

for (int i = 0; i< arr.count; i++) {

UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame= CGRectMake(SCREEN_WIDTH/4*i, 200, SCREEN_WIDTH/4-20, 40);
[btn setTitle:arr[i] forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
btn.tag = 100+i;
[btn addTarget:self action:@selector(onClick:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}


}
#pragma mark - 按钮响应方法
-(void)onClick:(UIButton *)btn{

switch (btn.tag) {
case 100:
{
[_audioRecoder record];
}
break;
case 101:
{
[_audioRecoder pause];
}
break;
case 102:
{
[_audioRecoder stop];
}
break;
case 103:
{
[self startPlay];
}
break;
default:
break;
}
}

#pragma mark - 播放录音
-(void)startPlay{
//初始化音频播放器
_player = [[AVPlayer alloc] init];
//取得播放路径
AVPlayerItem * item = [AVPlayerItem playerItemWithURL:_audioRecoder.url];
NSLog(@"%@",_audioRecoder.url);

//切换播放源
[_player replaceCurrentItemWithPlayerItem:item];
//播放
[_player play];
}

@end

视频播放

一、MPMoviePlayerController

MPMoviePlayerController支持本地视频和网络视频播放。这个类实现了MPMediaPlayback协议,因此具备一般的播放器控制功能,例如播放、暂停、停止等。但是MPMediaPlayerController自身并不是一个完整的视图控制器,如果要在UI中展示视频需要将view属性添加到界面中

二、MPMoviePlayerViewController

    其实MPMoviePlayerController如果不作为嵌入视频来播放(例如在新闻中嵌入一个视频),通常在播放时都是占满一个屏幕的,特别是在iPhone、iTouch上。因此从iOS3.2以后苹果也在思考既然MPMoviePlayerController在使用时通常都是将其视图view添加到另外一个视图控制器中作为子视图,那么何不直接创建一个控制器视图内部创建一个MPMoviePlayerController属性并且默认全屏播放,开发者在开发的时候直接使用这个视图控制器。这个内部有一个MPMoviePlayerController的视图控制器就是MPMoviePlayerViewController,它继承于UIViewController。MPMoviePlayerViewController内部多了一个moviePlayer属性和一个带有url的初始化方法,同时它内部实现了一些作为模态视图展示所特有的功能,例如默认是全屏模式展示、弹出后自动播放、作为模态窗口展示时如果点击“Done”按钮会自动退出模态窗口

iOS9之前的实现:
//初始化播放器并且设置播放资源
//网络视频播放
MPMoviePlayerViewController * player = [[MPMoviePlayerViewController alloc]initWithContentURL:[NSURL URLWithString:@"http://video.szzhangchu.com/1442391674615_6895658983.mp4"]];
//本地视频播放
MPMoviePlayerViewController * player = [[MPMoviePlayerViewController alloc]initWithContentURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"MovieTest" ofType:@"mp4"]]];
//设置播放来源
player.moviePlayer.movieSourceType = MPMovieSourceTypeFile;
//设置全屏播放
player.moviePlayer.controlStyle = MPMovieControlStyleFullscreen;
//播放前视频预处理
[player.moviePlayer prepareToPlay];
//开始播放
[player.moviePlayer play];
//推出视频播放的控制器
[self presentViewController:player animated:YES completion:nil];

iOS9之后的实现:
//初始化播放器
AVPlayerViewController * playerVC = [[AVPlayerViewController alloc]init];
//设置播放资源
AVPlayer * player = [AVPlayer playerWithURL:url];
playerVC.player = player;
[self presentViewController:playerVC animated:YES completion:nil];

三、AVPlayer

MPMoviePlayerController足够强大,几乎不用写几行代码就能完成一个播放器,但是正是由于它的高度封装使得要自定义这个播放器变得很复杂,甚至是不可能完成。例如有些时候需要自定义播放器的样式,那么如果要使用MPMoviePlayerController就不合适了,如果要对视频有*的控制则可以使用AVPlayer。AVPlayer存在于AVFoundation中,它更加接近于底层,所以灵活性也更强

AVPlayer本身并不能显示视频,而且它也不像MPMoviePlayerController有一个view属性。如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可。要使用AVPlayer首先了解一下几个常用的类:

AVAsset:主要用于获取多媒体信息,是一个抽象类,不能直接使用。
AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。
AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源

功能简单介绍:
视频的播放、暂停功能,这也是最基本的功能,AVPlayer对应着两个方法play、pause来实现。但是关键问题是如何判断当前视频是否在播放,在前面的内容中无论是音频播放器还是视频播放器都有对应的状态来判断,但是AVPlayer却没有这样的状态属性,通常情况下可以通过判断播放器的播放速度来获得播放状态。如果rate为0说明是停止状态,1是则是正常播放状态。

到目前为止无论是MPMoviePlayerController还是AVPlayer来播放视频都相当强大,但是它也存在着一些不可回避的问题,那就是支持的视频编码格式很有限:H.264、MPEG-4,扩展名(压缩格式):.mp4、.mov、.m4v、.m2v、.3gp、.3g2等。但是无论是MPMoviePlayerController还是AVPlayer它们都支持绝大多数音频编码

四、VLC视频播放(第三方视频集成播放)

集成步骤

1>加入libMobileVLCKit

2>加库:ibstdc++ libiconv libbz2 Security.framework QuartzCore.framework CoreText.framework CFnetWork.framework OpenGLES.framework AudioToolbox.framework

3>修改C++编译器为stdC++:
在[Build Setting]输入[c++][Apple LLVM 5.1 - Languate - C++]
[C++ Standard..]选第一个[libstdc++(GNU C++ standard library)]

4>在[Build Setting][search][Search Paths][Header..]加一个路径
打开[libMobileVLCKit][include][MobileVLCKit]显示简介复制地址
把地址的前半部(包括文件夹名称)分改为$(SRCROOT)

5>添加头文件//#import “VLCMediaPlayer.h”

6>使用的文件改为 .mm支持C++编译

举例视频链接

http://video.szzhangchu.com/1442391674615_6895658983.mp4

配置iOS9
NSAppTransportSecurity

NSAllowsArbitraryLoads