音频流缓冲

21

我需要播放实时音频流,实际上是广播。问题是我还需要管理20分钟的缓冲区进行流媒体播放。据我所知,这在Android上实现起来并不容易。

首先,我尝试使用MediaPlayer,但它不提供任何用于缓冲区管理的方法。事实上,您甚至无法直接设置缓冲区大小。

其次,我尝试使用本地文件来管理缓冲区:逐步下载流到临时文件并切换它们。但是,当您要切换到下一个文件(在MediaPlayer中更改数据源)时,音频不会连续播放。您可能会听到短暂的中断声。

最后一个想法是使用流代理。通常在Android版本8以下播放流时会使用它。在流代理中,您可以创建ServerSocket,从音频流中读取并写入播放器。因此,我可以在那里管理缓冲。我可以缓存流并将任何想要的内容写入MediaPlayer。但是,在Android 8上它不起作用。

我收到了异常:Connection reset by peer java.net.SocketException: Connection reset by peer. MediaPlayer 8不想从套接字读取数据。

因此,我有两个问题: 1)有什么其他方法可以实现流缓冲? 2)如何为Android 8调整StreamProxy?

任何想法都会受到赞赏。

谢谢!


"2) 如何为Android 8适配StreamProxy?除非您展示所使用的代码,否则任何人都无法提供帮助。" - Squonk
1
你能详细解释一下如何将流写入MediaPlayer吗? - Ashwin N Bhanushali
5个回答

8
我使用了和 NPR 项目使用的相同的 StreamProxy - https://code.google.com/p/npr-android-app/source/browse/Npr/src/org/npr/android/news/StreamProxy.java
因此,它可以得到原始音频流:
  String url = request.getRequestLine().getUri();
  HttpResponse realResponse = download(url);
  ...
  InputStream data = realResponse.getEntity().getContent();

从此流向客户端套接字进行写入:

  byte[] buff = new byte[1024 * 50];
  while (isRunning && (readBytes = data.read(buff, 0, buff.length)) != -1) {
    client.getOutputStream().write(buff, 0, readBytes);
  }

您可以从上面的链接中获取整个代码。

最后,他们如何初始化播放器(PlaybackService):

  if (stream && sdkVersion < 8) {
    if (proxy == null) {
      proxy = new StreamProxy();
      proxy.init();
      proxy.start();
    }
    String proxyUrl = String.format("http://127.0.0.1:%d/%s", proxy.getPort(), url);
    playUrl = proxyUrl;
   }
  ...
  mediaPlayer.setDataSource(playUrl);
  mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  mediaPlayer.prepareAsync();

所以他们检查SDK版本。但是如果我省略这个检查并且也使用代理来处理SDK 8,那么我会得到一个异常。奇怪的是MediaPlayer甚至不尝试读取流:

12-30 15:09:41.576: DEBUG/StreamProxy(266): downloading...
12-30 15:09:41.597: DEBUG/StreamProxy(266): reading headers
12-30 15:09:41.597: DEBUG/StreamProxy(266): headers done
12-30 15:09:41.647: DEBUG/StreamProxy(266): writing to client
12-30 15:09:41.857: INFO/AwesomePlayer(34): mConnectingDataSource->connect() returned -    1007
12-30 15:09:41.857: ERROR/MediaPlayer(266): error (1, -1007)
12-30 15:09:41.867: ERROR/MediaPlayer(266): Error (1,-1007)
12-30 15:09:41.867: WARN/AudioService(266): onError(1, -1007)
12-30 15:09:41.867: WARN/AudioService(266): MediaPlayer refused to play current item.  Bailing on prepare.
12-30 15:09:41.867: WARN/AudioService(266): onComplete()
12-30 15:09:42.097: ERROR/StreamProxy(266): Connection reset by peer
        java.net.SocketException: Connection reset by peer
        at org.apache.harmony.luni.platform.OSNetworkSystem.writeSocketImpl(Native Method)
        at org.apache.harmony.luni.platform.OSNetworkSystem.write(OSNetworkSystem.java:723)
        at org.apache.harmony.luni.net.PlainSocketImpl.write(PlainSocketImpl.java:578)
        at org.apache.harmony.luni.net.SocketOutputStream.write(SocketOutputStream.java:59)
        at com.skyblue.service.media.StreamProxy.processRequest(StreamProxy.java:204)
        at com.skyblue.service.media.StreamProxy.run(StreamProxy.java:103)
        at java.lang.Thread.run(Thread.java:1096)

似乎MediaPlayer变得更加聪明了。如果我传递这样的url "http://127.0.0.1:%d/%s",它不仅想要获取字节,还想要获取完整的http响应。
此外,我想知道是否有其他实现缓冲的方法?据我所知,MediaPlayer只能消耗文件和URL。解决方案中的文件无法正常工作。因此,我必须使用socket进行流传输。
谢谢

2
嗨Eugenious,你发布的NPR项目链接已经失效了,你能发一下有效的链接吗? - Ashwin N Bhanushali
这是正确的链接:https://code.google.com/p/npr-android-app/source/browse/Npr/src/org/npr/android/news/StreamProxy.java - stanri
嗨Eugenious,你找到原问题的解决方案了吗?brack发布的修复程序对你有用吗? - burakk
谢谢您发布这个问题!能否有人解释一下为什么以下Uri有效?String proxyUrl = String.format("http://127.0.0.1:%d/%s", proxy.getPort(), url); 谢谢! - dazza5000

5

博客文章的网址不存在。 - Murat

3

当您寻求或跳过连接且MediaPlayer一直在重新连接代理服务器时,必须在从客户端获取请求和范围(int)后发送此响应,并以状态206发送。

String headers += "HTTP/1.1 206 Partial Content\r\n";
headers += "Content-Type: audio/mpeg\r\n";
headers += "Accept-Ranges: bytes\r\n";
headers += "Content-Length: " + (fileSize-range) + "\r\n";
headers += "Content-Range: bytes "+range + "-" + fileSize + "/*\r\n";
headers += "\r\n";

当您收到来自不包含HTTP标头中范围的MediaPlayer请求时,它正在请求一个新的流文件。在这种情况下,您的响应标头应如下所示:

String headers = "HTTP/1.1 200 OK\r\n";
headers += "Content-Type: audio/mpeg\r\n";
headers += "Accept-Ranges: bytes\r\n";
headers += "Content-Length: " + fileSize + "\r\n";
headers += "\r\n";

享受吧!


2

SDK 8版本的MediaPlayer无法读取代理URL。但根据我的工作经验,这在不同设备上表现不同。在我的三星ACE(SDK 8)上,代理连接正常工作,但在我的HTC Incredible S上,代理连接会出现与您遇到的相同问题。直接连接音频流可以正常工作,但在某些设备上(如Sprint上的EVO)会导致杂音和跳动。

您解决了这个问题吗?您是如何处理的?

-Hari


我也遇到了同样的问题,我有99%的把握它与底层媒体播放器有关。在大多数使用当前媒体播放器(AwesomePlayer/StageFright)的SDK 8设备上,会发生“连接重置”的情况。然而,一些设备使用过时的媒体播放器(PVPlayer),不会出现任何错误。 - brack

1
我意识到这个问题已经有五年了,但是如果还有其他人在寻找答案:ExoPlayer 是来自 Google 的库,它可以让你更好地控制缓冲区大小等选项。
    //DefaultUriDataSource – For playing media that can be either local or loaded over the network.
    DefaultUriDataSource dataSource = new DefaultUriDataSource(WgtechApplication.getAppContext(),
            Util.getUserAgent(WgtechApplication.getAppContext(), WgtechApplication.class.getSimpleName()));
    Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);

    //ExtractorSampleSource – For formats such as MP3, M4A, MP4, WebM, MPEG-TS and AAC.
    ExtractorSampleSource sampleSource = new ExtractorSampleSource(Uri.parse(RADIO_STREAMING_URL),
            dataSource, allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
    player.prepare(new MediaCodecAudioTrackRenderer(sampleSource));

这是我创建的一个小项目,用来学习如何使用它。 https://github.com/feresr/MyMediaPlayer

http://developer.android.com/guide/topics/media/exoplayer.html https://github.com/google/ExoPlayer


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