【问题标题】:MediaCodec raw H264 decoding issue - times out and decoder.dequeueOutputBuffer always return -1MediaCodec 原始 H264 解码问题 - 超时并且 decoder.dequeueOutputBuffer 始终返回 -1
【发布时间】:2020-04-29 19:13:31
【问题描述】:

首先,感谢您花时间阅读本文。当我面临 h264 帧的解码问题时,我需要一些帮助或见解。我发布这个问题是因为与此相关的大多数其他相关帖子都没有提供明确的步骤。我已经共享了具有主要逻辑的解码器线程的代码。我从网络获取 sps、pps 和数据,因此当接收到 sps 和 pps 并馈送相应的数组时,我将这个 mConfigured 标志设为真。如果需要,我可以提供更多信息。 PS - 请原谅编码标准,因为它仍在开发中。

decoder.dequeueOutputBuffer >> 总是返回 -1

//接收到新数据包时从网络调用的以下函数。

public void decodeAndPlayVideoFrame(byte[] encodedData, int frameType) {
    if (frameType == 1) {
        SPS = encodedData;
    } else if (frameType == 2) {
        PPS = encodedData;
    } else if (frameType == 0) {
        Log.d("EncodeDecode", "enqueueing frame no: " + (frameID));

        try {
            Frame frame = new Frame(frameID);
            int totalDataLength = encodedData.length + SPS.length + PPS.length;
            frame.frameData = new byte[totalDataLength];
            System.arraycopy(SPS, 0, frame.frameData, 0, SPS.length);
            System.arraycopy(PPS, 0, frame.frameData, SPS.length, PPS.length);
            System.arraycopy(encodedData, 0, frame.frameData, SPS.length + PPS.length, encodedData.length);

            queue.add(frame);
            frameID++;
        } catch (NullPointerException e) {
            Log.e("EncodeDecode", "frame is null");
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            Log.e("EncodeDecode", "problem inserting in the queue");
            e.printStackTrace();
        } catch (IllegalStateException e) {
            Log.e("EncodeDecode", "problem inserting in the queue");
            e.printStackTrace();
        }
        Log.d("EncodeDecode", "frame enqueued. queue size now: " + queue.size());

    }
}

