I.はじめに
GPUを使用してリアルタイムビデオを描画することは常に困難な点でした。ビデオ監視の開発に携わっているのがセキュリティ業界の人である場合、このハードルを通過する必要があります。私はかなりニッチな市場セグメントであるセキュリティ業界の電子フェンスに従事しています。ビデオ監視の開発は、周辺技術との単なる遊びと議論であり、GPU描画に関して多くの回り道がありました。
以前にffmpegでデコードすると、ハードデコード用にqsv、dxva2、d3d11vaなどをサポートするなどのハードデコードが行われていましたが、そのときのデコード後、描画用にQImageに変換され、大幅に削減されました。 GPUの使用率を確認できますが、時間のかかる操作は引き続きCPUで描画および表示されるため、非常に厄介です。Qtは、ほとんどのopengl操作をカプセル化し、ffmpegデコードをサポートするQOPenGLWidgetを直接作成します。 yuyv形式のデータ表示は、ハードデコードされたnv12形式のデータ表示もサポートします。これは非常に優れた強力な方法です。これにより、CPUへの負荷が大幅に軽減され、描画のためにGPUに特別に渡されます。このような徹底的な変換の後、効率が向上します。少なくとも5回は、素晴らしすぎないでください。opengl描画が有効になっていると、対応するメモリが大幅に増加します。opengl描画では、データを交換するために大量のメモリを開く必要があるかもしれません。
GPUディスプレイは、yyyv形式とnv12形式を同時にサポートする必要があります。構成が不十分な一部のコンピューターでは、ハードデコードが停止する可能性が高いため、openglを使用して、ffmpegによってソフトにデコードされたyuyvデータを直接描画し、自動的に切り替える必要があります。すべての可能な状況と互換性があります。テストの結果、ffmpeg4のパフォーマンスはffmpeg3のパフォーマンスよりも優れており、64ビットのパフォーマンスは32ビットのパフォーマンスよりも優れています。64ビットオペレーティングシステムでは、UDPプロトコルのパフォーマンスはTCPよりも優れていますが、パケット損失が発生する可能性があります。
プログラム | CPU | 羊 | GPU |
---|---|---|---|
なし+なし | 12% | 147MB | 0% |
dxva2 + none | 3% | 360MB | 38% |
d3d11va + none | 2% | 277MB | 62% |
none + painter | 30% | 147MB | 0% |
dxva2 + painter | 30% | 360MB | 38% |
d3d11va + painter | 21% | 277MB | 62% |
none + yuyv | 17% | 177 MB | 22% |
dxva2 + yuyv | 25% | 400MB | 38% |
d3d11va + yuyv | 18% | 30MB | 65% |
qsv + nv12 | 22% | 970MB | 40% |
dxva2 + nv12 | 20% | 380MB | 40% |
d3d11va + nv12 | 15% | 320MB | 62% |
2.機能
- 1 + 4 + 6 + 8 + 9 + 13 + 16 + 25 + 36 + 64画面切り替えを含む、マルチ画面切り替え、全画面切り替えなどをサポートします。
- alt + Enter全画面をサポートし、escは全画面を終了します。
- カスタム情報ボックス+エラーボックス+問い合わせボックス+右下隅のプロンプトボックス(複数のフォーマットを含む)。
- 17セットのスキンスタイルは自由に変更でき、メニューを含め、すべてのスタイルが統一されています。
- ジンバルダッシュボード上でマウスを動かしてハイライトすると、8つの方向が正確に識別されます。
- 下部の画面のツールバー(画面分割スイッチ+スクリーンショットサウンドおよびその他の設定)が上に移動して強調表示されます。
- ロゴ+中国語のソフトウェア名+英語のソフトウェア名は、構成ファイルで変更できます。
- Baiduマップ、ビューの切り替え、モーショントラック、デバイスの位置、および緯度と経度を取得するためのマウスクリックなどをカプセル化します。
- 画像マップをサポートし、デバイスボタンを画像マップ上で自由にドラッグして、位置情報を自動的に保存できます。
- Baiduマップと画像マップで、ビデオをダブルクリックしてカメラのリアルタイムビデオをプレビューします。
- スタックウィンドウ。各ウィンドウは個別のqwidgetであり、独自のコードを記述するのに便利です。
- 上部マウスの右クリックメニューは、時間の表示と非表示を動的に制御でき、CPU +左上パネル+左下パネル+右上パネル+右下パネルで、デフォルトレイアウトの復元をサポートします。
- ツールバーには、複数の小さなアイコンと閉じるアイコンを配置できます。
- 左側と右側はドラッグして引き伸ばすことができ、幅と高さの位置は自動的に記憶され、再起動後に復元されます。
- カメラノードをダブルクリックすると、ビデオが自動的に再生され、ノードをダブルクリックすると、ビデオが順番に自動的に追加され、次のノードに自動的にスキップされます。親ノードをダブルクリックすると、ノードの下にあるすべてのビデオが自動的に追加されます。
- カメラノードを対応するウィンドウにドラッグしてビデオを再生します。ローカルファイルは直接再生用にサポートされています。
- ビデオフレームはドラッグとスワッピングをサポートし、即座に応答します。
- ノードをダブルクリックしてノードをドラッグし、フォームをドラッグして位置を入れ替えると、url.txtが自動的に更新されます。
- url.txtからのチャンネル動画再生の読み込みをサポートし、最後のチャンネルに対応する動画を自動的に記憶し、ソフトウェアの起動後に自動的に開いて再生します。
- 右下隅の音量バーコントロールは、フォーカスが失われると自動的に非表示になり、音量バーにはミュートアイコンが表示されます。
- Baiduオンラインマップとオフラインマップを統合し、デバイスの対応する場所を追加したり、マップを自動的に生成したり、ズームをサポートしたり、オーバーレイを追加したりできます。
- ビデオをチャンネルウィンドウの外にドラッグすると、ビデオが自動的に削除されます。
- マウスの右ボタンで、現在の+すべてのビデオを削除できます。
- レコーダー管理、カメラ管理、印刷情報を追加、削除、変更、インポート、エクスポートし、再起動せずにすぐに新しいデバイス情報を適用してツリーリストを生成できます。
- マップをproファイルにロードするかどうかを自由に有効にすることができます。
- ビデオ再生用に2種類のカーネルを自由に切り替えることができます。vlc+ ffmpeg、どちらもプロで設定できます。
- 1 + 4 + 9 + 16画面ポーリングを設定でき、ポーリング間隔とポーリングストリームタイプを設定できます。メインインターフェイスの下部にあるツールバーの右側にある[ポーリングの開始]ボタンをクリックし、もう一度クリックしてポーリングを停止します。
- デフォルトでは、マウスポインターは10秒以上操作しないと自動的に非表示になります。
- onvif検索機器をサポートし、任意のonvifカメラをサポートします。これには、Haikang Dahuayu Shitiandi Weiye Huaweiなどが含まれますが、これらに限定されません。
- onvif PTZコントロールをサポートし、PTZカメラを上下左右に移動できます(リセットとフォーカス調整を含む)。
- 同時に、sqlite、mysql、postsqlなどのデータベースもサポートしています。
- ビデオを保存でき、タイミングストレージまたは単一ファイルストレージはオプションで、ストレージインターバルはオプションです。
- ビデオストリームの通信方法をtcp + udpに設定し、ビデオのデコードを速度優先、品質優先、イコライゼーションなどに設定できます。
- ハードデコードタイプの設定、qsv、dxva2、d3d11vaなどのサポートが可能
- デフォルトでは、openglを使用してビデオを描画します。これは、CPUリソースの消費量が非常に少なく、yuyvおよびnv12形式の描画をサポートしています。これは非常に優れています。
- 高度にカスタマイズ可能で、ユーザーはこれに基づいて独自の機能を簡単に導出でき、LinuxおよびMacシステムをサポートします。
3、レンダリング
4、関連サイト
- 国内サイト:https : //gitee.com/feiyangqingyun/QWidgetDemo
- 国際サイト:https : //github.com/feiyangqingyun/QWidgetDemo
- 個人ホームページ:https : //blog.csdn.net/feiyangqingyun
- Zhihuホームページ:https ://www.zhihu.com/people/feiyangqingyun/
- エクスペリエンスアドレス:https : //blog.csdn.net/feiyangqingyun/article/details/97565652
5、コアコード
void YUVOpenGLWidget2::initializeGL()
{
initializeOpenGLFunctions();
glDisable(GL_DEPTH_TEST);
//传递顶点和纹理坐标
static const GLfloat ver[] = {-1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f};
static const GLfloat tex[] = {0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f};
//设置顶点,纹理数组并启用
glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, ver);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, tex);
glEnableVertexAttribArray(1);
//初始化shader
this->initShader();
//初始化textures
this->initTextures();
//初始化颜色
this->initColor();
}
void YUVOpenGLWidget2::paintGL()
{
if (!dataY || !dataU || !dataV || width == 0 || height == 0) {
this->initColor();
return;
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureY);
glPixelStorei(GL_UNPACK_ROW_LENGTH, linesizeY);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, dataY);
glUniform1i(textureUniformY, 0);
glActiveTexture(GL_TEXTURE0 + 1);
glBindTexture(GL_TEXTURE_2D, textureU);
glPixelStorei(GL_UNPACK_ROW_LENGTH, linesizeU);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width >> 1, height >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, dataU);
glUniform1i(textureUniformU, 1);
glActiveTexture(GL_TEXTURE0 + 2);
glBindTexture(GL_TEXTURE_2D, textureV);
glPixelStorei(GL_UNPACK_ROW_LENGTH, linesizeV);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width >> 1, height >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, dataV);
glUniform1i(textureUniformV, 2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
void YUVOpenGLWidget2::initColor()
{
//取画板背景颜色
QColor color = palette().background().color();
//设置背景清理色
glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
//清理颜色背景
glClear(GL_COLOR_BUFFER_BIT);
}
void YUVOpenGLWidget2::initShader()
{
//加载顶点和片元脚本
program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertShader);
program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragShader);
//设置顶点位置
program.bindAttributeLocation("vertexIn", 0);
//设置纹理位置
program.bindAttributeLocation("textureIn", 1);
//编译shader
program.link();
program.bind();
//从shader获取地址
textureUniformY = program.uniformLocation("textureY");
textureUniformU = program.uniformLocation("textureU");
textureUniformV = program.uniformLocation("textureV");
}
void YUVOpenGLWidget2::initTextures()
{
//创建纹理
glGenTextures(1, &textureY);
glBindTexture(GL_TEXTURE_2D, textureY);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenTextures(1, &textureU);
glBindTexture(GL_TEXTURE_2D, textureU);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenTextures(1, &textureV);
glBindTexture(GL_TEXTURE_2D, textureV);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}