it-swarm.com.de

MediaCodec und Camera: Farbbereiche stimmen nicht überein

Ich habe versucht, die H264-Kodierung mit den Eingaben der Kamera auf einem Android-Tablet unter Verwendung des neuen Low-Level-Moduls MediaCodec zu bearbeiten. Ich habe einige Schwierigkeiten damit gemacht, da die MediaCodecAPI schlecht dokumentiert ist, aber ich habe endlich etwas zu arbeiten bekommen.

Ich richte die Kamera wie folgt ein:

        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setPreviewFormat(ImageFormat.YV12); // <1>
        parameters.setPreviewFpsRange(4000,60000);
        parameters.setPreviewSize(640, 480);            
        mCamera.setParameters(parameters);

Für den Codierungsteil instantiiere ich das MediaCodec-Objekt wie folgt:

    mediaCodec = MediaCodec.createEncoderByType("video/avc");
    MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 640, 480);
    mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 500000);
    mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
    mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar); // <2>
    mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
    mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    mediaCodec.start();

Das Endziel besteht darin, einen RTP-Stream zu erstellen (und mit Skype zu korrespondieren), aber bisher streame ich nur den rohen H264 direkt auf meinen Desktop. Dort verwende ich die folgende GStreamer-Pipeline, um das Ergebnis anzuzeigen:

gst-launch udpsrc port=5555 ! video/x-h264,width=640,height=480,framerate=15/1 ! ffdec_h264 ! autovideosink

Alles funktioniert gut, bis auf die Farben. Ich muss zwei Farbformate im Computer einstellen: eines für die Kamera-Vorschau (Zeile mit <1>) und eines für das MediaCodec-Objekt (mit <2>).

Um die akzeptablen Werte für die Zeilen <1> zu ermitteln, habe ich parameters.getSupportedPreviewFormats() verwendet. Aus diesem Grund weiß ich, dass die einzigen unterstützten Formate auf der Kamera ImageFormat.NV21 und ImageFormat.YV2 sind.

Für <2> habe ich das MediaCodecInfo.CodecCapabilities - Objekt für type video/avc abgerufen, wobei es sich um die ganzzahligen Werte 19 handelt (entsprechend MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar und 2170308 (Dies entspricht keinem Wert von MediaCodecInfo.CodecCapabilities ). 

Jeder andere Wert als der oben genannte führt zu einem Absturz.

Die Kombination dieser Einstellungen führt zu unterschiedlichen Ergebnissen, die ich unten zeigen werde. Hier ist der Screenshot auf Android (d. H. Die "echten" Farben): Input on Android-tablet Hier sind die Ergebnisse von Gstreamer:

<1> = NV21, <2> = COLOR_FormatYUV420Planar Gstreamer-output for NV21-COLOR_FormatYUV420Planar

<1> = NV21, <2> = 2130708361 Gstreamer-output for NV21-2130708361

<1> = YV2, <2> = COLOR_FormatYUV420Planar Gstreamer-output for YV2-COLOR_FormatYUV420Planar

<1> = YV2, <2> = 2130708361 Gstreamer-output for YV2-2130708361

Wie man sehen kann, ist keine davon zufriedenstellend. Der YV2-Farbraum sieht am vielversprechendsten aus, aber Rot (Cr) und Blau (Cb) sind invertiert. Die NV21 sieht zwar interlaced aus, denke ich (allerdings bin ich kein Experte auf diesem Gebiet).

Da der Zweck darin besteht, mit Skype zu kommunizieren, gehe ich davon aus, dass ich den Decoder (d. H. Den Gstreamer-Befehl) nicht ändern sollte, richtig? Soll dies in Android gelöst werden und wenn ja: wie? Oder kann dies durch Hinzufügen bestimmter RTP Payload-Informationen gelöst werden? Irgendein anderer Vorschlag?

31
gleerman

Ich habe es gelöst, indem ich die Byteebenen selbst auf Android-Ebene mit einer einfachen Funktion ausgetauscht habe:

public byte[] swapYV12toI420(byte[] yv12bytes, int width, int height) {
    byte[] i420bytes = new byte[yv12bytes.length];
    for (int i = 0; i < width*height; i++)
        i420bytes[i] = yv12bytes[i];
    for (int i = width*height; i < width*height + (width/2*height/2); i++)
        i420bytes[i] = yv12bytes[i + (width/2*height/2)];
    for (int i = width*height + (width/2*height/2); i < width*height + 2*(width/2*height/2); i++)
        i420bytes[i] = yv12bytes[i - (width/2*height/2)];
    return i420bytes;
}
7
gleerman

Ich denke, es ist effizienter, die vorhandenen Werte auszutauschen.

        int wh4 = input.length/6; //wh4 = width*height/4
        byte tmp;
        for (int i=wh4*4; i<wh4*5; i++)
            {
            tmp = input[i];
            input[i] = input[i+wh4];
            input[i+wh4] = tmp;
            }

Vielleicht noch besser, Sie können stattdessen ersetzen

            inputBuffer.put(input);

Mit den 3 ebenen Scheiben in der richtigen Reihenfolge

            inputBuffer.put(input, 0, wh4*4);
            inputBuffer.put(input, wh4*5, wh4);
            inputBuffer.put(input, wh4*4, wh4);

Ich denke, das sollte nur einen kleinen Aufwand haben

6
John La Rooy

Es scheint, dass Android in YV12 sendet, aber das in den H264-Headern festgelegte Format ist YUV420. Diese Formate sind gleich, mit der Ausnahme, dass die U- und V-Kanäle in unterschiedlicher Reihenfolge angeordnet sind, was das Vertauschen von Rot und Blau erklärt.

Am besten wäre es natürlich, die Einstellung auf der Android-Seite zu korrigieren. Wenn Sie jedoch keine kompatiblen Einstellungen für Kamera und Encoder festlegen können, müssen Sie das Format auf der GStreamer-Seite erzwingen.

Dies kann durch Hinzufügen eines capssetter-Elements nach dem ffdec_h264 erfolgen.

... ! ffdec_h264 ! capssetter caps="video/x-raw-yuv, format=(fourcc)YV12" ! colorspace ! ...

3
jpa

Wenn ImageFormat.NV21 auf die Kamera eingestellt ist und COLOR_FormatYUV420Planar für den Encoder, sind in meinem Fall ähnliche blaue Schatten zu überlappen. Soweit ich weiß, dass die obige Swap-Funktion in meinem Fall nicht verwendet werden kann, sind Vorschläge zu einem Algorithmus, der für diesen Zweck verwendet werden kann? ps: Dies ist ein vollständiger schwarzer Bildschirm beim Decoder, wenn das Vorschauformat der Kamera auf YV12 eingestellt ist

0
Lakshmi