// 播放器线程在屏幕启动时启动并处理送入队列的数据包。

  private class PlayerThread extends Thread {
    //private MediaExtractor extractor;
    private MediaCodec decoder;
    private Surface surface;
    private boolean mConfigured;

    public PlayerThread(Surface surface) {
        this.surface = surface;
    }

    private void initCodec() throws IOException {

        MediaFormat mediaFormat = null;

        decoder = MediaCodec.createDecoderByType("video/avc");
        mediaFormat = MediaFormat.createVideoFormat("video/avc",
                320,
                240);

        try {

            decoder.configure(mediaFormat,
                    surface,
                    null,
                    0);
            frameID = 0;
            mConfigured = true;
            decoder.start();
            Log.d("EncodeDecode", "DECODER_THREAD:: decoder.start() called");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    public void run() {
        while (SPS == null || PPS == null || SPS.length == 0 || PPS.length == 0) {
            try {
                Log.d("EncodeDecode", "DECODER_THREAD:: sps,pps not ready yet");
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();

            }
        }

        if (!mConfigured) {
            try {
                initCodec();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
       int i = 0;
        ByteBuffer[] inputBuffers = decoder.getInputBuffers();
        ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
        while (!Thread.interrupted()) {
            Frame currentFrame = null;
            try {
                Log.d("EncodeDecode", "DECODER_THREAD:: calling queue.take(), if there is no frame in the queue it will wait");
                currentFrame = queue.take();
            } catch (InterruptedException e) {
                Log.e("EncodeDecode", "DECODER_THREAD:: interrupted while PlayerThread was waiting for the next frame");
                e.printStackTrace();
            }

            if (currentFrame == null)
                Log.e("EncodeDecode", "DECODER_THREAD:: null frame dequeued");
            else
                Log.d("EncodeDecode", "DECODER_THREAD:: " + currentFrame.id + " no frame dequeued");

            if (currentFrame != null && currentFrame.frameData != null && currentFrame.frameData.length != 0) {
                Log.d("EncodeDecode", "DECODER_THREAD:: decoding frame no: " + i + " , dataLength = " + currentFrame.frameData.length);

                int inIndex = decoder.dequeueInputBuffer(-1);

                if (inIndex >= 0) {
                    Log.d("EncodeDecode", "DECODER_THREAD:: sample size: " + currentFrame.frameData.length + "->" + currentFrame.frameData[0] +  "to" + currentFrame.frameData[currentFrame.frameData.length-1]);

                    ByteBuffer buffer = inputBuffers[inIndex];
                    buffer.clear();
                    buffer.put(currentFrame.frameData);
                    decoder.queueInputBuffer(inIndex, 0, currentFrame.frameData.length, 0, 0);

                    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
                    int outIndex = decoder.dequeueOutputBuffer(info, 0);

                    switch (outIndex) {
                        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                            Log.e("EncodeDecode", "DECODER_THREAD:: INFO_OUTPUT_BUFFERS_CHANGED");
                            outputBuffers = decoder.getOutputBuffers();
                            break;
                        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                            Log.e("EncodeDecode", "DECODER_THREAD:: New format " + decoder.getOutputFormat());
                            break;
                        case MediaCodec.INFO_TRY_AGAIN_LATER:
                            Log.e("EncodeDecode", "DECODER_THREAD:: dequeueOutputBuffer timed out!");
                            break;
                        default:
                            Log.d("EncodeDecode", "DECODER_THREAD:: decoded SUCCESSFULLY!!!");
                            ByteBuffer outbuffer = outputBuffers[outIndex];
                            decoder.releaseOutputBuffer(outIndex, true);
                            break;
                    }

                    try {
                        Thread.sleep(250);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    i++;
                }
            }
        }

        decoder.stop();
        decoder.release();
        mConfigured = false;

    }
}

【问题讨论】:

  • 也许看this question,它非常相似,我会从问你同样的问题开始。但是——我也注意到你似乎没有在这里配置“csd-0”/“csd-1”;就编解码器而言,您还没有对 SPS/PPS 做任何事情。没有它就行不通。
  • 感谢您的回复。我已经更新了代码 sn-p 以包含将 sps、pps 附加到数据数组的函数,所以您认为仍然需要将 csd-0/csd-1 添加到 mediaformat 中。你说什么?
  • 根据文档,“此类数据必须在调用 queueInputBuffer() 时使用标志 BUFFER_FLAG_CODEC_CONFIG 进行标记”。那个代码在哪里?
  • 如果您只是阅读@E.Abdel 的评论,如果一个设置 csd-0、csd-1 和 mediaformat,则 BUFFER_FLAG_CODEC_CONFIG 不是强制性的。
  • 对。你会注意到我在第一条评论中说了非常相似的话。需要明确的是,您有两个选择:1.)您可以使用“csd-0”/“csd-1”在MediaFormat 上配置 SPS/PPS,或者 2.)您可以使用 queueInputBuffer() 提交它BUFFER_FLAG_CODEC_CONFIG。在查看您发布的代码时,您还没有完成任何一项。但是,您已将数据添加到 queue,这表明您正在尝试选项 2。您显然没有使用选项 1,因为您没有尝试将 SPS/PPS 添加到 MediaFormat in任何方式.

标签: android video-streaming h.264 android-mediacodec


【解决方案1】:

我不知道您如何获得 SPS 和 PPS 以及如何检查它们...无论如何您必须在开始解码之前将 SPS 和 PPS 设置为 mediaformat,在您的代码中,您可以执行以下操作:

   ....
   while (SPS == null || PPS == null || SPS.length == 0 || PPS.length == 0) {
        try {
            Log.d("EncodeDecode", "DECODER_THREAD:: sps,pps not ready yet");
            sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();

        }
    }

   if (!mConfigured) {
        try {
            // init with SPS and PPS
            initCodec(sps,pps);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
   .....

然后在启动解码器之前在initCodec中设置解码器特定的数据:

private void initCodec(byte[] sps, byte[] pps) throws IOException {

        MediaFormat mediaFormat = null;

        decoder = MediaCodec.createDecoderByType("video/avc");
        mediaFormat = MediaFormat.createVideoFormat("video/avc",
                320,
                240);
        // set the codec specific datas cds-0 = SPS, cds-1 = PPS
        mediaFormat.setByteBuffer("cds-0", ByteBuffer.wrap(sps));
        mediaFormat.setByteBuffer("cds-1", ByteBuffer.wrap(pps));
        try {

            decoder.configure(mediaFormat,
                    surface,
                    null,
                    0);
            frameID = 0;
            mConfigured = true;
            decoder.start();
            Log.d("EncodeDecode", "DECODER_THREAD:: decoder.start() called");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    ...........

更新:来自文档

...许多解码器需要在实际压缩数据流之前 通过“编解码器特定数据”,即用于初始化编解码器的设置数据 例如 PPS/SPS 在 AVC 视频的情况下或代码表的情况下 沃比斯音频。 MediaExtractor 类提供编解码器特定的数据作为 在名为“csd-0”、“csd-1”的条目中返回的轨道格式的一部分 ...

这些缓冲区可以在 start() 或 flush() 之后直接提交 指定标志 BUFFER_FLAG_CODEC_CONFIG。但是,如果你 使用包含这些密钥的 MediaFormat 配置编解码器,它们 启动后直接由MediaCodec自动提交。 > 因此,不鼓励使用 BUFFER_FLAG_CODEC_CONFIG 标志,并且 仅推荐给高级用户

【讨论】:

  • 感谢您的回复。我已经更新了代码 sn-p 以包含将 sps、pps 附加到数据数组的函数,所以您认为仍然需要将 csd-0/csd-1 添加到 mediaformat 中。我要求您重新查看并让我知道您的 cmets。
  • 我已经阅读了你的更新,如果你看到我的代码,我没有使用 BUFFER_FLAG_CODEC_CONFIG。我在代码中的方法是内联 >> 但是,如果您使用包含这些密钥的 MediaFormat 配置编解码器,它们将在启动后直接由 MediaCodec 自动提交。 .你想提出任何改变吗?
  • 但是,当我阅读您的代码时,您将 SPS 和 PPS 放入一个队列中,其中包含常规帧,编解码器将使用这些帧(填充到缓冲区中)而不使用标志 BUFFER_FLAG_CODEC_CONFIG,这将nt 工作,最好在启动编解码器之前使用 SPS 和 PPS 配置您的媒体格式
猜你喜欢
  • 2014-02-06
  • 2016-05-27
  • 1970-01-01
  • 1970-01-01
  • 2016-03-27
  • 2015-09-27
  • 2017-02-19
  • 2017-12-16
  • 2013-12-03
相关资源
最近更新 更多