
計測データを動画プレイヤーで再生したいみなさん、
こんにちは。ソリューションアーキテクトの伊勢です。
こちらは aptpod Advent Calendar 2025 12月2日の記事です。
今回はintdashの計測データをMP4ファイルとして出力する方法をご紹介します。
はじめに
intdashのMedia Explorerでは、計測の映像をMP4ファイルでダウンロードできます。1

また、Meas Converterでは、車両データ解析で利用されるMDFファイルでダウンロードも可能です。
複数データの出力
利用用途によっては複数データを統合してファイル出力したいケースもあります。
今回は、より複雑な構成のMP4ファイルを出力してみます。
MP4ファイルとは
動画や音声をひとつのファイルにまとめて扱える形式です。
再生時には内部の映像や音声を取り出し、タイムスタンプに基づいて同期再生します。
おおまかな構成は以下のようになっています。
今回はこれらのトラックを統合(多重化)します。
trak (video): ビデオトラックtrak (audio): オーディオトラックtrak (subtitle): 字幕トラック
MP4 File ├─ ftyp # ファイルタイプ情報 (ブランド識別) ├─ moov # メタ情報(再生に必要な情報の塊) │ ├─ mvhd # ムービーヘッダ(全体の時間情報など) │ ├─ trak (video) # ビデオトラック │ │ ├─ tkhd # トラックヘッダ │ │ ├─ mdia # メディア情報 │ │ │ ├─ mdhd # メディアヘッダ(時間単位など) │ │ │ ├─ hdlr # ハンドラ(映像/音声の区別) │ │ │ ├─ minf # メディア情報 │ │ │ │ ├─ stbl # サンプルテーブル │ │ │ │ │ ├─ stsd # サンプル記述子(コーデック情報) │ │ │ │ │ │ ├─ avc1 (AVC/H.264 codec box) │ │ │ │ │ │ │ ├─ avcC (AVCDecoderConfigurationRecord) │ │ │ │ │ │ │ │ ├─ SPS (Sequence Parameter Set) │ │ │ │ │ │ │ │ └─ PPS (Picture Parameter Set) │ │ │ │ │ ├─ stts # 時間情報(各サンプルのduration) │ │ │ │ │ ├─ stsc # チャンク構造 │ │ │ │ │ ├─ stsz # サンプルサイズ │ │ │ │ │ └─ stco # チャンクオフセット │ ├─ trak (audio) # オーディオトラック │ │ ├─ tkhd │ │ ├─ mdia │ │ │ ├─ mdhd │ │ │ ├─ hdlr │ │ │ ├─ minf │ │ │ │ ├─ stbl │ │ │ │ │ ├─ stsd # サンプル記述子(例: mp4a) │ │ │ │ │ ├─ stts │ │ │ │ │ ├─ stsc │ │ │ │ │ ├─ stsz │ │ │ │ │ └─ stco │ └─ trak (subtitle / others) # 他トラック (例: 字幕) │ ├─ mdat # メディアデータ本体(映像フレームや音声サンプル) │ ├─ [Video Sample 1] → H.264フレーム (AVCC形式: length-prefixed NALU) │ ├─ [Video Sample 2] → H.264フレーム │ ├─ ... │ ├─ [Audio Sample 1] → AACフレーム │ ├─ [Audio Sample 2] │ └─ ... └─ free / udta / meta # 追加のメタデータ(オプション)
やってみた
おおまかな構成です。
iPhoneアプリ intdash Motion で取得した計測データをもとにします。
データ種類ごとにファイルを出力し、最後にMP4ファイルに多重化します。2
- 音声:PCMデータ → WAVファイル
- 映像:H.264データ → バイナリファイル
- 字幕:GNSSデータ(高度・速度・緯度経度) → SRTファイル
- 多重化後:MP4ファイル

出力パターン①:音声
シンプルな例として、音声ファイルをWAVファイルに出力します。3
対象のエッジと開始時刻〜終了時刻を指定して実行します。

出力パターン②:音声 + 映像
続いて、複数トラックを多重化します。音声 + 映像を出力するパターンです。

多重化で警告が出ています。
これは、開始時刻〜終了時刻を直接指定したためで、動画の基準点であるIDRフレームが最初に現れるまで映像フレームが生成されず、黒一色で表示されます。 4
[h264 @ 0x157704b90] no frame! [h264 @ 0x157704b90] non-existing PPS 0 referenced
出力パターン③:音声 + 映像 + 字幕
最後に音声 + 映像 + 字幕を出力・多重化する例です。
計測を指定して、計測の開始〜終了時刻のデータを対象とします。
字幕の緯度経度を逆ジオコーディングで住所に変換するため、Google Geocoding APIのAPIキーを指定しています。

