Androidのビデオ録画と処理





ビデオの記録と処理に関連するAndroidアプリケーションの作成は、かなり難しいタスクです。 MediaRecorderなどの標準ツールを使用することはそれほど難しくありませんが、通常を超えた何かを行おうとすると、本当の「楽しみ」が始まります。



Androidのビデオの何が問題になっていますか



Androidでバージョン4.3までのビデオを操作するための機能は非常に不足しています。CameraMediaRecorderを使用してカメラからビデオを記録し、カメラに標準のカラーフィルター(セピア、白黒など)を適用することができます。



バージョン4.1から、低レベルのコーデックへのアクセスを提供するMediaCodecクラスと、ソースからエンコードされたメディアを抽出できるMediaExtractorクラスを使用できるようになりました。



スキーム




Android 4.3では、複数のビデオおよびオーディオストリームを単一のファイルに記録できるMediaMuxerクラスが導入されました。



スキーム




ここではすでに創造性の機会が増えています。この機能により、ビデオストリームをエンコードおよびデコードできるだけでなく、録画中にビデオ処理を実行できます。



私が取り組んでいたプロジェクトには、アプリケーションに関するいくつかの要件がありました。





ツール



最初は、MediaRecorderを使用してビデオが記録されました。 この方法は最も単純で、長い間使用されており、多くの例があり、Androidのすべてのバージョンでサポートされています。 ただし、カスタマイズには適していません。 さらに、MediaRecorder'aを約700ミリ秒の遅延で使用すると、記録が開始されます。 ビデオの小片を記録する場合、ほぼ2秒の遅延は許容されません。



したがって、Android 4.3の最小互換バージョンを増やし、MediaCodecとMediaMuxerを使用してビデオを録画することが決定されました。 このソリューションにより、録音の初期化の遅延を取り除くことができました。 カメラからキャプチャされたフレームをレンダリングおよび変更するために、OpenGLがシェーダーと併用されました。



例はGoogleからのものです。 Grafikaと呼ばれるプロジェクトは、ビデオ録画および処理ツールの使用に対処するのに役立つサンプルの松葉杖のコンパイルです。

後処理ビデオにはFFmpegが使用されました。 ffmpegの主な問題は、必要なモジュールを構築し、プロジェクトに接続することです。 これは特定のスキルを必要とする長いプロセスであるため、Android用の既製ビルドを使用しました。 これらのffmpegアセンブリのほとんどで動作する特性は、実行可能なコマンドラインファイルとして使用する必要があることです。入力パラメーターと最終ビデオに適用するパラメーターを含む文字列コマンドを渡します。 デバッグする能力の欠如、そして間違いが何であるかを実際に知ることは、何かがうまくいかなかった場合、非常に憂鬱です。 情報の唯一のソースはログファイルであり、ffmpegの実行中に記録されます。 したがって、最初は、1つまたは別のチームがどのように機能するか、複数のアクションを一度に実行する複合コマンドを作成する方法などを理解するのに多くの時間がかかります。



スローモーション



現時点では、Androidデバイスの大部分が十分なフレームレートでビデオを録画するためのハードウェアサポートを持たないため、Slow Motionの実装を拒否しました。 ハードウェアがサポートされているデバイスのごく一部でも、この機能を「アクティブ化」する通常の機能はありません。



プログラムをスローモーションにすることができます。これにはオプションがあります:





しかし、結果はかなり質の悪いものです。







速い動き



ただし、タイムラプスビデオの録画には問題はありません。 MediaRecorderで録画する場合、フレームレート、たとえば10(ビデオ録画の標準フレームレートは30)を設定でき、3フレームごとに録画されます。 その結果、ビデオは3倍加速されます。



コード
private boolean prepareMediaRecorder() { if (camera == null) { return false; } camera.unlock(); if (mediaRecorder == null) { mediaRecorder = new MediaRecorder(); mediaRecorder.setCamera(camera); } mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); CamcorderProfile profile = getCamcorderProfile(cameraId); mediaRecorder.setCaptureRate(10); //         mediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight); mediaRecorder.setVideoFrameRate(30); mediaRecorder.setVideoEncodingBitRate(profile.videoBitRate); mediaRecorder.setOutputFile(createVideoFile().getPath()); mediaRecorder.setPreviewDisplay(cameraPreview.getHolder().getSurface()); mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); try { mediaRecorder.prepare(); } catch (Exception e) { releaseMediaRecorder(); return false; } return true; }
      
      











