我在使用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
。来自grafika的FrameCallback
也是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 ...
整个序列在流中不断重复。