Qt は ffmpeg を使用してオーディオとビデオの同期を実現します

1 はじめに

ffmpeg を使ってオーディオとビデオの同期を行う, 個人的には ffmpeg の基本的な処理の中でこれが最も難しいと思います. 無数の人々がここで立ち往生しており, 許可されていません. 私もインターネットでさまざまなデモを試しました. 基本的には.すべてのスカム、または非常に少数のビデオ ファイルしかサポートしていない. たとえば、受信したデータ パケットが 1 フレームのビデオと 1 フレームのオーディオである、またはまったく同期できない、または進行状況がスキップして直接クラッシュする.実際、最も完璧なオーディオとビデオ 同期処理のデモは ffplay です. 私は個人的に数十のさまざまなローカル オーディオとビデオ ファイル、および数十のビデオ ストリーミング ファイルをテストしましたが、それらはすべて完璧です. もちろん、これは私自身のものです.

ビデオ ストリームのみを (オーディオ ストリームなしで) 再生している場合、オーディオとビデオの同期は必要ない可能性があります。 rtmp、http、m3u8 などのビデオ ストリームを植える場合、問題は深刻です一度にやってくるのは hls 形式のビデオ ストリーム ファイルであり、小さなビデオ ファイルが次々とやってくる. 同期がない場合, それは突然、過去にたくさんの写真があったことを意味します, そして次にそれらが来るとき, それらは自分で計算して同期する必要があります. 前回受信したデータパケットはキューに入れられます. 、表示する必要があるときに表示されます。

一般的なオーディオとビデオの同期方法:

fps によって制御されます. fps は 1 秒間に何フレーム再生されるかを示します.たとえば 25 フレーム. 1 フレームをデコードするのにかかる時間は自分で計算できます. 1 フレームは (1000/25=40 ミリ秒) かかります.遅延で処理されますが、これは実は最悪の方法です。startTime のデコードを開始する時間を覚えておいて、av_rescale_q で pts 時間を計算します。この 2 つの差は、遅延する必要がある時間です。av_usleep を呼び出して遅延させます。一部のファイルのみが正常であり、多くの場合、正常ではありません。オーディオはビデオに同期され、ビデオクロックがメインクロックとして使用されます.私はそれを試していません.インターネット上で多くの人がこの方法は良くないと言っています. 映像と音声を同期させて、音声クロックをメインクロックにする、私は試していませんが、ほとんどの人がこの方法を使っていると言われています。オーディオとビデオは外部クロックに同期し、外部クロックをメインクロックとして使用し、最終的に採用された方法は、相互に干渉せず、外部クロックに従ってそれぞれが同期する分かりやすいものです。ffplay 自体には 3 つの組み込みの同期戦略があり、パラメーターで制御できます. デフォルトでは、ビデオをオーディオに同期します。

この記事の特典として、 Qt 開発学習教材パッケージ、テクニカル ビデオ (C++ 言語の基礎、C++ デザイン パターン、Q​​t プログラミング入門、QT シグナルとスロット メカニズム、QT インターフェイス開発イメージ図、QT ネットワーク、QT を含む) を無料で受け取ることができます。データベースプログラミング、QTプロジェクト実戦、QSS、OpenCV、Quickモジュール、取材質問等) ↓↓↓↓↓↓下記参照↓↓料金の受け取りは記事下部をクリック↓↓

2. レンダリング

 

3. 関連コード

#include "ffmpegsync.h"
#include "ffmpeghelper.h"
#include "ffmpegthread.h"

FFmpegSync::FFmpegSync(quint8 type, QObject *parent) : QThread(parent)
{
    this->stopped = false;
    this->type = type;
    this->thread = (FFmpegThread *)parent;
}

FFmpegSync::~FFmpegSync()
{

}

void FFmpegSync::run()
{
    if (!thread) {
        return;
    }

    this->reset();
    while (!stopped) {
        //暂停状态或者切换进度中或者队列中没有帧则不处理
        if (!thread->isPause && !thread->changePosition && packets.size() > 0) {
            mutex.lock();
            AVPacket *packet = packets.first();
            mutex.unlock();

            //h264的裸流文件同步有问题因为获取不到pts和dts(暂时用最蠢的延时办法解决)
            if (thread->formatName == "h264") {
                int sleepTime = (1000 / thread->frameRate) - 5;
                msleep(sleepTime);
            }

            //计算当前帧显示时间(外部时钟同步)
            ptsTime = FFmpegHelper::getPtsTime(thread->formatCtx, packet);
            if (!this->checkPtsTime()) {
                msleep(1);
                continue;
            }

            //显示当前的播放进度
            this->checkShowTime();

            //如果解码线程停止了则不用处理
            if (!thread->stopped) {
                //0-表示音频 1-表示视频
                if (type == 0) {
                    thread->decodeAudio1(packet);
                } else if (type == 1) {
                    thread->decodeVideo1(packet);
                }
            }

            //释放资源并移除
            mutex.lock();
            FFmpegHelper::freePacket(packet);
            packets.removeFirst();
            mutex.unlock();
        }

        msleep(1);
    }

    this->reset();
    this->clear();
    stopped = false;
}

void FFmpegSync::stop()
{
    if (this->isRunning()) {
        stopped = true;
        this->wait();
    }
}

void FFmpegSync::clear()
{
    mutex.lock();
    //释放还没有来得及处理的剩余的帧
    foreach (AVPacket *packet, packets) {
        FFmpegHelper::freePacket(packet);
    }
    packets.clear();
    mutex.unlock();
}

void FFmpegSync::reset()
{
    //复位音频外部时钟
    showTime = 0;
    bufferTime = 0;
    offsetTime = -1;
    startTime = av_gettime();
}

void FFmpegSync::append(AVPacket *packet)
{
    mutex.lock();
    packets << packet;
    mutex.unlock();
}

int FFmpegSync::getPacketCount()
{
    return this->packets.size();
}

bool FFmpegSync::checkPtsTime()
{
    //下面这几个时间值是参考的别人的
    bool ok = false;
    if (ptsTime > 0) {
        if (ptsTime > offsetTime + 100000) {
            bufferTime = ptsTime - offsetTime + 100000;
        }

        int offset = (type == 0 ? 1000 : 5000);
        //做梦都想不到倍速播放就这里控制个系数就行
        offsetTime = (av_gettime() - startTime) * thread->speed + bufferTime;
        if ((offsetTime <= ptsTime && ptsTime - offsetTime <= offset) || (offsetTime > ptsTime)) {
            ok = true;
        }
    } else {
        ok = true;
    }

    return ok;
}

void FFmpegSync::checkShowTime()
{
    //必须是文件(本地文件或网络文件)才有播放进度
    if (!thread->getIsFile()) {
        return;
    }

    //过滤重复发送播放时间
    bool showPosition = false;
    bool existVideo = (thread->videoIndex >= 0);
    if (type == 0) {
        //音频同步线程不能存在视频
        if (!existVideo) {
            showPosition = true;
        }
    } else if (type == 1) {
        //视频同步线程必须存在视频
        if (existVideo) {
            showPosition = true;
        }
    }

    //需要显示时间的时候发送对应进度(限定差值大于200毫秒没必要频繁发送)
    if (showPosition && (offsetTime - showTime > 200000)) {
        showTime = offsetTime;
        thread->position = showTime / 1000;
        emit receivePosition(thread->position);
    }
}

4.特徴

4.1 基本機能

  • mp3、wav、mp4、asf、rm、rmvb、mkv などのさまざまなオーディオおよびビデオ ファイル形式をサポートします。
  • ローカル カメラ機器をサポートし、解像度とフレーム レートを指定できます。
  • rtp、rtsp、rtmp、http などのさまざまなビデオ ストリーミング フォーマットをサポートします。
  • ローカルのオーディオおよびビデオ ファイルとネットワークのオーディオおよびビデオ ファイルは、ファイルの長さ、再生の進行状況、音量、ミュート状態などを自動的に識別します。
  • ファイルは、再生位置の指定、音量の調整、ミュート状態の設定などを行うことができます。
  • ファイルの倍速再生をサポートし、0.5 倍、1.0 倍、2.5 倍、5.0 倍などの速度を選択でき、低速再生と高速再生に相当します。
  • 再生の開始、再生の停止、再生の一時停止、再生の継続をサポートします。
  • スクリーンショットのスナップをサポートし、ファイル パスを指定できます。また、スナップが完了した後にプレビューを自動的に表示するかどうかを選択できます。
  • ビデオストレージをサポートし、手動で記録を開始および停止します。一部のカーネルは、記録を一時停止した後に記録を継続することをサポートし、記録する必要のない部分をスキップします。
  • 非知覚スイッチング ループ再生、自動再接続などのサポート メカニズム。
  • 再生の成功、再生の完了、デコードされた画像の受信、キャプチャされた画像の受信、ビデオ サイズの変更、録画ステータスの変更などの信号を提供します。
  • マルチスレッド処理、1 つのデコード スレッド、メイン インターフェイスでスタックなし。

4.2 特徴

  • qmedia カーネル (Qt4/Qt5/Qt6)、ffmpeg カーネル (ffmpeg2/ffmpeg3/ffmpeg4/ffmpeg5)、vlc カーネル (vlc2/vlc3)、mpv カーネル (mpv1/mp2)、Hikvision SDK など、複数のデコード カーネルを同時にサポート、easyplayerカーネルなど
  • 非常に完全な複数の基本クラスの設計、新しいデコード コアの追加は、ごく少量のコードを実装するだけで済み、メカニズムのセット全体を適用できます。
  • 同時に、さまざまな画面表示戦略、自動調整(元の解像度が表示コントロールのサイズよりも小さい場合、元の解像度に従って表示されます。それ以外の場合は比例してスケーリングされます)、比例スケーリングをサポートします。 (永久比例スケーリング)、ストレッチと塗りつぶし (永久ストレッチと塗りつぶし)。すべてのカーネルとすべてのビデオ表示モードで、3 つの画面表示方法がサポートされています。
  • 同時に、さまざまなビデオ表示モード、ハンドル モード (描画コントロールのためにコントロール ハンドルを相手に渡す)、描画モード (データを取得して QImage に変換し、QPainter で描画するためのコールバック) をサポートします。 )、GPU モード (コールバックしてデータを取得し、QOpenglWidget 描画を使用するためにそれを yuv に変換します)。
  • 複数のハードウェア アクセラレーション タイプをサポートします。ffmpeg は dxva2、d3d11va などを選択できます。mpv は auto、dxva2、d3d11va を選択できます。vlc は any、dxva2、d3d11va を選択できます。Linux システムの場合は vaapi と vdpau、macos システムの場合は videotoolbox など、システム環境が異なればオプションの種類も異なります。
  • デコード スレッドは表示ウィンドウから分離されており、任意のデコード コアを指定して、任意の表示ウィンドウにマウントし、動的に切り替えることができます。
  • デフォルトで有効化され、自動的に処理される共有デコード スレッドをサポート同じビデオ アドレスが認識されると、デコード スレッドが共有されるため、ネットワーク トラフィックとネットワーク ビデオ環境での相手デバイスのプッシュ プレッシャーを大幅に節約できます。国内のトップビデオメーカーはすべてこの戦略を採用しています。このように、1 つのビデオ ストリームがプルされる限り、数十または数百のチャネルに共有して表示することができます。
  • ビデオの回転角度を自動的に識別して描画します.たとえば、携帯電話で撮影したビデオは通常 90 度回転します.再生中に自動的に回転する必要があります.それ以外の場合、デフォルトは上下逆になります.
  • ビデオ ストリーミング中に解像度の変更を自動的に認識し、ビデオ コントロールのサイズを自動的に調整します。たとえば、カメラは使用中に解像度を動的に構成でき、解像度が変更されると、対応するビデオ コントロールも同期的に応答します。
  • オーディオとビデオのファイルは自動的に切り替えられ、知覚なしでループ再生され、切り替え中に黒い画面などの目に見える切り替え跡がありません。
  • ビデオ コントロールは、すべてのデコード コア、画面表示方法、およびビデオ表示モードもサポートします。
  • ビデオ コントロールのフローティング バーは、ハンドル、描画、GPU の 3 つのモードを同時にサポートし、非絶対座標を移動できます。
  • ローカル カメラ デバイスは、デバイス名、解像度、フレーム レートを指定して再生をサポートします。
  • 録画ファイルは、オープン ビデオ ファイル、ローカル カメラ、ネットワーク ビデオ ストリームなどもサポートします。
  • 存在しないビデオまたはネットワーク ストリームのオープン、デバイスの存在の検出、読み取りでのタイムアウトの待機、直前の操作の即時中断、クローズ コマンドの受信時に応答するなど、オープンとクローズに即座に応答します。
  • さまざまな画像ファイルを開くことをサポートし、ローカルのオーディオ ファイルとビデオ ファイルをドラッグして再生することをサポートします。
  • ビデオ コントロール フローティング バーには、録画の開始と停止の切り替え、サウンド ミュートの切り替え、スクリーンショットのスナップ、ビデオの終了などの機能が備わっています。
  • オーディオ コンポーネントは、音声波形値データ分析をサポートし、値に基づいて波形曲線と柱状のサウンド バーを描画でき、デフォルトで音声振幅信号を提供します。
  • 各コンポーネントの非常に詳細な印刷情報プロンプト、特にエラー メッセージ プロンプト、およびパッケージの統一された印刷形式。複雑な機器環境を現場でテストすることは非常に便利で便利です。これは、どのチャネルとどのステップが間違っているかを正確に特定することと同じです。
  • コードのフレームワークと構造は最適化されており、パフォーマンスはパワフルで、繰り返し更新され、継続的にアップグレードされます。
  • ソースコードは Qt4、Qt5、Qt6 をサポートし、すべてのバージョンと互換性があります。

