Nvidia NVENCを使用したLinuxでの効果的なビデオエンコーディング:パート2、オプション





最初の部分では、 Nvidia NVENCを使用したLinuxでのビデオエンコーディングについて説明しました。 前述したように、デスクトップグラフィックスカード用のNvidiaでは、エンコードストリームの数がシステムごとに2セッションに制限されています。 この部分は、この制限に対処することに専念しています。



環境



説明したすべては、前述の構成に従って、GTX 970およびFFmpegがインストールされたマシンで行われます。



外部症状



3つ以上のビデオストリームの並列エンコードを開始しようとすると、FFmpegでエラーが発生します。

 ...
 [nvenc @ 0x3187200] OpenEncodeSessionExが失敗しました:0xa-無効なライセンスキーですか?
 ...
出力ストリーム#0のエンコーダーを開いているときにエラーが発生しました:0-多分bit_rate、rate、width、heightなどのパラメーターが正しくありません


このエラーを常に便利に再現するために、ffmpegでトランスコーディングを2回実行し、SIGSTOP(ターミナルでCtrl + Z)を送信してプロセスを一時停止に設定しました。

 $ / usr / local / bin / ffmpeg -y -i input.mov -vcodec nvenc output1.mp4
 ...
ストリームマッピング:
  ストリーム#0:1->#0:0(mpeg4(ネイティブ)-> h264(nvenc))
  ストリーム#0:0->#0:1(aac(ネイティブ)-> aac(libfdk_aac))
 [q]を押して停止します。[?]ヘルプが必要です
フレーム= 81 fps = 80 q = 0.0サイズ= 1362kB時間= 00:00:03.24ビットレート= 3444.9kbits / s
 [1] +停止/ usr / local / bin / ffmpeg -y -i input.mov -vcodec nvenc out1.mp4
 $ / usr / local / bin / ffmpeg -y -i input.mov -vcodec nvenc output2.mp4
 ...
ストリームマッピング:
  ストリーム#0:1->#0:0(mpeg4(ネイティブ)-> h264(nvenc))
  ストリーム#0:0->#0:1(aac(ネイティブ)-> aac(libfdk_aac))
 [q]を押して停止します。[?]ヘルプが必要です
フレーム= 81 fps = 80 q = 0.0サイズ= 1362kB時間= 00:00:03.24ビットレート= 3444.9kbits / s
 [2] +停止/ usr / local / bin / ffmpeg -y -i input.mov -vcodec nvenc out1.mp4




ltrace



この場所をもっと詳しく見てみましょう。

 $ ltrace / usr / local / bin / ffmpeg -y -i input.mov -vcodec nvenc out3.mp4 2>&1 | 少ない
 ...
 dlsym(0x313e360、「cuInit」)= 0x7f93974182c0
 dlsym(0x313e360、 "cuDeviceGetCount")= 0x7f9397418760
 dlsym(0x313e360、 "cuDeviceGet")= 0x7f93974185c0
 dlsym(0x313e360、 "cuDeviceGetName")= 0x7f93974188e0
 dlsym(0x313e360、「cuDeviceComputeCapability」)= 0x7f9397418f80
 dlsym(0x313e360、 "cuCtxCreate_v2")= 0x7f9397419940
 dlsym(0x313e360、 "cuCtxPopCurrent_v2")= 0x7f9397419df0
 dlsym(0x313e360、 "cuCtxDestroy_v2")= 0x7f9397419af0
 dlopen( "libnvidia-encode.so.1"、1)= 0x3231970
 dlsym(0x3231970、「NvEncodeAPICreateInstance」)= 0x7f93970d4370
 posix_memalign(0x7fffb429d490、32、640、0x7fffb429d3f8)= 0
 memset(0x3141420、 '\ 0'、640)= 0x3141420
無料(0)= <void>
 pthread_mutex_lock(0x19a90e0、8、0xf8f340、0x7fffb429d3f8)= 0
 __vsnprintf_chk(0x7fffb429c3b4、1004、1、-1)= 20
 __vsnprintf_chk(0x7fffb429cbb4、1004、1、-1)= 55
 __snprintf_chk(0x7fffb429cfa0、1024、1、1024)= 75
 strcmp( "[nvenc @ 0x3187200] OpenEncodeSe" ...、 "\ n")= 81
 __strcpy_chk(0x19a8cc0、0x7fffb429cfa0、1024、0)= 0x19a8cc0
 fputs( "[nvenc @ 0x3187200]"、0x7f93a554e1c0 [nvenc @ 0x3187200])= 1
 fputs( "OpenEncodeSessionEx failed:0xa" ...、0x7f93a554e1c0OpenEncodeSessionEx failed:0xa-無効なライセンスキー?
 )= 1
 pthread_mutex_unlock(0x19a90e0、0、0x7fffb429cbb4、-1)= 0
 ...


何らかのエラーが表示されたのは明らかですが、ライブラリとそのシンボル(関数)が動的に読み込まれたため、表示されなかったものが発生しました。



ソースコードFFmpeg



FFmpeg自体のソースコードでこの場所を探して、出発点として見てみましょう。

 〜/ ffmpeg-2.7.1 $ fgrep -r OpenEncodeSessionEx
 ...
 libavcodec / nvenc.c:606:nv_status = p_nvenc-> nvEncOpenEncodeSessionEx(&encode_session_params、&ctx-> nvencoder);
 libavcodec / nvenc.c:609:av_log(avctx、AV_LOG_FATAL、 "OpenEncodeSessionEx failed:0x%x-invalid license key?\ n"、(int)nv_status);
 ...


すべてが明確です。ここでブレークポイントを設定します。



ライトgdb



GNUデバッガーはメインのUnixデバッガーであり、その目的はプログラムがエラーを生成しないようにデバッグすることです。



コンパイルされたアプリケーションをマシンコードに合わせてソースコードと関連付けるには、デバッグシンボルを使用してアプリケーションをコンパイルすることが望ましいです。 まず第一に、マシンとソースコードの対応に関する情報が含まれています。



ほとんどの配布パッケージでは、パッケージには、デバッグ情報がトリミングされたバイナリファイルが含まれています。一部のパッケージでは、デバッグ情報は個別のパッケージとして配信されます。 ubuntuでは、これらは通常-dbgサフィックスが付いたパッケージです。 centosでは、リポジトリをデバッグシンボルに接続し、yum-utilsのdebuginfo-installユーティリティを使用する必要があります。これにより、パッケージとその依存関係のデバッグシンボルがインストールされます。



自己組織化されたFFmpegの場合、uncrop binarは、アセンブリディレクトリでffmpeg_gという名前で使用できます。 デバッガーで実行し、ソースコードの目的の行にすぐにブレークポイントを配置できます。

 #gdb ffmpeg-2.7.1 / ffmpeg_g
 GNU gdb(Ubuntu 7.7.1-0ubuntu5〜14.04.2)7.7.1
 Copyright(C)2014 Free Software Foundation、Inc.
 ...
 ffmpeg-2.7.1 / ffmpeg_gからのシンボルの読み取り...完了。
 (gdb)


ブレークポイントを関心のある場所に設定します。

 (gdb)break nvenc.c:606
 0x44a890のブレークポイント1:ファイルlibavcodec / nvenc.c、行606。


runコマンドの引数から開始引数を指定して、プログラムを開始します。

 (gdb)-i in.mov -vcodec nvenc out3.mp4を実行します
 ...
ブレークポイント1、navnc_encode_init(avctx = 0x1b806e0)(libavcodec / nvenc.cで):606
 606 nv_status = p_nvenc-> nvEncOpenEncodeSessionEx(&encode_session_params、&ctx-> nvencoder);
 (gdb)リスト
 601}
 602
 603 encode_session_params.device = ctx-> cu_context;
 604 encode_session_params.deviceType = NV_ENC_DEVICE_TYPE_CUDA;
 605
 606 nv_status = p_nvenc-> nvEncOpenEncodeSessionEx(&encode_session_params、&ctx-> nvencoder);
 607 if(nv_status!= NV_ENC_SUCCESS){
 608 ctx-> nvencoder = NULL;
 609 av_log(avctx、AV_LOG_FATAL、 "OpenEncodeSessionEx failed:0x%x-無効なライセンスキー?\ N"、(int)nv_status);
 610 res = AVERROR_EXTERNAL;
 (gdb) 


ブレークポイントは機能し、実際にコード内の適切な場所に到達しました。 便宜上、Ctrl + X、Ctrl + Aを押すことにより、GDBをソース画面で分割画面モードに切り替えることができます。



この関数を返す前に、ステップごとにコードを見ていきましょう。

 606 nv_status = p_nvenc-> nvEncOpenEncodeSessionEx(&encode_session_params、&ctx-> nvencoder);
 (gdb)ステップ
 603 encode_session_params.device = ctx-> cu_context;
 (gdb)ステップ
 606 nv_status = p_nvenc-> nvEncOpenEncodeSessionEx(&encode_session_params、&ctx-> nvencoder);
 (gdb)ステップ
 607 if(nv_status!= NV_ENC_SUCCESS){


ところで、最後に入力したコマンドは、単にEnterを押すだけで繰り返すことができます。 関数の戻り値は、ローカル変数nv_statusに保存されます。 その中身を見てみましょう。

 (gdb)情報ローカル
 ...
 nv_status = NV_ENC_ERR_OUT_OF_MEMORY
 ...


ハングしているffmpegを強制終了し、デバッガーでrunコマンドでプログラムを再起動します。 これにより、同じ引数で起動します。 同じ場所に到達すると、次のように表示されます。

 (gdb)情報ローカル
 ...
 nv_status = NV_ENC_SUCCESS
 ...


したがって、コーディングセッションを作成する関数は、成功した場合はNV_ENC_SUCCESS(0)を返し、ユーザーが既に2つのコーディングセッションを開いている場合はNV_ENC_ERR_OUT_OF_MEMORY(10)を返します。 この機能をさらに詳しく見てみましょう。



ダークGDB



この関数の呼び出しに到達して、その関数に入ります。

ブレークポイント1、navnc_encode_init(avctx = 0x1b806e0)(libavcodec / nvenc.cで):606
 606 nv_status = p_nvenc-> nvEncOpenEncodeSessionEx(&encode_session_params、&ctx-> nvencoder);
 (gdb)レイアウトasm


GDBインターフェースは次の形式を取ります。



画面を強制的に再描画します。画面が散らかっている場合は、Ctrl + Lを押します。



実行ポインタは、呼び出す前に関数のパラメータをロードします。 私たちは深く入ります:

 (gdb)ステップモードをオンに設定
 (gdb)ステップ
 ...


/usr/lib/x86_64-linux-gnu/libnvidia-encode.so.1の関数内にいます。

   > | 0x7fffe289b010 mov%rbp、-0x20(%rsp)|
    | 0x7fffe289b015 mov%r12、-0x18(%rsp)|
    | 0x7fffe289b01a mov $ 0x6、%ebp |
    | 0x7fffe289b01f mov%rbx、-0x28(%rsp)|
    | 0x7fffe289b024 mov%r13、-0x10(%rsp)|
    | 0x7fffe289b029 mov%rsi、%r12 |
    | 0x7fffe289b02c mov%r14、-0x8(%rsp)|
    | 0x7fffe289b031 sub $ 0xa8、%rsp |
    | 0x7fffe289b038 test%rdi、%rdi |
    | 0x7fffe289b03b sete%dl |
    | 0x7fffe289b03e test%rsi、%rsi |
    | 0x7fffe289b041 sete%al |
    | 0x7fffe289b044または%al、%dl |
    | 0x7fffe289b046 jne 0x7fffe289b060 |
    | 0x7fffe289b048 mov(%rdi)、%eax


そのため、関数全体を順を追って説明し、条件付き遷移の方向を紙に書き留めます。 深くならないように他の関数の呼び出しに入ると、finishコマンドですぐに終了します。 すべてのコーディングセッションがビジーである場合、および無料のセッションがある場合、これを2回行います。



この方法に従って、分岐は場所から始まるという結論に達します。

    | 0x7fffe289b319 callq 0x7fffe288d510 |
    | 0x7fffe289b31e test%eax、%eax |
    | 0x7fffe289b320 mov%eax、%ebp |
    | 0x7fffe289b322 jne 0x7fffe289b332 |




決定関数:

   > | 0x7fffe288d510 mov%rbx、-0x20(%rsp)|  | 0x7fffe288d515 mov%rbp、-0x18(%rsp)|  | 0x7fffe288d51a mov%rdi、%rbx |  | 0x7fffe288d51d mov%r12、-0x10(%rsp)|  | 0x7fffe288d522 mov%r13、-0x8(%rsp)|  | 0x7fffe288d527 sub $ 0x28、%rsp |  | 0x7fffe288d52b test%rsi、%rsi |  | 0x7fffe288d52e mov%rsi、%r12 |  | 0x7fffe288d531 mov%rcx、%r13 |  | 0x7fffe288d534 mov $ 0x6、%ebp |  | 0x7fffe288d539 je 0x7fffe288d54d |  | 0x7fffe288d53b dec%edx |  | 0x7fffe288d53d mov $ 0xa、%bpl |  | 0x7fffe288d540 je 0x7fffe288d568 |  | 0x7fffe288d542 cmpb $ 0x1.0x10(%rbx)|  | 0x7fffe288d546 je 0x7fffe288d5a3 |  | 0x7fffe288d548 mov $ 0x2、%ebp |  | 0x7fffe288d54d mov%ebp、%eax |  | 0x7fffe288d54f mov 0x8(%rsp)、%rbx |  | 0x7fffe288d554 mov 0x10(%rsp)、%rbp |  | 0x7fffe288d559 mov 0x18(%rsp)、%r12 |  | 0x7fffe288d55e mov 0x20(%rsp)、%r13 |  | 0x7fffe288d563 $ 0x28、%rspを追加|  | 0x7fffe288d567 retq | 


この関数はメモリ内の何かをチェックし、成功した場合は何かを実行し、そうでない場合は0を返します。失敗した場合は、nvEncOpenEncodeSessionEx()によってこの関数の結果からこのエラーコードが返されます。 0を返したかのように、この関数の戻り値を無視してみましょう。



callq 0x7fffe288d510の後、test%eax、%eaxの前に停止します。 関数の結果でレジスタをゼロにし、プログラムの無料実行を継続します。

 (gdb)set eax = 0
 (gdb)続行


トランスコーディングが開始されました! そして、正しい結果さえ生み出します。 これは、コードで常に必要であることを意味します。 libnvidia-encode.so.1自体でこれを修正します



この場所がディスク上の物理ファイルのどこにあるかを理解する必要があります。 メモリ内にロードされたライブラリのコード内の仮想アドレスに対応するファイル内のオフセットを見つけます。

 (gdb)info proc mappings
プロセス1692
マッピングされたアドレススペース:

           Start Addr End Addr Size Offset objfile
 ...
       0x7fffe2887000 0x7fffe28a8000 0x21000 0x0 /usr/lib/x86_64-linux-gnu/libnvidia-encode.so.346.46
 ...


アドレス0x7fffe289b31eの近くに興味があり、この地域に該当します。 ファイルのオフセットは次のとおりです。アドレス-開始アドレス+セグメントオフセット。

 (gdb)print / x 0x7fffe289b31e-0x7fffe2887000 + 0x0
 7ドル= 0x1431e




ビウ



ファイル自体にパッチを適用します。 私はこれまでのところbewよりも良いものを見つけていません(beyeに改名されました)。 以前にバックアップを作成したので、ファイルを修正します。

 biew /usr/lib/x86_64-linux-gnu/libnvidia-encode.so.346.46


その中:F2->逆アセンブラー、F5-> 1431e

次の図が表示されます。





探しているコードとまったく同じように見えるので、正解です。 eaxレジスタには0が必要です。条件付き遷移は発生しません。



F4を押すと、編集モードがオンになります。 biewでは、hiewのような便利なモードはありません。hiewでは、命令を直接入力でき、エディターがそれをアセンブルします。 したがって、オペコードを数値的に操作する必要があります。 たとえば、次のように記述します。





値0x85からのオフセット0x1431eのバイトは0x29に変更されます。 命令「test eax、eax」は「sub eax、eax」に変わります。 オフセット0x14322と0x14323の2バイトを0x90に置き換えます-これはよく知られたnopオペコードです。



まとめ



結果のソリューションは非常にうまく機能します。 標準ツールを使用すると、多くのことを達成し、可能な範囲を広げることができます。



All Articles