web-dev-qa-db-fra.com

MediafPlayer Stutters au début de la lecture MP3

J'ai eu un problème de lecture d'un fichier MP3 stocké dans une ressource brute: lorsque le fichier commence par jouer, il génère peut-être un quart de seconde de son son et redémarre ensuite. (Je sais que cela est essentiellement un duplicata du problème décrit ici , mais la solution offerte là-bas n'a pas fonctionné pour moi.) J'ai essayé plusieurs choses et avons fait des progrès sur le problème, mais Ce n'est pas totalement corrigé.

Voici comment je me confectionne pour jouer un fichier:

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();
}

Si je quitte l'activité (qui appelle mPlayer.release()) et revenez-y (création d'un nouveau MediaPlayer), le bégaiement est généralement (mais pas toujours) parti - fourni I charger le même fichier son. J'ai essayé quelques choses qui n'ont fait aucune différence:

  • Chargez le fichier son comme un actif au lieu de ressource.
  • Créez le MediaPlayer à l'aide de MediaPlayer.create(getContext(), mAudioId) et ignorez les appels sur setDataSource(...) et prepare().

Ensuite, j'ai remarqué que Logcat montre toujours cette ligne à peu près au moment où la lecture commence:

DEBUG/AudioSink(37): bufferCount (4) is too small and increased to 12

Cela m'a été demandé si le bégaiement est dû à l'apparent réévaluant. Cela m'a conduit à essayer autre chose:

  • Après avoir appelé prepare(), appelez mPlayer.start() et appelez immédiatement mPlayer.pause().

À ma grande surprise, cela a eu un grand effet. Une grande partie du bégaiement est partie, plus aucun son (que je peux entendre) est effectivement joué à ce moment-là.

Cependant, il steille toujours de temps en temps lorsque j'appelle mPlayer.start() pour de vrai. De plus, cela semble être un énorme kludge. Y a-t-il un moyen de tuer ce problème complètement et proprement?

[~ # ~] Edit [~ # ~ ~] Plus d'infos; Je ne sais pas si connexe. Si j'appelle pause() Pendant la lecture, recherchez une position antérieure et appelez start() Encore une fois, j'entends un bit court (~ 1/4 sec) de son supplémentaire à la pause avant qu'il commence à jouer au nouveau poste. Cela semble indiquer plus de problèmes tamponnants.

De plus, les problèmes de bégaiement (et de tampon païens) apparaissent sur des émulateurs de 1,6 à 3.0.

31
Ted Hopp

Afaik Les tampons que MediafPlayer crée en interne pour stocker des échantillons décompressés, et non pour stocker des données compressées préfeteuses. Je soupçonne que votre bégaiement provient de la lenteur d'E/S alors qu'elle charge plus de données MP3 pour la décompression.

J'ai récemment dû résoudre un problème similaire avec la lecture vidéo. Merci à MediaPlayer étant incapable de jouer d'un InputStream (l'API est étrangement boiteux) La solution que j'ai proposée était de rédiger un petit service Web in-processus pour servir des fichiers locaux (sur le Carte SD) sur http. MediaPlayer puis le charge via une URI de la forme http://127.0.0.1:8888/videofilename .

Edit:

Vous trouverez ci-dessous la classe de streamproxy que j'utilise pour nourrir le contenu dans une instance MediaPlayer. L'utilisation de base est que vous l'instanciez, démarrez () et définissez votre lecteur multimédia avec quelque chose comme MediaPlayer.setDataSource("http://127.0.0.1:8888/localfilepath");

Je dois noter que c'est plutôt expérimental et probablement pas entièrement sans bug. Il a été écrit pour résoudre un problème similaire à la vôtre, à savoir que MediaPlayer ne peut pas lire un fichier qui est également téléchargé. Streaming Un fichier localement de cette manière fonctionne autour de cette restriction (c'est-à-dire que j'ai un thread téléchargeant le fichier lorsque le streamproxy l'alimente dans MediaPlayer).

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

    }
}
67
Reuben Scratton

Utiliserait prepareAsync et répondre à setOnPreparedListener vous convient mieux? En fonction de votre flux de travail d'activité, lorsque le MediaPlayer est d'abord initialisé, vous pouvez définir le écouteur de préparation , puis appelez mPlayer.prepareAsync() plus tard une fois que vous avez chargé la ressource, Puis commencez la lecture là-bas. J'utilise quelque chose de similaire, bien que pour une ressource de streaming basée sur un réseau:

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();
}

Il y a évidemment plus de solution complète (manipulation erronée, etc.) mais je pense que cela devrait fonctionner comme bon exemple pour commencer à partir de celui-ci, vous pouvez tirer le streaming de.

3
Matt Bishop