Decodificação do Android - fluxo h264 bruto com MediaCodec

Tenho problemas com a decodificação e desenho de dados h264 brutos com o MediaCodec no TextureView. Eu recebo os dados brutos em matrizes de bytes, cada uma delas é uma unidade NAL (começa com0x00 0x00 0x00 0x01), também existem unidades SPS e PPS NAL em intervalos constantes. Quando novos dados chegam, eu os coloco emLinkedBlockingQueue:

public void pushData(byte[] videoBuffer) {
    dataQueue.add(videoBuffer);

    if (!decoderConfigured) {
        // we did not receive first SPS NAL unit, we want to throw away all data until we do
        if (dataQueue.peek() != null && checkIfParameterSet(dataQueue.peek(), SPSID)) {

            // SPS NAL unit is followed by PPS NAL unit, we wait until both are present at the
            // start of the queue
            if (dataQueue.size() == 2) {

                // iterator will point head of the queue (SPS NALU),
                // iterator.next() will point PPS NALU
                Iterator<byte[]> iterator = dataQueue.iterator();

                String videoFormat = "video/avc";
                MediaFormat format = MediaFormat.createVideoFormat(videoFormat, width, height);
                format.setString("KEY_MIME", videoFormat);
                format.setByteBuffer("csd-0", ByteBuffer.wrap(concat(dataQueue.peek(), iterator.next())));

                try {
                    decoder = MediaCodec.createDecoderByType(videoFormat);
                } catch (IOException e) {
                    e.printStackTrace();
                }

                decoder.configure(format, mOutputSurface, null, 0);
                decoder.start();

                inputBuffer = decoder.getInputBuffers();

                decoderConfigured = true;
            }
        } else {
            // throw away the data which appear before first SPS NALU
            dataQueue.clear();
        }
    }
}

Como você pode ver, também há aqui a configuração do decodificador. Isso é feito quando o primeiro SPS + PPS aparece na fila. A parte principal em execuçãowhile ciclo:

private void work() {
    while(true) {
         if (decoderConfigured) {
            byte[] chunk = dataQueue.poll();
            if (chunk != null) {
                // we need to queue the input buffer with SPS and PPS only once
                if (checkIfParameterSet(chunk, SPSID)) {
                    if (!SPSPushed) {
                        SPSPushed = true;
                        queueInputBuffer(chunk);
                    }
                } else if (checkIfParameterSet(chunk, PPSID)) {
                    if (!PPSPushed) {
                        PPSPushed = true;
                        queueInputBuffer(chunk);
                    }
                } else {
                    queueInputBuffer(chunk);
                }
            }

            int decoderStatus = decoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
            if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                // no output available yet
                if (VERBOSE) Log.d(TAG, "no output from decoder available");
            } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                // not important for us, since we're using Surface
                if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
            } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                MediaFormat newFormat = decoder.getOutputFormat();
                if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
            } else if (decoderStatus < 0) {
                throw new RuntimeException(
                        "unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus);
            } else { // decoderStatus >= 0
                if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
                        " (size=" + mBufferInfo.size + ")");
                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    if (VERBOSE) Log.d(TAG, "output EOS");
                }

                boolean doRender = (mBufferInfo.size != 0);

                try {
                    if (doRender && frameCallback != null) {
                        Log.d(TAG, "Presentation time passed to frameCallback: " + mBufferInfo.presentationTimeUs);
                        frameCallback.preRender(mBufferInfo.presentationTimeUs);
                    }
                    decoder.releaseOutputBuffer(decoderStatus, doRender);

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

E aqueueInputBuffer se parece com isso:

private void queueInputBuffer(byte[] data) {
    int inIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
    if (inIndex >= 0) {
        inputBuffer[inIndex].clear();
        inputBuffer[inIndex].put(data, 0, data.length);
        decoder.queueInputBuffer(inIndex, 0, data.length, System.currentTimeMillis() * 1000, 0);
    }
}

A classe que encerra essa mecânica é executada em thread separado, da mesma forma queMoviePlayer degrafika. Também oFrameCallback éSpeedControlCallback de grafika.

A visualização do resultado está corrompida. Quando a câmera (a fonte de vídeo) está parada, está bem, mas quando está em movimento, rasga, pixelização e artefatos estão aparecendo. Quando eu salvo os dados brutos do vídeo no arquivo e os reproduzo na área de trabalho com o ffplay, tudo parece bem.

Quando eu estava procurando por uma solução, descobri que o problema pode ser causado por tempo de apresentação inválido. Tentei consertá-lo (você pode ver no código, eu estava fornecendo tempo do sistema juntamente com o uso depreRender()) sem sorte. Mas não tenho muita certeza se a falha é causada por esses registros de data e hora.

Alguém pode me ajudar a resolver esse problema?

ATUALIZAÇÃO 1

Como fadden sugeriu, testei meu player contra dados criados pelo próprio MediaCodec. Meu código capturou a visualização da câmera, codificou e salvou no arquivo. Fiz isso antes com o feed da câmera do meu dispositivo de destino, para poder trocar a fonte de dados. O arquivo com base na visualização da câmera do telefone não mostra nenhum artefato na reprodução. Portanto, a conclusão seria que os dados brutos provenientes da câmera do dispositivo de destino são processados (ou passados para o decodificador) de forma incorreta ou são incompatíveis com o MediaCodec (conforme sugerido por fadden).

A próxima coisa que fiz foi comparar as unidades NAL dos dois fluxos de vídeo. O vídeo codificado pelo MediaCodec tem a seguinte aparência:

0x00, 0x00, 0x00, 0x01, 0x67, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x65, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x21, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x21, 0xNN, 0xNN ...
.
. 
.    
0x00, 0x00, 0x00, 0x01, 0x21, 0xNN, 0xNN ...

O primeiro NALU ocorre apenas uma vez, no início do fluxo, depois vem o segundo (com 0x65) e depois é múltiplo com 0x21. Então, novamente 0x65, vários 0x21 e assim por diante.

No entanto, a câmera do dispositivo de destino me fornece o seguinte:

0x00, 0x00, 0x00, 0x01, 0x67, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x68, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x61, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x61, 0xNN, 0xNN ...
.
. 
.    
0x00, 0x00, 0x00, 0x01, 0x61, 0xNN, 0xNN ...

E toda essa sequência é repetida continuamente durante o fluxo.

questionAnswers(0)

yourAnswerToTheQuestion