[英]FFmpeg Javacv - Latency Issue
我正在使用 android v21 設備將數據流式傳輸到 javafx 應用程序。 它工作正常,但我有大約 2 秒的延遲。
截至目前,基本交通是這樣的
我的數據流到我的桌面和我的分包器比我的幀速率快得多,而且通常只是在等待。 其他任何地方都沒有數據積累,因此我假設我的任何代碼都沒有延遲。
我測試了我的 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.