Android音视频基础part2——Android中自带Media Api

一.音视频播放工具

1.1 MediaPlayer

我们平时最常使用MediaPlayer类来进行音视频的播放,其实它完成的是一个解码并播放的过程,并且是一个高度封装的工具类。使用它播放视频的时候我们还要配合SurfaceView或者TextureView来进行,后面的两个UI控件完成画面的展示。后来在前面的基础之上,系统提供了一个封装好的VideoView,为我们带来更简洁的调用。
任何事情都是有利弊两端的,简便使用的同时带来了可扩展性的问题。MediaPlayer的调用位于java层,内部的很多具体实现虽然也位于java层但是并不对应用开发者开放。那么就会带来一些问题,例如:视频容器文件格式的支持问题;缓冲区大小的定制;下载进度等。为了解决上面的问题,Media Codec API组应运而生。

1.2Media Codec API组

MediaPlayer把Extractor,和Codec API全部封锁在了Framework层,应用层完全接触不到。在新的API设计里面,这些api都对开发者开放了,我们可以定制自己的播放器了。
在这里面,最重要的就是MediaExtractor和MediaCodec这两个类,第一个功能是对容器文件进行读取控制,第二个是对数据进行编解码。

1.3Android目前支持的视频,音频,图片格式和通信协议

Android目前支持的视频,音频,图片格式和通信协议;这是官方的说明

二.MediaPlayer相关

2.1MediaPlayer状态切换

MediaPlayer的生命周期状态切换十分重要,对于正确使用具有指导意义

。下面是状态切换图:MediaPlayer状态切换图

主要的状态
  • Idle 状态
  • End 状态
  • Error 状态
  • Initialized状态
  • Prepared状态
  • Preparing状态
  • Started状态
  • Paused状态
  • Stopped状态
  • PlaybackCompleted状态
状态切换,切换流程
  • 椭圆代表MediaPlayer驻留的状态
  • 带箭头直线代表播放控制且驱动MediaPlayer状态进行切换过渡。 有两种类型的带箭头直线,单箭头表示的是同步调用,双箭头代表异步调用
Idle 状态

Idle:清闲,备用状态。

进入Idle状态可以通过两种途径,一种是直接new MediaPlayer()创建一个实例,便进入Idle状态;另一种在使用完之后调用reset()方法,进入Idle状态;

进入Idle状态不同途径的不同

  • new MediaPlayer()的方式:Idle状态下,调用如下stop(), seekTo(),getCurrentPosition(), getDuration(), getVideoHeight(), getVideoWidth(), setVolume(float, float), pause(), start(), prepare(), prepareAsync()等方法(此处不完全列出),即使事先注册了setOnErrorListener,也不会受到onError的回调,同时MediaPlayer实例的状态也不会发生变化。
  • reSet()方式:Idle状态下,调用如下stop(), seekTo(),getCurrentPosition(), getDuration(), getVideoHeight(), getVideoWidth(), setVolume(float, float), pause(), start(), prepare(), prepareAsync()等方法(此处不完全列出),如果事先注册了setOnErrorListener,那么就会回调onError方法。
    推荐:Idle状态之后不要做例如上面的错误操作,如果是reSet之后到达Idle状态,最好随手调用release方法,释放掉资源使其到达End状态。

注意 new MediaPlayer()和MediaPlayer.create()构造实例的不同
使用new MediaPlayer()会进入Idle状态,而MediaPlayer.create()由源码可知调用prepare()进入了Prepared状态。

End状态

当在Idle状态中此时调用release方法,此时进入End状态;release方法作用是将MediaPlayer的实例的所有占用的资源完全释放,End状态下MediaPlayer的资源完全释放,处于完全结束状态。及时释放所占用资源很重要,建议在不在使用的时候及时释放。

Error状态

造成Error状态的情况比较多,在实际开发中如果不熟悉MediaPlayer的状态流程,使用的场景又较复杂,在开发中很可能出现,例如不支持的文件格式,视频的分辨率问题等等。所以事先setOnErrorListener注册错误监听器很有必要,可以帮助我们找到错误原因。
如果想逃出Error状态, 可以使用 reset() 方法进入 Idle 状态;

Initalized状态

在 Idle 状态调用 setDataSource() 方法, MediaPlayer 到 Initialized 状态;注意只能在Idle状态下调用setDataSource() 进入,如果其他状态下调用则会抛出异常。

Prepared状态和Preparing状态

