javaCV系列文章:
javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG、javaCV-openCV)
javaCV开发详解之3:收流器实现,录制流媒体服务器的rtsp/rtmp视频文件(基于javaCV-FFMPEG)
javaCV开发详解之4:转流器实现(也可作为本地收流器、推流器,新增添加图片及文字水印,视频图像帧保存),实现rtsp/rtmp/本地文件转发到rtmp流媒体服务器(基于javaCV-FFMPEG)
javaCV开发详解之5:录制音频(录制麦克风)到本地文件/流媒体服务器(基于javax.sound、javaCV-FFMPEG)
javaCV开发详解之6:本地音频(话筒设备)和视频(摄像头)抓取、混合并推送(录制)到服务器(本地)
javaCV开发详解之7:让音频转换更加简单,实现通用音频编码格式转换、重采样等音频参数的转换功能(以pcm16le编码的wav转mp3为例)
javaCV开发详解之8:转封装在rtsp转rtmp流中的应用(无须转码,更低的资源消耗,更好的性能,更低延迟)
补充篇:
javaCV开发详解之补充篇:根据视频时间戳同步播放图像画面以及实现视频倍速播放
javaCV开发详解补充篇:基于avfoundation的苹果Mac和ios获取屏幕画面及录屏/截屏以及摄像头画面和音频采样获取实现
音视频编解码问题:javaCV如何快速进行音频预处理和解复用编解码(基于javaCV-FFMPEG)
音视频编解码问题:16/24/32位位音频byte[]转换为小端序short[],int[],以byte[]转short[]为例
javacv文字识别系列:
javaCV文字识别之1:基于google的tesserac ocr识别图片中的文字,跨平台支持英文中文简体繁体等各种字符识别
javacpp-ffmpeg系列:
javacpp-FFmpeg系列之1:视频拉流解码成YUVJ420P,并保存为jpg图片
javacpp-FFmpeg系列之2:通用拉流解码器,支持视频拉流解码并转换为YUV、BGR24或RGB24等图像像素数据
javacpp-FFmpeg系列之3: 图像数据转换(BGR与BufferdImage互转,RGB与BufferdImage互转)
javacpp-FFmpeg系列补充:FFmpeg解决avformat_find_stream_info检索时间过长问题
javacpp-opencv系列:
一、javaCV图像处理之1:实时视频添加文字水印并截取视频图像保存成图片,实现文字水印的字体、位置、大小、粗度、翻转、平滑等操作
二、javaCV图像处理之2:实时视频添加图片水印,实现不同大小图片叠加,图像透明度控制
三、javacv图像处理3:使用opencv原生方法遍历摄像头设备及调用(方便多摄像头遍历及调用,相比javacv更快的摄像头读取速度和效率,方便读取后的图像处理)
一、前言
不管是推流还是拉流,还是摄像头抓取抑或是屏幕画面抓取都需要对视频画面进行预览播放,查看视频实际播放效果,之前我们都是根据帧率简单的通过Thread.sleep(1000/帧率);来以不太准确的时间来播放视频画面。
本章将通过实现一个简单的例子如何以视频时间戳的时间同步播放视频画面,以及实现倍速播放的目的。
二、实现功能
1、获取视频源信息
2、根据时间戳同步播放视频画面
3、倍速播放
三、实现原理
1、同步播放
记录每次显示前的时间(startViewTime)和显示结束的时间(endViewTime)。
视频当前帧画面的时间戳(nextTime)。
上一帧画面的时间戳(lastTime)。
理论来上来说两帧之间间隔是(nextTime-lastTime)。
但是显示图像也是需要耗时的,所以实际耗时应该是:(nextTime-lastTime -(endViewTime-startViewTime))。
也有可能会出现(nextTime-lastTime)>(endViewTime-startViewTime)的情况,这时候就不需要等待直接播放即可。
2、倍速播放(快进和慢放)
我们知道(nextTime-lastTime)表示两帧间隔,只要对该结果变量进行调整就可以实现倍速快进和慢放的效果。
四、代码实现
/**
* 播放视频流
* @another 做好自己!-- eguid原创,博客地址:blog.csdn.net/eguid_1或者eguid.blog.csdn.net
* @param url 视频地址
* @param videoPlaySpeed 播放速度
*/
private static void playVideo(String url, double videoPlaySpeed) throws Exception, InterruptedException {FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(url);
grabber.setFormat("mp4");// 基于gdigrab的输入格式
grabber.start();double frameRate = grabber.getVideoFrameRate();
int bitRate = grabber.getVideoBitrate();
int width = grabber.getImageWidth();
int height = grabber.getImageHeight();
long duraion = grabber.getLengthInTime();// 视频时长,也可以通过grabber.getFormatContext().duration();获取视频时长
System.err.println(
"视频信息:{宽度=" + width + ",高度=" + height + ",帧率:" + frameRate + ",比特率=" + bitRate + "视频时长:" + duraion);
int videoStreamIndex = grabber.getVideoStream();
CanvasFrame canvas = new CanvasFrame("屏幕预览", CanvasFrame.getDefaultGamma());// 新建一个窗口
canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
canvas.setAlwaysOnTop(true);
Frame frame = null;long startRealTime;// 实际播放时间
long startViewTime = 0;
long consumTime = 0;
// 抓取屏幕画面
int i = 0;
long lastTime = 0;
for (startRealTime = System.nanoTime(); (frame = grabber.grabImage()) != null;) {
// 显示画面
if (frame.streamIndex == videoStreamIndex) {
// 同步视频播放时间,为什么不能直接sleep(1000/帧率),因为显示图像也要耗时,而且每帧间隔不一定是(1000/帧率)每秒,所以需要定制
if (lastTime > 0) {
long time = frame.timestamp - lastTime;
if (videoPlaySpeed > 0 && videoPlaySpeed != 1) {
time /= videoPlaySpeed;
}
consumTime = (System.nanoTime() - startViewTime) / 1000;
if (consumTime < time) {
long interval = (time - consumTime) / 1000;
int nano = (int) (time % (interval * 1000));
Thread.sleep(interval, nano);
}
}
startViewTime = System.nanoTime();
canvas.showImage(frame);// 显示图像
i++;
lastTime = frame.timestamp;
}
}
double inteval = (System.nanoTime() - startRealTime) / 1000000000.0;
System.out.println("总耗时:" + inteval + "秒,总帧数:" + i + ",平均帧率:" + i / inteval);
grabber.stop();
canvas.dispose();
}
public static void main(String[] args) throws Exception, UnsupportedEncodingException, InterruptedException {
// 视频地址,播放速度(0-1之间表示慢放,1以上表示快放)
playVideo("eguid.mp4", 3);//三倍快放//playVideo("eguid.mp4", 0.5);//2倍慢放
}