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


Estoy tratando de conseguir que esto funcione en Android 4.1 (utilizando una tableta Asus Transformer actualizada). Gracias a la respuesta de Alex a mi pregunta anterior, ya pude escribir algunos datos H. 264 sin procesar en un archivo, pero este archivo solo se puede reproducir con ffplay -f h264, y parece que ha perdido toda la información sobre la velocidad de fotogramas (reproducción extremadamente 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 la velocidad de fotogramas, pero como el objetivo principal es hacer un servicio de transmisión, esta no es una buena solución.

Soy consciente de que se me cayó parte 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 organizar los diferentes tipos de NALU en mi archivo/flujo?

Entonces, ¿qué soy falta aquí para hacer una corriente H. 264 válida y funcional? ¿Y qué ajustes 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 esta es más una pregunta relacionada con H. 264 que un tema de Android/MediaCodec. ¿O sigo sin utilizar correctamente la API de MediaCodec?

Gracias de antemano.

Author: Community, 2012-11-19

5 answers

Para su problema de velocidad de fotogramas de reproducción rápida, no hay nada que tenga que hacer aquí. Dado que es una solución de transmisión, al otro lado se le debe decir la velocidad de fotogramas por adelantado o las marcas de tiempo con cada fotograma. Ambos no son parte de elementary stream. Se elige la velocidad de fotogramas predeterminada o se pasa algún 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 así como rtp. Entonces el cliente tiene que depay la corriente de rtp y jugar bacl. Así es como funciona la transmisión elemental. [fije su velocidad de fotogramas si tiene un codificador de velocidad fija o dé marcas de tiempo]

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

ffplay -fps 30 in.264

Puede controlar la reproducción en el PC.

En cuanto al archivo que no se puede reproducir: ¿Tiene un SPS y un PPS? También debe tener las cabeceras NAL habilitadas-formato anexo b. No lo sé mucho sobre Android, pero este es el requisito para cualquier corriente primaria h.264 para ser jugable cuando no están en ningún contenedor y necesitan ser volcados y jugados más tarde. Si el valor predeterminado de Android es mp4, pero los encabezados predeterminados del anexo b se desactivarán, por lo que tal vez haya un interruptor para habilitarlo. O si está obteniendo datos cuadro por cuadro, simplemente agréguelos usted mismo.

En cuanto al formato de color: supongo que el predeterminado debería funcionar. Así que intenta no configurarlo. Si no, pruebe 422 Planar o UVYV / VYUY interleaved formato. normalmente las cámaras son una de esas. (pero no es necesario, estos pueden ser los que he encontrado más a menudo).

 7
Author: av501,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-24 20:01:58

Android 4.3 (API 18) proporciona una solución fácil. La clase MediaCodec ahora acepta 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 MediaMuxer que convertirá su flujo H. 264 raw a un archivo .mp4 (opcionalmente mezclando en un flujo de audio).

Ver la fuente CameraToMpegTest para un ejemplo de hacer exactamente esto. (También demuestra el uso de un sombreador de fragmentos de OpenGL ES para realizar una edición trivial en el video mientras se graba.)

 7
Author: fadden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-24 19:45:54

Puede convertir espacios de color como este, si ha establecido 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;
    }
 6
Author: br1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-16 16:42:16

Puede consultar el formato de mapa de bits de MediaCodec y consultar su vista previa. El problema es que algunos MediaCodecs solo admiten formatos YUV empaquetados propietarios que no puedes 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 Plano

 2
Author: Marcus Wolschon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-05 09:53:15

Si no solicitó explícitamente otro formato de píxel, 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 otras respuestas en esta página mencionan, no hay garantía de que en su dispositivo, el codificador AVC soporte este formato. Tenga en cuenta que existen algunos dispositivos extraños que no son compatibles con NV21, pero no conozco ninguno que se pueda actualizar a API 16 (por lo tanto, tienen 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 me recordó Andrew Cottrell, YV12 todavía necesita intercambio de croma para convertirse en COLOR_FormatYUV420Planar.

 2
Author: Alex Cohn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-20 21:08:50