Android媒体播放的基础知识

作者: lanceJin | 来源:发表于2017-05-18 08:47 被阅读0次

    1.前言


    媒体播放是Android开发中常见的功能,就拿视频来说,通常都是基于封装好的框架做二次开发,主要任务是界面的展示和用户交互,无需考虑视频是否支持,资源如何读取,保持屏幕常亮和防止设备休眠等。这样适合快速开发,但那些不以视频为主要业务的怎么办?要知道成熟的视频框架体积都不小,杀鸡焉用宰牛刀。
      其实Android系统已经支持大部分的媒体资源,对于播放要求不高的,可以直接使用自带的API开发,还避免设备不兼容问题。

    2.媒体概念


    媒体是指传播信息的媒介,在Android设备上包括图片、音频和视频。拆文解字一下,有三个关键点:信息、媒介和传播,对设备而言就是内容编解码、容器格式和网络协议。为什么要说这些,因为平时大家所接触到的资源文件就是为了能广泛地被使用而基于这三点设计出来的,是能否在设备上播放和如何播放的决定因素,像PNG、MP3、MP4都是文件类型,可以认为是方便大家去称呼的。
      为了容易理解,举个例子吧。南方潮湿、北方干燥,想把空气中的水汽由南运到北,并沿途分发水资源,怎么办:

    办法 水汽 媒体
    减少体积,方便运输 对水汽压缩形成水 使用算法将信息编码成更少的字节
    使用容器装载 不同材质、不同形状的杯子 不同文件类型所包含的不同后缀名的文件
    动态调整使用的资源 根据当地缺水情况分发 根据网络情况切换码率和缓冲大小

    知道了媒体的组成,也就能理解官网指南中给出的支持媒体格式表中数据的含义了。大家可以看看,知道什么情况下不用引入框架。

    3.API及权限


    在Android系统中播放音频和视频主要是以下两个类:

    • MediaPlayer,播放音频和视频的逻辑由它实现。
    • AudioManager,管理音频资源和在设备上的输出。

    当播放的资源来自网络中的数据流时,添加网络访问权限:

    <uses-permission android:name="android.permission.INTERNET" />
    

    当需要播放时屏幕常亮或阻止设备休眠时,添加唤醒锁权限:

    <uses-permission android:name="android.permission.WAKE_LOCK" />
    
    MediaPlayer.setScreenOnWhilePlaying(); // 若是视频,保持屏幕常量
    

    4.播放流程


    4.1.播放不同的来源

    使用MediaPlayer可以简单完成获取、解码和播放操作。不过根据资源来源不同,写法略有不同。

    • 本地资源(Raw资源)
    MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
    mediaPlayer.start(); // no need to call prepare(); create() does that for you
    
    • 内部URIs(设备文件系统,可通过Content Resolver获得)
    Uri myUri = ....; // initialize Uri here
    MediaPlayer mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setDataSource(getApplicationContext(), myUri);
    mediaPlayer.prepare();
    mediaPlayer.start();
    
    • 网络URLs(数据流)
    // 由于缓存需要,数据源必须支持边下边播
    String url = "http://........"; // your URL here
    MediaPlayer mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setDataSource(url);
    mediaPlayer.prepare(); // might take long! (for buffering, etc)
    mediaPlayer.start();
    
    4.2.异步准备资源

    资源的解码是比较耗时的,尤其是来自网络的视频,缓冲不稳定且解码量大。不建议将 prepare() 方法放在UI线程中处理,因为阻塞超过十分之一秒就会引起用户反感。系统提供另一个方法 prepareAsync() 来替代它, 先通过 setOnPreparedListener() 方法给MediaPlayer设置监听,
    当资源准备完成后将回调 MediaPlayer.OnPreparedListener 接口的 onPrepared() 方法,执行你写的播放逻辑。

    4.3.管理播放状态

    MediaPlayer的播放流程严格按照当前的状态一步一步执行,只要不对就会报错,请记住下面的图。


    State.png
    4.4.释放资源

    媒体播放是很耗资源的,当不用时,记得通过以下代码释放它:

    mediaPlayer.release();
    mediaPlayer = null;
    

    根据情况在 onStart()、onResume()、onRestart() 方法中创建,在 onStop()、onDestory() 方法中释放。有种情况得注意,当改变手机方向或者配置时会重启界面,若不及时释放,几次就会占满资源,详见 Handling Runtime Changes

    5.后台播放


    关于这块其实逻辑比较固定,系统提供了MediaXXXCompat系列API帮助快速实现,详见huyongl1989的文章。那自己如何实现呢?

    5.1.消息监听

    直接将代码移到Service中,不过需要注意的是Service默认和Activity一样运行在主线程,对于耗时操作也一样得异步处理。

    public class MyService extends Service implements MediaPlayer.OnPreparedListener {
        private static final String ACTION_PLAY = "com.example.action.PLAY";
        MediaPlayer mMediaPlayer = null;
    
        public int onStartCommand(Intent intent, int flags, int startId) {
            ...
            if (intent.getAction().equals(ACTION_PLAY)) {
                mMediaPlayer = ... // initialize it here
                mMediaPlayer.setOnPreparedListener(this);
                mMediaPlayer.prepareAsync(); // prepare async to not block main thread
            }
        }
    
        /** Called when MediaPlayer is ready */
        public void onPrepared(MediaPlayer player) {
            player.start();
        }
    }
    

    和异步准备资源一样,对于错误也要异步通知,不过监听器改为 MediaPlayer.OnErrorListener。

    public class MyService extends Service implements MediaPlayer.OnErrorListener {
        MediaPlayer mMediaPlayer;
    
        public void initMediaPlayer() {
            // ...initialize the MediaPlayer here...
    
            mMediaPlayer.setOnErrorListener(this);
        }
    
        @Override
        public boolean onError(MediaPlayer mp, int what, int extra) {
            // ... react appropriately ...
            // The MediaPlayer has moved to the Error state, must be reset!
        }
    }
    

    由于播放控制涉及Activity和Service的交互,不熟的看看圣骑士Wind的博客

    5.2.唤醒锁

    当应用程序在后台播放时,设备仍可能进入休眠,而系统此状态下为了省电,尝试关闭任何非必需功能,包括CPU和WiFi。所以得通过唤醒锁告诉系统别休眠,但是会明显减少电池使用时间,建议只在必需情况下使用。相关代码如下:

    mMediaPlayer = new MediaPlayer();
    // ... other initialization here ...
    mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); // CPU不休眠
    
    WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
        .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
    
    wifiLock.acquire(); // WiFi不休眠
    

    在MediaPlayer暂停或停止播放时,会自动释放CPU锁,不再需要WiFi功能时,得主动释放锁,wifiLock.release()。

    5.3.执行清理

    垃圾回收机制只对内存敏感,可能会花不短时间才能清理MediaPlayer,所以 onDestory() 方法中记得释放。

    public class MyService extends Service {
       MediaPlayer mMediaPlayer;
       // ...
    
       @Override
       public void onDestroy() {
           if (mMediaPlayer != null) mMediaPlayer.release();
       }
    }
    

    对于短时间的停止播放就别清理了,重建也是挺耗资源的。

    6.通过ContentResolver检索资源


    设备的媒体库中有许多资源,可以通过以下代码检索:

    ContentResolver contentResolver = getContentResolver();
    Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    Cursor cursor = contentResolver.query(uri, null, null, null, null);
    if (cursor == null) {
        // query failed, handle error.
    } else if (!cursor.moveToFirst()) {
        // no media on the device
    } else {
        int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
        int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
        do {
           long thisId = cursor.getLong(idColumn);
           String thisTitle = cursor.getString(titleColumn);
           // ...process entry...
        } while (cursor.moveToNext());
    }
    

    检索到后,拿ID拼接URI,进行播放:

    long id = /* retrieve it from somewhere */;
    Uri contentUri = ContentUris.withAppendedId(
            android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
    
    mMediaPlayer = new MediaPlayer();
    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mMediaPlayer.setDataSource(getApplicationContext(), contentUri);
    
    // ...prepare and start...
    

    7.总结


    这里仅仅讲了媒体的播放,那如何展示呢?系统提供了VideoView来播放视频,它继承SurfaceView来保证高效地渲染影片,内部封装了MediaPlayer来获取资源并监听控制。大家有兴趣可以看看,便于理解视频播放。

    相关文章

      网友评论

        本文标题:Android媒体播放的基础知识

        本文链接:https://www.haomeiwen.com/subject/tljdxxtx.html