Initialized 状态时候调用 prepareAsync() 方法(异步方式),或者prepare()方法(同步方式)会进入Prepared状态。
注意:1.只有Initialized 状态时候通过上面两种方式才能进入Prepared状态,其他状态下想通过上面两个方法中一种进入Prepared状态都会报异常。2.使用prepareAsync() 方法(异步方式)的时候会进入一个短暂的Preparing状态,事先注册好setOnCompletionListener,进入Prepared状态的时候会有回调。3.Prepared状态下可以设置音视频的属性(音量setVolume等),播放循环模式(setLooping()),常亮setScreenOnWhilePlaying等。

Started (开始) 状态

在 Prepared 状态调用 start() 方法, MediaPlayer 即到了 Started 状态;
判断 MediaPlayer 是否在 Started 状态 : 在任何状态下调用 isPlaying() 方法, 可以判断 MediaPlayer 是否在 Started 状态;Started状态下,可以在事先注册好的setOnBufferingUpdateListener中监听onBufferingUpdate得到相应的缓存进度。Started状态下,再调用start方法不会起总用。

Paused状态

Started 状态调用 pause() 方法, MediaPlayer 会进入 Paused 状态;Paused 状态调用 start() 方法, 恢复 Started 状态。
Started 状态转换为 Paused状态是瞬间的; 在 Paused 状态调用 start() 方法, 会进入 Started 状态,恢复之前的播放进度,这个过程是异步的,需要一段时间;当处于Paused状态的MediaPlayer重复调用pause方法是无效的。

Stoped状态

在 Prepared, Started, Paused, PlaybackCompleted 状态下 调用 stop() 方法, MediaPlayer 都会变为Stopped 状态;此状态下不同于Paused状态可以通过start方法恢复为Started状态,只能通过prepare方法或者prepareAsync方法回到Prepared状态,再通过start方法回到Started状态。处于Stoped状态时候重复调用stop方法无效。

PlaybackCompleted状态

如果播放时候开启了重复播放模式setLooping(true),Started状态在音频或者视频播放完成之后将重新进行播放。
如果没有 开启重复播放模式(默认),Started状态在播放完成之后会变为PlaybackCompleted模式。我们可以在事先注册的setOnCompletionListener()中的onCompletion中收到回调,我们也可以下这里直接调用start方法来恢复到Started状态。

播放进度调整seekTo()

该方法可以在 Prepared, Started,Paused, PlaybackCompleted 状态进行调用;
我们可以事先注册监听器setOnSeekCompleteListener,在onSeekComplete中获取回调;可以通过getCurrentPosition()获取播放进度。

2.2MediaPlayer的使用

/**
 * Created by gxo on 2019/2/25.
 */
public class RadioPlayer implements MediaPlayer.OnPreparedListener
        ,MediaPlayer.OnCompletionListener,MediaPlayer.OnErrorListener{
    private MediaPlayer mediaPlayer;
    private String playUrl;

    //暂停
    public void pause(){
        mediaPlayer.pause();
    }

    //判断是否在播放
    public boolean isPlaying(){
        return mediaPlayer.isPlaying();
    }

    //停止
    public void stop(){
        mediaPlayer.stop();
    }

    //播放一个网络音频
    public void play(String url){
        playUrl = url;
        initMediaplayer();
    }

    private void initMediaplayer() {
        mediaPlayer = new MediaPlayer();

        //准备完成监听
        mediaPlayer.setOnPreparedListener(this);
        //播放完成监听
        mediaPlayer.setOnCompletionListener(this);
        //播放错误监听
        mediaPlayer.setOnErrorListener(this);
        
        try {
            mediaPlayer.setDataSource(playUrl);
            mediaPlayer.prepareAsync();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        Mlog.e("onPrepared");
        mediaPlayer.start();
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        Mlog.e("onCompletion");
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        Mlog.e("onError");
        return false;
    }
}

上面是一个为了展示api的demo,很简单就能实现音频的播放,视频的播放当然要增加展示View如SurfaceView或者TextureView,这里不做展示,百度一下很多。
同时在新的api中可以看出,自带的MediaPlayer添加了字幕和保护版权的加密处理

三.Media Codec API组

实现的功能

其实主要是MediaExtractor和MediaCodec两个工具类,前者的主要功能是加载控制,后者是编解码功能。使用这个api组合就是要自己完成数据的加载和解码成可以在Android系统中展示的视频和播放的音频。

实际开发中遇到的问题

投入实际生产中,个人建议还是使用FFmpeg实现会好很多。在开发过程中会遇到各种问题,例如视频和音频同步问题,尤其是在播放网络流的时候,FFmpeg相对来说各个方面的资料较多,可以帮助我们完成开发。
这里不贴出具体的使用代码,百度一下写的不错的也很多,例如https://juejin.im/post/5aa6527e6fb9a028c06a79e3和https://www.jianshu.com/p/ec5fd369c518。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值