developer android video video-streaming h.264 decoder

android - developer - Caída de MediaCodec en transmisiones de alta calidad



android developer streaming video (1)

Estoy decodificando una transmisión de video h264 con el siguiente código (guía original) :

public void configure(Surface surface, int width, int height, ByteBuffer csd0) { String VIDEO_FORMAT = "video/avc"; if (mConfigured) { throw new IllegalStateException("Decoder is already configured"); } MediaFormat format = MediaFormat.createVideoFormat(VIDEO_FORMAT, width, height); // little tricky here, csd-0 is required in order to configure the codec properly // it is basically the first sample from encoder with flag: BUFFER_FLAG_CODEC_CONFIG format.setByteBuffer("csd-0", csd0); try { mCodec = MediaCodec.createDecoderByType(VIDEO_FORMAT); } catch (IOException e) { throw new RuntimeException("Failed to create codec", e); } mCodec.configure(format, surface, null, 0); mCodec.start(); mConfigured = true; } @SuppressWarnings("deprecation") public void decodeSample(byte[] data, int offset, int size, long presentationTimeUs, int flags) { if (mConfigured && mRunning) { int index = mCodec.dequeueInputBuffer(mTimeoutUs); if (index >= 0) { ByteBuffer buffer; // since API 21 we have new API to use if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { buffer = mCodec.getInputBuffers()[index]; buffer.clear(); } else { buffer = mCodec.getInputBuffer(index); } if (buffer != null) { buffer.put(data, offset, size); mCodec.queueInputBuffer(index, 0, size, presentationTimeUs, flags); } } } } @Override public void run() { try { MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); while (mRunning) { if (mConfigured) { int index = mCodec.dequeueOutputBuffer(info, mTimeoutUs); if (index >= 0) { // setting true is telling system to render frame onto Surface mCodec.releaseOutputBuffer(index, true); if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) { break; } } } else { // just waiting to be configured, then decode and render try { Thread.sleep(10); } catch (InterruptedException ignore) { } } } } finally { if (mConfigured) { mCodec.stop(); mCodec.release(); } } }

Puedo ejecutar esto tanto en mi Nexus 6 (api 22) como en Samsung galaxy core (api 16) en baja y mediana calidad. Sin embargo, cuando cambio a alta calidad (720p), se bloquea en el Samsung después de aproximadamente 30 fotogramas (pero no se muestra nada en la pantalla).

E/ACodec﹕ [OMX.qcom.video.decoder.avc] ERROR(0x8000100a) E/MediaCodec﹕ Codec reported an error. (omx error 0x8000100a, internalError -2147483648) [...] W/System.err﹕ java.lang.IllegalStateException W/System.err﹕ at android.media.MediaCodec.dequeueInputBuffer(Native Method) W/System.err﹕ at com.test.stream.VideoDecoder$Worker.decodeSample(VideoDecoder.java:95) W/System.err﹕ at com.test.stream.VideoDecoder.decodeSample(VideoDecoder.java:24) W/System.err﹕ at com.test.stream.VideoThread.run(VideoThread.java:160)

El error anterior es el primer error que aparece, la IllegalStateException se lanza posteriormente en cada fotograma.

Mi pregunta es, ¿es este un problema específico del dispositivo (debido a: api / dispositivo más antiguo, menos potente, etc.) o es algo realmente incorrecto? y ¿cómo debería lidiar con esto?


Para mi decodificador h.264 de Android lo hago un poco diferente a su configuración. Creo que estás usando un nivel de API más moderno. Pero para mí se parece más a esto:

public void startDecoder() { // Initilize codec mediaCodec = MediaCodec.createDecoderByType("video/avc"); mediaFormat = MediaFormat.createVideoFormat("video/avc", 0, 0); bufferInfo = new MediaCodec.BufferInfo(); // STOPS unit-tests from crashing here from mocked out android if (mediaCodec != null) { mediaCodec.configure(mediaFormat, targetSurface, null, 0); mediaCodec.start(); decoderThread = new Thread(this); decoderThread.start(); } }

// El subproceso del decodificador se refiere a esta clase que hace el ciclo de decodificador / renderizado:

public void run() { //mediaCodec input + output dequeue timeouts long kInputBufferTimeoutMs = 50; long kOutputBufferTimeoutMs = 50; while (running && mediaCodec != null) { synchronized (mediaCodec) { // stop if not running. if (!running || mediaCodec == null) break; // Only push in new data if there is data available in the queue if (naluSegmentQueue.size() > 0) { int inputBufferIndex = mediaCodec.dequeueInputBuffer(kInputBufferTimeoutMs); if (inputBufferIndex >= 0) { NaluSegment segment = naluSegmentQueue.poll(); codecInputBufferAvailable(segment, mediaCodec, inputBufferIndex); } } // always check if output is available. int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, kOutputBufferTimeoutMs); if (outputBufferIndex >= 0) { // Try and render first codecOuputBufferAvailable(mediaCodec, outputBufferIndex, bufferInfo); } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // Subsequent data will conform to new format. // Can ignore if using getOutputFormat(outputBufferId) mediaFormat = mediaCodec.getOutputFormat(); } } } }

Para poner datos en el decodificador, incluidos los parámetros. No me molesto en tratar de usar las secuencias de red csd-0/1, ya que puedo cambiar las descripciones de formato y es más fácil simplemente dejar que se recoja de forma dinámica.

private void codecInputBufferAvailable(NaluSegment segment, MediaCodec codec, int index) { int flags = (segment.getType() == NaluType.SPS || segment.getType() == NaluType.PPS || segment.getType() == NaluType.SUPP_ENHANCEMENT) ? MediaCodec.BUFFER_FLAG_CODEC_CONFIG : MediaCodec.BUFFER_FLAG_SYNC_FRAME; ByteBuffer[] buffers = codec.getInputBuffers(); ByteBuffer buffer = buffers[index]; // Can throw buffer overflow exception when buffer sizes are too small. try { buffer.put(segment.getBuffer()); codec.queueInputBuffer(index, 0, segment.getBufferSize(), 0, flags); } catch(Exception e) { Log.e(TAG, "Failed to push buffer to decoder"); } }

IMPORTANTE : buffer.put (segment.getBuffer ()); getBuffer () aquí siempre devuelve un bufferb adjunto de 4 bytes. Los decodificadores de Android no comprenden 3 unidades de bytes. Entonces, si tienes una unidad nal de 3 bytes, conviértela en una secuencia mágica de 4 bytes con longitud + 1 y 0x00, 0x00, 0x00, 0x01 como la secuencia mágica inicial, el resto del buffer debe ser & buffer [headerLength].

Observe el try-catch que aquí no proporciona una advertencia de compilación, pero puede arrojar una excepción de desbordamiento de búfer aquí si tiene una carga útil muy grande y el byte-buffer es demasiado pequeño.

Siempre que analice correctamente sus unidades NAL, esto debería funcionar para usted. Pero en mi caso noté que las unidades NAL pueden tener 3 o 4 bytes para el encabezado mágico.

/** * H264 is comprised of NALU segments. * * XXXX Y ZZZZZZZZ -> XXXX Y ZZZZZZZZ -> XXXX Y ZZZZZZZZ * * Each segment is comprised of: * * XXXX -> Magic byte header (0x00, 0x00, 0x00, 0x01) NOTE: this can be either 3 of 4 bytes * Y -> The Nalu Type * ZZZ... -> The Payload * * Notice there is no nalu length specified. To parse an nalu, you must * read until the next magic-byte-sequence AKA the next segment to figure * out the full nalu length **/ public static List<NaluSegment> parseNaluSegments(byte[] buffer) throws NaluBufferException { List<NaluSegment> segmentList = new ArrayList<>(); if (buffer.length < 6) { return segmentList; } int lastStartingOffset = -1; for (int i = 0; i < buffer.length - 10; ++i) { **if (buffer[i] == 0x00 && buffer[i+1] == 0x00 && buffer[i+2] == 0x01)** { int naluType = (buffer[i+3] & 0x1F); NaluSegment segment = new NaluSegment(naluType, 3, i); **if (i > 0 && buffer[i-1] == 0x00)** { // This is actually a 4 byte segment int currentSegmentOffset = segment.getOffset(); segment.setHeaderSize(4); segment.setOffset(currentSegmentOffset - 1); } ...

Cree sus propios objetos del segmento nalu y no olvide el NAL posterior.

Espero que esto ayude.