うるおいらんど

アプリ開発やサイト制作のメモとか。

【Swift 3】UIImageを1枚ずつ継ぎ足して動画を生成する【AVAssetWriter】

魚ライン
魚ライン

どうも。Reoです。

今回は、以前書いた記事「【Swift 3】UIImageの配列から動画を生成する【AVAssetWriter】」の別バージョンを紹介させていただきます。

というのも、この記事に対して「配列からでは無く、コマごとに1枚ずつ動画に足していきたい」という質問がありまして、それを実装してみたのでついでに記事にしちゃおうというわけです。

 

 

簡易的にこんなものを作ってみたので、まずはGifをみてください。

 



 

まずフレームレートを決めて、そこから画像をタップした順に繋ぎ合わせて動画を生成します。

 

以前の場合は、

「UIImageを入れる配列を用意する」–>{  (画像をタップする)–>「タップされた画像を配列に入れる」} –>(動画を生成ボタンを押す)–>「動画の生成を開始」–>「配列から一枚ずつ取り出してバッファに変換して動画に足す」–>「動画生成終了」 –> (動画を表示)

という流れでした。今回は、

(画像をタップする)–>「動画の生成を開始」–>「UIImageを一枚バッファに変換して最初のコマにする」–>{ (画像をタップする) –> 「バッファに変換して動画に足す」} –>(動画を生成ボタンを押す)–>「動画生成終了」–>(動画を表示)

という流れで実装しました。{}内は繰り返しできる処理です。

説明が下手でよくわからんですが、まぁ配列に一回いれなくてもいいよってことです。中身を紐解いてみるとやってることはほとんど変わらんです。

以前のやり方ではまとめてガバッと画像変換処理をするので、すごい負荷がかかってたんですが、今回のやり方にすると結構いい感じでした。

でもタップした時点で動画に足してしまうので、一度配列に入れる方式と違って、やっぱこの画像入れたくない〜ができないんですね。(できるのかもですが私にはわからない!)

 

今回このコードをGitHubに公開しました。

uruly/ MovieCreator: 【iOS】UIImageからmp4を生成する

初めてのgithubだぜ・・・!

プロジェクトごとドーンと公開してるのでクローンかダウンロードして頂ければ、上のgifと同じやつが実行できます。

 

コード公開しちゃうともうgithubで見てどうにかしてって言いたくなるような気もしますが、なんか記事放棄しちゃってる気がせんでもないので、ちゃんとここでも説明させていただきます。

 

MovieCreatorクラスの全体コード

まずは全体はこんな感じです。(画像リサイズ処理はちょっと省きました)

 

コードのベースは以前の記事と同じです。

まずは、適当にMovieCreatorというクラスを作成しました。AVAssetWriterとかを使いたいので、AVFoundationをimportしておきます。

 

まずはメンバ変数として以下のものを用意します。

 

動画をどこに生成するかを示すURLと、動画の現在のフレーム数と、フレームレートの値を用意しておきます。

以前のやり方では1つの関数内でしか用いなかったんですが、今回は1コマ目と2コマ目以降で違う関数を用いたいので、メンバ変数にしました。

videoWriterとかも複数の関数をまたがって使用したいので、メンバ変数として外に出してあります。

 

次に最初の1コマ目に呼び出したい関数を用意します。

 

まずは保存先のURLを決めます。今回はmp4を書き出すので.mp4つけています

次にAVAssetWriterを用意します。

これが用意できれば、メンバ変数のAVAssetWriterに入れておきます。

用意できなければエラーが出てこれ以後の処理はしません。

(最近ようやくこのguardの使い方を知ったのです・・・!)

 

AVAssetWriterに貼り付けるAVAssetWriterInputを用意します。

どういうサイズで、何を書き込みたいのかっていう設定みたいな感じです。

こんだけのサイズの(テキストや音声では無く)ビデオを作りたいんだよ〜!って書いてます。

writerInputは先ほどメンバ変数で用意したやつで、セットした後はnilにはならないので!つけています。

 

次にAVAssetWriterInputPixelBufferAdaptorを用意します。

長くてなんやこれって感じですね。

UIImageをCGImageに変換して、それをさらにピクセルバッファにして動画を生成するんですが、そのピクセルバッファと先ほどのAVAssetWriterInputをつなぐのがこいつです。