ストップモーション



複数のフレームのインスタント録画の場合、MediaRecorderを使用する標準バージョンは、録画を開始するまでの遅延が長いため適切ではありません。 ただし、MediaCodecとMediaMuxerを使用すると、パフォーマンスの問題を解決できます。







記録されたピースを単一のファイルに接着する



これは、アプリケーションの主要な機能の1つです。 その結果、いくつかのチャンクを記録した後、ユーザーは1つの完全なビデオファイルを受け取る必要があります。



最初はffmpegが使用されていましたが、ffmpegがビデオをトランスコーディングで接着し、プロセスが非常に長くなったため(Nexus 5では7-8チャンクを1つの15秒のビデオに接着するには15秒以上かかり、 100チャンク時間が1分以上に増加しました)。 同じビットレートでより良い結果が得られる、より高いビットレートまたはコーデックを使用する場合、プロセスにはさらに時間がかかりました。



したがって、 mp4parserライブラリが使用されるようになりました。これは、本質的に、コンテナファイルからエンコードされたデータを取得し、新しいコンテナを作成し、新しいコンテナに次々にデータを配置します。 次に、コンテナのヘッダーに情報を書き込みます。それだけで、出力ではビデオ全体が取得されます。 このアプローチの唯一の制限:すべてのチャンクは同じパラメーター(コーデックタイプ、解像度、アスペクト比など)でエンコードする必要があります。 このアプローチは、チャンクの数に応じて1〜4秒で機能します。



mp4parserを使用して複数のビデオファイルを1つに結合する例
 public void merge(List<File> parts, File outFile) { try { Movie finalMovie = new Movie(); Track[] tracks = new Track[parts.size()]; for (int i = 0; i < parts.size(); i++) { Movie movie = MovieCreator.build(parts.get(i).getPath()); tracks[i] = movie.getTracks().get(0); } finalMovie.addTrack(new AppendTrack(tracks)); FileOutputStream fos = new FileOutputStream(outFile); BasicContainer container = (BasicContainer) new DefaultMp4Builder().build(finalMovie); container.writeContainer(fos.getChannel()); } catch (IOException e) { Log.e(TAG, "Merge failed", e); } }
      
      







ビデオオーバーレイ、ビデオクロップ、および透かし



ここではFfmpegでは十分ではありません。 たとえば、オーディオトラックをビデオにオーバーレイするコマンドは次のとおりです。



 ffmpeg -y -ss 00:00:00.00 -t 00:00:02.88 -i input.mp4 -ss 00:00:00.00 -t 00:00:02.88 -i tune.mp3 -map 0:v:0 -map 1:a:0 -vcodec copy -r 30 -b:v 2100k -acodec aac -strict experimental -b:a 48k -ar 44100 output.mp4
      
      





-ss 00:00:00.00-この場合、処理を開始する時間

-t 00:00:02.88-入力ファイルの処理を継続する必要がある時間

-i input.mp4-入力ビデオファイル

-i tune.mp3-入力オーディオファイル

-map-ビデオチャネルとオーディオチャネルのマ​​ッピング

-vcodec-ビデオコーデックをインストールします(この場合、ビデオをエンコードした同じコーデックが使用されます)

-r-フレームレートの設定

-b:v-ビデオチャネルのビットレートを設定します

-acodec-オーディオコーデックをインストールします(この場合、AACエンコーディングを使用します)

-ar-オーディオチャネルのサンプルレート

-b:a-オーディオチャネルビットレート



透かしを適用してビデオをトリミングするコマンド:



 ffmpeg -y -i input.mp4 -strict experimental -r 30 -vf movie=watermark.png, scale=1280*0.1094:720*0.1028 [watermark]; [in][watermark] overlay=main_w-overlay_w:main_h-overlay_h, crop=in_w:in_w:0:in_h*in_h/2 [out] -b:v 2100k -vcodec mpeg4 -acodec copy output.mp4
      
      





movie =透かし.png-透かしへのパスを設定します

scale = 1280 * 0.1094:720 * 0.1028-サイズを指定

[in] [watermark] overlay = main_w-overlay_w:main_h-overlay_h、crop = in_w:in_w:0:in_h * in_h / 2 [out] -透かしをオーバーレイしてビデオをトリミングします。



