web-dev-qa-db-ja.com

Android非同期モードでのMediaCodecエンコードおよびデコード

ファイルからビデオをデコードし、MediaCodecを使用して別の形式にエンコードしようとしています。APIレベル21以降(Android)でサポートされる新しい非同期モード OS 5.0 Lollipop)。

Big Flake 、Googleの Grafika などのサイトのSynchronous Modeでこれを行う多くの例と、多数の回答がありますStackOverflowでは、非同期モードをサポートしていません。

処理中にビデオを表示する必要はありません。

一般的な手順は、MediaExtractor(decoder)への入力としてMediaCodecを使用してファイルを読み取り、Decoderの出力をSurfaceにレンダリングできるようにすることですこれは、MediaCodec(encoder)への共有入力でもあり、最後にMediaMuxerを介してエンコーダ出力ファイルを書き込みます。 Surfaceは、エンコーダーのセットアップ中に作成され、デコーダーと共有されます。

ビデオをTextureViewにデコードできますが、画面の代わりにエンコーダーでSurfaceを共有できませんでした。

両方のコーデックにMediaCodec.Callback() sを設定しました。問題は、エンコーダのコールバックのonInputBufferAvailable()関数で何をすべきかわからないことだと思います。 Surfaceからエンコーダーにデータをコピーする方法(または方法)がわかりません-これは自動的に行われます(codec.releaseOutputBuffer(outputBufferId, true);を使用したデコーダーの出力で行われるように)。しかし、onInputBufferAvailableが機能するには、codec.queueInputBufferの呼び出しが必要だと思います。デコード側で使用されるMediaExtractorなどのデータを取得せずにパラメーターを設定する方法がわからないだけです。

ビデオファイルを開き、それをデコードし、非同期MediaCodecコールバックを使用して別の解像度または形式にエンコードし、ファイルとして保存する例がある場合、サンプルコードを共有してください。

===[〜#〜]編集[〜#〜]===

以下は、非同期モードで実行しようとしている同期モードでの動作例です:ExtractDecodeEditEncodeMuxTest.Java: https://Android.googlesource.com/platform/cts/+/jb-mr2-release/tests /tests/media/src/Android/media/cts/ExtractDecodeEditEncodeMuxTest.Java この例は私のアプリケーションで動作しています

Android MediaCodec

15
David Manpearl

エンコーダのonInputBufferAvailable()コールバックで何もする必要はないと思います。encoder.queueInputBuffer()を呼び出さないでください。同期モードでSurface入力エンコードを実行するときにencoder.dequeueInputBuffer()およびencoder.queueInputBuffer()を手動で呼び出さないように、非同期モードでもこれを実行しないでください。

decoder.releaseOutputBuffer(outputBufferId, true);を呼び出すと(同期モードと非同期モードの両方で)、これは内部的に(指定したSurfaceを使用して)入力バッファーをサーフェスからデキューし、出力をレンダリングして、それをエンキューします表面に戻る(エンコーダーに)。同期モードと非同期モードの唯一の違いは、パブリックAPIでのバッファーイベントの公開方法ですが、Surface入力を使用する場合、異なる(内部)APIを使用して同じモードにアクセスするため、同期モードと非同期モードは関係ありません。これはまったく。

だから私が知る限り(私自身は試していませんが)、エンコーダーのonInputBufferAvailable()コールバックを空のままにしておく必要があります。

EDIT:それで、私はこれを自分でやってみました、そしてそれは上記のように(ほとんど)単純です。

エンコーダー入力サーフェスがデコーダーへの出力として直接構成されている場合(SurfaceTextureが間にない場合)、同期デコード/エンコードループが非同期ループに変換されて、問題なく機能します。

ただし、SurfaceTextureを使用すると、小さな問題に遭遇する可能性があります。呼び出しスレッドに関して、フレームがSurfaceTextureに到着するのを待つ方法に問題があります。参照 https://Android.googlesource.com/platform/cts/+/jb-mr2-release/tests /tests/media/src/Android/media/cts/DecodeEditEncodeTest.Java#106 および https://Android.googlesource.com/platform/cts/+/jb-mr2-release/tests/ tests/media/src/Android/media/cts/EncodeDecodeTest.Java#104 and https://Android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests /media/src/Android/media/cts/OutputSurface.Java#11 これへの参照。

私が見る限り、この問題はawaitNewImageにあります https://Android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/ media/src/Android/media/cts/OutputSurface.Java#24onFrameAvailableコールバックがメインスレッドで呼び出されることになっている場合、awaitNewImage呼び出しもメインスレッドで実行されると問題が発生します。 onOutputBufferAvailableコールバックもメインスレッドで呼び出され、そこからawaitNewImageを呼び出すと、コールバックを待機することになり、問題が発生します(wait()はスレッド全体をブロックします)、現在のメソッドが戻るまで実行できません。

そのため、onFrameAvailableコールバックがawaitNewImageを呼び出すスレッドとは別のスレッドで行われるようにする必要があります。これを行う非常に簡単な方法の1つは、onFrameAvailableコールバックにサービスを提供するだけで、新しい個別のスレッドを作成することです。これを行うには、たとえば、この:

    private HandlerThread mHandlerThread = new HandlerThread("CallbackThread");
    private Handler mHandler;
...
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
...
        mSurfaceTexture.setOnFrameAvailableListener(this, mHandler);

これで問題を解決するのに十分であることを願っています。公開コールバックのいずれかを編集して非同期コールバックを実装する必要がある場合は、お知らせください。

EDIT2:また、GLレンダリングはonOutputBufferAvailableコールバック内から行われる可能性があるため、これはEGLコンテキストを設定したスレッドとは別のスレッドである可能性があるため、その場合は、次のように、EGLコンテキストを設定したスレッドでEGLコンテキストを解放する必要があります。

mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);

