声纹模块开发

This commit is contained in:
weitang 2025-04-25 13:37:12 +08:00
parent d8002fae52
commit 3f9fe4d0d4
19 changed files with 936 additions and 13 deletions

View File

@ -2,6 +2,7 @@ package com.yfd.platform.component.mqtt;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.ExecutorChannel; import org.springframework.integration.channel.ExecutorChannel;
@ -21,7 +22,13 @@ import java.util.concurrent.ThreadPoolExecutor;
* *
* @date 2022/8/24 * @date 2022/8/24
*/ */
//@Configuration @Configuration
@ConditionalOnProperty(
// 配置属性名
name = "mqttserver.enable",
// 属性值为 true 时生效
havingValue = "true"
)
public class MqttConfig { public class MqttConfig {
@Autowired @Autowired

View File

@ -0,0 +1,88 @@
package com.yfd.platform.component.voiceserver;
import cn.hutool.core.util.HexUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class AudioDataDecoder extends ByteToMessageDecoder {
private static final int HEAD_LENGTH = 4;
private static final int VERSION_LENGTH = 1;
private static final int SIZE_LENGTH = 4;
private static final int TIMESTAMP_LENGTH = 8;
private static final int BASICINFO_LENGTH = 6;
private static final int ID_LENGTH = 6;
private static final int FIRMWARE_LENGTH = 3;
private static final int HARDWARE_LENGTH = 1;
private static final int PROTOCOL_LENGTH = 2;
private static final int FLAG_LENGTH = 3;
private static final int CRC_LENGTH = 2;
private static final int END_LENGTH = 1;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < HEAD_LENGTH + VERSION_LENGTH + SIZE_LENGTH) {
// 等待更多的数据
return;
}
// 读取头标志版本和包大小
in.markReaderIndex(); // 标记当前读取位置
byte[] headBytes = new byte[HEAD_LENGTH];
in.readBytes(headBytes);
String head = new String(HexUtil.encodeHex(headBytes, false));
byte version = in.readByte(); // 读取版本
int size = in.readInt(); // 读取包大小
// 检查是否有足够的数据来读取整个包
if (in.readableBytes() < size) {
in.resetReaderIndex(); // 重置读取位置
return;
}
// 读取整个数据包
ByteBuf buf = in.readBytes(size);
byte lastByte = buf.getByte(buf.readerIndex() + buf.readableBytes() - 1);
int decimalValue = lastByte & 0xFF;
if (253 != decimalValue) {
// 重置读取位置
in.resetReaderIndex();
}
// 解析数据包这里只展示如何读取头部信息实际使用时需要解析整个数据包
VoicePacket packet = new VoicePacket();
packet.setHead(head);
packet.setSize(size);
packet.setVersion(version);
packet.setTimestamp(buf.readLong());
packet.setBasicInfo(readBytes(buf, BASICINFO_LENGTH));
packet.setId(readBytes(buf, ID_LENGTH));
packet.setFirmware(readBytes(buf, FIRMWARE_LENGTH));
packet.setHardware(buf.readByte());
packet.setProtocol(buf.readShort());
packet.setFlag(readBytes(buf, FLAG_LENGTH));
DataGroup dataGroup = new DataGroup();
dataGroup.setSType(buf.readByte());
dataGroup.setSampleRate(buf.readShort());
dataGroup.setBits(buf.readByte());
dataGroup.setChannel(buf.readByte());
// 减去CRC和End
dataGroup.setData(readBytes(buf, buf.readableBytes() - 3));
packet.setDataGroup(dataGroup);
packet.setCrc(buf.readShort());
packet.setEnd(buf.readByte());
// 将解析后的数据添加到输出列表中
// 假设我们只需要数据部分
out.add(packet);
}
private byte[] readBytes(ByteBuf buf, int length) {
byte[] bytes = new byte[length];
buf.readBytes(bytes);
return bytes;
}
}

View File

@ -0,0 +1,14 @@
package com.yfd.platform.component.voiceserver;
import lombok.Data;
@Data
public class DataGroup {
private byte sType; // 1 byte
private short sampleRate; // 2 bytes
private byte bits; // 1 byte
private byte channel; // 1 byte
private byte[] data; // n bytes
// Getters and Setters
}

