Android播放audio音频踩坑实践

原文地址

https://medium.com/uptech-team/audio-not-playing-in-android-cde9a0fdfafd

开篇介绍

Android虽然现在已经是最受欢迎的移动端操作系统,并且有一个庞大的社区,但是有时候依然有那么一些特殊的问题,即使查阅StackOverflow也不能够有效快速的解决。我曾经就遇到了这些问题中的一个,然后我将在这篇文章中分享我关于这个问题一些踩坑的经验给那些需要这些信息的人。
问题大概是这样的:我需要许多的audio音频文件能立刻被播放,并且用户能在需要的时候打开和关闭,而且这些声音能够循环播放。一眼看过去没有什么复杂的地方,当然,如果真的这么简单那么你就不需要阅读这篇文章了:)
我花了相当长的时间来解决这个问题,并且从中收获了很多。在解决这个问题的过程中,我收集了各种网络上的资源以及个人经验,接下来我就会讲解在Android上播放音频文件你可能会遇到的的坑以及解决方案。

不同的方案

首先我们列出Android播放audio文件的一些方案

  1. MediaPlayer 这是最简单的并且用的最多的一个类,这个不仅能播音频还能播放视频。在这里不会讲解视频方面的细节,只会讲解有关音频的部分。
  2. SoundPool 在解决问题过程中发现的一个Android class,可以被用来播放小的音频文件,这个类的功能主要是可以同时控制播放多个小的声音。可以看到,我不断的强调这个“小”,简单的解释一下这个原理,SoundPool 接收一个文件(可能从raw文件夹或者是从本地存储)并解压缩成 PCM,一种数字采样模拟信号。最重要的提示就是,每一个解压缩的文件大小不能超过1Mb,否则他们就不会播放。所以SoundPool一般用来播放比较短的声音,比如游戏音效或者类似的东西。
  3. AudioTrack 也是一个用来播放音频的,不过这个相比于前一个更低级一点,一般只能用于播放解码后的PCM流或者是不需要解码的wav文件。
  4. ExoPlayer 这是google推荐用来替代 MediaPlayer的一个播放器,在开始这篇文章的时候,release的版本是1.5.11,然后Google发布了2.0.0版本,对比前者有了一些改变。

详细介绍

首先从 MediaPlayer 开始,这应该是上述列表中用的最多的一种了,提到这个不得不放出一个图,

MediaPlayer State

这个图展示了一个MediaPlayer的生命周期...这是一个大的状态机,你需要试着去理解整个工作流的运作,状态的切换。Google对于怎么使用MediaPlayer有一个比较好的 引导 ,如果你没有使用过这个类,可以先读一下整个文档,这里我就直接讲解存在的问题了。

  • 多个MediaPlayer的实例在Nexus 5和Nexus 5x上有可能不能同时播放,我已经测试过这些设备,在Nexus 6p这款上面还不能确定,其中的原因还不清楚。
  • 另外一个问题就是MediaPlayer的 isPlaying() 方法在播放audio音频结束后依然有可能返回true,比如下面这段代码,在onCompletion回调中打印出isPlaying的结果
private void init() {  
    MediaPlayer mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    // some init goes here...
    mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                Log.d("MediaPlayer", "onComplete, mediaPlayer.isPlaying() returns " + mediaPlayer.isPlaying());
            }   
        });
}

从下面的图可以看出确实在结束后依然打印出了true。


onCompletion
  • 除此之外,MediaPlayer中的 setVolume() 方法在Jelly Bean API 16可能不会正常的工作,虽然现在这个问题只是出现在LG Optimus这款设备上。为了解决这个问题不得已采用的Android中的AudioManager来通过STREAM_MUSIC设置音量,就像下面这样来减少音量
private void decreaseVolume() {
    AudioManager audioManager = ((AudioManager) getSystemService(Context.AUDIO_SERVICE));
    int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    if (currentVolume > 0) {
        audioManager.setStreamVolume(AudioManager.STREAM_MUSIC,  currentVolume - 1, 0);
    }
}

这种做法其实有很多的缺点,通过这种方式来设置音量会改变所有的音频流,也就是说,所有使用MediaPlayer播放的程序以及其他的app都会一起共用这个音量,这明显不是一个好的方式。

  • MediaPlayer的OnErrorListener 接口回调方法有一个方法 onError(MediaPlayer mp, int what, int extra),虽然官方已经有 文档 提到了一些接收的code码,但是依然有一些超出了文档的范围,比如,你可能得到一个code像这个图一样
error code

(-38,0),这个实际上表现网络存在问题,除此之外,也有一些其他的奇奇怪怪的code,也是文档没有提及的。

SoundPool. 因为这个对于文件大小的限制,我其实一直没有用过它,但是在how-bad-is-android-soundpool-what-alternative-to-use 这个问题上对于这个类的用途有一个比较详细的描述,所以可以直接看这个就好了。当然作为个人建议,如果你的文件超过30秒,那么最好不要选择这个。

**AudioTrack. ** 正如前面所说的那样,这个类有很大的局限性,一般不推荐使用这个,如果一定想要了解这个,可以看一下这篇引导, AudioTrack tutorial

ExoPlayer. 了解如何使用这个可以先check一份官方的资料 the official page ,这个库的可定制性是很强的,几乎可以实现一切你需要的需求。我本来想说一些我用这个时遇到过的一些问题,但是在发布的2.0.0版本中已经全部修复了 : ) ,当然我也会提醒那些还没有迁移到2.0版本的,对比中才能发现进步。在1.5.11版本中循环这个功能还不可靠(可用),你不得以在 onComplete 回调中去手动重启你的播放器,这会导致在下一个播放之前出现一个间隔。但是在2.0+的版本中,已经有了一个LoopingMediaSource ,这个类的效果让你感觉不到一个文件的播放结束或开始,无间隙的回放也已经支持。
在第一个版本中我们在没有其他组件的情况下不能设置音量,不得不采取发送一个message的方式,就像下面这样

private void setVolume(float volume) {
    ExoPlayer player = ExoPlayer.Factory.newInstance(1);
    SampleSource source = new ExtractorSampleSource(Uri.parse("audiourl"),
            new DefaultUriDataSource(this, Util.getUserAgent(this, getString(R.string.app_name))),
            new DefaultAllocator(BUFFER_SEGMENT_SIZE), BUFFER_SEGMENT_SIZE * BUFFER_SEGMENT_COUNT);
    MediaCodecAudioTrackRenderer renderer = new MediaCodecAudioTrackRenderer(source,
            MediaCodecSelector.DEFAULT);
    player.prepare(renderer);
    player.sendMessage(renderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, volume);
}

而从2.0.0版本开始已经在SimpleExoPlayer中已经有了一个 setVolume 方法,这十分的方便,并且修复了ExoPlayer 在API 16 Jelly Bean上部分机型的问题,问题如这个issue所提,this issue

根据这么多的研究以及个人使用经验,我只能提一个建议:使用ExoPlayer,主要基于以下几个原因,社区更新很快,有问题及时反馈和解决,定制能力没有其他的library可以与之相比,而且使用起来十分简单,没有任何难点。

相关资源:
How bad is SoundPool? What alternative to use?
Multiple MediaPlayers do not work on Nexus 5
Unable to play two MediaPlayer at same time in Nexus 5
Choppy Audio with ofxAndroidSoundPlayer (MediaPlayer)

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

推荐阅读更多精彩内容