web-dev-qa-db-fra.com

Retard soudain lors de l'enregistrement audio sur de longues périodes à l'intérieur de la JVM

J'implémente une application qui enregistre et analyse l'audio en temps réel (ou au moins aussi près du temps réel que possible), en utilisant la mise à jour 201 du JDK version 8. Lors d'un test qui simule des cas d'utilisation typiques de l'application, j'ai remarqué qu'après plusieurs heures d'enregistrement audio en continu, un retard soudain de une à deux secondes a été introduit. Jusqu'à ce point, il n'y avait pas de retard notable. Ce n'est qu'après ce point critique d'enregistrement pendant plusieurs heures que ce retard a commencé à se produire.

Ce que j'ai essayé jusqu'à présent

Pour vérifier si mon code de synchronisation de l'enregistrement des échantillons audio est incorrect, j'ai commenté tout ce qui concerne la synchronisation. Cela m'a laissé essentiellement avec cette boucle de mise à jour qui récupère les échantillons audio dès qu'ils sont prêts (Remarque: code Kotlin):

while (!isInterrupted) {
    val audioData = read(sampleSize, false)
    listener.audioFrameCaptured(audioData)
}

Voici ma méthode de lecture:

fun read(samples: Int, buffered: Boolean = true): AudioData {
    //Allocate a byte array in which the read audio samples will be stored.
    val bytesToRead = samples * format.frameSize
    val data = ByteArray(bytesToRead)

    //Calculate the maximum amount of bytes to read during each iteration.
    val bufferSize = (line.bufferSize / BUFFER_SIZE_DIVIDEND / format.frameSize).roundToInt() * format.frameSize
    val maxBytesPerCycle = if (buffered) bufferSize else bytesToRead

    //Read the audio data in one or multiple iterations.
    var bytesRead = 0
    while (bytesRead < bytesToRead) {
        bytesRead += (line as TargetDataLine).read(data, bytesRead, min(maxBytesPerCycle, bytesToRead - bytesRead))
    }

    return AudioData(data, format)
}

Cependant, même sans aucun timing de ma part, le problème n'a pas été résolu. Par conséquent, j'ai continué à expérimenter un peu et à laisser l'application s'exécuter en utilisant différents formats audio, ce qui conduit à des résultats très confus (je vais utiliser un format audio stéréo 16 bits signé PCM avec peu d'endian et une fréquence d'échantillonnage de 44100,0 Hz par défaut, sauf indication contraire):

  1. Le temps critique qui doit s'écouler avant que le délai n'apparaisse semble être différent selon la machine utilisée. Sur mon ordinateur de bureau Windows 10, cela se situe entre 6,5 et 7 heures. Sur mon ordinateur portable (utilisant également Windows 10), cependant, il se situe entre 4 et 5 heures pour le même format audio.
  2. La quantité de canaux audio utilisés semble avoir un effet. Si je change la quantité de canaux de stéréo en mono, le temps avant que le délai ne commence à apparaître est doublé entre 13 et 13,5 heures sur mon bureau.
  3. La diminution de la taille de l'échantillon de 16 bits à 8 bits entraîne également un doublement du temps avant que le retard ne commence à apparaître. Quelque part entre 13 et 13,5 heures sur mon bureau.
  4. Changer l'ordre des octets de petit endian en gros endian n'a aucun effet.
  5. Passer du stéréomix à un microphone physique n'a aucun effet non plus.
  6. J'ai essayé d'ouvrir la ligne en utilisant différentes tailles de tampon (1024, 2048 et 3072 échantillons de trames) ainsi que sa taille de tampon par défaut. Cela n'a rien changé non plus.
  7. Le vidage de TargetDataLine après le retard a commencé à se produire, tous les octets étant nuls pendant environ une à deux secondes. Après cela, j'obtiens à nouveau des valeurs non nulles. Le retard, cependant, est toujours là. Si je vide la ligne avant le point critique, je n'obtiens pas ces octets zéro.
  8. L'arrêt et le redémarrage de TargetDataLine après le retard apparu ne change rien non plus.
  9. La fermeture et la réouverture de TargetDataLine, cependant, supprime le délai jusqu'à ce qu'il réapparaisse après plusieurs heures à partir de là.
  10. Le vidage automatique du tampon interne TargetDataLines toutes les dix minutes n'aide pas à résoudre le problème. Par conséquent, un débordement de tampon dans le tampon interne ne semble pas en être la cause.
  11. L'utilisation d'un ramasse-miettes parallèle pour éviter le gel des applications n'aide pas non plus.
  12. Le taux d'échantillonnage utilisé semble être important. Si je double la fréquence d'échantillonnage à 88200 Hertz, le retard commence à se produire quelque part entre 3 et 3,5 heures d'exécution.
  13. Si je le laisse s'exécuter sous Linux en utilisant mon format audio "par défaut", il fonctionne toujours bien après environ 9 heures d'exécution.

Conclusions que j'ai tirées:

Ces résultats me permettent de conclure que la durée pendant laquelle je peux enregistrer de l'audio avant que ce problème ne se produise dépend de la machine sur laquelle l'application est exécutée et dépend du taux d'octets (c'est-à-dire la taille de la trame et la fréquence d'échantillonnage) du format audio. Cela semble vrai (bien que je ne puisse pas le confirmer complètement pour l'instant) car si je combine les modifications apportées en 2 et 3, je supposons que je puisse enregistrer des échantillons audio quatre fois plus longtemps (ce qui se situerait entre 26 et 27 heures) que lorsque j'utilise mon format audio "par défaut" avant que le délai ne commence à apparaître. Comme je n'ai pas encore trouvé le temps de laisser l'application s'exécuter aussi longtemps, je peux seulement dire qu'elle s'est bien déroulée pendant environ 15 heures avant de devoir l'arrêter en raison de contraintes de temps de mon côté. Cette hypothèse reste donc à confirmer ou à infirmer.

Selon le résultat du point 13, il semble que tout le problème n'apparaisse que lorsque vous utilisez Windows. Par conséquent, je pense qu'il pourrait être un bug dans la plate-forme spécifique parties de l'API javax.sound.sampled.

Même si je pense que j'ai pu trouver un moyen de changer lorsque ce problème commence à se produire, je ne suis pas satisfait du résultat. Je pourrais périodiquement fermer et rouvrir la ligne pour éviter que le problème ne commence à apparaître. Cependant, cela entraînerait un peu de temps arbitraire où je ne serais pas en mesure de capturer des échantillons audio. De plus, le Javadoc indique que certaines lignes ne peuvent plus être rouvertes après avoir été fermées. Par conséquent, ce n'est pas une bonne solution dans mon cas.

Idéalement, tout ce problème ne devrait pas se produire du tout. Y a-t-il quelque chose qui me manque complètement ou suis-je confronté à des limites de ce qui est possible avec l'API javax.sound.sampled? Comment puis-je me débarrasser de ce problème?

Edit: Par suggestion de Xtreme Biker et gidds, j'ai créé un petit exemple d'application. Vous pouvez le trouver dans ce dépôt Github .

17
Fabian B.

J'ai (une) assez grande expérience avec Java interface audio. Voici quelques points qui peuvent être utiles pour vous guider vers une solution appropriée:

  1. Ce n'est pas une question de version JVM - le système audio Java Java a à peine été mis à niveau depuis Java 1.3 ou 1.5
  2. Le système audio Java est un habillage du pauvre autour de l'API d'interface audio que le système d'exploitation a à offrir. Sous Linux, c'est la bibliothèque Pulseaudio, pour Windows, c'est l'API audio show direct (si je ne me trompe pas sur ce dernier).
  3. Encore une fois, l'API du système audio est une sorte d'API héritée - certaines fonctionnalités ne fonctionnent pas ou ne sont pas implémentées, d'autres comportements sont tout simplement étranges, car ils dépendent d'une conception obsolète (je peux fournir des exemples, si nécessaire).
  4. Ce n'est pas une question de collecte des ordures - Si votre définition de "retard" est ce que je comprends (les données audio sont retardées de 1 à 2 secondes, ce qui signifie que vous commencez à entendre des choses 1 à 2 secondes plus tard), eh bien, le garbage collector ne peut pas provoquer la capture magique de données vides par la ligne de données cible, puis ajouter les données comme d'habitude dans un décalage de 2 secondes.
  5. Ce qui se passe le plus probablement ici, c'est le matériel ou le pilote qui vous fournit 2 secondes de données tronquées à un moment donné, puis diffuse le reste des données comme d'habitude, ce qui entraîne le "retard" que vous rencontrez.
  6. Le fait qu'il fonctionne parfaitement sous Linux signifie qu'il ne s'agit pas d'un problème matériel, mais plutôt d'un problème lié au pilote.
  7. Pour affirmer cette suspicion, vous pouvez essayer de capturer l'audio via FFmpeg pour la même durée et voir si le problème est reproduit.
  8. Si vous utilisez du matériel de capture audio spécialisé, mieux vaut approcher le fabricant de votre matériel et lui poser des questions sur le problème que vous rencontrez sur Windows.
  9. Dans tous les cas, lors de l'écriture d'une application de capture audio à partir de zéro, je suggère fortement de rester à l'écart du système audio Java si possible. C'est bien pour les POC, mais c'est une API héritée non maintenue . JNA est toujours une option viable (je l'ai utilisé sous Linux avec ALSA/Pulse-audio pour contrôler les attributs matériels audio du Java ne peut pas être modifié), vous pouvez donc rechercher de l'audio capturer des exemples en C++ pour Windows et les traduire en Java. Cela vous donnera un contrôle précis sur les périphériques de capture audio, bien plus que ce que la JVM fournit OOTB. Si vous voulez voir un exemple de JNA utilisable vivant/respirant, consultez mon encodeur JNA AAC projet.
  10. Encore une fois, si vous utilisez un harwdare de capture spécial, il y a de fortes chances que le fabricant fournisse déjà sa propre API C de bas niveau pour l'interfaçage avec le matériel, et vous devriez également envisager de le consulter.
  11. Si ce n'est pas le cas, vous et votre entreprise/client devriez peut-être envisager d'utiliser du matériel de capture spécialisé (cela ne doit pas être si cher).
6
Sheinbergon