音频流的最佳实践

dar*_*rja 8 streaming android android-mediaplayer

我正在编写一个从远程服务器播放音频的应用程序.我尝试了几种方法来实现流式音频,但它们对我来说都不够好.这就是我尝试过的:

天真地使用MediaPlayer

就像是:

MediaPlayer player = new MediaPlayer(); 
player.setDataSource(context, Uri.parse("http://whatever.com/track.mp3"));
player.prepare();
player.start();
Run Code Online (Sandbox Code Playgroud)

(或者prepareAsync,不管)

但是MediaPlayer播放远程内容时标准非常不稳定.它经常跌落或停止播放,我无法处理.另一方面,我想实现媒体缓存.但是我没有找到任何方法从MediaPlayer获取缓冲内容以将其保存在设备上的某个位置.

实现自定义缓冲

然后有一个想法是按块下载媒体文件,将它们组合成一个本地文件并播放该文件.由于连接不良,下载整个文件可能会很慢,因此可以下载足够的初始文件,然后开始播放并继续下载和附加本地文件.此外,我们还获得了缓存功能.

听起来像一个计划,但它并不总是有效.它完美地适用于HTC Sensation XE,但在完成这个初始作品后没有停止4.1平板电脑播放.不知道,为什么会这样.我问过这个问题,但没有得到答案.

使用两个MediaPlayers

我创建了两个MediaPlayer实例并试图让它们互相改变.逻辑如下:

  • 开始下载初始媒体
  • 下载后,通过currentMediaPlayer开始播放.其余媒体继续下载
  • 当几乎播放下载的片段(完成前1秒)时,准备具有相同源文件的secondaryMediaPlayer(在播放期间附加)
  • 在currentMediaPlayer结束前261毫秒 - 暂停它,开始辅助,将辅助设置为当前,安排准备下一个辅助播放器.

来源:

private static final String FILE_NAME="local.mp3";
private static final String URL = ...;
private static final long FILE_SIZE = 7084032;

private static final long PREPARE_NEXT_PLAYER_OFFSET = 1000;
private static final int START_NEXT_OFFSET = 261;

private static final int INIT_PERCENTAGE = 3;

private MediaPlayer mPlayer;
private MediaPlayer mSecondaryPlayer;

private Handler mHandler = new Handler();

public void startDownload() {
    mDownloader = new Mp3Downloader(FILE_NAME, URL, getExternalCacheDir());
    mDownloader.setDownloadListener(mInitDownloadListener);
    mDownloader.startDownload();
}


private Mp3Downloader.DownloadListener mInitDownloadListener = new Mp3Downloader.DownloadListener() {
    public void onDownloaded(long bytes) {
        int percentage = Math.round(bytes * 100f / FILE_SIZE);

        // Start playback when appropriate piece of media downloaded
        if (percentage >= INIT_PERCENTAGE) {
            mPlayer = new MediaPlayer();
            try {
                mPlayer.setDataSource(mDownloader.getDownloadingFile().getAbsolutePath());
                mPlayer.prepare();
                mPlayer.start();

                mHandler.postDelayed(prepareSecondaryPlayerRunnable, mPlayer.getDuration() - PREPARE_NEXT_PLAYER_OFFSET);
                mHandler.postDelayed(startNextPlayerRunnable, mPlayer.getDuration() - START_NEXT_OFFSET);

            } catch (IOException e) {
                Log.e(e);
            }

            mDownloader.setDownloadListener(null);
        }
    }
};

// Starting to prepare secondary MediaPlayer
private Runnable prepareSecondaryPlayerRunnable = new Runnable() {
    public void run() {
        mSecondaryPlayer = new MediaPlayer();
        try {
            mSecondaryPlayer.setDataSource(mDownloader.getDownloadingFile().getAbsolutePath());
            mSecondaryPlayer.prepare();
            mSecondaryPlayer.seekTo(mPlayer.getDuration() - START_NEXT_OFFSET);

        } catch (IOException e) {
            Log.e(e);
        }
    }
};

// Starting secondary MediaPlayer playback, scheduling creating next MediaPlayer
private Runnable startNextPlayerRunnable = new Runnable() {
    public void run() {
        mSecondaryPlayer.start();

        mHandler.postDelayed(prepareSecondaryPlayerRunnable, mSecondaryPlayer.getDuration() - mPlayer.getCurrentPosition() - PREPARE_NEXT_PLAYER_OFFSET);
        mHandler.postDelayed(startNextPlayerRunnable, mSecondaryPlayer.getDuration() - mPlayer.getCurrentPosition() - START_NEXT_OFFSET);

        mPlayer.pause();
        mPlayer.release();

        mPlayer = mSecondaryPlayer;

    }
};
Run Code Online (Sandbox Code Playgroud)

再次 - 声音,就像一个计划,但工作不完美.切换MediaPlayers的时刻非常可以接受.在这里我有相反的情况:在4.1平板电脑上没关系,但在HTC Sensation上有明显的滞后.

我还试图实现不同的下载技术.我已经通过10Kb块和MP3帧实现了下载.我不确切地知道,但似乎在MP3帧搜索并开始工作更好.但这只是一种感觉,我不知道解释.

StreamingMediaPlayer

谷歌搜索时我多次看到这个词,并发现了这个实现:https://code.google.com/p/mynpr/source/browse/trunk/mynpr/src/com/webeclubbin/mynpr/StreamingMediaPlayer.java?r = 18

这是每个人都使用的解决方案吗?

如果是的话,那很难过,因为它对我来说也不好用.我在实施中没有看到任何新鲜的想法.

所以,问题

你们如何在你的应用程序中实现音频流?我不相信我是唯一一个遇到这样问题的人.应该有一些好的做法.

小智 1

就我而言,我将 FFMPEG 与 OpenSL ES 结合使用。缺点是复杂。很多东西你一定很熟悉:JNI、OpenSL、FFMPEG。它也很难调试(与纯java android应用程序相比)。对于您的情况,我建议您尝试低级别Media API。唯一的问题是缺乏例子。但是有一个单元测试显示了如何处理音频(您需要更改InputStream参考 - 第 82 行)。