Pourquoi cela prend-il tant de temps pour Android MediaPlayer de préparer certains stream en direct pour la lecture?

Je trouve de grandes différences dans le temps nécessaire à Android MediaPlayer pour se préparer à la lecture en direct avec différents stream.

Les données en dur

J’ai ajouté la journalisation entre prepareAsync () et le rappel onPrepared (MediaPlayer mp) et testé plusieurs stream plusieurs fois chacun. Les temps pour chaque stream étaient très constants (+/- une seconde), et voici les résultats:

  1. Flux de nouvelles MPR: 27 secondes (http://newsstream1.publicradio.org:80/)
  2. Flux de musique classique MPR: 15 secondes (http://classicalstream1.publicradio.org:80/)
  3. MPR Le stream actuel: 7 secondes (http://currentstream1.publicradio.org:80/)
  4. Flux PRI: 52 secondes (http://pri-ice.streamguys.biz/pri1)

Les tests ont été effectués sur un Nexus S avec Android 2.3.4 sur une connexion 3G (~ 1100 Kbps).

La lecture de fichiers audio MP3 sans diffusion n’est pas un problème.

Voici des extraits de la façon dont je joue les stream:

Préparez MediaPlayer:

... mediaPlayer.setDataSource(playUrl); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.prepareAsync(); ... 

Puis dans onPrepared (MediaPlayer mp):

 mediaPlayer.start(); 

Pourquoi faut-il si longtemps pour préparer certains stream mais pas d’autres? Les données ci-dessus semblent suggérer qu’il pourrait être basé sur la quantité de données mises en mémoire tampon et non sur la durée du contenu audio en mémoire tampon. Serait-ce vraiment possible?

Mise à jour: J’ai testé le streaming en direct sur des appareils physiques avec Android 1.6, 2.2 et 2.3.4 et des émulateurs avec 1.6, 2.1, 2.2, 2.3.1 et 2.3.3. Je ne vois que le long délai sur 2.3.3 et 2.3.4. Les anciennes versions démarrent la lecture dans les 5 secondes.

Il semble qu’il met en mémoire tampon une quantité fixe de données plutôt qu’une durée fixe. Pour ceux qui ne connaissent pas le débit de différents types de stream NPR, les données ressemblent à ceci:

  1. Flux de nouvelles MPR: 27 secondes ( http://newsstream1.publicradio.org:80/ ), 64 kbps
  2. Flux de musique classique MPR: 15 secondes ( http://classicalstream1.publicradio.org:80/ ), 128 kbps
  3. MPR Le stream actuel: 7 secondes ( http://currentstream1.publicradio.org:80/ ), 128 kbps
  4. Flux PRI: 52 secondes ( http://pri-ice.streamguys.biz/pri1 ), 32 kbps

Outre l’écart entre les deux stream de 128 kbit / s, il existe une très bonne corrélation entre le débit binary et la durée de la mise en mémoire tampon.

Dans tous les cas, Android est open-source, vous pouvez donc toujours regarder ce qu’il fait . Malheureusement, prepareAsync() et prepare() sont des méthodes natives et il semble que les événements liés aux tampons soient également envoyés depuis un processus natif.

Avez-vous essayé d’attacher un OnBufferingUpdateListener au OnBufferingUpdateListener pour obtenir des mises à jour plus OnBufferingUpdateListener sur l’état du tampon? Il peut être intéressant de comparer le rythme auquel les événements sont livrés et le pourcentage de tampon rempli par chaque événement dans les différents stream. Vous pouvez faire une référence croisée avec le bitrate du stream, et si 4 secondes de mise en mémoire tampon à 32 kbps remplissent le tampon le même pourcentage que 1 seconde de mémoire tampon à 128 kbps, je pense que vous aurez trouvé votre réponse.

Changer MediaPlayer by FFmpegMediaPlayer fonctionne bien mieux que MediaPlayer si vous voulez tester vos stream, vous pouvez le faire via la démo qu’ils ont.

J’ai récemment débogué ce même problème avec un fournisseur de streaming audio. Le problème est lié aux sources de stagefright et de streaming de 32 kbps et moins. Nous avons franchi le même stream de temps en mesurant le temps de réponse à 24, 32, 48, 64 et 128 kbps.

  • 24 -> 46 secondes pour commencer à diffuser
  • 32 -> 24 secondes pour commencer à diffuser
  • 48 -> 2 secondes pour commencer à diffuser
  • 64 -> 2 secondes pour lancer le streaming
  • 128 -> 2 secondes pour commencer à diffuser

Ceci est dû à une connexion sans fil cohérente, calculée en moyenne sur 10 tentatives à chaque débit binary. La clé, comme l’a souligné Travis, était que stagefright ne pouvait pas déterminer combien de temps il fallait mettre en mémoire tampon l’audio. Parfois, je voyais un message “erreur: 1, -21492389” ou alors, qui semblait bloquer le lecteur stagefright en silence. J’ai essayé de le retrouver et j’ai finalement conclu que les stream très lents (moins de 24 kbps) semblaient provoquer un dépassement de tampon car ils mettaient en mémoire tampon jusqu’à ce que le périphérique manque d’espace pour le stream audio.

Je voulais append que OnBufferingUpdateListener n’a pas du tout tiré pour moi pendant tout ce test. Je ne sais pas ce que c’est là pour ça. Je pense que la seule façon de savoir comment le chargement se déroule est de transférer le chargement de manière similaire à l’application NPR mentionnée ci-dessus.

Si vous diffusez en streaming depuis Icecast, jetez un coup d’œil au paramètre de burst-size :

La taille de rafale correspond à la quantité de données (en octets) à envoyer à un client au moment de la connexion. Comme burst-on-connect, cela permet de remplir rapidement le pré-tampon utilisé par les lecteurs multimédias. La valeur par défaut est de 64 Ko, ce qui correspond à la taille habituelle utilisée par la plupart des clients. Ce paramètre s’applique à tous les points de assembly, sauf s’ils sont remplacés dans les parameters de assembly. Assurez-vous que cette valeur est inférieure à la taille de la queue, si nécessaire, augmentez la taille de la queue pour qu’elle soit supérieure à la taille de rafale souhaitée. Dans le cas contraire, les tentatives de connexion du client écouteur seront abandonnées, car la rafale initiale a déjà dépassé la limite de taille de la queue.

J’ai augmenté la burst-size de rafale à 131072 sur mon serveur, et maintenant mon application Android basée sur MediaPlayer lit les stream sans trop tarder.

Je l’ai essayé avec 10 points de données, trois rapides, 7 lents. C’est cohérent, c’est-à-dire qu’un stream rapide est rapide et qu’un stream lent est toujours lent.

Je pense que c’est lié à la longueur du contenu du serveur, Android ne sait pas combien de tamponner si la longueur du contenu n’est pas correctement spécifiée.

Pourrait être faux, ne pas aller aussi loin que wireharking.

Lorsque j’ai eu ce problème, j’ai décidé de tester si le stream est disponible avant d’ouvrir le lecteur. Si vous obligez l’utilisateur à attendre longtemps et que la musique démarre correctement (ce n’est pas le cas, mais disons que c’est correct). Le pire scénario est de le faire attendre longtemps et la musique ne démarrera jamais! Donc, nous avons 2 situations:

  • Le scénario de diffusion en direct, comme une station de radio.
  • Le fichier mp3 enregistré qui est disponible en ligne.

Au scénario radio, nous pourrions vérifier si le port accepte les connexions (état ouvert / fermé). S’il est ouvert, préparez le lecteur pour la musique, sinon ne le préparez pas du tout.

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

Au scénario de fichier mp3, les choses sont un peu différentes. Vous devez vérifier le code de réponse qui suit la requête 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; } }