ピクセルバッファをこのアダプターに足してやれば、勝手にAVAssetWriterInputに適合するようにどうやらしてるんだと思います。

 

そしてこれ

正直何してるか知らにゃい。というか無くても動く。

これもしかしてカメラからの映像を取得してきた時とかに書くやつですかね・・・?今回はあってもなくてもなんも変わらんです。

 

ここまできたら準備が整っているのかチェックします。

おkだったら動画生成を開始します。時間はゼロから始めます。

 

空のピクセルバッファを用意し、アダプターを通してassetWriterInputの準備ができているのかチェックします。

ついでにフレームカウントを0にしておきます。

 

動画の時間を生成します。

画像をどこに埋め込むかの時間、と言えばいいんでしょうか。最初は0で、次回以降はフレームカウントに合わせて増えます。

 

画像をバッファに変換して、時間とともにアダプターに追加します。

追加できなければエラーを吐きます。

その後フレーム数を進めます。

これで1枚目の処理は終わりです。

 

CGImageからBufferを生成する関数を追加します。

 

ここの説明は割愛!(正直詳しくはよく知らない)

 

次に2回目以降に呼び出す関数です。

 

2回目以降は先ほどの、時間を計算し、UIImageをバッファに変換してアダプタに加えるという処理を書いているだけです。

っていうかこれ1回目もここ呼び出しちゃった方がハヤクナーイってことに今気づきました。

2回目ではちゃんとAVAssetWriterがあるかどうかを最初に確認しています。

 

最後に動画生成を終了する関数を作ります。

 

writerInputかvideoWriterがなければ動画生成をしていないので何もせずに抜けます。

最後に終わりの時間を決めます。

そして動画の生成を終えます。

動画生成が終わったら呼ばれるところに、この関数の引数に指定したクロージャを呼び出します。

urlが決まってなければ呼び出しません。

あとはwirterInputとvideoWriterをnilに戻しておきます。

githubの方のコードはどっちかだけnilにしてた気がするけど多分両方のがいい気がします。

 

MovieCreatorクラスを使ってみる

先ほど作ったMovieCreatorを適当なViewControllerで使ってみます。

 

ざっくりと割愛しましたが、先ほどのMovieCreatorクラスを用いてるのはこんだけです。

VC内に1つMovieCreatorクラスを生成しておいて適当にボタンを押したら画像を追加しています。

1コマ目の時は

を呼び出して、UIImageと動画のサイズを渡してあげます。

2コマ目ではUIImageだけを渡してあげます。

 

動画を作り終えたい時は

を呼び出します。{}の中は先ほどのcompletionの中身です。動画を作成し終えた後にurlを使って何か処理を書くこともできます。

 

AVPlayerViewControllerと組み合わせて使おう

最後におまけ。

作った動画をAVPlayerViewControllerにのっけてみます。最初に乗せたgifの動画を表示している部分です。

まずAVPlayerViewControllerを使うにはMediaPlayerとAVKitをimportします。

 

AVPlayerViewControllerを用意します。

サイズやらはちょっと適当で。

 

ここに動画を表示させます。

先ほどのMovieCreatorのfinishedでurlを取得できるのでそれを使います。

これちょっとアレなんですが、MovieCreatorのfinished(_:)側から見るとわかるんですが、

completionを呼び出しているのがfinishWritingのクロージャ内なんですね。

んでこれがおそらく別スレッドでの処理になってるんですね。

そうするとこのavPlayerがどうやら書いてるところの処理はメインスレッドじゃないとダメだよ!って怒られるんです。

This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
って言われます。

ログにこんな風に出てきて以下にブワーーーっと続くやつがでます。



なので

を使ってメインスレッドに戻してあげています。怒られる割には一応動くんですけども。

 

 

なんか頑張って書いたら結構なボリュームになってしまいましたが、コード見りゃわかるって人は「uruly/ MovieCreator: 【iOS】UIImageからmp4を生成する」で見てください。

 

というか結構頑張ってスッキリしたコード書いたつもりだったんですが、それでもまだまだ改善できますね・・・。initもちゃんと用意した方が良いんかなとも思ったり。

 

誰かの何かの役に立てれば嬉しいです。それではでは。

魚ライン
モッピー!お金がたまるポイントサイト
魚ライン

Swiftの記事一覧を見る

コメント

コメントは認証制です。詳しくは下記の注意をお読みください。

コメントを残す

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

魚ライン 魚ライン