Jetsonのカメラドライバを書く[モード追加とGStreamer編]

# はじめに

前の記事: [Jetsonのカメラドライバを書く[Argus撮影編]](https://qiita.com/uzuna/items/675d4f43e2bd97e6d0b9)
次の記事: [Jetsonのカメラドライバを書く[デバッグ編]](https://qiita.com/uzuna/items/9ed06f93bed47d3df153)

前回、無事絵を取ることが出来ました。
しかしGStreamerで取得が出来ませんでした。
画像の大きさに問題があるようなので、モードを増やしてGStreamerで取得をしてみます。

# モードの追加

イメージセンサは全画素撮影以外にも幾つかモードを持っているのが一般的です。
ドライバを見た感じ、IMX708は2x2binning、[DOL HDR(Digital-Overlap High Dynamic Range)](https://www.sony-semicon.com/ja/technology/security/index.html)に対応してそうです。
特に[binning](https://www.photometrics.com/learn/camera-basics/binning)は画角を維持しながら高fpsで低ノイズ(平均化によって)に出来るので、高解像度を必要としないケースではとても助かります。

HDRはちょっと調べないと難しそうなのでまたの機会に。
今回は2x2binningの移植をします。

## 2x2Binningモード

移植するモードのRaspberryPiの実装です
```imx708.c
{
    /* regular 2x2 binned. */
    .width = 2304,
    .height = 1296,
    .line_length_pix = 0x1e90,
    .crop = {
        .left = IMX708_PIXEL_ARRAY_LEFT,
        .top = IMX708_PIXEL_ARRAY_TOP,
        .width = 4608,
        .height = 2592,
    },
    .vblank_min = 40,
    .vblank_default = 1198,
    .reg_list = {
        .num_of_regs = ARRAY_SIZE(mode_2x2binned_regs),
        .regs = mode_2x2binned_regs,
    },
    .pixel_rate = 585600000,
    .exposure_lines_min = 4,
    .exposure_lines_step = 2,
    .hdr = false
},
```

こちらも[前回](https://qiita.com/uzuna/items/05c169c91460e60fecea#%E9%A7%86%E5%8B%95%E3%83%A2%E3%83%BC%E3%83%89%E3%82%92%E6%B1%BA%E3%82%81%E3%82%8B)のように計算すると以下のような設定となります。

| item | value |
| --- | --- |
| width | 2304 |
| height | 1296 |
| HSYNC(line_length) | 7824 |
| VSYNC | 2494 |
| pixel_rate(pix/s) | 585600000 |
| fps | 30.01067593 |
| hsync(us) | 13.36065574 |
| step exp(us) | 26.72131148 |
| min exp(us) | 53.44262295 |
| max exp(us) | 32680.16393 |


### コード追加作業

移植だけなので簡単ですね。
- デバイスツリーにモードの追加
- レジスタ設定の追加
- 動作確認


#### device treeにモードを追加

```hardware/nvidia/platform/t23x/p3768/kernel-dts/cvb/tegra234-p3768-0000-a0.dtsi
mode1 {
    mclk_khz = "24000";
    num_lanes = "2";
    tegra_sinterface = "serial_c";
    phy_mode = "DPHY";
    discontinuous_clk = "no";
    dpcm_enable = "false";
    cil_settletime = "0";
    lane_polarity = "0";

    active_w = "2304";
    active_h = "1296";
    mode_type = "bayer";
    pixel_phase = "rggb";
    csi_pixel_bit_depth = "10";
    readout_orientation = "0";
    line_length = "7824";
    inherent_gain = "1";
    pix_clk_hz = "300000000";

    gain_factor = "1";
    framerate_factor = "1000000";
    exposure_factor = "1000000"; /* us */
    min_gain_val = "112";
    max_gain_val = "960";
    step_gain_val = "1";
    default_gain = "112";
    min_hdr_ratio = "1";
    max_hdr_ratio = "1";
    min_framerate = "30010000";
    max_framerate = "30010000";
    step_framerate = "1";
    default_framerate = "30010000";
    min_exp_time = "52";
    max_exp_time = "32680";
    step_exp_time = "13";
    default_exp_time = "5000";

    embedded_metadata_height = "4";
};
```

#### レジスタ設定とモードリストの追加

レジスタ値は一箇所だけ変更します。`VSYNC=2924`のところですね。

```diff
static const imx708_reg imx708_mode_2304x1296_30fps[] = {
	{0x0342, 0x1E},
	{0x0343, 0x90},
-	{0x0340, 0x05},
-	{0x0341, 0x38},
+	{0x0340, 0x09},
+	{0x0341, 0xBE},
```

```nvidia/drivers/media/i2c/imx708_mode_tbls.h
enum {
	IMX708_MODE_4608x2592_14FPS,
	IMX708_MODE_2304x1296_30FPS,

	IMX708_MODE_COMMON,
	IMX708_START_STREAM,
	IMX708_STOP_STREAM,
};

static const imx708_reg *mode_table[] = {
	[IMX708_MODE_4608x2592_14FPS] = imx708_mode_4608x2592_14fps,
	[IMX708_MODE_2304x1296_30FPS] = imx708_mode_2304x1296_30fps,

	[IMX708_MODE_COMMON] = imx708_mode_common,
	[IMX708_START_STREAM] = imx708_start,
	[IMX708_STOP_STREAM] = imx708_stop,
};

static const int imx708_14_fr[] = {
	14,
};

static const int imx708_30_fr[] = {
	30,
};

static const struct camera_common_frmfmt imx708_frmfmt[] = {
	{{4608, 2592}, imx708_14_fr, 1, 0, IMX708_MODE_4608x2592_14FPS},
	{{2304, 1296}, imx708_30_fr, 1, 0, IMX708_MODE_2304x1296_30FPS},
};
```

### 動作確認

gstreamerで撮影

```sh
gst-launch-1.0 nvarguscamerasrc sensor-id=0 sensor-mode=1 num-buffers=60 ! 'video/x-raw(memory:NVMM), width=1920, height=1080,format=NV12' ! nvvideoconvert ! x264enc ! h264parse ! qtmux ! filesink location=test.mp4
```

動画を貼れないので省略しますが、おそらくこのようなログが出て`test.mp4`が作られたと思います。


```
Setting pipeline to PAUSED ...
Pipeline is live and does not need PREROLL ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
Redistribute latency...
GST_ARGUS: Creating output stream
CONSUMER: Waiting until producer is connected...
GST_ARGUS: Available Sensor modes :
GST_ARGUS: 4608 x 2592 FR = 14.350001 fps Duration = 69686408 ; Analog Gain range min 112.000000, max 960.000000; Exposure Range min 210000, max 68406000;

GST_ARGUS: 2304 x 1296 FR = 30.010000 fps Duration = 33322226 ; Analog Gain range min 112.000000, max 960.000000; Exposure Range min 52000, max 32680000;

GST_ARGUS: Running with following settings:
   Camera index = 1 
   Camera mode  = 1 
   Output Stream W = 2304 H = 1296 
   seconds to Run    = 0 
   Frame Rate = 30.010000 
GST_ARGUS: Setup Complete, Starting captures for 0 seconds
GST_ARGUS: Starting repeat capture requests.
CONSUMER: Producer has connected; continuing.
Got EOS from element "pipeline0".
Execution ended after 0:00:04.395651803
Setting pipeline to NULL ...
GST_ARGUS: Cleaning up
CONSUMER: Done Success
GST_ARGUS: Done Success
Freeing pipeline ...
```

フレームレートがわかるように`fpsdisplaysink`でみてみます。

```
gst-launch-1.0 nvarguscamerasrc sensor-id=1 sensor-mode=1 ! nvvideoconvert ! fpsdisplaysink
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92809/eca7c1c6-7a18-0352-1cf0-1f58ef943697.png)

無事30fpsで撮影できました :tada: 
写っているのはカーテンです。室内灯だけなので200lxもないはずですが、結構見えてますね。

## Cropモード

単純移植は簡単でちょっと物足りないですね。
もう一つCropされたモードがあるのでレジスタの意味を推測しながら追加してみます。

## モードの追加


`566400000/(5216*904) = 120.1205`となるので、これで120fps出るはずです。

| item | value |
| --- | --- |
| width | 1536 |
| height | 864 |
| HSYNC(line_length) | 0x1460=5216 |
| VSYNC | 904 |
| pixel_rate(pix/s) | 566400000 |
| fps | 120.1205 |
| hsync(us) | 6.168 |
| step exp(us) | 24.672 |
| min exp(us) | 49.344 |
| max exp(us) | 4120.224 |

### Cropモードのレジスタ設定

2x2binningモードも画素読み出し自体は全画素が対象でした。
今度はCropするためActiveAreaを明示的に指定します。
既存のレジスタ設定の違いを読むと、おそらくレジスタの意味は次のようになっていると思われます。
データレートの設定がありそうですがこちらはわからず。
しかし大きくサイズが変わらない限り欲しい形状の画像を得ることはできそうです。

```c
static const imx708_reg imx708_mode_1536x864_120fps[] = {
	/* HSYNC 5216 */
	{0x0342, 0x14},
	{0x0343, 0x60},
	/* VSYNC 904 */
	{0x0340, 0x03},
	{0x0341, 0x60},
	/* active area left */
	{0x0344, 0x03},
	{0x0345, 0x00},
	/* active area top */
	{0x0346, 0x01},
	{0x0347, 0xB0},
	/* active area right */
	{0x0348, 0x0E},
	{0x0349, 0xFF},
	/* active area bottom */
	{0x034A, 0x08},
	{0x034B, 0x6F},

	/* width 1536 */
	{0x040C, 0x06},
	{0x040D, 0x00},
	/* height 864 */
	{0x040E, 0x03},
	{0x040F, 0x60},
	/* width 1536 */
	{0x034C, 0x06},
	{0x034D, 0x00},
	/* height 864 */
	{0x034E, 0x03},
	{0x034F, 0x60},
```

# GStreamerで撮影、記録、配信

いつものようにビルドして転送します。
フレームレートがわかるように`fpsdisplaysink`で撮影

```
gst-launch-1.0 nvarguscamerasrc sensor-id=1 sensor-mode=1 ! nvvideoconvert ! fpsdisplaysink
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92809/00555cef-cf3f-4881-789a-a163cfc1c0b2.png)

期待通り120fpsを超えました :tada:
画角も少し狭くなっているのがわかりますね。

# まとめ

今回はモードの追加をしてGStreamerで記録、プレビューをしました。
GStreamerで読み出せれば加工や配信、アプリケーションとの連携など比較的容易に実現できます。

またモードを比較することでどのレジスタを変更したら、限られたモードしかないUVCカメラと比較して欲しい撮影モードを自分で作れるのが良いですね。

ほとんど単なる移植なのでうまく行きました。
次回、うまく行かない時に使える調査方法の話をします。

この記事時点のソースコードは[GitHub](https://github.com/uzuna/jetson-imx708/tree/31d4be7ae676160896339de347d403e6b039bea5)で公開しています。