MediaPlayer 在 mp3 播放开始时卡顿
Posted
技术标签:
【中文标题】MediaPlayer 在 mp3 播放开始时卡顿【英文标题】:MediaPlayer stutters at start of mp3 playback 【发布时间】:2011-07-17 15:43:51 【问题描述】:我在播放存储在原始资源中的 mp3 文件时遇到问题:当文件第一次开始播放时,它可能会产生四分之一秒的声音,然后重新启动。 (我知道这基本上是here 描述的问题的重复,但那里提供的解决方案对我没有用。)我已经尝试了几件事并在这个问题上取得了一些进展,但并不完全固定。
这是我设置播放文件的方式:
mPlayer.reset();
try
AssetFileDescriptor afd = getResources().openRawResourceFd(mAudioId);
if (afd == null)
Toast.makeText(mOwner, "Could not load sound.",
Toast.LENGTH_LONG).show();
return;
mPlayer.setDataSource(afd.getFileDescriptor(),
afd.getStartOffset(), afd.getLength());
afd.close();
mPlayer.prepare();
catch (Exception e)
Log.d(LOG_TAG, "Could not load sound.", e);
Toast.makeText(mOwner, "Could not load sound.", Toast.LENGTH_LONG)
.show();
如果我退出活动(调用 mPlayer.release()
)并返回到它(创建一个新的 MediaPlayer),通常(但并非总是)口吃消失了——假设我加载了相同的声音文件。我尝试了一些没有区别的方法:
MediaPlayer.create(getContext(), mAudioId)
创建MediaPlayer,并跳过对setDataSource(...)
和prepare()
的调用。
然后我注意到 LogCat 总是在播放开始时显示这一行:
DEBUG/Audiosink(37): bufferCount (4) is too small and increased to 12
这让我想知道口吃是否是由于明显的缓冲造成的。这让我尝试了其他方法:
拨打prepare()
后,拨打mPlayer.start()
,立即拨打mPlayer.pause()
。
令我惊喜的是,这产生了很大的影响。大部分的口吃消失了,而且在这个过程中实际上没有声音(我能听到)播放。
但是,当我真正拨打mPlayer.start()
时,它仍然会时不时地结结巴巴。另外,这似乎是一个巨大的kludge。有没有什么办法可以彻底彻底地解决这个问题?
编辑更多信息;不确定是否相关。如果我在播放过程中调用pause()
,寻找到更早的位置,然后再次调用start()
,我会在新位置开始播放之前从暂停的位置听到一小段(~1/4 秒)的额外声音.这似乎指向更多的缓冲问题。
此外,从 1.6 到 3.0 的模拟器上会出现卡顿(和暂停缓冲区)问题。
【问题讨论】:
对于那些有美好回忆的人,像这样调用start()
和pause()
可能会让您想起我们曾经强制在网页中预加载AudioClip
的技巧小程序。 :-)
可能与声音文件的编码有关,也可能与声音文件的长度有关。您是否尝试过将代码与不同的声音文件一起使用?
@Joseph - 这发生在四个文件中,长度从 56 秒到刚刚超过 4 分钟不等。每个都是使用 MPEG 音频层 1/2/3 (mpga)、立体声、44,100hz 采样率、128kb/s 比特率的单个音频流。它们是使用 GarageBand 5.1 生成的。我尝试使用 Audacity 1.3 重新保存文件。不用找了。这些文件在我电脑上的媒体播放器中播放良好。
MP3 文件是否在默认的 android 媒体播放器或其他 Android 媒体播放器应用程序中播放。如果是这样,那么您似乎已经排除了文件的编码/比特率。如果没有,请尝试使用 Android 附带的一些 MP3。
@Joseph - 当我将文件放在 sd 卡上并使用音乐应用程序播放时,文件播放正常。
【参考方案1】:
AFAIK MediaPlayer 在内部创建的缓冲区用于存储解压缩的样本,而不是用于存储预取的压缩数据。我怀疑您的卡顿来自 I/O 缓慢,因为它会加载更多 MP3 数据进行解压。
我最近不得不解决一个类似的视频播放问题。感谢MediaPlayer
无法播放任意InputStream
(API 奇怪地蹩脚),我想出的解决方案是编写一个小型进程内网络服务器,用于通过 HTTP 提供本地文件(在 SD 卡上)。 MediaPlayer
然后通过 http://127.0.0.1:8888/videofilename 形式的 URI 加载它。
编辑:
下面是我用来向 MediaPlayer 实例提供内容的 StreamProxy 类。基本用途是您实例化它,启动()它,并设置您的媒体播放器使用类似MediaPlayer.setDataSource("http://127.0.0.1:8888/localfilepath");
我应该注意到它是相当实验性的,可能并非完全没有错误。它是为解决与您类似的问题而编写的,即 MediaPlayer 无法播放也正在下载的文件。以这种方式在本地流式传输文件可以解决该限制(即,当 StreamProxy 将文件馈送到媒体播放器时,我有一个线程下载文件)。
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import android.os.AsyncTask;
import android.os.Looper;
import android.util.Log;
public class StreamProxy implements Runnable
private static final int SERVER_PORT=8888;
private Thread thread;
private boolean isRunning;
private ServerSocket socket;
private int port;
public StreamProxy()
// Create listening socket
try
socket = new ServerSocket(SERVER_PORT, 0, InetAddress.getByAddress(new byte[] 127,0,0,1));
socket.setSoTimeout(5000);
port = socket.getLocalPort();
catch (UnknownHostException e) // impossible
catch (IOException e)
Log.e(TAG, "IOException initializing server", e);
public void start()
thread = new Thread(this);
thread.start();
public void stop()
isRunning = false;
thread.interrupt();
try
thread.join(5000);
catch (InterruptedException e)
e.printStackTrace();
@Override
public void run()
Looper.prepare();
isRunning = true;
while (isRunning)
try
Socket client = socket.accept();
if (client == null)
continue;
Log.d(TAG, "client connected");
StreamToMediaPlayerTask task = new StreamToMediaPlayerTask(client);
if (task.processRequest())
task.execute();
catch (SocketTimeoutException e)
// Do nothing
catch (IOException e)
Log.e(TAG, "Error connecting to client", e);
Log.d(TAG, "Proxy interrupted. Shutting down.");
private class StreamToMediaPlayerTask extends AsyncTask<String, Void, Integer>
String localPath;
Socket client;
int cbSkip;
public StreamToMediaPlayerTask(Socket client)
this.client = client;
public boolean processRequest()
// Read HTTP headers
String headers = "";
try
headers = Utils.readTextStreamAvailable(client.getInputStream());
catch (IOException e)
Log.e(TAG, "Error reading HTTP request header from stream:", e);
return false;
// Get the important bits from the headers
String[] headerLines = headers.split("\n");
String urlLine = headerLines[0];
if (!urlLine.startsWith("GET "))
Log.e(TAG, "Only GET is supported");
return false;
urlLine = urlLine.substring(4);
int charPos = urlLine.indexOf(' ');
if (charPos != -1)
urlLine = urlLine.substring(1, charPos);
localPath = urlLine;
// See if there's a "Range:" header
for (int i=0 ; i<headerLines.length ; i++)
String headerLine = headerLines[i];
if (headerLine.startsWith("Range: bytes="))
headerLine = headerLine.substring(13);
charPos = headerLine.indexOf('-');
if (charPos>0)
headerLine = headerLine.substring(0,charPos);
cbSkip = Integer.parseInt(headerLine);
return true;
@Override
protected Integer doInBackground(String... params)
long fileSize = GET CONTENT LENGTH HERE;
// Create HTTP header
String headers = "HTTP/1.0 200 OK\r\n";
headers += "Content-Type: " + MIME TYPE HERE + "\r\n";
headers += "Content-Length: " + fileSize + "\r\n";
headers += "Connection: close\r\n";
headers += "\r\n";
// Begin with HTTP header
int fc = 0;
long cbToSend = fileSize - cbSkip;
OutputStream output = null;
byte[] buff = new byte[64 * 1024];
try
output = new BufferedOutputStream(client.getOutputStream(), 32*1024);
output.write(headers.getBytes());
// Loop as long as there's stuff to send
while (isRunning && cbToSend>0 && !client.isClosed())
// See if there's more to send
File file = new File(localPath);
fc++;
int cbSentThisBatch = 0;
if (file.exists())
FileInputStream input = new FileInputStream(file);
input.skip(cbSkip);
int cbToSendThisBatch = input.available();
while (cbToSendThisBatch > 0)
int cbToRead = Math.min(cbToSendThisBatch, buff.length);
int cbRead = input.read(buff, 0, cbToRead);
if (cbRead == -1)
break;
cbToSendThisBatch -= cbRead;
cbToSend -= cbRead;
output.write(buff, 0, cbRead);
output.flush();
cbSkip += cbRead;
cbSentThisBatch += cbRead;
input.close();
// If we did nothing this batch, block for a second
if (cbSentThisBatch == 0)
Log.d(TAG, "Blocking until more data appears");
Thread.sleep(1000);
catch (SocketException socketException)
Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly");
catch (Exception e)
Log.e(TAG, "Exception thrown from streaming task:");
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
e.printStackTrace();
// Cleanup
try
if (output != null)
output.close();
client.close();
catch (IOException e)
Log.e(TAG, "IOException while cleaning up streaming task:");
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
e.printStackTrace();
return 1;
【讨论】:
有趣。您是否建议我将 mp3 文件解码为未压缩的文件(如 PCM/WAVE)并将其提供给 MediaPlayer? 一点也不,我建议您将部分或全部压缩数据预加载到内存中。我认为你的问题是,当你简单地给 MediaPlayer 一个 FileDescriptor 时,它没有缓冲足够的数据来输入 MP3 解压缩器......你会遇到缓冲区不足,因此会卡顿。 啊。这可以解释为什么在开始时调用 start() 然后 pause() 的技巧会产生效果。我推测它不是完全有效的,因为对 pause() 的调用来得太早了,所以缓冲区没有机会填满。我用调用 seekTo(500) 替换了开始/暂停序列(并在 onSeekComplete 上做了一点改动),现在口吃似乎已经消失了! 很高兴您找到了出路。如果您后来发现需要高级缓冲解决方案(即在流程中将文件通过 HTTP 流式传输到 MediaPlayer),请告诉我,我会将代码发送给您。 不能这样做,因为我不再使用这种 hacky 技术了。这些天来,我完全避免使用 MediaPlayer,因为它塞满了错误。我用一个功能强大的自定义 Java 包装器替换它的 FFmpeg 端口,除其他外,它允许您播放任意 InputStreams。【参考方案2】:使用prepareAsync
并回复setOnPreparedListener
会更适合您吗?根据您的活动工作流程,当MediaPlayer
首次初始化时,您可以设置preparation listener,然后在实际加载资源后调用mPlayer.prepareAsync()
,然后在那里开始播放。我使用类似的东西,尽管是基于网络的流媒体资源:
MediaPlayer m_player;
private ProgressDialog m_progressDialog = null;
...
try
if (m_player != null)
m_player.reset();
else
m_player = new MediaPlayer();
m_progressDialog = ProgressDialog
.show(this,
getString(R.string.progress_dialog_please_wait),
getString(R.string.progress_dialog_buffering),
true);
m_player.setOnPreparedListener(this);
m_player.setAudioStreamType(AudioManager.STREAM_MUSIC);
m_player.setDataSource(someSource);
m_player.prepareAsync();
catch (Exception ex)
...
public void onPrepared(MediaPlayer mp)
if (m_progressDialog != null && m_progressDialog.isShowing())
m_progressDialog.dismiss();
m_player.start();
一个完整的解决方案显然还有更多功能(错误处理等),但我认为这应该是一个很好的例子,你可以从中提取流。
【讨论】:
我会试试这个并报告效果。我不抱太大希望,因为问题不在准备的时候;这似乎是在调用start()
之后完成的处理。但是,我将是第一个承认我并不完全了解 MediaPlayer 内部工作原理的人。
我现在已经尝试过这种变体,但对问题没有任何影响。
你能分享一些关于正在播放的文件的细节吗?比特率是多少?使用什么编码器构建 MP3 容器?我意识到这是一种解决方法,但也许您只是期望过高并且设备无法将数据推出,因此较低的比特率可能会解决您的问题。另外,这是在模拟器中吗?问题是否出现在不同的设备上?
@Matt - 涉及四个文件。它们由 GarageBand 5.1 创建,采样率为 44,100hz。三个的比特率为 128kbs,每个大约一分钟长。第四个,大约 4 分钟,是 86kbs。最初的卡顿后文件播放流畅(实际上,更像是重新启动),所以我想知道比特率可能是什么问题。
我试图找到Android的MP3播放源,但没有成功。一些编码器并不总是以最有效的方式创建容器,也许 Android 的 MP3 播放模块会暂时挂起。也许您可以尝试将声音文件导出为纯 PCM,然后使用其他编码器(如 LAME)来查看是否会产生任何播放差异。您还可以用半秒钟的静音填充音轨,这样就不会听到口吃。我意识到这些是解决方法,但 MediaPlayer 在这一点上似乎无法改变;缓冲区无法调整。以上是关于MediaPlayer 在 mp3 播放开始时卡顿的主要内容,如果未能解决你的问题,请参考以下文章
赛马林。加载 mp3 以通过 MediaPlayer 播放时遇到问题:FileNotFoundException
MediaPlayer播放在线MP3资源时报出java.io.IOException: Prepare failed.: status=0x1异常