android - El método onFrameAvailable() de SurfaceTexture siempre se llama demasiado tarde
opengl-es mediacodec (1)
Estoy intentando que funcione el siguiente ejemplo de MediaExtractor:
http://bigflake.com/mediacodec/ - ExtractMpegFramesTest.java (requiere 4.1, API 16)
El problema que tengo es que outputSurface.awaitNewImage (); parece lanzar siempre RuntimeException ("tiempo de espera agotado de la trama"), que se lanza cada vez que mFrameSyncObject.wait(TIMEOUT_MS)
el mFrameSyncObject.wait(TIMEOUT_MS)
la mFrameSyncObject.wait(TIMEOUT_MS)
. onFrameAvailable()
lo que configure TIMEOUT_MS
, onFrameAvailable()
siempre se llama justo después de que se onFrameAvailable()
el tiempo de espera. Intenté con 50ms y con 30000ms y es lo mismo.
Parece que la llamada onFrameAvailable()
no puede realizarse mientras el hilo está ocupado, y una vez que transcurre el tiempo de espera que finaliza la ejecución del código del hilo, puede analizar la llamada onFrameAvailable()
.
¿Alguien ha logrado que este ejemplo funcione, o sabe cómo se supone que MediaExtractor funciona con texturas GL?
Editar: intenté con dispositivos con API 4.4 y 4.1.1 y lo mismo ocurre en ambos.
Editar 2:
Lo tengo trabajando en 4.4 gracias a Fadden. El problema era que el método ExtractMpegFramesWrapper.runTest()
se llamaba th.join();
que bloqueó el hilo principal e impidió que se onFrameAvailable()
llamada onFrameAvailable()
. Una vez que comencé a th.join();
funciona en 4.4. Supongo que se ExtractMpegFramesWrapper.runTest()
el ExtractMpegFramesWrapper.runTest()
sí se ejecutaba en otro subproceso para que el hilo principal no se bloqueara.
También hubo un pequeño problema en 4.1.2 al llamar a codec.configure()
, dio el error:
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)
Lo cual solucioné agregando lo siguiente antes de la llamada:
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
Sin embargo, el problema que tengo ahora tanto en 4.1.1 (Galaxy S2 GT-I9100) como en 4.1.2 (Samsung Galaxy Tab GT-P3110) es que ambos siempre establecen info.size en 0 para todos los fotogramas. Aquí está la salida de registro:
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
Entonces no se guardan imágenes Sin embargo, el mismo código y el mismo video funcionan en 4.3. El video que estoy usando es un archivo .mp4 con códec de video "H264 - MPEG-4 AVC (avc1)" y un códec de audio "MPEG AAAC Audio (mp4a)".
También probé otros formatos de video, pero parecen morir incluso antes en 4.1.x, mientras que ambos funcionan en 4.3.
Editar 3:
Hice lo que sugirió, y parece guardar las imágenes de marco correctamente. Gracias.
En cuanto a KEY_MAX_INPUT_SIZE, traté de no configurar, o configurarlo en 0, 20, 200, ... 200000000, todo con el mismo resultado de info.size = 0.
Ahora no puedo configurar el render en SurfaceView o TextureView en mi diseño. Intenté reemplazar esta línea:
mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
con esto, donde surfaceTexture
es una SurfaceTexture definida en mi xml-layout:
mSurfaceTexture = textureView.getSurfaceTexture();
mSurfaceTexture.attachToGLContext(mTextureRender.getTextureId());
pero arroja un error extraño con getMessage()==null
en la segunda línea. No pude encontrar otra forma de hacer que dibujara en una Vista de algún tipo. ¿Cómo puedo cambiar el decodificador para mostrar los marcos en Surface / SurfaceView / TextureView en lugar de guardarlos?
La forma en que SurfaceTexture
funciona hace que sea un poco complicado hacerlo bien.
Los docs dicen que la devolución de llamada disponible en el marco "se invoca en un hilo arbitrario". La clase SurfaceTexture
tiene un bit de código que hace lo siguiente al inicializar ( línea 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
}
Los eventos disponibles en el marco se entregan a su aplicación a través del mecanismo Looper
/ Handler
habitual. Ese mecanismo es solo una cola de mensajes, lo que significa que el hilo debe estar sentado en el bucle de eventos de Looper
esperando que lleguen. El problema es que si duermes en awaitNewImage()
, no estás mirando la cola Looper
. Entonces llega el evento, pero nadie lo ve. Finalmente awaitNewImage()
agota el tiempo de espera y el hilo vuelve a mirar la cola de eventos, donde inmediatamente descubre el mensaje "nuevo marco" pendiente.
Entonces, el truco es asegurarse de que los eventos disponibles en el marco lleguen a un hilo diferente del que está en awaitNewImage()
. En el ejemplo ExtractMpegFramesTest
, esto se hace ejecutando la prueba en un subproceso recién creado (vea la clase ExtractMpegFramesWrapper
), que no tiene un Looper
. (Por algún motivo, el hilo que ejecuta las pruebas CTS tiene un looper). Los eventos disponibles en el marco llegan al hilo principal de la IU.
Actualización (para "editar 3") : estoy un poco triste por haber ignorado el campo de "tamaño", pero antes de 4.3 es difícil predecir cómo se comportarán los dispositivos.
Si solo desea mostrar el marco, pase la Surface
que obtiene de SurfaceView
o TextureView
en la llamada a configure()
decoder configure()
. Entonces no tiene que meterse con SurfaceTexture
en absoluto; los marcos se mostrarán a medida que los decodifica. Vea las dos actividades de "Reproducir video" en Grafika para ver ejemplos.
Si realmente desea ir a través de una SurfaceTexture
, debe cambiar CodecOutputSurface para representarla en una superficie de ventana en lugar de en un pbuffer. (La representación fuera de pantalla se hace para que podamos usar glReadPixels()
en una prueba sin cabeza).