MediaPlayer过早截断声音

12

我有两个设备可以测试我的游戏:一个美国电信手机(SCH-R880)和一个Kindle Fire,后者比手机性能更强。

我有几个短音效(少于或约1秒)。为了节省内存,我加载、播放和释放其中一些音效。在手机上它们(大多数情况下)正常播放。然而,在Kindle Fire上,它们被切断了。非常短的声音被迅速切断,我什么都听不到。那些已经被加载并保留的声音则正常播放。

有人知道这是怎么回事吗?我是否把我的媒体释放得太快了?以下是其中一个实例。在手机上我听到“Level two!”,但在Kindle上我听到类似“Lev tw”的声音。

mpNum = null;

try
{
    switch (level)
    {
        case 2:
            mpNum = MediaPlayer.create(contxt, R.raw.l2); break;
        case 3:
            mpNum = MediaPlayer.create(contxt, R.raw.l3); break;
        case 4:
            mpNum = MediaPlayer.create(contxt, R.raw.l4); break;
        case 5:
            mpNum = MediaPlayer.create(contxt, R.raw.l5); break;
        case 6:
            mpNum = MediaPlayer.create(contxt, R.raw.l6); break;
        case 7:
            mpNum = MediaPlayer.create(contxt, R.raw.l7); break;
        case 8:
            mpNum = MediaPlayer.create(contxt, R.raw.l8); break;
        default:
            return;
    }

    MediaPlayer vLevel = MediaPlayer.create(contxt, R.raw.level);

    vLevel.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
    {
        public void onPrepared(MediaPlayer mp)
        {
            mp.start();
        }
    });

    vLevel.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
    {
           public void onCompletion(MediaPlayer mpl)
           {
            mpNum.start();
            mpl.release();
           }
       });

        mpNum.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
        {
            public void onCompletion(MediaPlayer mp)
            {
                mp.release();
            }
        });
    }
    catch (Exception e) {}
为了试图解决这个问题,我尝试使用SoundPool,但它没有起作用;我什么也听不到。以下是我尝试使用SoundPool播放音乐的步骤:
SoundPool soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 100);
    soundPool.load(contxt, R.raw.song2, 1);
    AudioManager mgr = (AudioManager)contxt.getSystemService(Context.AUDIO_SERVICE);
    float streamVolumeCurrent = mgr.getStreamVolume(AudioManager.STREAM_MUSIC);
    float streamVolumeMax = mgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    float volume =  streamVolumeMax;  

    soundPool.play(1, volume, volume, 1, 5, 2);

更新

我注意到当应该播放但没有播放的声音时,会出现以下错误:

AudioPolicyManager: stopOutput() oldDevice 2
AudioPolicyManager: [getDeviceForStrategy] strategy : 0,forceUse(0)

我注意到 Nexus 5 设备上出现了这个问题。基本上,我的应用程序会流式传输音频,但 MediaPlayer 提前大约 1-5 秒钟就会中断。还有其他人也注意到这个问题吗? - shoke
我正在开发的应用程序也有这个问题,这让我很疯狂,因为我无法在我手头的手机上重现它,但似乎在Galaxy S4上会出现故障。看起来这个bug是在kitkat中引入的,并且只在非常特殊的情况下才会出现。 - German
5个回答

22

我曾经遇到同样的问题,确实是垃圾回收器的问题!如果你在方法内部将媒体播放器的实例赋值给某个局部变量,离开那个方法后该实例就不存在了,因此GC可以随时随地随机删除它。

为了避免这种情况发生,您需要将至少一个指针指向该实例的其他位置。我创建了一个静态集合来保存所有指针,它看起来像这样:

private static Set<MediaPlayer> activePlayers = new HashSet<MediaPlayer>();

然后创建媒体播放器如下:

MediaPlayer player = MediaPlayer.create(context, context.getResources().getIdentifier(id, "raw", pkg));
activePlayers.add(player);
player.setOnCompletionListener(releaseOnFinishListener);
player.start();

其中releaseOnFinishListener是一个监听器,与您的监听器一样,在播放完成后释放媒体播放器,但还从activePlayers集合中移除指针:

MediaPlayer.OnCompletionListener releaseOnFinishListener = new MediaPlayer.OnCompletionListener() {
    public void onCompletion(MediaPlayer mp) {
        mp.release();
        activePlayers.remove(mp);
    }
};

我希望这可以帮助到你。


工作正常。谢谢你 :) - Vaibhav Jani
1
确实是GC问题!对于我的情况,在全局声明MediaPlayer列表可以解决问题。谢谢~ - Jiyeh
1
在较新的Android Studio版本中,可以使用Profiler来强制运行GC,这使得测试这种情况变得更加容易。 - jayeffkay
您,先生,值得一枚奖章。 - Khaled Osman

3

我在一个项目中遇到了同样的问题,原因是在KindleFire上声音会在结束之前被切断,所以为了解决这个问题(通过发布),我在发布中添加了一些延迟:

    mpNum.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
    {
        public void onCompletion(MediaPlayer mp)
        {
            // Add a handler delay
            new Timer().schedule(new TimerTask() {

                @Override
                public void run() {
                   mp.release();
               }
            }, DELAY_TIME);
        }
    });

这不是最好的解决方案,但我认为可能会对您有所帮助。


我也认为这是问题所在,但我删除了我的release()代码,问题仍然存在。 - rrbrambley

3

我看到SoundPool类更适合处理短音频,与MediaPlayer不同,但这可能不是问题的关键。

你能否也发布一下你点击处理程序的代码?

此外,我建议您使用一个MediaPlayer实例而不是释放和重新获取MediaPlayer实例,并且每当需要重复使用它时,只需遵循典型的reset()、setDataSource()、prepare()和start()序列即可。这比每次构造一个新实例更有效率。例如:

MediaPlayer mp = new MediaPlayer();
AssetFileDescriptor afd = getResources().openRawResourceFd(R.raw.sound1);
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength();
mp.prepare();
mp.start();

//to reuse
mp.reset();
afd = getResources().openRawResourceFd(R.raw.sound2);
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength();
mp.prepare();
mp.start();

是的,你的onClick()方法...至少我假设这是你触发这些声音效果的方式?基本上就是任何你用来触发声音播放的东西。你上面的代码都没有做到这一点。 - Kevin Coppock
实际上不是这样的。这是一个游戏,所以这些声音效果是在特定事件中调用的。我可以向你展示它,但相信我,这并没有什么帮助。仅仅展示方法调用本身并没有任何帮助,为了更好地理解需要展示大量的代码。 - Jesse Jashinsky
更新的帖子。有任何想法是什么错误?似乎无法通过谷歌找到它。 - Jesse Jashinsky
当我调用reset()时,这对我不起作用,会抛出一个IllegalStateException - m0skit0
SoundPool示例的链接无法使用。您能否更新一下?谢谢。 - user2342558

1

我曾遇到过这个问题,通过将我的MediaPlayer对象设为私有,让垃圾回收器不会清除该实例来解决了这个问题。希望能对某些人有所帮助!


我不确定,但似乎声音不再被切断了。 - JerothKP
这不是垃圾收集器的工作方式。可能只是相关性而非因果关系。 - m0skit0

0

这很奇怪,也不是一个好的解决方法,但目前我找到的唯一解决方法。如果我移除 mp.setOnPreparedListenermp.setOnCompletionListener,只留下 mp.start(),它就能正常工作。


这对我也不起作用(在Android 4.1和6.0.1上测试过)。 - m0skit0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接