【问题标题】:Android MediaCodec slower in async-mode than in synchronous mode?Android MediaCodec 在异步模式下比在同步模式下慢?
【发布时间】:2017-02-21 07:00:25
【问题描述】:

再次,我有一个关于 Android 的 MediaCodec 类的问题。

我已成功解码原始 h264 内容并在两个 TextureView 中显示结果。 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(...) 部分外,设置类似于异步。两个变体所在的类是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。

所以我尝试了使用回调的 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();
    }
}

【问题讨论】:

  • 谢谢你!因为这个,我最终遇到了 ANR,我无法理解它......这就像魔法一样解决了它!

标签: java android asynchronous android-mediacodec


【解决方案1】:

如果onInputBufferAvailable 中的阻塞调用是罪魁祸首,我不会感到惊讶。感觉onInputBufferAvailable 和onOutputBufferAvailable 很可能是在同一个线程中调用的,如果你阻塞其中一个,就会阻止另一个运行。

我建议更改它,以便您在onInputBufferAvailable 中只需将缓冲区索引推送到某个队列,并通知另一个线程现在有另一个缓冲区可用,然后让第二个线程等待队列中的缓冲区,然后执行在那里阻塞获取输入数据。

【讨论】:

  • 你是对的。看起来它们是从同一个线程调用的。我将使用工作代码更新我的问题。
  • 请不要用新的固定代码更新原始问题 - 对于以后阅读问题的任何人(这就是全部!),这完全没有意义,阅读问题固定代码。如果您想共享固定代码,请添加它,例如作为问题末尾的单独部分,明确标记为编辑,cmets 表示这是固定版本。
  • @Christoph 您的固定代码有效吗?你在某个地方分享过吗?
  • @Christoph 我也有同样的问题。您是否在某处共享了固定代码?
猜你喜欢
  • 2017-01-03
  • 2015-06-26
  • 2022-06-20
  • 2020-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
相关资源
最近更新 更多