Qt/C++ オーディオおよびビデオ開発 57 オーディオおよびビデオ トラックの切り替え/プログラム ストリームの切り替え/オーディオ トラックとビデオ トラックの個別切り替え

I.はじめに

さまざまなオーディオおよびビデオ ファイル形式のサポートは、プレーヤーの基本機能です。一般に、オーディオ ファイルとビデオ ファイルにはストリームが 1 つだけあります。たとえば、オーディオ ファイルにはオーディオ ストリームが 1 つだけ、ビデオ ファイルにはオーディオ ストリームとビデオ ストリームが 1 つだけあります。練習中に、ts 形式のファイルもあることがわかりました。複数のストリームがある場合があります。この形式は通常、複数のプログラム ストリームを 1 つのファイルにカプセル化します。ユーザーは自分のニーズに応じて異なるプログラムを切り替えることができます。たとえば、CCTV1 と CCTV2 は両方とも 1 つの ts ストリーム ファイル内にあります。ユーザーは CCTV1 または CCTV2 に切り替えることを選択できます。 CCTV2. CCTV2、およびオーディオ ストリームとビデオ ストリームは別個のインデックスです。オーディオとビデオが一貫しているように見えるように、対応するストリームに切り替える必要もあります。もちろん、別のオーディオに切り替えることも選択できます。一部のファイルには、 3 ビデオ ストリームと 6 チャンネル。オーディオ ストリームは、中国語と英語のバイリンガル オーディオ ストリームも提供します。したがって、オーディオ トラックとビデオ トラックをプログラム インターフェイスの 1 つのインターフェイスにハードコードすることはできません。代わりに、異なる設定をサポートする必要があります。ユーザーが多言語オーディオトラックを簡単に切り替えることができます。

ffmpeg デコード プロセス中に、formatCtx->nb_streams を通じて特定のストリーム数を取得できます。各ストリームはオーディオまたはビデオであり、対応するインデックスがあります。ユーザーがどのストリームを確認する必要がある場合、デコードされたコード AVStream *videoStream = formatCtx->stream[videoIndex]; 対応するインデックスを渡すだけです。通常、毎回 1 つのビデオのみが再生されます。また、複数のビデオを同時にデコードして再生するように変更することもできます。これは、複数のビデオを視聴するのと同等です。同時にビデオを視聴できるプログラムです。

2.エフェクト描画

ここに画像の説明を挿入します

3. 体験アドレス

  1. 国内站点:https://gitee.com/feiyangqingyun
  2. 国际站点:https://github.com/feiyangqingyun
  3. 個人の作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. エクスペリエンス アドレス:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 抽出コード: 01jf ファイル名: bin_video_demo。

4. 機能的特徴

4.1. 基本機能

  1. mp3、wav、mp4、asf、rm、rmvb、mkv など、さまざまなオーディオおよびビデオ ファイル形式をサポートします。
  2. ローカル カメラ デバイスとローカル デスクトップ コレクションをサポートし、複数のデバイスと複数の画面をサポートします。
  3. rtp、rtsp、rtmp、http、udp などのさまざまなビデオ ストリーミング形式をサポートします。
  4. ローカルのオーディオ ファイルとビデオ ファイル、およびネットワーク オーディオ ファイルとビデオ ファイルは、ファイルの長さ、再生の進行状況、音量、ミュート ステータスなどを自動的に識別できます。
  5. ファイルでは再生位置の指定、音量調整、ミュート状態などの設定が可能です。
  6. ファイルの倍速再生をサポートしており、オプションで 0.5x、1.0x、2.5x、5.0x などの速度を選択できます。これはスロー再生と高速再生に相当します。
  7. 再生の開始、停止、一時停止、継続をサポートします。
  8. スクリーンショットのキャプチャをサポートし、ファイル パスを指定でき、キャプチャ完了後にプレビューを自動的に表示するかどうかを選択できます。
  9. ビデオストレージをサポートし、手動で録画を開始、録画を停止します。一部のカーネルは、録画を一時停止した後の録画の継続をサポートし、録画に必要のない部分をスキップします。
  10. 無意味な切り替えループ再生や自動再接続などの機構をサポートします。
  11. 再生の成功、再生の完了、デコードされた画像の受信、キャプチャされた画像の受信、ビデオ サイズの変更、録画ステータスの変更などの信号を提供します。
  12. マルチスレッド処理、1 つのデコード スレッド、メイン インターフェイスでのスタックなし。

