提交 05c97022 编写于 作者: E eguid

1、录像服务的持久层设计不合理,现去除录像服务的持久层接口

2、新增两个与springboot+postgre数据库演示demo
上级 54ee8d67
# easyCV
# easyCV
video recording and snapshot service,based on javaCV.
基于javaCV的跨平台视频录像和快照(截图)服务,开箱即用。
### 更新
1、录像服务的持久层设计不合理,现去除录像服务的持久层接口
2、新增两个与springboot+postgre数据库演示demo
### 演示demo
1、[截图服务在线演示:http://eguid.cc/screenshot/test](http://eguid.cc/screenshot/test)<br />
......@@ -10,23 +13,21 @@
同样可以通过http://eguid.cc/videorecord/查看历史录像列表并进行点播观看
### dependency library
Core lib based on 'javacv 1.4.x',web service based on 'spring-boot 2.x'.
Corelib based on 'javacv 1.4.x',exaples based on 'spring-boot 2.x'.
### build
Project is based on jdk1.8,build on maven.
Project is based on jdk1.8,build on maven 3.7.
### core lib
The core library of video recording and snapshots is two separate modules.
截图快照和视频录像是两个独立的核心库。
核心库提供截图快照和视频录像两套API,exaples中几个示例都是基于这两个库。
### web service
Web services used springboot services,each web service is an independent micro service.
The default port of video recording service is '8082',video capture service is '8081'.
同样的,web服务也是两个独立的springboot微服务,截图服务默认使用8081端口,录像服务使用8082端口。
### exaples
提供了几个springboot服务,截图服务默认使用8081端口,录像服务使用8082端口。
其中截图功能是支持文件和base64两种方式生成截图,而录像服务除了需要指定保存路径外,还需要配置一个可访问的http/ftp访问地址(我们一般把录像文件存放到一个http/ftp服务的目录下,以方便点播录像文件)。
截图和录像信息的表结构都是简单的单表,这里就不放了,直接查看*Mapper.xml文件即可
### support
Video source support rtsp/rtmp/flv/hls/file...,record file support mp4/flv/mkv/avi....
Image format support jpg/png/jpeg/gif/bmp.
视频源支持多种音视频流媒体源,录像文件可以任意指定保存的视频格式,视频截图快照支持以上五种格式。
Support rtsp/rtmp/flv/hls/file...,Recorder support mp4/flv/mkv/avi....
Image support jpg/png/jpeg/gif/bmp.
视频源支持rtsp/rtmp/flv/hls/视频文件等多种音视频流媒体源,录像文件可以支持mp4/flv/mkv/avi等视频格式,视频快照(截图)支持jpg/png/jpeg/gif/bmp等格式。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.eguid.cv</groupId>
<artifactId>easyCV</artifactId>
<version>2019.4.16</version>
</parent>
<groupId>cc.eguid.cv.corelib</groupId>
<artifactId>corelib</artifactId>
<name>corelib</name>
<version>1.0</version>
<packaging>pom</packaging>
<description>corelib</description>
<modules>
<module>videoimageshot</module>
<module>videoRecorder</module>
</modules>
<dependencies>
<!-- 日志
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
-->
<!-- test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.eguid.cv.corelib</groupId>
<artifactId>corelib</artifactId>
<version>1.0</version>
</parent>
<groupId>cc.eguid.cv.corelib</groupId>
<artifactId>videoRecorder</artifactId>
<version>1.1.0-2019.3.25</version>
<name>videoRecorder</name>
<url>https://blog.eguid.cc</url>
<description>2018年10月17日
1、修复javacv录制mp4视频的编码问题,修改为强制使用h264编码
2018年10月16日
1、增加历史任务持久化接口,原有内置历史任务存储实现转移到DefaultRecordInfoStorage中,如果未指定持久化,则使用默认存储器
2、增加目录设置,如果目录不为空,则使用该目录作为录像存储目录
2018年10月10日
完成主体程序开发,并完成整体流程测试
基本架构:
任务管理器(管理单元)--&gt;管理一个任务处理器对象池
| |
持久化接口 任务处理器(控制单元,预处理和控制工作子线程处理)----&gt;工作子线程(计算单元)
</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
</dependency>
-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<exclusions>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>opencv</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>flycapture</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>libdc1394</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>libfreenect</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>videoinput</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>libfreenect2</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>librealsense</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>artoolkitplus</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>flandmark</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>leptonica</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>tesseract</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg-platform</artifactId>
</dependency>
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg</artifactId>
</dependency>
-->
</dependencies>
</project>
package cc.eguid.cv.videoRecorder.entity;
import java.io.Serializable;
import java.util.Date;
/**
* 录制信息
*
* @author eguid
*
*/
public class RecordInfo implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
protected Integer id;//id
protected String src;// 视频源
protected String out;// 保存位置
protected String playurl;//播放地址
protected Date starttime;// 任务创建时间
protected Date endtime;// 任务结束时间
public RecordInfo() {
super();
}
public RecordInfo(String src, String out) {
super();
this.src = src;
this.out = out;
}
public RecordInfo(Integer id, String src, String out) {
super();
this.id = id;
this.src = src;
this.out = out;
}
public RecordInfo(Integer id, String src, String out, Date starttime, Date endtime) {
super();
this.id = id;
this.src = src;
this.out = out;
this.starttime = starttime;
this.endtime = endtime;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSrc() {
return src;
}
public void setSrc(String src) {
this.src = src;
}
public String getOut() {
return out;
}
public void setOut(String out) {
this.out = out;
}
public Date getstarttime() {
return starttime;
}
public void setstarttime(Date starttime) {
this.starttime = starttime;
}
public Date getEndtime() {
return endtime;
}
public void setEndtime(Date endtime) {
this.endtime = endtime;
}
public String getPlayurl() {
return playurl;
}
public void setPlayurl(String playurl) {
this.playurl = playurl;
}
@Override
public RecordInfo clone() throws CloneNotSupportedException {
return (RecordInfo) super.clone();
}
@Override
public String toString() {
return "RecordInfo [id=" + id + ", src=" + src + ", out=" + out + ", playurl=" + playurl + ", starttime="
+ starttime + ", endtime=" + endtime + "]";
}
}
package cc.eguid.cv.videoRecorder.entity;
import java.util.Date;
import cc.eguid.cv.videoRecorder.recorder.Recorder;
/**
* 录制任务
*
* @author eguid
*
*/
public class RecordTask extends RecordInfo{
private static final long serialVersionUID = -8883251081940691664L;
private Recorder recorder;//录制器
private int status;//录制器状态
public RecordTask(Integer id, String src, String out) {
super(id, src, out);
}
public RecordTask(Integer id, String src, String out, Recorder recorder) {
super(id, src, out);
this.recorder = recorder;
}
public RecordTask(Integer id, String src, String out, Date createtime, Date endtime) {
super(id, src, out, createtime, endtime);
}
public RecordTask(Integer id, String src, String out,Date createtime, Date endtime, Recorder recorder) {
super(id, src, out, createtime, endtime);
}
public Recorder getRecorder() {
return recorder;
}
public void setRecorder(Recorder recorder) {
this.recorder = recorder;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
@Override
public String toString() {
return "RecordTask [recorder=" + recorder + ", status=" + status + ", toString()=" + super.toString() + "]";
}
}
package cc.eguid.cv.videoRecorder.manager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.Stack;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import cc.eguid.cv.videoRecorder.entity.RecordTask;
import cc.eguid.cv.videoRecorder.recorder.JavaCVRecord;
import cc.eguid.cv.videoRecorder.recorder.Recorder;
/**
* 默认任务管理(内置对象池管理)
*
* @author eguid
*
*/
public class DefaultTasksManager implements TasksManager {
public static final int START_STATUS = 1;
public static final int PAUSE_STATUS = -1;
public static final int STOP_STATUS = 2;
protected static volatile int task_id_index = 0;// id累加
protected volatile int maxSize = -1;// 最大任务限制,如果小于0则无限大
private String record_dir;//文件存储目录
private String play_url;//播放地址
public String getRecordDir() {
return record_dir;
}
public String getPlay_url() {
return play_url;
}
@Override
public TasksManager setPlayUrl(String play_url) {
this.play_url = play_url;
return this;
}
@Override
public TasksManager setRecordDir(String recordDir) {
this.record_dir = recordDir;
return this;
}
// 对象池操作
// 当前任务池大小
protected volatile int pool_size = 0;
// 当前任务池中数量
protected volatile int work_size = 0;
// 当前空闲任务数量
protected volatile int idle_size = 0;
/** 工作任务池 */
protected Queue<RecordTask> workpool = null;
/** 空闲任务池 */
protected Stack<RecordTask> idlepool = null;
/**
* 初始化
* @param maxSize -最大工作任务大小
* @param historyStorage -历史任务存储
* @throws Exception
*/
public DefaultTasksManager(int maxSize) throws Exception {
super();
if (maxSize < 1) {
throw new Exception("maxSize不能空不能小于1");
}
this.maxSize = maxSize;
this.workpool = new ConcurrentLinkedQueue<>();
this.idlepool = new Stack<>();
}
@Override
public synchronized RecordTask createRcorder(String src, String out) throws Exception {
RecordTask task = null;
Recorder recorder=null;
int id = getId();
// System.out.println("创建时,当前池数量:"+pool_size+",空闲数量:"+idle_size+",工作数量:"+work_size);
// 限制任务线程数量,先看有无空闲,再创建新的
String playurl=(play_url==null?out:play_url+out);
out =(record_dir==null?out:record_dir+out);
if (idle_size > 0) {// 如果有空闲任务,先从空闲任务池中获取
idle_size--;//空闲池数量减少
task=idlepool.pop();
task.setId(id);
task.setOut(out);
task.setSrc(src);
task.setPlayurl(playurl);
saveTaskAndinitRecorder(task);
return task;
}else if (pool_size <maxSize) {// 池中总数量未超出,则新建,若超出,不创建
recorder = new JavaCVRecord(src, out);
task = new RecordTask(id, src, out, recorder);
task.setPlayurl(playurl);
saveTaskAndinitRecorder(task);
pool_size++;// 池中活动数量增加
return task;
}
//池中数量已满,且空闲池以空,返回null
// 超出限制数量,返回空
return null;
}
@Override
public boolean start(RecordTask task) {
if(task!=null) {
Recorder recorder = task.getRecorder();
task.setstarttime(now());// 设置开始时间
task.setStatus(START_STATUS);// 状态设为开始
recorder.start();
return true;
}
return false;
}
@Override
public boolean pause(RecordTask task) {
if(task!=null) {
task.setEndtime(now());
task.setStatus(PAUSE_STATUS);// 状态设为暂停
task.getRecorder().pause();
}
return false;
}
@Override
public boolean carryon(RecordTask task) {
if(task!=null) {
task.getRecorder().carryon();
task.setStatus(START_STATUS);// 状态设为开始
}
return false;
}
@Override
public boolean stop(RecordTask task) {
if (task!=null&&pool_size > 0 && work_size > 0 ) {
task.getRecorder().stop();// 停止录制
task.setEndtime(now());
task.setStatus(STOP_STATUS);
// 工作池中有没有
if (!workpool.contains(task)) {
return false;
}
// 从工作池中删除,存入空闲池
if (idlepool.add(task)) {
idle_size++;
if (workpool.remove(task)) {
work_size--;
System.out.println("归还后,当前池数量:"+pool_size+",空闲数量:"+idle_size+",工作数量:"+work_size);
return true;
}
}
}
return false;
}
@Override
public List<RecordTask> list() {
List<RecordTask> list=new ArrayList<>();
Iterator<RecordTask> itor=workpool.iterator();
for(;itor.hasNext();) {
list.add(itor.next());
}
return list;
}
@Override
public RecordTask getRcorderTask(Integer id) {
Iterator<RecordTask> itor=workpool.iterator();
for(;itor.hasNext();) {
RecordTask task=itor.next();
if(task.getId()==id) {
return task;
}
}
return null;
}
/*
* 保存任务并初始化录制器
* @param task
* @throws CloneNotSupportedException
* @throws IOException
*/
private void saveTaskAndinitRecorder(RecordTask task) throws CloneNotSupportedException, IOException {
Recorder recorder=task.getRecorder();
recorder.from(task.getSrc()).to(task.getOut());//重新设置视频源和输出
workpool.add(task);
//由于使用的是池中的引用,所以使用克隆用于保存副本
work_size++;//工作池数量增加
}
/*
* 获取当前时间
*
* @return
*/
private Date now() {
return new Date();
}
/*
* 获取自增id
* @return
*/
private int getId() {
return ++task_id_index;
}
/**
* 线程保活和销毁定时器
* @author eguid
*
*/
class WorkerThreadTimer{
private Timer timer=null;
private int period=60*1000;
public WorkerThreadTimer(int period){
this.period=period;
timer=new Timer("录像线程池保活定时器",false);
}
public int getPeriod() {
return period;
}
public void setPeriod(int period) {
this.period = period;
}
public void start() {
timer.schedule(new TimerTask() {
@Override
public void run() {
for(RecordTask rt:idlepool) {
Recorder r=rt.getRecorder();
if(r!=null) {
r.alive();
}
}
}
},period, period);
}
}
@Override
public boolean exist(String src, String out) {
System.err.println("检查本地服务是否在工作的录像任务:"+src+","+out);
int index=-1;
for(RecordTask rt:workpool) {
System.err.println("循环中:"+rt);
if(src.equals(rt.getSrc())&&rt.getOut().contains(out)) {
index++;
break;
}
}
System.err.println("检查本地服务是否在工作的录像任务结果:"+index);
return index>=0;
}
}
package cc.eguid.cv.videoRecorder.manager;
import java.io.IOException;
import java.util.List;
import cc.eguid.cv.videoRecorder.entity.RecordTask;
/**
* 录制任务管理(可以从该处获录制器,并管理这些录制器)
* @author eguid
*
*/
public interface TasksManager {
/**
* 设置录像目录
* @param recordDir -目录位置,如果为空,则只根据out存放
* @return TasksManager
*/
TasksManager setRecordDir(String recordDir);
/**
* 设置播放地址
* @param play_url
* @return
*/
TasksManager setPlayUrl(String play_url);
/**
* 获取一个新的录制器,如果为空表示工作线程已满
* @param src -视频源
* @param out -输出地址(如果recordDir为空,以out为准)
* @return
* @throws IOException
* @throws Exception
*/
RecordTask createRcorder(String src,String out) throws IOException, Exception;
/**
* 根据id获取录制任务
* @param id
* @return
*/
RecordTask getRcorderTask(Integer id);
/**
* 正在工作的录制任务列表
* @return
*/
List<RecordTask> list();
/**
* 是否存在某个正在工作的任务(以src和out判断)
* @param task
* @return
*/
boolean exist(String src,String out);
/**
* 停止并归还录像任务
* @param task
* @return
*/
boolean stop(RecordTask task);
/**
* 暂停
* @param task
* @return
*/
boolean pause(RecordTask task);
/**
* 继续(从暂停中恢复,停止后无法继续)
* @param task
* @return
*/
boolean carryon(RecordTask task);
/**
* 开始任务
* @param task
* @return
*/
boolean start(RecordTask task);
}
package cc.eguid.cv.videoRecorder.recorder;
import static org.bytedeco.javacpp.avcodec.*;
import static org.bytedeco.javacpp.avformat.*;
import java.io.IOException;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FrameGrabber.Exception;
/**
* rtsp转rtmp(转封装方式)
* @author eguid
*/
public class ConvertVideoPakcet {
FFmpegFrameGrabber grabber = null;
FFmpegFrameRecorderPlus record = null;
int width = -1, height = -1;
// 视频参数
protected int audiocodecid;
protected int codecid;
protected double framerate;// 帧率
protected int bitrate;// 比特率
// 音频参数
// 想要录制音频,这三个参数必须有:audioChannels > 0 && audioBitrate > 0 && sampleRate > 0
private int audioChannels;
private int audioBitrate;
private int sampleRate;
/**
* 选择视频源
* @param src
* @author eguid
* @throws Exception
*/
public ConvertVideoPakcet from(String src) throws Exception {
// 采集/抓取器
grabber = new FFmpegFrameGrabber(src);
if(src.indexOf("rtsp")>=0) {
grabber.setOption("rtsp_transport","tcp");
}
grabber.start();// 开始之后ffmpeg会采集视频信息,之后就可以获取音视频信息
if (width < 0 || height < 0) {
width = grabber.getImageWidth();
height = grabber.getImageHeight();
}
// 视频参数
audiocodecid = grabber.getAudioCodec();
System.err.println("音频编码:" + audiocodecid);
codecid = grabber.getVideoCodec();
framerate = grabber.getVideoFrameRate();// 帧率
bitrate = grabber.getVideoBitrate();// 比特率
// 音频参数
// 想要录制音频,这三个参数必须有:audioChannels > 0 && audioBitrate > 0 && sampleRate > 0
audioChannels = grabber.getAudioChannels();
audioBitrate = grabber.getAudioBitrate();
if (audioBitrate < 1) {
audioBitrate = 128 * 1000;// 默认音频比特率
}
return this;
}
/**
* 选择输出
* @param out
* @author eguid
* @throws IOException
*/
public ConvertVideoPakcet to(String out) throws IOException {
// 录制/推流器
record = new FFmpegFrameRecorderPlus(out, width, height);
record.setVideoOption("crf", "18");
record.setGopSize(2);
record.setFrameRate(framerate);
record.setVideoBitrate(bitrate);
record.setAudioChannels(audioChannels);
record.setAudioBitrate(audioBitrate);
record.setSampleRate(sampleRate);
AVFormatContext fc = null;
if (out.indexOf("rtmp") >= 0 || out.indexOf("flv") > 0) {
// 封装格式flv
record.setFormat("flv");
record.setAudioCodecName("aac");
record.setVideoCodec(codecid);
fc = grabber.getFormatContext();
}
record.start(fc);
return this;
}
/**
* 转封装
* @author eguid
* @throws IOException
*/
public ConvertVideoPakcet go() throws IOException {
long err_index = 0;//采集或推流导致的错误次数
//连续五次没有采集到帧则认为视频采集结束,程序错误次数超过1次即中断程序
for(int no_frame_index=0;no_frame_index<5||err_index>1;) {
AVPacket pkt=null;
try {
//没有解码的音视频帧
pkt=grabber.grabPacket();
if(pkt==null||pkt.size()<=0||pkt.data()==null) {
//空包记录次数跳过
no_frame_index++;
continue;
}
//不需要编码直接把音视频帧推出去
err_index+=(record.recordPacket(pkt)?0:1);//如果失败err_index自增1
av_free_packet(pkt);
}catch (Exception e) {//推流失败
err_index++;
} catch (IOException e) {
err_index++;
}
}
return this;
}
public static void main(String[] args) throws Exception, IOException {
new ConvertVideoPakcet().from("rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov")
.to("rtmp://eguid.cc:1935/rtmp/eguid")
.go();
}
}
package cc.eguid.cv.videoRecorder.recorder;
import static org.bytedeco.javacpp.avcodec.*;
import static org.bytedeco.javacpp.avutil.*;
import static org.bytedeco.javacpp.avformat.*;
import java.io.IOException;
import org.bytedeco.javacpp.avformat.AVFormatContext;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber.Exception;
import cc.eguid.cv.videoRecorder.work.RecordThread;
public class JavaCVRecord implements Recorder {
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
private final static String THREAD_NAME="录像工作线程";
FFmpegFrameGrabber grabber = null;
FFmpegFrameRecorderPlus record = null;
String src, out;
int width = -1, height = -1;
// 视频参数
protected int audiocodecid;
protected int codecid;
protected double framerate;// 帧率
protected int bitrate;// 比特率
// 音频参数
// 想要录制音频,这三个参数必须有:audioChannels > 0 && audioBitrate > 0 && sampleRate > 0
private int audioChannels;
private int audioBitrate;
private int sampleRate;
protected RecordThread cuThread;// 当前线程
public JavaCVRecord() {
super();
}
public JavaCVRecord(String src, String out) {
super();
this.src = src;
this.out = out;
}
public JavaCVRecord(String src, String out, int width, int height) {
super();
this.src = src;
this.out = out;
this.width = width;
this.height = height;
}
public String getSrc() {
return src;
}
public JavaCVRecord setSrc(String src) {
this.src = src;
return this;
}
public String getOut() {
return out;
}
public JavaCVRecord setOut(String out) {
this.out = out;
return this;
}
public int getWidth() {
return width;
}
public JavaCVRecord setWidth(int width) {
this.width = width;
return this;
}
public int getHeight() {
return height;
}
public JavaCVRecord setHeight(int height) {
this.height = height;
return this;
}
public Recorder stream() throws IOException {
return stream(src, out);
}
public int getAudioChannels() {
return audioChannels;
}
public JavaCVRecord setAudioChannels(int audioChannels) {
this.audioChannels = audioChannels;
return this;
}
public int getAudioBitrate() {
return audioBitrate;
}
public JavaCVRecord setAudioBitrate(int audioBitrate) {
this.audioBitrate = audioBitrate;
return this;
}
public int getSampleRate() {
return sampleRate;
}
public JavaCVRecord setSampleRate(int sampleRate) {
this.sampleRate = sampleRate;
return this;
}
/**
* 视频源
*
* @param src
* @return
* @throws Exception
*/
public Recorder from(String src) throws Exception {
av_log_set_level(AV_LOG_ERROR);
if (src == null) {
throw new Exception("源视频不能为空");
}
this.src = src;
// 采集/抓取器
grabber = new FFmpegFrameGrabber(src);
if (hasRTSP(src)) {
grabber.setOption("rtsp_transport", "tcp");
}
grabber.start();// 开始之后ffmpeg会采集视频信息,之后就可以获取音视频信息
if (width < 0 || height < 0) {
width = grabber.getImageWidth();
height = grabber.getImageHeight();
}
// 视频参数
audiocodecid = grabber.getAudioCodec();
codecid = grabber.getVideoCodec();
framerate = grabber.getVideoFrameRate();// 帧率
bitrate = grabber.getVideoBitrate();// 比特率
// 音频参数
// 想要录制音频,这三个参数必须有:audioChannels > 0 && audioBitrate > 0 && sampleRate > 0
audioChannels = grabber.getAudioChannels();
audioBitrate = grabber.getAudioBitrate();
if (audioBitrate < 1) {
audioBitrate = 128 * 1000;// 默认音频比特率
}
sampleRate = grabber.getSampleRate();
return this;
}
/**
* 音频参数设置
*
* @param audioChannels
* @param audioBitrate
* @param sampleRate
* @return
*/
public Recorder audioParam(int audioChannels, int audioBitrate, int sampleRate) {
this.audioChannels = audioChannels;
this.audioBitrate = audioBitrate;
this.sampleRate = sampleRate;
return this;
}
/**
* 视频参数设置
*
* @param width
* -可以为空,为空表示不改变源视频宽度
* @param height
* -可以为空,为空表示不改变源视频高度
* @param framerate-帧率
* @param bitrate-比特率
* @return
*/
public Recorder videoParam(Integer width, Integer height, int framerate, int bitrate) {
if (width != null) {
this.width = width;
}
if (height != null) {
this.height = height;
}
this.framerate = framerate;
this.bitrate = bitrate;
return this;
}
/**
* 输出视频到文件或者流服务
*
* @param out
* -输出位置(支持流服务和文件)
* @return
* @throws IOException
*/
public Recorder to(String out) throws IOException {
if (out == null) {
throw new Exception("输出视频不能为空");
}
this.out = out;
// 录制/推流器
record = new FFmpegFrameRecorderPlus(out, width, height);
record.setVideoOption("crf", "18");
record.setGopSize(2);
record.setFrameRate(framerate);
record.setVideoBitrate(bitrate);
record.setAudioChannels(audioChannels);
record.setAudioBitrate(audioBitrate);
record.setSampleRate(sampleRate);
AVFormatContext fc = null;
//rtmp和flv
if (hasRTMPFLV(out)) {
// 封装格式flv,并使用h264和aac编码
record.setFormat("flv");
record.setVideoCodec(AV_CODEC_ID_H264);
record.setAudioCodec(AV_CODEC_ID_AAC);
}else if(hasMP4(out)){//MP4
record.setFormat("mp4");
record.setVideoCodec(AV_CODEC_ID_H264);
record.setAudioCodec(AV_CODEC_ID_AAC);
}
record.start(fc);
return this;
}
/*
* 是否包含rtmp或flv
*/
private boolean hasRTMPFLV(String str) {
return str.indexOf("rtmp") >-1|| str.indexOf("flv") > 0;
}
/*
* 是否包含mp4
*/
private boolean hasMP4(String str) {
return str.indexOf("mp4") >0;
}
/*
* 是否包含rtsp
*/
private boolean hasRTSP(String str) {
return str.indexOf("rtsp") >-1;
}
/**
* 转发源视频到输出(复制)
*
* @param src
* -源视频
* @param out
* -输出流媒体服务地址
* @return
* @throws org.bytedeco.javacv.FrameRecorder.Exception
*/
public Recorder stream(String src, String out) throws IOException {
if (src == null || out == null) {
throw new Exception("源视频和输出为空");
}
this.src = src;
this.out = out;
// 采集/抓取器
grabber = new FFmpegFrameGrabber(src);
grabber.start();
if (width < 0 || height < 0) {
width = grabber.getImageWidth();
height = grabber.getImageHeight();
}
// 视频参数
int audiocodecid = grabber.getAudioCodec();
int codecid = grabber.getVideoCodec();
double framerate = grabber.getVideoFrameRate();// 帧率
int bitrate = grabber.getVideoBitrate();// 比特率
// 音频参数
// 想要录制音频,这三个参数必须有:audioChannels > 0 && audioBitrate > 0 && sampleRate > 0
int audioChannels = grabber.getAudioChannels();
int audioBitrate = grabber.getAudioBitrate();
int sampleRate = grabber.getSampleRate();
// 录制/推流器
record = new FFmpegFrameRecorderPlus(out, width, height);
record.setVideoOption("crf", "18");
record.setGopSize(1);
record.setFrameRate(framerate);
record.setVideoBitrate(bitrate);
record.setAudioChannels(audioChannels);
record.setAudioBitrate(audioBitrate);
record.setSampleRate(sampleRate);
AVFormatContext fc = null;
//rtmp和flv
if (hasRTMPFLV(out)) {
// 封装格式flv,并使用h264和aac编码
record.setFormat("flv");
record.setVideoCodec(AV_CODEC_ID_H264);
record.setAudioCodec(AV_CODEC_ID_AAC);
if(hasRTMPFLV(src)) {
fc = grabber.getFormatContext();
}
}else if(hasMP4(out)){//MP4
record.setFormat("mp4");
record.setVideoCodec(AV_CODEC_ID_H264);
record.setAudioCodec(AV_CODEC_ID_AAC);
}
record.start(fc);
return this;
}
/**
* 转封装
*
* @throws IOException
*/
public void forward() throws IOException {
long starttime = System.currentTimeMillis();
System.out.println("开始循环读取时间:" + starttime);
long err_index = 0;// 采集或推流失败次数
for (long i = 0; err_index < Long.MAX_VALUE;) {
AVPacket pkt = null;
try {
pkt = grabber.grabPacket();
System.err.println("采集到的");
if (pkt == null || pkt.size() <= 0 || pkt.data() == null) {// 空包结束
break;
}
System.err.println("准备推:" + pkt.stream_index());
if (record.recordPacket(pkt)) {
System.err.println("推送成功:" + i++);
}
av_free_packet(pkt);
} catch (Exception e) {// 推流失败
err_index++;
System.out.println("采集失败:" + err_index);
continue;
} catch (org.bytedeco.javacv.FrameRecorder.Exception e) {
err_index++;
System.out.println("推流失败:" + err_index);
continue;
} finally {
//
}
System.out.println("时间:" + (System.currentTimeMillis() - starttime));
}
}
/**
* 转码
*
* @throws IOException
*/
public void codec() throws IOException {
long starttime = System.currentTimeMillis();
System.out.println("开始循环读取时间:" + starttime);
long err_index = 0;// 采集或推流失败次数
for (; err_index < Long.MAX_VALUE;) {
try {
Frame pkt = grabber.grabFrame();
if (pkt == null) {// 空包结束
record.stop();
break;
}
record.record(pkt);
} catch (Exception e) {// 推流失败
record.stop();
err_index++;
System.out.println("采集失败:" + err_index);
throw e;
} catch (IOException e) {
record.stop();
err_index++;
System.out.println("录制失败:" + err_index);
throw e;
}
// System.out.println("推流后时间:"+(System.currentTimeMillis()-starttime));
}
}
/**
* 延迟录制
*
* @param starttime
* -开始录制的时间
* @param duration
* -持续时长
* @throws IOException
* @throws InterruptedException
*/
public void record(long starttime, long duration) throws IOException, InterruptedException {
long err_index = 0;// 采集或推流失败次数
long now = System.currentTimeMillis();
long delay = starttime - now;
if (starttime > 0 && delay > 0) {
System.out.println("进入休眠,等待开始时间,需要等待 " + delay / 1000 + " 秒");
// 休眠
Thread.sleep(delay);
}
for (; (now - starttime) <= duration; now = System.currentTimeMillis()) {
try {
Frame pkt = grabber.grabFrame();
if (pkt == null) {// 采集空包结束
if (err_index > 3) {// 超过三次则终止录制
break;
}
err_index++;
continue;
}
record.record(pkt);
} catch (Exception e) {// 采集失败
record.stop();
throw e;
} catch (IOException e) {// 录制失败
record.stop();
throw e;
}
}
record.stop();
}
/**
* 立即录制
*
* @param duration
* -持续时长
* @throws IOException
* @throws InterruptedException
*/
public void record(long duration) throws IOException, InterruptedException {
long err_index = 0;// 采集或推流失败次数
long now = System.currentTimeMillis();
long starttime = now;
for (; (now - starttime) <= duration; now = System.currentTimeMillis()) {
System.out.println("持续录制" + now);
try {
Frame pkt = grabber.grabFrame();
if (pkt == null) {// 采集空包结束
if (err_index > 3) {// 超过三次则终止录制
break;
}
err_index++;
continue;
}
record.record(pkt);
} catch (Exception e) {// 推流失败
record.stop();
throw e;
} catch (IOException e) {
record.stop();
throw e;
}
}
record.stop();
}
/**
* 开始
*
* @return
*/
public JavaCVRecord start() {
if (cuThread == null) {
String name=THREAD_NAME+nextThreadNum();
cuThread = new RecordThread(name,grabber, record, 1);
cuThread.setDaemon(false);
cuThread.start();
} else {
cuThread.reset(grabber, record);// 重置
cuThread.go();
}
return this;
}
/**
* 重新开始,实际链式调用了:from(src).to(out).start()
*
* @return
* @throws Exception
* @throws IOException
*/
public Recorder restart() throws Exception, IOException {
return from(src).to(out).start();
}
/**
* 暂停
*
* @return
*/
public JavaCVRecord pause() {
if (cuThread != null && cuThread.isAlive()) {
cuThread.pause();
}
return this;
}
/**
* 从暂停中恢复
*
* @return
*/
public JavaCVRecord carryon() {
if (cuThread != null && cuThread.isAlive()) {
cuThread.carryon();
}
return this;
}
/**
* 停止录制线程和录制器
*
* @return
*/
public JavaCVRecord stop() {
if (cuThread != null && cuThread.isAlive()) {
cuThread.over();// 先结束线程,然后终止录制
}
return this;
}
@Override
public boolean alive() {
/**用于空闲定时器定期执行*/
return cuThread.isAlive();
}
}
package cc.eguid.cv.videoRecorder.recorder;
import java.io.IOException;
import org.bytedeco.javacv.FrameGrabber.Exception;
/**
* 录制器接口
*
* @author eguid
*
*/
public interface Recorder {
/**
* 设置视频源
*
* @param src
* 视频源(文件或流媒体拉流地址)
* @return
*/
Recorder from(String src) throws Exception;
/**
* 设置输出文件或推流到流媒体服务
*
* @param out
* 输出文件或流媒体服务推流地址
* @return
* @throws IOException
*/
Recorder to(String out) throws IOException;
/**
* 转发源视频到输出(复制)
*
* @param src
* 视频源
* @param out
* 输出文件或流媒体服务推流地址
* @return
* @throws IOException
*/
Recorder stream(String src, String out) throws IOException;
/**
* 设置音频参数
*
* @param audioChannels
* @param audioBitrate
* @param sampleRate
* @return
*/
Recorder audioParam(int audioChannels, int audioBitrate, int sampleRate);
/**
* 设置视频参数
*
* @param width
* @param height
* @param framerate
* @param bitrate
* @return
*/
Recorder videoParam(Integer width, Integer height, int framerate, int bitrate);
/**
* 开始录制
*
* @return
*/
Recorder start();
/**
* 暂停录制
*
* @return
*/
Recorder pause();
/**
* 继续录制(从暂停中恢复)
*
* @return
*/
Recorder carryon();
/**
* 停止录制
*
* @return
*/
Recorder stop();
/**
* 用于保活
* @return
*/
boolean alive();
}
package cc.eguid.cv.videoRecorder.work;
import java.io.IOException;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import cc.eguid.cv.videoRecorder.recorder.FFmpegFrameRecorderPlus;
/**
* 录制任务工作线程
* @author eguid
*
*/
public class RecordThread extends Thread {
/**开始状态*/
public static final int START_STATUS=1;
/**停止状态*/
public static final int STOP_STATUS=2;
/**暂停状态*/
public static final int PAUSE_STATUS=1;
protected FFmpegFrameGrabber grabber =null;
protected FFmpegFrameRecorderPlus record =null;
/**
* 运行状态,0-初始状态,1-运行,2-停止
*/
protected volatile int status=0;
protected volatile int pause=0;//是否暂停,1-暂停
protected int err_stop_num=3;//默认错误数量达到三次终止录制
/**
*
* @param name -线程名称
* @param grabber -抓取器
* @param record -录制器
* @param err_stop_num 允许的错误次数,超过该次数后即停止任务
*/
public RecordThread(String name,FFmpegFrameGrabber grabber, FFmpegFrameRecorderPlus record,Integer err_stop_num) {
super(name);
this.grabber = grabber;
this.record = record;
if(err_stop_num!=null) {
this.err_stop_num=err_stop_num;
}
}
/**
* 运行过一次后必须进行重置参数和运行状态
*/
public void reset(FFmpegFrameGrabber grabber, FFmpegFrameRecorderPlus record) {
this.grabber = grabber;
this.record = record;
}
public int getErr_stop_num() {
return err_stop_num;
}
public void setErr_stop_num(int err_stop_num) {
this.err_stop_num = err_stop_num;
}
public FFmpegFrameGrabber getGrabber() {
return grabber;
}
public void setGrabber(FFmpegFrameGrabber grabber) {
this.grabber = grabber;
}
public FFmpegFrameRecorderPlus getRecord() {
return record;
}
public void setRecord(FFmpegFrameRecorderPlus record) {
this.record = record;
}
public int getStatus() {
return status;
}
@Override
public void run() {
go();
for(;;) {
if(status==2) {
System.out.println("工作线程进入等待状态");
synchronized (this){
try {
wait();
}catch(InterruptedException e) {
}
}
System.out.println("工作线程唤醒");
continue;
}
//核心任务循环
mainLoop();
}
}
/**
* 核心转换处理循环
*/
private void mainLoop() {
long startime=System.currentTimeMillis();
long err_index = 0;//采集或推流失败次数
long frame_index=0;
int pause_num=0;//暂停次数
try {
for(;status==START_STATUS;frame_index++) {
Frame pkt=grabber.grabFrame();
if(pause==1) {//暂停状态
pause_num++;
continue;
}
if(pkt==null) {//采集空包结束
if(err_index>err_stop_num) {//超过三次则终止录制
System.err.println(getName()+"工作线程采集视频帧连续"+err_index+"次空包,本次任务终止");
break;
}
err_index++;
continue;
}
record.record(pkt);
}
}catch (Exception e) {//推流失败
status=STOP_STATUS;//到这里表示已经停止了
System.err.println("异常导致停止录像,详情:"+e.getMessage());
}finally {
status=STOP_STATUS;
stopRecord();
System.out.println(getName()+"工作线程的录像任务已结束,持续时长:"+(System.currentTimeMillis()-startime)/1000+"秒,共录制:"+frame_index+"帧,遇到的错误数:"+err_index+",录制期间共暂停次数:"+pause_num);
}
}
/**
* 停止录制
*/
private void stopRecord() {
try {
if(grabber!=null) {
grabber.close();
}
if(record!=null) {
record.stop();
}
} catch (IOException e) {
}
}
/**
* 暂停
*/
public void pause() {
pause=PAUSE_STATUS;
}
/**
* 继续(从暂停中恢复)
*/
public void carryon() {
if(pause==PAUSE_STATUS) {//如果暂停状态为1才允许
pause=0;
status=START_STATUS;
}
}
/**
* 结束
*/
public void over() {
status=STOP_STATUS;
}
/**
* 开始(如果线程处于等待状态则唤醒)
*/
public void go() {
if(status==0) {//如果初始状态,则设置为开始状态1
status=START_STATUS;
System.out.println("开启工作线程");
}
if(status==STOP_STATUS) {//如果是停止状态,设置为开始状态1,并唤醒线程
this.status=START_STATUS;
synchronized(this) {
notify();
}
System.out.println("唤醒工作线程");
}
}
}
package cc.eguid.cv.videoRecorder;
/**
* 没有找到对应的编解码器
* @author eguid
*
*/
public class CodecNotFoundExpception extends RuntimeException{
private static final long serialVersionUID = 1L;
public CodecNotFoundExpception(String message) {
super(message);
}
}
package cc.eguid.cv.videoRecorder;
import static org.bytedeco.javacpp.avutil.AV_PIX_FMT_BGR24;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.avutil.AVFrame;
/**
* 视频帧抓取
*
* @author eguid
*
*/
public class FFmpegVideoImageGrabber extends GrabberTmplate {
ImageViewer viwer=null;
int index=0;
public ImageViewer getViwer() {
return viwer;
}
public void setViwer(ImageViewer viwer) {
this.viwer = viwer;
}
@Override
protected ByteBuffer saveFrame(AVFrame pFrame, int width, int height) {
BytePointer data = pFrame.data(0);
int size = width * height * 3;
ByteBuffer buf = data.position(0).limit(size).asBuffer();
if(viwer==null) {
viwer=new ImageViewer(width,height, BufferedImage.TYPE_3BYTE_BGR);
}else {
if(index==0) {
index++;
viwer.setSize(width, height);
}
}
viwer.show(buf,width,height);
return buf;
}
public ByteBuffer grabBuffer() throws IOException {
return grabBuffer(url);
}
public ByteBuffer grabBuffer(String url) throws IOException {
return grabBuffer(url, null);
}
public ByteBuffer grabBuffer(String url, Integer fmt) throws IOException {
ByteBuffer buf = null;
if (validateAndInit(url, fmt)) {
buf = grabVideoFrame(url, this.fmt);
}
return buf;
}
/*
* 验证并初始化
*
* @param url
*
* @param fmt
*
* @return
*/
private boolean validateAndInit(String url, Integer fmt) {
if (url == null) {
throw new IllegalArgumentException("Didn't open video file");
}
if (fmt == null) {
this.fmt = AV_PIX_FMT_BGR24;
}
return true;
}
public BufferedImage grabBufferImage() throws IOException {
return grabBufferImage(url, null);
}
public BufferedImage grabBufferImage(String url) throws IOException {
return grabBufferImage(url, null);
}
public BufferedImage grabBufferImage(String url, Integer fmt) throws IOException {
BufferedImage image = null;
ByteBuffer buf = grabBuffer(url, fmt);
image = JavaImgConverter.BGR2BufferedImage(buf, width, height);
return image;
}
/**
* bgr图像帧转BufferedImage图片
*
* @param pFrame
* -bgr图像帧
* @param width
* -宽度
* @param height
* -高度
* @return
*/
protected BufferedImage frame2BufferImage(AVFrame pFrame, int width, int height) {
BytePointer data = pFrame.data(0);
int size = width * height * 3;
ByteBuffer buf = data.position(0).limit(size).asBuffer();
return JavaImgConverter.BGR2BufferedImage(buf, width, height);
}
private String url;// 视频地址
private Integer fmt;// 图像数据结构
public FFmpegVideoImageGrabber() {
}
public FFmpegVideoImageGrabber(String url) {
this.url = url;
}
public FFmpegVideoImageGrabber(String url, Integer fmt) {
super();
this.url = url;
this.fmt = fmt;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getFmt() {
return fmt;
}
public void setFmt(int fmt) {
this.fmt = fmt;
}
}
package cc.eguid.cv.videoRecorder;
/**
* 文件或流无法打开
* @author eguid
*
*/
public class FileNotOpenException extends RuntimeException{
public FileNotOpenException(String message) {
super(message);
}
private static final long serialVersionUID = 1L;
}
package cc.eguid.cv.videoRecorder;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class GrabberTest {
/**
* test
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// ByteBuffer buf=new VideoFrameGrabber("rtmp://live.hkstv.hk.lxdns.com/live/hks").grab();
// ByteBuffer buf=new VideoFrameGrabber().grabBuffer();
ImageViewer viwer=new ImageViewer(800,600, BufferedImage.TYPE_3BYTE_BGR);
FFmpegVideoImageGrabber grabber=new FFmpegVideoImageGrabber();
grabber.setViwer(viwer);
viwer.setGrabber(grabber);
// grabber.grab("rtmp://live.hkstv.hk.lxdns.com/live/hks");
// new FFmpegVideoImageGrabber("http://live.hkstv.hk.lxdns.com/live/hks/playlist.m3u8").grabBuffer();
// JavaImgConverter.viewBGR(1280, 720, buf);
// BufferedImage image=JavaImgConverter.BGR2BufferedImage(buf, 1280,720);
// JavaImgConverter.viewImage(image);
}
}
package cc.eguid.cv.videoRecorder;
import static org.bytedeco.javacpp.avcodec.av_free_packet;
import static org.bytedeco.javacpp.avcodec.avcodec_close;
import static org.bytedeco.javacpp.avcodec.avcodec_decode_video2;
import static org.bytedeco.javacpp.avcodec.avcodec_find_decoder;
import static org.bytedeco.javacpp.avcodec.avcodec_open2;
import static org.bytedeco.javacpp.avcodec.avpicture_fill;
import static org.bytedeco.javacpp.avcodec.avpicture_get_size;
import static org.bytedeco.javacpp.avformat.av_read_frame;
import static org.bytedeco.javacpp.avformat.av_register_all;
import static org.bytedeco.javacpp.avformat.avformat_close_input;
import static org.bytedeco.javacpp.avformat.avformat_find_stream_info;
import static org.bytedeco.javacpp.avformat.avformat_network_init;
import static org.bytedeco.javacpp.avformat.avformat_open_input;
import static org.bytedeco.javacpp.avutil.AVMEDIA_TYPE_VIDEO;
import static org.bytedeco.javacpp.avutil.av_frame_alloc;
import static org.bytedeco.javacpp.avutil.av_free;
import static org.bytedeco.javacpp.avutil.av_malloc;
import static org.bytedeco.javacpp.swscale.SWS_BILINEAR;
import static org.bytedeco.javacpp.swscale.sws_freeContext;
import static org.bytedeco.javacpp.swscale.sws_getContext;
import static org.bytedeco.javacpp.swscale.sws_scale;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.DoublePointer;
import org.bytedeco.javacpp.PointerPointer;
import org.bytedeco.javacpp.avcodec.AVCodec;
import org.bytedeco.javacpp.avcodec.AVCodecContext;
import org.bytedeco.javacpp.avcodec.AVPacket;
import org.bytedeco.javacpp.avcodec.AVPicture;
import org.bytedeco.javacpp.avformat.AVFormatContext;
import org.bytedeco.javacpp.avformat.AVStream;
import org.bytedeco.javacpp.avutil.AVDictionary;
import org.bytedeco.javacpp.avutil.AVFrame;
import org.bytedeco.javacpp.swscale.SwsContext;
public abstract class GrabberTmplate {
static {
// Register all formats and codecs
av_register_all();
avformat_network_init();
}
protected int width;//宽度
protected int height;//高度
/**
* 打开视频流
* @param url -url
* @return
* @throws FileNotOpenException
*/
protected AVFormatContext openInput(String url) throws FileNotOpenException{
AVFormatContext pFormatCtx = new AVFormatContext(null);
if(avformat_open_input(pFormatCtx, url, null, null)==0) {
return pFormatCtx;
}
throw new FileNotOpenException("Didn't open video file");
}
/**
* 检索流信息
* @param pFormatCtx
* @return
*/
protected AVFormatContext findStreamInfo(AVFormatContext pFormatCtx) throws StreamInfoNotFoundException{
if (avformat_find_stream_info(pFormatCtx, (PointerPointer<?>) null)>= 0) {
return pFormatCtx;
}
throw new StreamInfoNotFoundException("Didn't retrieve stream information");
}
/**
* 获取第一帧视频位置
* @param pFormatCtx
* @return
*/
protected int findVideoStreamIndex(AVFormatContext pFormatCtx) {
int i = 0, videoStream = -1;
for (i = 0; i < pFormatCtx.nb_streams(); i++) {
AVStream stream=pFormatCtx.streams(i);
AVCodecContext codec=stream.codec();
if (codec.codec_type() == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
break;
}
}
return videoStream;
}
/**
* 指定视频帧位置获取对应视频帧
* @param pFormatCtx
* @param videoStream
* @return
*/
protected AVCodecContext findVideoStream(AVFormatContext pFormatCtx ,int videoStream)throws StreamNotFoundException {
if(videoStream >=0) {
// Get a pointer to the codec context for the video stream
AVStream stream=pFormatCtx.streams(videoStream);
AVCodecContext pCodecCtx = stream.codec();
return pCodecCtx;
}
throw new StreamNotFoundException("Didn't open video file");
}
/**
* 查找并尝试打开解码器
* @return
*/
protected AVCodecContext findAndOpenCodec(AVCodecContext pCodecCtx) {
// Find the decoder for the video stream
AVCodec pCodec = avcodec_find_decoder(pCodecCtx.codec_id());
if (pCodec == null) {
System.err.println("Codec not found");
throw new CodecNotFoundExpception("Codec not found");
}
AVDictionary optionsDict = null;
// Open codec
if (avcodec_open2(pCodecCtx, pCodec, optionsDict) < 0) {
System.err.println("Could not open codec");
throw new CodecNotFoundExpception("Could not open codec"); // Could not open codec
}
return pCodecCtx;
}
/**
* 抓取视频帧(默认跳过音频帧和空帧)
* @param url
* @param fmt - 像素格式,比如AV_PIX_FMT_BGR24
* @return
* @throws IOException
*/
public ByteBuffer grabVideoFrame(String url,int fmt) throws IOException {
// Open video file
AVFormatContext pFormatCtx=openInput(url);
// Retrieve stream information
pFormatCtx=findStreamInfo(pFormatCtx);
// Dump information about file onto standard error
//av_dump_format(pFormatCtx, 0, url, 0);
//Find a video stream
int videoStream=findVideoStreamIndex(pFormatCtx);
AVCodecContext pCodecCtx =findVideoStream(pFormatCtx,videoStream);
// Find the decoder for the video stream
pCodecCtx= findAndOpenCodec(pCodecCtx);
// Allocate video frame
AVFrame pFrame = av_frame_alloc();
//Allocate an AVFrame structure
AVFrame pFrameRGB = av_frame_alloc();
width = pCodecCtx.width();
height = pCodecCtx.height();
pFrameRGB.width(width);
pFrameRGB.height(height);
pFrameRGB.format(fmt);
// Determine required buffer size and allocate buffer
int numBytes = avpicture_get_size(fmt, width, height);
SwsContext sws_ctx = sws_getContext(width, height, pCodecCtx.pix_fmt(), width, height,fmt, SWS_BILINEAR, null, null, (DoublePointer) null);
BytePointer buffer = new BytePointer(av_malloc(numBytes));
// Assign appropriate parts of buffer to image planes in pFrameRGB
// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
// of AVPicture
avpicture_fill(new AVPicture(pFrameRGB), buffer, fmt, width, height);
AVPacket packet = new AVPacket();
int[] frameFinished = new int[1];
try {
// Read frames and save first five frames to disk
while (av_read_frame(pFormatCtx, packet) >= 0) {
// Is this a packet from the video stream?
if (packet.stream_index() == videoStream) {
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, packet);
// Did we get a video frame?
if (frameFinished != null&&frameFinished[0] > 0) {
// Convert the image from its native format to BGR
sws_scale(sws_ctx, pFrame.data(), pFrame.linesize(), 0, height, pFrameRGB.data(),pFrameRGB.linesize());
//Convert BGR to ByteBuffer
saveFrame(pFrameRGB, width, height);
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(packet);
}
return null;
}finally {
//Don't free buffer
// av_free(buffer);
av_free(pFrameRGB);// Free the RGB image
av_free(pFrame);// Free the YUV frame
sws_freeContext(sws_ctx);//Free SwsContext
avcodec_close(pCodecCtx);// Close the codec
avformat_close_input(pFormatCtx);// Close the video file
}
}
/**
* BGR图像帧转字节缓冲区(BGR结构)
*
* @param pFrame
* -bgr图像帧
* @param width
* -宽度
* @param height
* -高度
* @return
* @throws IOException
*/
abstract ByteBuffer saveFrame(AVFrame pFrameRGB, int width, int height);
}
package cc.eguid.cv.videoRecorder;
/**
* 图像像素算法
*
* @author eguid
*
*/
public class ImagePixelAlgorithm {
public static final ThreadLocal<byte[]> sharedArray = new ThreadLocal<byte[]>() {
byte[] sharedArr;
protected byte[] initialValue() {
if(sharedArr==null) {
sharedArr = new byte[3];
}
return sharedArr;
};
};
/*
* 像素转换算法
*/
/**
* rgb整型值转换为rgb数组
*
* @param rgb -rgb整型值
* @param arr -rgb数组
*/
public static void convertRGB(int rgb, byte[] arr) {
convertRGB(rgb,arr,0);
}
/**
* rgb整型值转换为rgb数组
*
* @param rgb -rgb整型值
* @param arr -rgb数组(0-红,1-绿,2-蓝)
* @param index -rgb数组开始位置(必须保证数组大小大于等于index+2,否则数组溢出)
*/
public static void convertRGB(int rgb, byte[] arr,int index) {
byte r = (byte)((rgb & 0xff0000) >> 16);
byte g = (byte)((rgb & 0xff00) >> 8);
byte b = (byte) (rgb & 0xff);
arr[index] = r;
arr[index+1] = g;
arr[index+2] = b;
}
/**
* rgb整型值转换为rgb数组(0-红,1-绿,2-蓝)
* @param rgb
* @return
*/
public static byte[] convertRGB(int rgb) {
byte[] arr=sharedArray.get();
byte r = (byte)((rgb & 0xff0000) >> 16);
byte g = (byte)((rgb & 0xff00) >> 8);
byte b =(byte)( (rgb & 0xff));
arr[0]=r;arr[1]=g;arr[2]=b;
return arr.clone();
}
/**
* 转换为单个RGB整型值
*
* @param r
* -红
* @param g
* -绿
* @param b
* - 蓝
* @return
*/
public static int getRGB(int r, int g, int b) {
int rgb = b & 0xff | (g & 0xff) << 8 | (r & 0xff) << 16 | 0xff000000;
return rgb;
}
/**
* 转换为单个RGB整型值
*
* @param rgbarr
* -像素数组(数组各位置表示像素:0-红,1-绿,2-蓝)
* @return
*/
public static int getRGB(int[] rgbarr) {
int rgb = ((int) rgbarr[0]) & 0xff | (((int) rgbarr[1]) & 0xff) << 8 | (((int) rgbarr[2]) & 0xff) << 16
| 0xff000000;
return rgb;
}
/**
* 转换为单个RGB整型值
*
* @param rgbarr
* -像素数组(数组各位置表示像素:0-红,1-绿,2-蓝)
* @param index -起始位置(必须保证数组大小大于等于index+2,否则数组溢出)
* @return
*/
public static int getRGB(byte[] rgbarr,int index) {
int rgb = ((int) rgbarr[index]) & 0xff | (((int) rgbarr[index+1]) & 0xff) << 8 | (((int) rgbarr[index+2]) & 0xff) << 16
| 0xff000000;
return rgb;
}
}
package cc.eguid.cv.videoRecorder;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.MouseInputAdapter;
/**
* 图像浏览器
*
* @author eguid
*
*/
public class ImageViewer extends JFrame {
/**
* 默认像素格式
*/
private int pixelfmt = BufferedImage.TYPE_3BYTE_BGR;
private JLabel label;
JDialog dialog=null;
private ImageIcon icon = null;
BufferedImage image=null;
ByteBuffer buf=null;
private FFmpegVideoImageGrabber grabber;//视频图像抓取器
public ImageViewer(int width, int height, int pixelfmt) {
this.pixelfmt = pixelfmt;
super.setSize(width, height);
label = new JLabel();
init();
}
private void init() {
setTitle("实时图像预览");
setLayout(new BorderLayout());
initMenu();
icon = new ImageIcon();
label.setSize(getWidth(), getHeight());
label.setIcon(icon);
add(label);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
setLocationRelativeTo(null);
Timer timer=new Timer(true);
timer.schedule(new TimerTask() {
@Override
public void run() {
label.repaint();
}
}, 0, 1000/25);
}
private void initMenu() {
//菜单栏
JMenuBar bar = new JMenuBar();
//主菜单
JMenu menu=new JMenu("打开");
menu.addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
showDialog();
}
});
bar.add(menu);
setJMenuBar(bar);
}
@Override
public void setSize(int width, int height) {
if(width!=getWidth()&&height!=getHeight()) {
super.setSize(width, height);
label.setSize(width,height);
}
}
private void showDialog(){
this.setVisible(false);
if(dialog==null) {
dialog = new JDialog(this, true);
dialog.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
dialog.setSize(400,150);
dialog.setTitle("打开媒体");
dialog.setLayout(new BorderLayout());
dialog.add(getNamePwdPandel(),BorderLayout.CENTER);
dialog.setLocationRelativeTo(this);
}
dialog.setVisible(true); //显示对话框,窗口阻塞,不往下执行,只有等到对话框关闭了才往下执行。
}
private Component getNamePwdPandel() {
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout(FlowLayout.LEFT));
JLabel jlabel = new JLabel("输入网络视频源URL");
JTextField srcText = new JTextField(30);
JButton button=new JButton("播放");
button.addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
String src=srcText.getText();
if(src!=null&&src.length()>0&&!"".equals(src.trim())) {
dialog.setVisible(false);
setVisible(true);
grabber.setUrl(src);
try {
grabber.grabBuffer();
} catch (IOException e1) {
}
}
}
});
panel.add(jlabel);
panel.add(srcText);
panel.add(button);
return panel;
}
public void show(BufferedImage img) {
icon.setImage(img);
// label.setSize(img.getWidth(), img.getHeight());
// label.repaint();
}
public void show(ByteBuffer src) {
BufferedImage img = BGR2BufferedImage(src,getWidth(), getHeight());
show(img);
}
public void show(ByteBuffer src,int width,int height) {
// BufferedImage image = new BufferedImage(width, height, pixelfmt);
BufferedImage img = BGR2BufferedImage(src, width, height);
show(img);
}
/**
* 24位BGR转BufferedImage
*
* @param src
* -源数据
* @param width
* -宽度
* @param height-高度
* @return
*/
public BufferedImage BGR2BufferedImage(ByteBuffer src, int width, int height) {
if(image==null) {
image = new BufferedImage(width, height, pixelfmt);
Raster ra = image.getRaster();
DataBufferByte db = (DataBufferByte) ra.getDataBuffer();
buf=ByteBuffer.wrap(db.getData()).put(src);
}else {
buf.flip();
buf.put(src);
}
return image;
}
public void setGrabber(FFmpegVideoImageGrabber grabber) {
this.grabber=grabber;
}
}
package cc.eguid.cv.videoRecorder;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Base64;
import java.util.Base64.Encoder;
import java.util.Timer;
import java.util.TimerTask;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
/**
* java图像转换器(将ffmpeg图像转为java图像和base64)
*
* @author Administrator
*
*/
public class JavaImgConverter {
public static void main(String[] args) {
// for(int i=0;i<3;i++) {
demoview();
// }
// int width=800,height=600;
// int[] allrgb=new int[width*height];
// int count=0;
// for (int i = 0; i < width; i++) {
// for (int j = 0; j < height; j++) {
// int rgb = ImagePixelAlgorithm.getRGB((int) (Math.random() * 255),(int) (Math.random() * 255),(int) (Math.random() * 255));
// allrgb[count++]=rgb;
// }
// }
// System.err.println(count);
// viewRGB(width,height,allrgb);
}
public static int getRGB(int[] rgbarr) {
int rgb = ((int) rgbarr[0]) & 0xff | (((int) rgbarr[1]) & 0xff) << 8 | (((int) rgbarr[2]) & 0xff) << 16
| 0xff000000;
return rgb;
}
private static int createRandomRgb() {
int[] rgbarr = new int[3];
rgbarr[0] = (int) (Math.random() * 255);
rgbarr[1] = (int) (Math.random() * 255);
rgbarr[2] = (int) (Math.random() * 255);
return getRGB(rgbarr);
}
public static void demoview() {
int width = 800, height = 600;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
int rgb = createRandomRgb();
image.setRGB(i, j, rgb);
}
}
JLabel label = new JLabel();
label.setSize(width, height);
label.setIcon(new ImageIcon(image));
JFrame frame = new JFrame();
frame.setSize(width, height);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(label);
frame.setVisible(true);
Timer timer=new Timer("定时刷新", true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
int rgb =createRandomRgb();
image.setRGB(i, j, rgb);
}
}
label.repaint();
}
}, 100, 1000/25);
}
/**
* BGR图像源数据转base64
* @param src
* @param width
* @param height
* @param format
* @return
* @throws IOException
*/
public static String imagedataBGR2Base64(ByteBuffer src,int width,int height,String format) throws IOException {
BufferedImage image=BGR2BufferedImage(src,width,height);
String base64=bufferedImage2Base64(image, format);
return base64;
}
/**
* 使用窗口显示BGR图像
* @param width
* @param height
* @param src
*/
public static void viewBGR(int width,int height,ByteBuffer src) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
// image.setRGB(0, 0, width, height, rgbarr, 0,height);
Raster ra = image.getRaster();
DataBuffer out = ra.getDataBuffer();
DataBufferByte db=(DataBufferByte)out;
ByteBuffer.wrap(db.getData()).put(src);
viewImage(image);
}
/**
* 使用窗口显示RGB图像
* @param width
* @param height
* @param rgbarr -int
*/
public static void viewRGB(int[] rgbarr,int width,int height){
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
image.setRGB(0, 0, width, height, rgbarr, 0,height);
viewImage(image);
}
/**
* bufferedImage转base64
* @param format -格式(jpg,png等等)
* @return
* @throws IOException
*/
public static String bufferedImage2Base64(BufferedImage image, String format) throws IOException {
Encoder encoder = Base64.getEncoder();
ByteArrayOutputStream baos = new ByteArrayOutputStream();// 字节流
// baos.reset();
ImageIO.write(image, format, baos);// 写出到字节流
byte[] bytes=baos.toByteArray();
// 编码成base64
String jpg_base64 = encoder.encodeToString(bytes);
return jpg_base64;
}
/**
* 24位BGR转BufferedImage
* @param src -源数据
* @param width -宽度
* @param height-高度
* @return
*/
public static BufferedImage BGR2BufferedImage(ByteBuffer src,int width,int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
Raster ra = image.getRaster();
DataBuffer out = ra.getDataBuffer();
DataBufferByte db=(DataBufferByte)out;
ByteBuffer.wrap(db.getData()).put(src);
return image;
}
/**
* 24位整型BGR转BufferedImage
* @param src -源数据
* @param width -宽度
* @param height-高度
* @return
*/
public static BufferedImage BGR2BufferedImage(IntBuffer src,int width,int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
Raster ra = image.getRaster();
DataBuffer out = ra.getDataBuffer();
DataBufferInt db=(DataBufferInt)out;
IntBuffer.wrap(db.getData()).put(src);
return image;
}
/**
* 24位整型RGB转BufferedImage
* @param src -源数据
* @param width -宽度
* @param height-高度
* @return
*/
public static BufferedImage RGB2BufferedImage(IntBuffer src,int width,int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Raster ra = image.getRaster();
DataBuffer out = ra.getDataBuffer();
DataBufferInt db=(DataBufferInt)out;
IntBuffer.wrap(db.getData()).put(src);
return image;
}
/**
* 使用窗口显示BufferedImage图片
* @param image -BufferedImage
*/
public static void viewImage(BufferedImage image) {
int width=image.getWidth(),height=image.getHeight();
JLabel label = new JLabel();
label.setSize(width, height);
label.setIcon(new ImageIcon(image));
JFrame frame = new JFrame();
frame.setSize(width, height);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(label);
frame.setVisible(true);
}
public static void saveImage(BufferedImage image,String format,String file) throws IOException {
ImageIO.write(image, format, new File(file));
}
public static void saveImage(BufferedImage image,String format,File file) throws IOException {
ImageIO.write(image, format, file);
}
public static void saveImage(BufferedImage image,String format,OutputStream file) throws IOException {
ImageIO.write(image, format, file);
}
public static void saveImage(BufferedImage image,String format,ImageOutputStream file) throws IOException {
ImageIO.write(image, format, file);
}
}
package cc.eguid.cv.videoRecorder;
import java.io.IOException;
import cc.eguid.cv.videoRecorder.entity.RecordTask;
import cc.eguid.cv.videoRecorder.manager.DefaultTasksManager;
import cc.eguid.cv.videoRecorder.manager.TasksManager;
import cc.eguid.cv.videoRecorder.recorder.JavaCVRecord;
public class RecorderTest {
//单个测试
public static void testSingle(TasksManager manager) throws Exception {
System.out.println("最后一个测试");
String src="rtmp://media3.sinovision.net:1935/live/livestream",out="eguid.flv";
RecordTask task=manager.createRcorder(src, out);
manager.start(task);
Thread.sleep(5*1000);
System.out.println("最后一个,当前任务数量:"+manager.list().size());
manager.stop(task);
}
//多个同时测试
public static void test3() throws Exception {
String src="rtmp://media3.sinovision.net:1935/live/livestream",out="test";
TasksManager manager=new DefaultTasksManager(10);
RecordTask[] tasks =new RecordTask[20] ;
// //开始10个
for(int i=1;i<20;i++) {
String file=out+i+".mp4";
RecordTask task=manager.createRcorder(src, file);
System.err.println("初始化任务,任务详情:"+task+",输出位置:"+file);
// manager.start(task);
tasks[i]=task;
}
//
for(int i=1;i<20;i++) {
boolean ret=manager.start(tasks[i]);
System.err.println("启动:"+i+(ret?",成功":",失败"));
}
//
System.err.println("当前任务数量:"+manager.list().size());
Thread.sleep(5*1000);
// //暂停全部
for(RecordTask task:manager.list()) {
manager.pause(task);
}
Thread.sleep(5*1000);
//恢复全部
for(RecordTask task:manager.list()) {
manager.carryon(task);
}
//停止全部
for(RecordTask task:manager.list()) {
manager.stop(task);
}
testSingle(manager);//增加一个测试看看历史任务
}
public static void test2() throws org.bytedeco.javacv.FrameGrabber.Exception, IOException, InterruptedException {
// Recorder cv1=new
// JavaCVRecord().from("rtmp://media3.sinovision.net:1935/live/livestream").audioParam(2,
// 128*1000, 48*1000).
// to("test.mp4");
// JavaCVRecord cv2=new
// JavaCVRecord().from("rtmp://media3.sinovision.net:1935/live/livestream")
// .to("test1.mp4");
// JavaCVRecord cv3=new
// JavaCVRecord().from("rtmp://media3.sinovision.net:1935/live/livestream")
// .to("test2.mp4");
// JavaCVRecord cv4=new
// JavaCVRecord().from("rtmp://media3.sinovision.net:1935/live/livestream")
// .to("test3.mp4");
// cvrecord.stream("rtmp://media3.sinovision.net:1935/live/livestream","eguid.mp4");
// JavaCVRecord cvrecord=new
// JavaCVRecord("rtmp://media3.sinovision.net:1935/live/livestream","rtmp://106.14.182.20:1935/rtmp/eguid",300,200);
JavaCVRecord record = new JavaCVRecord();
// record.from("rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov").to("rtmp://eguid.cc:1935/rtmp/eguid");
// record.forward();
// cvrecord.stream().forward();//转封装
record.from("rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov").videoParam(200, 100, 30, 10000).to("eguid.mp4").start();
// cv1.start();
// cv2.start();
// cv3.start();
// cv4.start();
Thread.sleep(5000);
record.stop();
// Thread.sleep(10000);
// record.from("rtmp://media3.sinovision.net:1935/live/livestream").to("eguid1.mp4").start();
// Thread.sleep(5000);
// record.stop();
// cv1.pause();
// Thread.sleep(10000);
// cv1.carryon();
// Thread.sleep(3000);
// cv1.stop();//停止
// Thread.sleep(2000);
// cv1.restart();
// cv2.start();
// cv3.start();
// cv4.start();
// Thread.sleep(5000);
// cv1.stop();
// cv2.stop();
// cv3.stop();
// cv4.stop();
}
public static void main(String[] args) throws Exception {
test2();
}
}
package cc.eguid.cv.videoRecorder;
/**
* 无法检索流信息
* @author eguid
*
*/
public class StreamInfoNotFoundException extends RuntimeException {
public StreamInfoNotFoundException(String message) {
super(message);
}
private static final long serialVersionUID = 1L;
}
package cc.eguid.cv.videoRecorder;
/**
* 无法检索到流
* @author eguid
*
*/
public class StreamNotFoundException extends RuntimeException {
public StreamNotFoundException(String message) {
super(message);
}
private static final long serialVersionUID = 1L;
}
package cc.eguid.cv.videoRecorder;
import static org.bytedeco.javacpp.avcodec.*;
import static org.bytedeco.javacpp.avformat.*;
import static org.bytedeco.javacpp.avutil.*;
import static org.bytedeco.javacpp.swscale.*;
import java.util.Map.Entry;
import org.bytedeco.javacpp.Pointer;
import org.bytedeco.javacpp.PointerPointer;
import org.bytedeco.javacpp.avcodec.AVCodec;
import org.bytedeco.javacpp.avcodec.AVCodecContext;
import org.bytedeco.javacpp.avcodec.AVPacket;
import org.bytedeco.javacpp.avformat.AVFormatContext;
import org.bytedeco.javacpp.avformat.AVOutputFormat;
import org.bytedeco.javacpp.avformat.AVStream;
import org.bytedeco.javacpp.avutil.AVDictionary;
/**
* 推流器
*
* @author Administrator
*
*/
public class TestPusher {
static {
// Register all formats and codecs
av_register_all();
avformat_network_init();
}
// 输入对应一个AVFormatContext,输出对应一个AVFormatContext
private AVOutputFormat ofmt = null;
// (Input AVFormatContext and Output AVFormatContext)
private AVFormatContext ifmt_ctx = new AVFormatContext(null);
private AVFormatContext ofmt_ctx = new AVFormatContext(null);
private AVPacket pkt = new AVPacket();
/**
* 打开视频流
*
* @param url
* -url
* @return
* @throws FileNotOpenException
*/
protected AVFormatContext openInput(String url) throws FileNotOpenException {
AVFormatContext pFormatCtx = new AVFormatContext(null);
if (avformat_open_input(pFormatCtx, url, null, null) == 0) {
return pFormatCtx;
}
throw new FileNotOpenException("Didn't open video file");
}
/**
* 检索流信息
*
* @param pFormatCtx
* @return
*/
protected AVFormatContext findStreamInfo(AVFormatContext pFormatCtx) throws StreamInfoNotFoundException {
// 解决rtsp默认udp丢帧导致检索时间过长问题
AVDictionary options = new AVDictionary();
av_dict_set(options, "rtsp_transport", "tcp", 0);
// 解决rtmp检索时间过长问题
// 限制最大读取缓存
pFormatCtx.probesize(500 * 1024);// 设置500k能保证高清视频也能读取到关键帧
// 限制avformat_find_stream_info最大持续时长,设置成3秒
pFormatCtx.max_analyze_duration(3 * AV_TIME_BASE);
if (avformat_find_stream_info(pFormatCtx, options) >= 0) {
return pFormatCtx;
}
throw new StreamInfoNotFoundException("Didn't retrieve stream information");
}
/**
* 获取第一帧视频位置
*
* @param pFormatCtx
* @return
*/
protected int findVideoStreamIndex(AVFormatContext pFormatCtx) {
int i = 0, videoStream = -1;
for (i = 0; i < pFormatCtx.nb_streams(); i++) {
AVStream stream = pFormatCtx.streams(i);
AVCodecContext codec = stream.codec();
if (codec.codec_type() == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
break;
}
}
return videoStream;
}
/**
* 指定视频帧位置获取对应视频帧
* @param pFormatCtx
* @param videoStream
* @return
*/
protected AVCodecContext findVideoStream(AVFormatContext pFormatCtx ,int videoStream)throws StreamNotFoundException {
if(videoStream >=0) {
// Get a pointer to the codec context for the video stream
AVStream stream=pFormatCtx.streams(videoStream);
AVCodecContext pCodecCtx = stream.codec();
return pCodecCtx;
}
throw new StreamNotFoundException("Didn't open video file");
}
/**
* 查找并尝试打开解码器
* @return
*/
protected AVCodecContext findAndOpenCodec(AVCodecContext pCodecCtx) {
// Find the decoder for the video stream
AVCodec pCodec = avcodec_find_decoder(pCodecCtx.codec_id());
if (pCodec == null) {
System.err.println("Codec not found");
throw new CodecNotFoundExpception("Codec not found");
}
AVDictionary optionsDict = null;
// Open codec
if (avcodec_open2(pCodecCtx, pCodec, optionsDict) < 0) {
System.err.println("Could not open codec");
throw new CodecNotFoundExpception("Could not open codec"); // Could not open codec
}
return pCodecCtx;
}
public void start() {
// AVDictionary metadata = new AVDictionary(null);
// for (Entry<String, String> e : this.metadata.entrySet()) {
// av_dict_set(metadata, e.getKey(), e.getValue(), 0);
// }
// /* write the stream header, if any */
// avformat_write_header(oc.metadata(metadata), options);
// av_dict_free(options);
}
public void push() {
}
public boolean push(String in_filename, String out_filename) {
int videoindex = -1;
ifmt_ctx = openInput(in_filename);
ifmt_ctx = findStreamInfo(ifmt_ctx);
videoindex = findVideoStreamIndex(ifmt_ctx);
//输入流的编解码器
AVCodecContext inCodecCtx =findVideoStream(ifmt_ctx,videoindex);
// Find the decoder for the video stream
inCodecCtx= findAndOpenCodec(inCodecCtx);
// 打印
av_dump_format(ifmt_ctx, 0, in_filename, 0);
//初始化输出流上下文
avformat_alloc_output_context2(ofmt_ctx, null,null,out_filename);
if(ofmt_ctx.isNull()) {
// 输出(Output)
if (avformat_alloc_output_context2(ofmt_ctx, null, "flv", out_filename) < 0) { // RTMP或flv
//初始化失败
return false;
}
}
//把输入流的编解码器复制给输出流
av_dump_format(ofmt_ctx, 0, out_filename, 1);
AVStream out_stream = avformat_new_stream(ofmt_ctx,null);
if(out_stream==null) {//fail to create new stream
return false;
}
av_dump_format(ofmt_ctx, 0, out_filename, 1);
// avcodec_parameters_copy(out_stream.codecpar(), ifmt_ctx.streams(videoindex).codecpar());
// if(avcodec_parameters_from_context(out_stream.codecpar(), inCodecCtx)<0) {
// return false;
// }
if (avcodec_copy_context(out_stream.codec(), inCodecCtx) < 0) {
return false;
}
// Dump Format------------------
av_dump_format(ofmt_ctx, 0, out_filename, 1);
// System.out.println("尝试打开输入文件");
/* open the output file, if needed */
// if(avio_open2(ifmt_ctx.pb(), in_filename, AVIO_FLAG_READ_WRITE, null, null) < 0) {
// System.out.println("打开输入流失败");
// return false;
// }
// System.out.println("已经打开了输入流");
//打开输出URL(Open output URL)
// System.out.println("尝试打开输出流");
// AVIOContext pb = new AVIOContext(null);
// if(avio_open(ofmt_ctx.pb(), out_filename, AVIO_FLAG_WRITE)<0) {
// System.out.println("打开输出流失败");
// //fail
// return false;
// }
System.out.println("已经打开输出流");
//
// 写文件头(Write file header)
// AVDictionary metadata = new AVDictionary(null);
/* write the stream header, if any */
System.out.println("写入头");
// AVDictionary options = new AVDictionary(null);
if (avformat_write_header(ofmt_ctx, (PointerPointer<?>)null) < 0) {
return false;
}
System.out.println("写入头完成");
AVStream in_stream;
try {
for (long errorindex=0; av_read_frame(ifmt_ctx, pkt) >= 0;) {
in_stream = ifmt_ctx.streams(pkt.stream_index());
out_stream = ofmt_ctx.streams(pkt.stream_index());
pkt.dts(av_rescale_q_rnd(pkt.dts(), in_stream.time_base(), out_stream.time_base(),(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)));
pkt.pos(-1);
if (av_interleaved_write_frame(ofmt_ctx, pkt) < 0) {
// 错误计数
errorindex++;
}
av_free_packet(pkt);
}
return true;
} finally {
// 写文件尾(Write file trailer)
av_write_trailer(ofmt_ctx);
avformat_close_input(ifmt_ctx);
/* close output */
avio_close(ofmt_ctx.pb());
avformat_free_context(ofmt_ctx);
}
}
public static void main(String[] args) {
// in_filename = "cuc_ieschool.mov";
// in_filename = "cuc_ieschool.mkv";
// in_filename = "cuc_ieschool.ts";
// in_filename = "cuc_ieschool.mp4";
// in_filename = "cuc_ieschool.h264";
String in_filename = "rtmp://media3.sinovision.net:1935/live/livestream";// 输入URL(Input file URL)
// in_filename = "shanghai03_p.h264";
String out_filename = "rtmp://106.14.182.20:1935/rtmp/tomcat";// 输出 URL(Output URL)[RTMP]
// out_filename = "rtp://233.233.233.233:6666";//输出 URL(Output URL)[UDP]
new TestPusher().push(in_filename, out_filename);
}
}
package cc.eguid.cv.videoRecorder;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 录制工作线程池,确实没啥用
* @author eguid
*
*/
public class WorkGroup {
public static final int KEEPALIVETIME = 30;//默认保活时间30分钟
public static final int DEFAULTMINSIZE=10;//默认最少保活10个工作线程
public static final int DEFAULTMAXSIZE=10;//默认一百个工作线程
private ThreadPoolExecutor pool = null;//工作线程池
private BlockingQueue<Runnable> workQueue;//空闲线程队列
protected int minPoolSize,maxPoolSize;//保活线程最少/最多数量
public WorkGroup(int size) {
if(size<1) {
minPoolSize=DEFAULTMINSIZE;
maxPoolSize=DEFAULTMAXSIZE;
}else {
minPoolSize=size;
maxPoolSize=size;
}
workQueue=new ArrayBlockingQueue(maxPoolSize);//工作线程队列,每次执行完成一个runnable都会存放在工作队列中用于下次操作
pool = new ThreadPoolExecutor(minPoolSize, maxPoolSize, KEEPALIVETIME, TimeUnit.MINUTES, workQueue);
}
public static void main(String[] args) {
}
}
package cc.eguid.cv.videoRecorder.recorder;
import static org.bytedeco.javacpp.avcodec.av_free_packet;
import static org.bytedeco.javacpp.avcodec.avcodec_close;
import static org.bytedeco.javacpp.avcodec.avcodec_decode_video2;
import static org.bytedeco.javacpp.avcodec.avcodec_find_decoder;
import static org.bytedeco.javacpp.avcodec.avcodec_open2;
import static org.bytedeco.javacpp.avformat.av_read_frame;
import static org.bytedeco.javacpp.avformat.av_register_all;
import static org.bytedeco.javacpp.avformat.avformat_close_input;
import static org.bytedeco.javacpp.avformat.avformat_find_stream_info;
import static org.bytedeco.javacpp.avformat.avformat_network_init;
import static org.bytedeco.javacpp.avformat.avformat_open_input;
import static org.bytedeco.javacpp.avutil.AVMEDIA_TYPE_VIDEO;
import static org.bytedeco.javacpp.avutil.AV_TIME_BASE;
import static org.bytedeco.javacpp.avutil.av_dict_set;
import static org.bytedeco.javacpp.avutil.av_frame_alloc;
import static org.bytedeco.javacpp.avutil.av_free;
import static org.bytedeco.javacpp.swscale.sws_freeContext;
import static org.bytedeco.javacpp.swscale.sws_scale;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.bytedeco.javacpp.PointerPointer;
import org.bytedeco.javacpp.avcodec.AVCodec;
import org.bytedeco.javacpp.avcodec.AVCodecContext;
import org.bytedeco.javacpp.avcodec.AVPacket;
import org.bytedeco.javacpp.avformat.AVFormatContext;
import org.bytedeco.javacpp.avformat.AVStream;
import org.bytedeco.javacpp.avutil.AVDictionary;
import org.bytedeco.javacpp.avutil.AVFrame;
import cc.eguid.cv.videoRecorder.CodecNotFoundExpception;
import cc.eguid.cv.videoRecorder.FileNotOpenException;
import cc.eguid.cv.videoRecorder.StreamInfoNotFoundException;
import cc.eguid.cv.videoRecorder.StreamNotFoundException;
/**
* ffmpeg录制器
*
* @author eguid
*
*/
public class FFmpegRecorder {
static {
// Register all formats and codecs
av_register_all();
avformat_network_init();
}
protected int width;//宽度
protected int height;//高度
private final static int PROBESIZE=500*1024;
private final static int MAX_ANALYZE_DURATION=3 * AV_TIME_BASE;
private AVDictionary options = new AVDictionary();
/**
* 打开视频流
* @param url -url
* @return
* @throws FileNotOpenException
*/
protected AVFormatContext openInput(String url) throws FileNotOpenException{
AVFormatContext pFormatCtx = new AVFormatContext(null);
if(avformat_open_input(pFormatCtx, url, null, null)==0) {
return pFormatCtx;
}
throw new FileNotOpenException("Didn't open video file");
}
/**
* 检索流信息(rtsp/rtmp检索时间过长问题解决)
* @param pFormatCtx
* @return
*/
protected AVFormatContext findStreamInfo(AVFormatContext pFormatCtx) throws StreamInfoNotFoundException{
if (avformat_find_stream_info(pFormatCtx, (PointerPointer<?>)null)>= 0) {
return pFormatCtx;
}
throw new StreamInfoNotFoundException("Didn't retrieve stream information");
}
/**
* 获取第一帧视频位置
* @param pFormatCtx
* @return
*/
protected int findVideoStreamIndex(AVFormatContext pFormatCtx) {
int i = 0;
for (i = 0; i < pFormatCtx.nb_streams(); i++) {
AVStream stream=pFormatCtx.streams(i);
AVCodecContext codec=stream.codec();
if (codec.codec_type() == AVMEDIA_TYPE_VIDEO) {
return i;
}
}
return -1;
}
/**
* 指定视频帧位置获取对应视频帧
* @param pFormatCtx
* @param videoStream
* @return
*/
protected AVCodecContext findVideoStream(AVFormatContext pFormatCtx ,int videoStream)throws StreamNotFoundException {
if(videoStream >=0) {
// Get a pointer to the codec context for the video stream
AVStream stream=pFormatCtx.streams(videoStream);
AVCodecContext pCodecCtx = stream.codec();
return pCodecCtx;
}
throw new StreamNotFoundException("Didn't open video file");
}
/**
* 查找并尝试打开解码器
* @return
*/
protected AVCodecContext findAndOpenCodec(AVCodecContext pCodecCtx) {
// Find the decoder for the video stream
AVCodec pCodec = avcodec_find_decoder(pCodecCtx.codec_id());
if (pCodec == null) {
System.err.println("Codec not found");
throw new CodecNotFoundExpception("Codec not found");
}
AVDictionary optionsDict = null;
// Open codec
if (avcodec_open2(pCodecCtx, pCodec, optionsDict) < 0) {
System.err.println("Could not open codec");
throw new CodecNotFoundExpception("Could not open codec"); // Could not open codec
}
return pCodecCtx;
}
/**
* 抓取视频帧(默认跳过音频帧和空帧)
* @param url
* @param fmt - 像素格式,比如AV_PIX_FMT_BGR24
* @return
* @throws IOException
*/
public ByteBuffer grabVideoFrame(String url,int fmt) throws IOException {
// Open video file
AVFormatContext pFormatCtx=openInput(url);
// if(url.indexOf("rtmp")>=0) {
//解决rtmp检索时间过长问题
//限制最大读取缓存
pFormatCtx.probesize(PROBESIZE);//设置500k能保证高清视频也能读取到关键帧
//限制avformat_find_stream_info最大持续时长,设置成3秒
pFormatCtx.max_analyze_duration(MAX_ANALYZE_DURATION);
// }
// Retrieve stream information
pFormatCtx=findStreamInfo(pFormatCtx);
// Dump information about file onto standard error
//av_dump_format(pFormatCtx, 0, url, 0);
//Find a video stream
int videoStream=findVideoStreamIndex(pFormatCtx);
AVCodecContext pCodecCtx =findVideoStream(pFormatCtx,videoStream);
// Find the decoder for the video stream
pCodecCtx= findAndOpenCodec(pCodecCtx);
// Allocate video frame
AVFrame pFrame = av_frame_alloc();
AVPacket packet = new AVPacket();
int[] frameFinished = new int[1];
// Read frames and save first five frames to disk
while (av_read_frame(pFormatCtx, packet) >= 0) {
// Is this a packet from the video stream?
if (packet.stream_index() == videoStream) {
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, packet);
// Did we get a video frame?
if (frameFinished != null&&frameFinished[0] != 0) {
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(packet);
}
//ge
av_free(pFrame);// Free the YUV frame
avcodec_close(pCodecCtx);// Close the codec
avformat_close_input(pFormatCtx);// Close the video file
return null;
}
/**
* 录像
*/
public void record() {
}
}
Manifest-Version: 1.0
Built-By: Administrator
Build-Jdk: 1.8.0_161
Created-By: Maven Integration for Eclipse
#Generated by Maven Integration for Eclipse
#Tue Apr 16 15:11:28 CST 2019
version=1.1.0-2019.3.25
groupId=cc.eguid.cv.corelib
m2e.projectName=videoRecorder
m2e.projectLocation=E\:\\workspace\\eclipse4.8\\easyCV\\corelib\\videoRecorder
artifactId=videoRecorder
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.eguid.cv.corelib</groupId>
<artifactId>corelib</artifactId>
<version>1.0</version>
</parent>
<groupId>cc.eguid.cv.corelib</groupId>
<artifactId>videoRecorder</artifactId>
<version>1.1.0-2019.3.25</version>
<name>videoRecorder</name>
<url>https://blog.eguid.cc</url>
<description>2018年10月17日
1、修复javacv录制mp4视频的编码问题,修改为强制使用h264编码
2018年10月16日
1、增加历史任务持久化接口,原有内置历史任务存储实现转移到DefaultRecordInfoStorage中,如果未指定持久化,则使用默认存储器
2、增加目录设置,如果目录不为空,则使用该目录作为录像存储目录
2018年10月10日
完成主体程序开发,并完成整体流程测试
基本架构:
任务管理器(管理单元)--&gt;管理一个任务处理器对象池
| |
持久化接口 任务处理器(控制单元,预处理和控制工作子线程处理)----&gt;工作子线程(计算单元)
</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
</dependency>
-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<exclusions>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>opencv</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>flycapture</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>libdc1394</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>libfreenect</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>videoinput</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>libfreenect2</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>librealsense</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>artoolkitplus</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>flandmark</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>leptonica</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>tesseract</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg-platform</artifactId>
</dependency>
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg</artifactId>
</dependency>
-->
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.eguid.cv.corelib</groupId>
<artifactId>corelib</artifactId>
<version>1.0</version>
</parent>
<groupId>cc.eguid.cv.corelib</groupId>
<artifactId>videoimageshot</artifactId>
<version>1.1.0-2019.3.25</version>
<name>videoimageshot</name>
<url>https://blog.eguid.cc</url>
<description>
版本更新
2018.9.21
1、多项性能优化
①base64转换性能优化
②ByteBuffer转BufferedImage性能优化
③ffmpeg截图性能优化
④提供线程安全的API
2、修复rtsp截图崩溃的bug
2018.9.18
1、修复流媒体视频检索时间过长问题
2018.9.14
1、支持rtsp/rtmp/hls/媒体流和视频文件截图
2、支持bmp/jpg/png/jpeg/gif等多种格式图片保存
3、支持base64图像数据返回
</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- ffmpeg -->
<!-- javacv -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
</dependency>
<!-- javacpp -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
</dependency>
<!-- ffmpeg -->
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg-platform</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package cc.eguid.cv.corelib.videoimageshot;
import java.awt.image.BufferedImage;
import java.io.IOException;
import cc.eguid.cv.corelib.videoimageshot.core.JavaImgConverter;
import cc.eguid.cv.corelib.videoimageshot.threaddata.CurrentThreadData;
/**
* 线程安全的FFmpeg截图服务
* @author eguid
*
*/
public class FFmpegScreenshot implements Screenshot {
@Override
public boolean shot(String url, String imgurl, String format) throws IOException {
if (format == null) {
format =CurrentThreadData.DETAULT_FORMAT;
}
BufferedImage img = CurrentThreadData.grabber.get().grabBufferImage(url);
if (img != null) {
JavaImgConverter.saveImage(img, format, imgurl);
return true;
}
return false;
}
@Override
public boolean shot(String url, String imgurl) throws IOException {
String fomrat = null;
int index = -1;
if (imgurl != null && (index = imgurl.lastIndexOf(".")) > 0) {
fomrat = imgurl.substring(index + 1, imgurl.length());
}
return shot(url, imgurl, fomrat);
}
@Override
public String getImgBase64(String url, String format) throws IOException {
if (format == null) {
format =CurrentThreadData.DETAULT_FORMAT;
}
BufferedImage img = CurrentThreadData.grabber.get().grabBufferImage(url);
// ByteBuffer buffer=CurrentThreadData.grabber.get().grabBuffer(url);
if (img!= null) {
String base64=JavaImgConverter.bufferedImage2Base64(img, format);
return base64;
}
return null;
}
@Override
public String getImgBase64(String url) throws IOException {
return getImgBase64(url, null);
}
@Override
public String shotAndGetBase64(String url, String imgurl)throws IOException {
return shotAndGetBase64(url,imgurl,null);
}
@Override
public String shotAndGetBase64(String url, String imgurl, String format) throws IOException {
if (format == null) {
format = CurrentThreadData.DETAULT_FORMAT;
}
BufferedImage img =CurrentThreadData.grabber.get().grabBufferImage(url);
if (img != null) {
JavaImgConverter.saveImage(img, format, imgurl);
return JavaImgConverter.bufferedImage2Base64(img, format);
}
return null;
}
}
package cc.eguid.cv.corelib.videoimageshot;
import java.io.IOException;
/**
* 视频截图(根据指定视频流截图,返回base64,并支持保存到指定位置的图片)
*
* @author eguid
*
*/
public interface Screenshot {
/**
* 截图
*
* @param url -视频地址
* @param imgurl -保存的图片地址(不带后缀)
* @param format 图片格式(图片后缀,如果为空默认jpg)
*/
boolean shot(String url, String imgurl,String format) throws IOException;
/**
* 截图
*
* @param url -视频地址
* @param imgurl-图片地址(带后缀,如果不带后缀默认jpg格式)
* @throws IOException
*/
boolean shot(String url,String imgurl) throws IOException;
/**
* 截图(只返回图像的base64编码,默认jpg格式)
* @param url -视频地址
* @return
* @throws IOException
*/
String getImgBase64(String url) throws IOException;
/**
* 截图(只返回图像的base64编码,默认jpg格式)
* @param url -视频地址
* @param format-图片格式(如果为空,默认jpg格式)
* @return
* @throws IOException
*/
String getImgBase64(String url, String fommat)throws IOException;
/**
* 截图并返回base64数据
* @param url -视频地址
* @param imgurl-图片地址(带后缀,如果不带后缀默认jpg格式)
* @return
*/
String shotAndGetBase64(String url,String imgurl) throws IOException;
/**
* 截图并返回base64数据
* @param url -视频地址
* @param imgurl-图片地址(带后缀,如果不带后缀默认jpg格式)
* @param format-图片格式(如果为空,默认jpg格式)
* @return
* @throws IOException
*/
String shotAndGetBase64(String url,String imgurl, String fommat) throws IOException ;
}
package cc.eguid.cv.corelib.videoimageshot.core;
import java.io.ByteArrayOutputStream;
/**
* ByteArrayOutputStream改进版 增加获取管理的数组
*
* @author eguid
*
*/
public class ByteArrayOutputStreamPlus extends ByteArrayOutputStream {
public ByteArrayOutputStreamPlus() {
super();
}
public ByteArrayOutputStreamPlus(int i) {
super(i);
}
public byte[] getBuf() {
return this.buf;
}
}
package cc.eguid.cv.corelib.videoimageshot.exception;
/**
* 没有找到对应的编解码器
* @author eguid
*
*/
public class CodecNotFoundExpception extends RuntimeException{
private static final long serialVersionUID = 1L;
public CodecNotFoundExpception(String message) {
super(message);
}
}
package cc.eguid.cv.corelib.videoimageshot.exception;
/**
* 文件或流无法打开
* @author eguid
*
*/
public class FileNotOpenException extends RuntimeException{
public FileNotOpenException(String message) {
super(message);
}
private static final long serialVersionUID = 1L;
}
package cc.eguid.cv.corelib.videoimageshot.exception;
/**
* 无法检索流信息
* @author eguid
*
*/
public class StreamInfoNotFoundException extends RuntimeException {
public StreamInfoNotFoundException(String message) {
super(message);
}
private static final long serialVersionUID = 1L;
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
#配置程序端口,默认为8080
server.port=8081
#thymeleaf缓存(开发时)取消,取消缓存-false
spring.thymeleaf.cache=true
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册