【问题标题】:FFmpeg Javacv - Latency IssueFFmpeg Javacv - 延迟问题
【发布时间】:2020-03-29 18:58:31
【问题描述】:

我正在使用 android v21 设备将数据流式传输到 javafx 应用程序。它工作正常,但我有大约 2 秒的延迟。

到目前为止,基本的交通是这样的

  1. android webrtc/自定义实现 16ms
  2. android 分包器(udp) 6 毫秒
  3. udp 传输假定
  4. windows depacketizer 没有在缓冲区中堆积数据
  5. windows ffmpeg framgrabber 未知延迟
  6. javafx 图像视图

我的数据流到我的桌面和我的分包器比我的帧速率快得多,而且通常只是在等待。其他任何地方都没有数据积累,因此我假设我的任何代码都没有延迟。

我通过将 yuv 从相机写入纹理并计时 android 设备将帧编码为 h264 之前多长时间以及发送之前多长时间来测试我的 android 设备。所以 16 + 6 = 22ms

我觉得问题出在 Javacv ffmpeg 帧抓取器上。我正在研究这个 api 以了解为什么会发生这种情况。

我主要担心的是 framegrabber 需要永远启动...大约 4 秒。

一旦它开始,我可以清楚地看到我插入了多少帧以及它抓取了多少帧,并且它总是滞后一些较大的数字,例如 40 到 200。

此外,无论我告诉它运行多快,Framegrabber.grab() 都会阻塞并每 100 毫秒运行一次以匹配我的帧速率,因此我永远赶不上。

你有什么建议吗?

我开始认为 javacv 不是一个可行的解决方案,因为似乎很多人都在为这个延迟问题而苦苦挣扎。如果您有其他建议,请告知。

我的 ffmpeg framgrabber

    public RapidDecoder(final InputStream inputStream, final ImageView view)
{
    System.out.println(TAG + " starting");

     grabber = new FFmpegFrameGrabber(inputStream, 0);
     converter = new Java2DFrameConverter();
     mView = view;


    emptyBuffer = new Runnable() {
        @Override
        public void run() {
            System.out.println(TAG + " emptybuffer thread running");
            try {

                grabber.setFrameRate(12);
                grabber.setVideoBitrate(10000);

                //grabber.setOption("g", "2");
               // grabber.setOption("bufsize", "10000");
                //grabber.setOption("af", "delay 20");
                //grabber.setNumBuffers(0);
                //grabber.setOption("flush_packets", "1");
                //grabber.setOption("probsize", "32");
                //grabber.setOption("analyzeduration", "0");
                grabber.setOption("preset", "ultrafast");

                grabber.setOption("fflags", "nobuffer");
                //grabber.setVideoOption("nobuffer", "1");
                //grabber.setOption("fflags", "discardcorrupt");
                //grabber.setOption("framedrop", "\\");
               //grabber.setOption("flags","low_delay");
                grabber.setOption("strict","experimental");
                //grabber.setOption("avioflags", "direct");
                //grabber.setOption("filter:v", "fps=fps=30");
                grabber.setVideoOption("tune", "zerolatency");
                //grabber.setFrameNumber(60);


                grabber.start();
            }catch (Exception e)
            {
                System.out.println(TAG + e);
            }

            while (true)
            {

                try{
                    grabFrame();
                    Thread.sleep(1);
                }catch (Exception e)
                {
                    System.out.println(TAG + " emptybuffer " + e);
                }

            }



        }
    };

    display = new Runnable() {
        @Override
        public void run() {

            System.out.println(TAG + " display thread running ");

            while(true)
            {

                try{
                    displayImage();
                    Thread.sleep(10);
                }catch (Exception e)
                {
                    System.out.println(TAG + " display " + e);
                }

            }


        }
    };




}


public void generateVideo()
{
    System.out.println(TAG + " genvid ");




    new Thread(emptyBuffer).start();
    new Thread(display).start();



}



public synchronized void grabFrame() throws FrameGrabber.Exception
{
           //frame = grabber.grabFrame();
        frame = grabber.grab();
    //System.out.println("grab");


}

public synchronized void displayImage()
{


    bufferedImage = converter.convert(frame);
    frame = null;
    if (bufferedImage == null) return;
    mView.setImage(SwingFXUtils.toFXImage(bufferedImage, null));
    //System.out.println("display");
}

在这里你可以看到我用图像绘制纹理并发送到 h264 编码器

@覆盖 public void onTextureFrameCaptured(int width, int height, int texId, float[] tranformMatrix, int rotation, long timestamp) { //Log.d(TAG, "onTextureFrameCaptured: ->");

            VideoRenderer.I420Frame frame = new VideoRenderer.I420Frame(width, height, rotation, texId, tranformMatrix, 0,timestamp);
            avccEncoder.renderFrame(frame);
            videoView.renderFrame(frame);
            surfaceTextureHelper.returnTextureFrame();

        }

