wav から TOYOTA のカーナビでメタデータを読める m4a を作る方法
トヨタ純正ナビの SD カード再生で、ffmpeg で作った m4a は音は鳴るのに曲名・アーティスト名・アルバム名が全部 (NO DATA) になる。macOS 標準の afconvert で作り直すと読めるようになる。
動くやり方
afconvert -f m4af -d aac -b 256000 input.wav output.m4a
タグは mutagen で書く。
from mutagen.mp4 import MP4
f = MP4("output.m4a")
f["\xa9nam"] = ["曲名"]
f["\xa9ART"] = ["アーティスト名"]
f["aART"] = ["アーティスト名"]
f["\xa9alb"] = ["アルバム名"]
f["trkn"] = [(4, 25)]
f.save()
afconvert は moov アトムを最初からファイル先頭に置き、タグ用の free 領域も確保する。-movflags +faststart に相当する後処理はいらない。
タグを書いた後に ffmpeg を通してはいけない。 -c copy であってもコンテナが ffmpeg 形式に組み直され、また読めなくなる。
なぜ ffmpeg ではダメなのか
ナビのパーサーが hdlr アトムを 34 バイト固定と決め打ちしているため。
hdlr は末尾に可変長の name フィールドを持つので、書き手によってサイズが変わる。
meta > hdlr |
mdia > hdlr |
|
|---|---|---|
| iTunes / afconvert | 34 (name はゼロ 2 バイト) | 34 (name 空) |
| ffmpeg | 33 (name はゼロ 1 バイト) | 45 (name は "SoundHandler") |
34 バイト決め打ちで次のアトムの位置を計算すると、ffmpeg 製のファイルでは 1 バイトずれた場所を ilst の先頭として読むことになる。そこにゴミを掴んで、タグが無いと判断する。音声トラックの読み出しは別経路なので再生だけは成功する。
ffmpeg は他にも、trak に edts (エディットリスト)、stbl に sgpd / sbgp を足す。iTunes と afconvert はどれも書かない。
効かない対策
以下はどれもナビの表示を変えなかった。
-movflags +faststartで moov をファイル先頭に移すftypを iTunes と同じにする (minor_versionを 0、compatible_brandsにmp42を含める)- アルバムアート (
covr) を入れる - 余計なタグを削る (
©cmtのコメント、©tooのエンコーダ名) - サンプリング周波数を 48kHz から 44.1kHz に落とす。そもそも 48kHz でも再生はできる
- mutagen で ffmpeg 製ファイルのタグを書き直す。mutagen は
hdlrを書き換えないので、ffmpeg の 33 バイトがそのまま残る
hdlr を 33 から 34 バイトへバイト単位で書き換えるパッチも試したが、これも足りない。mdia > hdlr が 45 バイトのままだと読めない。ffmpeg 側の差分を個別に潰していく方針は割に合わない。
著作権保護 (CPRM / SD-Audio) も無関係。CPRM 保護された音楽は SD_AUDIO フォルダに暗号化ファイルとして入り、鍵はカードの保護領域に書かれる。Finder でコピーしただけのファイルは原理的にその形式にならないし、カードに SD_AUDIO フォルダが無ければそもそも別の話になる。
手元のファイルを判定する
hdlr のサイズを見れば、実機に挿す前にわかる。
import struct
import sys
d = open(sys.argv[1], "rb").read(400000)
i = d.find(b"mdirappl") # meta の hdlr は mdir/appl の手前にある
h = d.rfind(b"hdlr", 0, i)
print("meta hdlr:", struct.unpack(">I", d[h - 4:h])[0])
f = d.find(b"hdlr") # 最初に出てくるのが mdia の hdlr
print("mdia hdlr:", struct.unpack(">I", d[f - 4:f])[0])
両方 34 なら読める。33 や 45 が出たら ffmpeg 製で、そのままでは読めない。
実機での切り分け方
再生自体は成功してしまうので、ファイルを眺めているだけでは原因にたどり着けない。条件を変えたファイルを並べたテストフォルダを作り、1 回の乗車でまとめて試すのが速い。
コツが 2 つある。
曲名タグに条件名を入れる。 T2.m4a の曲名タグを TAG2 OK にしておくと、画面に TAG2 OK と出ればタグを読んでいる、T2 と出ればファイル名にフォールバックしている、と運転席から一目で判別できる。
ID3v2 タグ付きの MP3 を 1 曲、対照群として同じフォルダに混ぜる。 その MP3 が読めて m4a が読めないなら、原因は m4a のコンテナ構造に絞られる。カードの状態やナビ側の挙動のように、同じフォルダの全ファイルへ等しくかかる要因はこれで外れる。
開発相談をお待ちしています。