banner
jzman

jzman

Coding、思考、自觉。
github

Camera2、MediaCodecによるmp4録画

PS:やりたいことをやり、毎日少しずつでも行動する。どれだけやるかは求めず、徐々に変わっていく。

音声と映像に関する知識を理解したら、以下の 2 つの記事を先に読むことができます:

この記事の主な内容は、Android のネイティブハードウェアエンコード・デコードフレームワーク MediaCodec とマルチプレクサ MediaMuxer を使用して mp4 ビデオファイルを録画することです。ビデオデータソースは Camera2 によって提供されます。ここでは、mp4 の録画ではなく、エンコードとマルチプレクシングのプロセスに重点を置いています。単にビデオを録画するだけであれば、より便利な MediaRecorder を選択できますが、慣例として MediaCodec を事例形式で学ぶことにします。MediaCodec のさらなる使用法は、今後の記事で紹介されます。この記事の主な内容は以下の通りです:

  1. Camera2 の使用
  2. MediaCodec の入力方式
  3. MediaCodec による Camera2 データのエンコード
  4. 録画プロセス
  5. 録画効果

Camera2 の使用#

Camera2 は Android 5.0 から導入された新しいカメラ API で、最新の CameraX は Camera2 に基づいており、Camera2 よりも使いやすい API を提供しています。後の文で言及される関連 API は、以下の図を直接参照できます。これが Camera2 の使用の概略図です:

image

MediaCodec の入力方式#

MediaCodec を使用してエンコード操作を行うためには、カメラのデータをエンコーダ MediaCodec に入力する必要があります。データを MediaCodec に書き込む方法は 2 つあります。具体的には以下の通りです:

  • Surface:Surface をエンコーダ MediaCodec の入力として使用します。つまり、MediaCodec が作成した Surface をその入力として使用します。この Surface は MediaCodec の createInputSurface メソッドによって作成され、カメラが有効なデータをこの Surface にレンダリングすると、MediaCodec は直接エンコードされたデータを出力できます。
  • InputBuffer:入力バッファをエンコーダ MediaCodec の入力として使用します。ここで必要なデータは原始フレームデータです。Camera2 の場合、ImageReader を介してフレームデータを直接取得できます。取得した Image には、幅、高さ、フォーマット、タイムスタンプ、YUV データ成分などの情報が含まれており、制御の程度が高くなります。

MediaCodec による Camera2 データのエンコード#

MediaCodec のデータ処理方式について簡単に説明します。Android 5.0 以前は ByteBuffer [] の同期方式のみをサポートしていましたが、その後は ByteBuffer の同期および非同期方式の使用が推奨されています。ここでは ByteBuffer の同期方式を使用し、関与するプロセスは主にビデオデータのエンコードとマルチプレクシングです。前述のように、MediaCodec の入力は Surface を介して行われるため、ここではすでにエンコードされたデータを取得し、マルチプレクサ MediaMuxer を使用して Mp4 ファイルを生成するだけです。重要なコードは以下の通りです:

// 成功裏にエンコードされた出力バッファのインデックスを返す
var outputBufferId: Int = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0)
if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    // ビデオトラックを追加
    mTrackIndex = mMediaMuxer.addTrack(mMediaCodec.outputFormat)
    mMediaMuxer.start()
    mStartMuxer = true
} else {
    while (outputBufferId >= 0) {
        if (!mStartMuxer) {
            Log.i(TAG, "MediaMuxerが開始されていません")
            continue
        }
        // 有効なデータを取得
        val outputBuffer = mMediaCodec.getOutputBuffer(outputBufferId) ?: continue
        outputBuffer.position(bufferInfo.offset)
        outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
        if (pts == 0L) {
            pts = bufferInfo.presentationTimeUs
        }
        bufferInfo.presentationTimeUs = bufferInfo.presentationTimeUs - pts
        // データをマルチプレクサに書き込んでファイルを生成
        mMediaMuxer.writeSampleData(mTrackIndex, outputBuffer, bufferInfo)
        Log.d(
            TAG,
            "pts = ${bufferInfo.presentationTimeUs / 1000000.0f} s ,${pts / 1000} ms"
        )
        mMediaCodec.releaseOutputBuffer(outputBufferId, false)
        outputBufferId = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0)
    }
}

録画プロセス#

ここでは Surface をエンコーダ MediaCodec の入力として使用します。MediaCodec が設定状態に入ると、Surface を作成できます。つまり、createInputSurface は configure と start の間にのみ呼び出すことができます。以下を参照してください:

// 設定状態
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
// MediaCodecの入力としてSurfaceを作成。createInputSurfaceはconfigureとstartの間にのみ呼び出すことができる
mSurface = mMediaCodec.createInputSurface()
// start ...

これを SessionConfiguration の出力 Surface リストに追加します。以下を参照してください:

// CaptureSessionを作成
@RequiresApi(Build.VERSION_CODES.P)
private suspend fun createCaptureSession(): CameraCaptureSession = suspendCoroutine { cont ->
    val outputs = mutableListOf<OutputConfiguration>()
    // プレビューSurface                                                                              
    outputs.add(OutputConfiguration(mSurface))
    // MediaCodec用の入力Surfaceを追加                                                                               
    outputs.add(OutputConfiguration(EncodeManager.getSurface()))
    val sessionConfiguration = SessionConfiguration(
        SessionConfiguration.SESSION_REGULAR,
        outputs, mExecutor, ...)
    mCameraDevice.createCaptureSession(sessionConfiguration)
}

次に CaptureRequest を発行してプレビューを開始し、Surface 出力を受信し、同時にエンコードを開始します。以下を参照してください:

// プレビューのSurfaceとImage生成のSurfaceを追加
mCaptureRequestBuild = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
val sur = EncodeManager.getSurface()
mCaptureRequestBuild.addTarget(sur)
mCaptureRequestBuild.addTarget(mSurface)

// 各種パラメータを設定
mCaptureRequestBuild.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, 1) // ビデオ安定機能が有効かどうか
// CaptureRequestを送信
mCameraCaptureSession.setRepeatingRequest(
    mCaptureRequestBuild.build(),
    null,
    mCameraHandler
)
// エンコードを開始
EncodeManager.startEncode()

録画効果#

image

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。