web-dev-qa-db-ja.com

JVM内で長時間オーディオを録音しているときの突然の遅延

JDKバージョン8アップデート201を使用して、リアルタイムで(または少なくとも可能な限りリアルタイムに近い)オーディオを記録および分析するアプリケーションを実装しています。アプリケーションの一般的な使用例をシミュレートするテストを実行しているときに、数時間連続してオーディオを録音した後、1秒から2秒の間の突然の遅延が発生しました。この時点まで、顕著な遅延はありませんでした。この遅延が発生し始めたのは、この数時間の記録の重要なポイントの後だけでした。

これまでに試したこと

オーディオサンプルの録音をタイミングするためのコードが間違っているかどうかを確認するために、タイミングに関連するすべてのものをコメント化しました。これにより、オーディオサンプルが準備ができるとすぐにフェッチする更新ループが本質的に残りました(注:Kotlinコード)。

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

これは私の読み取りメソッドです:

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

しかし、私の側からのタイミングがなくても、問題は解決されませんでした。したがって、少し実験を続けて、さまざまなオーディオ形式を使用してアプリケーションを実行すると、非常に混乱する結果になります(リトルエンディアンと44100.0 HzのサンプルレートのPCM署名付き16ビットステレオオーディオ形式を使用します)特に指定がない限り、デフォルトとして):

  1. 遅延が現れるまでに経過しなければならない臨界時間は、使用するマシンによって異なります。私のWindows 10デスクトップPCでは、6.5〜7時間程度です。私のラップトップ(Windows 10も使用)では、同じオーディオ形式で4〜5時間かかります。
  2. 使用するオーディオチャンネルの量が影響しているようです。チャンネルの量をステレオからモノに変更すると、遅延が表示されるまでの時間が2倍になり、デスクトップでは13〜13.5時間の範囲になります。
  3. サンプルサイズを16ビットから8ビットに減らすと、遅延が発生し始めるまでの時間が2倍になります。デスクトップで13〜13.5時間の間。
  4. バイトオーダーをリトルエンディアンからビッグエンディアンに変更しても効果はありません。
  5. ステレオミックスから物理的なマイクに切り替えても効果はありません。
  6. さまざまなバッファーサイズ(1024、2048、3072サンプルフレーム)とデフォルトのバッファーサイズを使用してラインを開こうとしました。これも何も変わりませんでした。
  7. TargetDataLineのフラッシュafter遅延が発生し始めた結果、すべてのバイトが約1〜2秒間ゼロになります。この後、再びゼロ以外の値を取得します。ただし、遅延はまだあります。行をフラッシュすると、beforeクリティカルポイントになりますが、これらのゼロバイトは取得されません。
  8. TargetDataLineの停止と再起動after表示される遅延も何も変更しません。
  9. ただし、TargetDataLineを閉じて再度開くと、そこから数時間後に再び表示されるまで、遅延が解消されます。
  10. TargetDataLines内部バッファーを10分ごとに自動的にフラッシュしても、問題の解決には役立ちません。したがって、内部バッファでのバッファオーバーフローは原因ではないようです。
  11. 並列ガベージコレクターを使用してアプリケーションのフリーズを回避することも役立ちません。
  12. 使用されるサンプルレートは重要なようです。サンプルレートを2倍の88200ヘルツにすると、実行時間の3〜3.5時間のどこかで遅延が発生し始めます。
  13. Linuxで「デフォルト」のオーディオ形式を使用して実行した場合、約9時間のランタイム後も問題なく動作します。

私が描いた結論:

これらの結果から、この問題が発生する前にオーディオを録音できる時間は、アプリケーションを実行しているマシンと、アプリケーションのバイトレート(フレームサイズとサンプルレート)に依存しているという結論に達しました。オーディオ形式。これはseemsを保持するためです(現時点ではこれを完全に確認することはできません)。2と3で行った変更を組み合わせると、オーディオサンプルを長い(これは26〜27時間の間です)、遅延が表示され始める前に「デフォルト」のオーディオ形式を使用する場合と同様アプリケーションをこれほど長く実行する時間を見つけられなかったので、私の側の時間の制約のため、アプリケーションを停止するまでに約15時間問題なく実行できたことがわかりました。したがって、この仮説はまだ確認または否定されています。

