|
@@ -0,0 +1,269 @@
|
|
|
+package com.yonge.netty.server.service;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
+import java.util.concurrent.ConcurrentMap;
|
|
|
+
|
|
|
+import javax.sound.sampled.AudioFormat;
|
|
|
+
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.alibaba.fastjson.JSONPath;
|
|
|
+import com.yonge.audio.analysis.AudioFloatConverter;
|
|
|
+import com.yonge.audio.analysis.detector.YINPitchDetector;
|
|
|
+import com.yonge.audio.utils.ArrayUtil;
|
|
|
+import com.yonge.netty.dto.NoteFrequencyRange;
|
|
|
+import com.yonge.netty.dto.WebSocketResponse;
|
|
|
+import com.yonge.netty.server.handler.NettyChannelManager;
|
|
|
+import com.yonge.netty.server.handler.message.MessageHandler;
|
|
|
+import com.yonge.netty.server.processor.WaveformWriter;
|
|
|
+
|
|
|
+import io.netty.channel.Channel;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class DelayCheckHandler implements MessageHandler {
|
|
|
+
|
|
|
+ private final static Logger LOGGER = LoggerFactory.getLogger(DelayCheckHandler.class);
|
|
|
+
|
|
|
+ private int standardFrequecy = 3000;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @describe 采样率
|
|
|
+ */
|
|
|
+ private float sampleRate = 44100;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 每个采样大小(Bit)
|
|
|
+ */
|
|
|
+ private int bitsPerSample = 16;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通道数
|
|
|
+ */
|
|
|
+ private int channels = 1;
|
|
|
+
|
|
|
+ private int bufferSize = 1024 * 1;
|
|
|
+
|
|
|
+ private boolean signed = true;
|
|
|
+
|
|
|
+ private boolean bigEndian = false;
|
|
|
+
|
|
|
+ private AudioFormat audioFormat = new AudioFormat(sampleRate, bitsPerSample, channels, signed, bigEndian);
|
|
|
+
|
|
|
+ private AudioFloatConverter converter = AudioFloatConverter.getConverter(audioFormat);
|
|
|
+
|
|
|
+ private boolean isRecWav = false;
|
|
|
+
|
|
|
+ private ConcurrentMap<Channel, UserContext> userABCMap = new ConcurrentHashMap<Channel, UserContext>();
|
|
|
+
|
|
|
+ private String tmpFileDir = "/mdata/soundCompare/";
|
|
|
+
|
|
|
+ private SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmSS");
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private NettyChannelManager nettyChannelManager;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getAction() {
|
|
|
+ return "DELAY_CHECK";
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean handleTextMessage(String userId, Channel channel, String jsonMsg) {
|
|
|
+
|
|
|
+ String command = (String) JSONPath.extract(jsonMsg, "$.header.commond");
|
|
|
+
|
|
|
+ UserContext userContext = null;
|
|
|
+
|
|
|
+ switch (command) {
|
|
|
+ case "recordStart":
|
|
|
+
|
|
|
+ userContext = new UserContext(0, false);
|
|
|
+
|
|
|
+ userABCMap.put(channel, userContext);
|
|
|
+
|
|
|
+ JSONObject dataObj = JSONObject.parseObject(JSON.toJSONString(JSONPath.extract(jsonMsg, "$.body")));
|
|
|
+
|
|
|
+ if(dataObj.get("HZ") != null) {
|
|
|
+ String hzStr = dataObj.get("HZ").toString();
|
|
|
+ standardFrequecy = Integer.parseInt(hzStr);
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ case "recordEnd":
|
|
|
+
|
|
|
+ userContext = userABCMap.get(channel);
|
|
|
+
|
|
|
+ if (userContext == null) {
|
|
|
+ userContext = new UserContext(0, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ WaveformWriter waveFileProcessor = userContext.getWaveformWriter();
|
|
|
+ if (waveFileProcessor != null) {
|
|
|
+ // 写文件头
|
|
|
+ waveFileProcessor.processingFinished();
|
|
|
+ }
|
|
|
+
|
|
|
+ userContext = userABCMap.get(channel);
|
|
|
+
|
|
|
+ if (userContext == null) {
|
|
|
+ userContext = new UserContext(0, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> params = new HashMap<String, Object>();
|
|
|
+ params.put("firstNoteDelayDuration", userContext.getDelayDuration());
|
|
|
+
|
|
|
+ WebSocketResponse<Map<String, Object>> resp = new WebSocketResponse<Map<String, Object>>(getAction(), command, params);
|
|
|
+
|
|
|
+ nettyChannelManager.sendTextMessage(userId, resp);
|
|
|
+
|
|
|
+ userContext.setDelayDuration(0);
|
|
|
+ userContext.setIsOver(false);
|
|
|
+ userContext.setWaveformWriter(null);
|
|
|
+ userContext.setChannelBufferBytes(new byte[0]);
|
|
|
+ userABCMap.put(channel, userContext);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean handleBinaryMessage(String userId, Channel channel, byte[] bytes) {
|
|
|
+
|
|
|
+ UserContext userContext = userABCMap.get(channel);
|
|
|
+
|
|
|
+ if (userContext == null) {
|
|
|
+ userContext = new UserContext(0, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 写录音文件
|
|
|
+ if (isRecWav) {
|
|
|
+ WaveformWriter waveFileProcessor = userContext.getWaveformWriter();
|
|
|
+ if (waveFileProcessor == null) {
|
|
|
+ File file = new File(tmpFileDir + userId + "_CHECK_" + sdf.format(new Date()) + ".wav");
|
|
|
+ waveFileProcessor = new WaveformWriter(file.getAbsolutePath());
|
|
|
+ userContext.setWaveformWriter(waveFileProcessor);
|
|
|
+
|
|
|
+ userABCMap.put(channel, userContext);
|
|
|
+ }
|
|
|
+ waveFileProcessor.process(bytes);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (userContext.isOver) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ userContext.setChannelBufferBytes(ArrayUtil.mergeByte(userContext.getChannelBufferBytes(), bytes));
|
|
|
+
|
|
|
+ int totalLength = userContext.getChannelBufferBytes().length;
|
|
|
+
|
|
|
+ while (totalLength >= bufferSize) {
|
|
|
+ byte[] bufferData = ArrayUtil.extractByte(userContext.getChannelBufferBytes(), 0, bufferSize - 1);
|
|
|
+
|
|
|
+ if (bufferSize != totalLength) {
|
|
|
+ userContext.setChannelBufferBytes(ArrayUtil.extractByte(userContext.getChannelBufferBytes(), bufferSize, totalLength - 1));
|
|
|
+ } else {
|
|
|
+ userContext.setChannelBufferBytes(new byte[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ float[] sampleFloats = new float[bufferSize / 2];
|
|
|
+
|
|
|
+ converter.toFloatArray(bufferData, sampleFloats);
|
|
|
+
|
|
|
+ YINPitchDetector frequencyDetector = new YINPitchDetector(sampleFloats.length, audioFormat.getSampleRate());
|
|
|
+
|
|
|
+ int playFrequency = (int) frequencyDetector.getFrequency(sampleFloats);
|
|
|
+
|
|
|
+ // int amplitude = (int) Signals.decibels(samples);
|
|
|
+
|
|
|
+ double durationTime = 1000 * (sampleFloats.length * 2) / audioFormat.getSampleRate() / (audioFormat.getSampleSizeInBits() / 8);
|
|
|
+
|
|
|
+ double playTime = userContext.delayDuration;
|
|
|
+
|
|
|
+ playTime += durationTime;
|
|
|
+
|
|
|
+ LOGGER.info("DurationTime:{} playFrequency:{} PlayTime:{}" ,durationTime,playFrequency,playTime);
|
|
|
+
|
|
|
+ NoteFrequencyRange nfr = new NoteFrequencyRange(440, playFrequency);
|
|
|
+
|
|
|
+ if (nfr.getMinFrequency() < standardFrequecy && nfr.getMaxFrequency() > standardFrequecy) {
|
|
|
+
|
|
|
+ userContext.setIsOver(true);
|
|
|
+ userABCMap.put(channel, userContext);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ userContext.setDelayDuration(playTime);
|
|
|
+
|
|
|
+ totalLength = userContext.getChannelBufferBytes().length;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ public ConcurrentMap<Channel, UserContext> getUserABCMap() {
|
|
|
+ return userABCMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ class UserContext {
|
|
|
+
|
|
|
+ public UserContext(double delayDuration, boolean isOver) {
|
|
|
+ this.delayDuration = delayDuration;
|
|
|
+ this.isOver = isOver;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double delayDuration;
|
|
|
+
|
|
|
+ private boolean isOver;
|
|
|
+
|
|
|
+ private byte[] channelBufferBytes = new byte[0];
|
|
|
+
|
|
|
+ private WaveformWriter waveformWriter;
|
|
|
+
|
|
|
+ public double getDelayDuration() {
|
|
|
+ return delayDuration;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setDelayDuration(double delayDuration) {
|
|
|
+ this.delayDuration = delayDuration;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isOver() {
|
|
|
+ return isOver;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setIsOver(boolean isOver) {
|
|
|
+ this.isOver = isOver;
|
|
|
+ }
|
|
|
+
|
|
|
+ public byte[] getChannelBufferBytes() {
|
|
|
+ return channelBufferBytes;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setChannelBufferBytes(byte[] channelBufferBytes) {
|
|
|
+ this.channelBufferBytes = channelBufferBytes;
|
|
|
+ }
|
|
|
+
|
|
|
+ public WaveformWriter getWaveformWriter() {
|
|
|
+ return waveformWriter;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setWaveformWriter(WaveformWriter waveformWriter) {
|
|
|
+ this.waveformWriter = waveformWriter;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|