FFmpeg Javacv-延迟问题

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

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

  1. android webrtc /自定义实现16毫秒
  2. android packetizer(udp)6毫秒
  3. udp传输假定为
  4. windows depacketizer在缓冲区中没有数据积累
  5. windows ffmpeg framgrabber未知延迟
  6. javafx imageview

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

我通过将相机中的yuv写入纹理并计时了android设备将帧编码为h264的时间,然后直到发送它的时间,测试了我的android设备。所以16 + 6 = 22ms

我觉得问题出在Javacv ffmpeg framegrabber。我正在研究此api,以了解发生这种情况的原因。

我主要担心的是框架抓取器需要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,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);


    }

}
cc0734 回答:FFmpeg Javacv-延迟问题

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

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

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

将线程从(0)设置为(1)。
  

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/

本文链接:https://www.f2er.com/2974346.html

大家都在问