箇条書き13の結果によると、問題はWindowsを使用している場合にのみ発生するようです。したがって、私はthinkそれがmightであることをjavax.sound.sampled APIのプラットフォーム固有の部分のバグであると考えています。

この問題が発生し始めたときに変更する方法を見つけたかもしれませんが、結果には満足できません。この問題がまったく発生し始めるのを回避するために、定期的にラインを閉じて再度開くことができました。ただし、これを行うと、オーディオサンプルをキャプチャできない任意の短い時間が発生します。さらに、Javadocは、閉じた後、一部の行をまったく再び開くことができないと述べています。したがって、これは私の場合には良い解決策ではありません。

理想的には、この問題全体がまったく発生しないはずです。 javax.sound.sampled APIで何ができるのか、完全に欠けているものや制限があるのでしょうか?この問題を完全に取り除くにはどうすればよいですか?

編集:Xtreme Bikerとgiddsの提案により、小さなサンプルアプリケーションを作成しました。この Githubリポジトリ の中にあります。

17
Fabian B.

私は(かなり)Javaオーディオインターフェイスとの)経験を豊富に持っています。適切な解決策に導くのに役立つかもしれないいくつかのポイントを次に示します。

  1. これは、JVMバージョンの問題ではありません-Javaオーディオシステムは、Java 1.3または1.5以降、ほとんどアップグレードされていません。
  2. Javaオーディオシステムは、オペレーティングシステムが提供するオーディオインターフェイスAPIのラッパーです。Linuxでは、Pulseaudioライブラリです。Windowsの場合、直接表示オーディオAPIです(後者について誤解されていません)。
  3. 繰り返しになりますが、オーディオシステムAPIはレガシーAPIの一種です-一部の機能が動作しないか、実装されていません。他の動作は、時代遅れのデザインに依存しているため、奇妙です(必要に応じて例を提供できます)。
  4. それはガベージコレクションの問題ではありません-「遅延」の定義が私が理解しているものである場合(オーディオデータは1〜2秒遅れ、つまり1〜2秒後に音声が聞こえるようになります)、ガベージコレクター空白のデータをターゲットデータラインで魔法のようにキャプチャし、通常どおり2秒に相当するバイトオフセットでデータを追加することはできません。
  5. ここで発生している可能性が最も高いのは、ある時点で2秒相当の文字化けしたデータを提供するハードウェアまたはドライバーであり、その後、通常どおり残りのデータをストリーミングし、その結果「遅延」が発生します。
  6. Linuxで完全に機能するという事実は、ハードウェアの問題ではなく、ドライバーに関連した問題であることを意味します。
  7. その疑いを確認するために、同じ期間にFFmpeg経由でオーディオをキャプチャしてみて、問題が再現されるかどうかを確認できます。
  8. 専用のオーディオキャプチャハードウェアを使用している場合は、ハードウェアの製造元に連絡して、Windowsで直面している問題について問い合わせてください。
  9. いずれにせよ、オーディオキャプチャアプリケーションを最初から作成するときは、Javaオーディオシステムは可能な限り避けてください。POCにはいいのですが、メンテナンスされていないレガシーAPIです。 。JNAは常に実行可能なオプションです(LinuxでALSA/Pulse-audioを使用して、オーディオハードウェアの属性を制御しましたJavaオーディオシステムは変更できません)。したがって、オーディオを探すことができます。 C++ for WindowsでサンプルをキャプチャしてJavaに変換します。これにより、JVMが提供するOOTBよりもはるかに、オーディオキャプチャデバイスをきめ細かく制御できるようになります。私の JNA AACエンコーダー プロジェクトをチェックしてください。
  10. 繰り返しますが、特別なキャプチャハーダーを使用する場合、製造元がハードウェアとのインターフェイス用に独自の低レベルC apiを既に提供している可能性が高いので、それも検討することを検討する必要があります。
  11. そうでない場合は、おそらくあなたとあなたの会社/クライアントは、特別なキャプチャハードウェアの使用を検討する必要があります(それほど高価である必要はありません)。
6
Sheinbergon