前回 libFLAC を組み込めるか検討しましたが大変そうなのでやめました. そのあと... とても長い間やる気をなくしてしまいました.
WavPack を利用する
別途同目的の規格を探していたところ、 WavPack のダウンロードページに https://www.wavpack.com/downloads.html Tiny Decoder (Tiny Encoder もある)があるのを見つけたので利用してみました.
Tiny Decoder は10年以上更新されていないソースでしたが、現在のコンパイラ(x64 の clang)でも特に問題なく通り、最新バージョンの encoder で生成したファイルも問題なく decode できました. またドキュメントには当時の MCU で利用していることが限られた性能で十分に動いているし、 malloc を使っていないことを書いてあるのでこれを深く試すことにしました.
Tiny Decoder のソースを触る
これは本当に再生だけで seek 機能がないので、wavpack 規格を知ることも兼ねてをソースに手をいれました. 弱いと感じたのは順番にデータを読むので時刻とファイルオフセットを1度すべて読んでキャッシュを作る必要がある点でした.
ほかのソースも作りましたがこれが一番時間がかかりました.(3日間)
wavpack ですべての PCE の CD をエンコードする
data track も無関係にエンコードしたところ、平均圧縮率(decoded file size / original file size, 小さいほど優秀) は 61% でした. audio data だけでみるとおおよそ 1/2 ぐらいで、 flac とも同じに見えます.
平均ですので、最悪値もあり圧縮率 101% もありました. これは 1 disc で data track の割合が 100% のソフトがあったからで、それ以外にも data track が多いソフトは圧縮率 70% もあります.
PC 上の .wv file を再生すると当然ながら data track を audio track して再生したことになり不快な音がでてしまいます.
dll を利用して、専用 wavpack encoder を作ってみる
WavPack5FileFormat.pdf 内 2.0 Block Header に下記の記述があります.
typedef struct { char ckID [4]; // "wvpk" uint32_t ckSize; // size of entire block (minus 8) uint16_t version; // 0x402 to 0x410 are valid for decode uchar block_index_u8; // upper 8 bits of 40-bit block_index uchar total_samples_u8; // upper 8 bits of 40-bit total_samples uint32_t total_samples; // lower 32 bits of total samples for // entire file, but this is only valid // if block_index == 0 and a value of -1 // indicates an unknown length uint32_t block_index; // lower 32 bit index of the first sample // in the block relative to file start, // normally this is zero in first block uint32_t block_samples; // number of samples in this block, 0 = // non-audio block uint32_t flags; // various flags for id and decoding uint32_t crc; // crc for actual decoded data } WavpackHeader;
block_samples = 0 とした場合は non-audio block にできるとあるので、実際に生成されたファイルのヘッダを分析し、手を加えることにしました. block_samples = 0 として、こちらで data sector (中身だけ, 0x800 bytes) をいれて、audio data は 0 だけの無音をいれました.
重要なソースは下記です. ソース全体は https://pastebin.com/T3Hj2HMc でみてください.
const uint8_t compactdisc_data_header[] = { 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0 }; WavpackHeader h = {"wvpk", 0, 0x410, 0, 0, src_sector_num * config.block_samples, 0, 0, 0x14801821, 0}; assert(sizeof(h) == 0x20); (中略) int32_t *audio_src = b; if(memcmp(compactdisc_data_header, fb, sizeof(compactdisc_data_header)) == 0){ uint8_t compressed_data[0x800 * 4]; uint32_t compressed_size = (*packer)(fb + 0x10, 0x800, compressed_data, 0x800); if(compressed_size == 0){ compressed_size = 0x800; memcpy(compressed_data, fb+10, 0x800); } h.ckSize = 0x20 - 8 + compressed_size; h.crc = crc32(0, fb + 0x10, 0x800); fwrite(&h, 1, 0x20, d); fwrite(compressed_data, 1, compressed_size, d); audio_src = audio_silent; } r = WavpackPackSamples(w, audio_src, config.block_samples); assert(r == TRUE); r = WavpackFlushSamples(w); (以下略)
いまは .img のファイルだけみてるので data/audio 振り分けはヘッダの有無というとても雑なものですが、実験ということで細かい部分は後回しにします.
*packer に関しては data 用の可逆圧縮ルーチンで非力な MCU でも利用できる lz4 か lzf を選んでいれてみました. ただこれでも圧縮できない場合は、もとのデータをそのままいれることにしました.
エンコードしたデータは PC 用の player (foobar2000, MPC-HC)で問題なく再生できます. ソース全体見ればわかりますがこちらが作成したのは1つのCファイルでこれも1日間で作れました.
非可逆オーディオもいれられる
data track は可逆圧縮(または無圧縮)は必須ですが、オーディオは非可逆でもなんとかなる気がしたので、 wavpack の hybrid mode を利用してみました. これだと audio data が 1/4 程度になります.
音質に関しては詳しくないのですが、違和感はありませんでした.
これも同様に PC 用の player でも再生できますし、 tiny decoder でも問題なく decode できました.
ソフト別で圧縮率を比較する
全部を見たい方は http://dev.upergrafx.com/download/cdimage_fraction_ratio.csv .
0.82 0.18 0.61 0.578 0.577 0.27 0.51 (average) audio data wv/img lz4/img lzf/img lossy/img chd/img name 0.00 1.00 1.01 0.84 0.84 0.85 0.61 jccd3012_sherlock_holmes_no_tantei_kouza 0.00 1.00 1.00 0.86 0.85 0.86 0.61 jccd1004_sherlock_holmes_no_tantei_kouza 0.02 0.98 0.95 0.66 0.63 0.66 0.42 nipr1002_de-ja 0.12 0.88 0.89 0.73 0.72 0.69 0.54 kmcd4007_tokimeki_memorial (中略) 0.97 0.03 0.60 0.61 0.61 0.21 0.57 nxcd3019_downtown_nekketsu_monogatari 0.97 0.03 0.60 0.60 0.60 0.21 0.56 napr1025_forgotten_worlds 0.93 0.07 0.60 0.61 0.61 0.23 0.56 nxcd2010_double_dragon2the_revenge 0.97 0.03 0.60 0.62 0.62 0.21 0.57 fccd4001_shin_nihon_pro_wrestling_kounin (中略) 0.90 0.11 0.43 0.39 0.39 0.24 0.35 hcd4064_deden_no_den 0.86 0.15 0.41 0.40 0.40 0.19 0.35 tjcd1015_cosmic_fantasy2bouken_shounen_b 0.93 0.08 0.40 0.39 0.39 0.22 0.34 nscd0002_sol_bianca 0.96 0.04 0.37 0.38 0.38 0.20 0.33 nxcd2011_wizardry5heart_of_the_maelstrom
値の説明
- audio, data: 1 disc の audio/data の割合. 2つを足すと1になる.
- wv/img: 全部audioとして encode した .wv file size / original file size
- lz4/img: audio は可逆 wavpack, data は lz4 + 無音で encode した圧縮率
- lzf/img: 上記から lz4 を lzf にかえたもの
- lossy/img: lz4/img から audio は hybrid (非可逆) wavpack にかえたもの
- chd/img: mame の chdman で encode した chd file size / original file size
- 平均圧縮率は lz4:0.578, lz4:0.577 でほんのわずか lzf が優秀
考察
付加データ
.wv ファイルに cuesheet とジャケット画像をいれられるので、ぱっとみが非常によくできます. 画像は web 上から勝手にとってきました.
ちょっと気になるのは cuesheet や tag の文字コードが混沌としていて、palyer 側は UTF-16LE で非ASCII文字がでる可能性が高いです. 規格上は UTF-8 と書いてるんですが大丈夫なのでしょうか. また古参ユーザが無意識に Windows-31J をねじ込んできそうで混乱はありそうです.