リバースビデオ



リバースビデオを作成するには、いくつかの操作を実行する必要があります。





ソリューションはエレガントでも生産的でもありませんが、特定の選択肢はありません。



ビデオをフレーム付きのファイルに分割するコマンドの例:



 ffmpeg -y -i input.mp4 -strict experimental -r 30 -qscale 1 -f image2 -vcodec mjpeg %03d.jpg
      
      





その後、フレームファイルの名前を逆順にするように名前を変更する必要があります(つまり、最初のフレームが最後、最後のフレームが最初、2番目のフレームが最後から2番目、最後から2番目のフレームなど)。



次に、次のコマンドを使用して、フレームからビデオを収集できます。



 ffmpeg -y -f image2 -i %03d.jpg -r 30 -vcodec mpeg4 -b:v 2100k output.mp4
      
      









ビデオGIF



また、アプリケーションの機能の1つは、複数のフレームで構成される短いビデオの作成です。これにより、ループ時にGIFエフェクトが作成されます。 このトピックは現在需要があります。Instagramは最近、Boomerangを開始しました-このような「gif」を作成するための特別なアプリケーションです。



プロセスは非常に簡単です-等しい時間(この場合は125ミリ秒)で8枚の写真を撮り、最初と最後を除くすべてのフレームを複製してスムーズな逆効果を実現し、ビデオのフレームを収集します。



たとえば、ffmpegを使用する場合:



 ffmpeg -y -f image2 -i %02d.jpg -r 15 -filter:v setpts=2.5*PTS -vcodec libx264 output.mp4
      
      





-f-入力ファイル形式

-i%02d.jpg-動的な名前形式の入力ファイル(01.jpg、02.jpgなど)

-filter:v setpts = 2.5 * PTSは各フレームの持続時間を2.5倍に延長します



現時点では、UXを最適化するため(ユーザーが長いビデオ処理を待たないように)、ビデオの保存と共有の段階でビデオファイル自体を作成しています。 これに先立ち、RAMにロードされCanvas'e TextureViewに描画される写真で作業が行われます。



描画プロセス
  private long drawGif(long startTime) { Canvas canvas = null; try { if (currentFrame >= gif.getFramesCount()) { currentFrame = 0; } Bitmap bitmap = gif.getFrame(currentFrame++); if (bitmap == null) { handler.notifyError(); return startTime; } destRect(frameRect, bitmap.getWidth(), bitmap.getHeight()); canvas = lockCanvas(); canvas.drawBitmap(bitmap, null, frameRect, framePaint); handler.notifyFrameAvailable(); if (showFps) { canvas.drawBitmap(overlayBitmap, 0, 0, null); frameCounter++; if ((System.currentTimeMillis() - startTime) >= 1000) { makeFpsOverlay(String.valueOf(frameCounter) + "fps"); frameCounter = 0; startTime = System.currentTimeMillis(); } } } catch (Exception e) { Timber.e(e, "drawGif failed"); } finally { if (canvas != null) { unlockCanvasAndPost(canvas); } } return startTime; }
      
      





  public class GifViewThread extends Thread { public void run() { long startTime = System.currentTimeMillis(); try { if (isPlaying()) { gif.initFrames(); } } catch (Exception e) { Timber.e(e, "initFrames failed"); } finally { Timber.d("Loading bitmaps in " + (System.currentTimeMillis() - startTime) + "ms"); } long drawTime = 0; while (running) { if (paused) { try { Thread.sleep(10); } catch (InterruptedException ignored) {} continue; } if (surfaceDone && (System.currentTimeMillis() - drawTime) > FRAME_RATE_BOUND) { startTime = drawGif(startTime); drawTime = System.currentTimeMillis(); } } } }
      
      











おわりに



一般に、Androidプラットフォームでビデオを操作することは依然として苦痛です。 多かれ少なかれ高度なアプリケーションを実装するには、多くの時間、非標準ソリューションの松葉杖 、そしておそらくJNIの深化が必要です。 最悪なのは、iOSプラットフォームでは、多くの機能がそのままで、またははるかに少ない労力で機能することです。 将来の計画のために、私は独自のffmpegアセンブリを作成し、JNIレベルで使用したいと思います。これにより、生産性、使用の柔軟性が向上し、ライブラリの全体の重量が軽減されます(プロジェクトにすべてのモジュールが必要というわけではないため)。



All Articles