ポータブルプレイヤーをWalkmanからAndroidに
Live With Linux::dailyhack
- TOP
- Articles
- Live With Linux
- ポータブルプレイヤーをWalkmanからAndroidに
序
最近ウォークマンの調子が悪い。充電すると電池はフルを示す4ゲージになるのだが、10分程度で1ゲージに減ってしまう。その状態でも5時間程度は再生できるのだが、とはいえ3年足らずでこれは、とてもがっかりだ。
だが、それはそれとしても、ウォークマンにはかなりがっかりポイントが色々あった。
- サポートしているフォーマットにVorbis, Opusがない
- アルバムアートが
cover.jpg
ではなく、<アルバム名>.jpg
である必要がある - 接続端子が独自である上に、常時ゴムのカバー(非常に外れやすい)をつけておく必要がある
- Bluetooth接続が弱く、周囲にBluetoothイヤフォンを使っている人がいるとめちゃくちゃ音が飛ぶ
- Bluetooth接続ではイコライザは効かない
特に接続端子は面倒で、モバイルバッテリーを使うようなケースでは専用ケーブルを持ち歩く必要があるし、「ゴムカバーが外れた状態での故障は保証しない」という強気さで、でそのカバーが持ち歩いてるといつの間にか外れている。 なのでしょっちゅうゴムカバーのスペアを購入することになるし、ものすごく邪魔だ。
良いところとしては
- バッテリー持ちが良い
- 日本語のアーティスト名を読みで並べ替えてくれる
というのがあるのだが、これでバッテリーもちが悪くなってしまうと、あまり使う理由がない。
本記事はこれをきっかけにしてオーディオライブラリの持ち歩きを見直した話なのだが、多岐に渡ってかなりテクニカルなことをしているので、参考になったり、楽しめたりするのではないだろうか。
AAC, Vorbis, Opus
ウォークマンはVorbisをサポートしない関係でAACでエンコードする必要があった。公式サイトによるとサポートされているのは
- MP3 ( .mp3):32 - 320kbps (VBR対応) / 32, 44.1, 48kHz
- WMA ( .wma):32 - 192kbps (VBR対応) / 44.1kHz
- ATRAC ( .oma):48 - 352 kbps (66/105/132kbps は ATRAC3) / 44.1kHz
- ATRAC Advanced Lossless ( .oma):64 - 352 kbps (132 kbps は ATRAC3 base layer) / 44.1 kHz
- FLAC ( .flac):16, 24bit / 8-384kHz
- WAV ( .wav):16, 24, 32bit(Float/Integer) / 8-384kHz
- AAC ( .mp4, .m4a, .3gp):16-320kbps / 8-48kHz
- HE-AAC ( .mp4, .m4a, .3gp):32-144kbps / 8-48kHz
- Apple Lossless ( .mp4, .m4a):16, 24bit / 8-384kHz
- AIFF ( .aif, .aiff, .afc, .aifc):16, 24, 32bit/ 8-384kHz
- DSD ( .dsf, .dff):1bit / 2.8224, 5.6448, 11.2896 MHz
- APE ( .ape):8, 16, 24bit / 8-192kHz (Fast, Normal, High)
- MQA ( .mqa.flac):対応
とのことである。 SonyとしてはATRACを推したいのだろうけど、なかなか馬鹿げている。 そしてマイナーな形式をサポートしているにも関わらずVorbisをサポートしない。 この中で現実的に最も高音質なのはAACである。
だが、AACはMP3同様に、エンコーダによる音質の差異が大きい音声フォーマットである。
効率的な変換を考えると、音質と両立するにはfdkaacが有力だが、Manjaroの標準のffmpegはfdkaacを含まずにビルドされており、ffmpeg-libfdk_aac
というAURパッケージが必要となる。
しかし、ffmpegは様々なものから依存されており、AURパッケージを使うとアップデート時にかなり困ることがある。
ffmpegを使わず、fdkaac(1)を使うという方法もあるが、メタデータ取り扱いにおいて満足できる結果にならなかったため、結局ffmpegを使う方法に戻っている。
AACの場合、.aac
(ADTS)だとメタデータの扱いでものすごく困ることが多々あるが、.m4a
(MPEG4)だと非常に安定している。だが、取り扱いという意味ではFLACほどじゃないけれどちょっとクセがあるので、歓迎するかというと、正直あんまり歓迎しない。
音質の話をするならば、192kbps以上のAACは非常に良くて、特にAppleのエンコーダは非常に優れている。 Opusはかなり良いが、fdkaacと良い勝負といったところだ。
つまり、現代においてVorbisはわずかに音質面で見劣りする。
一方、Opusもサポートは難しい。Opusをサポートする機器が少なく、AndroidはOpusをサポートするものの、拡張子.opus
は認識せず、.oga
または.ogg
である必要がある。さらに、.oga
では認識しないアプリがいるほか、私が愛用するONKYO
HF Music PlayerはOpusをサポートしない。
まとめると、次のようなデメリットを抱えている。
フォーマット | 問題点 |
---|---|
AAC | ファイルそのものの扱いがやや難しく、エンコーダ事情がとても面倒 |
Vorbis | 音質面で他の2フォーマットより見劣りするほか、エンコードが少し遅い |
Opus | 再生環境が限られる |
なお、この話を前提として、「Vorbisで良いか、Opusにこだわるべきか」を判断するべくスペクトラム確認&聴き比べを行ったのだが、「Vorbisのほうが良い音に聴こえる」という問題に直面した。 スペクトラムで見てもVorbisのほうが再現度が高そうだったので非常に困惑したが、ちゃんと見てみるとOpusがだいたい320kbpsだった(311kbps)のに対し、Vorbisは400kbpsを越えていた(405kbps)ので、そういうことかと思ったのだけど、320kbps級のlossyなフォーマットで良し悪しを判断できる耳であることが判明してテンションが上がった。
さて、これを踏まえてなのだけれど、128kbpsあたりでやるつもりであれば、Opusにするのはかなりの利益が得られるからやる価値はある。 192kbpsだとOpusとAACはだいぶクオリティが近づくので、Opusの再生環境の厳しさを考えるとAACのほうが有力だ。 256kbps, 320kbpsであれば音質を取るならAACだけれど、そもそも劣化度合いは相当小さくなってくるため、Vorbisで音質を少し妥協することで扱いやすくするという選択肢もある。
MP3は音質面でも取り扱い面でもそんなに良くないので、MP3にするくらいなら、Ogg Vorbisを使うか、libaac使ってAAC/m4aにするほうがメリットはあると思う。
前提の現状
- オーディオフルライブラリはカテゴリ分けされ、ネットワークストレージ上に置かれている。元データがPCMである場合、FLACになっている
- オーディオフルライブラリはアルバムアートが埋め込みになっているものが多い
- ポータブルライブラリはVorbisが2種類とAACが1種類あるが、Vorbisは古い端末用に変換されたものなので使わない
- ポータブルライブラリはオーディオライブラリから実際にプレイヤーで再生できる形になっているものを規則的に配置してひとつのライブラリにしている
- ポータブルライブラリは元ライブラリでWAV/FLACのものについては320kbps VBR AAC(m4a)にしている
- ポータブルライブラリのAAC変換はfdkaacを使っている
- プレイリストはポータブルライブラリ上で組まれており、元のライブラリはひとつにまとまっていないという点を含めて再生に適さない
- ポータブルライブラリはWalkmanへの転送を前提としているため、カバーアートは
cover.jpg
の形になっているほか、<アルバム名>.jpg
としてもコピーされている - ポータブルライブラリは88.1GB, 同様に組んだ場合のオーディオライブラリは226.4GBである
FLACにした場合の44.1kHz/16bitオーディオはだいたい1000kbpsあたりになるため、320kbps AACに対しては3倍程度と考えられる。
FLACでいいのでは…?
割とギリギリではあるが、現状ならFLACでも256GBのmicroSDに収めることができる。 また、将来的に考えても今は512GB microSDが6000円くらいだから、「足りなくなったら512GB」というのもナシではないレベルだ。
microSDはほんとにすぐ壊れるので出費は安くないかもしれないけれど。
まず多くの端末はハイレゾFLACは全くメリットを持たない。 また、Bluetoothイヤフォンを使う場合もFLACであるメリットは全くない。 ここでFLACにする理由はシンプルに「変換めんどい」である。
手間がないことを重視するならVorbisにすればいい、320kbpsあればポータブルプレイヤーで気にすることなどない、ということではあるのだけど、なんとなく精神衛生上「ほかより音質が劣る」ということが気になってしまったためである。
また、FLACかAACにすれば、AndroidでもWalkmanでもどっちでも再生できるSDカードをつくる道が拓ける。 私はシンプルにAACが好きじゃないので、FLACのほうがいい。変換しないほうが圧倒的に問題も少ないし。
だがそもそも、現状ではライブラリとして完成しているのがAAC側だけなので、まずFLACをライブラリとして完成させる必要がある。 逆にいえば、それさえしてしまえば変換は難しい話ではない。
ライブラリ構築への道
ファイルを集める
オーディオライブラリはmasterストレージの様々なディレクトリにあり、これらを変換して集めたのが現在のポータブルライブラリである。 ポータブルライブラリで調整されたものもあり、単にこれらをひとつのディレクトリに集めただけでは足りないが、まずはひとつのディレクトリにする必要がある。
masterストレージはファイルサーバー上のHDDであるが、書き込みは断片化を避けるためまとまった形での転送のみが許可される。通常はSSHFSでroマウントしている。
これをメインPCのSSD上に載せると、更新を反映するのも楽になる。
ファイルサーバーへの反映はnasrsync
というコマンドを用いてやっている。このコマンドは
nasrsync <from...> <to>
の形となっており、基本的にはrsync
の引数だが、サーバー側ではrrsync
が使われるため<to>
仮想ルートからのパスになる。
ライブラリ上ではファイルサーバー上ではディレクトリ名も変わっているため推測不可能だが、個々のディレクトリをファイルサーバー上のディレクトリにマップすることが可能である。
そこで、.upstream
というテキストファイルを置くことにした。
このファイルに
/global/media/sound/CD/
などと書いておき、
nasrsync ./ $(<.upstream)
とすれば同期できるわけだ。
プレイリスト変換
AAC側のプレイリストは、当然ながら拡張子.m4a
になっているからファイルパスそのままでは機能しない。
また、AAC側はプレイヤーでの再生を考慮してDOS形式にファイル名が丸められている。
これは、LinuxよりもDOSファイルシステムのほうが使える文字種が限られており、Linuxではvalidなファイル名がDOSファイルシステムでは通らないことがあるためである。
そこで、これまで何度か登場しているあいまいマッチングのテクニックを使って存在するファイルに投射していく。 ここでAAC側には存在するがFLAC側にはないファイル、何らかの理由でファイル名が異なっているファイル、同一パスとみなされる重複したファイルを検出する。
この処理はconvert-playlist.rb
としてripcdに置いてある。
#!/bin/ruby
require 'find'
SOURCE_DIR = File.expand_path ARGV.shift
DEST_DIR = File.expand_path ARGV.shift
PLAYLISTS = {}
$music_db = {}
if !DEST_DIR || DEST_DIR.empty?
abort "convert-playlist.rb"
end
Dir.chdir DEST_DIR
= []
playlist_files
Find.find(".") do |fp|
if not %w:.wav .flac .ogg .mp3 .aac .m4a .oga .opus .wma .ra:.include? File.extname fp
next
end
= fp.unicode_normalize(:nfkc).downcase.sub(/\.[^.]+$/, "").delete('!?"\\<>*|:_ -')
nfp if $music_db[nfp]
abort "Normalized path name #{nfp} is not unique (assigning #{fp}, already #{$music_db[nfp]})"
end
$music_db[nfp] = fp
end
Dir.chdir SOURCE_DIR
= Dir.glob("*.m3u")
playlist_files
.each do |pfp|
playlist_files= []
pfl File.foreach(pfp) do |line|
if line =~ /^\s*#/
.push line
pflelse
= pfp.sub(%r:^./:, "").include?("/") ? (pfp.sub(%r:/[^/]*$:, "") + "/" + line).strip.unicode_normalize(:nfkc).downcase.sub(/\.[^.]+$/, "").delete('!?"\\<>*|:_ -') : line.strip.unicode_normalize(:nfkc).downcase.sub(/\.[^.]+$/, "").delete('!?"\\<>*|:_ -')
ptrp if ptrp !~ %r:^\./:
= "./" + ptrp
ptrp end
= $music_db[ptrp]
ptr_dest unless ptr_dest
#pp $music_db
abort "No match #{line.strip} in #{pfp}"
end
.push(ptr_dest + "\n")
pflend
end
PLAYLISTS[pfp] = pfl.join
end
Dir.chdir DEST_DIR
PLAYLISTS.each do |k, v|
File.open(k, "w") do |f|
.puts v
fend
end
これが通るようになるまでやると、かなりライブラリとしては整ってくる。
(中にはプレイリスト自体が古かったりして苦労した。)
まいてつ……
まいてつというエロゲーのコンプリートパック(DLsiteで安売りしてることが多いので結構有名)に入っているサントラはすごく面倒な構造をしているので、これを他と同じ形式になるように整える。 ちなみに、これは整ったソースライブラリが存在しないので、元データから新規に起こしている。
まずはartist/album/song
形式になるのが正しいため、Lose
ディレクトリ以下にフラットになるように配置していき、
for i in *
do
(
cd $i
flac --best *.wav
rm *.wav
)
done
としてFLACにする。
あとはkid3を使ってメタデータを書いていけばいいのだけど、なかなかめんどくさい。
とりあえずtrack
, artist
,
title
は埋めたいところだが、これまた一貫性がない。
ファイル名にartist名等がないものはとりあえず無視して、ファイル名=titleとみなしてkid3で処理する。
<track>_「<title>」<artist>.<ext>
形式になっているものは次の方法で拾う。
#!/usr/bin/ruby
# -*- mode: ruby; coding: UTF-8 -*-
require 'taglib'
Dir.glob("*.flac").each do |i|
abort "FILE NAME INVALID" unless i =~ /^(\d+)_「(.*?)」(.*)\.flac/
= $1
track = $2
title = $3
artist TagLib::FileRef.open(i) do |flac|
= flac.tag
tag .track = track.to_i
tag.title = title
tag.artist = artist
tag.save
flacend
end
これでAlbum Artist
, Album
,
Disc Number
を入れればOKである。
ただし、11_「ロオド・ラスト」ハチロクver.wav
のような例外が紛れ込んでおり、これは手で修正が必要。
続いて<track>_「<title>」(<scene>) <artist>.<ext>
形式になっているもの。
こちらは
#!/usr/bin/ruby
# -*- mode: ruby; coding: UTF-8 -*-
require 'taglib'
Dir.glob("*.flac").each do |i|
p i
abort "FILE NAME INVALID" unless i =~ /^(\d+)_「(.*?)」\((.*?)\) (.*)\.flac/
= $1
track = $2 + $3
title = $4
artist = File.basename Dir.pwd.sub(/-\d+$/, "")
album TagLib::FileRef.open(i) do |flac|
= flac.tag
tag .track = track.to_i
tag.title = title
tag.artist = artist
tag.album = album
tag.save
flacend
end
として拾う。
だが、こちらは02_「GRAND EXPRESS」(グランドOP さくらみこ.wav
というファイル(カッコが閉じてない)が紛れこんでおり、こちらはファイル名のほうを手で修正した。
DOSファイル名への変換
DOSファイル名の変換はいままで圧縮ライブラリ生成時に行っていたのだが、面倒なので元ライブラリ側を修正してしまうことにした。 これはファイルサーバー上にも影響が出るため、ファイルサーバーにも同じ処理が必要。
#!/bin/zsh
find . -name '*[?"\<>*|:]*' | while read; do mv -v "$REPLY" "${REPLY//[\?\"\\<>\*|:]/_}"; done
ファイルサーバーではコマンドとして実行するため、1行になっている。
この方法はパスの中に複数回禁止文字が含まれる場合、親ディレクトリのリネームが発生して失敗する。 回避するようにも書けるが、それよりは単純に複数回実行して全部置き換えるまでやるほうが簡単。
プレイリストにも影響が出るので、同じことをプレイリストにもやる。 いつも言っているけれど、sedのアドレス指定の仕方は覚えておくべき。
#!/bin/zsh
for i in *.m3u
do
sed -i '/^[^#]/ s/[?"<>*|:]/_/g' $i
done
アルバムアートの転送
アルバムアートをcover.jpg
にする処理は変換時に行っている上に、AAC側で追加したアルバムアートもあるため、FLAC側が不完全だ。
そこで、また曖昧マッチングしてカバーアートをコピーする。
#!/bin/ruby
require 'find'
SOURCE_DIR = File.expand_path ARGV.shift
DEST_DIR = File.expand_path ARGV.shift
$cover_db = {}
if !DEST_DIR || DEST_DIR.empty?
abort "convert-playlist.rb"
end
Dir.chdir DEST_DIR
= []
playlist_files
Find.find(".") do |fp|
next unless File.directory? fp
= fp.sub(%r:^\./:, "").unicode_normalize(:nfkc).downcase.delete('!?"\\<>*|:_ -')
nfp $cover_db[nfp] = fp
end
Dir.chdir SOURCE_DIR
Find.find(".") do |fp|
if File.basename(fp) =~ /^cover\./
= File.dirname(fp).sub(%r:^\./:, "").unicode_normalize(:nfkc).downcase.delete('!?"\\<>*|:_ -')
nfp = $cover_db[nfp]
dir unless dir
$stderr.puts "Not found: #{nfp}"
File.open("/home/haruka/cover_not_found", "a") {|f| f.puts fp}
next
end
= [DEST_DIR, dir].join("/")
target_dir next if File.exist?("#{target_dir}/cover.jpg") || File.exist?("#{target_dir}/cover.png")
system("cp", "-v", fp, (target_dir + "/"))
end
end
アルバムアートの複製
cover.jpg
をアルバムネーム(ディレクトリ名)に合わせてコピーする。
#!/bin/zsh
for i in **/cover.(jpg|png)
do
ext=${${i:t}:e}
if [[ ! -e "${i:h}/${${i:h}:t}.${ext}" ]]
then
cp -v "$i" "${i:h}/${${i:h}:t}.${ext}"
fi
done
ライブラリの転送
SDカードへの転送を念頭に置いている。
各種スクリプトファイルや.upstream
ファイルなどが置かれているが、これらはオリジナルにのみあるべきものなので転送には含めない。
また、DOS形式のファイルに合わせるため、rsyncの-y
オプションを使う。
ripitを使ってリッピングしているため、CDのアルバムディレクトリには.m3u
ファイルが含まれているが、これは機能せず邪魔なので、これも除外して後から手動でやる。
このスクリプトはAACライブラリにあったものをベースにしている。
#!/bin/zsh
if (( $# != 1 ))
then
print "transfar.zsh <dest>" >&2
exit 1
fi
rsync -rvy --exclude "*.zsh" --exclude "*.txt" --exclude "*.rb" --exclude ".*" --exclude "*.m3u" --ignore-existing ./ "${1%/}"/
圧縮ライブラリの作成
寝室PCのほうは持っているデータ量がやや多いことと、メインPCが7TBのSSDディスクを持つのに対して寝室は4TBであるため、大きいライブラリを持つとやや圧迫感がある。 そこで、従来同様に圧縮ライブラリを持つようにした。
これは、ポータブルプレイヤーと共有するためではなく、今後のためのテストを兼ねたものである。
#!/bin/zsh
setopt EXTENDED_GLOB
DEST_DIR="$1"
shift
rsync -rv --ignore-existing --exclude "*.flac" --exclude ".*" --exclude "*.zsh" --exclude "*.rb" --exclude "*.txt" --exclude "*.m3u" ./ "$DEST_DIR"/
for i in *~HiResMusics(/)
do
(
cd $i
for j in **/*.flac
do
if [[ ! -e "$DEST_DIR/$i/${j:h}" ]]
then
mkdir -pv "$DEST_DIR/$i/${j:h}"
fi
ffmpeg -n -i "$j" -vn -c:v libopus -ac 2 -b:a 320k "$DEST_DIR/$i/${j:r}".opus
done
) &
done
(
cd HiResMusics
for j in **/*.flac
do
if [[ ! -e "$DEST_DIR/HiResMusics/${j:h}" ]]
then
mkdir -pv "$DEST_DIR/HiResMusics/${j:h}"
fi
ffmpeg -n -i "$j" -vn -c:v libopus -b:a 320k -ar 48000 -ac 2 "$DEST_DIR/HiResMusics/${j:r}".opus
done
)
wait
機能はするが、やや微妙である。 保有するライブラリのうち、FLACは以下のように配分されている。
Amazon : 444
CD : 6443
DLM : 497
HAL-Remastered : 3
HAL-Remix : 3
Harukamy : 21
HiResMusics : 166
SXG_Render : 30
変換が必要なファイルは圧倒的にCD
に多い。
そのため、マルチスレッドで処理するようになっているにも関わらず、最終的には1スレッドだけが残ってしまう。 また、重複処理の排除をファイル単位でやっているため、無駄に重いループだ。
途中で間違ってPCを落としてしまったため、CD
だけ16並列で別途処理するようにした。
#!/usr/bin/ruby
# -*- mode: ruby; coding: UTF-8 -*-
Dir.chdir("CD")
DEST_DIR = File.expand_path ARGV.shift
abort "No dest dir" unless DEST_DIR
= []
list
16.times do |i|
.push []
listend
Dir.glob("*/*").each_with_index do |x, index|
[index % 16].push x
listend
.each do |albums|
listfork do
.each do |album|
albumsunless File.exist? [DEST_DIR, "CD", album].join("/")
system "mkdir", "-pv", [DEST_DIR, "CD", album].join("/")
end
Dir.children(album).each do |i|
next unless File.extname(i) == ".flac"
system "ffmpeg", "-n", "-i", [album, i].join("/"), "-vn", "-c:v", "libopus", "-ac", "2", "-b:a", "320k", [DEST_DIR, "CD", album, i.sub(/\.flac$/, ".opus")].join("/")
end
end
end
end
Process.waitall
並列にするとだいぶややこしいので、直列にして差分で送りやすいようにしたほうが良さそう。
なお、Opusにするときは-ac 2
は必須で、今回の場合はハイレゾのままにはしたくないので-ar 48000
も入れている。
プレイヤーの問題
ウォークマン
充電しておけば1日は使えるため、「使えない」というのはちょっと気が早い。 とりあえず共存できるフォーマットにはしたので、納得のいく形になるまでつなぐことは可能だ。
以前使っていたCOWON M2ほど特殊なフォーマットにはなっていないため、共存しやすいのは大きい。 ただし、もう少しライブラリサイズが大きくなると256GBをこえてしまうが、この場合はOgg VorbisであってもOgg Opusであっても対応できない。
F-02H
Fujitsu Arrowsの2016年のフラッグシップモデルで、Snapdragon808を搭載するタフネススマホ。 ハンドソープで洗ったが、特に問題はなかった。
もともと今の会社でGoogle Authenticatorの利用が必須になったときに会社用にするために買ったのだけど、本モデルではそもそも会社のアカウントでログインして使うことができなかったため、眠っていた。
しかしこやつ、Dolby持ちで、ハイレゾも対応したDACを載せているというプレイヤーとしては悪くないもの。 大きいのと、実用性に乏しい虹彩認証しか持ってないのが難点だったりするけれど。
ただ、実際使ってみると音飛びする。 そういえばあったなぁ、そんなの。SDカードから再生すると音飛びして使い物にならないっていうの。 Axon7で完璧に快適に聴けてたから忘れてた。 Zenfone 2 Selfieとか、その前のK4000 Proとか使ってたときは結構苦しめられてた。
本体だと発生しないのでおそらくは転送速度とバッファリングなんだけど、そんなことあるだろうか。 使ってるのSanDisk Ultraだというのに。
HF Playerは大惨事、Foobar2000も飛び飛びだった。 Powerampは問題なさそう。 飛びにくいプレイヤーというとPulserが思いつくけど、今はプレイリスト中心の使い方になっているので無理。 mpvもバックグラウンド再生できないので無理。
なお、Foobar2000は普通にUTF-8になっている.m3u
のエンコーディングを正しく認識できないため、Foobar2000に合わせると複数環境での共存が難しいので使わないほうが良さそう。
やっぱりAndroidスマートフォンで音楽を楽しむのはかなり難しい感じがする。
また、古いスマートフォンだと音楽を再生してるとバッテリーがごりごり減る。 再生可能時間が結構短い。
補足 2024-03-12
Android 13で.opus
が認識されるようになった模様。