java - Xuggler 解码 h264 问题

标签 java ffmpeg streaming h.264 xuggler

我正在尝试使用 Xuggler 将 IP 摄像机 rtsp 流转码为 rtmp 流(全部使用 h264 编解码器)。我目前有 2 个 IP 摄像机可供测试,并使用 Xuggler 编写了一个基本的 Java 程序来对这些流进行转码。

这是有问题的代码片段:

    // Setup the Input Container
    InContainer = IContainer.make();
    if(InContainer.open(InUrl, IContainer.Type.READ, null, false, false) < 0)
    {
        System.err.println("Could not open input container");
        return false;
    }
    System.out.println("Input cointainer opened...");

    // Loop until we find the key packet
    IPacket keyPacket = IPacket.make(); 
    InContainer.readNextPacket(keyPacket);
    //System.out.println("Waiting on key frame...");
    //while(InContainer.readNextPacket(keyPacket) >= 0 && !keyPacket.isKeyPacket()) {
        //System.out.println(keyPacket.toString());
    //}
    System.out.println(keyPacket.toString());
    System.out.println(bytesToHex(keyPacket.getData().getByteArray(0, keyPacket.getData().getSize())));

    videoStreamId = -1;
    int numStreams = InContainer.getNumStreams();
    System.out.println("Num. Streams in Container: " + numStreams);
    for(int i = 0; i < numStreams; i++){
        IStream stream = InContainer.getStream(i);
        IStreamCoder coder = stream.getStreamCoder();

        if(coder.getCodecType() == ICodec.Type.CODEC_TYPE_VIDEO)
        {
            VideoDecoder = coder;
            videoStreamId = i;

            if(VideoDecoder.open(null, null) < 0){
                System.err.println("Could not open video decoder for input container");
                return false;
            }
            System.out.println("Video decoder opened...");
            // Need to decode at least one key frame
            IVideoPicture keyPicture = IVideoPicture.make(VideoDecoder.getPixelType(), 0, 0);
            int bytesDecoded = VideoDecoder.decodeVideo(keyPicture, keyPacket, 0);
            if(bytesDecoded < 0)
            {
                throw new RuntimeException("Unable to decode key video packet");
            }

            System.out.println(DatatypeConverter.printBase64Binary(VideoDecoder.getExtraData().getByteArray(0, VideoDecoder.getExtraData().getSize())));
        }
        else // The stream has an unkown codec type, and no codec ID, we need to set the StreamCoder
        {
            coder.setCodec(ICodec.findDecodingCodec(ICodec.ID.CODEC_ID_H264));
            coder.setWidth(352);
            coder.setHeight(288);
            coder.setPixelType(IPixelFormat.Type.YUV420P);

            VideoDecoder = coder;
            videoStreamId = i;

            /*
            // Create the Extradata buffer
            byte[] start_sequence = new byte[]{0, 0, 1};
            byte[] extraData1 = DatatypeConverter.parseBase64Binary("Z0IAHtoFglMCKQI=");
            byte[] extraData2 = DatatypeConverter.parseBase64Binary("aN4Fcg==");
            int extraDataSize = extraData1.length + extraData2.length + start_sequence.length * 2;
            int destPos = 0;
            byte[] extraData = new byte[extraDataSize];
            System.arraycopy(start_sequence, 0, extraData, destPos, start_sequence.length);
            destPos += start_sequence.length;
            System.arraycopy(extraData1, 0, extraData, destPos, extraData1.length);
            destPos += extraData1.length;
            System.arraycopy(start_sequence, 0, extraData, destPos, start_sequence.length);
            destPos += start_sequence.length;
            System.arraycopy(extraData2, 0, extraData, destPos, extraData2.length);
            */
            if(VideoDecoder.open(null, null) < 0)
            {
                System.err.println("Could not open video decoder for input container");
                return false;
            }

            /*
            // Set the StreamCoder extradata
            IBuffer extraBuffer = IBuffer.make(null, extraData, 0, extraDataSize);
            int result = VideoDecoder.setExtraData(extraBuffer, 0, extraDataSize, true);
            if(result < 0)
            {
                System.err.println("Could not set the coder ExtraData");
            }
            else 
            {
                System.out.println("VideoDecoder ExtraData set!");
            }*/

            //System.out.println(DatatypeConverter.printBase64Binary(VideoDecoder.getExtraData().getByteArray(0, VideoDecoder.getExtraData().getSize())));

            IVideoPicture keyPicture = IVideoPicture.make(VideoDecoder.getPixelType(), VideoDecoder.getWidth(), VideoDecoder.getHeight());
            int bytesDecoded = VideoDecoder.decodeVideo(keyPicture, keyPacket, 0); //key/keyPacket
            if(bytesDecoded < 0)
            {
                throw new RuntimeException("Unable to decode key video packet");
            }
        }

    }

