Violación de acceso en código nativo con acelerador acelerado del decodificador de MediaCodec de Android

Mi objetivo es utilizar el Android MediaCodec para la decodificación de un flujo de vídeo, a continuación, utilizar las imágenes de salida para el procesamiento de imágenes en código nativo.

Plataforma: ASUS tf700t android 4.1.1. Flujo de prueba: H.264 full HD @ 24 frm / s

Con el interior de Tegra-3 SoC, estoy contando con soporte de hardware para la decodificación de vídeo. Funcionalmente, mi aplicación se comporta como se esperaba: de hecho puedo acceder a las imágenes del decodificador y procesarlas correctamente. Sin embargo, experimento una carga de cpu de decodificador muy alta.

En los experimentos siguientes, la carga del proceso / hilo se mide mediante "top -m 32 -t" en el shell de adb. Para obtener una salida fiable desde "top", todos los núcleos de 4 cpu se ven forzados activos ejecutando unos cuantos hilos que se vuelcan para siempre en la prioridad más baja. Esto se confirma mediante la ejecución repetida de "cat / sys / devices / system / cpu / cpu [0-3] / online". Para mantener las cosas simples, sólo hay decodificación de vídeo, no hay audio; Y no hay control de temporización por lo que el decodificador se ejecuta tan rápido como sea posible.

Primer experimento: ejecute la aplicación, llamando a la función de procesamiento JNI, pero todas las llamadas de procesamiento adicionales son comentadas. Resultados:

  • Rendimiento: 25 frm / s
  • 1% de carga de hilo VideoDecoder de la aplicación
  • 24% carga de hilo Binder_3 de proceso / system / bin / mediaserver

Parece que la velocidad de decodificación es CPU limitada (25% de una CPU de cuatro núcleos) … Cuando se habilita el procesamiento de salida, las imágenes decodificadas son correctas y la aplicación funciona. Único problema: carga de CPU demasiado alta para la decodificación.

Después de toneladas de experimentos, pensé en darle al MediaCodec una superficie para dibujar su resultado. En todos los demás aspectos, el código es idéntico. Resultados:

  • Rendimiento 55 frm / s (agradable !!)
  • 2% de carga de hilo VideoDecoder de la aplicación
  • 1% de carga de thread mediaserver de proceso / system / bin / mediaserver

De hecho, el video se muestra en la superficie proporcionada. Dado que casi no hay carga de CPU, esto debe acelerarse por hardware …

Parece que de MediaCodec sólo está utilizando la aceleración de hardware si se proporciona una superficie?

Hasta aquí todo bien. Yo ya estaba inclinado a utilizar la superficie como un trabajo alrededor (no es necesario, pero en algunos casos incluso un agradable-a-tener). Pero, en caso de que se proporcione una superficie, ¡no puedo acceder a las imágenes de salida! El resultado es una infracción de acceso en el código nativo.

Esto realmente me desconcierta! No vi ninguna noción de las limitaciones de acceso, o en absoluto en la documentación http://developer.android.com/reference/android/media/MediaCodec.html . También nada en esta dirección se mencionó en la presentación de Google I / O http://www.youtube.com/watch?v=RQws6vsoav8 .

Entonces: ¿cómo utilizar el acelerador accelarated Android MediaCodec decodificador y acceder a las imágenes en código nativo? ¿Cómo evitar la infracción de acceso? Cualquier ayuda es appreceated! También cualquier explicación o sugerencia.

Estoy bastante seguro de que MediaExtractor y MediaCodec se utilizan correctamente, ya que la aplicación es ok funcionalmente (siempre y cuando no proporcionan una superficie). Todavía es bastante experimental, y un buen diseño de API está en la lista de tareas 😉

Observe que la única diferencia entre los dos experimentos es variable mSuperficie: null o una superficie real en "mDecoder.configure (mediaFormat, mSurface, null, 0);"

Código de inicialización:

mExtractor = new MediaExtractor(); mExtractor.setDataSource(mPath); // Locate first video stream for (int i = 0; i < mExtractor.getTrackCount(); i++) { mediaFormat = mExtractor.getTrackFormat(i); String mime = mediaFormat.getString(MediaFormat.KEY_MIME); Log.i(TAG, String.format("Stream %d/%d %s", i, mExtractor.getTrackCount(), mime)); if (streamId == -1 && mime.startsWith("video/")) { streamId = i; } } if (streamId == -1) { Log.e(TAG, "Can't find video info in " + mPath); return; } mExtractor.selectTrack(streamId); mediaFormat = mExtractor.getTrackFormat(streamId); mDecoder = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME)); mDecoder.configure(mediaFormat, mSurface, null, 0); width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH); height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT); Log.i(TAG, String.format("Image size: %dx%d format: %s", width, height, mediaFormat.toString())); JniGlue.decoutStart(width, height); 

