我发现Android MediaPlayer准备使用不同的流进行实时流播放所花费的时间差异很大。
硬数据
我在prepareAsync()和onPrepared(MediaPlayer mp)回调之间添加了日志记录,并且每次都测试了几个流。每个流的时间非常一致(+/- 1秒),结果如下:
测试是在带有Android 2.3.4的Nexus S上通过3G连接(~1100 Kbps)进行的。
播放非流式MP3音频文件不是问题。
以下是我如何播放视频流的片段:
准备MediaPlayer:
...
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秒内开始播放。
答案 0 :(得分:24)
似乎它正在缓冲固定数量的数据而不是固定的时间量。对于那些不知道各种类型的NPR流的比特率的人来说,数据看起来像:
除了两个128 kbps流之间的差异外,比特率和缓冲持续时间之间存在非常好的相关性。
在任何情况下,Android都是开源的,因此您始终可以look at what it's doing。不幸的是,prepareAsync()
和prepare()
是本机方法,并且似乎也从本机进程调度与缓冲区相关的事件。
您是否尝试将OnBufferingUpdateListener
附加到MediaPlayer以获取有关缓冲区状态的更细粒度的更新?比较事件传递的速率和缓冲区填充不同流中每个事件的百分比可能会很有趣。您可以针对流比特率进行交叉引用,如果以32 kbps缓冲4秒,缓冲区填充缓冲区的速度与1秒的128 kbps缓冲相同,那么我认为您将找到答案。
答案 1 :(得分:7)
MediaPlayer
切换MediaPlayer
比override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
for touch in touches {
var tap = touch as? UITouch
var touchPoint: CGPoint = tap!.locationInView(self)
self.touchDown(touchPoint)
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
self.releaseTouch()
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesMoved(touches, withEvent: event)
}
func touchDown(point: CGPoint){
if rippleEffect == true {
touchView.frame = touchViewInitFrame
self.addSubview(touchView)
var touchViewFrameXPosition: CGFloat = point.x - (frame.size.width) / 2
println("X position = \(touchViewFrameXPosition)")
var touchViewFrameYPosition: CGFloat = -(self.frame.size.width - self.frame.size.height) / 2
touchViewFrame = CGRectMake(touchViewFrameXPosition, touchViewFrameYPosition, frame.size.width, frame.size.width)
UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseInOut, animations: {
self.touchView.frame = self.touchViewFrame
}, completion: nil)
}
}
func releaseTouch(){
if rippleEffect == true {
UIView.animateWithDuration(0.0, delay: 0.0, options: .CurveEaseInOut, animations: {
self.touchView.removeFromSuperview()
}, completion: nil)
}
}
更有效,如果您想测试自己的流,可以通过他们拥有的FFmpegMediaPlayer
来完成。
答案 2 :(得分:4)
我最近使用流音频提供程序调试了同样的问题。该问题与stagefright和32kbps及更低的流媒体源有关。我们逐步完成了相同的流媒体测量,响应时间分别为24,32,48,64和128 kbps。
这是来自每个比特率平均超过10次尝试的一致无线连接。正如特拉维斯指出的那样,关键是舞台无法弄清楚缓冲音频需要多长时间。有时候我会看到一条消息'错误:1,-21492389'左右,这似乎会让舞台上的玩家静静地崩溃。我试图追踪这一点,最终得出的结论是,非常慢的流(低于24 kbps)似乎会导致缓冲区溢出,因为它们会缓冲,直到设备用完音频流的空间。
我想补充一点,OnBufferingUpdateListener
在整个测试过程中根本没有为我开火。我不知道那是什么。我认为你能说出加载方式的唯一方法就是以与上面提到的NPR应用程序类似的方式代理加载。
答案 3 :(得分:2)
如果您要从Icecast流式传输,请查看burst-size
设置:
突发大小是突发到客户端的数据量(以字节为单位) 在连接时。就像突发连接一样,这是为了快速填充 媒体播放器使用的预缓冲器。默认值为64千字节,即a 大多数客户使用的典型尺寸通常不会改变它 需要。此设置适用于所有挂载点,除非被覆盖 装载设置。确保此值小于queue-size, 如果需要,将队列大小增加到大于您想要的大小 突发大小。如果不这样做可能会导致侦听器客户端中止 连接尝试,由于初始突发导致连接 已超过队列大小限制。
我在服务器上将burst-size
增加到了131072,现在我的基于MediaPlayer
的Android应用程序播放流没有太多延迟。
答案 4 :(得分:1)
我尝试了10个数据点,3个快速,7个慢速。 它是一致的,即快速流快速而慢速慢速。
我认为这与服务器提供的“内容长度”有关,如果没有正确指定内容长度,Android不知道缓冲多少。
可能是错的,没有像wireharking那样远。
答案 5 :(得分:0)
当我遇到这个问题时,我决定在打开播放器之前测试流是否可用。如果你让用户等待很长时间,音乐就会启动它(不是,但是我们说它没问题)。 最糟糕的情况是让他等了很长时间,音乐永远不会开始! 所以,我们有两种情况:
在无线电场景,我们可以检查端口是否正在接受连接(打开/关闭状态)。如果它是开放的准备播放器的音乐,否则根本不准备它。
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;
}
}