在这里你可以看到 webrtc 编码发生

 @Override
    public void renderFrame(VideoRenderer.I420Frame i420Frame) {
        start = System.nanoTime();
        bufferque++;

        mediaCodecHandler.post(new Runnable() {
            @Override
            public void run() {
                videoEncoder.encodeTexture(false, i420Frame.textureId, i420Frame.samplingMatrix, TimeUnit.NANOSECONDS.toMicros(i420Frame.timestamp));
            }
        });


    }

    /**
     * Called to retrieve an encoded frame
     */
    @Override
    public void onEncodedFrame(MediaCodecVideoEncoder.OutputBufferInfo frame, MediaCodec.BufferInfo bufferInfo) {

        b = new byte[frame.buffer().remaining()];
        frame.buffer().get(b);
        synchronized (lock)
        {
            encodedBuffer.add(b);
            lock.notifyAll();
            if(encodedBuffer.size() > 1)
            {
                Log.e(TAG, "drainEncoder: too big: " + encodedBuffer.size(),null );

            }
        }
        duration = System.nanoTime() - start;
        bufferque--;
        calcAverage();
        if (bufferque > 0)
        {
        Log.d(TAG, "onEncodedFrame: bufferque size: " + bufferque);


    }

}

【问题讨论】:

  • 可能与编码有关。您可以尝试将 IFRAME_INTERVAL 设置为 -1 而不是 5。不要使用 v21 的许多其他选项来减少延迟。
  • 感谢您的回复。我注意到大多数视频通话应用程序都需要 4.4。你有任何示例项目的链接吗?
  • 大多数视频通话应用可能没有使用 MediaCodec api。如果那是您要去的路线,请查看 WebRTC。
  • 我认为你是对的。我去看看
  • 此更改至少应考虑启动时间:@​​987654321@ 如果没有,请告诉我。

标签: java android ffmpeg h.264 javacv


【解决方案1】:

我在上面编辑了我的问题,因为我在几天内解决了这个问题,但让我为可能需要它们的人提供详细信息。

Android - 我最终使用了这个库 https://github.com/Piasy/VideoCRE 它会打开 webrtc 函数,并允许您逐帧编码视频。这就是我如何在 16 毫秒时对帧进行基准测试,以便在一部糟糕的旧手机上进行编码。

javacv ffmpeg - 解决方案是 c++ avcodec 中的缓冲问题。为了证明这一点,请尝试以两次或 10 次而不是一次来馈送每一帧。尽管提要也变得无用,但它以相同的因素减少了延迟。它还减少了视频源的启动时间。然而,在 javacv 代码中 ffmpegframegrabber 的第 926 行,我通过此链接将线程从 (0) 设置为 (1) https://mailman.videolan.org/pipermail/x264-devel/2009-May/005880.html

thread_count = 0 指示 x264 使用足够的线程来加载所有 编码期间的 CPU 内核。因此,您可能会在双重设备上运行测试 核心机(2个核心将有3个线程)。要获得 x264 编码而不 延迟,设置thread_count = 1。

您可能会发现无数关于通过 javacv 设置选项的建议,但是我从来没有让 javacv 拒绝我设置的选项,并且多次了解到我影响了错误的因素。这是我尝试过的事情的清单;

                //grabber.setFrameRate(12);
                //grabber.setVideoBitrate(10000);

                //grabber.setOption("g", "2");
               // grabber.setOption("bufsize", "10000");
                //grabber.setOption("af", "delay 20");
                //grabber.setNumBuffers(0);
                //grabber.setOption("flush_packets", "1");
                //grabber.setOption("probsize", "32");
                //grabber.setOption("analyzeduration", "0");
                //grabber.setOption("preset", "ultrafast");

                //grabber.setOption("fflags", "nobuffer");
                //grabber.setVideoOption("nobuffer", "1");
                //grabber.setOption("fflags", "discardcorrupt");
                //grabber.setOption("framedrop", "\\");
               //grabber.setOption("flags","low_delay");
                //grabber.setOption("strict","experimental");
                //grabber.setOption("avioflags", "direct");
                //grabber.setOption("filter:v", "fps=fps=30");
                //grabber.setOptions("look_ahead", "0");
                //Map options = new HashMap();
                //options.put("tune", "zerolatency");
                grabber.setVideoOption("look_ahead", "0");
                //grabber.setFrameNumber(60);

它们都不起作用,当您阅读文档时,您会明白,当 ffmpeg 启动时,有不同的编码器(avcontext、videocontext、audiocontext)采用不同的值,并且有不同的 api framegrabber 和 ffply 采用不同的标志(i相信)所以往墙上扔东西是徒劳的。

先尝试将额外的帧添加到您的流中。此外,如果您只需要一个图像,只需向您的输入流添加一个空数据包,它将刷新缓冲区。

如果您需要为机器人视觉流式传输视频,请查看我的博文 http://cagneymoreau.com/stream-video-android/

【讨论】:

猜你喜欢
  • 2011-01-14
  • 2018-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-14
相关资源
最近更新 更多