Может ли Android MediaCodec одновременно декодировать на две поверхности?

Я пытаюсь улучшить извлечение кадров нашего приложения. По сути, я объединил решение из MoviePlayer для прямого поиска и BigFlake ExtractMpegFramesTest для извлечения кадра. Для извлечения я ищу предыдущий ключевой кадр, затем декодирую вперед и сохраняю только последний кадр. Что-то вроде этого (см. мой предыдущий вопрос для более полного объяснения):

decoder.releaseOutputBuffer(decoderStatus, doRender);
    if (doRender) {
        if (VERBOSE) Log.d(TAG, "awaiting decode of frame " + decodeCount);
        outputSurface.awaitNewImage();
        outputSurface.drawImage(false);

        if(extractor.getSampleTime() == mPosition){ 
            Log.d(TAG, "sampleTime: " + extractor.getSampleTime() + " mPosition: " + mPosition + "----- EXTRACTING FRAME");
            long startWhen = System.currentTimeMillis();
            outputSurface.saveFrame();
            long frameSaveTime = System.currentTimeMillis() - startWhen;
            Log.d(TAG, "sampleTime: frame saved in: " + frameSaveTime + " millisecond");
            return;
         }
         decodeCount++;
     }

Проблема иногда заключается в том, что шаг расчета, полученный из extractor.getSampleTime() при поиске назад, а затем при декодировании вперед, кажется, не соответствует таковому при прямом поиске вперед.

Я включил журнал, чтобы было понятнее:

position is the seeking position in microsecond
sampleTime: 12112100 -- position: 12139000 ----- FORWARD
sampleTime: 12120441 -- position: 12139000 ----- FORWARD
sampleTime: 12128783 -- position: 12139000 ----- FORWARD
sampleTime: 12137125 -- position: 12139000 ----- FORWARD

sampleTime: 12012000 -- position: 12139000 ----- BACKWARD
sampleTime: 12020341 -- position: 12139000 ----- BACKWARD
sampleTime: 12028683 -- position: 12139000 ----- BACKWARD
sampleTime: 12037025 -- position: 12139000 ----- BACKWARD
sampleTime: 12045366 -- position: 12139000 ----- BACKWARD
sampleTime: 12053708 -- position: 12139000 ----- BACKWARD
sampleTime: 12062050 -- position: 12139000 ----- BACKWARD
sampleTime: 12070391 -- position: 12139000 ----- BACKWARD
sampleTime: 12078733 -- position: 12139000 ----- BACKWARD
sampleTime: 12087075 -- position: 12139000 ----- BACKWARD
sampleTime: 12095416 -- position: 12139000 ----- BACKWARD
sampleTime: 12103758 -- position: 12139000 ----- BACKWARD
sampleTime: 12112100 -- position: 12139000 ----- BACKWARD
sampleTime: 12120441 -- position: 12139000 ----- BACKWARD
sampleTime: 12128783 -- position: 12139000 ----- BACKWARD

Как вы можете видеть, при прямом поиске extractor.getSampleTime() может достичь позиции 12137125, а при поиске назад, а затем при декодировании вперед, он может достичь только 12128783. Я не уверен, почему это происходит, но это приводит к несоответствию между кадром представления и извлеченным кадром. Также этот метод не очень эффективен, так как мне приходится настраивать EGLSurface и декодировать его каждый раз, когда мне нужно извлечь кадр. В зависимости от того, насколько удален требуемый кадр от предыдущего ключевого кадра, эта операция может занять от 3 до 5 секунд, что определенно слишком долго для многократного извлечения.

Я хотел бы спросить, возможно ли сделать так, чтобы декодер декодировал обе поверхности (SurfaceView для отображения и EGLSurface для поиска кадров) одновременно, чтобы я мог потенциально решить обе эти проблемы с точностью и производительностью.

Я также пытался использовать FFmpeg для извлечения кадров раньше, производительность примерно такая же. Если есть лучший способ получить кадр, чем использование OpenGL, я очень хочу попробовать.

EDIT: После дальнейшего тестирования я могу сопоставить extractor.getSampleTime() из обоих методов, даже несмотря на то, что полученный кадр иногда может не совпадать с кадром дисплея.

EDIT 2: Что касается несоответствия между отображаемым кадром и извлеченным кадром, на самом деле это очень просто, но поначалу это довольно запутанно, если вы не знаете, как работает MediaCodec. Мне приходится перечитывать каждый комментарий Фаддена, чтобы лучше понять проблему (это тот, который вызывает у меня момент "ах-ха" ).

Короче говоря, декодер любит потреблять несколько буферов, прежде чем он выдаст какой-либо буфер представления. Таким образом, тот, который отображается в данный момент, не совпадает с тем, который находится в текущей позиции extractor.getSampleTime(). Таким образом, правильным значением для синхронизации между отображением и извлечением должно быть PresentationTime выходного буфера, что-то вроде этого:

mCurrentSampleTime = mBufferInfo.presentationTimeUs;

Понимание этого помогает решить множество загадочных вопросов (например, почему первый кадр не находится в нулевой позиции?). Надеюсь, это поможет кому-то.


person vxh.viet    schedule 19.08.2016    source источник


Ответы (1)


Не конкретный ответ на мой вопрос, но я нахожу способ улучшить время извлечения кадров. По сути, если у вас нет строгих требований к формату PNG, просто сожмите выходное изображение как jpeg следующим образом:

outputBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);

Это будет использовать аппаратное ускорение вместо чистого программного сжатия, как в PNG, и это значительно быстрее. Я получаю ~ 600 мс за всю операцию, бит сжатия занимает около ~ 200 мс. Это очень большое улучшение по сравнению с предыдущими 5 секундами с использованием сжатия PNG.

Теоретически вы могли бы получить еще большую производительность, используя Bitmap.Config.RGB_565 для выходного изображения вместо Bitmap.Config.ARGB_8888, если вас не волнует прозрачность. Однако на практике я сталкиваюсь с 2 проблемами, которые мешают мне это сделать:

  • цвет выходного изображения искажается.
  • на самом деле извлечение изображения занимает больше времени.
person vxh.viet    schedule 23.08.2016