AudioCompareHandler.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. package com.yonge.netty.server.service;
  2. import io.netty.channel.Channel;
  3. import java.io.File;
  4. import java.math.BigDecimal;
  5. import java.text.SimpleDateFormat;
  6. import java.util.Comparator;
  7. import java.util.Date;
  8. import java.util.HashMap;
  9. import java.util.List;
  10. import java.util.Map;
  11. import java.util.Map.Entry;
  12. import java.util.Objects;
  13. import java.util.stream.Collectors;
  14. import javax.sound.sampled.AudioFormat;
  15. import org.apache.commons.lang3.StringUtils;
  16. import org.slf4j.Logger;
  17. import org.slf4j.LoggerFactory;
  18. import org.springframework.beans.factory.annotation.Autowired;
  19. import org.springframework.stereotype.Component;
  20. import com.alibaba.fastjson.JSON;
  21. import com.alibaba.fastjson.JSONObject;
  22. import com.alibaba.fastjson.JSONPath;
  23. import com.ym.mec.auth.api.client.SysUserFeignService;
  24. import com.ym.mec.biz.dal.entity.SysMusicCompareRecord;
  25. import com.ym.mec.biz.dal.enums.DeviceTypeEnum;
  26. import com.ym.mec.biz.dal.enums.FeatureType;
  27. import com.ym.mec.biz.dal.enums.HeardLevelEnum;
  28. import com.ym.mec.biz.service.SysMusicCompareRecordService;
  29. import com.ym.mec.thirdparty.storage.StoragePluginContext;
  30. import com.ym.mec.thirdparty.storage.provider.KS3StoragePlugin;
  31. import com.ym.mec.util.upload.UploadUtil;
  32. import com.yonge.audio.analysis.AudioFloatConverter;
  33. import com.yonge.audio.utils.ArrayUtil;
  34. import com.yonge.netty.dto.SectionAnalysis;
  35. import com.yonge.netty.dto.UserChannelContext;
  36. import com.yonge.netty.dto.WebSocketResponse;
  37. import com.yonge.netty.entity.MusicXmlBasicInfo;
  38. import com.yonge.netty.entity.MusicXmlNote;
  39. import com.yonge.netty.server.handler.NettyChannelManager;
  40. import com.yonge.netty.server.handler.message.MessageHandler;
  41. import com.yonge.netty.server.processor.WaveformWriter;
  42. @Component
  43. public class AudioCompareHandler implements MessageHandler {
  44. private static final Logger LOGGER = LoggerFactory.getLogger(AudioCompareHandler.class);
  45. @Autowired
  46. private UserChannelContextService userChannelContextService;
  47. @Autowired
  48. private NettyChannelManager nettyChannelManager;
  49. @Autowired
  50. private SysMusicCompareRecordService sysMusicCompareRecordService;
  51. @Autowired
  52. private SysUserFeignService sysUserFeignService;
  53. @Autowired
  54. private StoragePluginContext storagePluginContext;
  55. /**
  56. * @describe 采样率
  57. */
  58. private float sampleRate = 44100;
  59. /**
  60. * 每个采样大小(Bit)
  61. */
  62. private int bitsPerSample = 16;
  63. /**
  64. * 通道数
  65. */
  66. private int channels = 1;
  67. /**
  68. * @describe 采样大小
  69. */
  70. private int bufferSize = 1024 * 2;
  71. private boolean signed = true;
  72. private boolean bigEndian = false;
  73. private AudioFormat audioFormat = new AudioFormat(sampleRate, bitsPerSample, channels, signed, bigEndian);
  74. private AudioFloatConverter converter = AudioFloatConverter.getConverter(audioFormat);
  75. private String tmpFileDir = "/mdata/soundCompare/";
  76. private SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmSS");
  77. @Override
  78. public String getAction() {
  79. return "SOUND_COMPARE";
  80. }
  81. @Override
  82. public boolean handleTextMessage(String user, Channel channel, String jsonMsg) {
  83. String command = (String) JSONPath.extract(jsonMsg, "$.header.commond");
  84. JSONObject dataObj = (JSONObject) JSONPath.extract(jsonMsg, "$.body");
  85. UserChannelContext channelContext = userChannelContextService.getChannelContext(channel);
  86. MusicXmlBasicInfo musicXmlBasicInfo = null;
  87. switch (command) {
  88. case "musicXml": // 同步music xml信息
  89. musicXmlBasicInfo = JSONObject.toJavaObject(dataObj, MusicXmlBasicInfo.class);
  90. userChannelContextService.remove(channel);
  91. channelContext = new UserChannelContext();
  92. channelContext.setHandlerSwitch(false);
  93. channelContext.getSongMusicXmlMap().put(musicXmlBasicInfo.getExamSongId(), musicXmlBasicInfo);
  94. channelContext.init(musicXmlBasicInfo, audioFormat.getSampleRate(), bufferSize / 2);
  95. channelContext.setUser(user);
  96. userChannelContextService.register(channel, channelContext);
  97. break;
  98. case "recordStart": // 开始评测
  99. // 清空缓存信息
  100. channelContext.resetUserInfo();
  101. channelContext.setHandlerSwitch(false);
  102. musicXmlBasicInfo = channelContext.getMusicXmlBasicInfo(null);
  103. if (musicXmlBasicInfo != null) {
  104. Date date = new Date();
  105. SysMusicCompareRecord sysMusicCompareRecord = new SysMusicCompareRecord(FeatureType.CLOUD_STUDY_EVALUATION);
  106. sysMusicCompareRecord.setCreateTime(date);
  107. sysMusicCompareRecord.setUserId(Integer.parseInt(user));
  108. sysMusicCompareRecord.setSysMusicScoreId(musicXmlBasicInfo.getExamSongId());
  109. sysMusicCompareRecord.setBehaviorId(musicXmlBasicInfo.getBehaviorId());
  110. sysMusicCompareRecord.setClientId(musicXmlBasicInfo.getClientId());
  111. sysMusicCompareRecord.setDeviceType(DeviceTypeEnum.valueOf(musicXmlBasicInfo.getPlatform()));
  112. sysMusicCompareRecord.setSpeed(musicXmlBasicInfo.getSpeed());
  113. sysMusicCompareRecord.setPartIndex(musicXmlBasicInfo.getPartIndex());
  114. //SysUser sysUser = sysUserFeignService.queryUserById(sysMusicCompareRecord.getUserId());
  115. sysMusicCompareRecord.setTenantId(musicXmlBasicInfo.getTenantId());
  116. MusicXmlNote musicXmlNote = musicXmlBasicInfo.getMusicXmlInfos().stream().max(Comparator.comparing(MusicXmlNote::getTimeStamp)).get();
  117. sysMusicCompareRecord.setSourceTime((float) ((musicXmlNote.getTimeStamp()+musicXmlNote.getDuration())/1000));
  118. sysMusicCompareRecordService.insert(sysMusicCompareRecord,musicXmlBasicInfo.getCampId());
  119. channelContext.setRecordId(sysMusicCompareRecord.getId());
  120. }
  121. break;
  122. case "recordEnd": // 结束评测
  123. case "recordCancel": // 取消评测
  124. if (channelContext == null) {
  125. return false;
  126. }
  127. channelContext.setHandlerSwitch(false);
  128. WaveformWriter waveFileProcessor = channelContext.getWaveFileProcessor();
  129. if (waveFileProcessor != null) {
  130. // 写文件头
  131. waveFileProcessor.processingFinished();
  132. }
  133. if (StringUtils.equals(command, "recordEnd")) {
  134. // 生成评测报告
  135. Map<String, Object> params = new HashMap<String, Object>();
  136. Map<String, Integer> scoreMap = channelContext.evaluateForMusic();
  137. for (Entry<String, Integer> entry : scoreMap.entrySet()) {
  138. params.put(entry.getKey(), entry.getValue());
  139. }
  140. //保存评测结果
  141. Long recordId = channelContext.getRecordId();
  142. SysMusicCompareRecord sysMusicCompareRecord = sysMusicCompareRecordService.get(recordId);
  143. if(sysMusicCompareRecord != null){
  144. musicXmlBasicInfo = channelContext.getMusicXmlBasicInfo(null);
  145. if (scoreMap != null && scoreMap.size() > 1) {
  146. sysMusicCompareRecord.setScore(new BigDecimal(scoreMap.get("score")));
  147. sysMusicCompareRecord.setIntonation(new BigDecimal(scoreMap.get("intonation")));
  148. sysMusicCompareRecord.setIntegrity(new BigDecimal(scoreMap.get("integrity")));
  149. sysMusicCompareRecord.setCadence(new BigDecimal(scoreMap.get("cadence")));
  150. sysMusicCompareRecord.setPlayTime(scoreMap.get("playTime") / 1000);
  151. LOGGER.info("Score:{} Intonation:{} Integrity:{} Cadence:{}", sysMusicCompareRecord.getScore(),sysMusicCompareRecord.getIntonation(),sysMusicCompareRecord.getIntegrity(),sysMusicCompareRecord.getCadence());
  152. }
  153. sysMusicCompareRecord.setFeature(FeatureType.CLOUD_STUDY_EVALUATION);
  154. String url = null;
  155. try {
  156. String folder = UploadUtil.getFileFloder();
  157. url = storagePluginContext.asyncUploadFile(KS3StoragePlugin.PLUGIN_NAME,"cloud-coach/" + folder, waveFileProcessor.getFile(), true);
  158. } catch (Exception e) {
  159. LOGGER.error("录音文件上传失败:{}", e);
  160. }
  161. sysMusicCompareRecord.setRecordFilePath(url);
  162. //sysMusicCompareRecord.setVideoFilePath(videoFilePath);
  163. Map<String, Object> scoreData = new HashMap<>();
  164. List<SectionAnalysis> sectionAnalysisList = channelContext.getDoneSectionAnalysisList();
  165. sectionAnalysisList = sectionAnalysisList.stream().filter(t -> t.isIngore() == false).collect(Collectors.toList());
  166. scoreData.put("userMeasureScore", sectionAnalysisList.stream().collect(Collectors.toMap(SectionAnalysis :: getIndex, t -> t)));
  167. Map<String, Object> musicalNotesPlayStats = new HashMap<>();
  168. musicalNotesPlayStats.put("detailId", musicXmlBasicInfo.getDetailId());
  169. musicalNotesPlayStats.put("examSongId", musicXmlBasicInfo.getExamSongId());
  170. musicalNotesPlayStats.put("xmlUrl", musicXmlBasicInfo.getXmlUrl());
  171. musicalNotesPlayStats.put("notesData", channelContext.getDoneNoteAnalysisList().stream().filter(t -> t.isIgnore() == false).collect(Collectors.toList()));
  172. scoreData.put("musicalNotesPlayStats", musicalNotesPlayStats);
  173. sysMusicCompareRecord.setScoreData(JSON.toJSONString(scoreData));
  174. sysMusicCompareRecord.setHeardLevel(HeardLevelEnum.valueOf(channelContext.getHardLevel().name()));
  175. sysMusicCompareRecordService.saveMusicCompareData(sysMusicCompareRecord);
  176. }
  177. int totalPlayTimeOfCurrentDate = sysMusicCompareRecordService.queryCurrentDatePlayTimeByUserId(sysMusicCompareRecord.getUserId());
  178. params.put("totalPlayTimeOfCurrentDate", totalPlayTimeOfCurrentDate);
  179. WebSocketResponse<Map<String, Object>> resp = new WebSocketResponse<Map<String, Object>>("overall", params);
  180. nettyChannelManager.sendTextMessage(user, resp);
  181. }
  182. // 清空缓存信息
  183. channelContext.resetUserInfo();
  184. break;
  185. case "audioPlayStart": // ???
  186. Integer offsetTime = dataObj.getInteger("offsetTime");
  187. if(offsetTime != null){
  188. channelContext.setOffsetMS(offsetTime);
  189. channelContext.setHandlerSwitch(true);
  190. }
  191. break;
  192. case "videoUpload": // 上传音频
  193. SysMusicCompareRecord musicCompareRecord = null;
  194. if (dataObj.containsKey("recordId")) {
  195. musicCompareRecord = sysMusicCompareRecordService.get(dataObj.getLong("recordId"));
  196. }
  197. if (Objects.nonNull(musicCompareRecord) && dataObj.containsKey("filePath")) {
  198. musicCompareRecord.setVideoFilePath(dataObj.getString("filePath"));
  199. sysMusicCompareRecordService.update(musicCompareRecord);
  200. } else {
  201. musicCompareRecord.setVideoFilePath(musicCompareRecord.getRecordFilePath());
  202. sysMusicCompareRecordService.update(musicCompareRecord);
  203. }
  204. break;
  205. default:
  206. // 非法请求
  207. break;
  208. }
  209. return true;
  210. }
  211. @Override
  212. public boolean handleBinaryMessage(String user, Channel channel, byte[] datas) {
  213. UserChannelContext channelContext = userChannelContextService.getChannelContext(channel);
  214. if (channelContext == null) {
  215. return false;
  216. }
  217. // 写录音文件
  218. WaveformWriter waveFileProcessor = channelContext.getWaveFileProcessor();
  219. if (waveFileProcessor == null) {
  220. File file = new File(tmpFileDir + user + "_" + sdf.format(new Date()) + ".wav");
  221. waveFileProcessor = new WaveformWriter(file.getAbsolutePath());
  222. channelContext.setWaveFileProcessor(waveFileProcessor);
  223. }
  224. waveFileProcessor.process(datas);
  225. /*datas = channelContext.skipMetronome(datas);
  226. if (datas.length == 0) {
  227. return false;
  228. }*/
  229. channelContext.setChannelBufferBytes(ArrayUtil.mergeByte(channelContext.getChannelBufferBytes(), datas));
  230. int totalLength = channelContext.getChannelBufferBytes().length;
  231. if (channelContext.getHandlerSwitch() == false) {
  232. return false;
  233. }
  234. if (channelContext.getOffsetMS() + channelContext.getBeatDuration() > 0) {
  235. int beatByteLength = (int) (audioFormat.getSampleRate() * audioFormat.getSampleSizeInBits() / 8 * (channelContext.getOffsetMS() + channelContext.getBeatDuration()) / 1000);
  236. if(totalLength > beatByteLength){
  237. if(beatByteLength % 2 != 0){
  238. LOGGER.debug("**************奇数*****************");
  239. beatByteLength--;
  240. }
  241. channelContext.setChannelBufferBytes(ArrayUtil.extractByte(channelContext.getChannelBufferBytes(), beatByteLength, totalLength - 1));
  242. LOGGER.debug("--------Length:{} Times[{} + {}]:{}--------", waveFileProcessor.getFile().length() - channelContext.getChannelBufferBytes().length, channelContext.getOffsetMS() , channelContext.getBeatDuration(),(waveFileProcessor.getFile().length() - channelContext.getChannelBufferBytes().length) * 1000 /audioFormat.getSampleRate()/2);
  243. channelContext.setOffsetMS(0);
  244. channelContext.setBeatDuration(0);
  245. }else{
  246. return false;
  247. }
  248. }
  249. totalLength = channelContext.getChannelBufferBytes().length;
  250. while (totalLength >= bufferSize) {
  251. byte[] bufferData = ArrayUtil.extractByte(channelContext.getChannelBufferBytes(), 0, bufferSize - 1);
  252. if (bufferSize != totalLength) {
  253. channelContext.setChannelBufferBytes(ArrayUtil.extractByte(channelContext.getChannelBufferBytes(), bufferSize, totalLength - 1));
  254. } else {
  255. channelContext.setChannelBufferBytes(new byte[0]);
  256. }
  257. float[] sampleFloats = new float[bufferSize / 2];
  258. converter.toFloatArray(bufferData, sampleFloats);
  259. channelContext.handle(sampleFloats, audioFormat);
  260. MusicXmlBasicInfo musicXmlBasicInfo = channelContext.getMusicXmlBasicInfo(null);
  261. int sectionIndex = channelContext.getEvaluatingSectionIndex().get();
  262. // 评分
  263. int score = channelContext.evaluateForSection(sectionIndex, musicXmlBasicInfo.getSubjectId());
  264. if (score >= 0) {
  265. Map<String, Object> params = new HashMap<String, Object>();
  266. params.put("score", score);
  267. params.put("measureIndex", sectionIndex);
  268. params.put("measureRenderIndex", channelContext.getCurrentMusicSection(null, sectionIndex).getMeasureRenderIndex());
  269. WebSocketResponse<Map<String, Object>> resp = new WebSocketResponse<Map<String, Object>>("measureScore", params);
  270. nettyChannelManager.sendTextMessage(user, resp);
  271. }
  272. totalLength = channelContext.getChannelBufferBytes().length;
  273. }
  274. return true;
  275. }
  276. }