どうもこんばんわ
D.S. -Dal Segno- 攻略しました。
D.C.4 ってもしかして D.S.2 、、、?
OP曲が良すぎ。ぜひ聞いてみてね
ヒロインみんなかわいいので置いておきますね。
イベントCGがめっちゃいいのでやってみてね。
声がふわふわしててかわいい
ここ何回でも聞ける
オンオフ合ったほうがいいよね
姉さん女房!!!
ファンディスクに期待、、!
どうやらファンディスクの方ではデフォルトネームを呼んでくれるらしい?ので気になっております
それはそれとしてじゃあ D.C.5 やるからまたね
動画に文字をかさねてみた
↓ こんな感じに テキストとドロイド君(画像) 重ねてエンコードしてみる話です。
元動画はこれ:https://nico.ms/sm36044089
https://github.com/takusan23/AkariDroid
お正月に試してたことの話をします、、、
二番煎じ
https://www.sisik.eu/blog/android/media/add-text-to-video
はい。
めんどいんだけど?
本当に(ほんとうに)最低限の状態でMavenCentral
に公開したのでお試しには使えるかも、、、
最低限過ぎてこの記事で紹介する音声の追加
部分、MediaStore
の部分はまだ存在しないので自分で作る必要があります。
Android で文字を動画にかさねるには
FFmpeg
とかを利用しない場合、MediaCodec
をそのまま使うしかないです。(使いにくいやつ)
これのそのままですが
https://speakerdeck.com/masayukisuda/mediacodecdedong-hua-bian-ji-wositemiyou
- MediaExtractorで動画を取り出して
- MediaCodecで動画をデコードして
- Canvasで文字を書く
- AndroidのCanvasに書ければ図形でも画像でも行けるはず
- OpenGLで動画とCanvasを描画する
- OpenGLの出力をもとにMediaCodecでエンコードする
- エンコーダーから
- 繰り返す
OpenGL
を使う理由ですが、MediaCodec
の入力用Surface
ではlockCanvas
を使っての描画ができないことが書かれています。
https://developer.android.com/reference/android/media/MediaCodec#createInputSurface()
動画関係のメモ
コーデックとコンテナ
一応置いておきます
- コンテナ
- エンコードした映像と音声を一つのファイルに保存するための技術
- AAC / H.264 (AVC) はコーデックの種類なので間違い
- コーデック
- 映像、音声を圧縮するプログラム
- 圧縮する作業をエンコードとかいいます
- 逆に再生するために戻す作業をデコードといいます
- パラパラ漫画にするよりも動画にするほうが容量が小さいのはコーデックが圧縮しているから
- AAC
- Opus
- 音声
- JavaScript の MediaRecorder はこれが採用されていたかな
- H.264 / AVC
- H.265 / HEVC
- 映像
- H.264 の半分で同じ画質と言われている(つまり容量半分)
- カメラアプリによっては H.265 を利用して容量を節約する機能があったり
- VP9
- 映像
- JavaScript の MediaRecorder で使えたような?
- H.265 の Google バージョン
つくる
OpenGLの部分とかはほぼコピーです
なまえ | あたい |
---|
言語 | Kotlin / OpenGL (一部) |
targetSdk | 33 |
Android Studio | Android Studio Electric Eel |
たんまつ | Xperia Pro-I Android 13 |
app/build.gradle
app/build.gradle
に書き足します。
ViewBinding
と最低限のライブラリを
activity_main.xml
動画を選択するボタンと、エンコードするボタンと、現在の状態を表すTextView を置きました。
動画ファイルをコピーする
まずは動画ファイルをアプリの固有ストレージにコピーする部分を作ります。
Uri
だと使いにくいので、一旦Context#getExternalFilesDir
の領域に保存します。その領域ではJava File API
が使えるので。
OpenGL の用意をする
AOSPのCTSテストとかでも使われているやつですね。
https://cs.android.com/android/platform/superproject/+/master:cts/tests/tests/media/src/android/media/cts/InputSurface.java
GLSurfaceView
とかはこの辺意識しなくてもいきなりOpenGL
のシェーダー書くところから始められるので良いですね、、、
よく知らないのでコピペしてください、、、
TextureRenderer.kt
こちらは映像とCanvasをOpenGLを利用してかさねるためのクラスです。
まずコード全文を
よく分からんなりの解説
Surface
の映像はAndroid
のSurfaceTexture
を利用することでOpenGL
のテクスチャ(画像)として取得できます。(sampler2D
ではなくsamplerExternalOES
です)
Canvas
はBitmap
にすることで、OpenGL
のテクスチャとして取得できます。(sampler2D
です)
- 映像のフレームがやってくる
- Canvasに描画する
glDrawArrays
を呼び出して映像を描画する
- 今度は
Canvas
の内容を glDrawArrays
を呼び出して重ねて描画する
uniform 変数 uDrawVideo
を切り替えて Canvas を描画する
- エンコーダーに行く?
今回はフラグメントシェーダ
に用意したフラグを切り替えることで映像とCanvasの画像を切り替えて描画できるようにしてあります。(uDrawVideo
フラグ)
(どうやって 映像とCanvasの画像 を重ねるんだろうって一週間ぐらい悩んでましたが、二回描画すればいいんですね。基礎がなってないね)
また、gl_Position
の値を制御することで回転やスケールの調整ができます。
今回はgl_Position
をいい感じにして縦動画でも真ん中にフィットさせて描画されるようにしてあります。Matrix.scaleM
の部分です。(引数に動画のサイズを取ってるのはそのせい)
また、(後でまた書きますが)縦動画の場合はgl_Position
をいい感じにして回転させる処理を追加しています。行列の回転とかいうやつらしいです。Matrix.rotateM
の部分です。
(もしうまく動いてない(ひっくり返ってる)場合は Matrix.rotateM
の部分を見直してみてください、、よくわかりません。)
あと、Canvas
の何も書いていない部分は透明になるのですが、アルファブレンド
の設定をしていないと重ねたCanvas
のせいで透明の部分が真っ黒になります。
Snapdragon端末で映像が乱れた
glClear
関数を呼ぶことで直りました。
VideoProcessor.kt
最後に MediaCodec とかと上で書いたコードを組み合わせます。
まず全文貼りますね
ここにもいくつか罠があって、、、
雑な解説
といってもデコーダーの出力をOpenGLに向けている以外になさそう?
映像が取得できたら、Canvas
の更新をするようにしています。
わな 縦動画の場合は動画の回転情報が入っている。
- しれっと書いてあった
- それだけじゃなく、動画のの幅、動画の高さも回転されている状態で保存される
- 今回はこれを修正するため、縦動画の場合は
OpenGL
側でMatrix.rotateM
をして回転情報がなくても縦動画にするようにしています。
↓ ちょうどここ
あとはMediaCodec
特有の使いにくさが相変わらずあるのですがそれは前に書いた他の記事で...
MainActivity.kt
本当は長時間のタスクになるので、フォアグラウンドサービス
でやるべきですが本題じゃないので、、
Canvasでお絵かきタイム
おまたせしました。お絵かきタイムです
this
にCanvas
、currentTimeMs
は動画の再生位置(ミリ秒)になります。
videoProcessor.encode { currentTimeMs ->
// this は Canvas
// currentTimeMs は動画の再生位置(ミリ秒)
}
例えば動画の再生時間を重ねた(ついでに画像も)場合はこんな感じ
ついでにここまでのMainActivity.kt
を置いておきます。
動かしてみる
動画を選んだあとに、エンコードボタンを押します。
しばらく待ちます、終了しましたと表示されたら終わりです。
で、、、動画のパスなんですが、
/storage/emulated/0/Android/data/アプリケーションID/files/video/result.mp4
です。端末の動画フォルダに保存する処理はまだ書いてないのでこうなります
アプリケーションID
はbuild.gradle
のapplicationId
の部分の値です。
どうでしょう、動画の上にCanvasで落書きした画像が重なってエンコードされていますでしょうか?
しかし音声がなくなってしまいました。
この修正を次やります。
音声を追加する
さっきよりは難しくない。Kotlin
で完結する上、mp4
->mp4
の場合はそのまま取り出して入れ直すだけなのでMediaCodec
すら出てきません。
(mp4
->WebM
の場合はAAC
をOpus
にするためエンコードする必要がありますが、、、(MediaCodec
利用))
名前はお任せします。
Util
クラス、スペルがTool
のほうが簡単だからTool
にしてるんだけどどうなんだろう(超どうでもいい)
MainActivity.kt
音声を追加した動画ファイルを最終的なファイルとするため、ちょっと直します。
定数が増えているのが分かる通り、ファイルが三種類になりました(元動画
、Canvasと重ねた動画
、Canvasと重ねた動画に音声を追加した動画
)
これで音声が追加されているはずです!いかがでしょう!
VLC
で見るとオーディオについての項目が増えています!
これでGoogle フォト
アプリや他のギャラリーに見つけてもらうことができます。
が、結構面倒くさいのでコピペしましょう。
Android
のMediaStore
とかいう仕組み、使いにくいというか、、なんかなあ、、、
MediaMuxer
やMediaExtractor
とかがMediaStore や Storage Access Framework
で取得できるUri (File#path のようなものだけど違う)
に対応してないから、
結局File
が使えるgetExternalFilesDir
とかに転送しないといけないのがなあ、、
Android 10 の Scoped Storage
、やっぱ影響範囲めっちゃでかいよなあ
MainActivity.kt
あとはMixingTool
のあとに書き足すだけ。終わり
これで Google フォト
アプリのデバイス内の写真
に表示されているはずです、どうでしょう?
以上です。
ソースコードです。
https://github.com/takusan23/AndroidMediaCodecAddCanvasTextToVideo
最終的な MainActivity.kt です
https://github.com/takusan23/AndroidMediaCodecAddCanvasTextToVideo/blob/master/app/src/main/java/io/github/takusan23/androidmediacodecaddcanvastexttovideo/MainActivity.kt
おわりに
この更新からNext.js
のscrollRestoration
をtrue
にしてます。
experimental
なので使うか迷ってたんですけど特に影響なさそうなので有効にしました。
おわりに 2
WebKit
だとJavaScript
のDate.parse()
がYYYY-MM-DD
をパースできなくて、何日前に投稿したかどうかの部分が NaN
になっていました。
Apple
デバイス持っていないので知りませんでした、、、そのうち直します