発端
ドライブレコーダーを買ったんですよ。
買ったのはコムテックのHDR-352GH
GPS搭載モデルで、windowsだとビューワーソフトがあり、動画と軌跡を同時に閲覧することが出来ます。
ビューワーでは地図表示はあるもののGPSログの取り出しは出来ませんし、windows以外にはビューワーソフトがありません。
折角GPSのログも保存されているはずなので、なんとかして取り出してGPSロガーとしての機能も見いだしたいと思います。
動画を調べてみる
micro SDカードをドライブレコーダーから取り出してmacから読み出してみると、動画の拡張子はmov, 1分ごとに分割されていました。
隠しファイルでもGPSデータは保存されていません。
つまり動画の中に埋め込まれているようです。
動画と言えばffmpeg。
ffmpegに含まれているffprobeで解析してみます。
ffprobe 003_202423_A.MOV
ffprobe version 3.3.4 Copyright (c) 2007-2017 the FFmpeg developers
built with Apple LLVM version 8.0.0 (clang-800.0.42.1)
configuration: --prefix=/usr/local/Cellar/ffmpeg/3.3.4 --enable-shared --enable-pthreads --enable-gpl --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-libmp3lame --enable-libx264 --enable-libxvid --enable-opencl --enable-videotoolbox --disable-lzma --enable-vda
libavutil 55. 58.100 / 55. 58.100
libavcodec 57. 89.100 / 57. 89.100
libavformat 57. 71.100 / 57. 71.100
libavdevice 57. 6.100 / 57. 6.100
libavfilter 6. 82.100 / 6. 82.100
libavresample 3. 5. 0 / 3. 5. 0
libswscale 4. 6.100 / 4. 6.100
libswresample 2. 7.100 / 2. 7.100
libpostproc 54. 5.100 / 54. 5.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '003_202423_A.MOV':
Metadata:
major_brand : qt
minor_version : 0
compatible_brands:
creation_time : 2017-10-01T20:24:24.000000Z
Duration: 00:00:59.99, start: 0.000000, bitrate: 11264 kb/s
Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080, 10546 kb/s, 29 fps, 29 tbr, 90k tbn, 180k tbc (default)
Metadata:
creation_time : 2017-10-01T20:24:24.000000Z
handler_name : iCatchTek Video Handler
Stream #0:1(eng): Audio: pcm_s16le (sowt / 0x74776F73), 44100 Hz, 1 channels, s16, 705 kb/s (default)
Metadata:
creation_time : 2017-10-01T20:24:24.000000Z
handler_name : iCatchTek Audio Handler
動画の中身は分かりましたが、これでは位置情報に関して分からないですね。
ドライブレコーダーによっては字幕ファイルとして動画を合わせて保存しているものもあるようですが、字幕ファイルもない様子。
動画ファイルにどうやって埋め込まれているんでしょうか。
バイナリにそれっぽいデータがあるか見てみましょう。
xxd 003_202423_A.MOV | grep gps
17ef160: 3556 7367 7073 4b6a 6911 e1a9 3470 6ced 5VsgpsKji...4pl.
1c5a880: ef53 6770 73e4 547b d05f 92cb 3a28 9051 .Sgps.T{._..:(.Q
3dc9a00: 4872 3967 7073 6594 7eed 2376 071d cafa Hr9gpse.~.#v....
gpsでgrepしても見つからないので、バイナリデータ全体を覗いてみると
507c430: 2020 2020 5220 3030 3230 3041 3334 3635 R 00200A3465
507c440: 2e39 3635 344e 3133 3632 382e 3432 3930 .9654N13628.4290
507c450: 4530 3037 3431 3731 3030 3132 3032 3432 E007417100120242
507c460: 3400 2020 2020 2020 2020 3030 3230 302d 4. 00200-
507c470: 3030 3835 3030 3032 382d 3030 3137 4e4e 008500028-0017NN
507c480: 4e4e 4e4e 2020 2020 2020 2020 2020 2020 NNNN
3465.9654N13628.4290E
なんてそれっぽいですね。1
動画からデータを抜き出す
動画からデータを抜き出すにはMP4動画のバイナリファイルをコマンドラインでいじる方法のメモが大変参考になりました。
動画はBoxと呼ばれる単位で構造化されているので、読み出すのは簡単にできそうです。
先頭のBoxを読んでみましょう
irb(main):001:0> io = File.open '003_202423_A.MOV'
=> #<File:003_202423_A.MOV>
irb(main):002:0> length = io.read(4)
=> "\x00\x00\x00\x18"
irb(main):003:0> code = io.read(4)
=> "ftyp"
不要なData部は読み飛ばしたいので、lengthからバイトに変換します。
irb(main):004:0> size = length.unpack('H*').first.to_i(16)
=> 24
io = File.open '003_202423_A.MOV'
while length = io.read(4)
size = length.unpack('H*').first.to_i(16)
seek = size - 8
code = io.read(4)
p "code #{code}"
p "box size #{size} byte"
io.seek(seek, IO::SEEK_CUR)
end
これを実行すると
"code ftyp"
"box size 24 byte"
"code mdat"
"box size 84367444 byte"
"code moov"
"box size 27428 byte"
"code udat"
"box size 76928 byte"
"code ICAT"
"box size 12 byte"
Boxの種別の中でも以下は代表的なものなので
- ftyp:ファイルタイプ
- mdat:ビデオデータ自体
- moov:メディアのメタデータ
残るはudatかICAT、サイズ的にはudatが一番怪しいですね。3
io = File.open '003_202423_A.MOV'
while length = io.read(4)
size = length.unpack('H*').first.to_i(16)
seek = size - 8
code = io.read(4)
if code == 'udat'
udat = io.read(seek)
File.write 'udat', udat
else
io.seek(seek, IO::SEEK_CUR)
end
end
生成されたファイルの中身を確認すると、先ほどの緯度経度が入っていることを確認できます。
後は文字列から緯度経度を抜き出すだけですね。
GPXファイルを生成する
DMM形式の緯度経度では使いにくいので、変換した上でGPSファイルに書き出してみることにします。
ドライブレコーダーの動画は一度のドライブで一つのディレクトリ、動画は1分ごとに分割されています。
なのでディレクトリの中の動画の位置情報は一つのGPXファイルにまとめることにしてみます。
require 'pathname'
require 'rexml/document'
# DMM形式から変換
def dmm_to_dms str
number, fractional = str.split('.')
number[0...-2].to_r + "#{number[-2..-1]}.#{fractional}".to_r / 60
end
doc = REXML::Document.new
doc << REXML::XMLDecl.new('1.0', 'UTF-8')
gpx = REXML::Element.new('gpx')
gpx.add_attributes({
'creator' => "ruby",
'version' => "1.1",
'xmlns' => 'http://www.topografix.com/GPX/1/1',
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation' => 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd'
})
doc.add_element(gpx)
Pathname.glob("*.MOV") do |path|
if path.file?
p "#{path.basename.to_s} processing"
io = File.open path.basename.to_s
trk = REXML::Element.new('trk')
gpx.add_element(trk)
trkseg = REXML::Element.new('trkseg')
trk.add_element(trkseg)
while length = io.read(4)
size = length.unpack('H*').first.to_i(16)
seek = size - 8
code = io.read(4)
if code == 'udat'
udat = io.read(seek)
udat.scan(/A([0-9.]*)([NS])([0-9.]*)([EW])/) do |m|
# 緯度経度を読み込んで変換
lat = dmm_to_dms(m[0]).to_f * (m[1] == 'N' ? 1 : -1)
lon = dmm_to_dms(m[2]).to_f * (m[3] == 'E' ? 1 : -1)
trkpt = REXML::Element.new('trkpt')
trkpt.add_attributes({'lat' => lat, 'lon' => lon})
trkseg.add_element(trkpt)
end
else
io.seek(seek, IO::SEEK_CUR)
end
end
end
end
# GPXファイルを書き出し
File.open('output.gpx', 'w') do |file|
doc.write(file, indent=2)
end
これを実行するとGPXファイルが生成されます。
GoogleMapなどにインポートすると通った軌跡が描画されます。
緯度経度以外の情報も取り出す
udatの中には緯度経度の情報以外にも速度、時刻、加速度が含まれていました。
それぞれの動画トラックからそれらのデータをCSVにして取り出してみます。
require 'csv'
require 'pathname'
pat = /A([0-9.]*)([NS])([\d.]*)([EW])(\d{4})(\d{12})\s+(\d{5})([\d-]{1}\d{4})([\d-]{1}\d{4})([\d-]{1}\d{4})/
Pathname.glob("*.MOV") do |path|
if path.file?
p "#{path.basename.to_s} processing"
io = File.open path.basename.to_s
csv = CSV.open("#{path.basename.to_s.split('.').first}.csv", 'w') do |csv|
while length = io.read(4)
size = length.unpack('H*').first.to_i(16)
seek = size - 8
code = io.read(4)
if code == 'udat'
udat = io.read(seek)
udat.gsub(/[[:cntrl:]]/,"").scan(pat) do |m|
p m
csv << m
end
else
io.seek(seek, IO::SEEK_CUR)
end
end
end
end
end
CSVには
緯度, 緯度の符号, 経度, 経度の符号, 速度, UTC時刻, 経過時間(ミリ秒、60000でリセット), X加速度, Y加速度, Z加速度 が書き出されています。
Gemにする
ソースコードをきれいにして、Gem化したものをGithubに公開しました
コードはこちら
まとめ
ドライブレコーダーの動画の中身を覗いて、GPXファイルの取り出しをしました。
HDR-352GHは32GBまでのカードをサポートしており、最高画質でも6時間ほどの動画を残すことが出来ます。
さらにそれ以上の容量のカードでも認識するので、自分も64GBのカードを入れて使用しています。4