起動オプション
データ取得オプション
--api_urlrequired:サーバーURL--api_tokenrequired:APIトークン--project_uuid:プロジェクトUUID(省略時は Global Project)- いずれか
required- 計測指定
--meas_uuid:計測UUID
- エッジ+時間範囲指定
--edge_uuid:エッジUUID--start:開始時刻、RFC3339形式--end:終了時刻、RFC3339形式
- 計測指定
出力オプション
--outdir:出力先ディレクトリ(省略時は./out)--tracks:出力するトラックaudio,video,subtitleを指定(複数可、省略時はすべて)--fps:映像の入力フレームレート(映像出力時、省略時は15)--gmap-api-key:Google Maps API Key(字幕出力時、逆ジオコーディングに使用、省略時は緯度経度を出力)
多重化オプション
--mux:WAV / H.264 / SRT を MP4 に多重化(省略時は多重化なし)
サンプルプログラム
多重化にはSDK入門③でインストールしたffmpegを利用しています。

データ取得
intdash REST APIからのデータ取得はメモリ消費を抑えるため、チャンク転送エンコーディングでリクエストしています。
api = measurement_service_data_points_api.MeasurementServiceDataPointsApi(
self.client
)
params: dict[str, object] = {
"project_uuid": self.project_uuid,
"name": self.meas_uuid if self.meas_uuid else self.edge_uuid,
"time_format": "ns",
"_preload_content": False, # 全データロードの抑止
}
if self.start:
params["start"] = self.start
if self.end:
params["end"] = self.end
if self.data_id_filter:
params["data_id_filter"] = self.data_id_filter
stream = api.list_project_data_points(**params)
データ変換
Motionで収集した音声を単純に繋げるだけだと、zzzzやppppと聞こえるジッタノイズが入っているため、除去します。
PCMデータサンプル間の時間揺らぎを等間隔サンプリングします。
f32 = decode_pcm_s16le(data_bytes) # bytes -> float32 f32_out = self.resampler.push_block(t_rel, f32) if f32_out.size: i16 = encode_pcm_s16le(f32_out) # float32 -> bytes
データ出力
映像と音声はメモリに溜め込まず、1件ずつ出力しています。
if data_name == "1/pcm": ... self._wav.write(i16) ... elif data_name == "1/h264": ... self._h264.write(data_bytes)
また、字幕はGNSSの複数項目を統合して生成します。
高度・速度・緯度経度・住所の字幕セグメントに集約します。
elif data_name == "1/gnss_speed" ... aggregator.update_speed(v) ... elif data_name == "1/gnss_altitude": ... aggregator.update_altitude(alt) ... elif data_name == "1/gnss_coordinates": ... changed, lat_q, lon_q = aggregator.update_latlon(lat, lon) ... aggregator.update_address(addr)
集約した字幕は高度が更新されたタイミング、約1秒に1回出力します。
seg = aggregator.on_tick(t_rel) # 高度は約1Hz
if seg:
self._write_segment(seg)
今回は1回だけREST APIへのリクエストして、データ名で処理を分岐しています。5
for t_ns, _, data_name, data_bytes in self.reader.get_datapoints(): ... if data_name == "1/pcm": ... elif data_name == "1/h264": ... elif data_name == "1/gnss_speed": ... elif data_name == "1/gnss_altitude": ... elif data_name == "1/gnss_coordinates":
MP4多重化
音声・映像・字幕それぞれで最初のデータポイントの相対時刻が異なるため、計測開始からのオフセット(ffmpegの-itsoffsetオプション)を与えて調整しています。
cmd += [
...
"-itsoffset",
f"{opts.v_offset:.6f}",
"-i",
str(inputs.video),
...
cmd += ["-itsoffset", f"{opts.a_offset:.6f}", "-i", str(inputs.audio)]
...
cmd += ["-itsoffset", f"{opts.s_offset:.6f}", "-i", str(inputs.subtitle)]
おわりに
今回は、複数データを一般的な動画ファイルに多重化する例を紹介しました。
マルチモーダルデータも、SDKを使えば、自由に整形が可能です。
intdashはデータの時系列が管理されているため、出力時にもデータが同期されます。
なお、今回は海外eSIMを使ったiPhoneで計測を行いました。
海外での計測は以下の記事でも検証しています。
リンク
本シリーズの過去記事はこちらからご覧ください。
- SDK入門①〜社用車で走ったとこ全部見せます〜 :REST APIでデータ取得
- SDK入門②〜データ移行ツールの作り方〜:REST APIでデータ送信
- SDK入門③〜RTSPで映像配信するぞ〜:リアルタイムAPIでデータ取得
- SDK入門④〜YOLOで物体検知しちゃう〜:リアルタイムAPIでデータ送信
- SDK入門⑤〜iPadでData Visualizerを見る会〜:リアルタイムAPIでキャプチャデータ送信
- SDK入門⑥〜最速最高度で計測する日〜: AWS LambdaでREST APIデータ送信
- SDK入門⑦〜計測リプレイツールの作り方〜: REST APIでデータ取得、リアルタイムAPIでデータ送信
- SDK入門⑧〜動画アップロードツールの作り方〜:REST APIで映像データ送信