Bucle del descodificador (en un hilo separado):

 ByteBuffer[] inputBuffers = mDecoder.getInputBuffers(); ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers(); while (!isEOS && !Thread.interrupted()) { int inIndex = mDecoder.dequeueInputBuffer(10000); if (inIndex >= 0) { // Valid buffer returned int sampleSize = mExtractor.readSampleData(inputBuffers[inIndex], 0); if (sampleSize < 0) { Log.i(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM"); mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); isEOS = true; } else { mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0); mExtractor.advance(); } } int outIndex = mDecoder.dequeueOutputBuffer(info, 10000); if (outIndex >= 0) { // Valid buffer returned ByteBuffer buffer = outputBuffers[outIndex]; JniGlue.decoutFrame(buffer, info.offset, info.size); mDecoder.releaseOutputBuffer(outIndex, true); } else { // Some INFO_* value returned switch (outIndex) { case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: Log.i(TAG, "RunDecoder: INFO_OUTPUT_BUFFERS_CHANGED"); outputBuffers = mDecoder.getOutputBuffers(); break; case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: Log.i(TAG, "RunDecoder: New format " + mDecoder.getOutputFormat()); break; case MediaCodec.INFO_TRY_AGAIN_LATER: // Timeout - simply ignore break; default: // Some other value, simply ignore break; } } if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { Log.d(TAG, "RunDecoder: OutputBuffer BUFFER_FLAG_END_OF_STREAM"); isEOS = true; } } 

Si configura una superficie de salida, los datos descodificados se escriben en un búfer gráfico que puede utilizarse como una textura de OpenGL ES (a través de la extensión "textura externa"). Los varios pedacitos del hardware consiguen a los datos de la mano alrededor en un formato que tienen gusto, y la CPU no tiene que copiar los datos.

Si no configura una Superficie, la salida entra en un java.nio.ByteBuffer . Hay al menos una copia de búfer para obtener los datos del búfer asignado a MediaCodec a su ByteByffer , y presumiblemente otra copia para recuperar los datos en su código JNI. Espero que lo que está viendo es el costo de gastos generales más que el costo de decodificación del software.

Es posible que pueda mejorar las cuestiones enviando la salida a SurfaceTexture , SurfaceTexture en un FBO o pbuffer, y luego utilizando glReadPixels para extraer los datos. Si usted lee en un ByteBuffer "directo" o llama glReadPixels del código nativo, reduce su sobrecarga JNI. El lado negativo de este enfoque es que sus datos estarán en RGB en lugar de YCbCr. (OTOH, si las transformaciones deseadas pueden expresarse en un shader de fragmentos de GLES 2.0, puede hacer que la GPU haga el trabajo en lugar de la CPU).

Como se señala en otra respuesta, los decodificadores de diferentes dispositivos emiten datos de ByteBuffer en diferentes formatos, por lo que la interpretación de los datos en el software puede no ser viable si la portabilidad es importante para usted.

Editar: Grafika ahora tiene un ejemplo de usar la GPU para hacer el procesamiento de imágenes. Puedes ver un vídeo de demostración aquí .

Yo uso mediacodec api en el nexo 4 y obtener el formato de salida de color de QOMX_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka. Creo que este formato es un tipo de formato de hardware y sólo puede ser procesado por la renderización de hardware. Curiosamente, encuentro que cuando uso nulo y superficie real para configurar la superficie para MediaCodec, la longitud del búfer de salida será el cambio a un valor real y 0, respectivamente. No sé por qué. Creo que usted puede hacer algunos experimentos en diferentes dispositivos para obtener más resultados. Acerca de aceleración de hardware se puede ver http://www.saschahlusiak.de/2012/10/hardware-acceleration-on-sgs2-with-android-4-0/

  • Cómo cambiar el archivo de salida de un mediarecorder sin detener el mediarecorder
  • ¿Cómo utilizar Valgrind con la aplicación android en Eclipse en Ubuntu para encontrar fugas de memoria de código nativo android en tiempo de ejecución?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.