レンダリングする前に、他のスレッドに再接続します。

mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);

EDIT3:さらに、エンコーダとデコーダのコールバックが同じスレッドで受信された場合、レンダリングを行うデコーダonOutputBufferAvailableがエンコーダをブロックする可能性があります配信からのコールバック。配信されない場合、エンコーダーが出力バッファーを返さないため、レンダリングが無限にブロックされる可能性があります。これは、ビデオデコーダーコールバックが別のスレッドで受信されるようにすることで修正でき、代わりにonFrameAvailableコールバックの問題を回避できます。

私はExtractDecodeEditEncodeMuxTestの上にこれらすべてを実装してみましたが、一見問題なく動作しているようです。 https://github.com/mstorsjo/Android-decodeencodetest をご覧ください。最初は変更されていないテストをインポートし、非同期モードへの変換とトリッキーな詳細の修正を個別に行って、コミットログで個々の修正を簡単に確認できるようにしました。

13
mstorsjo

MediaEncoderでハンドラーを設定することもできます。

---> AudioEncoderCallback(aacSamplePreFrameSize)、mHandler);


MyAudioCodecWrapper myMediaCodecWrapper;

public MyAudioEncoder(long startRecordWhenNs){
    super.startRecordWhenNs = startRecordWhenNs;
}

@RequiresApi(api = Build.VERSION_CODES.M)
public MyAudioCodecWrapper prepareAudioEncoder(AudioRecord _audioRecord , int aacSamplePreFrameSize)  throws Exception{
    if(_audioRecord==null || aacSamplePreFrameSize<=0)
        throw new Exception();

    audioRecord = _audioRecord;
    Log.d(TAG, "audioRecord:" + audioRecord.getAudioFormat() + ",aacSamplePreFrameSize:" + aacSamplePreFrameSize);

    mHandlerThread.start();
    mHandler = new Handler(mHandlerThread.getLooper());

    MediaFormat audioFormat = new MediaFormat();
    audioFormat.setString(MediaFormat.KEY_MIME, MIMETYPE_AUDIO_AAC);
    //audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE );
    audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
    audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, audioRecord.getSampleRate());//44100
    audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, audioRecord.getChannelCount());//1(單身道)
    audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
    audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384);
    MediaCodec codec = MediaCodec.createEncoderByType(MIMETYPE_AUDIO_AAC);
    codec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    codec.setCallback(new AudioEncoderCallback(aacSamplePreFrameSize),mHandler);
    //codec.start();

    MyAudioCodecWrapper myMediaCodecWrapper = new MyAudioCodecWrapper();
    myMediaCodecWrapper.mediaCodec = codec;

    super.mediaCodec = codec;

    return myMediaCodecWrapper;

}
1
user8270308