目录
1. 摄像头任务的线程池
/**
* 线程池
*
* @author xuyihao
* @date 2020/12/22
*/
@Configuration
@Slf4j
@EnableAsync
public class ExecutorConfig {
// 只希望最多有4个摄像头线程 做任务 如果有多余的 摄像头则抛弃任务
@Bean("asyncServiceExecutor")
public Executor asyncServiceExecutor() {
log.info("start asyncServiceExecutor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(1);
//配置最大线程数
executor.setMaxPoolSize(4);
//配置队列大小
executor.setQueueCapacity(0);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("camera-");
executor.setAllowCoreThreadTimeOut(true);
executor.setKeepAliveSeconds(1);
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
2. 摄像头业务
@Slf4j
@Component
public class CameraTask {
@Async("asyncServiceExecutor")
@Transactional(rollbackFor = Exception.class)
public void startCameraThread(CameraInfo cameraInfo) {
// 设置线程名称 camer_+"摄像头别名"
Thread.currentThread().setName("camera_" + cameraInfo.getAlias() + "");
log.info(Thread.currentThread().getName() + "启动准备拉流....");
FFmpegFrameGrabber grabber = MediaUtils.createGrabber(cameraInfo.appendUrl());
try {
grabber.start();
// Constant.map.get(cameraInfo.getAlias()) 控制功能:当为true表示持续拉流获取图片 false结束这个任务
while (Constant.map.get(cameraInfo.getAlias())) {
Frame frame = grabber.grabImage()
// 死循环不断获取frame对象 然后调用接口 返回json报文保存.....
}
} catch (FrameGrabber.Exception e) {
log.error("FrameGrabber.Exception e" + e.getMessage());
} catch (IOException e) {
log.error("启动摄像头线程错误IO: " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
log.error("启动摄像头线程错误: " + e.getMessage());
} finally {
if (grabber != null) {
try {
grabber.close();
} catch (Exception e) {
e.printStackTrace();
}
}
log.info("线程" + Thread.currentThread().getName() + "结束");
}
}
}
3. contronller层对摄像头的增删改查
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wd.gansubox.bean.PageVo;
import com.wd.gansubox.bean.Query;
import com.wd.gansubox.bean.QueryCondition;
import com.wd.gansubox.bean.Resp;
import com.wd.gansubox.common.Constant;
import com.wd.gansubox.component.CameraTask;
import com.wd.gansubox.dao.CameraInfoDao;
import com.wd.gansubox.entity.CameraInfo;
import com.wd.gansubox.entity.CameraInfoDetail;
import com.wd.gansubox.exception.RRException;
import com.wd.gansubox.service.CameraInfoDetailService;
import com.wd.gansubox.service.CameraInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
public class CameraInfoController {
@Autowired
private CameraInfoDao cameraInfoDao;
@Autowired
private CameraInfoService cameraInfoService;
@Autowired
private CameraInfoDetailService cameraInfoDetailService;
@Autowired
private CameraTask cameraTask;
private ReentrantLock lockSave = new ReentrantLock();
private ReentrantLock lockUpdate = new ReentrantLock();
private ReentrantLock lockRemove = new ReentrantLock();
/**
* 保存
*/
@Log("保存盒子上报信息")
@ApiOperation("保存")
@PostMapping("/save")
@Transactional(rollbackFor = Exception.class)
public Resp<Object> save(@RequestBody CameraInfo cameraInfo ) {
//非空判断
...
//查询数据库摄像头数量是否超过4个
...
// 保存至数据库并且启动线程
if (lockSave.tryLock()) {
try {
// 设置别名 作为摄像头线程的名称 别名只有4个 1111 2222 3333 4444
if (aliasList.size() == 0) {
cameraInfo.setAlias(CameraInfo.ALIAS_1);
} else {
for (int z = CameraInfo.ALIAS_1; z <= CameraInfo.ALIAS_4; z += CameraInfo.ALIAS_1) {
if (!aliasList.contains(z)) {
cameraInfo.setAlias(z);
break;
}
}
}
// 保存数据
cameraInfoService.save(cameraInfo);
// 开启线程
Constant.map.put(cameraInfo.getAlias(), true);
cameraTask.startCameraThread(cameraInfo);
} catch (Exception e) {
throw new RRException("lockSave 发生错误" + e.getMessage());
} finally {
lockSave.unlock();
}
} else {
return Resp.fail("请等待其他摄像头启动后再试");
}
return Resp.ok(null);
}
/**
* 修改
*/
@Log("修改盒子初始信息")
@ApiOperation("修改")
@PostMapping("/update")
@Transactional(rollbackFor = Exception.class)
public Resp<Object> update(@RequestBody CameraInfo cameraInfo ) {
// 非空判断
...
//更新摄像头
try {
if (lockUpdate.tryLock()) {
// 更新摄像头
cameraInfoService.updateById(cameraInfo);
// 关闭线程
Constant.map.put(cameraInfo.getAlias(), false);
// 开启线程
Constant.map.put(cameraInfo.getAlias(), true);
cameraTask.startCameraThread(cameraInfo);
} else {
return Resp.fail("请等待其他摄像头更新后再试");
}
} catch (Exception e) {
throw new RRException("lockUpdate 发生错误" + e.getMessage());
} finally {
lockUpdate.unlock();
}
return Resp.ok(null);
}
/**
* 删除
*/
@Log("删除盒子初始信息")
@ApiOperation("删除")
@GetMapping("/delete/{id}")
@Transactional(rollbackFor = Exception.class)
public Resp<Object> delete(@PathVariable("id") Long id) {
CameraInfo cameraInfo = cameraInfoService.getBaseMapper().selectById(id);
if (cameraInfo == null) {
return Resp.fail("摄像头未查询到");
}
// 删除摄像头
if (lockRemove.tryLock()) {
try {
cameraInfoService.removeById(id);
// 关闭线程
Constant.map.put(cameraInfo.getAlias(), false);
} catch (Exception e) {
throw new RRException("lockRemove 发生错误" + e.getMessage());
} finally {
lockRemove.unlock();
}
} else {
return Resp.fail("请等待其他摄像头删除后再试");
}
return Resp.ok(null);
}
}
4. 实体类
@ApiModel
@Data
@TableName("camera_info")
public class CameraInfo implements Serializable {
private static final long serialVersionUID = 300926603958321459L;
public static final int ALIAS_1 = 1111;
public static final int ALIAS_2 = 2222;
public static final int ALIAS_3 = 3333;
public static final int ALIAS_4 = 4444;
@TableId
@ApiModelProperty(name = "id", value = "")
private Long id;
/**
* 设备名称
*/
@ApiModelProperty(name = "deviceName", value = "设备名称")
@NotEmpty
private String deviceName;
/**
* 用户名
*/
@ApiModelProperty(name = "username", value = "用户名")
@NotEmpty
private String username;
/**
* 密码
*/
@ApiModelProperty(name = "password", value = "密码")
@NotEmpty
private String password;
/**
* ip
*/
@ApiModelProperty(name = "ip", value = "ip")
@NotEmpty
private String ip;
/**
* port
*/
@ApiModelProperty(name = "port", value = "port")
@NotEmpty
private String port;
@ApiModelProperty(name = "alias", value = "别名 固定4个")
private int alias;
// 把用户名 密码 ip 拼接成摄像头地址
public String appendUrl() {
return "rtsp://" + username + ":" + password + "@" + ip + ":" + port + "/cam/realmonitor?channel=1&subtype=0";
}
}
5. 常量
/**
* 参数定义
*
* @author yangxiaofeng
* @since 2020-03-18 10:16:01
*/
public interface Constant {
Map<Integer, Boolean> map = new ConcurrentHashMap(16);
/**
* 初始化线程开关 状态为可开启 启动时调用
*/
static void initMap() {
Constant.map.put(CameraInfo.ALIAS_1, true);
Constant.map.put(CameraInfo.ALIAS_2, true);
Constant.map.put(CameraInfo.ALIAS_3, true);
Constant.map.put(CameraInfo.ALIAS_4, true);
}
}