View File

@ -0,0 +1,23 @@
package com.yfd.platform.component.voiceserver;
import lombok.Data;
@Data
public class VoicePacket {
private String head; // 4 bytes
private byte version; // 1 byte
private int size; // 4 bytes
private long timestamp; // 8 bytes
private byte[] basicInfo; // 6 bytes
private byte[] id; // 6 bytes
private byte[] firmware; // 3 bytes
private byte hardware; // 1 byte
private short protocol; // 2 bytes
private byte[] flag; // 3 bytes
private DataGroup dataGroup; // n bytes
private short crc; // 2 bytes
private byte end; // 1 byte
// Getters and Setters
}

View File

@ -0,0 +1,134 @@
package com.yfd.platform.component.voiceserver;
import cn.hutool.core.util.HexUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class VoicePacketHandler extends SimpleChannelInboundHandler<VoicePacket> {
public static List<DataGroup> dataGroups = new ArrayList<>();
boolean flag = false;
@Override
protected void channelRead0(ChannelHandlerContext ctx, VoicePacket voicePacket) throws Exception {
// 处理接收到的数据包
dataGroups.add(voicePacket.getDataGroup());
}
private VoicePacket parseVoicePacket(ByteBuf buf) throws Exception {
if (buf == null) {
return null;
}
System.out.println("字节长度" + buf.readableBytes());
VoicePacket packet = new VoicePacket();
byte[] headBytes = new byte[4];
buf.readBytes(headBytes);
String head = new String(HexUtil.encodeHex(headBytes, false));
if (!"FCFCFCFC".equals(head)) {
System.out.println("不符合声纹和巡视主机的通讯协议!");
}
packet.setHead(head);
packet.setVersion(buf.readByte());
packet.setSize(buf.readInt());
System.out.println("size" + packet.getSize());
packet.setTimestamp(buf.readLong());
packet.setBasicInfo(readBytes(buf, 6));
packet.setId(readBytes(buf, 6));
packet.setFirmware(readBytes(buf, 3));
packet.setHardware(buf.readByte());
packet.setProtocol(buf.readShort());
packet.setFlag(readBytes(buf, 3));
DataGroup dataGroup = new DataGroup();
dataGroup.setSType(buf.readByte());
dataGroup.setSampleRate(buf.readShort());
dataGroup.setBits(buf.readByte());
dataGroup.setChannel(buf.readByte());
dataGroup.setData(readBytes(buf, buf.readableBytes() - 3)); // 减去CRC和End
packet.setDataGroup(dataGroup);
packet.setCrc(buf.readShort());
packet.setEnd(buf.readByte());
return packet;
}
private byte[] readBytes(ByteBuf buf, int length) {
byte[] bytes = new byte[length];
buf.readBytes(bytes);
return bytes;
}
private void processVoicePacket(VoicePacket packet) throws IOException {
// byte bits = dataGroup.getBits();
// byte channel = dataGroup.getChannel();
// short sampleRate = dataGroup.getSampleRate();
// // 保存为 WAV 文件
// saveAsWav(dataGroup.getData(), sampleRate, channel, bits, "D:\\output.wav");
// System.out.println("WAV 文件生成成功");
}
/**
* 生成模拟音频数据PCM 格式
*
* @param sampleRate 采样率
* @param channels 声道数
* @param bitsPerSample 位深
* @return 模拟的音频数据
*/
public static byte[] generateAudioData(int sampleRate, int channels, int bitsPerSample) {
int duration = 2; // 音频时长
int frameSize = channels * (bitsPerSample / 8); // 每帧的字节数
int dataSize = sampleRate * duration * frameSize; // 总数据大小
byte[] audioData = new byte[dataSize];
for (int i = 0; i < dataSize; i++) {
audioData[i] = (byte) (Math.random() * 256 - 128); // 随机生成 PCM 数据
}
return audioData;
}
/**
* 解析声纹数据并保存为 WAV 文件
*
* @param audioData 音频数据PCM 格式
* @param sampleRate 采样率 48000
* @param channels 声道数 2 表示双声道
* @param bitsPerSample 位深 16
* @param outputFile 输出文件路径 "output.wav"
*/
public static void saveAsWav(byte[] audioData, int sampleRate, int channels, int bitsPerSample, String outputFile) {
try {
// 创建音频格式
AudioFormat format = new AudioFormat(sampleRate, bitsPerSample, channels, true, false);
// 将字节数组转换为音频输入流
ByteArrayInputStream bais = new ByteArrayInputStream(audioData);
AudioInputStream audioInputStream = new AudioInputStream(bais, format,
audioData.length / format.getFrameSize());
// 保存为 WAV 文件
AudioSystem.write(audioInputStream, javax.sound.sampled.AudioFileFormat.Type.WAVE, new File(outputFile));
System.out.println("Saved audio data to: " + outputFile);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}

View File

@ -0,0 +1,82 @@
package com.yfd.platform.component.voiceserver;
import com.yfd.platform.component.nettyserver.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VoiceServer {
private static final Logger logger = LoggerFactory.getLogger(VoiceServer.class);
private final int port;
public VoiceServer(int port) {
this.port = port;
}
public void run() throws Exception {
//创建两个线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); //8个NioEventLoop
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//获取到pipeline
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new AudioDataDecoder());
//加入自己的业务处理handler
pipeline.addLast(new VoicePacketHandler());
}
});
logger.info("Voice TCP server is started!port:" + port + "");
ChannelFuture channelFuture = b.bind(port).sync();
//监听关闭
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
// public VoiceServer(int port) {
// this.port = port;
// }
//
// public void run() throws Exception {
// EventLoopGroup bossGroup = new NioEventLoopGroup();
// EventLoopGroup workerGroup = new NioEventLoopGroup();
// try {
// ServerBootstrap b = new ServerBootstrap();
// b.group(bossGroup, workerGroup)
// .channel(NioServerSocketChannel.class)
// .childHandler(new ChannelInitializer<SocketChannel>() {
// @Override
// public void initChannel(SocketChannel ch) {
// //获取到pipeline
// ChannelPipeline pipeline = ch.pipeline();
// pipeline.addLast(new AudioDataDecoder());
// pipeline.addLast(new VoicePacketHandler());
// }
// });
//
// ChannelFuture f = b.bind(port).sync();
// f.channel().closeFuture().sync();
// } finally {
// workerGroup.shutdownGracefully();
// bossGroup.shutdownGracefully();
// }
// }
}

