MediaPlayer заикается в начале воспроизведения mp3
У меня возникла проблема с воспроизведением mp3-файла, хранящегося на необработанном ресурсе: когда файл впервые начинает воспроизводиться, он генерирует примерно четверть секунды звука, а затем перезапускается. (Я знаю, что это в основном дубликат проблемы, описанной здесь, но предложенное там решение не сработало для меня.) Я попробовал несколько вещей и добился некоторого прогресса в этой проблеме, но она не полностью исправлена.
Вот как я настраиваюсь для воспроизведения файла:
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, используя
MediaPlayer.create(getContext(), mAudioId)
и пропустить звонкиsetDataSource(...)
а такжеprepare()
,
Затем я заметил, что LogCat всегда показывает эту строку примерно в то время, когда начинается воспроизведение:
DEBUG/AudioSink(37): bufferCount (4) is too small and increased to 12
Мне стало интересно, происходит ли заикание из-за явного отпора. Это заставило меня попробовать что-то еще:
- После звонка
prepare()
, вызовmPlayer.start()
и сразу же позвонитьmPlayer.pause()
,
К моему приятному удивлению, это имело большой эффект. Большая часть заикания ушла, плюс в тот момент процесса не воспроизводился звук (который я слышу).
Тем не менее, он по-прежнему заикается время от времени, когда я звоню mPlayer.start()
серьезно. Плюс, это похоже на огромный клудж. Есть ли способ убить эту проблему полностью и чисто?
РЕДАКТИРОВАТЬ Больше информации; не уверен, если связано. Если я позвоню pause()
во время воспроизведения найдите предыдущую позицию и позвоните start()
снова я слышу короткий (~1/4 сек) дополнительный звук от того места, где он был приостановлен, прежде чем он начнет играть на новой позиции. Это, кажется, указывает на большее количество проблем с буферизацией.
Кроме того, проблемы с заиканием (и приостановленным буфером) обнаруживаются на эмуляторах с 1.6 по 3.0.
2 ответа
AFAIK буферы, которые MediaPlayer создает внутри, предназначены для хранения распакованных выборок, а не для хранения предварительно выбранных сжатых данных. Я подозреваю, что ваше заикание происходит из-за медлительности ввода-вывода, поскольку он загружает больше данных MP3 для декомпрессии.
Недавно мне пришлось решить аналогичную проблему с воспроизведением видео. Благодаря MediaPlayer
будучи не в состоянии играть произвольно InputStream
(API странно хромает), решение, которое я придумал, было написать небольшой внутрипроцессный веб-сервер для обслуживания локальных файлов (на SD-карте) по HTTP. MediaPlayer
затем загружает его через URI вида http://127.0.0.1:8888/videofilename.
РЕДАКТИРОВАТЬ:
Ниже приведен класс StreamProxy, который я использую для подачи контента в экземпляр MediaPlayer. Основное использование заключается в том, что вы создаете его экземпляр, запускаете его и запускаете медиаплеер с чем-то вроде 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;
}
}
}
Будет использовать prepareAsync
и отвечая на setOnPreparedListener
тебе лучше подойдет? В зависимости от вашего рабочего процесса, когда MediaPlayer
сначала инициализируется, вы можете установить слушателя подготовки, а затем вызвать 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();
}
Очевидно, есть еще что-то для полного решения (обработка ошибок и т. Д.), Но я думаю, что это должно послужить хорошим примером, чтобы начать с того, из чего вы можете вытянуть поток.