Android音频和视频开发

前言

在如今快节奏,网络发达的社会。人们对于静态的图片和文字往往很难有精力去安心完整的阅读完网上内容。为了满足人们可以更生动的阅读网上的内容,也为了使网络交互更加生动。我们便需要应用到音频和视频来增加人们在网络上动作交互的丰富性。

Android开发支持的媒体格式

在使用媒体播放器时,我们可以使用移动设备上所有可用的媒体播放器,但最佳开发方法是我们应该使用与设备自带无关的媒体播放器为最佳。

  • 音频支持

    包括了支持的音频解码方式和音频播放文件格式
    音频支持

  • 视频支持
    包括了支持的视频解码方式和视频播放文件格式
    视频支持
    具体参考Android开发文档的媒体格式

媒体应用开发架构

  • 播放器和页面

    这是最基本的一种架构,通过Android提供的MediaPlayer类来对本地的音频/视频文件进行最基本的播放功能。

    -播放器:用于吸收数字媒体并将其呈现为视频和/或音频

    -UI:带有用于运行播放器并显示播放器状态(可选)的传输控件

    界面与播放器构架图

  • 在页面和播放器中添加了媒体控制器和媒体会话结构

    由于播发器和页面的架构只能通过自身设备的播放器控件来进行播放,且对于许多我们需要的播放控制行为设备自身播发器并不具备,我们更多需要在其基础上的扩充,于是Android 框架定义了两个类(媒体会话和媒体控制器),它们为构建媒体播放器应用提供了一个完善的结构。

    媒体会话和媒体控制器通过以下方式相互通信:使用与标准播放器操作(播放、暂停、停止等)相对应的预定义回调,以及用于定义应用独有的特殊行为的可扩展自定义调用。

媒体控制器与媒体会话构架图

-媒体控制器:媒体控制器会隔离您的界面。您的界面代码只与媒体控制器(而非播放器本身)通信。媒体控制器会将传输控制操作转换为对媒体会话的回调。每当会话状态发生变化时,它也会接收来自媒体会话的回调。这提供了一种自动更新关联界面的机制。媒体控制器一次只能连接到一个媒体会话。

当您使用媒体控制器和媒体会话时,您可以在运行时部署不同的接口和/或播放器。您可以根据运行应用的设备的功能单独更改该应用的外观和/或性能。

-媒体会话:负责与播放器的所有通信。它会对应用的其他部分隐藏播放器的 API。系统只能从控制播放器的媒体会话中调用播放器。

会话会维护播放器状态(播放/暂停)的表示形式以及播放内容的相关信息。会话可以接收来自一个或多个媒体控制器的回调。这样,应用的界面以及运行 Wear OS 和 Android Auto 的配套设备便可以控制您的播放器。响应回调的逻辑必须保持一致。无论哪个客户端应用发起了回调,对 MediaSession 回调的响应都应该相同。

  • 视频开发和音频开发的差异性

    视频开发是我们需要一个完整页面来观看的,故我们应该用一个activity来展示视频内容,但是音频则不同,需要的仅仅是聆听,但同时也可以使用其他应用。每种使用情形都有不同的设计。

    • 视频应用

      视频应用图

      视频应用需要一个窗口来查看内容。出于这个原因,视频应用通常作为单个 Android Activity 来实现。呈现视频的屏幕是 Activity 的一部分。

    • 音频应用

      音频播放器并不总是需要显示其界面。一旦开始播放音频,播放器就可以作为后台任务运行。用户可以切换到其他应用,同时继续聆听。

      要在 Android 中实现此设计,您可以使用两个组件构建音频应用:对界面使用 Activity,对播放器使用服务。如果用户切换到其他应用,该服务可以在后台运行。通过将音频应用的两个部分分解为单独的组件,每个组件都可以更高效地独立运行。与播放器(可能会在没有界面的情况下运行很长时间)不同,界面通常是短时的。

      后台音频使用关系图

      支持库提供了两个类来实现此客户端/服务器方法:MediaBrowserService 和 MediaBrowser。服务组件作为包含媒体会话及其播放器的 MediaBrowserService 子类来实现。包含界面和媒体控制器的 Activity 应该包括一个MediaBrowser,后者与 MediaBrowserService进行通信。

  • 自身开发的音频App与设备中其他App音频之间关系

    设计良好的媒体应用应该能够与其他播放音频的应用“和谐共存”。它应准备好与使用音频的其他应用共用手机并相互配合。它还应该对设备上的硬件控制做出响应。

    自身APP与第三方APP直接音频关系

