当前位置 主页 > 服务器问题 > win服务器问题汇总 >

    Javacv使用ffmpeg实现音视频同步播放

    栏目:win服务器问题汇总 时间:2019-12-05 18:06

    最近用javaCV的ffmpeg包的FFmpegFrameGrabber帧捕捉器对捕捉到的音频帧和视频帧做了同步的播放。采用的同步方法是视频向音频同步。

    程序和源码

    具体的思路如下:

    (1)首先介绍ffmpeg是如何捕捉视频文件的图像和声音的

    FFmpegFrameGrabber fg = new FFmpegFrameGrabber("a video file path or a url); 

    得到帧捕捉器对象后,调用它的grab()方法就会返回捕捉到的Frame对象。这个Frame可以是视频帧或者是音频帧,这是因为音视频帧时按照时间戳在播放时间先上排列的。当然捕捉到的帧都是已经译码过的,并且存储在java.nio.Buffer对象中,对于视频帧,Buffer是储存图像的像素数据比如RGB,然后通过

    BufferedImage bi = (new Java2DFrameConverter()).getBufferedImage(f); 

    就可以得到图片,得到的图片可以进行一系列的处理或者不处理直接显示在swing组件上。对应音频帧,Buffer是储存音频的PCM数据,这个PCM可以是float或者short的,然后用java.sounds.sample里面的sourceDataLine.write方法就可以将这些音频PCM数据写入到扬声器中。

    (2)接着介绍如何不断得将得到的帧播放出来。首先是单独播放视频:

    while(true) 
    { 
      Frame f = fg.grab();  
      if(f.image!=null) 
      label.setIcon(new ImageIcon((new Java2DFrameConverter()).getBufferedImage(f))); 
      Thread.sleep(1000/视频帧率);  
    } 
    

    单独播放音频同理,将数据写入到声卡即可。例子

    (3)生产消费者模式。

    上图是程序实现的方法,采用生产者模式将捕获到的帧进行判断,如果是视频帧就生产到视频FIFO中,如果是音频帧就生产到音频FIFO中,然后音频播放线程和视频播放线程分别从各自的帧仓库消费里面的帧。之所以采用生产消费者模式是因为帧捕获的速度是大于帧的消耗的,所以我们优先捕获帧来缓冲,或者进一步对捕获的帧进行预处理,而视频和音频播放线程只需要将处理过的帧直接播放显示即可。

    (4)实现音视频同步的方法:播放两帧音频里面的所有视频帧。

    想要实现音视频同步,必须要有帧的时间戳,这里捕获到的帧只有播放的时间戳PTS,没有译码时间戳DTS,所以我们只需要根据播放时间戳来决定播放即可。

    程序的实现是根据上图来的, 当音频线程开始播放音频帧A1时,就调用视频线程的setRun方法,并且传递当前要播放的音频帧时间戳curTime和下一帧音频帧A2的时间戳nextTime给处于wait态的视频线程,然后视频线程启动,开始从视频FIFO中取出视频帧G1,然后计算G1和A1的时间差,作为播放的延时,Thread.sleep(t1)后,视频线程就将图片显示在swing组件上,比如JLabel.setIcon(image)。然后视频线程再取出一帧图像G2,比较G2的时间戳和A2的时间戳,如果G2时间戳小于A2,那么视频线程继续延时t2以后,播放这个G2图像,接着G3同理,直到取得G4,和A2比较发现G4时间戳大于A2,那么视频线程就进入wait态,等待下一次启动。然后音频线程播放完A1音频帧以后,就从仓库取出音频帧A3,然后将A2的时间戳和A3的时间戳传递给视频线程,然后开始播放A2,然后堵塞的视频线程同理继续播放。

    (5)动态调节延时时间

    由于个人PC都不是实时操作系统,也就是Thread.sleep是不精确的,并且受到声卡播放声音的制约,所以上面的基本实现思路是需要加以完善的。首先java的sourceDataLine的方法是依照一定的速度从内部缓冲区取出音频线程写入的数据,如果音频写入的数据被取光了,那么音频播放就会发生卡顿,但是如果一次音频数据写入过多,那么就会发生音视频可能就会不同步,所以要确保sourceDataLine的内部缓冲区是留有一定数据的,否则就会造成卡顿,但是数据量又不能过多,所以我们在G3到A2这段时间来进行声音播放的调节,由于延时的不精准性,写入的A1帧的数据可能时间还没满t6就可能被声卡取光了,所以在播放完G3图像以后,声音线程会判断根据sourceDataLine.available()返回的数据量进行判断,如果数据量快要完了,就减少G3到A2的延时时间t4。这样子就可以保证数据量是不会变为0造成声音卡顿。