该程序能够成功地对相机的一个流进行转码,没有任何问题。 然而,另一个问题已经让我几天来一直头痛。在查看容器流的循环中,我有一个 else 语句,因为问题流具有 CODEC_TYPE_UNKOWN 和 CODEC_ID_NONE,所以我认为我需要手动设置所有内容。我遇到了各种各样的错误,例如:

15:22:36.964 [main] ERROR org.ffmpeg - [h264 @ 0000000000423870] no frame!

每次我尝试解码帧时都会收到此错误。我意识到这通常意味着没有读取关键帧,并且解码器需要 SPS/PPS 信息进行 h264 解码,我尝试手动设置(您可以在评论部分之一中看到),但没有成功。 我什至尝试创建一个数据包,用 SPS/PPS 信息填充它,将 key 数据包设置为 true 等等......也没有成功。即使在 while 循环(当前已注释掉)中,程序似乎也从未从一台摄像机获取关键帧。

我也收到了来自 Xuggler 的警告:

16:22:43.412 [main] WARN  com.xuggle.xuggler - Could not find streams in input container (../../../../../../../csrc/com/xuggle/xuggler/Container.cpp:898)

...我也研究过,但我见过的解决方案都不起作用。

我尝试在命令行中通过 FFMPEG 本身运行这两个相机的流 并且两者均已转码且没有错误。我还认为 Xuggler 可能是用太旧的 FFMPEG 版本构建的,无法正确支持 rtsp 流,但我回去下载了许多旧版本(0.10、1.0.1 - 1.2 和当前的 2.2)并尝试了命令行一切都成功了。

我在 Xuggler google 小组中看到了很多解决 rtsp 流和“无帧!”问题的帖子。错误,但他们都没有找到解决方案(或者至少有一个对我有用的解决方案)。 有谁知道可能是什么原因造成的?我完全没有想法了! (也是第一次在这里发帖,如果我做错了什么或遗漏了信息,我很抱歉)谢谢。

最佳答案

经过长时间的搜索和测试,我找到了解决问题的方法。

首先,我了解到“无框架!”当未设置 NAL 单元/额外数据时,可能会导致错误。经过进一步调查,我发现此信息可以在 RTSP DESCRIBE 响应发送的 SDP 信息中找到。 然后我使用 WireShark 查看了相机在 SDP 中发送的内容,一切看起来都很好,所以问题肯定出在 Xuggler 程序中。 我使用 Xuggler 函数设置了捕获的 FFMPEG 日志记录级别进行调试,以便当打开流 IContainer 时,FFMPEG 打印它得到的 SDP 信息。同样,这看起来与 Wireshark 向我展示的一模一样。 然后我发现 Xuggler IContainers 有一个“createSDP()”方法,它将打印容器的 SDP。使用后,我发现 SDP 实际上被错误地解析。媒体负载类型(在原始 SDP 中)被指定为 35 RTP/AVP,并且 SDP 中的“rtpmap”行将其设置为 H264。但是,“createSDP”方法将媒体负载类型显示为“3”。

我挖掘了 Xuggler 中包含的强制 FFMPEG 代码,发现解析媒体有效负载类型的逻辑是向后的,它首先检查动态类型 (96 +),然后检查静态有效负载类型 (1- 34),它不考虑未分配的有效负载类型(例如我的例子中的 35)。我将逻辑调整为当前 FFMPEG 代码的逻辑,然后 Xuggler 将 35 解析为动态负载类型,并且流已成功设置和转码。

我不知道这是否是问题的正确解决方案,但它确实有效,并且据我了解有效负载类型,它不应该影响程序的任何其他部分。

您可以在 Xuggler google groups here 上查看我的完整帖子.

关于java - Xuggler 解码 h264 问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23665562/

相关文章:

audio - 在编写时通过 HTTP 流式传输 MP3

java - 使用 JAVA 处理大文件的音频流服务器

java - 在接缝中自定义#{currentDate} 的格式?

java - 有点奇怪的java代码,它是如何工作的?

java - 纯文本字段验证以防止 XSS 攻击

java - 使方法只能被模板类中的某些对象访问

ffmpeg - 使用 ffmpeg 将图像转换为视频

c++ - 如何使用 ffmpeg 解码 AAC 网络音频流

node.js - ffprobe 无法读取 AWS 上 Node JS createWriteStream() 生成的文件

iphone - 通过 m3u8 流式传输到 iPhone