Подтвердить что ты не робот

MediaCodec и Camera: цветовые пространства не совпадают

Я пытаюсь получить кодировку H264 для работы с вводом, захваченным камерой на планшете Android, с использованием нового низкоуровневого MediaCodec. С этим я столкнулся с некоторыми трудностями, поскольку MediaCodecAPI плохо документирован, но у меня наконец-то появилось что-то.

Я настраиваю камеру следующим образом:

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

Для части кодирования я создаю объект MediaCodec следующим образом:

    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();

Конечная цель - создать RTP-поток (и соответствовать Skype), но до сих пор я просто передавал необработанный H264 прямо на мой рабочий стол. Там я использую следующий конвейер GStreamer, чтобы показать результат:

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

Все работает хорошо, за исключением цветов. Мне нужно установить 2 цветовыхформата на компьютере: один для предварительного просмотра камеры (строка с тегами <1>) и одна для объекта MediaCodec (с тегами <2>)

Чтобы определить допустимые значения для строк <1>, я использовал parameters.getSupportedPreviewFormats(). Из этого я знаю, что только поддерживаемые форматы на камере ImageFormat.NV21 и ImageFormat.YV2.

В <2> я получил MediaCodecInfo.CodecCapabilities -объект для типа video/avc, являющийся целыми значениями 19 (соответствует MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar и 2130708361 (который не соответствует ни одному значению MediaCodecInfo.CodecCapabilities).

Любое другое значение, чем приведенное выше, приводит к сбою.

Объединение этих настроек дает разные результаты, которые я покажу ниже. Вот скриншот на Android (т.е. "Настоящие" цвета): Input on Android-tablet Вот результаты, показанные 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

Как видно, ни одно из них не удовлетворяет. Цветовое пространство YV2 выглядит наиболее перспективным, но похоже, что красный (Cr) и синий (Cb) инвертированы. NV21 выглядит чересстрочным, я думаю (однако, я не эксперт в этой области).

Поскольку целью является общение со Skype, я предполагаю, что я не должен менять декодер (т.е. команду Gstreamer), правильно? Это нужно решить в Android, и если да: как? Или это можно решить, добавив определенную информацию полезной нагрузки RTP? Любое другое предложение?

4b9b3361

Ответ 1

Я решил это, заменив байтовые самолеты на уровне Android, используя простую функцию:

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

Ответ 2

Я думаю, что более эффективно менять значения на месте.

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

Может быть, даже лучше, вместо этого вы можете заменить

            inputBuffer.put(input);

С 3 планарными срезами в правильном порядке

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

Я думаю, что у него должны быть только небольшие накладные расходы

Ответ 3

Кажется, Android передается в YV12, но формат, заданный в заголовках H264, - YUV420. Эти форматы равны, за исключением того, что каналы U и V находятся в другом порядке, что объясняет замену красного и синего.

Лучшим было бы, конечно, исправить настройку на стороне Android. Но если нет способа установить совместимые настройки для камеры и кодировщика, вам придется форсировать формат на стороне GStreamer.

Это можно сделать, добавив элемент capssetter после ffdec_h264

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

Ответ 4

С установкой ImageFormat.NV21 на камеру и COLOR_FormatYUV420Planar для кодировщика аналогичная синяя тень, как видно, перекрывается в моем случае. Насколько я понимаю, вышеупомянутая функция обмена не может быть использована в моем случае, какие-либо предложения по алгоритму, который можно использовать для этого? ps: Его полный черный экран в декодере, когда формат предварительного просмотра камеры установлен как YV12