AudioCompareHandler.java 14 KB

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