たくさんの自由帳
Androidのお話
たくさんの自由帳
投稿日 : | 0 日前
文字数(だいたい) : 4576
どうもこんばんわ。
うーん、特に書くことがない
前回かその前くらいに作った、AV1
に再エンコードするアプリ、ちゃんとUI
作ってきました。
審査が通るかは分からないですが、通ったらここからアプリをダウンロード出来ます→ https://play.google.com/store/apps/details?id=io.github.takusan23.himaridroid
一応AV1
以外にもAVC / HEVC / VP9
でエンコード出来ます。
mp4
だと勘違いしてどこでも再生できると思われてしまうから嫌な場合、WebM
に書き込む処理を自前で実装して追加したので使ってね。
そんな事あるのか知らないけど、考え過ぎか?
何回かwebm
のバイナリ触ったので何となくは分かってたんですが、それでもめっちゃ大変でした。
AV1
エンコーダーがtargetSdk 34 (Android 14)
からっぽいので、最低 Android バージョン
は14
です。
以上です。
MediaCodec
/ MediaExtractor
周りのワナをここに残しておくことにします。
てかこっちが本題です。
スマホの解像度 1440 x 3120
をMediaCodec
の動画解像度として入れたらエラーになった。
AVC (H.264)
なら起動できるのでよくわからない。
1280x720
とか1920x1080
は起動できるので、おそらくキリがいい?動画の解像度しか受け付けて無さそうな雰囲気。
webm
に入れる機能をつけたわけですが、考えないといけないことが2つあって
Android
のコンテナフォーマットに書き込むMediaMuxer
がWebM
の場合AV1
を受け付けないmp4
で音声トラックがAAC
の場合、Opus
に再エンコードする必要がある1つ目は自前でWebM
に書き込む処理を書きました。多分動いています。
2つ目ですが、これはAAC
のデータをデコードした(PCM
にした)後、Opus
のエンコーダーに突っ込めば終わり。ではないんですね。
AAC
のほとんどのファイルがサンプリングレート44.1k
なんですが、Opus
は44.1k
だとダメらしく、48k
にしないといけないみたいなんですよね。
Opus
のエンコーダーに入れる前に、デコードしたデータのサンプリングレートを48k
に変換する(アップサンプリングする)必要があります。
うーん。自前で作れる気がしないよ、、、
というわけでAndroid
のmedia3
でも使われている?Sonic
を使わせてもらうことにしました。ちゃんと動いていそうです!
https://github.com/waywardgeek/sonic
ちなみに、どうしても自前で変換処理を書きたい + 数学の知識無し(私は数学の知識無い)でやる場合、何に気をつけないといけないか残しておきます。
まずaac / opus
のデータは圧縮されているのでデコードしてPCM
にしてあげる必要があります。
これは ↑ のSonic
でもデコードしたデータを渡す必要があるので、音声データに手を加えるならどっちにしろ必要です。
次にデータの読み方です。とその前に知らないといけないワードが
1
、ステレオの場合は2
です。AAC
が44.1k
、Opus
なら48k
だと思います。AAC
に48k
を入れることは出来るらしいですが、Opus
に44.1k
はダメだと思います。同様にコンテナフォーマットの解析でわかると思います。16bit
だと思います。ハイレゾとかは知らない...これが分かっていれば多分データを操作することができます、、
今回はそれぞれ、チャンネル数 = 2 / サンプリングレート = 44.1k / 量子化ビット数 = 16bit
として話を進めます。
説明しやすいように先にサンプリングレート
から、44.1k
ですね。
サンプリングレートというのは一秒間に何回音声を記録するかなので、44.1k
だと4万4千回
音声を記録するわけですね。
で、その記録したデータが何バイトで表現されるかと言うと...
量子化ビット数
。今回は16bit
なので、バイトに直した2バイト
で記録される感じですね。
そして、チャンネル数が2
なので、
二回、量子化ビット数で音声が記録され、それをサンプリングレートの回数分繰り返す、そんな感じのデータになっています。
以外にシンプルですね。
2回繰り返すので、最初の2バイトは片耳、次の2バイトはもう方っぽの耳、、、みたいな感じになります。
余談ですが、少なくとも圧縮(エンコードしていない場合)、一秒間の音声データを記録するのに必要なストレージは
2 (チャンネル数) * 44_100 (サンプリングレート) * 2 (量子化ビット数、bit -> byte 単位変換済み) = 176_400
となり、176.4 KB
必要らしいです!エンコードってすごい!
話を戻して、2チャンネルの場合、先に左が来るのか、右が来るのかは忘れちゃいました、、、が、
例えば、片耳だけ聞こえる音声を作りたい場合、
量子化ビット数を見て、例えば16bit
なら、
まず2バイト
そのままにして、次の2バイト
を0x00 0x00
で埋めて、また次の2バイト
はそのままにして、その次は0x00 0x00
で埋めて、、、
って繰り返すと見事に片耳だけ聞こえるデータが出来るはずです。
また、これの逆をやることで、片耳だけ聞こえる音声を両耳聞こえるようにできます。(片耳のを無理やり両方流すだけのハリボテ感ありますが)
そういえば音は波だと物理で習うらしいです、忘れました。
波らしいので、半分に減らしたり、他の音声(波)を足し算することで2つの音声が同時に流れるみたいなことが出来るはずです。
例えば量子化ビット数が16bit
なら、2バイト
ずつ値を扱えばいいので、
音量を半分に減らしたければ2バイト
をInt
にして、半分にして、それを2バイト
のバイト配列に戻して、、、を繰り返せば半分の音量に出来るはずです。
2つの音声を混ぜる際も、量子化ビット数とサンプリングレート、チャンネル数が合っていれば、
それぞれの音声ファイルをPCM
にして、それぞれ2バイトずつ取り出してint
にして、それぞれのint
を足して、2バイトのバイト配列にして混ぜるファイルに書き込んで、次もまた2バイトずつ取り出して足し算して、、、を繰り返せば2つの音声が同時に流れるようなPCM
データが出来るんじゃないですか(?)
足し算の際に2バイト
(0xFF 0xFF
)を超えないようにする必要はあると思います!
なので、サンプリングレート変換を数学の知識無し(私は数学の知識無い)で素直にやりたい場合、
44_100
回16ビット (2バイト)
が記録されているのを、48_000
回16ビット (2バイト)
が記録されるようにすれば良いはず。
ただ、48_000 - 44_100 = 3_900
、つまり3_900
回分のデータが無いので、その間を埋めてあげる必要があります。
それだけなのですが、、、それが難しい、、、
MediaExtractor
にAAC
でエンコードした動画を入れた後、MediaExtractor#getFormat
で音声トラックのサンプリングレートを確認しようとしたのですが、、、
なぜかHE-AACのときだけサンプリングレートが半分の状態で値が返ってきました。なんで?
ffprobe
で見ても44_100
なのですが、なぜかHE-AAC
をMediaExtractor
に入れるとサンプリングレートが22_050
(半分)、になります。
これだとサンプリングレートの変換処理がおかしくなっちゃうので困った!
https://stackoverflow.com/questions/33609775/
ちなみに解決策ですが、デコーダーからMediaFormat
を取得する口があるので(MediaCodec#getOutputFormat
)、
そこからサンプリングレートを取得するとちゃんと44_100
が返ってきました。
うーん。こういうの書いておいてほしいなあ。。。
もしデコードした後に音声ファイルを操作する場合、MediaCodec#getOutputFormat
の方を信用すると良いかもしれません、、、
ちなみにサンプリングレート 22_050
+ HE-AAC
でデコーダー(MediaCodec
)を作って、サンプリングレート = 44_100
のPCM
を流し込んだんですが、
特に問題なくデコーダーは起動したし、データもエンコードされました。うーん?デコーダー側でHE-AAC
の時はサンプリングレートを2倍にしてるとかなの?
以上です。MediaCodec
もMediaExtractor
も何もわからない。。。