4.3 ビデオコントロール

  • 任意の数の osd ラベル情報を動的に追加できます. ラベル情報には、名前、表示/非表示、フォント サイズ、テキスト テキスト、テキストの色、ラベルの画像、ラベルの座標、ラベルの形式 (テキスト、日付、時刻、日時、画像) が含まれます。 、ラベルの位置 (左上、左下、右上、右下、中央揃え、カスタム座標)。
  • 任意の数のグラフィック情報を動的に追加できます。これは非常に便利です。たとえば、人工知能アルゴリズムによって分析されたグラフィック領域情報をビデオ コントロールに直接送信できます。グラフィック情報はあらゆる形状に対応しており、絶対座標で原画に直接描画できます。
  • グラフィック情報には、名前、境界サイズ、境界色、背景色、矩形領域、パス コレクション、ポイント座標コレクションなどがあります。
  • グラフィック情報ごとに 3 種類の領域を 1 つ以上指定することができ、指定されたすべての領域が描画されます。
  • 組み込みのフローティング バー コントロール、フローティング バーの位置は上、下、左、右をサポートします。
  • フローティング バー コントロールのパラメーターには、余白、間隔、背景の透明度、背景色、テキストの色、押された色、位置、ボタン アイコン コード コレクション、ボタン名識別コレクション、およびボタン プロンプト情報コレクションが含まれます。
  • フローティング バー コントロール内のツール ボタンの列はカスタマイズできます. 構造パラメーターの設定により、アイコンはグラフィック フォントまたはカスタム画像を選択できます.
  • フローティング バー ボタンは、ビデオの切り替え、スクリーンショットのスナップ、ミュートの切り替え、ビデオのオフなどの機能を内部で実現し、ソース コードに独自の対応する機能を追加することもできます。
  • フローティングバーボタンは機能を実現したボタンに対応しており、対応するアイコンの切り替え処理があります.例えば、録音ボタンを押すと録音中のアイコンに切り替わります.サウンドボタンが切り替わった後. 、ミュートアイコンになり、再び切り替えて復元します。
  • フローティング バーのボタンがクリックされると、名前の一意の識別と共に信号として送信され、それ自体で応答処理に関連付けることができます。
  • フローティングバーの空白領域にプロンプ​​ト情報を表示できますデフォルトでは、現在のビデオ解像度が表示され、フレームレートやコードストリームサイズなどの情報を追加できます。
  • ビデオ コントロール パラメータには、境界線のサイズ、境界線の色、フォーカスの色、背景色 (既定では透明)、テキストの色 (既定のグローバル テキストの色)、塗りつぶしの色 (ビデオの外側の空白スペースは黒で塗りつぶされます)、背景のテキスト、背景の画像 (画像が最初に撮影される場合)、画像をコピーするかどうか、スケーリング表示モード (自動調整、比例スケーリング、ストレッチと塗りつぶし)、ビデオ表示モード (ハンドル、描画、GPU)、フローティング バーを有効にする、フローティング バーのサイズ (水平は高さ) 、垂直方向は幅)、フローティング バーの位置 (上、下、左、右)。

