繁体   English   中英

FFmpeg Javacv - 延迟问题

[英]FFmpeg Javacv - Latency Issue

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

截至目前,基本交通是这样的

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

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

我测试了我的 android 设备,方法是将 yuv 从相机写入纹理,并在 android 设备将帧编码为 h264 之前的时间以及发送之前的时间进行计时。 所以 16 + 6 = 22 毫秒

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

我主要担心的是 framegrabber 需要 foever 才能启动……大约 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 编码器

@Override 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);


    }

}

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

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 采用不同的标志(我相信)所以往墙上扔东西是徒劳的。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM