どうもこんばんわ。
今でも 8/31 まで夏休みあるんですかね?
本題
ノイズキャンセリングって、周りの音を逆位相にして周囲の音を打ち消しているらしいんですよね。
で、それを使えば音楽の通常版とカラオケ版トラックを使いカラオケ版トラックを逆位相にすることで、カラオケ版の逆、つまりボーカルだけのトラックが作れる
昔からあるやつなので、普通はAudacity
とかでやればいいと思うんですけど、今回はAndroid
でやります!!!
ながれ
- 音声ファイルを用意する
- 音声ファイルを未圧縮状態にする(デコード)
- 通常版トラックと逆位相カラオケ版トラックを足し算して、ボーカルだけにする
- 正確には通常版からカラオケ版トラックを引く
- 音は波なので、そのまま足したり引いたりできる
- 逆位相にして合成するのとやってることは変わらんはず
- 未圧縮状態のデータなので、このままだとファイルサイズが大きいままなので、圧縮する(エンコード)
- 未圧縮状態だと多分音楽プレイヤーでも再生できない
ffplay
とかAudacity
でパラメータ合わせれば再生できるかもしれん
未圧縮状態にする
CD
とかで取り込むと、既にエンコードされている状態で保存されますよね。(.mp3
、.flac
、.aac
)
エンコードされているということは、圧縮されている状態なので、音声データを加工したい場合はまず圧縮を元に戻す必要があります。デコードと呼ばれる作業ですね。
(zip
を解凍しないと中身いじれない感じで)
動画/音楽プレイヤーが圧縮されている音声を再生できるのは、デコードと呼ばれる作業をし、元の音声ファイルに戻しているからなんですね。
Audacity
のファイルが大きいのは編集のために未圧縮状態で持っておく必要があるからなんですね
音声を支える技術
- MediaCodec
- このブログでも何回か取り上げているくせ者
- 映像や音声のエンコード、デコードをするクラス
Media3 (ExoPlayer)
が動画を再生できるのは、くせ者MediaCodec
を使っているからなんですね
- 最終的にはC言語とかで書かれた処理に到達する、エラーが何もわからない
H.264
、H.265
、VP9
とかAAC
とか、コーデックと呼ばれるやつをやってくれる
- くせ者
- MediaExtractor
mp4
やaac
などのファイルから、MediaCodec
へ渡すためのデータを取り出してくれるクラス
mp4
とかaac
には実際のデータ以外にもメタデータを持っている(動画の縦横サイズ、ビットレート、fps など)ので、メタデータと実際のデータをそれぞれ分けて取り出してくれる
mp4
とかaac
はコンテナフォーマットとかいうやつですね
- くせ者
- MediaMuxer
MediaCodec
から出てきたデータをmp4
とかに保存するためのクラス、MediaExtractor
の逆をする
- くせ者
環境
なまえ | あたい |
---|
Windows | 10 Pro |
Android Studio | Android Studio Giraffe |
たんまつ | Google Pixel 6 Pro |
使う音声ファイル | flac (多分AAC でもいいはず)/ サンプリング周波数 44.1 kHz / ビットレート 1 Mbps / チャンネル数 2 ch |
音声ファイルは、通常版とカラオケ版で、サンプリング周波数(ビットレートも??)が同じじゃないとダメだと思う(CDから入れれば同じ設定だと思いますが...)
作る
つくります
適当にプロジェクトを作成
Jetpack Compose
でいきます!もうレイアウトのxml
一生書きたくない!
ついに!build.gradle.kts
がデフォルトで作れるようになりましたね!!!
長かったけどまぁ旨味あんまりないからbuild.gradle
から移行するほどではなさそう
音楽を選ぶためのUI
ファイルピッカーを開いて、音声ファイルを選ぶ処理を書きます。
適当にボタンを置きます。
本当はHomeScreen
は別ファイルのほうが良さそうですがまぁ単発企画なのでこのままで行きます
ファイルピッカーの返り値はUri
で、これはファイルパスではないです。
もちろんちゃんとUri
からデータを取り出したり、書き込んだりする方法があります(InputStream / OutputStream
が開けます)。
はぁ~???って感じですよね、、、なんでだよファイルパスよこせよって話ですが、この方法だとファイルパスを持たないアプリからデータを受け取れるんですよね。
つまりどういうことかというと、Google ドライブ
やGoogle フォト
などの、端末内には無いファイルもファイルピッカーで選択することができます。(端末内に無いのでファイルパスを持ってません...)
(もちろん、上記のアプリから端末外にあるファイルを選んだ場合、一時的には端末内にダウンロードされるとは思いますが)
あとUri
はプロセスが生きている間(アプリが動いている間)のみ有効みたいな話だったはず、、、ちょっと思い出せない
選んだらnormalTrackUri / instrumentalTrackUri
にそれぞれ入れます。
実行ボタンともろもろを書く
まずは UI に状態を通知するため、状態一覧を書きます。
WWDC 2023
で久しぶりに出たOne more thing...
、iPhone X
の発表以来使ってないんじゃないと思って調べたらApple Silicon
で使ったのか
そして実行ボタンを設置しました、HomeScreen
だけです以外は変えてないです。
音声を編集する処理を書く
MediaCodec
とかのくせ者をここから使っていくわけですが...
今回は前私が書いたやつがあるのでそれをパクることにします。ありがとう過去の私
https://github.com/takusan23/AkariDroid/tree/master/akari-core/src/main/java/io/github/takusan23/akaricore/common
AudioEncoder / AudioDecoder
以下のクラスを作ってください...
やってることは
MediaCodec
を初期化する
- 終わりまでデータを
MediaCodec
に流す
音声ファイルをデコードして、一時的にファイルに書き込む
aac
やflac
はエンコードされている(圧縮されている)ので、まずはデコードして未圧縮状態のデータに戻す必要があると言いました、それをします。
で出来た、未圧縮状態のデータをファイルに書き込みます。
雑にコメントに書いたので何してるか見たい方はどうぞ。
一点、Uri
からデータを取り出すのにInputStream
みたいなのを使おうとしたんですけど、それ自体は渡せなさそうで、FileDescriptor
とかいうやつを経由してデータを取り出すようにするようにしました。よく分かりません
あ!、もう一点、
出力先ファイルがFile クラス
になってたりしますが、これはこの下のどっかで話すと思う
通常版とカラオケ版を使って、ボーカルだけを取り出す処理
冒頭に言った通り、未圧縮状態のデータなので、足し算・引き算が出来ます。
未圧縮状態のデータのバイト配列の同じ位置同士を足したり引いたりすれば良いはずです...!
readNBytes
とかいう、指定サイズのByteArray
を作って読み出して返してくれるやつがあるんですけど、古いAndroid
をターゲットにするなら使えなさそう...
https://developer.android.com/reference/java/io/InputStream#readNBytes(int)
流石に3つもuse { }
すると分かりにくい気がしてきた...でも自動で閉じてくれるの便利なんだよな
エンコードする処理
はい。こっちもコメントに書いたので見てね
MediaMuxer
を開始するには、MediaCodec
を開始した後に取得できるMediaFormat
を待つ必要があります...
出来た音声ファイルを端末の音楽フォルダに移動させる
最後!
完成品したファイルを端末の音声フォルダに移動する処理を書きます!
というのも、作業のためにすべてのファイルをContext#getExternalFilesDir
で返される保存先に書き込んでいたわけですが、(Java
のFile
クラスが使えるので一時的にフォルダ作るのに良い)
この保存先というのはアプリ固有ストレージとか言われていて、他のアプリからアクセスできないんですよね...
(ちなみにsdcard/Android/data/{applicationId}
みたいな保存先パスになるはず)
というわけで、MediaStore (ContentResolver ?)
に音楽データ追加を依頼して、そっちにデータを移動させます!
これで他のアプリから参照できるはずです!!!
new File("sdcard/Music/VocalOnly")
みたいなコードは動かないので、大人しくドキュメントどおりにしましょう...
https://developer.android.com/training/data-storage/shared/media
ここまでのコード
つぎはこれらをUIから呼び出していきます...
つなぎ合わせる
UI
からさっき作った関数を呼び出していきます。
本当はUI
ではなく、フォアグラウンドサービス
なんかでActivity
を破棄した後でも動くようにすべきです
というわけで UI のコード全部張ります!どーーーん
やってることはコメントに書いてるので見てください(全投げ
使ってみた
多分実行できるはず。
起動したらそれぞれファイルを選んで処理を開始します
保存先はここになります!!
感想
- うまくボーカルだけ取れる パターン
- 若干ボーカル以外も入ってる パターン
- 失敗しちゃう パターン(音割れ)
いくつか試しましたが結構な確率で失敗しちゃいますね。
原因はおそらく、音声の波が通常版とカラオケ版で若干ずれてることがあるんですよね、、、(オフセットがある?)
Audacity
で逆位相にして同時再生する方法でも、まずは波を合わせる作業をする必要がある場合がが多く、
今回のようにそのままカラオケ版トラックを逆位相にして(通常版から引き算して)同時再生してもうまく抜けません。
なかなか難しい...うまく抜けるとほんとにボーカルしか聞こえなくて感動ものなのですが...
そーすこーど
どうぞ
最新の Android Studio で実行できるはずです。
https://github.com/takusan23/VocalOnlyDroid
おわりに
8月も終わりますね...
全然話変わるんだけど、三井住友銀行さんさあ...メール普通にビビるからやめてほしい
展開するとちゃんとウソであることが書いてある