4.4 カーネル ffmpeg

  • さまざまなオーディオおよびビデオ ファイル、ローカル カメラ機器、およびさまざまなビデオ ストリーム ネットワーク ストリームをサポートします。
  • 再生の開始、再生の一時停止、再生の継続、再生の停止、再生の進行状況の設定、倍速再生をサポートします。
  • 音量、ミュート切り替え、スナップ写真、ビデオストレージを設定できます。
  • タイトル、アーティスト、アルバム、アルバム カバーなどのアルバム情報を自動的に抽出し、アルバム カバーを自動的に表示します。
  • オーディオとビデオの同期と倍速再生を完全にサポートします。
  • デコード方式は、速度優先、品質優先、イコライズ処理、最速に対応。
  • 携帯電話のビデオ回転角度表示をサポート. 例えば、一般的な携帯電話で撮影したビデオは 90 度回転します. デコードして表示するとき, 正しくするにはもう一度 90 度回転する必要があります.
  • yuv420 形式を自動的に変換します。たとえば、ローカル カメラは yuyv422 形式で、一部のビデオ ファイルは xx 形式であり、yuv420 以外の形式は一様に変換されてから処理されます。
  • dxva2、d3d11va などのハード デコードをサポートし、特に 4K ビデオなどの高解像度で非常に高いパフォーマンスを発揮します。
  • ビデオ応答は非常に低く、遅延は約 0.2 秒で、特別に最適化されたビデオ ストリームを開くための高速応答は約 0.5 秒です。
  • ハードウェア デコードと GPU 描画の組み合わせ、CPU 使用率が非常に低く、Hikvision Dahua などのクライアントよりも優れています。
  • AAC、PCM、G.726、G.711A、G.711Mu、G.711ulaw、G.711alaw、MP2L2 など、ビデオ ストリームでさまざまなオーディオ フォーマットをサポートします。互換性とクロスプラットフォームのために AAC を選択することをお勧めします。
  • ビデオ ストレージは yuv、h264、mp4 形式をサポートし、オーディオ ストレージは pcm、wav、aac 形式をサポートします。デフォルトのビデオ mp4 形式、オーディオ aac 形式。
  • オーディオ ファイルとビデオ ファイルの個別のストレージをサポートし、1 つの mp4 ファイルへのマージもサポートします. デフォルトの戦略では、保存されているオーディオ ファイルとビデオ ファイルの形式に関係なく、最終的には mp4 および aac 形式に変換され、次に 1 つの mp4 ファイルにマージされます。オーディオとビデオの両方を含む mp4 ファイル。
  • オーディオ入力と出力、オーディオとビデオの記録を 1 つの mp4 ファイルにマージしたローカル カメラのリアルタイム ビデオ表示をサポートします。
  • H264/H265 エンコーディングをサポートし (現在、ますます多くの監視カメラが H265 ビデオ ストリーム フォーマットを使用しています)、ビデオ ファイルを生成し、内部でエンコーディング フォーマットを自動的に識別して切り替えます。
  • ビデオ ストリームの動的な解像度の変更を自動的に認識し、ビデオ ストリームを再度開きます。
  • ユーザー情報に特殊文字 (たとえば、ユーザー情報に +#@ などの文字) を含むビデオ ストリームの再生と、組み込みの解析およびエスケープ処理をサポートします。
  • 純粋な qt+ffmpeg デコード、非 sdl およびその他のサードパーティのレンダリングと再生の依存関係、gpu レンダリングは qopenglwidget を使用し、オーディオ再生は qaudiooutput を使用します。
  • 同時に、ffmpeg2、ffmpeg3、ffmpeg4、および ffmpeg5 バージョンをサポートし、すべて互換性があります。XP をサポートする必要がある場合は、ffmpeg3 以下を選択する必要があります。

この記事の特典として、 Qt 開発学習教材パッケージ、テクニカル ビデオ (C++ 言語の基礎、C++ デザイン パターン、Q​​t プログラミング入門、QT シグナルとスロット メカニズム、QT インターフェイス開発イメージ図、QT ネットワーク、QT を含む) を無料で受け取ることができます。データベースプログラミング、QTプロジェクト実戦、QSS、OpenCV、Quickモジュール、取材質問等) ↓↓↓↓↓↓下記参照↓↓料金の受け取りは記事下部をクリック↓↓

おすすめ

転載: blog.csdn.net/m0_73443478/article/details/130065519