English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Javacvでffmpegを使用して音声と動画の同期再生を実現する

最近、javaCVのffmpegパッケージのFFmpegFrameGrabberフレームキャプチャーを使って、キャプチャしたオーディオフレームとビデオフレームを同期再生しました。同期方法はビデオからオーディオへの同期です。

プログラムとソースコード

具体的な考え方は以下の通りです:

)1まず、ffmpegがビデオファイルの画像と音声をどうキャプチャするかを紹介します。

FFmpegFrameGrabber fg = new FFmpegFrameGrabber("a video file path or a url"); 

フレームキャプチャーオブジェクトを取得した後、そのgrab()メソッドを呼び出すと、キャプチャしたFrameオブジェクトが返されます。このFrameはビデオフレームまたはオーディオフレームであり、これは音声とビデオフレームが再生時間に基づいてタイムスタンプの順に並べられたためです。もちろん、キャプチャしたフレームは既にデコードされており、java.nio.Bufferオブジェクトに保存されています。ビデオフレームの場合、BufferはRGBなどのピクセルデータを保存し、

BufferedImage bi = (new Java2DFrameConverter()).getBufferedImage(f); 

画像を取得でき、取得した画像は一連の処理を行うか、直接Swingコンポーネント上に表示することができます。オーディオフレームに対して、BufferはPCMデータを保存するもので、floatまたはshortのものが使用されます。それから、java.sounds.sampleのsourceDataLine.writeメソッドを使って、これらのPCMデータをスピーカーに書き込むことができます。

)2}}

)続いて、取得したフレームを再生し続ける方法について説明します。まずはビデオを単独で再生する場合: 
while(true) 
  {  
  Frame f = fg.grab(); 
  if(f.image!=null)2label.setIcon(new ImageIcon((new Java 
  DFrameConverter()).getBufferedImage(f)));10Thread.sleep(/00  
ビデオフレームレート); 

}

)3オーディオを単独で再生する場合も同様に、データをサウンドカードに書き込めば良いです。例

生産者・消費者モデル。

)4上の図はプログラムの実装方法です。キャプチャされたフレームを判定し、ビデオフレームの場合はビデオFIFOに、オーディオフレームの場合はオーディオFIFOに生産します。その後、オーディオ再生スレッドとビデオ再生スレッドがそれぞれのフレームストアからフレームを消費します。生産者・消費者モデルを選んだのは、フレームのキャプチャ速度がフレームの消費速度を上回るため、キャプチャされたフレームを優先してバッファリングし、またはさらにキャプチャされたフレームを事前処理するためです。ビデオとオーディオ再生スレッドは、処理されたフレームを直接再生表示するだけで十分です。

音声とビデオの同期を実現する方法:二つのオーディオフレーム内のすべてのビデオフレームを再生します。

の実装は上の図に基づいています、オーディオスレッドがオーディオフレームAを再生し始めたとき、音声とビデオの同期を実現するには、フレームのタイムスタンプが必要です。ここで捕獲されたフレームには再生のタイムスタンプPTSがありますが、コーデックのタイムスタンプDTSはありませんので、再生のタイムスタンプに基づいて再生を決定するだけで十分です。1のとき、ビデオスレッドのsetRunメソッドを呼び出し、現在再生するオーディオフレームのタイムスタンプcurTimeと次のオーディオフレームAを渡します。2のタイムスタンプnextTimeをwait状態のビデオスレッドに渡し、ビデオスレッドが起動し、ビデオFIFOからビデオフレームGを取り出し始めます。1、それからG1とA1の時間差を再生の遅延として、Thread.sleep(t)1が終わった後、ビデオスレッドは画像をSwingコンポーネント上に表示します、例えばJLabel.setIcon(image)。その後、ビデオスレッドはもう一つの画像Gを取り出します。2を比較します。2のタイムスタンプとA2のタイムスタンプがG2のタイムスタンプがA2、その場合、ビデオスレッドはtの遅延を続けます。2以降、このGを再生します。2の画像が続きます、それからG3と同様に、Gを取得するまで繰り返します。4、そしてA2を比較するとG4のタイムスタンプがA2、その場合、ビデオスレッドはwait状態に入り、次の起動を待ちます。その後、オーディオスレッドがAを再生終了します。1のオーディオフレーム以降、倉庫からオーディオフレームAを取り出します。3、それからA2のタイムスタンプとA3のタイムスタンプがビデオスレッドに渡され、それからAを再生し始めます。2、それから、塞ぎ込まれたビデオスレッドも同様に再生を続けます。

)5)動的調整遅延時間

由于个人PC都不是实时操作系统,也就是Thread.sleep是不精确的,并且受到声卡播放声音的制约,所以上面的基本实现思路是需要加以完善的。首先java的sourceDataLine的方法是依照一定的速度从内部缓冲区取出音频线程写入的数据,如果音频写入的数据被取光了,那么音频播放就会发生卡顿,但是如果一次音频数据写入过多,那么就会发生音视频可能就会不同步,所以要确保sourceDataLine的内部缓冲区是留有一定数据的,否则就会造成卡顿,但是数据量又不能过多,所以我们在G3からA2個人のPCはリアルタイムオペレーティングシステムではありません。つまり、Thread.sleepは不確実であり、サウンドカードの音再生に制約されています。したがって、上記の基本的な実現方法は改善する必要があります。まず、javaのsourceDataLineメソッドは内部バッファから一定の速度でオーディオスレッドが書き込んだデータを取り出します。もしオーディオデータが完全に取り去られた場合、オーディオ再生は途切れることがありますが、一度に多くのオーディオデータが書き込まれた場合、音声とビデオが同期しなくなる可能性があります。したがって、sourceDataLineの内部バッファには一定のデータを残しておく必要があります。そうしないと途切れることがありますが、データ量が多すぎると途切れることがあります。したがって、G1この期間に音声再生の調整を行いますが、遅延の不確実性により、書き込まれたA6が完全に取り去られる可能性がありますので、Gを再生した後のフレームのデータが時間が満たされていない場合があります。3画像が読み込まれた後、音声スレッドはsourceDataLine.available()が返すデータ量に基づいて判断し、データ量がほぼ尽きると、Gを減らします。3からA2の遅延時間t4これにより、データ量が0にされ、音が途切れることがないことを保証できます。

)6)以下はプログラムのwindow64以下のテストとubuntu14以下のテスト結果の図:再生は非常にスムーズで、同期も問題ありませんが、再生中にIDEAなどのIDE(インテリジェント開発環境)でコードを書くと遅延が発生します。なぜならIDEAはJavaで開発されているからです。したがって、IDEAの実行は他のJavaプログラムに影響を与える可能性がありますが、他のプロセスには影響を与えません。


これでこの記事のすべての内容が終わります。皆様の学習に役立つことを願っています。また、ナイアラガイドのサポートを多くいただければ幸いです。

声明:この記事の内容はインターネットから取得しており、著作権者に帰属します。インターネットユーザーにより自発的に貢献し、アップロードされたコンテンツであり、このサイトは所有権を持ちません。また、人工編集は行われておらず、関連する法的責任も負いません。著作権侵害が疑われる内容がある場合は、以下のメールアドレスまでご連絡ください:notice#oldtoolbag.com(メールを送信する際は、#を@に置き換えてください。報告を行い、関連する証拠を提供してください。一旦確認が取れましたら、このサイトは即座に侵害される可能性のあるコンテンツを削除します。)

おすすめ