【发布时间】:2020-03-29 18:58:31
【问题描述】:
我正在使用 android v21 设备将数据流式传输到 javafx 应用程序。它工作正常,但我有大约 2 秒的延迟。
到目前为止,基本的交通是这样的
- android webrtc/自定义实现 16ms
- android 分包器(udp) 6 毫秒
- udp 传输假定
- windows depacketizer 没有在缓冲区中堆积数据
- windows ffmpeg framgrabber 未知延迟
- 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