281 lines
12 KiB
Java
281 lines
12 KiB
Java
|
|
package com.luxsin.app.service;
|
||
|
|
|
||
|
|
import cn.hutool.core.io.FileUtil;
|
||
|
|
import cn.hutool.core.util.NumberUtil;
|
||
|
|
import cn.hutool.core.util.StrUtil;
|
||
|
|
import cn.hutool.http.HttpUtil;
|
||
|
|
import cn.hutool.json.JSONArray;
|
||
|
|
import cn.hutool.json.JSONObject;
|
||
|
|
import cn.hutool.json.JSONUtil;
|
||
|
|
import com.luxsin.app.bean.Model;
|
||
|
|
import com.luxsin.app.bean.Target;
|
||
|
|
import com.luxsin.app.bean.table.ModelTableDef;
|
||
|
|
import com.luxsin.app.bean.table.TargetTableDef;
|
||
|
|
import com.luxsin.app.config.MyConfig;
|
||
|
|
import com.luxsin.app.dao.ModelMapper;
|
||
|
|
import com.luxsin.app.dao.TargetMapper;
|
||
|
|
import com.meilisearch.sdk.Index;
|
||
|
|
import com.meilisearch.sdk.SearchRequest;
|
||
|
|
import com.meilisearch.sdk.model.Searchable;
|
||
|
|
import com.mybatisflex.core.query.QueryWrapper;
|
||
|
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||
|
|
import lombok.extern.slf4j.Slf4j;
|
||
|
|
import org.eafon.data.EResponseTool;
|
||
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
||
|
|
import org.springframework.stereotype.Service;
|
||
|
|
|
||
|
|
import java.util.*;
|
||
|
|
import java.util.concurrent.TimeUnit;
|
||
|
|
import java.util.stream.Collectors;
|
||
|
|
|
||
|
|
@Slf4j
|
||
|
|
@Service
|
||
|
|
public class ModelService extends ServiceImpl<ModelMapper, Model> {
|
||
|
|
private final RedisTemplate<String, String> redisTemplate;
|
||
|
|
|
||
|
|
private final TargetMapper targetMapper;
|
||
|
|
|
||
|
|
private final MyConfig myConfig;
|
||
|
|
|
||
|
|
private final Index modelIdx;
|
||
|
|
|
||
|
|
public ModelService(RedisTemplate<String, String> redisTemplate, TargetMapper targetMapper,
|
||
|
|
MyConfig myConfig, Index modelIdx) {
|
||
|
|
this.redisTemplate = redisTemplate;
|
||
|
|
this.targetMapper = targetMapper;
|
||
|
|
this.myConfig = myConfig;
|
||
|
|
this.modelIdx = modelIdx;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 获取手机列表
|
||
|
|
* @param key
|
||
|
|
* @param count
|
||
|
|
* @return
|
||
|
|
*/
|
||
|
|
public String modelList(String key, int count){
|
||
|
|
List<Model> list = Collections.emptyList();
|
||
|
|
Searchable searchable = modelIdx.search(SearchRequest.builder()
|
||
|
|
.q(key)
|
||
|
|
.attributesToRetrieve(new String[]{"rig", "form", "name", "brand_name", "source", "eq_key"})
|
||
|
|
.limit(count)
|
||
|
|
.build());
|
||
|
|
if(!searchable.getHits().isEmpty()){
|
||
|
|
return JSONUtil.toJsonStr(searchable.getHits());
|
||
|
|
}else {
|
||
|
|
return JSONUtil.toJsonStr(list);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 获取目标曲线
|
||
|
|
* @param brand
|
||
|
|
* @param name
|
||
|
|
* @param targetName
|
||
|
|
* @param raw 是否只是需要机型的raw数据
|
||
|
|
* @return
|
||
|
|
*/
|
||
|
|
public String getCurvePoint(String brand, String name, String targetName){
|
||
|
|
if(StrUtil.isEmpty(brand) || StrUtil.isEmpty(name) || StrUtil.isEmpty(targetName)){
|
||
|
|
return EResponseTool.error(EResponseTool.ResultCode.VALIDATE_FAILED).toString();
|
||
|
|
}
|
||
|
|
String cacheKey = brand + " " + name;
|
||
|
|
String cache = String.valueOf(redisTemplate.opsForHash().get(cacheKey, targetName));
|
||
|
|
if(StrUtil.isEmpty(cache) || "null".equals(cache)){
|
||
|
|
String lockKey = cacheKey+":"+targetName+":lock";
|
||
|
|
boolean locked = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS));
|
||
|
|
if(locked){
|
||
|
|
try {
|
||
|
|
cache = String.valueOf(redisTemplate.opsForHash().get(cacheKey, targetName));
|
||
|
|
if(StrUtil.isEmpty(cache) || "null".equals(cache)){
|
||
|
|
//redis中没有数据的,需要请求eq接口
|
||
|
|
JSONObject resp = getCurvePointFromPEQ(brand, name, targetName);
|
||
|
|
if(null!=resp && resp.getInt("code") == EResponseTool.ResultCode.SUCCESS.getCode()){
|
||
|
|
//将请求接口得到的数据,放入缓存
|
||
|
|
redisTemplate.opsForHash().put(brand+" "+name, targetName, resp.toString());
|
||
|
|
return resp.toString();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}finally {
|
||
|
|
redisTemplate.delete(lockKey);
|
||
|
|
}
|
||
|
|
}else{
|
||
|
|
try {
|
||
|
|
TimeUnit.MILLISECONDS.sleep(100);
|
||
|
|
} catch (InterruptedException e) {
|
||
|
|
throw new RuntimeException(e);
|
||
|
|
}
|
||
|
|
return getCurvePoint(brand, name, targetName);
|
||
|
|
}
|
||
|
|
}else{
|
||
|
|
log.info("brand: {} name: {} target {} read cache.", brand, name, targetName);
|
||
|
|
return cache;
|
||
|
|
}
|
||
|
|
|
||
|
|
return EResponseTool.error(EResponseTool.ResultCode.EMPTY).toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
private JSONObject getCurvePointFromPEQ(String brand, String name, String targetName){
|
||
|
|
Model model = getOne(QueryWrapper.create().where(ModelTableDef.MODEL.BRAND_NAME.eq(brand)).
|
||
|
|
and(ModelTableDef.MODEL.NAME.eq(name)));
|
||
|
|
if(!Objects.isNull(model)){
|
||
|
|
Target target = targetMapper.selectOneByQuery(QueryWrapper.create().where(TargetTableDef.TARGET.LABEL.eq(targetName)));
|
||
|
|
if(!Objects.isNull(target)){
|
||
|
|
String eq_key = model.getEq_key();
|
||
|
|
String headPhone = brand+" "+name;
|
||
|
|
if(StrUtil.isNotEmpty(eq_key) && eq_key.equals("name")){
|
||
|
|
headPhone = name;
|
||
|
|
}
|
||
|
|
|
||
|
|
JSONObject measurement = null;
|
||
|
|
if("Eafonyoung".equals(model.getSource())){
|
||
|
|
measurement = getCSV(myConfig.getMeasurementBasePath()+"/Eafonyoung/data/"+model.getForm()+"/"+headPhone+".csv");
|
||
|
|
if(Objects.isNull(measurement)){
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
JSONObject resp;
|
||
|
|
if(target.isRead_csv()){
|
||
|
|
//读取自定义的target
|
||
|
|
JSONObject targetRaw = getCSV(myConfig.getTargetBasePath()+"/"+target.getFile());
|
||
|
|
resp = reqEqualize(headPhone, measurement, targetRaw, JSONUtil.parseObj(target.getBassBoost()),
|
||
|
|
model.getSource(), model.getRig());
|
||
|
|
}else{
|
||
|
|
resp = reqEqualize(headPhone, measurement, targetName, JSONUtil.parseObj(target.getBassBoost()),
|
||
|
|
model.getSource(), model.getRig());
|
||
|
|
}
|
||
|
|
|
||
|
|
if(resp.getInt("code") == EResponseTool.ResultCode.SUCCESS.getCode()){
|
||
|
|
return resp;
|
||
|
|
}else{
|
||
|
|
log.info("请求reqEqualize接口失败,没有数据");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
private JSONObject getCSV(String path){
|
||
|
|
if(FileUtil.exist(path)){
|
||
|
|
List<String> list = FileUtil.readLines(path, "UTF-8");
|
||
|
|
JSONArray frequency = new JSONArray();
|
||
|
|
JSONArray raw = new JSONArray();
|
||
|
|
for(int i=1;i<list.size();i++){
|
||
|
|
String[] values = list.get(i).split(",");
|
||
|
|
frequency.add(NumberUtil.parseFloat(values[0]));
|
||
|
|
raw.add(NumberUtil.parseFloat(values[1]));
|
||
|
|
}
|
||
|
|
return new JSONObject(Map.of("frequency",frequency,"raw",raw));
|
||
|
|
}
|
||
|
|
log.info("找不到耳机频响文件:{}", path);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* @param headPhone 耳机名称(品牌+型号)
|
||
|
|
* @param measurement 耳机csv文件,和耳机名称二选一,如果耳机名称不为空优先耳机名称
|
||
|
|
* @param target 目标曲线名称,或者目标曲线的csv文件
|
||
|
|
* @param bassBoost
|
||
|
|
* @param source
|
||
|
|
* @param rig
|
||
|
|
* @return
|
||
|
|
*/
|
||
|
|
private JSONObject reqEqualizeInternal(String headPhone,JSONObject measurement, Object target, JSONObject bassBoost, String source, String rig) {
|
||
|
|
Map<String, Object> reqMap = new HashMap<>();
|
||
|
|
reqMap.put("target", target);
|
||
|
|
reqMap.put("sound_signature", null);
|
||
|
|
reqMap.put("sound_signature_smoothing_window_size", 1);
|
||
|
|
reqMap.put("bass_boost_gain", bassBoost.getFloat("gain"));
|
||
|
|
reqMap.put("bass_boost_fc", bassBoost.getInt("fc"));
|
||
|
|
reqMap.put("bass_boost_q", bassBoost.getFloat("q"));
|
||
|
|
reqMap.put("treble_boost_gain",0);
|
||
|
|
reqMap.put("treble_boost_fc", 10000);
|
||
|
|
reqMap.put("treble_boost_q", 0.7);
|
||
|
|
reqMap.put("tilt", 0);
|
||
|
|
reqMap.put("fs",48000);
|
||
|
|
reqMap.put("bit_depth", 16);
|
||
|
|
reqMap.put("phase", "minimum");
|
||
|
|
reqMap.put("f_res", 16);
|
||
|
|
reqMap.put("preamp",0);
|
||
|
|
reqMap.put("max_gain" ,12);
|
||
|
|
reqMap.put("max_slope", 18);
|
||
|
|
reqMap.put("window_size", 0.08);
|
||
|
|
reqMap.put("treble_window_size", 2);
|
||
|
|
reqMap.put("treble_f_lower", 6000);
|
||
|
|
reqMap.put("treble_f_upper", 8000);
|
||
|
|
reqMap.put("treble_gain_k", 1);
|
||
|
|
reqMap.put("graphic_eq", false);
|
||
|
|
reqMap.put("parametric_eq", true);
|
||
|
|
reqMap.put("fixed_band_eq", false);
|
||
|
|
reqMap.put("convolution_eq", false);
|
||
|
|
|
||
|
|
|
||
|
|
if(null!=measurement){
|
||
|
|
reqMap.put("measurement", measurement);
|
||
|
|
}
|
||
|
|
else if(StrUtil.isNotEmpty(headPhone)){
|
||
|
|
reqMap.put("name", headPhone);
|
||
|
|
}
|
||
|
|
|
||
|
|
reqMap.put("source", source);
|
||
|
|
reqMap.put("rig", rig);
|
||
|
|
reqMap.put("parametric_eq_config", "MINIDSP_IL_DSP");
|
||
|
|
|
||
|
|
Map<String, Object> response = new HashMap<>();
|
||
|
|
JSONArray fr_fields = new JSONArray();
|
||
|
|
fr_fields.addAll(List.of("raw"));
|
||
|
|
response.put("fr_f_step", 1.02);
|
||
|
|
response.put("base64fp16" ,false);
|
||
|
|
response.put("fr_fields", fr_fields);
|
||
|
|
|
||
|
|
reqMap.put("response", response);
|
||
|
|
|
||
|
|
String resp = HttpUtil.post("https://autoeq.app/equalize", new JSONObject(reqMap).toString(), 10000);
|
||
|
|
JSONObject result = new JSONObject();
|
||
|
|
if(StrUtil.isNotEmpty(resp)){
|
||
|
|
JSONObject respJson = JSONUtil.parseObj(resp);
|
||
|
|
JSONObject parametric_eq = respJson.getJSONObject("parametric_eq");
|
||
|
|
if(Objects.isNull(parametric_eq)){
|
||
|
|
result.putOnce("code", 0);
|
||
|
|
result.putOnce("msg", "req param error");
|
||
|
|
result.putOnce("param", reqMap);
|
||
|
|
return result;
|
||
|
|
}else {
|
||
|
|
result.putOnce("parametric_eq", respJson.getJSONObject("parametric_eq"));
|
||
|
|
result.putOnce("fr", respJson.getJSONObject("fr"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
result.putOnce("code", EResponseTool.ResultCode.SUCCESS.getCode());
|
||
|
|
result.putOnce("msg", "ok");
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
public JSONObject reqEqualize(String headPhone, JSONObject measurement, String target, JSONObject bassBoost, String source, String rig) {
|
||
|
|
return reqEqualizeInternal(headPhone, measurement, target, bassBoost, source, rig);
|
||
|
|
}
|
||
|
|
|
||
|
|
public JSONObject reqEqualize(String headPhone, JSONObject measurement, JSONObject target, JSONObject bassBoost, String source, String rig) {
|
||
|
|
return reqEqualizeInternal(headPhone, measurement, target, bassBoost, source, rig);
|
||
|
|
}
|
||
|
|
|
||
|
|
public String getModels(String brandName, String modelName){
|
||
|
|
List<Model> list;
|
||
|
|
if(StrUtil.isNotEmpty(brandName)){
|
||
|
|
list = list(QueryWrapper.create().where(ModelTableDef.MODEL.BRAND_NAME.eq(brandName)).orderBy(ModelTableDef.MODEL.NAME, true));
|
||
|
|
}
|
||
|
|
else if(StrUtil.isNotEmpty(modelName)){
|
||
|
|
list = list(QueryWrapper.create().where(ModelTableDef.MODEL.NAME.like(modelName)).orderBy(ModelTableDef.MODEL.NAME, true));
|
||
|
|
}
|
||
|
|
else{
|
||
|
|
list = Collections.emptyList();
|
||
|
|
}
|
||
|
|
return JSONUtil.toJsonStr(list);
|
||
|
|
}
|
||
|
|
|
||
|
|
public String getModelCurve(String brand, String name){
|
||
|
|
return getCurvePoint(brand, name, "Harman over-ear 2018");
|
||
|
|
}
|
||
|
|
}
|