我需要播放实时音频流,实际上它是收音机。问题是我还需要管理20分钟的流量缓冲区。据我所知,用android实现起来并不容易。
首先我检查了MediaPlayer,但它没有提供任何缓冲区管理方法。实际上你甚至无法直接设置缓冲区大小。
其次,我尝试使用本地文件管理缓冲区:逐步将流下载到临时文件并切换它们。但是当您想切换到以下文件(在MediaPlayer中更改数据源)时,音频不会连续播放。你可以听到短暂的中断。
最后一个想法是使用流代理。通常它用于在8以下的Android版本中播放流。在流代理中,您创建ServerSocket,从音频流中读取并写入播放器。所以实际上我可以在那里管理缓冲。我可以缓存流并写入MediaPlayer,无论我想要什么。但。它不适用于Android 8.
我遇到异常:通过peer java.net.SocketException重置连接:由peer重置连接。 MediaPlayer 8不想从套接字读取数据。
因此我有两个问题: 1)实现流缓冲的其他方法是什么? 2)如何为Android 8改编StreamProxy?
任何想法都表示赞赏。
由于
答案 0 :(得分: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变得更加聪明。如果我通过这样的网址"http://127.0.0.1:%d/%s"
,它不仅要获得字节,还要获得“完整”的http响应。
另外我想知道是否还有其他方法可以实现缓冲?据我所知,MediaPlayer只使用文件和网址。 文件解决方案不起作用。所以我必须使用socket进行流传输。
由于
答案 1 :(得分:5)
我刚刚测试了以下修复程序,它确实有效。此问题是由processRequest()
StreamProxy
方法标题中使用的“\ n”引起的。将此更改为“\ r \ n”应该会使错误消失。
至于实现自定义流媒体,我过去曾经使用过它,虽然它似乎主要用于无限流媒体广播。 http://blog.pocketjourney.com/2009/12/27/android-streaming-mediaplayer-tutorial-updated-to-v1-5-cupcake/
答案 2 :(得分: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";
当您收到来自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";
享受!
答案 3 :(得分:2)
SDK 8中的Mediaplayer将无法读取代理网址。但根据我目前的工作,这与设备不同。在我的Samsung ACE(SDK 8)中,代理连接工作正常,但在我的HTC Incredible S中,代理连接会产生与您相同的问题。与音频流的直接连接工作正常,但这会导致某些设备(如sprint上的EVO)出现blips和boops。
您是否获得了此问题的解决方案?你是怎么处理的?
-Hari
答案 4 :(得分:1)
我意识到这个问题就像5岁一样,但是如果有其他人在徘徊:ExoPlayer是来自谷歌的库,它可以让你更多地控制缓冲区大小等选项。
//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