簡體   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