Android使用MediaCodec解码原始h264流
  我在TextureView上使用MediaCodec解码和绘制原始h264数据时遇到了麻烦。  我以字节数组的形式接收原始数据,每个数组都是NAL单元(从0x00 0x00 0x00 0x01 ),也有SPS和PPS NAL单元以固定的间隔。  当新数据到达时,我将它放入LinkedBlockingQueue : 
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();
        }
    }
}
  如您所见,此处也有解码器配置。  这是在第一个SPS + PPS出现在队列中时完成的。  while循环中运行的主要部分: 
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();
                }
            }
        }
    }
}
  queueInputBuffer看起来像这样: 
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);
    }
}
  包装这个机制的类运行在单独的线程上,类似于Grafika的MoviePlayer 。  FrameCallback也是SpeedControlCallback的SpeedControlCallback。 
  结果预览已损坏。  当相机(视频源)仍然存在时,它很好,但是当它移动时,会出现撕裂,像素化和伪像。  当我将原始视频数据保存到文件并使用ffplay在桌面上播放时,它看起来没问题。 
  当我在寻找解决方案时,我发现问题可能是由无效的演示时间造成的。  我试图修复它(你可以在代码中看到,我在提供系统时间的同时使用preRender() )并没有运气。  但我不太确定这个问题是否由这些时间戳引起。 
  有人可以帮我解决这个问题吗? 
更新1
  就像fadden所建议的那样,我测试了我的播放器与MediaCodec本身创建的数据。  我的代码捕获了相机预览,将其编码并保存到文件中。  我之前用目标设备的摄像头提供了这个功能,所以我可以切换数据源。  基于手机相机预览的文件在播放时不会显示任何文物。  所以得出的结论是,来自目标设备摄像头的原始数据不经意地被处理(或传递给解码器),或者与MediaCodec不兼容(正如fadden所暗示的那样)。 
  接下来我做的是比较两个视频流的NAL单元。  MediaCodec编码的视频如下所示: 
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 ...
第一个NALU仅在流的开始处出现一次,然后是第二个(带有0x65),然后是0x21。 然后再次0x65,多个0x21等等。
但是目标设备的相机给了我这个:
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 ...
这整个序列在流中不断重复。
链接地址: http://www.djcxy.com/p/72047.html