再次,我有一个关于 Android 的 MediaCodec 类的问题。
我已成功解码原始 h264 内容并将结果显示在两个纹理视图中。
h264 流来自运行 openGL 场景的服务器。
该场景有一个摄像头,因此可以响应用户输入。
为了进一步减少服务器上的输入与智能手机上的实际结果之间的延迟,我正在考虑在异步模式下使用 MediaCodec。
以下是我设置两种变体的方法:同步和异步:
Async:
//decoderCodec is "video/avc"
MediaFormat fmt = MediaFormat.createVideoFormat(decoderCodec, 1280,720);
codec.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
byte[] frameData;
try {
frameData = frameQueue.take(); //this call is blocking
} catch (InterruptedException e) {
return;
}
ByteBuffer inputData = codec.getInputBuffer(index);
inputData.clear();
inputData.put(frameData);
codec.queueInputBuffer(index, 0, frameData.length, 0, 0);
}
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
codec.releaseOutputBuffer(index, true);
}
//The two other methods are left blank at the moment.
});
codec.configure(fmt, surface, null, 0);
codec.start();
Sync:(设置类似于异步,除了codec.setCallback(...)
部分。两个变体所在的类都是Runnable
.
public void run() {
while(!Thread.interrupted())
{
if(!IS_ASYNC) {
byte[] frameData;
try {
frameData = frameQueue.take(); //this call is blocking
} catch (InterruptedException e) {
break;
}
int inIndex = codec.dequeueInputBuffer(BUFFER_TIMEOUT);
if (inIndex >= 0) {
ByteBuffer input = codec.getInputBuffer(inIndex);
input.clear();
input.put(frameData);
codec.queueInputBuffer(inIndex, 0, frameData.length, 0, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outIndex = codec.dequeueOutputBuffer(bufferInfo, BUFFER_TIMEOUT);
if(outIndex >= 0)
codec.releaseOutputBuffer(outIndex, true);
}
else sleep(3000); //Just for testing, if we are in Async, this thread has nothing to do actually...
}
}
两种方法都有效,但我观察到以同步模式播放的视频更加流畅,延迟也更低。
我想出了使用异步模式的想法,因为frameQueue
is a LinkedBlockingDeque
我推断,如果同步解码器等待新帧数据到达的时间太长,则解码输出可能已经可用,但由于队列的阻塞性质而不会显示。另一方面,我不想做类似的事情busy wait
并始终轮询队列、输入缓冲区和输出缓冲区。
因此,我尝试使用回调的 AsyncMode,但得到的结果甚至比同步模式更糟糕。
我现在要问你们的问题是:
为什么?我是否滥用了异步模式?或者是别的什么?
感谢您的任何反馈!
克里斯托夫
Edit:以下是更新后的代码。我只列出更新的部分。这样
@mstorsjo 正确地指出,罪魁祸首是我在等待更多帧数据onInputBufferAvailable()
。更新后的版本向另一个 BlockingQueue 提供可用的缓冲区索引。在另一个线程中,我们正在等待新的帧数据和新的缓冲区索引来对帧数据进行排队以进行解码。
public class DisplayThread implements Runnable {
private BlockingQueue<Integer> freeInputBuffers;
//skipped the uninteresting parts.
private void initCodec(String decoderCodec) {
//skipped the uninteresting parts.
codec.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
freeInputBuffers.add(index);
}
//Dont care about the rest of the Callbacks for this demo...
}
}
@Override
public void run() {
while(!Thread.interrupted())
{
byte [] frameData;
int inputIndex;
try {
frameData = frameQueue.take();
//this was, indeed the culprit. We can wait in an additional thread for an buffer index to
// become free AND to get new frameData. When waiting in the callback, we will slow down
// the decoder.
inputIndex = freeInputBuffers.take();
} catch (InterruptedException e) {
break;
}
ByteBuffer inputData = codec.getInputBuffer(inputIndex);
inputData.clear();
inputData.put(frameData);
codec.queueInputBuffer(inputIndex, 0, frameData.length, 0, 0);
}
codec.stop();
codec.release();
}
}