ドライブレコーダーのデータ解析をした話

# 初めに

[IoTLT Advent Calendar 2021](https://qiita.com/advent-calendar/2021/iotlt) に投稿した記事です。ここでは、私が主催してます **KURUMA IoTLT** で発表した内容を書き起こしました。

ここで解析した内容の正確性につきましては保証できませんので、引用する際にはご注意くださいますようお願いいたします。

**KURUMA IoTLT** は、車とIoTの組合せをテーマにしたLightning Talkの発表会です。一般的にIoTはインターネットに繋がった物( **Internet of Things** )を指しますが、自動車のコネクテッド化は現段階ではまだまだ進んでいない状況ですので、 **Internet or Things** としています。ですので、インターネットに繋がっていないマイコンでもいいですし、スマホアプリのようにモノの要素が無いテーマでもウェルカムです。

# ドライブレコーダーに記録されているデータについて

世の中には様々な種類のドライブレコーダーが販売されています。カメラで映像を記録するだけでなくGPSやGセンサーが搭載されているものもあり、これらのデータを専用のソフトウェアを使って表示したり、映像に表示させることが可能となってます。

なお、業務用のドライブレコーダーにはウィンカーの作動状況やハンドル操作など車両からの情報も記録するような高度な機器も存在しますが、本記事では扱いません。一般的な家庭用レベルの機器を前提として話をすすめます。

## GPS (Global Positioning System)

今いる場所を把握するためのモジュールで、スマートフォンやカーナビ等にも搭載されている身近な存在となってます。正確には **GNSS** (Global Navigation Satellite System: 全球測位衛星システム)と言わなければならないのですが、Geolocation方面ではない人にとっては「GPS」という方が馴染み深いと思うので、ここでは敢えてGPSと表記しています。

GPSモジュールからは次のデータが取得できます。ただし下3つ(高度、進行方向、位置情報の精度)に関しては映像記録の観点では不要となることから、記録されない機種も存在します。

| データ名 | 利用目的 |
|:--------|:-------|
| 緯度経度 | 映像を記録した場所を地図に表示するため |
| 時刻 | 映像を撮影した時刻を記録するため |
| 速度 | 映像を記録した時点の走行速度を記録するため |
| 高度 | 走行した場所の標高を記録するため |
| 進行方向 | どの方向に向かって走行していたかを把握するため |
| 位置情報の精度 | GPSから得られた緯度経度の正確性を把握するため |

## Gセンサー

Gは重力(Gravity)です。「加速度センサー」や「IMU」と呼ばれることがあります。スマートフォンにも同様のセンサーがあり、重力方向を検知して画面の向きを変えるのに使用されますが、車にGセンサーを取り付けた場合は車が何かにぶつかった時の衝撃検知や、道路の凹凸検知、運転操作の滑らかさ(急の付く操作を行ってないか)の判定に使われます。

機種により1秒間に1～10サンプルと幅があり精度が異なることや、ドアの開閉や手や小物がぶつかった時の衝撃によるノイズ成分などで信頼性としては高くないデータとなります。

## 解析した機種

今回見ていくデータの機種は **Pioneer VREC-DH300D** です。この機種を選んだ理由はもちろんGPSとGセンサーの搭載は前提としてますが、Pioneerの **この機種だけなぜかPC用アプリが非対応** だったので、どんな状況なのかを知りたくて調査の目的として選びました。

Pioneer ドライブレコーダー 主な機能比較表
https://jpn.pioneer/ja/carrozzeria/recorder_sd/list/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2267750/e1d302f1-3fd3-2754-d0b9-bd062d26e935.png)

### Pioneer VREC-DH300Dの仕様

解析に関係する所だけ (映像の良し悪しには触れない為) ですが、ドライブレコーダーの仕様をまとめました。

| 項目 | 値 | 備考 |
|------|----|---|
| カメラ数 | 2カメラ(前後) | 前:WQHD、後:Full HD|
| 動画形式 | H.264(MP4) / H.265(MOV) | 初期状態はH.264でした。変更せずに使ってます |
| microSD容量 | 16 ～ 128GB | 出来るだけ多くのデータを残すために256GBを使ってます(自己責任で) |
| 音声録音 | ON / OFF | 音声は記録するようにしています |
| GPS | 搭載 | GLONASS、みちびきの対応は不明(恐らく非対応) |
| Gセンサー | 3軸センサー搭載 | |
| 動画ファイル長さ | 1分 / 3分 | 3分のファイルを解析対象としてます |

# 解析する

解析の前段階として、まずはドライブレコーダーを動かしてみて、microSDに書き込まれるファイルとディレクトリ構造を確認します。

## ディレクトリ構造とファイル

microSDの中身を確認すると、最上位には `Event` と `Video` の2つのディレクトリのみとなってました。一応、隠しファイルも確認してますが存在してません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2267750/be1d57bc-fa42-3f39-b0bb-14ce94f82565.png)

