再次,我对Android的MediaCodec类有疑问。
我已经成功地解码了原始的h264内容,并在两个TextureViews中显示了结果。
h264流来自运行openGL场景的服务器。
该场景具有摄像头,因此可以响应用户输入。
为了进一步减少服务器上的输入和智能手机上的实际结果之间的等待时间,我正在考虑在异步模式下使用MediaCodec。
这是我设置两种变体的方式:同步和异步:
异步:
//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();
同步:(除了
codec.setCallback(...)
部分外,类似于Async的设置。两个变体都位于的类是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
是LinkedBlockingDeque
,并且我认为如果同步解码器等待太长的时间才能等待新的帧数据到达,解码后的输出可能已经可用,但由于阻塞的性质而不会显示的队列。另一方面,我不想做类似busy wait
的事情并一直轮询队列,inputBuffers和outputBuffers。因此,我尝试使用Callbacks进行AsyncMode操作,但获得的结果甚至比同步模式下还要差。
我现在给你们的问题是:
为什么?我是否滥用了异步模式?或者是别的什么?
感谢您的任何反馈!
克里斯多夫
编辑:
以下是更新的代码。我只列出更新的零件。这样
@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();
}
}
最佳答案
如果onInputBufferAvailable
中的阻塞调用是罪魁祸首,我不会感到惊讶。很有可能在同一线程中同时调用了onInputBufferAvailable
和onOutputBufferAvailable
,并且如果在其中一个线程中进行阻塞,则会停止另一个线程的运行。
我建议更改它,以便您在onInputBufferAvailable
中将缓冲区索引推入某个队列,并向另一个线程发出信号,告知现在还有另一个缓冲区可用,然后让第二个线程等待队列中的缓冲区,并阻塞输入那里的数据。