LoginSignup
13
4

More than 3 years have passed since last update.

FFmpegのアクセラレーションとして Intel PACを使ってみた。

Last updated at Posted at 2020-12-03

概要

PACに画像処理をRTL実装し、FFmpegから、この機能を呼び出せるように ソフト側も実装し、実行するまでの過程を説明する。

開発環境準備

A10PAC が搭載されたマシン上で、プログラマブル・アクセラレーション・カード (インテルArria 10 GX FPGA対応)のインテル・アクセラレーション・スタックのクイック・スタートガイド内の 2.4.2. ホストマシン上のインテルのアクセラレーション・スタックのDevelopmentパッケージのインストール を参照してアクセラレーション・スタック1.2のDevelopmentパッケージをインストールします。

上記と同じクイック・スタートガイド内にある 7.3. 仮想マシンでのHello FPGAサンプルの実行の Hello FPGAサンプルが実行できる環境にします。
具体的には、以下のコマンドを実行します。

source (inteldevstackインストールディレクトリ)/init_env.sh
sudo fpgaconf $OPAE_PLATFORM_ROOT/hw/samples/nlb_mode_0/bin/nlb_mode_0.gbs
sudo sh -c "echo 20 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages"

PAC プラグインライブラリ作成

Hello FPGAサンプル(nlb_mode_0.gbs と hello_fpga.c) は、FPGAのループバックテストプログラムになっています。
このサンプルを改造して、FFmpegのプラグインライブラリを作成します。

まずは、hello_fpga.c を改造した hello_fpga_pac.c を作成し、ループバック機能をプラグインします。
hello_fpga.cのコードを以下の3関数に分けます。

Function Description
init_pac ffmpegが起動時に一回呼ぶ初期設定関数。
frame_pac ffmpegがFrame処理時に呼ぶ関数。
close_pac ffmpegが終了前に一回呼ぶ関数。
frame_pac()は、以下の修正もしました。
- 複数回呼ばれることに対応。
- 入力、出力データの引数を追加。
- 出力データは、データサイズが変わる場合でもフレキシブルに対応できるようにコールバック関数で対応。

作成した、hello_fpga_pac.c はこちらになります。

そして、以下のようにヘッダファイルpacif.hも作成します。

#ifndef PACIF_H
#define PACIF_H

#include <stdint.h>

int init_pac(void);
int frame_pac(const void** src, int frame_bytes, void (*callback)(void*,uint8_t*,int,int), void *pkt);
int close_pac(void);

#endif //#ifndef PACIF_H

ffmpegから呼び出せるように共有ライブラリ化します。

以下の環境変数を設定します。

例:

export INSTALL_PATH=$HOME/local
export PATH=$INSTALL_PATH/bin:$PATH
export LIBRARY_PATH=$INSTALL_PATH/lib:$LIBRARY_PATH
export LD_LIBRARY_PATH=$INSTALL_PATH/lib:$LD_LIBRARY_PATH
export C_INCLUDE_PATH=$INSTALL_PATH/include:$C_INCLUDE_PATH
export PKG_CONFIG_PATH+=$INSTALL_PATH/lib/pkgconfig:$PKG_CONFIG_PATH

その後、以下のコマンドを実行します。

mkdir $INSTALL_PATH/include/pactest
mkdir $INSTALL_PATH/lib
cp pacif.h $INSTALL_PATH/include/pactest
gcc -c -fstack-protector -fPIC -std=gnu99 ./hello_fpga_pac.c
gcc -shared hello_fpga_pac.o -o $INSTALL_PATH/lib/libpac.so -z noexecstack -z relro -z now -luuid -lpthread -ljson-c -lopae-c

これで、プラグインライブラリの
ヘッダファイル $INSTALL_PATH/include/pactest/pacif.h
共有ライブラリ $INSTALL_PATH/lib/libpac.so
の準備が完了です。

FFmpegからプラグインライブラリを呼び出す

次にffmpeg側をこのプラグインライブラリを呼び出せるように改造します。
任意のディレクトリで、以下のコマンドでffmpeg をGitHubから取得します。
(リビジョンは筆者が作業したものということ以上の意味はありません。)

git clone https://github.com/FFmpeg/FFmpeg.git
cd FFmpeg
git checkout 60b1f85b

筆者が慣れていることもあり、今回は libavcodec に仮のエンコーダを登録して、
そのエンコーダを実行時に指定した時、PACでアクセラレーションできるように改造します。