MediaPlayer类概括

MediaPlayer类可以播放存储在应用资源(原始资源)内的媒体文件、文件系统中的独立文件或者通过网络连接获得的数据流中的音频或视频。

基本知识

  • MediaPlayer:用于播放声音和视频的主要 API。

  • MediaPlayer 状态管理:

    MediaPlayer 对象播放控制生命周期状态图如下所示

    MediaPleayer状态图

    这张状态转换图清晰的描述了MediaPlayer的各个状态,也列举了主要的方法的调用时序,每种方法只能在一些特定的状态下使用,如果使用时MediaPlayer的状态不正确则会引发IllegalStateException异常。

    Idle 状态:当使用**new()方法创建一个MediaPlayer对象或者调用了其reset()**方法时,该MediaPlayer对象处于idle状态。这两种方法的一个重要差别就是:如果在这个状态下调用了getDuration()等方法(相当于调用时机不正确),通过reset()方法进入idle状态的话会触发OnErrorListener.onError(),并且MediaPlayer会进入Error状态;如果是新创建的MediaPlayer对象,则并不会触发onError(),也不会进入Error状态。

    End 状态:通过release()方法可以进入End状态,只要MediaPlayer对象不再被使用,就应当尽快将其通过release()方法释放掉,以释放相关的软硬件组件资源,这其中有些资源是只有一份的(相当于临界资源)。如果MediaPlayer对象进入了End状态,则不会在进入任何其他状态了。

    Initialized 状态:这个状态比较简单,MediaPlayer调用setDataSource()方法就进入Initialized状态,表示此时要播放的文件已经设置好了。

    Prepared 状态:初始化完成之后还需要通过调用prepare()或prepareAsync()方法,这两个方法一个是同步的一个是异步的,只有进入Prepared状态,才表明MediaPlayer到目前为止都没有错误,可以进行文件播放。

    Preparing 状态:这个状态比较好理解,主要是和prepareAsync()配合,如果异步准备完成,会触发OnPreparedListener.onPrepared(),进而进入Prepared状态。

    Started 状态:显然,MediaPlayer一旦准备好,就可以调用start()方法,这样MediaPlayer就处于Started状态,这表明MediaPlayer正在播放文件过程中。可以使用isPlaying()测试MediaPlayer是否处于了Started状态。如果播放完毕,而又设置了循环播放,则MediaPlayer仍然会处于Started状态,类似的,如果在该状态下MediaPlayer调用了seekTo()或者start()方法均可以让MediaPlayer停留在Started状态。

    Paused 状态:Started状态下MediaPlayer调用pause()方法可以暂停MediaPlayer,从而进入Paused状态,MediaPlayer暂停后再次调用start()则可以继续MediaPlayer的播放,转到Started状态,暂停状态时可以调用seekTo()方法,这是不会改变状态的。

    Stop 状态:Started或者Paused状态下均可调用stop()停止MediaPlayer,而处于Stop状态的MediaPlayer要想重新播放,需要通过prepareAsync()和prepare()回到先前的Prepared状态重新开始才可以。

    PlaybackCompleted状态:文件正常播放完毕,而又没有设置循环播放的话就进入该状态,并会触发OnCompletionListener的onCompletion()方法。此时可以调用start()方法重新从头播放文件,也可以stop()停止MediaPlayer,或者也可以seekTo()来重新定位播放位置。

    Error状态:如果由于某种原因MediaPlayer出现了错误,会触发OnErrorListener.onError()事件,此时MediaPlayer即进入Error状态,及时捕捉并妥善处理这些错误是很重要的,可以帮助我们及时释放相关的软硬件资源,也可以改善用户体验。通过setOnErrorListener(android.media.MediaPlayer.OnErrorListener)可以设置该监听器。如果MediaPlayer进入了Error状态,可以通过调用reset()来恢复,使得MediaPlayer重新返回到Idle状态。

配置声明

在开始使用 MediaPlayer 开发应用之前,请确保配置环境文件的声明。

  • 互联网权限 - 如果使用 MediaPlayer 流式播放基于网络的内容,则应用必须申请网络访问权限。

    <uses-permission android:name="android.permission.INTERNET" />
    
  • 唤醒锁定权限 - 如果播放器应用需要防止屏幕变暗或处理器进入休眠状态,或者要使用 MediaPlayer.setScreenOnWhilePlaying() 或 MediaPlayer.setWakeMode() 方法,则必须申请此权限。

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

开始使用MediaPlayer

对本地媒体文件进行最基本的执行,不尝试以任何特定方式解析文件代码:

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you

但是如果不用特定的解析方式去解析文件,可能会造成使用过多无用内存,造成资源浪费,于是Android提供了解析各类格式的文件解码格式:

Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start(); 

通过 HTTP 流式传输并播放远程网址上的内容如下所示:

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();

释放 MediaPlayer 资源

MediaPlayer 会占用宝贵的系统资源。因此,您应该始终采取额外的预防措施,确保 MediaPlayer 实例保留的时间不会过长。完成该操作后,您应始终调用 release() 以确保分配给它的所有系统资源均已正确释放。例如,如果您使用 MediaPlayer,并且您的 Activity 接收到对 onStop() 的调用,则您必须释放该 MediaPlayer,因为当 Activity 未与用户互动时,保留该 MediaPlayer 并没有什么意义(除非您在后台播放媒体内容,这将在下一部分中介绍)。当然,当 Activity 恢复或重启时,您需要先创建一个新的 MediaPlayer 并再次完成准备工作,然后才能恢复播放。

mediaPlayer.release();
mediaPlayer = null;

在 Service 中使用 MediaPlayer

异步准备

原则上,使用 MediaPlayer 会非常简单。不过,请务必注意,要正确地将其与典型的 Android 应用集成,还需执行一些额外操作。例如,对 prepare() 的调用可能需要很长时间来执行,因为它可能涉及获取和解码媒体数据。因此,与任何可能需要很长时间来执行的方法一样,切勿从应用的界面线程中调用它。这样做会导致界面挂起,直到系统返回该方法,这是一种非常糟糕的用户体验,并且可能会导致 ANR(应用无响应)错误。即使您预计资源会快速加载,但也请记得,界面中任何响应时间超过十分之一秒的操作都会导致明显的暂停,并让用户觉得您的应用运行缓慢。

为避免界面线程挂起,请生成其他线程来准备 MediaPlayer,并在准备工作完成后通知主线程。不过,尽管您可以自行编写线程逻辑,但此模式在使用 MediaPlayer 时非常普遍,因此框架通过 prepareAsync() 方法提供了一种完成此任务的便捷方式。此方法会在后台开始准备媒体,并立即返回。当媒体准备就绪后,系统会调用通过 setOnPreparedListener() 配置MediaPlayer.OnPreparedListener的 onPrepared() 方法。

异步运行

从主线程中使用 MediaPlayer 时,应该调用 prepareAsync() 而非 prepare(),并实现 MediaPlayer.OnPreparedListener,以便在准备工作完成后获得通知并开始播放。

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
            if (intent.getAction().equals(ACTION_PLAY)) {
                mediaPlayer = ... // initialize it here
                    mediaPlayer.setOnPreparedListener(this);
                mediaPlayer.prepareAsync(); // prepare async to not block main thread
            }
    }

    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}

处理异步错误

在同步操作中,系统通常会通过异常或错误代码来指示错误,但当使用异步资源时,应确保以适当的方式向应用发出错误通知。对于 MediaPlayer,可以实现 MediaPlayer.OnErrorListener 并在 MediaPlayer 实例中对其进行设置来发出错误通知:

public class MyService extends Service implements MediaPlayer.OnErrorListener {
    MediaPlayer mediaPlayer;

    public void initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer.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!
    }
}

使用唤醒锁定

当设计在后台播放媒体内容的应用时,设备可能会在 Service 运行时进入休眠状态。由于 Android 系统尝试在设备处于休眠状态时节省电量,因此系统会尝试关闭手机上任何不必要的功能,包括 CPU 和 WLAN 硬件。不过,如果 Service 正在播放或流式传输音乐,则需要防止系统干扰播放。

