Codificación H.264 de la cámara con Android MediaCodec

Estoy tratando de conseguir que funcione en Android 4.1 (utilizando una tableta de Asus Transformer actualizada). Gracias a la respuesta de Alex a mi pregunta anterior , ya era capaz de escribir algunos datos crudos H.264 en un archivo, pero este archivo sólo es jugable con ffplay -f h264 , y parece que ha perdido toda la información sobre el framerate Reproducción rápida). También el espacio de color parece incorrecto (atm usando el defecto de la cámara en el lado del codificador).

 public class AvcEncoder { private MediaCodec mediaCodec; private BufferedOutputStream outputStream; public AvcEncoder() { File f = new File(Environment.getExternalStorageDirectory(), "Download/video_encoded.264"); touch (f); try { outputStream = new BufferedOutputStream(new FileOutputStream(f)); Log.i("AvcEncoder", "outputStream initialized"); } catch (Exception e){ e.printStackTrace(); } mediaCodec = MediaCodec.createEncoderByType("video/avc"); MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 320, 240); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar); mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mediaCodec.start(); } public void close() { try { mediaCodec.stop(); mediaCodec.release(); outputStream.flush(); outputStream.close(); } catch (Exception e){ e.printStackTrace(); } } // called from Camera.setPreviewCallbackWithBuffer(...) in other class public void offerEncoder(byte[] input) { try { ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(input); mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0); } MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0); while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; byte[] outData = new byte[bufferInfo.size]; outputBuffer.get(outData); outputStream.write(outData, 0, outData.length); Log.i("AvcEncoder", outData.length + " bytes written"); mediaCodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); } } catch (Throwable t) { t.printStackTrace(); } } 

Cambiar el tipo de codificador a "video / mp4" aparentemente resuelve el problema de framerate, pero como el objetivo principal es hacer un servicio de streaming, esta no es una buena solución.

Soy consciente de que he dejado caer algo del código de Alex teniendo en cuenta el SPS y PPS NALU, pero esperaba que esto no fuera necesario, ya que esa información también venía de outData y asumí que el codificador formatearía esto correctamente. Si este no es el caso, ¿cómo debo arreglar los diferentes tipos de NALU en mi archivo / flujo?

Entonces, ¿qué es lo que me falta aquí para hacer un flujo válido, trabajando H.264? ¿Y qué ajustes debo usar para hacer una coincidencia entre el espacio de colores de la cámara y el espacio de colores del codificador?

Tengo la sensación de que se trata más de una cuestión relacionada con H.264 que un tema de Android / MediaCodec. ¿O todavía no uso la API de MediaCodec correctamente?

Gracias por adelantado.

Para su rápida reproducción – problema de velocidad de fotogramas, no hay nada que tenga que hacer aquí. Dado que se trata de una solución de transmisión, el otro lado tiene que ser informado de la velocidad de fotogramas de antemano o de las marcas de tiempo con cada fotograma. Ambos no son parte del flujo elemental. Ya sea pre-determinado framerate es elegido o pasar en algunos sdp o algo así o utilizar protocolos existentes como rtsp. En el segundo caso las marcas de tiempo son parte del flujo enviado en forma de algo como rtp. Entonces el cliente tiene que depay la corriente del rtp y jugarlo bacl. Así es como funciona el streaming elemental. [Ya sea arreglar la velocidad de fotogramas si tiene un codificador de tasa fija o dar marcas de tiempo]

La reproducción de PC local será rápida porque no conoce los fps. Dando el parámetro fps antes de la entrada, por ejemplo

 ffplay -fps 30 in.264 

Puede controlar la reproducción en el PC.

En cuanto al archivo no se puede jugar: ¿Tiene un SPS y PPS. También debe tener encabezados NAL habilitados – formato anexo b. No sé mucho sobre android, pero esto es requisito para que cualquier flujo elemental h.264 sea jugable cuando no están en ningún contenedor y necesitan ser descargados y reproducidos más tarde. Si por defecto de android es mp4, pero por defecto los encabezados de annexb estarán apagados, así que tal vez haya un switch para habilitarlo. O si usted está recibiendo datos marco por cuadro, sólo tiene que añadir usted mismo.

En cuanto al formato de color: Supongo que el valor predeterminado debería funcionar. Así que trate de no establecerlo. Si no intenta los formatos entrelazados 422 Planar o UVYV / VYUY. Por lo general las cámaras son uno de ellos. (Pero no es necesario, estos pueden ser los que he encontrado más a menudo).

Android 4.3 (API 18) proporciona una solución fácil. La clase MediaCodec acepta ahora la entrada de Superficies, lo que significa que puede conectar la vista previa de la superficie de la cámara al codificador y evitar todos los extraños problemas de formato YUV.

También hay una nueva clase de MediaMuxer que convertirá tu flujo crudo H.264 en un archivo .mp4 (opcionalmente, mezcla en una secuencia de audio).

Vea la fuente CameraToMpegTest para ver un ejemplo de cómo hacer exactamente esto. (También demuestra el uso de un fragmento de fragmento de OpenGL ES para realizar una edición trivial en el video a medida que se está grabando).

Puede convertir espacios de color como este, si ha configurado el espacio de color de vista previa en YV12:

 public static byte[] YV12toYUV420PackedSemiPlanar(final byte[] input, final byte[] output, final int width, final int height) { /* * COLOR_TI_FormatYUV420PackedSemiPlanar is NV12 * We convert by putting the corresponding U and V bytes together (interleaved). */ final int frameSize = width * height; final int qFrameSize = frameSize/4; System.arraycopy(input, 0, output, 0, frameSize); // Y for (int i = 0; i < qFrameSize; i++) { output[frameSize + i*2] = input[frameSize + i + qFrameSize]; // Cb (U) output[frameSize + i*2 + 1] = input[frameSize + i]; // Cr (V) } return output; } 

O

  public static byte[] YV12toYUV420Planar(byte[] input, byte[] output, int width, int height) { /* * COLOR_FormatYUV420Planar is I420 which is like YV12, but with U and V reversed. * So we just have to reverse U and V. */ final int frameSize = width * height; final int qFrameSize = frameSize/4; System.arraycopy(input, 0, output, 0, frameSize); // Y System.arraycopy(input, frameSize, output, frameSize + qFrameSize, qFrameSize); // Cr (V) System.arraycopy(input, frameSize + qFrameSize, output, frameSize, qFrameSize); // Cb (U) return output; } 

Puede consultar el MediaCodec para su formato de mapa de bits compatible y consultar su vista previa. El problema es que algunos MediaCodecs sólo admiten formatos YUV empaquetados propietarios que no se pueden obtener de la vista previa. Particularmente 2130706688 = 0x7F000100 = COLOR_TI_FormatYUV420PackedSemiPlanar. El formato predeterminado para la vista previa es 17 = NV21 = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411Planar = YCbCr 420 Semi Planar

Si no solicitó explícitamente otro formato de píxeles, los búferes de previsualización de la cámara llegarán en un formato YUV 420 conocido como NV21 , para el cual COLOR_FormatYCrYCb es el equivalente de MediaCodec.

Desafortunadamente, como otras respuestas en esta página mencionan, no hay garantía de que en su dispositivo, el codificador AVC admita este formato. Tenga en cuenta que existen algunos dispositivos extraños que no soportan NV21, pero no conozco ninguno que pueda actualizarse a la API 16 (por lo tanto, tienen MediaCodec).

La documentación de Google también afirma que YUV12 planar YUV debe ser compatible como formato de vista previa de cámara para todos los dispositivos con API> = 12. Por lo tanto, puede ser útil intentarlo (el equivalente de MediaCodec es COLOR_FormatYUV420Planar que utiliza en su fragmento de código).

Actualización : como Andrew Cottrell me recordó, YV12 todavía necesita intercambio de croma para convertirse en COLOR_FormatYUV420Planar.

  • Android ffmpeg y aceleración de hardware
  • RTSP streaming con resultados de video HTML5 en el servidor murió error (100,0) en Android
  • Cómo obtener el ID de video de YouTube, incluidos los parámetros
  • GsaIOException Desbordamiento del búfer, sin espacio disponible
  • Transmisión de vídeo desde el dispositivo Android al servidor LAMP
  • Cómo cambiar framerate cuando se utiliza clase MediaRecorder
  • Cómo usar RajawaliVR o Rajawali para jugar un video 360
  • Grabar MPEG TS usando MediaRecorder
  • ¿Cómo obtener estadísticas de tráfico de red para aplicaciones de streaming de vídeo en Android?
  • Video streaming en android por parcelFileDescriptor
  • Adición de VideoView a un diseño XML
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.