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

Метод SurfaceTexture onFrameAvailable() всегда называется слишком поздним

Я пытаюсь использовать следующий пример MediaExtractor:

http://bigflake.com/mediacodec/- ExtractMpegFramesTest.java(требуется 4.1, API 16)

Проблема заключается в том, что outputSurface.awaitNewImage(); похоже, всегда бросает RuntimeException ( "время ожидания кадра" ), которое вызывается всякий раз, когда вызов mFrameSyncObject.wait(TIMEOUT_MS) истекает. Независимо от того, что я установил TIMEOUT_MS, onFrameAvailable() всегда вызывается сразу после истечения таймаута. Я пробовал с 50ms и с 30000ms, и это то же самое.

Похоже, вызов onFrameAvailable() не может быть выполнен, пока поток занят, и как только произойдет таймаут, который завершит выполнение кода потока, он может разобрать вызов onFrameAvailable().

Кто-нибудь смог заставить этот пример работать или знает, как MediaExtractor должен работать с текстурами GL?

Изменить: попробовал это на устройствах с API 4.4 и 4.1.1, и то же самое происходит на обоих.

Изменить 2:

Получил работу над 4.4 благодаря fadden. Проблема заключалась в том, что метод ExtractMpegFramesWrapper.runTest(), называемый th.join();, который заблокировал основной поток и не позволил обработать вызов onFrameAvailable(). Как только я прокомментировал th.join();, он работает с 4.4. Вероятно, сам ExtractMpegFramesWrapper.runTest() должен был запускать еще один поток, поэтому основной поток не блокировался.

Также была небольшая проблема на 4.1.2 при вызове codec.configure(), она дала ошибку:

A/ACodec(2566): frameworks/av/media/libstagefright/ACodec.cpp:1041 CHECK(def.nBufferSize >= size) failed.
A/libc(2566): Fatal signal 11 (SIGSEGV) at 0xdeadbaad (code=1), thread 2625 (CodecLooper)

Что я решил, добавив следующее перед вызовом:

format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);

Однако проблема, с которой я столкнулся как в 4.1.1 (Galaxy S2 GT-I9100), так и в 4.1.2 (Samsung Galaxy Tab GT-P3110), заключается в том, что оба они всегда устанавливают значение info.size равным 0 для всех кадров. Вот вывод журнала:

loop
input buffer not available
no output from decoder available
loop
input buffer not available
no output from decoder available
loop
input buffer not available
no output from decoder available
loop
input buffer not available
no output from decoder available
loop
submitted frame 0 to dec, size=20562
no output from decoder available
loop
submitted frame 1 to dec, size=7193
no output from decoder available
loop
[... skipped 18 lines ...]
submitted frame 8 to dec, size=6531
no output from decoder available
loop
submitted frame 9 to dec, size=5639
decoder output format changed: {height=240, what=1869968451, color-format=19, slice-height=240, crop-left=0, width=320, crop-bottom=239, crop-top=0, mime=video/raw, stride=320, crop-right=319}
loop
submitted frame 10 to dec, size=6272
surface decoder given buffer 0 (size=0)
loop
[... skipped 1211 lines ...]
submitted frame 409 to dec, size=456
surface decoder given buffer 1 (size=0)
loop
sent input EOS
surface decoder given buffer 0 (size=0)
loop
surface decoder given buffer 1 (size=0)
loop
surface decoder given buffer 0 (size=0)
loop
surface decoder given buffer 1 (size=0)
loop
[... skipped 27 lines all with size=0 ...]
surface decoder given buffer 1 (size=0)
loop
surface decoder given buffer 0 (size=0)
output EOS
Saving 0 frames took ? us per frame // edited to avoid division-by-zero error

Таким образом, изображения не будут сохранены. Однако тот же код и видео работают на 4.3. Видео, которое я использую, является файлом .mp4 с видеокодеком "H264 - MPEG-4 AVC (avc1)" и аудиокодеком MPEG AAAC Audio (mp4a).

Я также пробовал другие видеоформаты, но они, кажется, умирают еще раньше на 4.1.x, а оба работают на 4.3.

Изменить 3:

Я сделал то, что вы предлагали, и, похоже, правильно сохраняет изображения фреймов. Спасибо.

Что касается KEY_MAX_INPUT_SIZE, я попытался не устанавливать или устанавливать его на 0, 20, 200,... 200000000, все с тем же результатом info.size = 0.

Теперь я не могу установить рендер в SurfaceView или TextureView на мой макет. Я попытался заменить эту строку:

mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());

с этим, где surfaceTexture является SurfaceTexture, определенным в моем xml-макете:

mSurfaceTexture = textureView.getSurfaceTexture();
mSurfaceTexture.attachToGLContext(mTextureRender.getTextureId());

но он вызывает странную ошибку с getMessage()==null во второй строке. Я не мог найти другого способа заставить его использовать какой-либо вид. Как я могу изменить декодер для отображения фреймов на Surface/SurfaceView/TextureView вместо их сохранения?

4b9b3361

Ответ 1

Как работает SurfaceTexture, это немного сложно сделать.

docs говорят, что обратный вызов, доступный кадром, вызывается в произвольном потоке. Класс SurfaceTexture имеет бит кода, который выполняет при инициализации (строка 318):

if (this thread has a looper) {
    handle events on this thread
} else if (there a "main" looper) {
    handle events on the main UI thread
} else {
    no events for you
}

Доступные кадры события доставляются в ваше приложение через обычный механизм Looper/Handler. Этот механизм является просто очередью сообщений, что означает, что поток должен находиться в цикле событий Looper, ожидая их поступления. Проблема в том, что если вы спите в awaitNewImage(), вы не смотрите очередь Looper. Таким образом, событие прибывает, но никто его не видит. В конце концов awaitNewImage() истекает время, и поток возвращается к просмотру очереди событий, где он сразу обнаруживает ожидающее сообщение "новый кадр".

Итак, трюк состоит в том, чтобы убедиться, что события, доступные для кадра, прибывают на другой поток из того, что сидит в awaitNewImage(). В примере ExtractMpegFramesTest это выполняется путем запуска теста во вновь создаваемом потоке (см. Класс ExtractMpegFramesWrapper), который не имеет Looper. (По какой-то причине поток, который выполняет тесты CTS, имеет петлитель.) События, доступные для кадра, поступают в основной поток пользовательского интерфейса.

Обновление (для "edit 3" ). Мне немного грустно, что игнорирование поля "размер" помогло, но до 4.3 было трудно предсказать, как будут себя вести устройства.

Если вы просто хотите отобразить фрейм, передайте Surface, который вы получите из SurfaceView или TextureView в вызов MediaCodec декодера configure(). Тогда вам не нужно возиться с SurfaceTexture вообще - кадры будут отображаться при их декодировании. Для примера см. Две операции "Воспроизвести видео" в Grafika.

Если вы действительно хотите пройти через SurfaceTexture, вам нужно изменить CodecOutputSurface для рендеринга на поверхность окна, а не на pbuffer. (Внеэкранная рендеринг выполняется, поэтому мы можем использовать glReadPixels() в тесте без звука.)