4.2. 特徴

  1. qmedia カーネル (Qt4/Qt5/Qt6)、ffmpeg カーネル (ffmpeg2/ffmpeg3/ffmpeg4/ffmpeg5/ffmpeg6)、vlc カーネル (vlc2/vlc3)、mpv カーネル (mpv1/mp2) を含む複数のデコード カーネルを同時にサポートします。 mdk カーネル、Hikvision SDK、easyplayer カーネルなど。
  2. 非常に完全な複数の基本クラス設計により、新しいデコード カーネルを追加する際に必要なのは、メカニズム全体を適用するのに必要なコードの量が非常に少ないため、拡張が容易になります。
  3. 複数の画面表示戦略を同時にサポート、自動調整 (元の解像度がディスプレイ コントロールのサイズより小さい場合、元の解像度に従って表示されます。それ以外の場合は比例的に拡大縮小されます)、比例スケーリング (常に比例したスケーリング) )、ストレッチ充填(常に引き伸ばされて充填されます))。 3 つの画像表示戦略がすべてのコアとすべてのビデオ表示モードでサポートされています。
  4. 複数のビデオ表示モードを同時にサポート、ハンドル モード (受信したコントロール ハンドルは描画制御のために相手に渡されます)、描画モード (コールバックはデータを取得し、それを QPainter で描画するために QImage に変換します)、GPUモード (コールバックはデータを取得して yuv に変換します) QOpenglWidget 描画)。
  5. 複数のハードウェア アクセラレーション タイプをサポートし、ffmpeg は dxva2、d3d11va などを選択でき、vlc は any、dxva2、d3d11va、mpv は auto、dxva2、d3d11va を選択でき、mdk は dxva2、d3d11va、cuda、mft などを選択できます。システム環境が異なれば、選択できるタイプも異なります。たとえば、Linux システムには vaapi と vdpau があり、macos システムには videotoolbox があります。
  6. デコードスレッドと表示フォームが分離されており、任意のデコードコアを任意の表示フォームに実装するように指定し、動的に切り替えることができます。
  7. デフォルトで有効になり、自動的に処理される共有デコード スレッドをサポートします。同じビデオ アドレスが認識されると、デコード スレッドが共有されるため、ネットワーク ビデオ環境における他のデバイスのネットワーク トラフィックとストリーミングの負荷を大幅に節約できます。国内のトップビデオメーカーは皆この戦略を採用しています。このようにして、1 つのビデオ ストリームを取得する限り、それを数十または数百のチャネルで共有して表示できます。
  8. ビデオの回転角度を自動的に識別して描画します。たとえば、携帯電話で撮影したビデオは通常 90 度回転します。再生中に自動的に回転する必要があります。そうしないと、デフォルトで上下が逆になってしまいます。
  9. ビデオ ストリームの再生中に解像度の変化を自動的に識別し、ビデオ コントロールのサイズを自動的に調整します。たとえば、カメラは使用中に解像度を動的に構成できますが、解像度が変更された場合、対応するビデオ コントロールも同期して応答する必要があります。
  10. オーディオ ファイルとビデオ ファイルは認識されることなく自動的に切り替わり、ループ再生されます。また、切り替え中に黒い画面が表示されるなど、目に見える切り替えの痕跡はありません。
  11. ビデオ コントロールは、任意のデコード コア、任意のピクチャ表示戦略、および任意のビデオ表示モードもサポートします。
  12. ビデオ コントロール フローティング バーは、ハンドル、描画、GPU の 3 つのモードを同時にサポートし、非絶対座標を移動できます。
  13. ローカル カメラ デバイスは、再生用のデバイス名、解像度、およびフレーム レートの指定をサポートしています。
  14. ローカルデスクトップ収集では、収集領域、オフセット値、指定したデスクトップインデックス、フレームレート、複数デスクトップの同時収集などの設定をサポートしています。
  15. 録画ファイルは、開いているビデオ ファイル、ローカル カメラ、ローカル デスクトップ、ネットワーク ビデオ ストリームなどもサポートします。
  16. 存在しないビデオやネットワーク ストリームを開く場合、デバイスの存在を検出する場合、読み取り中にタイムアウトを待機する場合、直前の操作を即座に中断してシャットダウン コマンドを受信した後に応答する場合など、開閉に対する即時応答。
  17. さまざまな画像ファイルを開くことをサポートし、ローカルのオーディオおよびビデオ ファイルのドラッグ アンド ドロップ再生をサポートします。
  18. ビデオ ストリーミングの通信方式は tcp/udp ですが、デバイスによっては tcp などの特定のプロトコル通信しか提供していない場合もあるため、開くプロトコル方式を指定する必要があります。
  19. 接続タイムアウト(ビデオストリーム検出のタイムアウト)と読み取りタイムアウト(取得時のタイムアウト)を設定できます。
  20. フレームごとの再生をサポートし、前フレーム/次フレーム機能インターフェイスを提供し、収集した画像をフレームごとに表示できます。
  21. オーディオ ファイルからタイトル、アーティスト、アルバム、アルバム カバーなどのアルバム情報が自動的に抽出され、アルバム カバーが自動的に表示されます。
  22. ビデオ応答の遅延は約 0.2 秒と非常に低く、ビデオ ストリームを開くまでの応答は約 0.5 秒と非常に高速であり、特別に最適化されています。
  23. H264/H265 エンコード(現在、H265 ビデオ ストリーム形式を使用する監視カメラが増えています)をサポートしてビデオ ファイルを生成し、内部でエンコード形式を自動的に認識して切り替えます。
  24. 組み込みの解析およびエスケープ処理により、ユーザー情報に特殊文字 (ユーザー情報の +#@ などの文字) を含むビデオ ストリームの再生をサポートします。
  25. フィルター、さまざまな透かし、グラフィック効果をサポートし、複数の透かしと画像をサポートし、OSD タグ情報とさまざまなグラフィック情報を MP4 ファイルに書き込むことができます。
  26. AAC、PCM、G.726、G.711A、G.711Mu、G.711ulaw、G.711alaw、MP2L2 など、ビデオ ストリームでさまざまなオーディオ形式をサポートします。最高のクロスプラットフォーム互換性を得るには、AAC を選択することをお勧めします。 。
  27. カーネル ffmpeg は純粋な qt+ffmpeg デコードを使用し、SDL などのサードパーティの描画と再生に依存しません。GPU 描画は qopenglwidget を使用し、オーディオ再生は qaudiooutput を使用します。
  28. カーネル ffmpeg とカーネル mdk は Android をサポートしており、その中でも mdk は Android のハード デコードをサポートしており、パフォーマンスは非常に厳しいです。
  29. オーディオ トラックとビデオ トラック、つまりプログラム チャンネルを切り替えることができます。おそらく ts ファイルには複数のオーディオ プログラム ストリームとビデオ プログラム ストリームが含まれています。どれを再生するかをそれぞれ設定できます。再生前に設定することも、再生中に動的に設定することもできます。
  30. ビデオの回転角度は、再生前に設定でき、再生中に動的に設定できます。
  31. ビデオコントロールフローティングバーには、録画の開始と停止、サウンドのミュート、スクリーンショットの撮影、ビデオの終了などの機能が付属しています。
  32. オーディオ コンポーネントは、音響波形値データ分析をサポートしています。この値に基づいて波形曲線と円柱サウンド バーを描画できます。音響振幅信号はデフォルトで提供されます。
  33. ラベルとグラフィック情報は、マスク レイヤへの描画、ピクチャへの描画、およびソース描画の 3 つの描画方法をサポートします (対応する情報はファイルに保存できます)。
  34. URL アドレスを渡すことにより、他の設定を行わずに、そのアドレスから通信プロトコル、解像度、フレーム レートなどの情報を取得できます。
  35. ビデオをファイルに保存するには、自動処理、ファイルのみ、およびすべてのトランスコーディングの 3 つの戦略がサポートされています。トランスコーディング戦略は、自動識別、264 への変換、および 265 への変換をサポートします。エンコーディングの保存は、指定された解像度スケーリングまたは等倍スケーリングをサポートします。たとえば、保存するファイルのサイズに要件がある場合は、保存する前にスケーリングを指定できます。
  36. 暗号化保存ファイルと復号再生ファイルに対応し、秘密鍵のテキストを指定できます。
  37. 電子拡大に対応しており、フローティングバーで電子拡大モードに切り替え、画面上で拡大したい部分を選択すると、選択後自動的に拡大され、再度倍率モードを切り替えることでリセットできます。
  38. 各コンポーネントの非常に詳細な印刷情報プロンプト、特にエラー メッセージ プロンプト、およびパッケージング用の統一された印刷形式。複雑な機器環境を現場でテストすることは非常に便利で役に立ちます。これは、問題が発生した特定のチャネルとステップを特定することに相当します。
  39. 同時に、簡単なサンプル、ビデオプレーヤー、マルチスクリーンビデオモニタリング、モニタリング再生、フレームバイフレーム再生、マルチスクリーンレンダリングなどの別の形式のサンプルが提供され、対応する機能の使用方法を具体的に示します。
  40. コードのフレームワークと構造は最適化されており、パフォーマンスは強力で、継続的に更新およびアップグレードされています。
  41. ソース コードは Qt4、Qt5、および Qt6 をサポートしており、すべてのバージョンと互換性があります。

4.3. ビデオコントロール

  1. osdタグ情報は、名前、表示/非表示、フォントサイズ、文字テキスト、文字色、背景色、タグ画像、タグ座標、タグ形式(テキスト、日付、時刻、日付時刻)を含めて動的に追加できます。 、画像)、ラベルの位置 (左上隅、左下隅、右上隅、右下隅、中央、カスタム座標)。
  2. 任意の量のグラフィック情報を動的に追加できるため、非常に便利です。たとえば、人工知能アルゴリズムによって分析されたグラフィック領域情報をビデオ コントロールに直接送信できます。グラフィック情報は任意の形状をサポートし、絶対座標を使用して元の画像上に直接描画されます。
  3. グラフィック情報には、名前、境界線のサイズ、境界線の色、背景色、長方形領域、パスセット、点座標セットなどが含まれます。
  4. 各グラフィック情報は 3 つの領域のうち 1 つ以上を指定でき、指定された領域が描画されます。
  5. フローティングバーコントロールを内蔵しており、フローティングバーの位置は上下左右をサポートします。
  6. フローティング バー コントロールのパラメーターには、マージン、間隔、背景の透明度、背景色、テキストの色、押された色、位置、ボタン アイコン コード セット、ボタン名識別セット、およびボタン プロンプト情報セットが含まれます。
  7. フローティング バー コントロールのツール ボタンの行はカスタマイズ可能で、構造パラメータ設定を通じて、アイコンのグラフィック フォントやカスタム画像を選択できます。
  8. フローティングバーボタンはビデオの切り替え、スクリーンショットのキャプチャ、ミュートの切り替え、ビデオを閉じるなどの機能を内部で実装しており、対応する独自の機能をソースコードに追加することもできます。
  9. フローティングバーボタンは機能を実装したボタンに対応しており、ビデオボタンを押した後は録画中のアイコンに切り替わり、サウンドボタンを押した後はアイコンの切り替え処理が対応しています。がミュートアイコンになり、再度切り替えると復帰します。
  10. フローティングバーボタンがクリックされると、ユニークな名前でシグナルとして送出され、単独でレスポンス処理に関連付けることができます。
  11. フローティング バーの空白領域にプロンプ​​ト情報を表示できます。デフォルトで現在のビデオ解像度が表示されます。フレーム レートやビット ストリーム サイズなどの情報を追加できます。
  12. ビデオ コントロール パラメーターには、境界線のサイズ、境界線の色、フォーカスの色、背景色 (デフォルトは透明)、テキストの色 (デフォルトのグローバル テキスト色)、塗りつぶしの色 (ビデオの外側の空白スペースは黒で埋められます)、背景テキスト、背景画像 (設定されている場合、写真が最初に撮影されます)、写真をコピーするかどうか、ズーム表示モード (自動調整、プロポーショナル スケーリング、ストレッチ フィル)、ビデオ表示モード (ハンドル、描画、GPU)、フローティング バーを有効にする、フローティング バーのサイズ (水平は高さ、垂直方向は幅です)、フローティングバーの位置(上、下、左、右)。

