为什么Android的MediaPlayer需要这么长时间来准备一些实时流来播放呢?

时间:2022-10-19 18:46:48

I am finding big differences in the time it takes the Android MediaPlayer to prepare for live stream playback with different streams.

我发现在使用Android MediaPlayer来准备不同的流的实时流回放时,有很大的不同。

The hard data

数据

I added logging between prepareAsync() and the onPrepared(MediaPlayer mp) callback and tested several streams a few times each. The times for each stream were very consistent (+/- one second), and here are the results:

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

  1. MPR news stream: 27 seconds (http://newsstream1.publicradio.org:80/)
  2. MPR新闻流:27秒(http://newsstream1.publicradio.org:80/)
  3. MPR classical music stream: 15 seconds (http://classicalstream1.publicradio.org:80/)
  4. MPR经典音乐流:15秒(http://classicalstream1.publicradio.org:80/)
  5. MPR The Current stream: 7 seconds (http://currentstream1.publicradio.org:80/)
  6. MPR当前流:7秒(http://currentstream1.publicradio.org:80/)
  7. PRI stream: 52 seconds (http://pri-ice.streamguys.biz/pri1)
  8. PRI流:52秒(http://pri-ice.streamguys.biz/pri1)

The tests were performed on a Nexus S with Android 2.3.4 on a 3G connection (~1100 Kbps).

这些测试是在3G连接(1100 ~ Kbps)上搭载Android 2.3.4的Nexus S上进行的。

Playing non-streaming MP3 audio files is not an issue.

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

Here are snippets of how I am playing the streams:

下面是我如何演奏这些溪流的片段:

Prepare MediaPlayer:

准备媒体播放器:

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

Then in onPrepared(MediaPlayer mp):

然后在onPrepared(媒体播放器mp):

mediaPlayer.start();

Why does it take so long to prepare some streams but not others? The above data seems to suggest that it might be based on the amount of data that has been buffered and not the duration of the buffered audio content. Could this really be?

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

Update: I have tested live streaming on physical devices with Android 1.6, 2.2 and 2.3.4 and emulators with 1.6, 2.1, 2.2, 2.3.1 and 2.3.3. I am only seeing the long delay on 2.3.3 and 2.3.4. The older versions start playback within 5 seconds.

更新:我测试了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秒内开始播放。

6 个解决方案

#1


23  

It does appear that it is buffering a fixed amount of data rather than a fixed amount of time. For anyone who doesn't know the bitrates of various types of NPR streams off the top of their head, the data looks like:

它似乎是在缓冲固定数量的数据而不是固定的时间。对于那些不知道各种类型的NPR流的比特率的人来说,数据看起来是这样的:

  1. MPR news stream: 27 seconds (http://newsstream1.publicradio.org:80/), 64 kbps
  2. MPR新闻流:27秒(http://newsstream1.publicradio.org:80/), 64 kbps
  3. MPR classical music stream: 15 seconds (http://classicalstream1.publicradio.org:80/), 128 kbps
  4. MPR古典音乐流:15秒(http://classicalstream1.publicradio.org:80/), 128 kbps
  5. MPR The Current stream: 7 seconds (http://currentstream1.publicradio.org:80/), 128 kbps
  6. 当前流:7秒(http://currentstream1.publicradio.org:80/), 128 kbps
  7. PRI stream: 52 seconds (http://pri-ice.streamguys.biz/pri1), 32 kbps
  8. PRI流:52秒(http://pri-ice.streamguys.biz/pri1), 32kbps。

Apart from the discrepancy between the two 128 kbps streams, there is a very good correlation between bitrate and buffering duration.

除了两个128kbps流之间的差异外,比特率和缓冲时间之间有很好的相关性。

In any case, Android is open-source, so you could always look at what it's doing. Unfortunately, prepareAsync() and prepare() are native methods, and it appears that buffer-related events are dispatched from a native process as well.

无论如何,Android是开源的,所以你总可以看看它在做什么。不幸的是,prepareAsync()和prepare()是本机方法,而且似乎与缓冲区相关的事件也来自本机进程。

Have you tried attaching an OnBufferingUpdateListener to the MediaPlayer to get finer-grained updates about the buffer-state? It might be interesting to compare the rate at which the events are delivered and by what percentage the buffer fills on each event across the different streams. You can cross-reference that against the stream bitrates, and if 4 seconds of buffering at 32 kbps fills the buffer the same percentage as 1 second of buffering at 128 kbps then I think you will have found your answer.

您是否尝试过将OnBufferingUpdateListener附加到MediaPlayer以获得关于缓冲区状态的更细粒度的更新?比较事件交付的速率和跨不同流对每个事件的缓冲区填充的百分比可能会很有趣。您可以针对流比特率交叉引用它,如果32 kbps上的4秒缓冲填满缓冲区的百分比与128 kbps上的1秒缓冲相同,那么我认为您已经找到了答案。

#2


7  

Switch MediaPlayer by FFmpegMediaPlayer works much betther than the MediaPlayer if you want to test your streams you can do it through the demo that they have.

通过FFmpegMediaPlayer切换MediaPlayer比MediaPlayer工作得更好,如果你想测试你的流,你可以通过他们拥有的demo来完成。

#3


4  

I have recently debugged this same issue with a streaming audio provider. The issue is related to stagefright and streaming sources 32kbps and lower. We stepped through the same streaming measuring the response time at 24, 32, 48, 64 and 128 kbps.

我最近在一个流媒体音频提供商调试了这个问题。这个问题与怯场和32kbps及更低的流媒体有关。我们在24、32、48、64和128 kbps上通过同样的流度量响应时间。

  • 24 -> 46 seconds to start streaming
  • 24 - >46秒开始流媒体
  • 32 -> 24 seconds to start streaming
  • 32 - >24秒开始流媒体
  • 48 -> 2 seconds to start streaming
  • 48 ->开始流媒体2秒
  • 64 -> 2 seconds to start streaming
  • >开始流媒体2秒
  • 128 -> 2 seconds to start streaming
  • >开始流媒体2秒

This is from a consistent, wireless connection averaged over 10 attempts at each bit rate. The key, as Travis pointed out, was that stagefright could not figure out how long to buffer the audio. Sometimes i would see a message 'error: 1,-21492389' or so, which seemed to crash the stagefright player silently. I tried to track this down and eventually came to the conclusion that the very slow streams (sub 24 kbps) seemed to cause a buffer overflow because they would buffer until the device ran out of space for the audio stream.

这是一种一致的无线连接,平均每比特率超过10次尝试。正如特拉维斯指出的,关键在于怯场者无法计算出缓冲音频的时间。有时我会看到一条“错误:1,-21492389”之类的信息,这似乎在无声地打击怯场的玩家。我试图跟踪它,最终得出的结论是,非常慢的流(sub 24 kbps)似乎会导致缓冲区溢出,因为它们会缓冲区溢出,直到设备耗尽音频流的空间。

I wanted to add that OnBufferingUpdateListener did not fire at all for me during this entire test. I don't know what it is there for. I think the only way you can tell how loading is going is to proxy the loading, in a similar fashion to the NPR app mentioned above.

我想添加OnBufferingUpdateListener在整个测试期间没有为我启动。我不知道有什么用。我认为,你能判断加载方式的唯一方法就是代理加载,就像上面提到的NPR应用一样。

#4


2  

If you're streaming from Icecast, take a look at the burst-size setting:

如果你是来自Icecast的流媒体,请查看最大的设置:

The burst size is the amount of data (in bytes) to burst to a client at connection time. Like burst-on-connect, this is to quickly fill the pre-buffer used by media players. The default is 64 kbytes which is a typical size used by most clients so changing it is not usually required. This setting applies to all mountpoints unless overridden in the mount settings. Ensure that this value is smaller than queue-size, if necessary increase queue-size to be larger than your desired burst-size. Failure to do so might result in aborted listener client connection attempts, due to initial burst leading to the connection already exceeding the queue-size limit.

突发大小是连接时向客户端发送的数据量(以字节为单位)。就像连接中的负载一样,这可以快速填充媒体播放器使用的预缓冲。默认的大小是64 kbytes,这是大多数客户端使用的典型尺寸,因此通常不需要更改它。此设置适用于所有挂载点,除非在挂载设置中重写。确保该值小于队列大小,如果必要的话,将队列大小增加到大于所需的崩溃大小。如果不这样做,可能会导致中断侦听器客户端连接尝试,因为初始的突发导致连接已经超过队列大小的限制。

I increased the burst-size to 131072 on my server, and now my Android app based on MediaPlayer plays streams without much delay.

在我的服务器上,我将负载大小增加到131072,现在我的基于MediaPlayer的Android应用程序可以毫无延迟地播放流。

#5


1  

I've tried this with 10 datapoints, three fast, 7 slow. It's consistent, that is a fast stream is fast and a slow one is always slow.

我试过10个数据池,3个快,7个慢。它是一致的,那就是快的流是快的,慢的总是慢的。

I think it's related to the server delivered 'content-length,' Android doesn't know how much to buffer if the content-length isn't properly specified.

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

Could be wrong, didn't go as far as wiresharking.

可能是错的,也没那么深入。

#6


0  

When I had this problem I decided to test if stream is available before opening the player. If you make the user to wait for a long time and the music will start it ok (it's not, but let's say it is ok). The worst case scenario is to make him wait for a long time and the music will never start! So, we have 2 situations:

当我遇到这个问题时,我决定在打开播放器之前测试流是否可用。如果你让用户等待很长一段时间,音乐就会启动(不是,但我们假设它是好的)。最糟糕的情况是让他等待很长时间,音乐永远不会开始!我们有两种情况

  • The live streaming scenario, like a radio station.
  • 实时流媒体场景,比如一个广播站。
  • The recorded mp3 file which is available online.
  • 录制的mp3文件,可以在网上找到。

At the radio scenario we could check if the port is accepting connections (open/close state). If it is open prepare the player for the music, else do not prepare it at all.

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

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;
    }

At the mp3 file scenario the things are a little bit different. You should check for the response code which follows after the http request.

在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;
        }
    }

#1


23  

It does appear that it is buffering a fixed amount of data rather than a fixed amount of time. For anyone who doesn't know the bitrates of various types of NPR streams off the top of their head, the data looks like:

它似乎是在缓冲固定数量的数据而不是固定的时间。对于那些不知道各种类型的NPR流的比特率的人来说,数据看起来是这样的:

  1. MPR news stream: 27 seconds (http://newsstream1.publicradio.org:80/), 64 kbps
  2. MPR新闻流:27秒(http://newsstream1.publicradio.org:80/), 64 kbps
  3. MPR classical music stream: 15 seconds (http://classicalstream1.publicradio.org:80/), 128 kbps
  4. MPR古典音乐流:15秒(http://classicalstream1.publicradio.org:80/), 128 kbps
  5. MPR The Current stream: 7 seconds (http://currentstream1.publicradio.org:80/), 128 kbps
  6. 当前流:7秒(http://currentstream1.publicradio.org:80/), 128 kbps
  7. PRI stream: 52 seconds (http://pri-ice.streamguys.biz/pri1), 32 kbps
  8. PRI流:52秒(http://pri-ice.streamguys.biz/pri1), 32kbps。

Apart from the discrepancy between the two 128 kbps streams, there is a very good correlation between bitrate and buffering duration.

除了两个128kbps流之间的差异外,比特率和缓冲时间之间有很好的相关性。

In any case, Android is open-source, so you could always look at what it's doing. Unfortunately, prepareAsync() and prepare() are native methods, and it appears that buffer-related events are dispatched from a native process as well.

无论如何,Android是开源的,所以你总可以看看它在做什么。不幸的是,prepareAsync()和prepare()是本机方法,而且似乎与缓冲区相关的事件也来自本机进程。

Have you tried attaching an OnBufferingUpdateListener to the MediaPlayer to get finer-grained updates about the buffer-state? It might be interesting to compare the rate at which the events are delivered and by what percentage the buffer fills on each event across the different streams. You can cross-reference that against the stream bitrates, and if 4 seconds of buffering at 32 kbps fills the buffer the same percentage as 1 second of buffering at 128 kbps then I think you will have found your answer.

您是否尝试过将OnBufferingUpdateListener附加到MediaPlayer以获得关于缓冲区状态的更细粒度的更新?比较事件交付的速率和跨不同流对每个事件的缓冲区填充的百分比可能会很有趣。您可以针对流比特率交叉引用它,如果32 kbps上的4秒缓冲填满缓冲区的百分比与128 kbps上的1秒缓冲相同,那么我认为您已经找到了答案。

#2


7  

Switch MediaPlayer by FFmpegMediaPlayer works much betther than the MediaPlayer if you want to test your streams you can do it through the demo that they have.

通过FFmpegMediaPlayer切换MediaPlayer比MediaPlayer工作得更好,如果你想测试你的流,你可以通过他们拥有的demo来完成。

#3


4  

I have recently debugged this same issue with a streaming audio provider. The issue is related to stagefright and streaming sources 32kbps and lower. We stepped through the same streaming measuring the response time at 24, 32, 48, 64 and 128 kbps.

我最近在一个流媒体音频提供商调试了这个问题。这个问题与怯场和32kbps及更低的流媒体有关。我们在24、32、48、64和128 kbps上通过同样的流度量响应时间。

  • 24 -> 46 seconds to start streaming
  • 24 - >46秒开始流媒体
  • 32 -> 24 seconds to start streaming
  • 32 - >24秒开始流媒体
  • 48 -> 2 seconds to start streaming
  • 48 ->开始流媒体2秒
  • 64 -> 2 seconds to start streaming
  • >开始流媒体2秒
  • 128 -> 2 seconds to start streaming
  • >开始流媒体2秒

This is from a consistent, wireless connection averaged over 10 attempts at each bit rate. The key, as Travis pointed out, was that stagefright could not figure out how long to buffer the audio. Sometimes i would see a message 'error: 1,-21492389' or so, which seemed to crash the stagefright player silently. I tried to track this down and eventually came to the conclusion that the very slow streams (sub 24 kbps) seemed to cause a buffer overflow because they would buffer until the device ran out of space for the audio stream.

这是一种一致的无线连接,平均每比特率超过10次尝试。正如特拉维斯指出的,关键在于怯场者无法计算出缓冲音频的时间。有时我会看到一条“错误:1,-21492389”之类的信息,这似乎在无声地打击怯场的玩家。我试图跟踪它,最终得出的结论是,非常慢的流(sub 24 kbps)似乎会导致缓冲区溢出,因为它们会缓冲区溢出,直到设备耗尽音频流的空间。

I wanted to add that OnBufferingUpdateListener did not fire at all for me during this entire test. I don't know what it is there for. I think the only way you can tell how loading is going is to proxy the loading, in a similar fashion to the NPR app mentioned above.

我想添加OnBufferingUpdateListener在整个测试期间没有为我启动。我不知道有什么用。我认为,你能判断加载方式的唯一方法就是代理加载,就像上面提到的NPR应用一样。

#4


2  

If you're streaming from Icecast, take a look at the burst-size setting:

如果你是来自Icecast的流媒体,请查看最大的设置:

The burst size is the amount of data (in bytes) to burst to a client at connection time. Like burst-on-connect, this is to quickly fill the pre-buffer used by media players. The default is 64 kbytes which is a typical size used by most clients so changing it is not usually required. This setting applies to all mountpoints unless overridden in the mount settings. Ensure that this value is smaller than queue-size, if necessary increase queue-size to be larger than your desired burst-size. Failure to do so might result in aborted listener client connection attempts, due to initial burst leading to the connection already exceeding the queue-size limit.

突发大小是连接时向客户端发送的数据量(以字节为单位)。就像连接中的负载一样,这可以快速填充媒体播放器使用的预缓冲。默认的大小是64 kbytes,这是大多数客户端使用的典型尺寸,因此通常不需要更改它。此设置适用于所有挂载点,除非在挂载设置中重写。确保该值小于队列大小,如果必要的话,将队列大小增加到大于所需的崩溃大小。如果不这样做,可能会导致中断侦听器客户端连接尝试,因为初始的突发导致连接已经超过队列大小的限制。

I increased the burst-size to 131072 on my server, and now my Android app based on MediaPlayer plays streams without much delay.

在我的服务器上,我将负载大小增加到131072,现在我的基于MediaPlayer的Android应用程序可以毫无延迟地播放流。

#5


1  

I've tried this with 10 datapoints, three fast, 7 slow. It's consistent, that is a fast stream is fast and a slow one is always slow.

我试过10个数据池,3个快,7个慢。它是一致的,那就是快的流是快的,慢的总是慢的。

I think it's related to the server delivered 'content-length,' Android doesn't know how much to buffer if the content-length isn't properly specified.

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

Could be wrong, didn't go as far as wiresharking.

可能是错的,也没那么深入。

#6


0  

When I had this problem I decided to test if stream is available before opening the player. If you make the user to wait for a long time and the music will start it ok (it's not, but let's say it is ok). The worst case scenario is to make him wait for a long time and the music will never start! So, we have 2 situations:

当我遇到这个问题时,我决定在打开播放器之前测试流是否可用。如果你让用户等待很长一段时间,音乐就会启动(不是,但我们假设它是好的)。最糟糕的情况是让他等待很长时间,音乐永远不会开始!我们有两种情况

  • The live streaming scenario, like a radio station.
  • 实时流媒体场景,比如一个广播站。
  • The recorded mp3 file which is available online.
  • 录制的mp3文件,可以在网上找到。

At the radio scenario we could check if the port is accepting connections (open/close state). If it is open prepare the player for the music, else do not prepare it at all.

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

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;
    }

At the mp3 file scenario the things are a little bit different. You should check for the response code which follows after the http request.

在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;
        }
    }