jjzjj

android - 音频流的最佳实践

coder 2023-11-24 原文

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

MediaPlayer 的简单使用

类似于:

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

(或prepareAsync,无所谓)

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

实现自定义缓冲

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

听起来像是一个计划,但并不总是奏效。它在 HTC Sensation XE 上完美运行,但在 4.1 平板电脑上播放完这个初始 fragment 后没有停止播放。不知道,为什么会这样。我问过question关于这个,但没有收到任何答复。

使用两个 MediaPlayer

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

  • 开始下载初始媒体
  • 下载完成后,通过 currentMediaPlayer 开始播放。其余媒体继续 下载
  • 当下载的 fragment 快要播放时(完成前 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;

    }
};

同样——听起来像是一个计划,但效果并不完美。切换 MediaPlayer 的时刻非常好听。我这里有相反的情况:在 4.1 平板电脑上还可以,但在 HTC Sensation 上有明显的滞后。

我还尝试实现不同的下载技术。我已经通过 10Kb block 和 MP3 帧实现了下载。我不太清楚,但似乎在 MP3 帧的情况下 seekTo 和 start 工作得更好。但这只是一种感觉,我不知道解释。

流媒体播放器

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

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

如果是,那很可悲,因为它对我也不好。而且我在实现中没有看到任何新的想法。

所以,问题

你们如何在您的应用程序中实现音频流?我不相信我是唯一遇到过此类问题的人。应该有一些好的做法。

最佳答案

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

关于android - 音频流的最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16405017/

有关android - 音频流的最佳实践的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  4. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  5. ruby-on-rails - Rails 中同一个类的多个关联的最佳实践? - 2

    我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来

  6. ruby-on-rails - 向 Rails 3 添加 Ruby 扩展方法的最佳实践? - 2

    我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion

  7. ruby - 最佳原则中的原则 - 2

    我似乎经常遇到一些设计问题,但我不知道是什么是真的很合适。一方面我经常听到我应该限制耦合和坚持单一职责,但当我这样做时,我常常发现它很困难到在需要时将信息获取到程序的一部分。为了例如,classSingerdefinitialize(name)@name=nameendattr:nameend那么Song应该是:classSongdefnew(singer)@singer=singerendend或classSongdefnew(singer_name)@singer_name=singer_nameendend后者耦合性小,按道理应该用。但如果我以后发现宋有什么需要了解更多歌手,我的

  8. ruby-on-rails - 与 ActiveMerchant 一起使用的最佳支付网关是什么? - 2

    我需要使用ActiveMerchant库在我们的一个Rails应用程序中设置支付解决方案。尽管这个问题非常主观,但人们对主要网关(BrainTree、Authorize.net等)的体验如何?它必须:处理定期付款。有能力记入个人帐户。能够取消付款。有办法存储用户的付款详细信息(例如Authotize.netsCIM)。干杯 最佳答案 ActiveMerchant很棒,但在过去一年左右的时间里,我在使用它时发现了一些问题。首先,虽然某些网关可能会得到“支持”——但并非所有功能都包含在内。查看功能矩阵以确保完全支持您选择的网关-http

  9. ruby - 将 ruby​​ 哈希转换为数组的最佳方法是什么 - 2

    我有一个像这样的ruby​​散列{"stuff_attributes"=>{"1"=>{"foo"=>"bar","baz"=>"quux"},"2"=>{"foo"=>"bar","baz"=>"quux"}}}我想把它变成一个看起来像这样的散列{"stuff_attributes"=>[{"foo"=>"bar","baz"=>"quux"},{"foo"=>"bar","baz"=>"quux"}]}我还需要保留键的数字顺序,并且键的数量是可变的。上面是super简化的,但我在底部包含了一个真实的例子。执行此操作的最佳方法是什么?附言还需要递归就递归而言,这是我们可以假设的:1)

  10. Ruby 最佳实践 : working with classes - 2

    参见下面的示例,我想最好使用第二种方法,但第一种也可以。哪种方法最好,使用另一种的后果是什么?classTestdefstartp"started"endtest=Test.newtest.startendclassTest2defstartp"started"endendtest2=Test2.newtest2.start 最佳答案 我肯定会说第二种变体更有意义。第一个不会导致错误,但对象实例化完全过时且毫无意义。外部变量在类的范围内不可见:var="string"classAvar=A.newendputsvar#=>strin

随机推荐