5. 関連コード

void FFmpegHelper::getTracks(AVFormatContext *formatCtx, QList<int> &audioTracks, QList<int> &videoTracks)
{
    
    
    //获取音视频轨道信息(一般有一个音频或者一个视频/ts节目文件可能有多个)
    audioTracks.clear();
    videoTracks.clear();
    int count = formatCtx->nb_streams;
    for (int i = 0; i < count; ++i) {
    
    
        AVMediaType type = FFmpegHelper::getMediaType(formatCtx->streams[i]);
        if (type == AVMEDIA_TYPE_AUDIO) {
    
    
            audioTracks << i;
        } else if (type == AVMEDIA_TYPE_VIDEO) {
    
    
            videoTracks << i;
        }
    }
}

bool FFmpegThread::initVideo()
{
    
    
    //找到视频流索引
    videoIndex = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (videoIndex < 0) {
    
    
        //有些没有视频流所以这里不用返回
        videoIndex = -1;
        debug(0, "无视频流", "");
    } else {
    
    
        //如果手动指定了轨道则取指定的(节目流有多个轨道可以指定某个)
        if (videoTrack >= 0 && videoTracks.contains(videoTrack)) {
    
    
            videoIndex = videoTrack;
        }

        //取出流获取对应的信息创建解码器
        int result = -1;
        AVStream *videoStream = formatCtx->stream[videoIndex];

        //如果主动设置过旋转角度则将旋转信息设置到流信息中以便保存那边也应用(不需要保存也旋转可以注释)
        if (rotate != -1) {
    
    
            FFmpegHelper::setRotate(videoStream, rotate);
        }

        //先获取旋转角度(如果有旋转角度则不能用硬件加速)
        this->getRotate();
        if (rotate != 0) {
    
    
            hardware = "none";
        }

        //查找视频解码器(如果上面av_find_best_stream第五个参数传了则这里不需要)
        AVCodecID codecID = FFmpegHelper::getCodecId(videoStream);
        if (codecID == AV_CODEC_ID_NONE) {
    
    
            debug(result, "无视解码", "");
            return false;
        }

        //获取默认的解码器
        videoCodec = avcodec_find_decoder(codecID);
        videoCodecName = videoCodec->name;

        //创建视频解码器上下文
        videoCodecCtx = avcodec_alloc_context3(NULL);
        if (!videoCodecCtx) {
    
    
            debug(result, "创建视解", "");
            return false;
        }

        result = FFmpegHelper::copyContext(videoCodecCtx, videoStream, false);
        if (result < 0) {
    
    
            debug(result, "视频参数", "");
            return false;
        }

        //初始化硬件加速(也可以叫硬解码/如果当前格式不支持硬解则立即切换到软解码)
        if (hardware != "none" && !initHardware()) {
    
    
            hardware = "none";
            videoCodec = avcodec_find_decoder(codecID);
        }

        if (!videoCodec) {
    
    
            return false;
        }

        //设置低延迟和加速解码等参数(设置max_lowres的话很可能画面采用最小的分辨率)
        if (!getIsFile()) {
    
    
            //videoCodecCtx->lowres = videoCodec->max_lowres;
            videoCodecCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
            videoCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
            videoCodecCtx->flags2 |= AV_CODEC_FLAG2_FAST;
        }

        //打开视频解码器
        result = avcodec_open2(videoCodecCtx, videoCodec, NULL);
        if (result < 0) {
    
    
            debug(result, "打开视解", "");
            return false;
        }

        if (videoCodecCtx->pix_fmt == AV_PIX_FMT_NONE) {
    
    
            debug(0, "格式为空", "");
            return false;
        }

        //获取分辨率大小
        FFmpegHelper::getResolution(videoStream, videoWidth, videoHeight);
        //如果没有获取到宽高则返回
        if (videoWidth == 0 || videoHeight == 0) {
    
    
            debug(0, "无分辨率", "");
            return false;
        }

        //记录首帧开始时间和解码器名称
        videoFirstPts = videoStream->start_time;
        videoCodecName = videoCodec->name;
        frameRate = FFmpegHelper::getFrameRate(videoStream, formatName);
        QString msg = QString("索引: %1 解码: %2 帧率: %3 宽高: %4x%5 角度: %6").arg(videoIndex).arg(videoCodecName).arg(frameRate).arg(videoWidth).arg(videoHeight).arg(rotate);
        debug(0, "视频信息", msg);
    }

    return true;
}

void MdkThread::setAudioTrack(int audioTrack)
{
    
    
    this->audioTrack = audioTrack;
    if (audioTrack >= 0 && audioTracks.count() > 1 && audioTracks.contains(audioTrack)) {
    
    
        mdkPlayer->setAudioTrack(audioTrack);
    }
}

void MdkThread::setVideoTrack(int videoTrack)
{
    
    
    this->videoTrack = videoTrack;
    if (videoTrack >= 0 && videoTracks.count() > 1 && videoTracks.contains(videoTrack)) {
    
    
        mdkPlayer->setVideoTrack(videoTrack);
    }
}

おすすめ

転載: blog.csdn.net/feiyangqingyun/article/details/134762923