Как пропускать кадры при записи с помощью MediaCodec и InputSurface?

В моем приложении для Android я хочу записать видео с интервальной съемкой. У меня есть InputSurface -> MediaCodec (кодер) -> MediaMuxer.

Но если я хочу ускорить видео (например: x3), я получаю результирующее видео с очень высокой частотой кадров. Например: с нормальной скоростью я получаю видео 30 кадров в секунду. Если я ускоряюсь (x3), я получаю видео 90 кадров в секунду.

Поскольку частота кадров видео высока, видеоплеер моего телефона не может нормально воспроизводить видео (видеоплеер компьютера воспроизводит видео без проблем). Поэтому я думаю, что мне нужно пропустить несколько кадров, чтобы поддерживать частоту кадров ниже 60 кадров в секунду.

Но я не знаю, как сбрасывать кадры. Потому что в потоке AVC у нас есть кадры I, B, P, и они могут зависеть от других, поэтому мы не можем произвольно их отбрасывать. Кто-нибудь может мне помочь?


person TOP    schedule 22.06.2015    source источник


Ответы (1)


Вы должны декодировать и перекодировать поток, пропуская кадры. Просто уменьшив вдвое временные метки в видео со скоростью 60 кадров в секунду, вы получите видео со скоростью 120 кадров в секунду.

Имейте в виду, что необработанный видеопоток H.264 не имеет встроенных временных меток. Оболочка .mp4, проанализированная MediaExtractor и добавленная MediaMuxer, содержит информацию о времени. Интерфейсы MediaCodec, кажется, принимают и создают отметку времени презентации, но в основном они просто передают ее, чтобы помочь вам сохранить отметку времени, связанную с правильным кадром - кодировщик может переупорядочивать кадры. (Некоторые кодировщики просматривают метки времени, чтобы попытаться достичь цели скорости передачи данных, поэтому вы не можете передавать фиктивные значения.)

Вы можете сделать что-то вроде примера DecodeEditEncode. Когда декодер вызывает releaseOutputBuffer(), вы просто передаете "false" для аргумента рендеринга в каждом втором кадре.

Если вы принимаете видеокадры из какого-либо другого источника, например виртуального дисплея для записи экрана, вы не можете передать поверхность кодировщика непосредственно на дисплей. Вам нужно будет создать SurfaceTexture, создать Открывайте его, а затем обрабатывайте кадры по мере их поступления. Пример DecodeEditEncode делает именно это, модифицируя каждый кадр с помощью шейдера GLES.

Однако запись экрана представляет дополнительную трудность. Кадры с виртуальных дисплеев поступают по мере их создания, а не с фиксированной частотой кадров, что дает видео с переменной частотой кадров. Например, у вас может быть такая последовательность кадров:

[1] [2] <10 seconds pass> [3] [4] [5] ...

Хотя большинство кадров поступают с разницей в 16,7 мс (60 кадров в секунду), бывают промежутки, когда дисплей не обновляется. Если ваша запись захватывает каждый второй кадр, вы получите:

[1] <10+ seconds pass> [3] [5] ...

В итоге вы останавливаетесь на 10 секунд не на том кадре, что может бросаться в глаза, если между 1 и 2. Чтобы эта работа работала правильно, требуется некоторый интеллект в отбрасывании кадров, например. повторение предыдущего кадра по мере необходимости для создания видео с постоянной частотой кадров 30 кадров в секунду.

person fadden    schedule 22.06.2015
comment
Спасибо за ваш ответ. Я получаю видеокадры с виртуального дисплея. И у меня 2 вопроса. Во-первых: я не вижу класс SurfaceTexture в примере DecodeEditEncode. Там автор использовал пользовательский класс (InputSurface), который содержит Surface. Второй вопрос: вы сказали, что я должен создать SurfaceTexture, а затем создать из него Surface. Но как передать созданный Surface в Encoder? Я имею в виду Surface1 = new Surface(mSurfaceTexture) и Surface2 = Encoder.createInputSurface(). Итак, у нас есть 2 объекта Surface. Что я должен делать? - person TOP; 23.06.2015
comment
Чтобы просмотреть вспомогательные классы, создайте резервную копию в каталоге: android.googlesource.com/platform/cts/+/jb-mr2-release/tests/ . SurfaceTexture является частью OutputSurface.java, куда отправляется декодированное видео. Поверхность, используемая кодировщиком, может исходить только от getInputSurface(); это передается конструктору InputSurface. Декодер -> OutputSurface -> GLES -> InputSurface -> encoder. Таким образом, вы передали бы Surface от OutputSurface#getSurface() к вашему виртуальному дисплею. - person fadden; 23.06.2015