【问题标题】:MediaCodec not decoding H264 encoded video dataMediaCodec 不解码 H264 编码的视频数据
【发布时间】:2019-06-21 23:11:42
【问题描述】:

我正在开发一个通过 RTP 接收 H264 编码数据的应用程序,但我无法让 Android 的 MediaCodec 输出任何内容。我正在按照此处https://stackoverflow.com/a/7668578/10788248

的描述对 RTP 数据包进行解包

在重新组装编码帧后,我将它们送入出队的输入缓冲区。

当我对输入缓冲区进行排队时,我没有收到任何错误,但解码器的回调永远不会调用 onOutputBufferAvailable 方法。

我能够调用它的唯一方法是传递流结束标志,然后输出大小为 0。

我的问题是,“我遗漏了什么明显的错误吗?”以及“哪些潜在问题可能导致输出缓冲区永远不可用,但编解码器不会引发错误?”

代码更新

LinkedList<DatagramPacket> packets = new LinkedList<>();
Thread socketReader = new Thread(() -> {
        try {
            DatagramSocket socket  = new DatagramSocket(videoPort);
            socket.connect(InetAddress.getByName(remoteAddress),remotePort);
            byte[] b;
            while (true){
                b = new byte[1500];
                DatagramPacket p = new DatagramPacket(b,1500);
                socket.receive(p);
                //Log.d(TAG,"RTP: "+bytesToHex(p.getData()));
                packets.add(p);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    });
@SuppressLint({"NewApi", "LocalSuppress"}) Thread packetHandler = new Thread(() -> {
        try {
            MediaCodec decoder = MediaCodec.createDecoderByType(MIME_TYPE);
            MediaFormat decoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, 1280, 720);
            LinkedList<ByteBuffer> decoderInputs = new LinkedList<>();
            LinkedList<Integer> decoderIndices = new LinkedList<>();

            decoder.setCallback(new MediaCodec.Callback() {
                @Override
                public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
                    decoderInputs.add(codec.getInputBuffer(index));
                    decoderIndices.add(new Integer(index));
                }

                @Override
                public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
                    Log.d(TAG,"OUTPUT AVAILABLE!!!!");
                    Log.d(TAG, String.valueOf(info.size));
                }

                @Override
                public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
                    Log.e(TAG,e.toString());
                }
                @Override
                public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
                    Log.d(TAG,"FORMAT CHANGED");
                }
            });
            decoder.configure(decoderFormat, null,null,0);
            decoder.start();
            RTPFrame frame = new RTPFrame();
            boolean sps = false;
            boolean pps = false;
            while (true){
                if(decoderIndices.peek()!=null){
                    DatagramPacket packet = packets.poll();
                    if(packet!=null){
                        byte[] data = packet.getData();
                        if(frame==null||(!frame.isInitialized())){
                            frame = new RTPFrame(data);
                        }
                        else{
                            if(frame.isComplete()){
                                Integer index = null;
                                ByteBuffer decoderInput = null;
                                try {
                                    decoderInput = decoderInputs.poll().put(frame.getFrame());
                                    index = decoderIndices.poll();
                                    int size = frame.getFrameSize();
                                    if (frame.SPS) {
                                        Log.d(TAG,"Depacketized at "+Integer.toUnsignedString(frame.getTime())+" length = "+frame.getFrameSize()+": " + bytesToHex(frame.getFrame().array()));
                                        decoder.queueInputBuffer(index, 0, size, frame.getTime(), MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
                                        sps = true;
                                        pps = false;
                                    } else if (frame.PPS) {
                                        if(sps){
                                            Log.d(TAG,"Depacketized at "+Integer.toUnsignedString(frame.getTime())+" length = "+frame.getFrameSize()+": " + bytesToHex(frame.getFrame().array()));
                                            decoder.queueInputBuffer(index, 0, size, frame.getTime(), MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
                                            pps = true;
                                        }
                                    } else if (!frame.badFrame) {
                                        if(sps&&pps){
                                            Log.d(TAG,"Depacketized at "+Integer.toUnsignedString(frame.getTime())+" length = "+frame.getFrameSize()+": " + bytesToHex(frame.getFrame().array()));
                                            decoder.queueInputBuffer(index, 0, size, frame.getTime(), 0);
                                        }
                                    }
                                    else{
                                        throw new RuntimeException();
                                    }
                                }
                                catch(Exception e){
                                    e.printStackTrace();
                                    if(index!=null){
                                        decoderIndices.push(index);
                                    }
                                    decoderInput.clear();
                                    decoderInputs.push(decoderInput);
                                }
                                frame = new RTPFrame();
                            }
                            else{
                                frame.addNALUnit(data);
                            }
                        }
                        if(frame.isComplete()){
                            Integer index = null;
                            ByteBuffer decoderInput = null;
                            try {
                                decoderInput = decoderInputs.poll().put(frame.getFrame());
                                index = decoderIndices.poll();
                                int size = frame.getFrameSize();
                                if (frame.SPS) {
                                    Log.d(TAG,"Depacketized at "+Integer.toUnsignedString(frame.getTime())+" length = "+frame.getFrameSize()+": " + bytesToHex(frame.getFrame().array()));
                                    decoder.queueInputBuffer(index, 0, size, frame.getTime(), MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
                                    sps = true;
                                    pps = false;
                                } else if (frame.PPS) {
                                    if(sps){
                                        Log.d(TAG,"Depacketized at "+Integer.toUnsignedString(frame.getTime())+" length = "+frame.getFrameSize()+": " + bytesToHex(frame.getFrame().array()));
                                        decoder.queueInputBuffer(index, 0, size, frame.getTime(), MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
                                        pps = true;
                                    }
                                } else if (!frame.badFrame) {
                                    if(sps&&pps){
                                        Log.d(TAG,"Depacketized at "+Integer.toUnsignedString(frame.getTime())+" length = "+frame.getFrameSize()+": " + bytesToHex(frame.getFrame().array()));
                                        decoder.queueInputBuffer(index, 0, size, frame.getTime(), 0);
                                    }
                                }
                                else{
                                    throw new Exception("bad frame");
                                }
                            }
                            catch(Exception e){
                                e.printStackTrace();
                                if(index!=null){
                                    decoderIndices.push(index);
                                }
                                decoderInput.clear();
                                decoderInputs.push(decoderInput);
                            }
                            frame = new RTPFrame();
                        }
                    }
                }
            }
        } catch (Exception e) {e.printStackTrace();}
    });

这是我收到的 1 帧的所有 NAL 单元的示例,以及我如何对它们进行解包。

RTP:80 63 00 2D 27 0E 64 30 66 B4 BA 42 3C 81 E0 00 80 6F...

RTP:80 63 00 2E 27 0E 64 30 66 B4 BA 42 3C 01 F3 0F E0 3F...

RTP:80 63 00 2F 27 0E 64 30 66 B4 BA 42 3C 01 37 9B FA BA...

RTP:80 63 00 30 27 0E 64 30 66 B4 BA 42 3C 01 7F EA 75 7C...

RTP:80 63 00 31 27 0E 64 30 66 B4 BA 42 3C 01 FA D8 A9 FF...

RTP:80 63 00 32 27 0E 64 30 66 B4 BA 42 3C 01 1B C5 BC C0...

RTP:80 63 00 33 27 0E 64 30 66 B4 BA 42 3C 01 0F F4 9A DE...

RTP:80 63 00 34 27 0E 64 30 66 B4 BA 42 3C 01 F4 35 CD 28...

RTP:80 63 00 35 27 0E 64 30 66 B4 BA 42 3C 01 9E 45 70 13...

RTP:80 E3 00 36 27 0E 64 30 66 B4 BA 42 3C 41 0F 18 0D 83...

D/RTPreader:在 2985639104 处解包长度 = 12611: 00 00 00 01 21 E0 00 80 6F F0 B4 24 CD 5F 45 80 79 6E 0C...

这是解包前后的 SPS 和 PPS 示例

RTP: 80 63 00 00 5F DF A1 70 4F 2F 8A 3E 27 42 00 1F 8D 68 05 00 5B A1 00 00 03 00 01 00 00 03 00 1E 0F 10 7A 80

在 1608491376 处解包长度 = 27: 00 00 01 27 42 00 1F 8D 68 05 00 5B A1 00 00 03 00 01 00 00 03 00 1E 0F 10 7A 80

在 1608491376 长度 = 7: 00 00 01 28 CE 32 48 处解包

RTP:80 63 00 01 5F DF A1 70 4F 2F 8A 3E 28 CE 32 48

【问题讨论】:

  • 可能只是因为输入数据错误。也就是说,您放入输入缓冲区的 NALU。我认为在输入数据错误的情况下不会调用onError()。不确定你的 RTPFrame 类对每一帧做了什么,但根据我的经验,你需要 NALU 标头。如果您发布一些 decoderInput 内容(前十几个字节)的示例,那么我会将它们与我自己的代码中的内容进行比较。
  • 我没有看到您在输入缓冲区中添加任何数据。访问ByteBuffer decoderInput = decoderInputs.poll(); 后,您立即致电decoderInput = frame.getFrame(); 而不是您应该使用` ` decoderInput.put(fragme.getFrame()); 另外,我不了解您的SPS 和PPS 设置和检查。请注意,理论上这些可以在流式传输期间发生变化
  • @AnthonyAngrimson 不,我说错了,我认为你解包是正确的。 ChrisBe 的观察结果如何?
  • @AnthonyAngrimson 我同意@greeble31 的观点,即没有输出通常意味着格式错误或缺少数据/CSD。那么frame.SPSframe.PPS 是做什么的呢? SPSPPS NALU 是否已格式化?请注意,BUFFER_FLAG_CODEC_CONFIG 始终很重要。您是在更新SPSPPS,还是确定它们永远不会改变。你喂了多少帧?为什么if (frame.isComplete()) 块被调用了两次,还是我的代码有误?我还建议您只使用一个队列使用Pair&lt;Integer, ByteBuffer&gt;,这样您就永远不会混淆索引和相应的缓冲区。
  • 请更新您的代码以反映您现在实际运行的内容,并添加示例 SPS 和 PPS decoderInput 的十六进制转储。

标签: android h.264 rtp android-mediacodec


【解决方案1】:

我要感谢 @greeble31 和 @ChrisBe 在解决问题方面的帮助。问题确实出在我的 RTPFrame 类上。具体来说,getFrame() 方法,它遍历包含 NAL 单元数据的 ByteBuffers 列表,并使用put(ByteBuffer) 将它们添加到bb。这增加了bbposition,直到等于limit

public ByteBuffer getFrame() {
    size = 4;
    int nalCount = nalUnits.size();
    for (int i = 0;i<nalCount;i++){
        size+=nalUnits.get(i).data.length;
    }

    ByteBuffer bb = ByteBuffer.allocate(size);
    bb.put(new byte[]{0,0,0,1});
    for(NALUnit unit:nalUnits){
        bb.put(unit.data);
    }
    return bb;
}

return bb; 更改为return (ByteBuffer)bb.position(0); 解决了这个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-30
    • 2018-10-17
    • 2017-12-16
    • 1970-01-01
    • 1970-01-01
    • 2016-01-19
    • 1970-01-01
    • 2014-02-06
    相关资源
    最近更新 更多