目次: Linux
半年経ったら完全に忘れるのでメモします。最近JPEGのデコードエンコードが必要になって色々調べていました。Jetson特有のAPI群がありまして、API名はJetson Linux API(のなかのMultimedia APIs)だそうです(Jetson Linux APIドキュメント)。ハードウェアJPEGデコーダ/エンコーダ(NVJPG)を自動的に使用してくれます。
今回はデコードのAPIをご紹介します。APIの使い方は簡単でこんな感じでした。
NvJPEGDecoder *jpgdec = nullptr;
NvBuffer *outbuf = nullptr;
int r;
// Create
jpgdec = NvJPEGDecoder::createJPEGDecoder("jpgdec");
// Decoding
uint32_t jpeg_pixfmt;
uint32_t jpeg_width;
uint32_t jpeg_height;
r = jpgdec->decodeToBuffer(&outbuf, jpegbuf, jpegsize, &jpeg_pixfmt, &jpeg_width, &jpeg_height);
// Destroy
delete outbuf;
delete jpgdec;
注意すべき点は2つあります。1つ目はdecodeToBuffer()が勝手にoutbufを確保して返してくるので、delete outbufしなければならないことです。Turbo JPEGと異なり、予めバッファを確保しておけばメモリ確保処理を回避するような仕組みはなさそうでした。イマイチです。
2つ目はProgressive JPEGがデコードできないことです。デコードしようとするとdecodeToBuffer()でハングします。どんなJPEGファイルが来るかわからない状況で使う場合、JPEGファイルの中身をチェックしてBaseline JPEGはハードウェアデコード、Progressive JPEGはソフトウェアデコードする仕組みが必要です。
しかし前に話したとおりJetson APIの裏にいるlibnvjpeg.soが謎にIJG JPEGのAPIを実装しているため、本家IJG JPEGのlibjpeg.soがリンクできません。この状態でどうやってProgressive JPEGをソフトウェアデコードすれば良いのでしょう。Jetson APIの裏にいるlibnvjpeg.soをIJG JPEGだと思って呼べば動作するんでしょうか?
ソースコードを置いておきます。
使い方はコードの先頭にコメントで書いている通りですが、ここでも説明しておきます。引数はありません。ファイル名test_420.yuvのRaw YUV420ファイルを読み込んで、ファイル名jetson_420.jpgのJPEGファイルを書き出します。
$ g++ -g -O2 -g -Wall \ -I jetson_multimedia_api/include/ \ -I jetson_multimedia_api/include/libjpeg-8b/ \ jetson_multimedia_api/samples/common/classes/NvJpegDecoder.cpp \ jetson_multimedia_api/samples/common/classes/NvJpegEncoder.cpp \ jetson_multimedia_api/samples/common/classes/NvBuffer.cpp \ jetson_multimedia_api/samples/common/classes/NvElement.cpp \ jetson_multimedia_api/samples/common/classes/NvElementProfiler.cpp \ jetson_multimedia_api/samples/common/classes/NvLogging.cpp \ jetson_dec.cpp \ -L /usr/lib/aarch64-linux-gnu/nvidia/ \ -lnvjpeg $ ./a.out $ ffplay -f rawvideo -video_size 1920x1440 -pixel_format yuv420p -i jetson_420.yuv
デコード結果のRawvideoを確認するときはffplayを使うと便利です。
目次: Linux
半年経ったら完全に忘れるのでメモします。最近JPEGのデコードエンコードが必要になって色々調べていました。Jetson特有のAPI群がありまして、API名はJetson Linux API(のなかのMultimedia APIs)だそうです(Jetson Linux APIドキュメント)。
Jetsonを持っていないと使えません。最新機種はJetson AGX OrinかJetson Orin Nanoですが、AGX Orinは40万円、Orin Nanoは10万円近くするボッタクリ価格です。Xavierまではそこそこ安かったのにねえ……。
Jetson Linux APIがもしシステムにインストールされていなければapt-get install nvidia-l4t-jetson-multimedia-apiで使えるようになるはずです。Jetson用のパッケージは一覧があります(一覧へのリンク)ので、手動でダウンロードしたいときにはありがたいですね。
Jetson Linux APIは実装も使い方もかなり独特というか……はっきり言って変です。
変な点その1、APIの提供方法です。API実装は/usr/src/jetson_multimedia_api/samples/common/classesにソースコードで置かれています。ライセンスはBSDライセンスです。なぜ*.soや*.aで提供しないのでしょう?
後ほど紹介するJPEGデコード、エンコードのコンパイルにはNvJpegDecoder.cppとNvJpegEncoder.cppとお供のツール類が必要ですから、あえてディレクトリ名を省かずに書くと、下記のようなコマンドでコンパイルできます。面倒ならばjetson_multimedia_api/samples/common/classes/*.cppで良いです、NVIDIA提供のサンプルもワイルドカードを使っていました。
g++ -O2 -g -Wall \ -I jetson_multimedia_api/include/ \ -I jetson_multimedia_api/include/libjpeg-8b/ \ jetson_multimedia_api/samples/common/classes/NvJpegDecoder.cpp \ jetson_multimedia_api/samples/common/classes/NvJpegEncoder.cpp \ jetson_multimedia_api/samples/common/classes/NvBuffer.cpp \ jetson_multimedia_api/samples/common/classes/NvElement.cpp \ jetson_multimedia_api/samples/common/classes/NvElementProfiler.cpp \ jetson_multimedia_api/samples/common/classes/NvLogging.cpp \ (自分のソースコード) \ -L /usr/lib/aarch64-linux-gnu/nvidia/ \ -lnvjpeg
変な点その2、JPEG機能のヘッダ名です。jetson_multimedia_api/include/NvJpegDecoder.hを見ると、JPEG関連の機能はjetson_multimedia_api/include/libjpeg-8b/jpeglib.hにあるjpeglib.hヘッダにて定義されています。そう、このヘッダ名はIJG JPEGライブラリのヘッダと同じ名前です。なぜそんな場所にヘッダを置くのか?IJG JPEGと互換性がない(APIが増えている)のになぜ同じ名前?
変な点その3、JPEG機能のライブラリ名です。jpeglib.hだからlibjpeg.soかと思うじゃないですか、それは罠でリンクすべきはlibnvjpeg.soです。良くないことにlibjpeg.soも存在していて、さらに良くないことにデコード機能だけ使っている場合は間違ってlibjpeg.soを指定してもリンクできてしまい、
$ ./a.out JPEG parameter struct mismatch: library thinks size is 656, caller expects 776
実行時にこのようなエラーが出るだけでうんともすんとも動きません。IJG JPEGと同じAPI名にするならTurbo JPEGのようにlibjpeg.soの方も置き換えれば問題は起きなかったのに、なぜ中途半端な実装に?
変な点その4、Jetson APIとNVJPEGの衝突です。以前紹介したNVJPEGはJetsonでも動きますがインストールはオススメしないです。
この2つが両方ともインストールされ、同じ-lnvjpegでリンクしてもライブラリパスによってリンクエラーになったり実行時のダイナミックリンクエラーになります。全く違う機能とAPI体系なのに、なぜ同じライブラリ名になっているの?
JPEGデコードの話は次にしたいと思います。Jetsonは適当なのか突貫なのか知りませんが、すっごい変だしちょっと使っただけなのに無限に疑問が出てきます。みんな良くこんなの使ってるなあ……。
目次: Linux
FFMPEG(ffmpeg)を使ってYUV420やYUV444のrawvideo画像を生成する方法です。元にしたJPEG画像の解像度は1920x1440なので、YUV444の場合は1920 * 1440 * 3 = 8294400バイト、YUV420の場合は1920 * 1440 * 1.5 = 4147200バイトになるはずです。
$ ffmpeg -i test_420.jpg -f rawvideo -pix_fmt yuvj444p output_444.yuv $ ls -la output.yuv -rw-r--r-- 1 katsuhiro katsuhiro 8294400 12月 4 00:43 output_444.yuv $ ffmpeg -i test_420.jpg -f rawvideo -pix_fmt yuvj420p output_420.yuv $ ls -la output_420.yuv -rw-r--r-- 1 katsuhiro katsuhiro 4147200 12月 4 00:44 output_420.yuv
FFMPEG(ffplay)を使ってYUV420やYUV444のrawvideo画像を表示する方法です。ffmpegとffplayはピクセルフォーマットのオプションが微妙に違うのが嫌なところですね……。
$ ffplay -f rawvideo -video_size 1920x1440 -pixel_format yuv420p output_420.yuv
目次: Linux
半年経ったら完全に忘れるのでメモします。最近JPEGのデコードエンコードが必要になって色々調べていました。libjpeg-turboを使ってJPEGのデコードとエンコードを行う方法を紹介します(公式ドキュメントへのリンク)。
以前(2024年11月25日の日記参照)紹介したようにlibjpeg-turboは2.x系と3.x系の2系統のAPIがありまして、今回は2.x系APIの使い方を紹介します。DebianやUbuntuのaptパッケージだと2.x系が使われているので特に困らないと思いますが、最新版をビルドして使う場合は3.x系APIになるのでご注意ください。
デコードと対になるような作りです。libjpeg-turboはとても簡単でありがたい。
tj_handle = tjInitCompress();
// Rawvideo
img_stride[0] = width;
img_stride[1] = width / 2;
img_stride[2] = width / 2;
img_buf[0] = (uint8_t *)malloc(img_stride[0] * height);
img_buf[1] = (uint8_t *)malloc(img_stride[1] * height / 2);
img_buf[2] = (uint8_t *)malloc(img_stride[2] * height / 2);
// Encoding
tjCompressFromYUVPlanes(tj_handle, (const uint8_t **)img_buf, width, img_stride, height, subsamp, &jpegbuf, &jpegsize, quality, 0);
// Destroy
free(img_buf[0]);
free(img_buf[1]);
free(img_buf[2]);
tjDestroy(tj_handle);
デコード同様にinitしてエンコードするだけです。デコードと違う点が1つあって、エンコードではJPEGを格納するバッファ(上記だとjpegbuf)のサイズが不明である問題があります。解決方法は2つあります。連続でエンコードする場合など、メモリの確保解放を減らしたい場合は1番目が良いでしょう。1枚だけエンコードするだけならどちらでも構いません。
やってはいけないのが1番目だけどバッファサイズが不足することです。エンコード関数が勝手にバッファmalloc()してポインタを返してきて、元々確保していたバッファが行方不明になってしまいます。メモリリークするので注意が必要です。
私のコードは1番目の方法をとっていて、バッファサイズはYUV420つまり非圧縮時のサイズです。非圧縮時のサイズを上回ることはほぼないらしいのであまり問題ないはず。気になる人はlibjpeg-turboのAPIにJPEGサイズの最大値を返すtjBufSize()があるので、APIが返す値でjpegbufを確保するのが正しいお作法です。
ソースコードを置いておきます。
使い方はコードの先頭にコメントで書いている通りですが、ここでも説明しておきます。引数はありません。ファイル名test_420.yuvのRaw YUV420ファイルを読み込んで、ファイル名turbo2_420.jpgのJPEGファイルを書き出します。
$ g++ -g -O2 -Wall 20241205_turbo2_enc.cpp -lturbojpeg $ ./a.out $ ffplay -i turbo2_420.jpg
エンコード結果はJPEGです。ffplayでも普段お使いの画像ビューアでも、何を使って確認しても構いません。
目次: Linux
半年経ったら完全に忘れるのでメモします。最近JPEGのデコードエンコードが必要になって色々調べていました。libjpeg-turboを使ってJPEGのデコードとエンコードを行う方法を紹介します(公式ドキュメントへのリンク)。
以前(2024年11月25日の日記参照)紹介したようにlibjpeg-turboは2.x系と3.x系の2系統のAPIがありまして、今回は2.x系APIの使い方を紹介します。DebianやUbuntuのaptパッケージだと2.x系が使われているので特に困らないと思いますが、最新版をビルドして使う場合は3.x系APIになるのでご注意ください。
前に使ったnvJPEGは訳のわからん儀式が必要でしたが、libjpeg-turboはとても簡単です。
// Create
tj_handle = tjInitDecompress();
// Rawvideo
img_stride[0] = width;
img_stride[1] = width / 2;
img_stride[2] = width / 2;
img_sz[0] = img_stride[0] * height;
img_sz[1] = img_stride[1] * height / 2;
img_sz[2] = img_stride[2] * height / 2;
img_buf[0] = (uint8_t *)malloc(img_sz[0]);
img_buf[1] = (uint8_t *)malloc(img_sz[1]);
img_buf[2] = (uint8_t *)malloc(img_sz[2]);
// Decoding
tjDecompressToYUVPlanes(tj_handle, jpegbuf, jpegsize, img_buf, width, img_stride, height, 0);
// Destroy
free(img_buf[0]);
free(img_buf[1]);
free(img_buf[2]);
tjDestroy(tj_handle);
基本的にはinitしてデコードするだけです。
ソースコードを置いておきます。
使い方はコードの先頭にコメントで書いている通りですが、ここでも説明しておきます。引数はありません。ファイル名test_420.jpgのJPEGファイルを読み込んで、ファイル名turbo2_420.yuvのRawvideoファイルを書き出します。
$ g++ -g -O2 -Wall 20241204_turbo2_dec.cpp -lturbojpeg $ ./a.out $ ffplay -f rawvideo -video_size 1920x1440 -pixel_format yuv420p -i turbo2_420.yuv
デコード結果のRawvideoを確認するときはffplayを使うと便利です。
目次: Linux
Debian Testingのapt-get updateをしたら、xrdp経由で起動しているX.orgがエラーで起動しなくなりました。こんなエラーが出ています。
[ 1416.016] (EE) [ 1416.016] (EE) Backtrace: [ 1416.016] (EE) 0: /usr/lib/xorg/Xorg (OsLookupColor+0x14d) [0x556fb13c8dad] [ 1416.017] (EE) 1: /lib/x86_64-linux-gnu/libc.so.6 (__sigaction+0x40) [0x7f084f9dad20] [ 1416.017] (EE) 2: /lib/x86_64-linux-gnu/libc.so.6 (pthread_key_delete+0x14c) [0x7f084fa2ee5c] [ 1416.017] (EE) 3: /lib/x86_64-linux-gnu/libc.so.6 (gsignal+0x12) [0x7f084f9dac82] [ 1416.018] (EE) 4: /lib/x86_64-linux-gnu/libc.so.6 (abort+0xd3) [0x7f084f9c34f0] [ 1416.018] (EE) 5: /lib/x86_64-linux-gnu/libc.so.6 (perror+0xcb7) [0x7f084f9c432d] [ 1416.018] (EE) 6: /lib/x86_64-linux-gnu/libc.so.6 (timer_settime+0x395) [0x7f084fa389e5] [ 1416.019] (EE) 7: /lib/x86_64-linux-gnu/libc.so.6 (timer_settime+0x59c) [0x7f084fa38bec] [ 1416.019] (EE) 8: /lib/x86_64-linux-gnu/libc.so.6 (__libc_free+0xb8) [0x7f084fa3d548] [ 1416.019] (EE) 9: /lib/x86_64-linux-gnu/libdrm.so.2 (drmFreeDevice+0x7d) [0x7f084febf13d] [ 1416.020] (EE) 10: /lib/x86_64-linux-gnu/libdrm.so.2 (drmFreeDevices+0x2e) [0x7f084febf2ae] [ 1416.020] (EE) 11: /lib/x86_64-linux-gnu/libnvidia-egl-gbm.so.1 (loadEGLExternalPlatform+0x8a8) [0x7f084477dd18] [ 1416.020] (EE) 12: /lib/x86_64-linux-gnu/libEGL_nvidia.so.0 (NvEglwlaf47906in+0x6e240) [0x7f08404ad480] [ 1416.020] (EE) 13: /lib/x86_64-linux-gnu/libEGL_nvidia.so.0 (NvEglwlaf47906in+0xd73c) [0x7f084044c97c] [ 1416.020] (EE) unw_get_proc_name failed: no unwind info found [-10] [ 1416.020] (EE) 14: /lib/x86_64-linux-gnu/libEGL.so.1 (?+0x0) [0x7f0844a6ead5] [ 1416.020] (EE) unw_get_proc_name failed: no unwind info found [-10] [ 1416.020] (EE) 15: /usr/lib/x86_64-linux-gnu/dri/swrast_dri.so (?+0x0) [0x7f0844aaf3f3] [ 1416.021] (EE) unw_get_proc_name failed: no unwind info found [-10] [ 1416.021] (EE) 16: /usr/lib/xorg/modules/extensions/libglx.so (?+0x0) [0x7f084f65cbe3] [ 1416.021] (EE) unw_get_proc_name failed: no unwind info found [-10] [ 1416.021] (EE) 17: /usr/lib/xorg/modules/extensions/libglx.so (?+0x0) [0x7f084f65ba1f] [ 1416.021] (EE) 18: /usr/lib/xorg/Xorg (_CallCallbacks+0x3c) [0x556fb125002c] [ 1416.021] (EE) 19: /usr/lib/xorg/Xorg (dri3_send_open_reply+0x111f) [0x556fb138334f] [ 1416.021] (EE) 20: /usr/lib/xorg/Xorg (InitExtensions+0x89) [0x556fb12bde29] [ 1416.021] (EE) 21: /usr/lib/xorg/Xorg (InitFonts+0x1f8) [0x556fb124e968] [ 1416.022] (EE) 22: /lib/x86_64-linux-gnu/libc.so.6 (__libc_init_first+0x88) [0x7f084f9c4d68] [ 1416.022] (EE) 23: /lib/x86_64-linux-gnu/libc.so.6 (__libc_start_main+0x85) [0x7f084f9c4e25] [ 1416.022] (EE) 24: /usr/lib/xorg/Xorg (_start+0x21) [0x556fb12373f1] [ 1416.022] (EE) [ 1416.022] (EE) Fatal server error: [ 1416.022] (EE) Caught signal 6 (Aborted). Server aborting [ 1416.022] (EE) [ 1416.022] (EE) Please consult the The X.Org Foundation support at http://wiki.x.org for help. [ 1416.022] (EE) Please also check the log file at ".xorgxrdp.10.log" for additional information. [ 1416.022] (EE) [ 1416.022] rdpLeaveVT: [ 1416.022] (EE) Server terminated with error (1). Closing log file.
エラーの原因であるlibEGL_nvidia.soを削除すれば起動しそうだったので、libegl-nvidia0パッケージを消したところX.orgが起動しました。めでたしめでたし……ではありません。どうしてこうなった?
他にも何か起きているだろうか?と調べたらCUDAが使えないし、nvidia-smiがデバイスがないと言っています。NVIDIA系の機能が全滅です。どうやらnvidia.koが消滅していて、GPUカードが認識されなくなったようです。NVIDIAドライバパッケージを再インストールするとこんなメッセージが出ました。
nvidia-kernel-dkms (535.216.03-1) を設定しています ... Loading new nvidia-current-535.216.03 DKMS files... Building for 6.11.10-amd64 Module build for kernel 6.11.10-amd64 was skipped since the kernel headers for this kernel do not seem to be installed.
メッセージはカーネルヘッダをインストールしないとカーネルモジュールがビルドされないとおっしゃっています。カーネルヘッダパッケージlinux-headers-6.11.10-amd64をインストールしたところ無事/lib/modules/6.11.10-amd64/updates/dkms/の下にnvidia-current.ko.xzが生成され、CUDAが復活しました。めでたしめでたし……じゃないんだ、まだ。
X.orgは/dev/driの下にあるデバイスファイルにアクセスするので、DRM関連のドライバも必要です。DRM用のドライバパッケージをインストールするとnvidia-current-drm.ko.xzが生成されます。たしかパッケージ名はnvidia-vdpau-driverだったと思います。
カーネルモジュールが生成されたらmodprobe nvidia-drmでロードすれば/dev/dri/card0が出現します。先ほど削除したlibegl-nvidia0パッケージを再インストールしてもX.orgの起動に失敗しなくなりました。めでたしめでたし。
とはいえxrdp経由で起動していると、DRMが有効でも"unsupported render mode"と言われてしまってHWアクセラレーションが効いてなさそうに見えます。せっかく直したのに悲しいですね……。
< | 2024 | > | ||||
<< | < | 12 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | - | - | - | - |
合計:
本日: