たくさんの自由帳
Androidのお話
たくさんの自由帳
投稿日 : | 0 日前
文字数(だいたい) : 4582
どうもこんばんわ。
うーん、特に書くことがない
AV1に再エンコードするアプリ、ちゃんとUI作ってきました。一応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は起動できるので、おそらくキリがいい?動画の解像度しか受け付けて無さそうな雰囲気。
Process: io.github.takusan23.himaridroid, PID: 31106
java.lang.IllegalArgumentException:
at android.media.MediaCodec.native_configure(Native Method)
at android.media.MediaCodec.configure(MediaCodec.java:2332)
at android.media.MediaCodec.configure(MediaCodec.java:2248)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を使わせてもらうことにしました。ちゃんと動いていそうです!
ちなみに、どうしても自前で変換処理を書きたい + 数学の知識無し(私は数学の知識無い)でやる場合、何に気をつけないといけないか残しておきます。
まず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(半分)、になります。
これだとサンプリングレートの変換処理がおかしくなっちゃうので困った!
// MediaExtractor で取り出した MediaFormat、profile=5 が HE-AAC
// https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel#AACObjectHE
{sample-rate=22050, mime=audio/mp4a-latm, profile=5}ちなみに解決策ですが、デコーダーからMediaFormatを取得する口があるので(MediaCodec#getOutputFormat)、
そこからサンプリングレートを取得するとちゃんと44_100が返ってきました。
うーん。こういうの書いておいてほしいなあ。。。
// デコーダーから出てきた MediaFormat、よかった!
{sample-rate=44100, mime=audio/raw}もしデコードした後に音声ファイルを操作する場合、MediaCodec#getOutputFormatの方を信用すると良いかもしれません、、、
ちなみにサンプリングレート 22_050 + HE-AACでデコーダー(MediaCodec)を作って、サンプリングレート = 44_100のPCMを流し込んだんですが、
特に問題なくデコーダーは起動したし、データもエンコードされました。うーん?デコーダー側でHE-AACの時はサンプリングレートを2倍にしてるとかなの?
以上です。MediaCodecもMediaExtractorも何もわからない。。。