目次: Linux
高速なJPEGデコード/エンコードライブラリで有名なlibjpeg-turboですが、ライブラリというかAPIが2系統あります(公式ドキュメントへのリンク、libjpeg-turboのソースコード)。系統その1はlibjpeg(IJG: Independent JPEG Groupのライブラリ)互換のAPIで、系統その2は独自のTurboJPEG APIです。パッケージの名前もややこしくて、しばらくしたら忘れてしまいそうなのでメモしておきます。
各パッケージに含まれるファイルも示しておきます。
libjpeg-turbo8 /usr/lib/aarch64-linux-gnu/libjpeg.so.8 /usr/lib/aarch64-linux-gnu/libjpeg.so.8.2.2 libjpeg-turbo8-dev /usr/include/aarch64-linux-gnu/jconfig.h /usr/include/jerror.h /usr/include/jmorecfg.h /usr/include/jpegint.h /usr/include/jpeglib.h /usr/lib/aarch64-linux-gnu/libjpeg.a /usr/lib/aarch64-linux-gnu/libjpeg.so /usr/lib/aarch64-linux-gnu/pkgconfig/libjpeg.pc libturbojpeg /usr/lib/aarch64-linux-gnu/libturbojpeg.so.0 /usr/lib/aarch64-linux-gnu/libturbojpeg.so.0.2.0 libturbojpeg0-dev /usr/include/turbojpeg.h /usr/lib/aarch64-linux-gnu/libturbojpeg.a /usr/lib/aarch64-linux-gnu/libturbojpeg.so /usr/lib/aarch64-linux-gnu/pkgconfig/libturbojpeg.pc
例としてUbuntu 22.04 AArch64向けのものを示しましたが、他のアーキテクチャやバージョンでも構造は同じです。
目次: Linux
半年経ったら完全に忘れるのでメモします。最近JPEGのデコードエンコードが必要になって色々調べていました。NVIDIA GPUとCUDAを使ってJPEGが扱えるそうで、API名はnvJPEGだそうです(nvJPEGのAPIドキュメント)。
前回まではデコード処理でした。今回はエンコードのAPIをご紹介します。decoupled decodingのような複雑なAPIは存在しないようです。
Raw YUV420の画素データを置くメモリを確保する際はcudaMalloc()ではなく、cudaMallocHost()を使う点がデコードと異なります。エンコードはこんな感じでした。
cudaStream_t stream = nullptr;
nvjpegHandle_t nvj_handle = nullptr;
nvjpegEncoderState_t nvj_state = nullptr;
nvjpegEncoderParams_t nvj_param = nullptr;
nvjpegImage_t inbuf = {0};
uint8_t *jpegbuf = nullptr;
size_t bufsize = 0;
int r;
// Create
cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking);
nvjpegCreateEx(NVJPEG_BACKEND_DEFAULT, nullptr, nullptr, NVJPEG_FLAGS_DEFAULT, &nvj_handle);
nvjpegEncoderStateCreate(nvj_handle, &nvj_state, stream);
nvjpegEncoderParamsCreate(nvj_handle, &nvj_param, stream);
// Set JPEG parameters
nvjpegEncoderParamsSetQuality(nvj_param, 80, stream);
nvjpegEncoderParamsSetSamplingFactors(nvj_param, NVJPEG_CSS_420, stream);
// 2のべき乗境界に切り上げる
#define ALIGN_2N(a, b) (((a) + (b) - 1) & ~((b) - 1))
inbuf.pitch[0] = ALIGN_2N(width, 256);
inbuf.pitch[1] = ALIGN_2N(width, 256);
inbuf.pitch[2] = ALIGN_2N(width, 256);
cudaMallocHost((void **)&inbuf.channel[0], inbuf.pitch[0] * height);
cudaMallocHost((void **)&inbuf.channel[1], inbuf.pitch[1] * height);
cudaMallocHost((void **)&inbuf.channel[2], inbuf.pitch[2] * height);
bufsize = width * height * 3 / 2;
jpegbuf = (uint8_t *)malloc(bufsize);
// YUVデータをinbufにロードする
// 詳細はソースコードを参照
// Encoding
nvjpegChromaSubsampling_t jpegsamp = NVJPEG_CSS_420;
size_t jpegsize = bufsize;
nvjpegEncodeYUV(nvj_handle, nvj_state, nvj_param, &inbuf, jpegsamp, width, height, stream);
nvjpegEncodeRetrieveBitstream(nvj_handle, nvj_state, jpegbuf, &jpegsize, stream);
cudaStreamSynchronize(stream);
// Destroy
free(jpegbuf);
cudaFree(inbuf.channel[0]);
cudaFree(inbuf.channel[1]);
cudaFree(inbuf.channel[2]);
nvjpegEncoderParamsDestroy(nvj_param);
nvjpegEncoderStateDestroy(nvj_state);
nvjpegDestroy(nvj_handle);
cudaStreamDestroy(stream);
YUV420Pのロード部分は本質と関係ないことと、若干長いので省略しました。nvJPEG APIの数は少なくてシンプルです。nvJPEGやNVJPGは機械学習用データ(JPEGファイルのことが多いらしい)を高速に入力するため?らしく、デコード命なのでしょう。エンコードもできるけど主眼ではないから、APIもシンプルなものしかないのかな?
前回同様にソースコードを置いておきます。
使い方はコードの先頭にコメントで書いている通りですが、ここでも説明しておきます。引数はありません。ファイル名test_420.yuvのRaw YUV420ファイルを読み込んで、ファイル名simple_420.jpgのJPEGファイルを書き出します。
$ g++ -g -O2 -Wall 20241120_nvjpeg_simple_enc.cpp -lnvjpeg -lcudart $ ./a.out $ ffplay -i simple_420.jpg
エンコード結果はJPEGです。ffplayでも普段お使いの画像ビューアでも、何を使って確認しても構いません。
目次: Linux
半年経ったら完全に忘れるのでメモします。最近JPEGのデコードエンコードが必要になって色々調べていました。NVIDIA GPUとCUDAを使ってJPEGが扱えるそうで、API名はnvJPEGだそうです(nvJPEGのAPIドキュメント)。
前回ご紹介したdecoupled decodingは呼び出すべきAPI数が多くて、ウワァ……と引いてしまう見た目でした。今回のsimple decodingはその名の通りシンプルです。ちなみにエンコード側もあります。なぜかsimpleに該当するAPIしかなく、decoupled相当のエンコード用APIは存在しないようです。変なの。
Simple decodingはこんな感じでした。Decoupledと比べるとかなりAPIが少なく済みます。
cudaStream_t stream = nullptr;
nvjpegHandle_t nvj_handle = nullptr;
nvjpegJpegState_t nvj_state = nullptr;
nvjpegImage_t outbuf = {0};
uint8_t *img_buf[4] = {nullptr};
int img_stride[4] = {0};
int img_sz[4] = {0};
int r;
// Create
cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking);
nvjpegCreateEx(NVJPEG_BACKEND_DEFAULT, nullptr, nullptr, NVJPEG_FLAGS_DEFAULT, &nvj_handle);
nvjpegJpegStateCreate(nvj_handle, &nvj_state);
//2のべき乗境界に切り上げる
#define ALIGN_2N(a, b) (((a) + (b) - 1) & ~((b) - 1))
outbuf.pitch[0] = ALIGN_2N(width, 256);
outbuf.pitch[1] = ALIGN_2N(width, 256);
outbuf.pitch[2] = ALIGN_2N(width, 256);
cudaMalloc(&outbuf.channel[0], outbuf.pitch[0] * height);
cudaMalloc(&outbuf.channel[1], outbuf.pitch[1] * height);
cudaMalloc(&outbuf.channel[2], outbuf.pitch[2] * height);
img_stride[0] = ALIGN_2N(width, 256);
img_stride[1] = ALIGN_2N(width, 256);
img_stride[2] = ALIGN_2N(width, 256);
img_buf[0] = (uint8_t *)malloc(img_stride[0] * height);
img_buf[1] = (uint8_t *)malloc(img_stride[1] * height);
img_buf[2] = (uint8_t *)malloc(img_stride[2] * height);
//Decoupled phase decoding
nvjpegGetImageInfo(nvj_handle, jpegbuf, jpegsize, &jpegcomps, &jpegsamp, jpegwidths, jpegheights);
nvjpegDecode(nvj_handle, nvj_state, jpegbuf, jpegsize, NVJPEG_OUTPUT_YUV, &outbuf, stream);
cudaStreamSynchronize(stream);
for (int i = 0; i < 3; i++) {
cudaMemcpy2D(img_buf[i], img_stride[i], outbuf.channel[i], outbuf.pitch[i],
width, height, cudaMemcpyDeviceToHost);
}
// Destroy
free(img_buf[0]);
free(img_buf[1]);
free(img_buf[2]);
cudaFree(outbuf.channel[0]);
cudaFree(outbuf.channel[1]);
cudaFree(outbuf.channel[2]);
nvjpegJpegStateDestroy(nvj_state);
nvjpegDestroy(nvj_handle);
cudaStreamDestroy(stream);
1枚だけJPEGをデコードするならこちらの方が断然楽ですね。
前回同様にソースコードを置いておきます。
使い方はコードの先頭にコメントで書いている通りですが、ここでも説明しておきます。引数はありません。ファイル名test_420.jpgのJPEGファイルを読み込んで、ファイル名simple_420.yuvのRawvideoファイルを書き出します。
$ g++ -g -O2 -Wall 20241120_nvjpeg_simple_dec.cpp -lnvjpeg -lcudart $ ./a.out $ ffplay -f rawvideo -video_size 1920x1440 -pixel_format yuv420p -i simple_420.yuv
デコード結果のRawvideoを確認するときはffplayを使うと便利です。
目次: Linux
半年経ったら完全に忘れるのでメモします。最近JPEGのデコードエンコードが必要になって色々調べていました。NVIDIA GPUとCUDAを使ってJPEGが扱えるそうで、API名はnvJPEGだそうです(nvJPEGのAPIドキュメント)。それと別にJPEGのHWコーデックもあり、名前はNVJPG(Eがない)です。nvJPEGと紛らわしくて仕方ありません。
NVIDIAがnvJPEGのサンプルを公開しています(nvJPEGデコードサンプルコード)。ありがたいですね。でもなぜかサンプルはデコーダーしかありません。一応Resizeサンプルでエンコーダーを扱っていますが、なぜこんなサンプルの構造にしたのでしょう。
エンコード方法は公式ドキュメント(nvJPEGのドキュメント)の3.1.5 JPEG Encoding Exampleがシンプルで見やすいかもしれません。こちらはなぜかデコーダーのサンプルがありません。変なの。
困ったことにデコーダーのサンプルはRGBからYUVに変更すると動きません。試行錯誤したところストライドが間違っているようです。あとYUV420P(UとVプレーンの幅と高さはYプレーンの半分)なのに、YとUVが同じ高さじゃないとお気に召さないようでした。すなわち、
このようにするとデコードできました。ドキュメントに何も書いていないので、バグか合っているか全くわかりません。上記を考慮しつつDecoupled decodingする場合のAPI呼び出し順を載せておきます。
CUDA関連の謎APIについては、CUDA Stream Management(cudaStream_tなどのドキュメント)と、CUDA Memory Management(cudaMalloc()などのドキュメント)をご参照ください。
cudaStream_t stream = nullptr;
nvjpegHandle_t nvj_handle = nullptr;
nvjpegJpegState_t nvj_dcstate = nullptr;
nvjpegBufferPinned_t pinned_buffers[2] = {nullptr};
nvjpegBufferDevice_t device_buffer = nullptr;
nvjpegJpegStream_t jpeg_streams[2] = {nullptr};
nvjpegDecodeParams_t nvj_decparams = nullptr;
nvjpegJpegDecoder_t nvj_dec = nullptr;
nvjpegImage_t outbuf = {0};
uint8_t *img_buf[4] = {nullptr};
int img_stride[4] = {0};
int img_sz[4] = {0};
int r;
// Create
cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking);
nvjpegCreateEx(NVJPEG_BACKEND_DEFAULT, nullptr, nullptr, NVJPEG_FLAGS_DEFAULT, &nvj_handle);
nvjpegDecoderCreate(nvj_handle, NVJPEG_BACKEND_DEFAULT, &nvj_dec);
nvjpegDecoderStateCreate(nvj_handle, nvj_dec, &nvj_dcstate);
nvjpegBufferPinnedCreate(nvj_handle, nullptr, &pinned_buffers[0]);
nvjpegBufferPinnedCreate(nvj_handle, nullptr, &pinned_buffers[1]);
nvjpegBufferDeviceCreate(nvj_handle, nullptr, &device_buffer);
nvjpegJpegStreamCreate(nvj_handle, &jpeg_streams[0]);
nvjpegJpegStreamCreate(nvj_handle, &jpeg_streams[1]);
nvjpegDecodeParamsCreate(nvj_handle, &nvj_decparams);
//2のべき乗境界に切り上げる
#define ALIGN_2N(a, b) (((a) + (b) - 1) & ~((b) - 1))
outbuf.pitch[0] = ALIGN_2N(width, 256);
outbuf.pitch[1] = ALIGN_2N(width, 256);
outbuf.pitch[2] = ALIGN_2N(width, 256);
cudaMalloc(&outbuf.channel[0], outbuf.pitch[0] * height);
cudaMalloc(&outbuf.channel[1], outbuf.pitch[1] * height);
cudaMalloc(&outbuf.channel[2], outbuf.pitch[2] * height);
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]);
//Decoupled phase decoding
nvjpegStateAttachDeviceBuffer(nvj_dcstate, device_buffer);
nvjpegOutputFormat_t fmt = NVJPEG_OUTPUT_YUV;
nvjpegDecodeParamsSetOutputFormat(nvj_decparams, fmt);
int index = 0;
nvjpegJpegStreamParse(nvj_handle, jpegbuf, jpegsize, 0, 0, jpeg_streams[index]);
nvjpegStateAttachPinnedBuffer(nvj_dcstate, pinned_buffers[index]);
nvjpegDecodeJpegHost(nvj_handle, nvj_dec, nvj_dcstate, nvj_decparams, jpeg_streams[index]);
nvjpegDecodeJpegTransferToDevice(nvj_handle, nvj_dec, nvj_dcstate, jpeg_streams[index], stream);
nvjpegDecodeJpegDevice(nvj_handle, nvj_dec, nvj_dcstate, &outbuf, stream);
cudaStreamSynchronize(stream);
for (int i = 0; i < 3; i++) {
cudaMemcpy2D(img_buf[i], img_stride[i], outbuf.channel[i], outbuf.pitch[i],
(i == 0) ? width : width / 2,
(i == 0) ? height : height / 2,
cudaMemcpyDeviceToHost);
}
// Destroy
free(img_buf[0]);
free(img_buf[1]);
free(img_buf[2]);
cudaFree(outbuf.channel[0]);
cudaFree(outbuf.channel[1]);
cudaFree(outbuf.channel[2]);
nvjpegDecodeParamsDestroy(nvj_decparams);
nvjpegJpegStreamDestroy(jpeg_streams[0]);
nvjpegJpegStreamDestroy(jpeg_streams[1]);
nvjpegBufferPinnedDestroy(pinned_buffers[0]);
nvjpegBufferPinnedDestroy(pinned_buffers[1]);
nvjpegBufferDeviceDestroy(device_buffer);
nvjpegJpegStateDestroy(nvj_dcstate);
nvjpegDecoderDestroy(nvj_dec);
nvjpegDestroy(nvj_handle);
cudaStreamDestroy(stream);
今回紹介したdecoupled decodingは速度が稼げるみたいですが、複雑です。もっと簡単なsimple decodingもあるので次回にご紹介しようと思います。
ソースコードも置いておきます。
使い方はコードの先頭にコメントで書いている通りですが、ここでも説明しておきます。引数はありません。ファイル名test_420.jpgのJPEGファイルを読み込んで、ファイル名decoupled_420.yuvのRawvideoファイルを書き出します。
$ g++ -g -O2 -Wall 20241118_nvjpeg_decoupled.cpp -lnvjpeg -lcudart $ ./a.out $ ffplay -f rawvideo -video_size 1920x1440 -pixel_format yuv420p -i decoupled_420.yuv
Rawvideoを確認するときはffplayを使うと便利です。FFMPEGは本当にありがたい。
目次: 射的
JTSA Limitedの大会に参加しました。去年はベレッタが壊れましたが、今年は大丈夫でした。記録は絶好調というほどではありませんでしたが、自己ベストに近い71.65秒のタイムが出ました(総合79位/115人、LM 16位/26人)。さすがに3年目ともなると大会本番のまぐれ当たり&自己ベスト、なんて嬉しいアクシデントは発生しませんでした。
大会の記録だけ見ると、2022年85秒、2023年76秒、2024年71秒と順調に記録は伸びています。良きかな良きかな。来年はどうなるかな?
< | 2024 | > | ||||
<< | < | 11 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | - | 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 |
合計:
本日: