我正在使用android v21设备将数据流传输到javafx应用程序。它的工作正常,但我有大约2秒钟的延迟。
截至目前,基本交通方式是这样的
- android webrtc /自定义实现16毫秒
- android packetizer(udp)6毫秒
- udp传输假定为
- windows depacketizer在缓冲区中没有数据积累
- windows ffmpeg framgrabber未知延迟
- 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);
}
}