`Event` は強い衝撃が加わった時に記録されるディレクトリで、 `Video` は通常時の映像が入っていくディレクトリです。怪しい所は見当たりません。

`Video` ディレクトリを見ると、MP4の動画ファイルが格納されているだけでした。Pioneerの他の機種では `nmea` という拡張子のファイルが存在して、GPSモジュールが出しているテキストをそのまま書き出しているようだったのですが、VREC-DH300Dは異なるようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2267750/346a1ebe-f9db-9daa-aaf3-7e5fe03034a2.png)

前後のカメラで `... A.MP4` と `... B.MP4` と別のファイルとなってました。

## MP4ファイルの構造 

MP4ファイルしか存在しないとの事なので、MP4ファイルの中を見ていきます。

### MP4(MPEG4)の仕様

ここでは大雑把な説明にとどめておきます。大元の仕様はISOで定義されており「ISO_IEC 14496」で検索すれば色々出てくると思います。

MP4ファイルはBOXと呼ばれるデータ領域の集まりとなってます。先頭4バイトでBOXの大きさを示し、続いて4バイトで `FourCC` と言う4文字のアスキーで何のデータが入っているかを表します。その後にデータ本体(Payload)が続きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2267750/8746a600-be2d-e4af-7025-3777f83d322d.png)

Payloadの中に、更に別のBOXが入って親子関係になる場合もあります。これによって階層構造を作り出しています。

FourCCについてはApple QuickTimeのドキュメントにも詳しく出ています。

https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html

なぜAppleなのかと疑問に思う人がいるかもしれませんが、元々Appleが作っていたQuickTimeフォーマットが後にMPEG4としてISOで標準化された経緯があるためです。ちなみにドライブレコーダーにもMOV(Quick Time)形式で書き出す機種もありますが、ファイル構造においてはMP4と同じです。ただし、「BOX」の事を、QuickTimeでは「ATOM」と呼ぶ点が異なります。ドキュメントを見る際にはお気を付けください。

## VREC-DH300Dの動画ファイルで変わっている所

解析を進めるにはBOXを調べていけばいいのですが、一般的な動画ファイルとVREC-DH300Dが書き出す動画ファイルで変わった点が無いか見ていきます。

### 見慣れないFourCCやBOXの外のデータ

MP4の規格にないFourCCやSizeで指定してる領域の外に余分なデータを付けている可能性を疑ってみましたが、見慣れないFourCCは無く、Sizeからはみ出しているデータは存在しませんでした。

一見すると、VREC-DH300Dが書き出すファイルは規格に沿っている、まともなMP4ファイルに見えます。

### 見慣れないトラック

ffprobeで確認すると3つのストリームがあり、3番目にメタデータのトラックが存在してました。

```shell:ffprobeの実行結果
$ ffprobe 2021_07_18_165224_A.MP4 
ffprobe version N-98670-g8d48e8d Copyright (c) 2007-2020 the FFmpeg developers
  built with gcc 9.3.0 (GCC)
  configuration: --prefix=/usr --libdir=/usr/lib64 --shlibdir=/usr/lib64 --disable-debug --enable-shared --enable-pic --enable-gpl --enable-version3 --enable-nonfree --enable-doc --enable-ffmpeg --disable-ffplay --disable-avisynth --enable-gray --enable-avdevice --enable-avcodec --enable-avformat --enable-avfilter --enable-postproc --enable-bzlib --enable-fontconfig --enable-libcelt --enable-swresample --enable-swscale --disable-libopencore-amrnb --disable-libopencore-amrwb --disable-libopencv --disable-libdc1394 --enable-libfdk-aac --enable-libfreetype --enable-libgsm --enable-libmp3lame --enable-libopenjpeg --enable-librtmp --enable-libspeex --enable-libtheora --enable-libvidstab --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265 --disable-libxavs --enable-zlib
  libavutil      56. 58.100 / 56. 58.100
  libavcodec     58.100.100 / 58.100.100
  libavformat    58. 50.100 / 58. 50.100
  libavdevice    58. 11.101 / 58. 11.101
  libavfilter     7. 87.100 /  7. 87.100
  libswscale      5.  8.100 /  5.  8.100
  libswresample   3.  8.100 /  3.  8.100
  libpostproc    55.  8.100 / 55.  8.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '2021_07_18_165224_A.MP4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: mp42isom
    creation_time   : 2021-07-18T07:52:24.000000Z
  Duration: 00:03:00.00, start: 0.000000, bitrate: 11184 kb/s
    Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuvj420p(pc, bt709), 2560x1440, 10848 kb/s, 27 fps, 27 tbr, 108k tbn, 216k tbc (default)
    Metadata:
      creation_time   : 2021-07-18T07:52:24.000000Z
      handler_name    : ?Hisilicon VIDEO
    Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, mono, fltp, 125 kb/s (default)
    Metadata:
      creation_time   : 2021-07-18T07:52:24.000000Z
      handler_name    : ?Hisilicon AUDIO
    Stream #0:2(eng): Data: bin_data (gpmd / 0x646D7067), 44 kb/s (default)
    Metadata:
      creation_time   : 2021-07-18T07:52:24.000000Z
      handler_name    : ?Hisilicon MetaData
Unsupported codec with id 100359 for input stream 2
```