FFmpeg/libavcodec の下にlibpactest.c というファイルを作成し、
libpactest というエンコーダを作成します。
libpactest.c内でエンコーダの基本構成を以下のように作成しました。

AVCodec ff_libpactest_encoder = {
    .name           = "pac_plugin",
    .long_name      = NULL_IF_CONFIG_SMALL("PAC Plug-in TEST"),
    .type           = AVMEDIA_TYPE_VIDEO,
    .id             = AV_CODEC_ID_NONE,
    .priv_data_size = sizeof(PACContext),
    .init           = pactest_init,
    .close          = pactest_close,
    .encode2        = pactest_frame,
    .pix_fmts       = (const enum AVPixelFormat[]){
                      AV_PIX_FMT_UYVY422,
                      AV_PIX_FMT_NONE},
    .capabilities   = AV_CODEC_CAP_AUTO_THREADS | AV_CODEC_CAP_DELAY,
    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP,
    .priv_class     = &pac_plugin_class,
};

ffmpeg実行時にこの(仮の)エンコーダを呼ぶために "pac_plugin" を指定するようにしました。
対応する入力フォーマットは、 "uyvy422"のみとしました。
以下のようなffmpegの実行コマンドを想定しています。

ffmpeg -s 1920x1080 -f rawvideo -pix_fmt uyvy422 -i input.yuv -vcodec pac_plugin output.yuv

また、以下の関数を作成しました。

Function Description
pactest_init ffmpegが起動時に一回呼ぶ初期設定関数。
プラグインライブラリのinit_pac() を呼びます。
pactest_frame ffmpegがFrame処理時に呼ぶ関数。
プラグインライブラリのframe_pac()を呼びます。
pactest_close ffmpegが終了前に一回呼ぶ関数。
プラグインライブラリのclose_pac()を呼びます。

作成した libpactest.c はこちらになります。

また、libpactest をコンフィグ、コンパイルするために必要な修正を
行います。以下のファイルを修正します。

  • FFmpeg/confirure
  • FFmpeg/libavcodec/Makefile
  • FFmpeg/libavcodec/allcodec.c

これら修正したファイルはこちらに置いたので参照して下さい。
https://github.com/ryo3ryo3/a10pac_plugin/tree/main/FFmpeg

あとは、FFmpeg/ に移動し、以下のコマンドで、コンフィグ、コンパイル、インストールを行います。

./configure --prefix=$INSTALL_PATH --enable-shared --enable-libpactest --enable-ffplay > configure.log 2>&1
make all
make install

これで、単純なループバックではありますが、ffmpeg からPACでのアクセラレーションできる環境が整いました。
実行テストします。
入力ファイルは、適当な動画ファイルから、以下のコマンドで作成して下さい。

ffmpeg -i (動画ファイル) -f rawvideo -pix_fmt uyvy422 -s 1920x1080 input.yuv

適当な動画ファイルがない場合は、以下のコマンドで作成してもいいです。

ffmpeg -f lavfi -i testsrc=size=1920x1080 -pix_fmt uyvy422 -frames 100 -f rawvideo input.yuv

以下のコマンド実行テストします。

ffmpeg -s 1920x1080 -f rawvideo -pix_fmt uyvy422 -i input.yuv -vcodec pac_plugin output.yuv

output.yuv を以下のffplayコマンド で再生して、input.yuv と同じなら成功です。

ffplay -video_size 1920x1080 -f rawvideo -pixel_format uyvy422 output.yuv

PAC内のFPGAロジックに画像処理を追加する

単純なループバックですと、PAC内のロジックが呼ばれている実感が湧きません。
そこで、Hello FPGAサンプルの FPGAロジックに画像処理を追加して、nlb_mode_0.gbsを作り直します。
まずは以下のコマンドで合成環境を作ります。

cd $OPAE_PLATFORM_ROOT/hw/samples/nlb_mode_0
afu_synth_setup --source hw/rtl/filelist_mode_0.txt build_synth

$OPAE_PLATFORM_ROOT/hw/samples/nlb_mode_0/hw/rtl/nlb_lpbk.sv
にあるデータパスに、画像処理を追加します。

今回は、比較的簡単な左右反転処理を実装します。
更にシンプルにするため 入力画像は、横幅1920固定の仕様としています。
作成した回路は以下です。
uyvy422の1ラインのデータ数は 1920*2 = 3840byte (1画素は Y と UV で 2byte)。
512bitのデータパスになっているので (3840*8)/512 = 60ワードの1ライン分のデータをバッファにためてから、逆順(LIFO)に出力しています。
(hello_fpga_pac.c内の転送単位 "LPBK1_BUFFER_SIZE" も60wordの倍数にする必要がありましたので、1024*1024 から 1200*1024に修正しています。)
データを出力している間も、バッファにためれるように2ライン分のバッファを用意しました。

