it-swarm.com.de

Android MediaCodec Kodieren und Dekodieren im asynchronen Modus

Ich versuche, ein Video aus einer Datei zu decodieren und mit MediaCodec im neuen Asynchronous Mode zu codieren, der in API Level 21 und höher (Android OS 5.0 Lollipop) unterstützt wird.

Es gibt viele Beispiele dafür in Synchronous Mode auf Websites wie Big Flake , Googles Grafika und Dutzenden von Antworten auf StackOverflow, aber keiner von ihnen unterstützt den asynchronen Modus.

Ich muss das Video während des Vorgangs nicht anzeigen.

Ich glaube, dass die allgemeine Prozedur darin besteht, die Datei mit einer MediaExtractor als Eingabe für eine MediaCodec (Decoder) zu lesen, die Ausgabe des Decoders in eine Surface zu rendern, die auch die gemeinsame Eingabe in eine MediaCodec (Encoder) ist, und dann um schließlich die Encoder-Ausgabedatei über eine MediaMuxer zu schreiben. Die Surface wird während der Einrichtung des Encoders erstellt und mit dem Decoder gemeinsam genutzt.

Ich kann das Video in eine TextureView dekodieren, aber das Teilen der Surface mit dem Encoder anstelle des Bildschirms war nicht erfolgreich.

Ich setze MediaCodec.Callback()s für meine beiden Codecs ein. Ich glaube, ein Problem ist, dass ich nicht weiß, was ich mit der Funktion onInputBufferAvailable() des Encoders tun soll. Ich weiß nicht, was ich (oder weiß, wie man) Daten von der Surface in den Encoder kopiert - das sollte automatisch geschehen (wie am Decoder-Ausgang mit codec.releaseOutputBuffer(outputBufferId, true);). Ich glaube jedoch, dass onInputBufferAvailable einen Aufruf von codec.queueInputBuffer erfordert, um funktionieren zu können. Ich weiß einfach nicht, wie ich die Parameter einstellen soll, ohne Daten von einer MediaExtractor zu erhalten, wie sie auf der Decoderseite verwendet wird.

Wenn Sie ein -Beispiel haben, das eine Videodatei öffnet, decodiert, mit Hilfe der asynchronen MediaCodec-Callbacks in eine andere Auflösung oder ein anderes Format codiert und sie dann als Datei speichert , geben Sie bitte Ihren Beispielcode frei.

===EDIT===

Hier ist ein funktionierendes Beispiel für den asynchronen Modus, den ich im asynchronen Modus ausführen möchte: ExtractDecodeEditEncodeMuxTest.Java: https://Android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests /media/src/Android/media/cts/ExtractDecodeEditEncodeMuxTest.Java Dieses Beispiel funktioniert in meiner Anwendung

 Android MediaCodec

11
David Manpearl

Ich glaube, Sie sollten im onInputBufferAvailable() callback des Encoders nichts tun müssen - Sie sollten encoder.queueInputBuffer() nicht aufrufen. So wie Sie encoder.dequeueInputBuffer() und encoder.queueInputBuffer() nie manuell aufrufen, wenn Sie die Oberflächeneingabecodierung im synchronen Modus ausführen, sollten Sie dies auch nicht im asynchronen Modus tun.

Wenn Sie decoder.releaseOutputBuffer(outputBufferId, true); (sowohl im synchronen als auch im asynchronen Modus) aufrufen, nimmt dieser intern (unter Verwendung der von Ihnen angegebenen Surface) einen Eingabepuffer von der Oberfläche ab, rendert die Ausgabe in ihn und reiht ihn an die Oberfläche (an den Encoder) zurück. Der einzige Unterschied zwischen dem synchronen und dem asynchronen Modus besteht darin, wie die Pufferereignisse in der öffentlichen API verfügbar gemacht werden. Bei Verwendung der Surface-Eingabe verwendet sie jedoch eine andere (interne) API, um auf dieselbe zuzugreifen, sodass der synchrone vs. asynchrone Modus keine Rolle spielt das überhaupt.