为了确保 Service 在这些情况下能继续运行,必须使用“唤醒锁定”。唤醒锁定可以告诉系统:应用正在使用一些即使在手机处于闲置状态时也应该可用的功能。

  • 为确保 CPU 在 MediaPlayer 播放时继续运行,请在初始化 时调用 setWakeMode() 方法。完成该操作后,MediaPlayer 会在播放时保持指定的锁定状态,并在暂停或停止播放时释放锁定:

    mediaPlayer = new MediaPlayer();
    // ... other initialization here ...
    mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
    
  • 不过,此示例中获取的唤醒锁定只能保证 CPU 保持唤醒状态。如果使用 WLAN 并通过网络流式传输媒体内容,则可能也希望保持 WifiLock,该锁定必须手动获取和释放。因此,当开始使用远程网址准备 MediaPlayer 时,应创建并获取 WLAN 锁定:

    WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
        .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
    
    wifiLock.acquire();
    
  • 当暂停或停止媒体内容,或者当不再需要网络时,应释放该锁定:

    wifiLock.release();
    

进行清理

如前文所述,MediaPlayer 对象会消耗大量的系统资源,因此应仅使其保留必要的时长,并在操作完成后调用 release()。请务必明确调用此清理方法,而非依赖于系统垃圾回收,因为垃圾回收器要经过一段时间才会回收 MediaPlayer,原因在于它仅对内存需求敏感,而对缺少其他媒体相关资源并不敏感。因此,当使用 Service 时,应始终替换 onDestroy() 方法以确保释放 MediaPlayer:

public class MyService extends Service {
    MediaPlayer mediaPlayer;
    // ...

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null) mediaPlayer.release();
    }
}

除了在关闭时释放 MediaPlayer 之外,还应始终寻找其他释放机会。例如,如果预计在很长一段时间都无法播放媒体内容(例如,在失去音频焦点后),则应果断释放现有的 MediaPlayer 并在之后重新创建。另一方面,如果预计只是短时间停止播放,则可能应该保留 MediaPlayer,以避免再次创建和准备它所产生的开销。

更多MediaPlayer操作方法

  • void statr():开始或恢复播放。
  • void stop():停止播放。
  • void pause():暂停播放。
  • int getDuration():获取流媒体的总播放时长,单位是毫秒。
  • int getCurrentPosition():获取当前流媒体的播放的位置,单位是毫秒。
  • void seekTo(int msec):设置当前MediaPlayer的播放位置,单位是毫秒。
  • void setLooping(boolean looping):设置是否循环播放。
  • boolean isLooping():判断是否循环播放。
  • boolean isPlaying():判断是否正在播放。
  • void prepare():同步的方式装载流媒体文件。
  • void prepareAsync():异步的方式装载流媒体文件。
  • void release ():回收流媒体资源。
  • void setAudioStreamType(int streamtype):设置播放流媒体类型。
  • void setWakeMode(Context context, int mode):设置CPU唤醒的状态。
  • setNextMediaPlayer(MediaPlayer next):设置当前流媒体播放完毕,下一个播放的MediaPlayer。

大部分方法的看方法名就可以理解,但是有几个方法需要单独说明一下。

在使用MediaPlayer播放一段流媒体的时候,需要使用prepare()或prepareAsync()方法把流媒体装载进MediaPlayer,才可以调用start()方法播放流媒体。

setAudioStreamType()方法用于指定播放流媒体的类型,它传递的是一个int类型的数据,均以常量定义在AudioManager类中, 一般我们播放音频文件,设置为AudioManager.STREAM_MUSIC即可。

除了上面介绍的一些方法外,MediaPlayer还提供了一些事件的回调函数,这里介绍几个常用的:

  • setOnCompletionListener(MediaPlayer.OnCompletionListener listener):当流媒体播放完毕的时候回调。
  • setOnErrorListener(MediaPlayer.OnErrorListener listener):当播放中发生错误的时候回调。
  • setOnPreparedListener(MediaPlayer.OnPreparedListener listener):当装载流媒体完毕的时候回调。
  • setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener):当使用seekTo()设置播放位置的时候回调。
  • setOnBufferingUpdateListener:网络上的媒体资源缓存进度更新的时候会触发。
  • setOnVideoSizeChangedListener:视频宽高发生改变的时候会触发。当所设置的媒体资源没有视频图像、MediaPlayer没有设置展示的holder或者视频大小还没有被测量出来时,获取宽高得到的都是0.

以上内容参考自Android开发音频与视频和网上他人的MediaPlayer使用见解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值