reg   [6:0]   r_wwordcnt;
wire  [6:0]   w_wwordcnt;
reg   [6:0]   r_rwordcnt;
wire  [6:0]   w_rwordcnt;
reg   [551:0] r_lbuf[0:119];
wire          w_wlastword = ( (r_wwordcnt[6:0]==7'd59) || (r_wwordcnt[6:0]==7'd119) ) ? 1'b1 : 1'b0;
wire          w_rlastword = ( (r_rwordcnt[6:0]==7'd0)  || (r_rwordcnt[6:0]==7'd60)  ) ? 1'b1 : 1'b0;
reg   [1:0]   r_wordstocknum;
wire  [1:0]   w_wordstocknum;
wire          w_wordstocken;
wire          w_wordstock_empty   = (r_wordstocknum[1:0] == 2'd0) ? 1'b1 : 1'b0;
wire          w_wordstock_full_p  = (r_wordstocknum[1:0] == 2'd1) ? ( (w_wlastword & w_wen) & ~(w_rlastword & w_ren) ) : 1'b0;
reg           r_wordstock_full;
wire          w_wordstock_almfull = ( (r_wordstocknum[1:0] == 2'd1) && ((r_wwordcnt[6:0]==7'd52) || (r_wwordcnt[6:0]==7'd112)) ) ? 1'b1 : 1'b0;
reg           r_wordstock_almfull;
wire          w_wen = ~r_wordstock_full & ab2re_WrEn;
wire          w_ren = ~w_wordstock_empty & re2ab_WrSent;
reg           r_lstwen_1z;

wire  [511:0] w_selword;
wire  [511:0] w_sendword;
wire          w_sendworden;

assign w_wwordcnt[6:0] = (r_wwordcnt[6:0]==7'd119) ? 7'd0 : (r_wwordcnt[6:0]+7'd1);
always @(posedge Clk_400) begin
    if (~test_SoftReset)  r_wwordcnt[6:0] <= 7'd0;
    else if(w_wen)        r_wwordcnt[6:0] <= w_wwordcnt[6:0];
end

assign w_rwordcnt[6:0] = (r_rwordcnt[6:0]==7'd0) ? 7'd119 : (r_rwordcnt[6:0]-7'd1);
always @(posedge Clk_400) begin
    if (~test_SoftReset)  r_rwordcnt[6:0] <= 7'd59;
    else if(w_ren)        r_rwordcnt[6:0] <= w_rwordcnt[6:0];
end

always @(posedge Clk_400) begin
    if (~test_SoftReset)   r_lstwen_1z <= 1'b0;
    else                   r_lstwen_1z <= (w_wlastword & w_wen);
end

assign w_wordstocknum[1:0] = ( r_lstwen_1z & ~(w_rlastword & w_ren) ) ? (r_wordstocknum[1:0] + 2'd1) :
                             (~r_lstwen_1z &  (w_rlastword & w_ren) ) ? (r_wordstocknum[1:0] - 2'd1) :
                                                                         r_wordstocknum[1:0];
assign w_wordstocken = r_lstwen_1z | (w_rlastword & w_ren);
always @(posedge Clk_400) begin
    if (~test_SoftReset)   r_wordstocknum[1:0] <= 2'd0;
    else if(w_wordstocken) r_wordstocknum[1:0] <= w_wordstocknum[1:0];
end

always @(posedge Clk_400) begin
    if (~test_SoftReset)         r_wordstock_almfull <= 1'b0;
    else if(w_rlastword & w_ren) r_wordstock_almfull <= 1'b0;
    else if(w_wordstock_almfull) r_wordstock_almfull <= 1'b1;
end

always @(posedge Clk_400) begin
    if (~test_SoftReset)         r_wordstock_full <= 1'b0;
    else if(w_rlastword & w_ren) r_wordstock_full <= 1'b0;
    else if(w_wordstock_full_p)  r_wordstock_full <= 1'b1;
end


always @(posedge Clk_400) begin
    if (w_wen) r_lbuf[r_wwordcnt[6:0]][551:0] <= {ab2re_WrAddr[19:0], ab2re_WrTID[15:0], ab2re_WrFence, ab2re_WrLen[1:0], ab2re_WrSop, ab2re_WrDin[511:0]};
end

assign w_selword[511:0]  = r_lbuf[r_rwordcnt[6:0]][511:0];
                           //   Y1                  V0                  Y0                  U0
assign w_sendword[511:0] = {
                                w_selword[ 15:  8], w_selword[ 23: 16], w_selword[ 31: 24], w_selword[  7:  0],
                                w_selword[ 47: 40], w_selword[ 55: 48], w_selword[ 63: 56], w_selword[ 39: 32],
                                w_selword[ 79: 72], w_selword[ 87: 80], w_selword[ 95: 88], w_selword[ 71: 64],
                                w_selword[111:104], w_selword[119:112], w_selword[127:120], w_selword[103: 96],
                                w_selword[143:136], w_selword[151:144], w_selword[159:152], w_selword[135:128],
                                w_selword[175:168], w_selword[183:176], w_selword[191:184], w_selword[167:160],
                                w_selword[207:200], w_selword[215:208], w_selword[223:216], w_selword[199:192],
                                w_selword[239:232], w_selword[247:240], w_selword[255:248], w_selword[231:224],
                                w_selword[271:264], w_selword[279:272], w_selword[287:280], w_selword[263:256],
                                w_selword[303:296], w_selword[311:304], w_selword[319:312], w_selword[295:288],
                                w_selword[335:328], w_selword[343:336], w_selword[351:344], w_selword[327:320],
                                w_selword[367:360], w_selword[375:368], w_selword[383:376], w_selword[359:352],
                                w_selword[399:392], w_selword[407:400], w_selword[415:408], w_selword[391:384],
                                w_selword[431:424], w_selword[439:432], w_selword[447:440], w_selword[423:416],
                                w_selword[463:456], w_selword[471:464], w_selword[479:472], w_selword[455:448],
                                w_selword[495:488], w_selword[503:496], w_selword[511:504], w_selword[487:480]
                            };

assign w_sendworden      = w_ren;
wire [7:0]   w_addridx = (r_rwordcnt[6:0] < 7'd60) ? (8'd59 - {1'b0, r_rwordcnt[6:0]} ) : (8'd179 - {1'b0, r_rwordcnt[6:0]} );
wire [19:0]  w_WrAddr  = r_lbuf[w_addridx[6:0]][551:532];
wire [15:0]  w_WrTID   = r_lbuf[w_addridx[6:0]][531:516];
wire         w_WrFence = r_lbuf[w_addridx[6:0]][515];
wire [1:0]   w_WrLen   = r_lbuf[w_addridx[6:0]][514:513];
wire         w_WrSop   = r_lbuf[w_addridx[6:0]][512];
512bit のバスには、各8bitの UYVYデータ64個が以下の順番でパックされているので、
(MSB)                                                           (LSB)
Y31 V31 Y30 U30 Y29 V29 Y28 U28 ..... Y3  V3  Y2  U2  Y1  V1  Y0  U0
以下のように並べ替えを行っています。
Y0  V1  Y1  U0  Y2  V3  Y3  U2  ..... Y28 V29 Y29 U28 Y30 V31 Y31 U30

作成した、nlb_lpbk.sv はこちらになります。

修正した回路を以下のコマンドで再合成します。

cd $OPAE_PLATFORM_ROOT/hw/samples/nlb_mode_0/build_synth
run.sh

出来上がった GBSファイルを書き込みます。

sudo fpgaconf $OPAE_PLATFORM_ROOT/hw/samples/nlb_mode_0/build_synth/nlb_400.gbs

以下の以前と同じ、ffmpegコマンドを実行します。

ffmpeg -s 1920x1080 -f rawvideo -pix_fmt uyvy422 -i input.yuv -vcodec pac_plugin output.yuv

以下のコマンドで映像を再生して、input.yuv と output.yuv が左右逆転していれば成功です。

ffplay -video_size 1920x1080 -f rawvideo -pixel_format uyvy422 input.yuv
ffplay -video_size 1920x1080 -f rawvideo -pixel_format uyvy422 output.yuv

(input.yuvの画像キャプチャ)

(output.yuvの画像キャプチャ)

おわりに

今回行ったFPGAアクセラレーションは、画像処理の負荷、作成したFPGAの処理の能力を考えると、オフロードやアクセラレーション効果はありませんが、FPGAアクセラレーションを繋げるまでの作業手順がどなたかのお役に立てれば幸いです。

References

13
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
4