banner
jzman

jzman

Coding、思考、自觉。
github

Camera2、MediaCodecでmp4を録画する

PS:やりたいことをやるために、毎日少なくとも少しでも行動し、多くを求めずにゆっくりと変化させていきましょう。

音声および動画の関連知識を理解したら、以下の 2 つの記事を読むことができます:

この記事の主な内容は、Android のネイティブなハードウェアエンコード / デコードフレームワークである MediaCodec と MediaMuxer を使用して、mp4 ビデオファイルの録画を実現することです。ビデオデータソースは Camera2 から提供され、重点はエンコードとマルチプレクサのプロセスであり、mp4 の録画ではありません。単にビデオの録画を行う場合は、より便利な MediaRecorder を選択できます。MediaCodec の使用法については、後続の記事で詳しく説明します。この記事の主な内容は次のとおりです:

  1. Camera2 の使用
  2. MediaCodec の入力方法
  3. Camera2 データの MediaCodec エンコード
  4. 録画の手順
  5. 録画の効果

Camera2 の使用#

Camera2 は Android 5.0 から導入された新しいカメラ API であり、最新のバージョンは CameraX です。CameraX は Camera2 に基づいており、より使いやすい API を提供しています。関連する API については、以下の図を参照してください。これは Camera2 の使用例です。

image

MediaCodec の入力方法#

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

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

Camera2 データの MediaCodec エンコード#

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 not start")
            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)
// 入力として使用するSurfaceをMediaCodecの入力として作成する。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

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