| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627 |
- package api.service.quality.impl;
- import api.entity.database.call.Translate;
- import api.entity.database.quality.*;
- import api.entity.input.PageInput;
- import api.entity.input.quality.QcResultPush;
- import api.entity.input.quality.RepTranslate;
- import api.entity.view.quality.ExcelSentiment;
- import api.entity.view.quality.HitRules;
- import api.mapper.quality.QcResultMapper;
- import api.service.call.ITranslateService;
- import api.service.quality.*;
- import api.service.BaseServiceImpl;
- import api.service.system.IConfigService;
- import api.util.annotation.Log;
- import api.util.enums.RulesCondition;
- import api.util.helper.DateHelper;
- import api.util.helper.StringHelper;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.baomidou.mybatisplus.core.metadata.IPage;
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
- import com.fasterxml.jackson.core.JsonProcessingException;
- import com.fasterxml.jackson.databind.ObjectMapper;
- //import edu.stanford.nlp.ling.CoreAnnotations;
- //import edu.stanford.nlp.neural.rnn.RNNCoreAnnotations;
- //import edu.stanford.nlp.pipeline.Annotation;
- //import edu.stanford.nlp.pipeline.StanfordCoreNLP;
- //import edu.stanford.nlp.sentiment.SentimentCoreAnnotations;
- //import edu.stanford.nlp.util.CoreMap;
- //import com.hankcs.hanlp.HanLP;
- import lombok.var;
- import opennlp.tools.doccat.*;
- import opennlp.tools.tokenize.SimpleTokenizer;
- import opennlp.tools.util.CollectionObjectStream;
- import opennlp.tools.util.ObjectStream;
- import opennlp.tools.util.model.ModelUtil;
- import org.apache.poi.ss.usermodel.Cell;
- import org.apache.poi.ss.usermodel.Row;
- import org.apache.poi.ss.usermodel.Sheet;
- import org.apache.poi.ss.usermodel.Workbook;
- import org.apache.poi.xssf.usermodel.XSSFWorkbook;
- import org.slf4j.Logger;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import javax.sound.sampled.*;
- import java.io.*;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.nio.ByteBuffer;
- import java.nio.ByteOrder;
- import java.nio.MappedByteBuffer;
- import java.nio.channels.FileChannel;
- import java.nio.charset.StandardCharsets;
- import java.nio.file.Files;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.time.Duration;
- import java.time.Instant;
- import java.time.LocalDate;
- import java.util.*;
- @Transactional
- @Service
- public class QcResultServiceImpl extends BaseServiceImpl<QcResultMapper, QcResult> implements IQcResultService{
- public QcResultServiceImpl(){ super(false); }
- //质检规则主体
- @Autowired
- private IInspectionRulesService inspectionrulesService;
- //通话翻译主体
- @Autowired
- private ITranslateService translateService;
- //评分标准主体
- @Autowired
- private IScoringCriteriaService scoringCriteriaService;
- //系统配置主体
- @Autowired
- private IConfigService configService;
- //触发规则主体
- @Autowired
- private ITriggerRulesService triggerRulesService;
- //词汇表
- @Autowired
- private ISearchLexiconService searchLexiconService;
- @Autowired
- private IQualityModelService modelService;
- private <T> Page<T> GetPage(PageInput page) {
- if (page.getPageNum() != null && page.getPageSize() != null) {
- Page<T> ipage = new Page<>();
- ipage.setCurrent(page.getPageNum());
- ipage.setSize(page.getPageSize());
- return ipage;
- } else {
- return null;
- }
- }
- //分隔音频获取音量音速
- @Override
- public List<String> GetAudio(String callId) throws UnsupportedAudioFileException, IOException {
- //获取通话记录
- LambdaQueryWrapper<QcResult> qw =new LambdaQueryWrapper<>();
- //qw.apply("IFNULL(user_name,'') != ''");
- qw.apply("IFNULL(file_path,'') != ''");
- qw.eq(QcResult::getIsQuality,0);
- // if (!Objects.equals(callId, ""))
- // {
- // qw.eq(QcResult::getCallid,callId);
- // }
- // qw.eq(QcResult::getUserName,"8002");
- // qw.ge (QcResult::getCallid,580);
- qw.orderByAsc(QcResult::getQcId);
- PageInput pageInput=new PageInput();
- pageInput.setPageNum(1);
- pageInput.setPageSize(100);
- Page<QcResult> page = GetPage(pageInput);
- IPage<QcResult> iPage = this.getListPage(page, qw);
- // var calls=this.getList(qw);
- var calls=iPage.getRecords();
- List<String> audio=new ArrayList<>();
- if (calls!=null&&calls.size()>0)
- {
- for (QcResult call : calls )
- {
- //获取质检规则
- LambdaQueryWrapper<InspectionRules> qe =new LambdaQueryWrapper<>();
- qe.le ( InspectionRules::getStartTime, new Date());
- qe.ge ( InspectionRules::getEndTime, new Date());
- qe.eq(InspectionRules::getIsDelete,0);
- qe.like(InspectionRules::getApplySeats,call.getUserName());
- var Rules=inspectionrulesService.getList(qe);
- new InspectionRules();
- InspectionRules rule;
- if (Rules!=null&&Rules.size()>0)
- {
- rule=Rules.get(0);
- }
- else
- {
- //当前时间无质检规则直接抛出
- continue;
- }
- String FileUrl="";String URL="";
- //获取录音路径
- if (!StringHelper.isEmpty(call.getFilePath())) {
- URL="http://1.194.161.64:9000/"+call.getFilePath();
- FileUrl= downloadFile(URL,"files/luYin/"+ DateHelper.getDate());
- }
- if (Objects.equals(FileUrl, ""))
- {
- continue;
- }
- call.setRuleId(rule.getId());
- LambdaQueryWrapper<Translate> qt=new LambdaQueryWrapper<>();
- //获取语音翻译
- qt.eq(Translate::getCallid,call.getCallid());
- var mobile=translateService.getList(qt);
- if (mobile!=null&&mobile.size()>0)
- {
- ObjectMapper mapper = new ObjectMapper();
- try
- {
- if (StringHelper.isNotEmpty(mobile.get(0).getTranslate()))
- {
- //解析同声翻译
- RepTranslate [] translate = mapper.readValue(mobile.get(0).getTranslate(), RepTranslate[].class);
- StringBuilder customerSpeech= new StringBuilder();StringBuilder seatSpeech= new StringBuilder();
- int duration=0;int Time=0;
- for (RepTranslate item :translate)
- {
- if(item.getNumber().length()>4)
- {
- customerSpeech.append(item.getSpeech());
- }
- else
- {
- seatSpeech.append(item.getSpeech());
- }
- int[] Millisecond=GetMillisecond(item.getTime());
- if (Millisecond!=null&&Millisecond.length>0)
- {
- duration+=Millisecond[0]-Time;
- Time=Millisecond[1];
- }
- }
- double decibel = calculateRMSVolume(new File(FileUrl));//获取音量
- call.setVolume(String.format("%.2f", decibel));
- int wordCount=returnCharacters(customerSpeech.toString()).length()+returnCharacters(seatSpeech.toString()).length();
- double speechRate=calculateSpeechRate( wordCount, new File(FileUrl));//获取语速
- call.setSpeed(String.format("%.2f", speechRate));
- //静音时长
- call.setMute((long) (duration/1000));
- //客户情绪
- call.setCustomerEmotion(modelService.OpenNLPDetection(customerSpeech.toString()));
- //坐席情绪
- call.setSeatEmotion(modelService.OpenNLPDetection(seatSpeech.toString()));
- //分段录音解析音量音速
- try {
- GetRepTranslates(translate, FileUrl);
- } catch (Exception e) {
- e.printStackTrace();
- }
- LambdaQueryWrapper<ScoringCriteria> qs =new LambdaQueryWrapper<>();
- qs.eq(ScoringCriteria::getIsDelete,0);//是否确认
- qs.eq(ScoringCriteria::getRulesId,rule.getId());//规则id
- var scoring=scoringCriteriaService.getList(qs);
- long score=100;int index=0;List<TriggerRules> AllTriggerRules=new ArrayList<>();
- int start=0;int validity = 0;
- if (scoring!=null&&scoring.size()>0)
- {
- LinkedHashMap<Long,List<String>> keyWorlds=keyWorlds(scoring);
- var filterKeyWorlds=filterKeyWorlds();
- for (RepTranslate item :translate)
- {
- index=index+1; List<TriggerRules> triggerRules=new ArrayList<>();
- StringBuilder trigger= new StringBuilder();
- if(item.getNumber().length()>4)
- {
- //只质检话务员,客户语音跳过
- if ( index!=translate.length)
- {
- continue;
- }
- }
- else
- {
- start=start+1;
- }
- for (ScoringCriteria scoringCriteria :scoring)
- {
- String value= RulesCondition.getTypeValue(scoringCriteria.getConditionName());
- TriggerRules triggerRules1=new TriggerRules();
- triggerRules1.setRulesId(rule.getId());//规则id
- triggerRules1.setRulesName(rule.getRuleName());//触发规则名称
- triggerRules1.setScoringId(scoringCriteria.getId());//触发标准id
- triggerRules1.setScoringName(value);//触发标准名称
- triggerRules1.setQualityId(call.getQcId());
- triggerRules1.setSoundName(item.getFilePath());
- HitRules hit =new HitRules();
- hit.setId(scoringCriteria.getId());
- hit.setName(value);
- hit.setScore(scoringCriteria.getScore());
- if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.prologue.name())
- &&start!=1)
- {
- //检测开场白非第一条跳过
- continue;
- }
- else if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.conclusion.name())&&
- index!=translate.length)
- {
- //检测结束语非不是最后一条跳过
- continue;
- }
- if(item.getNumber().length()>4&&Objects.equals(scoringCriteria.getConditionName(), RulesCondition.conclusion.name()))
- {
- for (int i=translate.length-1;i>=0;i--)
- {
- if (translate[i].getNumber().length()<=4)
- {
- item=translate[i];
- break;
- }
- }
- }
- else if(item.getNumber().length()>4)
- {
- continue;
- }
- if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.ringing.name())
- &&start==1)
- {
- if (call.getRingTime()!=null&& call.getAnswerTime()!=null)
- {
- // 将Date转换为Instant
- Instant instant1 = call.getRingTime().toInstant();
- Instant instant2 = call.getAnswerTime().toInstant();
- // 计算时间差
- long seconds = Duration.between(instant1, instant2).getSeconds() ;
- if (scoringCriteria.getMaxValue()!=null &&seconds>scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- // trigger.append(",振铃时长").append(seconds).append("命中规则条件超过")
- // .append(value);
- }
- }
- }
- else if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.duration.name())
- &&start==1)
- {
- if (call.getAnswerTime()!=null&& call.getHangupTime()!=null)
- {
- // 将Date转换为Instant
- Instant instant1 = call.getAnswerTime().toInstant();
- Instant instant2 = call.getHangupTime().toInstant();
- // 计算时间差
- long seconds = Duration.between(instant1, instant2).getSeconds() ;
- if (scoringCriteria.getMaxValue()!=null &&scoringCriteria.getMaxValue()>0&&
- scoringCriteria.getMinValue()!=null&&scoringCriteria.getMinValue()>0
- )
- {
- if ( seconds>scoringCriteria.getMaxValue() ||seconds<scoringCriteria.getMinValue())
- {
- triggerRules.add(triggerRules1);
- // trigger.append(",通话时长异常").append("命中规则条件")
- // .append(value);
- }
- }
- else if (scoringCriteria.getMaxValue()!=null &&scoringCriteria.getMaxValue()>0 &&seconds>scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- // trigger.append(",通话时长异常").append("命中规则条件")
- // .append(value);
- }
- else if (scoringCriteria.getMinValue()!=null&&scoringCriteria.getMinValue()>0 &&seconds<scoringCriteria.getMinValue())
- {
- triggerRules.add(triggerRules1);
- //trigger.append(",通话时长异常").append("命中规则条件")
- // .append(value);
- }
- }
- }
- else if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.mute.name())
- &&start==1)
- {
- if (scoringCriteria.getMaxValue()!=null &&(long) (duration/1000)>scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- // trigger.append(",静音分析").append((long) (duration/1000)).append("命中规则条件超过")
- // .append(value);
- }
- }
- else if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.satisfaction.name())
- &&start==1)
- {
- if (call.getMyd()!=null&&call.getMyd()==3)
- {
- triggerRules.add(triggerRules1);
- // trigger.append(",用户满意度").append("不满意").append("命中规则条件")
- // .append(value);
- }
- }
- else if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.emotional.name())
- )
- {
- if (StringHelper.isNotEmpty(item.getSpeech())
- &&item.getSpeech().length()>10)
- {
- if(Objects.equals(modelService.OpenNLPDetection(item.getSpeech()), "消极"))
- {
- triggerRules.add(triggerRules1);
- hit.setHit("消极");
- trigger.append(",").append(hit.toJsonString());
- }
- }
- }
- else if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.response.name())
- &&start==1)
- {
- if (scoringCriteria.getMaxValue()!=null)
- {
- if (index==1)
- {
- int[] Millisecond=GetMillisecond(item.getTime());
- if(Millisecond!=null)
- {
- validity=Millisecond[0]/1000;
- if (validity>scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- hit.setHit(String.valueOf(validity));
- trigger.append(",").append(hit.toJsonString());
- }
- }
- }
- else
- {
- int[] Millisecond=GetMillisecond(item.getTime());
- if(Millisecond!=null)
- {
- int[] Millisecond1=GetMillisecond(translate[index-2].getTime());
- validity=(Millisecond[0]-Millisecond1[1])/1000;
- if (validity>scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- hit.setHit(String.valueOf(validity));
- trigger.append(",").append(hit.toJsonString());
- }
- }
- }
- }
- }
- else
- {
- //声音过短不质检
- if (StringHelper.isNotEmpty(item.getSpeech())
- &&(item.getSpeech().length()>2||Objects.equals(scoringCriteria.getConditionName(), RulesCondition.prologue.name())
- ||Objects.equals(scoringCriteria.getConditionName(), RulesCondition.conclusion.name()))) {
- //当前评分为音量
- if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.volume.name())) {
- if (scoringCriteria.getMaxValue()!=null &&scoringCriteria.getMinValue()!=null
- ){
- if(Double.parseDouble(item.getVolume()) < scoringCriteria.getMinValue()
- ||Double.parseDouble(item.getVolume())> scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- hit.setHit(String.valueOf(item.getVolume()));
- trigger.append(",").append(hit.toJsonString());
- }
- }
- else if (scoringCriteria.getMaxValue()!=null &&
- Double.parseDouble(item.getVolume())> scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- hit.setHit(String.valueOf(item.getVolume()));
- trigger.append(",").append(hit.toJsonString());
- }
- else if (scoringCriteria.getMinValue()!=null&&
- Double.parseDouble(item.getVolume()) < scoringCriteria.getMinValue())
- {
- triggerRules.add(triggerRules1);
- hit.setHit(String.valueOf(item.getVolume()));
- trigger.append(",").append(hit.toJsonString());
- }
- }
- //当前评分为语速
- else if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.speed.name())) {
- //语速
- if (scoringCriteria.getMaxValue()!=null &&scoringCriteria.getMinValue()!=null
- ){
- if (Double.parseDouble(item.getSpeed()) < scoringCriteria.getMinValue()
- ||Double.parseDouble(item.getSpeed())> scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- hit.setHit(String.valueOf(item.getSpeed()));
- trigger.append(",").append(hit.toJsonString());
- }
- }
- else if (scoringCriteria.getMaxValue()!=null &&
- Double.parseDouble(item.getSpeed())> scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- hit.setHit(String.valueOf(item.getSpeed()));
- trigger.append(",").append(hit.toJsonString());
- }
- else if (scoringCriteria.getMinValue()!=null&&
- Double.parseDouble(item.getSpeed()) < scoringCriteria.getMinValue())
- {
- triggerRules.add(triggerRules1);
- hit.setHit(String.valueOf(item.getSpeed()));
- trigger.append(",").append(hit.toJsonString());
- }
- }
- else if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.interrupt.name()))
- {
- if (index>1)
- {
- if(translate[index-2].getNumber().length()>4&&translate[index-1].getNumber().length()<=4)
- {
- int[] Millisecond=GetMillisecond(item.getTime());
- int[] Millisecond1=GetMillisecond(translate[index-2].getTime());
- if (Millisecond[0]<Millisecond1[1])
- {
- triggerRules.add(triggerRules1);
- // hit.setHit(String.valueOf(item.getSpeed()));
- trigger.append(",").append(hit.toJsonString());
- }
- }
- }
- }
- else
- {
- //获取关键词
- List<String> keywords=keyWorlds.get(scoringCriteria.getId());
- if(keywords!=null&&keywords.size()>0)
- {
- if (scoringCriteria.getIsContain()!=null&&scoringCriteria.getIsContain()>1)
- {
- if (!searchLexiconService.containsAnyParallel(item.getSpeech(),keywords))
- {
- triggerRules.add(triggerRules1);
- trigger.append(",").append(hit.toJsonString());
- }
- }
- else
- {
- if (searchLexiconService.containsAnyParallel(item.getSpeech(),keywords))
- {
- if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.swearing.name()))
- {
- if (!searchLexiconService.containsAnyParallel(item.getSpeech(),filterKeyWorlds))
- {
- triggerRules.add(triggerRules1);
- trigger.append(",").append(hit.toJsonString());
- }
- }
- else
- {
- triggerRules.add(triggerRules1);
- trigger.append(",").append(hit.toJsonString());
- }
- }
- }
- }
- }
- }
- }
- }
- if (triggerRules.size()>0)
- {
- item.setTrigger("["+trigger.deleteCharAt(0).toString()+"]");
- AllTriggerRules.addAll(triggerRules);
- }
- }
- }
- List<LinkedHashMap<String,Object>> criteria=new ArrayList<>();
- if (scoring != null) {
- for (ScoringCriteria scoringCriteria :scoring) {
- long count=AllTriggerRules.stream().filter(x-> Objects.equals(x.getScoringId(),
- scoringCriteria.getId()))
- .count();
- Long frequency=scoringCriteria.getFrequency();
- if (scoringCriteria.getFrequency()==null||scoringCriteria.getFrequency()<=0)
- {
- frequency=1L;
- }
- if (count>=frequency)
- {
- long multiple=count/frequency*scoringCriteria.getScore();
- if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.volume.name()))
- {
- //音量只扣一次分
- multiple=scoringCriteria.getScore();
- }
- LinkedHashMap<String,Object> map=new LinkedHashMap<>();
- map.put("conditionName",RulesCondition.getTypeValue(scoringCriteria.getConditionName()));
- // if (scoringCriteria.getScoreType()==0)
- // {
- // map.put("score",scoringCriteria.getScore());
- // score+=multiple;
- // }
- // else
- // {
- // map.put("score",-(scoringCriteria.getScore()));
- // score-=multiple;
- // }
- map.put("score",-multiple);
- score-=multiple;
- map.put("isCritical",scoringCriteria.getIsCritical());
- if (scoringCriteria.getIsCritical()==1)
- {
- //是否致命
- call.setIsCritical(1L);
- }
- map.put("scoring",scoringCriteria);
- criteria.add(map);
- }
- }
- }
- if(AllTriggerRules.size()>0)
- {
- triggerRulesService.insert(AllTriggerRules);
- }
- if (criteria.size()>0)
- {
- //评分标准
- call.setScoringCriteria(mapper.writeValueAsString(criteria));
- }
- call.setValidity((long) validity);
- //质检是否成功
- call.setIsSuccess(1L);
- //AI质检分值
- call.setScore(score);
- //合格分数
- call.setPassingScore(rule.getPassingScore());
- //是否合格
- if (score>=rule.getPassingScore())
- {
- call.setIsQualified(1L);
- call.setAiResult("合格");
- }
- else
- {
- call.setIsQualified(2L);
- call.setAiResult("不合格");
- }
- //保存json文件
- call.setTextJson(mapper.writeValueAsString(translate));
- }
- else
- {
- call.setIsSuccess(2L);
- }
- }
- catch (Exception e)
- {
- call.setIsSuccess(2L);
- }
- }
- else
- {
- try
- {
- ObjectMapper mapper = new ObjectMapper();
- //没有翻译只检测音量音速
- double decibel = calculateRMSVolume(new File(FileUrl));//获取音量
- call.setVolume(String.format("%.2f", decibel));
- //double speechRate = analyzeSpeechSpeed(new File(FileUrl), -40.0);;//获取语速
- // call.setSpeed(String.format("%.2f", speechRate));
- LambdaQueryWrapper<ScoringCriteria> qs =new LambdaQueryWrapper<>();
- qs.eq(ScoringCriteria::getIsDelete,0);//是否确认
- qs.eq(ScoringCriteria::getRulesId,rule.getId());//规则id
- var scoring=scoringCriteriaService.getList(qs);
- List<TriggerRules> triggerRules=new ArrayList<>();
- StringBuilder trigger= new StringBuilder();long score=100;
- List<LinkedHashMap<String,Object>> criteria=new ArrayList<>();
- if (scoring != null) {
- Long isSuccess=1L;
- for (ScoringCriteria scoringCriteria :scoring) {
- if (RulesCondition.getTypeIsCharacters(scoringCriteria.getConditionName()))
- {
- isSuccess=2L;
- break;
- }
- }
- if (isSuccess==1L)
- {
- for (ScoringCriteria scoringCriteria :scoring)
- {
- String value= RulesCondition.getTypeValue(scoringCriteria.getConditionName());
- TriggerRules triggerRules1=new TriggerRules();
- triggerRules1.setRulesId(rule.getId());//规则id
- triggerRules1.setRulesName(rule.getRuleName());//触发规则名称
- triggerRules1.setScoringId(scoringCriteria.getId());//触发标准id
- triggerRules1.setScoringName(value);//触发标准名称
- triggerRules1.setQualityId(call.getQcId());
- triggerRules1.setSoundName(FileUrl);
- HitRules hit =new HitRules();
- hit.setId(scoringCriteria.getId());
- hit.setName(value);
- hit.setScore(scoringCriteria.getScore());
- long count=0;
- if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.ringing.name())
- )
- {
- if (call.getRingTime()!=null&& call.getAnswerTime()!=null)
- {
- // 将Date转换为Instant
- Instant instant1 = call.getRingTime().toInstant();
- Instant instant2 = call.getAnswerTime().toInstant();
- // 计算时间差
- long seconds = Duration.between(instant1, instant2).getSeconds() ;
- if (scoringCriteria.getMaxValue()!=null &&seconds>scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- // trigger.append(",振铃时长").append("命中规则条件")
- // .append(value);
- }
- }
- }
- else if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.duration.name())
- )
- {
- if (call.getAnswerTime()!=null&& call.getHangupTime()!=null)
- {
- // 将Date转换为Instant
- Instant instant1 = call.getAnswerTime().toInstant();
- Instant instant2 = call.getHangupTime().toInstant();
- // 计算时间差
- long seconds = Duration.between(instant1, instant2).getSeconds() ;
- if (scoringCriteria.getMaxValue()!=null&& scoringCriteria.getMaxValue()>0&&
- scoringCriteria.getMinValue()!=null&& scoringCriteria.getMinValue()>0
- )
- {
- if ( seconds>scoringCriteria.getMaxValue() ||seconds<scoringCriteria.getMinValue())
- {
- triggerRules.add(triggerRules1);
- // trigger.append(",通话时长异常").append("命中规则条件")
- // .append(value);
- }
- }
- else if (scoringCriteria.getMaxValue()!=null&& scoringCriteria.getMaxValue()>0 &&seconds>scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- // trigger.append(",通话时长异常").append("命中规则条件")
- // .append(value);
- }
- else if (scoringCriteria.getMinValue()!=null && scoringCriteria.getMinValue()>0
- &&seconds<scoringCriteria.getMinValue())
- {
- triggerRules.add(triggerRules1);
- //trigger.append(",通话时长异常").append("命中规则条件")
- // .append(value);
- }
- }
- }
- else
- if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.volume.name())) {
- if (scoringCriteria.getMaxValue()!=null &&scoringCriteria.getMinValue()!=null
- ){
- if (decibel < scoringCriteria.getMinValue()
- ||decibel> scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- // trigger.append(",音量").append(decibel).append("命中规则条件")
- // .append(value);
- hit.setHit(String.valueOf(decibel));
- trigger.append(",").append(hit.toJsonString());
- count=1;
- }
- }
- else if (scoringCriteria.getMaxValue()!=null &&
- decibel> scoringCriteria.getMaxValue())
- {
- triggerRules.add(triggerRules1);
- // trigger.append(",音量").append(decibel).append("命中规则条件")
- // .append(value);
- hit.setHit(String.valueOf(decibel));
- trigger.append(",").append(hit.toJsonString());
- count=1;
- }
- else if (scoringCriteria.getMinValue()!=null&&
- decibel < scoringCriteria.getMinValue())
- {
- triggerRules.add(triggerRules1);
- // trigger.append(",音量").append(decibel).append("命中规则条件")
- // .append(value);
- hit.setHit(String.valueOf(decibel));
- trigger.append(",").append(hit.toJsonString());
- count=1;
- }
- }
- //当前评分为语速
- // else if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.speed.getValue())) {
- // //音量音速固定为减分项
- // if (scoringCriteria.getIsContain()!=null&&scoringCriteria.getIsContain()>1)
- // {
- // if (speechRate < scoringCriteria.getMinValue()
- // &&speechRate> scoringCriteria.getMaxValue()){
- // triggerRules.add(triggerRules1);
- // trigger.append(",语速").append(speechRate).append("命中规则条件")
- // .append(scoringCriteria.getConditionName());
- // }
- // else if (scoringCriteria.getMaxValue()!=null &&
- // speechRate> scoringCriteria.getMaxValue())
- // {
- // triggerRules.add(triggerRules1);
- // trigger.append(",语速").append(speechRate).append("命中规则条件")
- // .append(scoringCriteria.getConditionName());
- // }
- // else if (scoringCriteria.getMinValue()!=null&&
- // speechRate < scoringCriteria.getMinValue())
- // {
- // triggerRules.add(triggerRules1);
- // trigger.append(",语速").append(speechRate).append("命中规则条件")
- // .append(scoringCriteria.getConditionName());
- // }
- // }
- // else
- // {
- // if (speechRate>= scoringCriteria.getMinValue()
- // &&speechRate<= scoringCriteria.getMaxValue()){
- // triggerRules.add(triggerRules1);
- // trigger.append(",语速").append(speechRate).append("命中规则条件")
- // .append(scoringCriteria.getConditionName());
- // }
- // else if (scoringCriteria.getMaxValue()!=null &&
- // speechRate<= scoringCriteria.getMaxValue())
- // {
- // triggerRules.add(triggerRules1);
- // trigger.append(",语速").append(speechRate).append("命中规则条件")
- // .append(scoringCriteria.getConditionName());
- // }
- // else if (scoringCriteria.getMinValue()!=null&&
- // speechRate >= scoringCriteria.getMinValue())
- // {
- // triggerRules.add(triggerRules1);
- // trigger.append(",语速").append(speechRate).append("命中规则条件")
- // .append(scoringCriteria.getConditionName());
- // }
- // }
- // }
- if (count==1)
- {
- LinkedHashMap<String,Object> map=new LinkedHashMap<>();
- map.put("conditionName",value);
- // if (scoringCriteria.getScoreType()==0)
- // {
- // map.put("score",scoringCriteria.getScore());
- // score+=scoringCriteria.getScore();
- // }
- // else
- // {
- // map.put("score",-(scoringCriteria.getScore()));
- // score-=scoringCriteria.getScore();
- // }
- map.put("score",-(scoringCriteria.getScore()));
- score-=scoringCriteria.getScore();
- map.put("isCritical",scoringCriteria.getIsCritical());
- map.put("scoring",scoringCriteria);
- if (scoringCriteria.getIsCritical()==1)
- {
- //是否致命
- call.setIsCritical(1L);
- }
- map.put("Id",scoringCriteria.getId());
- criteria.add(map);
- }
- }
- }
- if(triggerRules.size()>0)
- {
- triggerRulesService.insert(triggerRules);
- }
- if (criteria.size()>0)
- {
- //评分标准
- call.setScoringCriteria(mapper.writeValueAsString(criteria));
- }
- call.setValidity(0L);
- //质检是否成功
- call.setIsSuccess(isSuccess);
- //AI质检分值
- call.setScore(score);
- //合格分数
- call.setPassingScore(rule.getPassingScore());
- //是否合格
- if (score>=rule.getPassingScore())
- {
- call.setIsQualified(1L);
- call.setAiResult("合格");
- }
- else
- {
- call.setIsQualified(2L);
- call.setAiResult("不合格");
- }
- RepTranslate item=new RepTranslate();
- if (triggerRules.size()>0)
- {
- item.setTrigger("["+trigger.deleteCharAt(0).toString()+"]");
- }
- item.setVolume(String.format("%.2f", decibel));
- // item.setSpeed(String.format("%.2f", speechRate));
- item.setScore(score);
- item.setFilePath(FileUrl);
- //保存json文件
- call.setTextJson(mapper.writeValueAsString(item));
- }
- }
- catch (Exception e)
- {
- call.setIsSuccess(2L);
- }
- }
- call.setQcTime(new Date());
- call.setIsQuality(1L);
- this.update(call);
- try {
- // 延时1秒(1000毫秒)
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- // 处理中断异常
- e.printStackTrace();
- // 恢复中断状态
- Thread.currentThread().interrupt();
- }
- deleteFiles(FileUrl) ;
- }
- }
- return audio;
- }
- private static String returnCharacters(String str)
- {
- String result = str.replaceAll("[^\\p{L}\\p{N} ]", "");
- return result;
- }
- private void deleteFiles(String files)
- {
- Path path = Paths.get(files);
- try {
- System.gc();
- Thread.sleep(100); // 短暂等待
- // 删除文件
- Files.delete(path);
- System.out.println("文件删除成功!");
- } catch (IOException e) {
- System.err.println("删除失败: " + e.getMessage());
- // 可根据具体异常类型处理不同情况
- if (e instanceof java.nio.file.NoSuchFileException) {
- System.err.println("文件不存在!");
- } else if (e instanceof java.nio.file.AccessDeniedException) {
- System.err.println("权限不足!");
- } else {
- e.printStackTrace();
- }
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- public static double calculateRMSVolume(File audioFile)
- throws UnsupportedAudioFileException, IOException {
- // 使用 try-with-resources 确保流自动关闭
- try (AudioInputStream originalStream = AudioSystem.getAudioInputStream(audioFile)) {
- AudioInputStream audioStream = originalStream;
- AudioFormat format = audioStream.getFormat();
- // 检查是否需要转换为 PCM 格式
- if (format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED
- && format.getEncoding() != AudioFormat.Encoding.PCM_UNSIGNED) {
- // 定义目标格式(转换为带符号16位PCM)
- AudioFormat targetFormat = new AudioFormat(
- AudioFormat.Encoding.PCM_SIGNED,
- format.getSampleRate(),
- 16,
- format.getChannels(),
- format.getChannels() * 2,
- format.getSampleRate(),
- false);
- // 获取转换后的流,并包裹在 try-with-resources 中
- try (AudioInputStream convertedStream =
- AudioSystem.getAudioInputStream(targetFormat, originalStream)) {
- return processAudioStream(convertedStream, targetFormat);
- }
- } else {
- // 直接处理原始流
- return processAudioStream(audioStream, format);
- }
- }
- }
- private static double processAudioStream(AudioInputStream audioStream, AudioFormat format)
- throws IOException {
- // 读取所有音频数据
- byte[] buffer = new byte[(int) (audioStream.getFrameLength() * format.getFrameSize())];
- audioStream.read(buffer);
- // 将字节转换为样本值
- double[] samples = convertBytesToSamples(buffer, format);
- // 计算RMS
- double sumSquares = 0.0;
- for (double sample : samples) {
- sumSquares += sample * sample;
- }
- double rms = Math.sqrt(sumSquares / samples.length);
- // 转换为分贝(避免除以0)
- return rms > 0 ? 20 * Math.log10(rms) : Double.NEGATIVE_INFINITY;
- }
- private static double[] convertBytesToSamples(byte[] bytes, AudioFormat format) {
- int sampleSize = format.getSampleSizeInBits() / 8;
- int numSamples = bytes.length / sampleSize;
- double[] samples = new double[numSamples];
- ByteOrder byteOrder = format.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
- for (int i = 0; i < numSamples; i++) {
- int offset = i * sampleSize;
- int value = 0;
- // 根据位深度解析样本
- switch (sampleSize) {
- case 1: // 8位
- value = bytes[offset] & 0xFF; // 转换为无符号
- samples[i] = (value - 128) / 128.0; // 归一化到[-1, 1]
- break;
- case 2: // 16位
- value = ByteBuffer.wrap(bytes, offset, 2)
- .order(byteOrder)
- .getShort();
- samples[i] = value / 32768.0; // 归一化到[-1, 1]
- break;
- default:
- throw new UnsupportedOperationException("不支持的位深度: " + format.getSampleSizeInBits());
- }
- }
- return samples;
- }
- public static String downloadFile(String fileUrl, String savePath) throws IOException {
- URL url = new URL(fileUrl);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("GET");
- conn.setConnectTimeout(5000);
- String file="";
- if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
- // 解析文件名
- String disposition = conn.getHeaderField("Content-Disposition");
- String fileName = (disposition != null)
- ? disposition.substring(disposition.indexOf("filename=") + 10, disposition.length() - 1)
- : fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
- // 创建目录
- File saveDir = new File(savePath);
- if (!saveDir.exists()) saveDir.mkdirs();
- file=savePath + File.separator + fileName;
- // 读写数据
- try (InputStream in = new BufferedInputStream(conn.getInputStream());
- FileOutputStream out = new FileOutputStream(savePath + File.separator + fileName)) {
- byte[] buffer = new byte[4096];
- int bytesRead;
- while ((bytesRead = in.read(buffer)) != -1) {
- out.write(buffer, 0, bytesRead);
- }
- }
- finally {
- conn.disconnect();
- }
- }
- return file;
- }
- @Override
- public List<ExcelSentiment> excelSentiment() throws JsonProcessingException {
- var list =translateService.getList();
- ObjectMapper mapper = new ObjectMapper();
- List<ExcelSentiment> sentiments=new ArrayList<>();
- for (Translate item:list)
- {
- if (StringHelper.isNotEmpty(item.getTranslate()))
- {
- RepTranslate [] translate = mapper.readValue(item.getTranslate(), RepTranslate[].class);
- int index=0;
- StringBuilder message= new StringBuilder();
- for (RepTranslate i :translate)
- {
- index=index+1;
- if (translate.length>index)
- {
- if (i.getNumber().length()==translate[index].getNumber().length())
- {
- message.append(i.getSpeech());
- }
- else
- {
- ExcelSentiment excelSentiment=new ExcelSentiment();
- message.append(i .getSpeech());
- if (message.toString().length()<10)
- {
- message= new StringBuilder();
- continue;
- }
- excelSentiment.setContent(message.toString());
- if(i.getNumber().length()>4)
- {
- excelSentiment.setSeat( "客户");
- }
- else
- {
- excelSentiment.setSeat( "坐席");
- }
- sentiments.add(excelSentiment);
- message= new StringBuilder();
- }
- }
- else
- {
- message.append(i .getSpeech());
- if (message.toString().length()<10)
- {
- message= new StringBuilder();
- continue;
- }
- ExcelSentiment excelSentiment=new ExcelSentiment();
- excelSentiment.setContent(message.toString());
- if(i.getNumber().length()>4)
- {
- excelSentiment.setSeat( "客户");
- }
- else
- {
- excelSentiment.setSeat( "坐席");
- }
- sentiments.add(excelSentiment);
- message= new StringBuilder();
- }
- }
- }
- }
- return sentiments;
- }
- @Override
- public boolean QcResultPush(QcResultPush push)
- {
- QcResult result=new QcResult();
- result.setIsQuality(0L);
- result.setCallid(push.getCallid());
- result.setTel(push.getTel());
- result.setTalkStartTime(push.getTalkStartTime());
- result.setWaitTime(push.getWaitTime());
- result.setRingTime(push.getRingTime());
- result.setAnswerTime(push.getAnswerTime());
- result.setHangupTime(push.getHangupTime());
- result.setFilePath(push.getFilePath());
- result.setCallType(push.getCallType());
- result.setUserName(push.getUserName());
- result.setMyd(push.getMyd());
- result.setNickName(push.getNickName());
- boolean n=this.insert(result);
- Translate translate=new Translate();
- if (StringHelper.isNotEmpty(push.getTranslate()))
- {
- translate.setCallid(push.getCallid());
- translate.setTranslate(push.getTranslate());
- translate.setTranslateJson(push.getTranslateJson());
- translateService.insert(translate);
- }
- return n;
- }
- private List<String> filterKeyWorlds()
- {
- List<String> linke =new ArrayList<>();
- linke.add("妈妈的");
- return linke;
- }
- private LinkedHashMap<Long,List<String>> keyWorlds(List<ScoringCriteria> scoring)
- {
- LinkedHashMap<Long, List<String>> linkedMap = new LinkedHashMap<>();
- for (ScoringCriteria scoringCriteria :scoring)
- {
- List<Long> result = new ArrayList<>();
- if (StringHelper.isEmpty(scoringCriteria.getContent()))
- {
- continue;
- }
- String[] parts = scoringCriteria.getContent().split(",");
- try
- {
- for (String part : parts) {
- String trimmed = part.trim();
- if (!trimmed.isEmpty()) {
- result.add(Long.parseLong(trimmed));
- }
- }
- }
- catch (Exception E)
- {
- //关键词中存在汉字直接抛出
- continue;
- }
- LambdaQueryWrapper<SearchLexicon> qw = new LambdaQueryWrapper<>();
- qw.in(SearchLexicon::getCategory,result);
- qw.in(SearchLexicon::getType,2);
- List<SearchLexicon> search=searchLexiconService.getList(qw);
- List<String> keywords =new ArrayList<>();
- if(search!=null&&search.size()>0)
- {
- for (SearchLexicon item :search)
- {
- keywords.add(item.getTerm());
- }
- }
- linkedMap.put(scoringCriteria.getId(),keywords);
- }
- return linkedMap;
- }
- //解析同声翻译
- private void GetRepTranslates(RepTranslate [] translate, String inputFile) throws Exception {
- LocalDate currentDate = LocalDate.now();
- //获取当前年月日
- int year = currentDate.getYear();
- int month = currentDate.getMonthValue();
- int day = currentDate.getDayOfMonth();
- //设置保存路径
- String File= "files/luYin/" +year+"/"+month+"/"+day+"/";
- //下载录音文件到指定路径
- // String fileName= DownloadHelper.downloadFile(File,FileUrl);
- //获取录音文件
- //String inputFile = FileUrl;
- for (RepTranslate item :translate)
- {
- if (StringHelper.isNotEmpty(item.getSpeech())
- &&item.getSpeech().length()>2)
- {
- if(item.getNumber().length()>4)
- {
- //只质检话务员,客户语音跳过
- continue;
- }
- if (StringHelper.isNotEmpty(item.getTime()))
- {
- int[] Millisecond=GetMillisecond(item.getTime());
- if (Millisecond!=null)
- {
- String newFile=File+Millisecond[0]+"-"+new File(inputFile).getName();
- //分割wav
- splitWav(new File(inputFile),
- new File(newFile), Millisecond[0], Millisecond[1]);
- double decibel = calculateRMSVolume(new File(newFile));
- //double speechRate = analyzeSpeechSpeed(new File(newFile), -40.0);
- double speechRate=calculateSpeechRate(returnCharacters(item.getSpeech()).length(), new File(newFile));//获取语速
- //分割路径
- item.setFilePath(newFile);
- //音量
- item.setVolume( String.format("%.2f", decibel));
- //音速
- item.setSpeed( String.format("%.2f", speechRate));
- //质检完毕删除附件
- deleteFiles(newFile);
- }
- }
- }
- }
- }
- //获取音量
- public static double calculateDecibel(File wavFile) throws Exception {
- try (DataInputStream dis = new DataInputStream(Files.newInputStream(wavFile.toPath()))) {
- // 跳过WAV头(假设头长度为44字节)
- dis.skipBytes(44);
- // 读取PCM数据
- List<Short> samples = new ArrayList<>();
- while (dis.available() > 0) {
- samples.add(dis.readShort());
- }
- // 计算RMS
- double sum = 0;
- for (short sample : samples) {
- sum += (sample * sample);
- }
- double rms = Math.sqrt(sum / samples.size());
- // 计算分贝(参考值取16位最大值32767)
- return 20 * Math.log10(rms / 32767.0);
- }
- }
- //获取语速
- public static double analyzeSpeechSpeed(File wavFile, double silenceThresholdDb) throws Exception {
- try (DataInputStream dis = new DataInputStream(Files.newInputStream(wavFile.toPath()))) {
- dis.skipBytes(44); // 跳过WAV头
- int sampleRate = 16000; // 需从WAV头读取实际值
- List<Boolean> isSpeech = new ArrayList<>();
- List<Short> buffer = new ArrayList<>();
- // 分帧检测静音(假设帧长度100ms)
- int frameSize = sampleRate / 10;
- while (dis.available() >= 2) {
- short sample = dis.readShort();
- buffer.add(sample);
- if (buffer.size() >= frameSize) {
- double rms = calculateFrameRms(buffer);
- double db = 20 * Math.log10(rms / 32767.0);
- isSpeech.add(db > silenceThresholdDb);
- buffer.clear();
- }
- }
- // 统计语音段数量
- int speechSegments = 0;
- boolean lastState = false;
- for (boolean state : isSpeech) {
- if (state && !lastState) speechSegments++;
- lastState = state;
- }
- // 计算语速(假设每个语音段对应一个词)
- double totalTime = (double) isSpeech.size() * 0.1; // 总时间(秒)
- return (speechSegments / totalTime) * 60; // 词/分钟
- }
- }
- // 主计算方法
- public static double calculateSpeechRate(int wordCount, File wavFile) throws Exception {
- long durationMs = getAudioDuration(wavFile);
- if (durationMs == 0) throw new IllegalArgumentException("音频时长为零");
- return (wordCount * 60000.0) / durationMs; // 60,000 = 1000ms * 60s
- }
- // 安全的头解析方法
- private static long parseDurationFromHeader(ByteBuffer buffer) throws IOException {
- try {
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- // 验证RIFF头
- byte[] riff = new byte[4];
- buffer.get(riff);
- if (!"RIFF".equals(new String(riff, StandardCharsets.US_ASCII))) {
- throw new IOException("Invalid WAV file header");
- }
- // 跳过文件总大小
- buffer.position(buffer.position() + 4);
- // 验证WAVE标识
- byte[] wave = new byte[4];
- buffer.get(wave);
- if (!"WAVE".equals(new String(wave, StandardCharsets.US_ASCII))) {
- throw new IOException("Not a WAVE file");
- }
- // 查找fmt和data块
- long sampleRate = -1;
- long dataSize = -1;
- while (buffer.remaining() > 8) {
- byte[] chunkIdBytes = new byte[4];
- buffer.get(chunkIdBytes);
- String chunkId = new String(chunkIdBytes, StandardCharsets.US_ASCII);
- int chunkSize = buffer.getInt();
- if (chunkId.equals("fmt ")) {
- // 解析采样率
- buffer.position(buffer.position() + 8); // 跳过格式和声道
- sampleRate = buffer.getInt() & 0xFFFFFFFFL;
- buffer.position(buffer.position() + 6); // 跳过其他参数
- } else if (chunkId.equals("data")) {
- dataSize = chunkSize & 0xFFFFFFFFL;
- break;
- } else {
- // 跳过未知块(处理补位)
- int padding = chunkSize % 2;
- buffer.position(buffer.position() + chunkSize + padding);
- }
- }
- if (sampleRate == -1 || dataSize == -1) {
- throw new IOException("Missing required chunks");
- }
- return (dataSize * 1000L) / (sampleRate * 2); // 假设16位单声道
- } finally {
- // 显式释放内存映射(Java 9+)
- if (buffer instanceof sun.nio.ch.DirectBuffer) {
- ((sun.nio.ch.DirectBuffer) buffer).cleaner().clean();
- }
- }
- }
- // 语速计算核心
- private static double calculateRate(int wordCount, long durationMs) {
- if (durationMs == 0) throw new IllegalArgumentException("Audio duration cannot be zero");
- return (wordCount * 60000.0) / durationMs;
- }
- // 获取音频时长(毫秒)
- private static long getAudioDuration(File wavFile) throws Exception {
- try (RandomAccessFile raf = new RandomAccessFile(wavFile, "r")) {
- // 验证RIFF头
- if (!readString(raf).equals("RIFF")) {
- throw new IllegalArgumentException("无效的WAV文件");
- }
- // 2. 跳过RIFF总大小(不需要使用)
- raf.skipBytes(4);
- // 3. 验证格式类型必须是"WAVE"
- String format = readString(raf);
- if (!format.equals("WAVE")) {
- throw new IllegalArgumentException("非WAVE格式文件");
- }
- // 查找fmt和data块
- long sampleRate = -1;
- long dataSize = -1;
- while (true) {
- String chunkId = readString(raf);
- long chunkSize = readLittleEndianInt(raf) & 0xFFFFFFFFL;
- long chunkStart = raf.getFilePointer();
- if (chunkId.equals("fmt ")) {
- // 读取采样率
- raf.skipBytes(4); // 跳过音频格式和声道数
- sampleRate = readLittleEndianInt(raf) & 0xFFFFFFFFL;
- raf.skipBytes(6); // 跳过其他fmt参数
- } else if (chunkId.equals("data")) {
- dataSize = chunkSize;
- break;
- }
- if (chunkStart + chunkSize >= raf.length()) break;
- raf.seek(chunkStart + chunkSize);
- }
- if (sampleRate == -1 || dataSize == -1) {
- throw new IllegalArgumentException("缺少关键数据块");
- }
- // 计算时长:数据大小(字节) / (声道数 * 位深度/8) / 采样率
- // 简化计算(假设声道数=1,位深度=16)
- // 实际应根据fmt块参数动态计算
- long totalSamples = dataSize / 2; // 16位=2字节/样本
- return (totalSamples * 1000L) / sampleRate;
- }
- }
- private static double calculateFrameRms(List<Short> buffer) {
- double sum = 0;
- for (short sample : buffer) {
- sum += sample * sample;
- }
- return Math.sqrt(sum / buffer.size());
- }
- //获取分段语音开始毫秒数和结束毫秒数
- private static int[] GetMillisecond(String input)
- {
- // 去除外层的中括号
- String content = input.substring(2, input.length() - 2);
- // 分割各个子区间
- String[] intervals = content.split("],\\[");
- if (intervals.length == 0) {
- System.out.println("无有效区间");
- return null;
- }
- // 提取第一个区间的第一个值
- String[] firstParts = intervals[0].split(",");
- int firstValue = Integer.parseInt(firstParts[0]);
- // 提取最后一个区间的最后一个值
- String[] lastParts = intervals[intervals.length - 1].split(",");
- int lastValue = Integer.parseInt(lastParts[1]);
- return new int[]{firstValue,lastValue};
- }
- //分割音频
- private static void splitWav(File inputFile, File outputFile, long startMs, long endMs) throws Exception {
- try (RandomAccessFile raf = new RandomAccessFile(inputFile, "r")) {
- // 读取RIFF头
- if (!readString(raf).equals("RIFF")) {
- throw new IllegalArgumentException("不是有效的RIFF文件");
- }
- long riffSize = readLittleEndianInt(raf) & 0xFFFFFFFFL;
- String format = readString(raf);
- if (!format.equals("WAVE")) {
- throw new IllegalArgumentException("不是WAVE文件");
- }
- // 查找fmt和data块
- int numChannels = -1;
- long sampleRate = -1;
- int bitsPerSample = -1;
- long dataStart = -1;
- long dataSize = -1;
- while (true) {
- String chunkId;
- try {
- chunkId = readString(raf);
- } catch (EOFException e) {
- break; // 所有块处理完毕
- }
- long chunkSize = readLittleEndianInt(raf) & 0xFFFFFFFFL;
- long chunkDataStart = raf.getFilePointer();
- if (chunkId.equals("fmt ")) {
- // 解析fmt块
- int audioFormat = readLittleEndianShort(raf);
- numChannels = readLittleEndianShort(raf);
- sampleRate = readLittleEndianInt(raf) & 0xFFFFFFFFL;
- long byteRate = readLittleEndianInt(raf);
- int blockAlign = readLittleEndianShort(raf);
- bitsPerSample = readLittleEndianShort(raf);
- } else if (chunkId.equals("data")) {
- dataStart = chunkDataStart;
- dataSize = chunkSize;
- break; // 找到data块后退出循环
- }
- // 跳到下一个块
- raf.seek(chunkDataStart + chunkSize);
- }
- if (numChannels == -1 || dataStart == -1) {
- throw new IllegalArgumentException("缺少fmt或data块");
- }
- // 计算参数
- int bytesPerFrame = numChannels * (bitsPerSample / 8);
- long totalSamples = dataSize / bytesPerFrame;
- long durationMs = (totalSamples * 1000L) / sampleRate;
- // 调整时间范围
- if (startMs < 0) startMs = 0;
- if (endMs > durationMs) endMs = durationMs;
- if (startMs >= endMs) {
- throw new IllegalArgumentException("起始时间必须小于结束时间");
- }
- // 计算样本位置
- long startSample = (startMs * sampleRate) / 1000L;
- long endSample = (endMs * sampleRate) / 1000L;
- long startByte = startSample * bytesPerFrame;
- long endByte = endSample * bytesPerFrame;
- long newDataSize = endByte - startByte;
- // 转换为Path对象并获取父目录
- Path outputPath = outputFile.toPath();
- Path parentDir = outputPath.getParent();
- // 如果父目录存在则创建所有必要目录
- if (parentDir != null) {
- Files.createDirectories(parentDir);
- }
- // 写入新文件
- try (DataOutputStream dos = new DataOutputStream(Files.newOutputStream(outputFile.toPath()))) {
- // RIFF头
- writeString(dos, "RIFF");
- writeLittleEndianInt(dos, (int) (36 + newDataSize)); // RIFF大小
- writeString(dos, "WAVE");
- // fmt块
- writeString(dos, "fmt ");
- writeLittleEndianInt(dos, 16); // fmt块大小
- writeLittleEndianShort(dos, (short) 1); // PCM格式
- writeLittleEndianShort(dos, (short) numChannels);
- writeLittleEndianInt(dos, (int) sampleRate);
- writeLittleEndianInt(dos, (int) (sampleRate * numChannels * bitsPerSample / 8));
- writeLittleEndianShort(dos, (short) (numChannels * bitsPerSample / 8));
- writeLittleEndianShort(dos, (short) bitsPerSample);
- // data块头
- writeString(dos, "data");
- writeLittleEndianInt(dos, (int) newDataSize);
- // 复制音频数据
- raf.seek(dataStart + startByte);
- byte[] buffer = new byte[4096];
- long remaining = newDataSize;
- while (remaining > 0) {
- int read = raf.read(buffer, 0, (int) Math.min(buffer.length, remaining));
- if (read == -1) break;
- dos.write(buffer, 0, read);
- remaining -= read;
- }
- }
- }
- }
- // 辅助方法:读取小端整数
- private static int readLittleEndianInt(DataInput in) throws IOException {
- int b1 = in.readUnsignedByte();
- int b2 = in.readUnsignedByte();
- int b3 = in.readUnsignedByte();
- int b4 = in.readUnsignedByte();
- return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
- }
- private static short readLittleEndianShort(DataInput in) throws IOException {
- int b1 = in.readUnsignedByte();
- int b2 = in.readUnsignedByte();
- return (short) ((b2 << 8) | b1);
- }
- private static String readString(DataInput in) throws IOException {
- byte[] bytes = new byte[4];
- in.readFully(bytes);
- return new String(bytes, StandardCharsets.US_ASCII);
- }
- private static void writeLittleEndianInt(DataOutput out, int value) throws IOException {
- out.write(value & 0xFF);
- out.write((value >> 8) & 0xFF);
- out.write((value >> 16) & 0xFF);
- out.write((value >> 24) & 0xFF);
- }
- private static void writeLittleEndianShort(DataOutput out, short value) throws IOException {
- out.write(value & 0xFF);
- out.write((value >> 8) & 0xFF);
- }
- private static void writeString(DataOutput out, String s) throws IOException {
- out.write(s.getBytes(StandardCharsets.US_ASCII));
- }
- }
|