Метод onFrameAvailable() SurfaceTexture всегда вызывается слишком поздно
Я пытаюсь заставить работать следующий пример MediaExtractor:
http://bigflake.com/mediacodec/ExtractMpegFramesTest.java.txt
У меня проблема в том, что outputSurface.awaitNewImage(); кажется, всегда генерирует RuntimeException("тайм-аут ожидания кадра"), который генерируется всякий раз, когда mFrameSyncObject.wait(TIMEOUT_MS)
время ожидания звонка Неважно, что я установил TIMEOUT_MS
быть, onFrameAvailable()
всегда вызывается сразу после истечения времени ожидания. Я пробовал с 50 мс и с 30000 мс, и это то же самое.
Кажется, что 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 вместо их сохранения?
1 ответ
Путь SurfaceTexture
Работы делают это немного сложно, чтобы получить права.
Документы говорят, что обратный вызов, доступный для фрейма, "вызывается в произвольном потоке". SurfaceTexture
Класс имеет немного кода, который делает следующее при инициализации ( строка 318):
if (this thread has a looper) {
handle events on this thread
} else if (there's 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"): мне немного грустно, что игнорирование поля "size" помогло, но до 4.3 трудно предсказать, как будут вести себя устройства.
Если вы просто хотите отобразить рамку, передайте Surface
вы получаете от SurfaceView
или же TextureView
в MediaCodec
декодер configure()
вызов. Тогда вам не нужно связываться с SurfaceTexture
вообще - кадры будут отображаться по мере их декодирования. Посмотрите два действия "Воспроизвести видео" в Grafika для примеров.
Если вы действительно хотите пройти через SurfaceTexture
вам нужно изменить CodecOutputSurface для рендеринга на поверхность окна, а не на pbuffer. (Вне экрана рендеринг сделан, поэтому мы можем использовать glReadPixels()
в тесте без головы.)