53

我发现 Android MediaPlayer 准备使用不同流进行实时流播放所需的时间存在很大差异。

硬数据

我在 prepareAsync() 和 onPrepared(MediaPlayer mp) 回调之间添加了日志记录,并分别测试了几个流。每个流的时间非常一致(+/- 1 秒),结果如下:

  1. MPR 新闻流:27 秒 (http://newsstream1.publicradio.org:80/)
  2. MPR古典音乐流:15秒(http://classicalstream1.publicradio.org:80/)
  3. MPR 当前流:7 秒 (http://currentstream1.publicradio.org:80/)
  4. PRI 流:52 秒 (http://pri-ice.streamguys.biz/pri1)

测试是在 Nexus S 上执行的,Android 2.3.4,3G 连接(~1100 Kbps)。

播放非流式 MP3 音频文件不是问题。

以下是我如何播放流的片段:

准备媒体播放器:

...
mediaPlayer.setDataSource(playUrl);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.prepareAsync();
...

然后在 onPrepared(MediaPlayer mp) 中:

mediaPlayer.start();

为什么准备一些流而不是其他流需要这么长时间?上述数据似乎表明它可能基于已缓冲的数据,而不是缓冲音频内容的持续时间。这真的可以吗?

更新:我已经在具有 Android 1.6、2.2 和 2.3.4 的物理设备以及具有 1.6、2.1、2.2、2.3.1 和 2.3.3 的模拟器上测试了直播。我只看到 2.3.3 和 2.3.4 的长时间延迟。旧版本在 5 秒内开始播放。

4

6 回答 6

26

看起来它正在缓冲固定数量的数据,而不是固定的时间。对于任何不知道各种类型 NPR 流的比特率的人来说,数据如下所示:

  1. MPR 新闻流:27 秒(http://newsstream1.publicradio.org:80/),64 kbps
  2. MPR古典音乐流:15秒(http://classicalstream1.publicradio.org:80/),128 kbps
  3. MPR 当前流:7 秒(http://currentstream1.publicradio.org:80/),128 kbps
  4. PRI 流:52 秒(http://pri-ice.streamguys.biz/pri1),32 kbps

除了两个 128 kbps 流之间的差异之外,比特率和缓冲持续时间之间存在非常好的相关性。

无论如何,Android 是开源的,所以你可以随时查看它在做什么。不幸的是,prepareAsync()andprepare()是本机方法,并且似乎与缓冲区相关的事件也是从本机进程分派的。

您是否尝试过将一个附加OnBufferingUpdateListener到 MediaPlayer 以获取有关缓冲区状态的更细粒度的更新?比较事件传递的速率以及缓冲区在不同流中每个事件的填充百分比可能会很有趣。您可以将其与流比特率交叉引用,如果 4 秒的 32 kbps 缓冲填充缓冲区的百分比与 1 秒 128 kbps 的缓冲相同,那么我认为您会找到答案。

于 2011-07-05T13:16:44.957 回答
8

Switch MediaPlayerby FFmpegMediaPlayer MediaPlayer如果你想测试你的流,你可以通过他们拥有的演示来做到这一点要好得多。

于 2015-05-27T23:36:12.157 回答
5

我最近使用流式音频提供商调试了同样的问题。该问题与怯场和 32kbps 及更低的流媒体源有关。我们逐步完成了相同的流式传输,测量了 24、32、48、64 和 128 kbps 的响应时间。

  • 24 -> 46 秒开始流式传输
  • 32 -> 24 秒开始流式传输
  • 48 -> 2 秒开始流式传输
  • 64 -> 2 秒开始流式传输
  • 128 -> 2 秒开始流式传输

这是来自一致的无线连接,平均每个比特率尝试了 10 次以上。正如 Travis 指出的那样,关键是怯场无法计算缓冲音频的时间。有时我会看到一条消息“错误:1,-21492389”左右,这似乎使怯场播放器无声地崩溃。我试图追查这一点并最终得出结论,非常慢的流(低于 24 kbps)似乎会导致缓冲区溢出,因为它们会缓冲直到设备用完音频流的空间。

我想补充一点,OnBufferingUpdateListener在整个测试过程中对我来说根本没有触发。我不知道它是为了什么。我认为你可以知道加载情况的唯一方法是代理加载,以类似于上面提到的 NPR 应用程序的方式。

于 2012-10-12T01:22:40.873 回答
2

我已经尝试了 10 个数据点,三个快,7 个慢。这是一致的,即快的流是快的,慢的总是慢的。

我认为这与服务器提供的“内容长度”有关,如果没有正确指定内容长度,Android 不知道要缓冲多少。

可能是错的,没有达到wiresharking的程度。

于 2011-12-07T18:28:48.293 回答
2

如果您从 Icecast 流式传输,请查看burst-size设置:

突发大小是在连接时向客户端突发的数据量(以字节为单位)。与burst-on-connect 一样,这是为了快速填充媒体播放器使用的预缓冲区。默认值为 64 KB,这是大多数客户端使用的典型大小,因此通常不需要更改它。此设置适用于所有挂载点,除非在挂载设置中被覆盖。确保此值小于队列大小,如有必要,将队列大小增加到大于所需的突发大小。不这样做可能会导致侦听器客户端连接尝试中止,因为初始突发导致连接已经超过队列大小限制。

burst-size在我的服务器上增加到 131072,现在我的 Android 应用基于MediaPlayer播放流,没有太多延迟。

于 2015-09-30T20:08:01.080 回答
0

当我遇到这个问题时,我决定在打开播放器之前测试流是否可用。如果您让用户等待很长时间并且音乐将启动它就可以了(不是,但假设它可以)。最坏的情况是让他等很久,音乐永远不会响起!所以,我们有两种情况:

  • 直播场景,如广播电台。
  • 在线提供的录制的 mp3 文件。

无线电场景中,我们可以检查端口是否正在接受连接(打开/关闭状态)。如果它是打开的,请为播放器准备音乐,否则根本不准备。

public static boolean isLiveStreamingAvailable() {
        SocketAddress sockaddr = new InetSocketAddress(STREAMING_HOST, STREAMING_PORT);
        // Create your socket
        Socket socket = new Socket();
        boolean online = true;
        // Connect with 10 s timeout
        try {
            socket.connect(sockaddr, 10000);
        } catch (SocketTimeoutException stex) {
            // treating timeout errors separately from other io exceptions
            // may make sense
            return false;
        } catch (IOException iOException) {
            return false;
        } finally {
            // As the close() operation can also throw an IOException
            // it must caught here
            try {
                socket.close();
            } catch (IOException ex) {
                // feel free to do something moderately useful here, eg log the event
            }

        }
        return true;
    }

mp3 文件场景中,情况略有不同。您应该检查 http 请求之后的响应代码。

public static boolean isRecordedStreamingAvailable() {
        try {
            HttpURLConnection.setFollowRedirects(false);
            // note : you may also need
            //        HttpURLConnection.setInstanceFollowRedirects(false)
            HttpURLConnection con =
                    (HttpURLConnection) new URL(RECORDED_URL).openConnection();
            con.setRequestMethod("HEAD");
            return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
于 2017-02-04T15:40:25.280 回答