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

Estoy tratando de hacer que esto funcione en Android 4.1 (usando una tableta Asus Transformer actualizada). Gracias a la respuesta de Alex a mi pregunta anterior , ya pude escribir algunos datos H.264 sin formato en un archivo, pero este archivo solo se puede reproducir con ffplay -f h264 , y parece que ha perdido toda la información con respecto a la velocidad de fotogtwigs (extremadamente reproducción rápida). También el espacio de color parece incorrecto (atm usando el valor predeterminado 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 velocidad de fotogtwigs, pero como el objective principal es hacer un servicio de transmisión, esta no es una buena solución.

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

Entonces, ¿qué es lo que me falta aquí para hacer una transmisión H.264 válida y de trabajo? ¿Y qué configuración debo usar para hacer una coincidencia entre el espacio de color de la cámara y el espacio de color del codificador?

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

Gracias por adelantado.

Para su reproducción rápida, problema de velocidad de cuadros, no hay nada que tenga que hacer aquí. Como se trata de una solución de transmisión, al otro lado se le debe indicar la velocidad de fotogtwigs por adelantado o las marcas de tiempo con cada fotogtwig. Ambos no son parte de la transmisión primaria. Se elige framerate predeterminado o se pasa algo de sdp o algo así o se usan protocolos existentes como rtsp. En el segundo caso, las marcas de tiempo son parte de la secuencia enviada en forma de algo como rtp. Entonces el cliente tiene que pagar el flujo de rtp y jugarlo bacl. Así es como funciona la transmisión elemental. [arregle su velocidad de cuadros si tiene un codificador de velocidad fija o proporcione marcas de tiempo]

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

 ffplay -fps 30 in.264 

puede controlar la reproducción en la PC.

En cuanto al archivo no se puede reproducir: ¿Tiene un SPS y PPS. También debe tener encabezados NAL habilitados – formato anexo b. No sé mucho sobre Android, pero este es un requisito para que se pueda reproducir cualquier transmisión elemental h.264 cuando no están en ningún contenedor y deben ser descargadas y reproducidas más tarde. Si el valor predeterminado de Android es mp4, los encabezados de Annexb predeterminados se desactivarán, por lo que tal vez haya un interruptor que lo habilite. O si obtiene datos cuadro por cuadro, simplemente agréguelo usted mismo.

En cuanto al formato de color: supongo que el predeterminado debería funcionar. Así que intenta no configurarlo. Si no, intente con 422 formatos intercalados Planar o UVYV / VYUY. usualmente las cámaras son una de esas. (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 ahora acepta entradas de Surfaces, lo que significa que puede conectar la vista previa de Surface de la cámara al codificador y evitar todos los problemas extraños del formato YUV.

También hay una nueva clase MediaMuxer que convertirá la transmisión de H.264 sin procesar en un archivo .mp4 (opcionalmente mezclando en una transmisión de audio).

Consulte la fuente CameraToMpegTest para ver un ejemplo de cómo hacer exactamente esto. (También demuestra el uso de un sombreador de fragmentos OpenGL ES para realizar una edición trivial en el video a medida que se graba).

Puede convertir espacios de color como este, si ha configurado el espacio de color de vista previa para 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 conocer su formato de bitmap compatible y consultar su vista previa. El problema es que algunos MediaCodecs solo son compatibles con formatos YUV empaquetados patentados que no se pueden obtener desde 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 vista previa 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 mencionan otras respuestas en esta página, no hay garantía de que en su dispositivo, el codificador AVC sea compatible con este formato. Tenga en cuenta que existen algunos dispositivos extraños que no son compatibles con NV21, pero no conozco ninguno que pueda actualizarse a API 16 (por lo tanto, tiene MediaCodec).

La documentación de Google también afirma que YV12 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 probarlo (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 el intercambio de croma para convertirse en COLOR_FormatYUV420Planar.