Soweit ich weiß (obwohl ich es selbst nicht ausprobiert habe), sollten Sie den onInputBufferAvailable()-Callback für den Encoder leer lassen.

EDIT: Also habe ich es selbst versucht, und es ist (fast) so einfach wie oben beschrieben.

Wenn die Encoder-Eingabefläche direkt als Ausgabe für den Decoder konfiguriert ist (ohne SurfaceTexture dazwischen), funktionieren die Dinge einfach, wobei eine synchrone Decode-Codierschleife in eine asynchrone konvertiert wird.

Wenn Sie SurfaceTexture verwenden, stoßen Sie jedoch möglicherweise auf ein kleines Gotcha. Es gibt ein Problem damit, wie gewartet wird, bis Frames in Bezug auf den aufrufenden Thread zur SurfaceTexture gelangen, siehe https://Android.googlesource.com/platform/cts/+/jb-mr2-release/tests/ tests/media/src/Android/media/cts/DecodeEditEncodeTest.Java # 106 und https://Android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/ media/src/Android/media/cts/EncodeDecodeTest.Java # 104 und https://Android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/ src/Android/media/cts/OutputSurface.Java # 113 für Verweise darauf.

Das Problem ist, soweit ich es sehe, in awaitNewImage wie in https://Android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/Android/ media/cts/OutputSurface.Java # 240 . Wenn der Callback onFrameAvailable im Haupt-Thread aufgerufen werden soll, liegt ein Problem vor, wenn der Aufruf awaitNewImage auch im Haupt-Thread ausgeführt wird. Wenn die onOutputBufferAvailable-Callbacks auch im Haupt-Thread aufgerufen werden und Sie von dort aus awaitNewImage aufrufen, haben wir ein Problem, da Sie auf einen Rückruf warten müssen (mit einer wait(), die den gesamten Thread blockiert), die erst ausgeführt werden kann Die aktuelle Methode kehrt zurück.

Wir müssen also sicherstellen, dass die onFrameAvailable-Callbacks in einem anderen Thread erscheinen als der, der awaitNewImage aufruft. Eine ziemlich einfache Möglichkeit, dies zu tun, besteht darin, einen neuen separaten Thread zu erstellen, der nur die onFrameAvailable-Callbacks bedient. Um dies zu tun, können Sie z. diese:

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

Ich hoffe, dass dies für Sie ausreichend ist, um Ihr Problem lösen zu können. Lassen Sie mich wissen, wenn Sie eines der öffentlichen Beispiele bearbeiten müssen, um dort asynchrone Rückrufe zu implementieren.

EDIT2: Da das Rendern von GL möglicherweise innerhalb des Callbacks onOutputBufferAvailable erfolgt, ist dies möglicherweise ein anderer Thread als der, der den EGL-Kontext eingerichtet hat. In diesem Fall muss also der EGL-Kontext in dem Thread, in dem er eingerichtet wurde, wie folgt freigegeben werden:

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

Fügen Sie es vor dem Rendern erneut in den anderen Thread ein:

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

EDIT3: Wenn der Encoder und der Decoder-Callbacks im selben Thread empfangen werden, kann der Decoder onOutputBufferAvailable, der das Rendering ausführt, die Ausgabe des Encoder-Callbacks blockieren. Wenn sie nicht geliefert werden, kann das Rendern unendlich blockiert werden, da der Encoder die Ausgabepuffer nicht zurückgibt. Dies kann behoben werden, indem sichergestellt wird, dass die Video-Decoder-Callbacks stattdessen in einem anderen Thread empfangen werden. Dadurch wird das Problem mit dem Callback onFrameAvailable vermieden.

Ich habe versucht, all dies über ExtractDecodeEditEncodeMuxTest zu implementieren, und es funktioniert scheinbar gut. Schauen Sie sich https://github.com/mstorsjo/Android-decodeencodetest an. Ich habe zunächst den unveränderten Test importiert und die Konvertierung in den asynchronen Modus sowie die Korrekturen für die kniffligen Details separat vorgenommen, um die einzelnen Fixes im Commit-Protokoll einfacher zu betrachten.

12
mstorsjo

Kann auch den Handler im MediaEncoder einstellen.

---> 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