View File

@ -0,0 +1,33 @@
package com.yfd.platform.component.voiceserver;
import com.yfd.platform.component.nettyserver.NettyServer;
import com.yfd.platform.config.HttpServerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
@Order(value=21)
public class VoiceServerRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(VoiceServerRunner.class);
@Override
public void run(String... args) throws Exception {
new Thread(()->{
try {
//项目启动时已经加载好了
new VoiceServer(8972).run();
logger.info("Netty TCP server is started!");
} catch (Exception e){
logger.error("发生错误", e);
throw new RuntimeException("Netty TCP server start failed!", e);
}
}).start();
}
}

View File

@ -0,0 +1,17 @@
package com.yfd.platform.component.voiceserver;
import com.yfd.platform.modules.basedata.domain.SubstationPatroldevice;
import com.yfd.platform.modules.basedata.domain.VoicePatrolLog;
/**
* @Author pcj
* @Date 2025/3/6 18:28
* @Version 1.0
*/
public interface VoiceServerService {
boolean sendVoiceServerControl(String ID, String actionPower,String fileName);
boolean createAudioFile(String fileName);
void executeVoiceTaskAsync(SubstationPatroldevice substationPatroldevice, String duration, VoicePatrolLog log);
}

View File

@ -0,0 +1,243 @@
package com.yfd.platform.component.voiceserver.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.LogFactory;
import com.yfd.platform.component.mqtt.MqttGateway;
import com.yfd.platform.component.voiceserver.DataGroup;
import com.yfd.platform.component.voiceserver.VoicePacketHandler;
import com.yfd.platform.component.voiceserver.VoiceServerService;
import com.yfd.platform.config.HttpServerConfig;
import com.yfd.platform.config.thread.ThreadPoolExecutorUtil;
import com.yfd.platform.modules.basedata.domain.SubstationPatroldevice;
import com.yfd.platform.modules.basedata.domain.VoicePatrolLog;
import com.yfd.platform.modules.basedata.mapper.VoicePatrolLogMapper;
import com.yfd.platform.utils.ExecutionJob;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @Date: 2025/3/6 18:28
* @Description:
*/
@Service
public class VoiceServerServiceImpl implements VoiceServerService {
@Autowired
private MqttGateway mqttGateway;
@Resource
private HttpServerConfig httpServerConfig;
@Resource
private VoicePatrolLogMapper voicePatrolLogMapper;
@Override
public boolean sendVoiceServerControl(String ID, String actionPower, String fileName) {
if ("ON".equals(actionPower)) {
VoicePacketHandler.dataGroups.clear();
}
JSONObject data = new JSONObject();
data.putOpt("GLOBAL_MODE", "ACTION");
data.putOpt("GLOBAL_CLIENTID", ID);
data.putOpt("GLOBAL_GROUPID", "admin");
data.putOpt("ACTION_POWER", actionPower);
try {
mqttGateway.sendToMqtt("CONTROL/" + ID, 1, JSONUtil.toJsonStr(data));
} catch (Exception e) {
e.printStackTrace();
}
if ("OFF".equals(actionPower)) {
this.createAudioFile(fileName);
}
return true;
}
@Override
public boolean createAudioFile(String fileName) {
List<DataGroup> voicePackets = VoicePacketHandler.dataGroups;
if (voicePackets.size() <= 0) {
return false;
}
DataGroup dataGroup = voicePackets.get(0);
byte sampleSizeInBits = dataGroup.getBits();
byte numChannels = dataGroup.getChannel();
short sampleRate = dataGroup.getSampleRate();
List<byte[]> pcmDataList = voicePackets.stream().map(DataGroup::getData).collect(Collectors.toList());
try {
FileUtil.touch(httpServerConfig.getSnapFilePath() + fileName);
generateWavFile(httpServerConfig.getSnapFilePath() + fileName, pcmDataList, sampleRate, numChannels,
sampleSizeInBits);
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
/***********************************
* 用途说明: 线程执行声纹任务
* 参数说明 device 设备
* 参数说明 duration 录制时间
* 参数说明 log 声音日志
* 返回值说明: void
***********************************/
@Override
public void executeVoiceTaskAsync(SubstationPatroldevice device, String duration, VoicePatrolLog log) {
try {
ExecutionJob.EXECUTOR.submit(() -> {
// 生成文件路径异步线程内操作
String fullPath = buildFilePath(device);
log.setFileName(fullPath);
log.setFilePath(httpServerConfig.getSnapFilePath() + fullPath);
// 控制音频采集
this.sendVoiceServerControl(
device.getPatroldeviceCode(), "ON", fullPath
);
try {
TimeUnit.SECONDS.sleep(Integer.parseInt(duration));
} catch (InterruptedException e) {
e.printStackTrace();
}
this.sendVoiceServerControl(
device.getPatroldeviceCode(), "OFF", fullPath
);
// 保存日志
voicePatrolLogMapper.insert(log);
});
} catch (Exception e) {
// 异常处理记录详细日志
e.printStackTrace();
}
}
private String buildFilePath(SubstationPatroldevice device) {
return String.format("%s/%s/%s/%s/%s/%s/%s-%s.wav",
device.getStationCode(),
DateUtil.format(new Date(), "yyyy"),
DateUtil.format(new Date(), "MM"),
DateUtil.format(new Date(), "dd"),
device.getPatroldeviceCode(),
"Audio",
device.getPatroldeviceCode(),
DateUtil.format(new Date(), "yyyyMMddHHmmss")
);
}
public byte[] mergePcmData(List<byte[]> pcmDataList) {
int totalLength = pcmDataList.stream().mapToInt(data -> data.length).sum();
byte[] fullPcmData = new byte[totalLength];
int offset = 0;
for (byte[] data : pcmDataList) {
System.arraycopy(data, 0, fullPcmData, offset, data.length);
offset += data.length;
}
return fullPcmData;
}
public void generateWavFile(String savePath, List<byte[]> pcmDataList, int sampleRate, int numChannels,
int bitsPerSample) throws IOException {
// 合并 PCM 数据
byte[] fullPcmData = mergePcmData(pcmDataList);
// 构建 WAV 文件头
byte[] header = buildWavHeader(sampleRate, numChannels, bitsPerSample, fullPcmData.length);
// 写入文件
try (FileOutputStream fos = new FileOutputStream(savePath)) {
fos.write(header);
fos.write(fullPcmData);
}
System.out.println("音频数据已保存为 " + savePath);
}
private byte[] buildWavHeader(int sampleRate, int numChannels, int bitsPerSample, int dataSize) throws IOException {
ByteArrayOutputStream headerStream = new ByteArrayOutputStream();
// ChunkID: "RIFF" (4 bytes)
headerStream.write("RIFF".getBytes());
// ChunkSize: File size - 8 (4 bytes, little-endian)
int fileSize = 36 + dataSize; // 36 = 44 - 8 (header size)
headerStream.write(ByteBuffer.allocate(4)
.order(ByteOrder.LITTLE_ENDIAN)
.putInt(fileSize)
.array());
// Format: "WAVE" (4 bytes)
headerStream.write("WAVE".getBytes());
// Subchunk1ID: "fmt " (4 bytes)
headerStream.write("fmt ".getBytes());
// Subchunk1Size: 16 (4 bytes, little-endian)
headerStream.write(ByteBuffer.allocate(4)
.order(ByteOrder.LITTLE_ENDIAN)
.putInt(16)
.array());
// AudioFormat: 1 (PCM, 2 bytes, little-endian)
headerStream.write(ByteBuffer.allocate(2)
.order(ByteOrder.LITTLE_ENDIAN)
.putShort((short) 1)
.array());
// NumChannels (2 bytes, little-endian)
headerStream.write(ByteBuffer.allocate(2)
.order(ByteOrder.LITTLE_ENDIAN)
.putShort((short) numChannels)
.array());
// SampleRate (4 bytes, little-endian)
headerStream.write(ByteBuffer.allocate(4)
.order(ByteOrder.LITTLE_ENDIAN)
.putInt(sampleRate)
.array());
// ByteRate: SampleRate * NumChannels * BitsPerSample/8 (4 bytes, little-endian)
int byteRate = sampleRate * numChannels * (bitsPerSample / 8);
headerStream.write(ByteBuffer.allocate(4)
.order(ByteOrder.LITTLE_ENDIAN)
.putInt(byteRate)
.array());
// BlockAlign: NumChannels * BitsPerSample/8 (2 bytes, little-endian)
short blockAlign = (short) (numChannels * (bitsPerSample / 8));
headerStream.write(ByteBuffer.allocate(2)
.order(ByteOrder.LITTLE_ENDIAN)
.putShort(blockAlign)
.array());
// BitsPerSample (2 bytes, little-endian)
headerStream.write(ByteBuffer.allocate(2)
.order(ByteOrder.LITTLE_ENDIAN)
.putShort((short) bitsPerSample)
.array());
// Subchunk2ID: "data" (4 bytes)
headerStream.write("data".getBytes());
// Subchunk2Size: data size (4 bytes, little-endian)
headerStream.write(ByteBuffer.allocate(4)
.order(ByteOrder.LITTLE_ENDIAN)
.putInt(dataSize)
.array());
return headerStream.toByteArray();
}
}

View File

@ -0,0 +1,119 @@
package com.yfd.platform.modules.basedata.controller;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.platform.annotation.Log;
import com.yfd.platform.component.voiceserver.VoiceServerService;
import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.modules.basedata.domain.SubstationPatroldevice;
import com.yfd.platform.modules.basedata.domain.VoicePatrolLog;
import com.yfd.platform.modules.basedata.service.ISubstationPatroldeviceService;
import com.yfd.platform.modules.basedata.service.IVoicePatrolLogService;
import com.yfd.platform.utils.HttpRESTfulUtils;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;
/**
* <p>
* 前端控制器
* </p>
*
* @author zhengsl
* @since 2025-04-25
*/
@RestController
@RequestMapping("/basedata/voice-patrol-log")
public class VoicePatrolLogController {
@Resource
private IVoicePatrolLogService voicePatrolLogService;
@Resource
private ISubstationPatroldeviceService substationPatroldeviceService;
@Resource
private HttpRESTfulUtils httpRESTfulUtils;
@Resource
private VoiceServerService voiceServerService;
@GetMapping("/getVoicePatrolPage")
@ApiOperation("分页查看声纹检测数据")
public ResponseResult getVoicePatrolPage(Page<Map<String, Object>> page, String stationId, String patroldeviceId,
String patroldeviceName
, String startDate, String endDate) {
if (StrUtil.isBlank(stationId)) {
return ResponseResult.error("未传变电站信息");
}
String startFormat = "";
if (StrUtil.isNotBlank(startDate)) {
Date parseStart = DateUtil.parse(startDate);
//一天的开始
Date beginOfDay = DateUtil.beginOfDay(parseStart);
startFormat = DateUtil.format(beginOfDay, "yyyy-MM-dd HH:mm:ss");
}
String endFormat = "";
if (StrUtil.isNotBlank(startDate)) {
Date parseEnd = DateUtil.parse(endDate);
//一天的结束
Date endOfDay = DateUtil.endOfDay(parseEnd);
endFormat = DateUtil.format(endOfDay, "yyyy-MM-dd HH:mm:ss");
}
LambdaQueryWrapper<VoicePatrolLog> queryWrapper = new LambdaQueryWrapper<>();
if (StrUtil.isNotBlank(stationId)) {
queryWrapper.eq(VoicePatrolLog::getStationId, stationId);
}
if (StrUtil.isNotBlank(patroldeviceId)) {
queryWrapper.eq(VoicePatrolLog::getPatroldeviceId, patroldeviceId);
}
if (StrUtil.isNotBlank(patroldeviceName)) {
queryWrapper.like(VoicePatrolLog::getPatroldeviceName, patroldeviceName);
}
if (StrUtil.isNotBlank(startFormat) && StrUtil.isNotBlank(endFormat)) {
queryWrapper.le(VoicePatrolLog::getDate, endFormat);
queryWrapper.ge(VoicePatrolLog::getDate, startFormat);
}
queryWrapper.orderByDesc(VoicePatrolLog::getDate);
Page<Map<String, Object>> mapPage = voicePatrolLogService.pageMaps(page, queryWrapper);
return ResponseResult.successData(mapPage);
}
@Log(module = "声纹检测", value = "执行声纹任务", type = "1")
@PostMapping("/executeVoicePatrolTask")
@ApiOperation("执行声纹任务")
public ResponseResult executeVoicePatrolTask(String id, String duration) {
SubstationPatroldevice substationPatroldevice = substationPatroldeviceService.getById(id);
if (substationPatroldevice == null) {
return ResponseResult.error("当前设备不存在");
}
// 生成日志对象基础信息同步执行
VoicePatrolLog log = buildBaseLog(substationPatroldevice, duration);
// 提交异步任务
voiceServerService.executeVoiceTaskAsync(substationPatroldevice, duration, log);
// 立即返回响应
return ResponseResult.success("任务已提交");
}
// 基础日志对象构建方法
private VoicePatrolLog buildBaseLog(SubstationPatroldevice device, String duration) {
VoicePatrolLog log = new VoicePatrolLog();
log.setId(IdUtil.fastSimpleUUID());
log.setPatroldeviceId(device.getPatroldeviceId());
log.setPatroldeviceCode(device.getPatroldeviceCode());
log.setPatroldeviceName(device.getPatroldeviceName());
log.setStationId(device.getStationId());
log.setStationCode(device.getStationCode());
log.setStationName(device.getStationName());
log.setDate(DateUtil.now());
log.setDuration(duration);
return log;
}
}

View File

@ -0,0 +1,84 @@
package com.yfd.platform.modules.basedata.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
*
* </p>
*
* @author zhengsl
* @since 2025-04-25
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("iis_voice_patrol_log")
public class VoicePatrolLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private String id;
/**
* 变电站id
*/
private String stationId;
/**
* 变电站名称
*/
private String stationName;
/**
* 变电站编码
*/
private String stationCode;
/**
* 巡视设备编号
*/
private String patroldeviceCode;
/**
* 巡视设备名称
*/
private String patroldeviceName;
/**
* 巡视设备id
*/
private String patroldeviceId;
/**
* 巡视点位id
*/
private String deviceId;
/**
* 文件名称
*/
private String fileName;
/**
* 文件完整路径
*/
private String filePath;
/**
* 采集时长单位
*/
private String duration;
/**
* 录制时长
*/
private String date;
}

View File

@ -0,0 +1,16 @@
package com.yfd.platform.modules.basedata.mapper;
import com.yfd.platform.modules.basedata.domain.VoicePatrolLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author zhengsl
* @since 2025-04-25
*/
public interface VoicePatrolLogMapper extends BaseMapper<VoicePatrolLog> {
}

View File

@ -0,0 +1,16 @@
package com.yfd.platform.modules.basedata.service;
import com.yfd.platform.modules.basedata.domain.VoicePatrolLog;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author zhengsl
* @since 2025-04-25
*/
public interface IVoicePatrolLogService extends IService<VoicePatrolLog> {
}

View File

@ -0,0 +1,20 @@
package com.yfd.platform.modules.basedata.service.impl;
import com.yfd.platform.modules.basedata.domain.VoicePatrolLog;
import com.yfd.platform.modules.basedata.mapper.VoicePatrolLogMapper;
import com.yfd.platform.modules.basedata.service.IVoicePatrolLogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务实现类
* </p>
*
* @author zhengsl
* @since 2025-04-25
*/
@Service
public class VoicePatrolLogServiceImpl extends ServiceImpl<VoicePatrolLogMapper, VoicePatrolLog> implements IVoicePatrolLogService {
}

View File

@ -18,10 +18,7 @@ package com.yfd.platform.modules.patroltask.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.*;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray; import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
@ -29,6 +26,7 @@ import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.yfd.platform.component.WebSocketServer; import com.yfd.platform.component.WebSocketServer;
import com.yfd.platform.component.voiceserver.VoiceServerService;
import com.yfd.platform.config.HttpServerConfig; import com.yfd.platform.config.HttpServerConfig;
import com.yfd.platform.constant.SystemCode; import com.yfd.platform.constant.SystemCode;
import com.yfd.platform.modules.basedata.domain.LinkageSignal; import com.yfd.platform.modules.basedata.domain.LinkageSignal;
@ -101,6 +99,7 @@ public class TodoTaskJob extends QuartzJobBean implements InterruptableJob {
ISysDictionaryItemsService sysDictionaryItemsService = ISysDictionaryItemsService sysDictionaryItemsService =
SpringContextHolder.getBean(ISysDictionaryItemsService.class); SpringContextHolder.getBean(ISysDictionaryItemsService.class);
TaskResultMapper taskResultMapper = SpringContextHolder.getBean(TaskResultMapper.class); TaskResultMapper taskResultMapper = SpringContextHolder.getBean(TaskResultMapper.class);
VoiceServerService voiceServerService = SpringContextHolder.getBean(VoiceServerService.class);
// QuartzMultiTaskManage quartzMultiTaskManage = SpringContextHolder.getBean(QuartzMultiTaskManage.class); // QuartzMultiTaskManage quartzMultiTaskManage = SpringContextHolder.getBean(QuartzMultiTaskManage.class);
if ("false".equals(config.getDoTask())) { if ("false".equals(config.getDoTask())) {
@ -377,7 +376,6 @@ public class TodoTaskJob extends QuartzJobBean implements InterruptableJob {
JSONObject callresult = null; JSONObject callresult = null;
if ("sound".equals(typelist.get(0).toString())) { if ("sound".equals(typelist.get(0).toString())) {
//声音检测 //声音检测
// rtspurl = map.get("rtspDeviceUrl").toString();
filename = String.format("%s_%s_%s_%s_%s.wav", filename = String.format("%s_%s_%s_%s_%s.wav",
DateUtil.format(DateUtil.date(), "yyyyMMdd"), DateUtil.format(DateUtil.date(), "yyyyMMdd"),
DateUtil.format(DateUtil.date(), "HHmmss"), DateUtil.format(DateUtil.date(), "HHmmss"),
@ -386,17 +384,40 @@ public class TodoTaskJob extends QuartzJobBean implements InterruptableJob {
map.get("deviceName").toString() map.get("deviceName").toString()
); );
fullfilename = filepath + filename; fullfilename = filepath + filename;
httpUtil.getMonitorAudioSnap(config.getffmpegPath(), rtspurl, fullfilename); String encodefilename = fullfilename;
int duration = 10;
if (ObjUtil.isNotEmpty(map.get("duration"))) {
duration = NumberUtil.parseInt(map.get("duration").toString());
}
if (ObjectUtil.isNotEmpty(map.get("outsideAngle"))) {
if (JSONUtil.isTypeJSONArray(map.get("outsideAngle").toString())) {
JSONArray jsonArray = JSONUtil.parseArray(map.get("outsideAngle").toString());
customParams.put("SoundAlarmThresholdList", jsonArray);
}
if (JSONUtil.isTypeJSONObject(map.get("outsideAngle").toString())) {
cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(map.get(
"outsideAngle").toString());
customParams.put("SoundAlarmThreshold", jsonObject);
}
}
try {
String patroldeviceCode = map.get("patroldeviceCode").toString();
voiceServerService.sendVoiceServerControl(patroldeviceCode, "ON", fullfilename);
Thread.sleep(duration * 1000);
voiceServerService.sendVoiceServerControl(patroldeviceCode, "OFF", fullfilename);
} catch (Exception e) {
e.printStackTrace();
}
//更新当前记录状态修改为 1已巡视(声音已采集) //更新当前记录状态修改为 1已巡视(声音已采集)
fullfilename = URLEncoder.encode(fullfilename, "utf-8");//转码存储 fullfilename = URLEncoder.encode(fullfilename, "utf-8");//转码存储
todoTaskService.updateTaskResultStatus(quartzJob.getTaskTodoId(), todoTaskService.updateTaskResultStatus(quartzJob.getTaskTodoId(),
map.get("resultId").toString(), map.get("resultId").toString(),
fullfilename, "2"); fullfilename, "2");
log.info("customParams" + JSONUtil.toJsonStr(customParams)); System.out.println("customParams" + JSONUtil.toJsonStr(customParams));
callresult = httpUtil.callPicAnalyse(map.get("taskTodoId").toString(), callresult = httpUtil.callPicAnalyse(map.get("resultId").toString(),
map.get("resultId").toString(), JSONUtil.toJsonStr(typelist), map.get("deviceId").toString(), JSONUtil.toJsonStr(typelist),
JSONUtil.toJsonStr(customParams), fullfilename); JSONUtil.toJsonStr(customParams), encodefilename, analysePort);
} else { } else {
if (!"3".equals(finalLinkageType)) { if (!"3".equals(finalLinkageType)) {

View File

@ -171,7 +171,7 @@ public class CodeGenerator {
//rca_project,rca_projectanalysisrd,rca_projectinvestigaterd,rca_projectreport,rca_projectsummary,rca_projecttarget,rca_projecttrackrd,rca_analysisguide,rca_eventeffect,rca_failurecase,rca_failurecause,rca_failureclass,rca_failuremode //rca_project,rca_projectanalysisrd,rca_projectinvestigaterd,rca_projectreport,rca_projectsummary,rca_projecttarget,rca_projecttrackrd,rca_analysisguide,rca_eventeffect,rca_failurecase,rca_failurecause,rca_failureclass,rca_failuremode
strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true); strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("fk_"); strategy.setTablePrefix("iis_");
mpg.setStrategy(strategy); mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute(); mpg.execute();

View File

@ -191,6 +191,7 @@ algorithmserver:
password: 123456 password: 123456
mqttserver: mqttserver:
enable: false
username: admin # 账号 username: admin # 账号
password: public # 密码 password: public # 密码
host-url: tcp://82.156.18.154:1883 # mqtt连接tcp地址 host-url: tcp://82.156.18.154:1883 # mqtt连接tcp地址

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yfd.platform.modules.basedata.mapper.VoicePatrolLogMapper">
</mapper>

View File

@ -87,7 +87,7 @@
AND (t.valid1= #{valid} AND (t.valid1= #{valid}
OR ( t.flag = '4' OR t.flag = '6' )) OR ( t.flag = '4' OR t.flag = '6' ))
</if> </if>
ORDER BY t.order_num ORDER BY sd.device_code
</select> </select>
<select id="getHistoricalCurve" resultType="java.util.Map"> <select id="getHistoricalCurve" resultType="java.util.Map">
SELECT SELECT