通常は映像で音声で2ストリームですので、メタデータとしてGPSとGセンサーのデータを格納している可能性が出てきました。

## 3番目のトラックからデータを抜き出す

ffprobeの出力に `Data: bin_data (gpmd / 0x646D7067)` という記述があったので `gpmd` とは何だろうと調べて、GoPro等で使われる周期的なセンサーのデータを格納する方法にたどり着きました。パーサーもあるようでここの情報とVREC-DH300Dのファイルを見ていきました。

https://github.com/gopro/gpmf-parser

調べた所、メタデータはGPMDの形式ではなさそうでした。

調査にあたって色々あったような気がするのですが細かい経緯は忘れました。メタデータストリームの `stbl (Sample Table)` の中の `stco (Chunk Offset)` のデータを読み取って、 `276バイト` の領域が `180(偶に181)個` 存在するのを見つけました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2267750/856924d6-6b8e-5d0c-f1df-bffd6b378992.png)

この数からすると、1秒間のデータが格納されていると想定されます。また、Chunk Offsetが示す `0x2A6832` アドレスから276バイト分を見てみると、「FMAS」という文字列から「DATEETAD」で終わる領域で規則性がありました。

最終的に、この276バイトの領域を読み解いて行ってデータを得ることができました。データを1つ見ていきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2267750/16c4010c-d44b-299a-32d8-3ede9519bc45.png)

その時に映像に埋め込まれた値は以下です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2267750/e41ba283-13ae-e786-daa5-dc8ffe5f3bb7.png)

### 時刻

時刻データは `0x60 から 0x66` の範囲

| 0x60 | 0x61 | 0x62 | 0x63 | 0x64 | 0x65 | 0x66 |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| E5 | 07 | 0B | 1A | 0E | 01 | 29 |
| 年 | 年 | 月 | 日 | 時 | 分 | 秒 |

* 年はリトルエンディアン
    * `0x07E5` → 2021 
* 時刻は日本時刻 (本体設定で変わるのかは未確認)

となり、 `2021年11月26日 14時01分29秒` が取得できました

### Gセンサー

Gセンサーは `0x6C から 0x77` の範囲です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2267750/0c8a584c-062e-9b9c-db00-53acd4a0e7d6.png)

1軸当たり4バイトと仮定して16進数をFloatに直してみます。

```javascript
console.log(Buffer.from([0x0d, 0x34, 0xd0, 0xbc]).readFloatLE(0));
console.log(Buffer.from([0x59, 0x63, 0x8d, 0x3f]).readFloatLE(0));
console.log(Buffer.from([0x09, 0x22, 0x88, 0xbd]).readFloatLE(0));

// 実行結果
-0.025415444746613503
1.104594349861145
-0.066471166908741
```

ドラレコの内部でどの向きにGセンサーが付けられているかでXYZの方向は変わりますが、1に近い値が重力方向で1Gに近い値が出ているように見えます。

Gセンサーの値は映像には書かれていないので、この結果があっているのかは不明です。

### 緯度経度

Gセンサーの後に `AEN` という文字列があり、その後 `0x7b から 0x83` までが緯度経度です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2267750/7de96c30-5af6-ea39-8949-10bc87d7a77b.png)

* 経度の"分"が2桁なのは、理由は分かりません
* 秒の若干のズレは、動画の埋め込みとメタデータの書き込みの間でタイムラグが生じている可能性を疑ってます
* `AEN` の `EN` は恐らく East(東経) と North(北緯) を表しているような気がします

### 速度

`0x84` に速度が入り、 `3C` → 60km/h となります

# 最後に

KURUMA IoTでは4輪自動車に限らず、もちろん2輪でも、更には車とは呼べなくても自分で操る動力機関を持って移動するための物まで幅広いテーマを扱っています。もし興味が沸いた方がおりましたら定期的に(3ヶ月間隔を目標に)開催しようと思っていますので、ご参加をお待ちしております。

https://iotlt.connpass.com/event/231255/