音频流缓冲

发布于 2024-10-09 11:55:58 字数 588 浏览 7 评论 0原文

我需要播放实时音频流,实际上它是广播。问题是我还需要管理 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)如何将StreamProxy适配为android 8?

任何想法表示赞赏。

谢谢

I need to play live audio stream, actually it is radio. The problem is that I also need to manage 20 minute buffer for streaming. As far as I understand it's not easy to implement with android.

Firstly I checked MediaPlayer, but it doesn't provide any methods for buffer management. In fact you even can't set buffer size directly.

Secondly I tried to manage buffer using local files: progressively download the stream to a temporary files and switch them. But when you want to switch to following file (change datasource in MediaPlayer), audio is not played continuously. You can hear short interruptions.

The last idea is to use stream proxy. Usually it is used for playing streams in Android versions below 8. In stream proxy you create ServerSocket, read from audio stream and write to player. So actually I can manage buffering there. I can cache stream and write to MediaPlayer whatever I want. But. It doesn't work with Android 8.

I got exception: Connection reset by peer java.net.SocketException: Connection reset by peer. MediaPlayer 8 doesn't want to read data from socket.

Thus I have two questions:
1) What other way to implement stream buffering?
2) how to adapt StreamProxy for android 8?

Any ideas are appreciated.

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

掀纱窥君容 2024-10-16 11:55:58

我使用与 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 仅使用文件和网址。
文件解决方案不起作用。所以我必须使用套接字进行流传输。

谢谢

I use the same StreamProxy that guys used for NPR project - https://code.google.com/p/npr-android-app/source/browse/Npr/src/org/npr/android/news/StreamProxy.java

So it gets original audio stream:

  String url = request.getRequestLine().getUri();
  HttpResponse realResponse = download(url);
  ...
  InputStream data = realResponse.getEntity().getContent();

And writes from this stream to client socket:

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

(You can get whole code from the link above.)

And finally how they initialize the player (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();

So they check SDK version. But if I omit this check and use proxy for SDK 8 as well I get an exception.
It is strange that MediaPlayer even doesn't try to read the stream:

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)

It seems that MediaPlayer became more clever. And If i pass such url "http://127.0.0.1:%d/%s" it wants to get not only bytes but "full" http responce.

Also I wonder if there any other ways implement buffering? As I know the MediaPlayer consumes only files and urls.
Solution with files doesn't work. So I have to use socket for stream transmitting.

Thanks

猥︴琐丶欲为 2024-10-16 11:55:58

我刚刚测试了以下修复,它有效。此问题是由 StreamProxyprocessRequest() 方法中的标头中使用的 "\n" 引起的。将其更改为“\r\n”应该会使错误消失。

至于实现自定义流媒体,我过去曾使用过它,尽管它似乎主要用于无限流媒体广播。 http:// blog.pocketjourney.com/2009/12/27/android-streaming-mediaplayer-tutorial-updated-to-v1-5-cupcake/

I just tested the following fix, and it works. This issue is caused by the "\n" used in the headers in the processRequest() method of StreamProxy. Changing this to "\r\n" should make the error go away.

As for implementing custom streaming, I've used this in the past, though it seems to be mainly for infinitely streaming radio. http://blog.pocketjourney.com/2009/12/27/android-streaming-mediaplayer-tutorial-updated-to-v1-5-cupcake/

财迷小姐 2024-10-16 11:55:58

当您查找或跳过或连接丢失并且 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";

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

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

Enjoy!

When you seek or skip or the connection is lost and MediaPlayer keeps reconnecting to the proxy server, you must send this response with Status 206 after you get the request and range(int) from the client.

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

And when you receive a request from MediaPlayer that does not contain Range in the HTTP header , then it is requesting a new stream file, in this case your response header should look like this:

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

Enjoy!

吾家有女初长成 2024-10-16 11:55:58

SDK 8 中的 Mediaplayer 将无法读取代理 URL。但根据我目前的工作,这因设备而异。在我的 Samsung ACE (SDK 8) 中,代理连接工作正常,但在我的 HTC Incredible S 中,代理连接会出现与您相同的问题。直接连接到音频流工作正常,但这会导致某些设备(例如 sprint 上的 EVO)出现杂音和杂音。

您找到这个问题的解决方案了吗?你是怎么处理的?

-哈里

The Mediaplayer from SDK 8 would not be able to read a proxy url. But based on my current work, this is differing from device to device. In my Samsung ACE (SDK 8) the proxy connection works fine, but in my HTC Incredible S, the proxy connection would give the same issue as you have. A direct connection to the audio stream works fine but this causes blips and boops in some devices like EVO on sprint.

Did you get a resolution for this issue? How did you handle this?

-Hari

怀念你的温柔 2024-10-16 11:55:58

我意识到这个问题已经有 5 年历史了,但以防万一其他人在徘徊: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

I realise this Question is like 5 years old but in case some one else is wandering: ExoPlayer is library from Google that gives you much more control over options like buffer size.

    //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));

Here's a small project I created to learn how to use it.
https://github.com/feresr/MyMediaPlayer

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文