镜头降落
This commit is contained in:
parent
4c2d20f1b4
commit
984dadb7bf
|
|
@ -0,0 +1,759 @@
|
|||
package com.aros.apron.entity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MessageDown {
|
||||
|
||||
private String tid;
|
||||
private String bid;
|
||||
private long timestamp;
|
||||
private String method;
|
||||
private Data data;
|
||||
|
||||
|
||||
|
||||
public String getTid() {
|
||||
return tid;
|
||||
}
|
||||
|
||||
public void setTid(String tid) {
|
||||
this.tid = tid;
|
||||
}
|
||||
|
||||
public String getBid() {
|
||||
return bid;
|
||||
}
|
||||
|
||||
public void setBid(String bid) {
|
||||
this.bid = bid;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public Data getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Data data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
private int result;
|
||||
private String url;
|
||||
private int video_quality;
|
||||
private int ideo_quality;
|
||||
private AlternateLandPoint alternate_land_point;
|
||||
private int rth_mode;
|
||||
private int task_type;
|
||||
private int wayline_precision_type;
|
||||
private String video_type;
|
||||
private long execute_time;
|
||||
private int exit_wayline_when_rc_lost;
|
||||
private File file;
|
||||
private String flight_id;
|
||||
private int flight_safety_advance_check;
|
||||
private int out_of_control_action;
|
||||
private boolean takeoffToPointTask;
|
||||
private BreakPoint break_point;
|
||||
private int camera_mode=0;//初始化为拍照
|
||||
private String h;
|
||||
private String seq;
|
||||
private String w;
|
||||
//摇杆和云台
|
||||
private String x;
|
||||
private String y;
|
||||
|
||||
private String camera_type;
|
||||
private String payload_index;
|
||||
|
||||
private float height;
|
||||
private double latitude;
|
||||
private boolean locked;
|
||||
private double longitude;
|
||||
private double zoom_factor;
|
||||
private boolean enable;
|
||||
private int exposure_mode;
|
||||
private int exposure_value;
|
||||
private int focus_mode;
|
||||
private int focus_value;
|
||||
|
||||
private double commander_flight_height;
|
||||
private int commander_mode_lost_action;
|
||||
private String max_speed;
|
||||
private int rc_lost_action;
|
||||
private int rth_altitude;
|
||||
private String security_takeoff_height;
|
||||
private double target_height;
|
||||
private double target_latitude;
|
||||
private double target_longitude;
|
||||
private String fly_to_id;
|
||||
private List<Points> points;
|
||||
private int reset_mode;
|
||||
|
||||
private double pitch_speed;
|
||||
|
||||
private double yaw_speed;
|
||||
|
||||
private float width;
|
||||
|
||||
private int psdk_index;
|
||||
private int play_mode;
|
||||
private int play_volume;
|
||||
private int volume;
|
||||
private int type;
|
||||
private int language;
|
||||
|
||||
|
||||
|
||||
private Tts tts;
|
||||
|
||||
|
||||
public Tts getTts() {
|
||||
return tts;
|
||||
}
|
||||
|
||||
public void setTts(Tts tts) {
|
||||
this.tts = tts;
|
||||
}
|
||||
|
||||
public static class Tts {
|
||||
private String md5;
|
||||
private String name;
|
||||
private String text;
|
||||
|
||||
public String getMd5() {
|
||||
return md5;
|
||||
}
|
||||
|
||||
public void setMd5(String md5) {
|
||||
this.md5 = md5;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public int getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public void setSpeed(int speed) {
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
private int speed;
|
||||
|
||||
public void setHeight(float height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public void setCommander_flight_height(double commander_flight_height) {
|
||||
this.commander_flight_height = commander_flight_height;
|
||||
}
|
||||
|
||||
public void setTarget_height(double target_height) {
|
||||
this.target_height = target_height;
|
||||
}
|
||||
|
||||
public int getPsdk_index() {
|
||||
return psdk_index;
|
||||
}
|
||||
|
||||
public void setPsdk_index(int psdk_index) {
|
||||
this.psdk_index = psdk_index;
|
||||
}
|
||||
|
||||
public int getPlay_mode() {
|
||||
return play_mode;
|
||||
}
|
||||
|
||||
public void setPlay_mode(int play_mode) {
|
||||
this.play_mode = play_mode;
|
||||
}
|
||||
|
||||
public int getPlay_volume() {
|
||||
return play_volume;
|
||||
}
|
||||
|
||||
public void setPlay_volume(int play_volume) {
|
||||
this.play_volume = play_volume;
|
||||
}
|
||||
|
||||
public int getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
public void setVolume(int volume) {
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(int language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(float width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public double getPitch_speed() {
|
||||
return pitch_speed;
|
||||
}
|
||||
|
||||
public void setPitch_speed(double pitch_speed) {
|
||||
this.pitch_speed = pitch_speed;
|
||||
}
|
||||
|
||||
public double getYaw_speed() {
|
||||
return yaw_speed;
|
||||
}
|
||||
|
||||
public void setYaw_speed(double yaw_speed) {
|
||||
this.yaw_speed = yaw_speed;
|
||||
}
|
||||
|
||||
public int getReset_mode() {
|
||||
return reset_mode;
|
||||
}
|
||||
|
||||
|
||||
public void setReset_mode(int reset_mode) {
|
||||
this.reset_mode = reset_mode;
|
||||
}
|
||||
|
||||
public String getFly_to_id() {
|
||||
return fly_to_id;
|
||||
}
|
||||
|
||||
public void setFly_to_id(String fly_to_id) {
|
||||
this.fly_to_id = fly_to_id;
|
||||
}
|
||||
|
||||
public List<Points> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
public void setPoints(List<Points> points) {
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
public static class Points {
|
||||
private int height;
|
||||
private double latitude;
|
||||
private double longitude;
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(int height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
}
|
||||
public double getCommander_flight_height() {
|
||||
return commander_flight_height;
|
||||
}
|
||||
|
||||
public void setCommander_flight_height(int commander_flight_height) {
|
||||
this.commander_flight_height = commander_flight_height;
|
||||
}
|
||||
|
||||
public int getCommander_mode_lost_action() {
|
||||
return commander_mode_lost_action;
|
||||
}
|
||||
|
||||
public void setCommander_mode_lost_action(int commander_mode_lost_action) {
|
||||
this.commander_mode_lost_action = commander_mode_lost_action;
|
||||
}
|
||||
|
||||
public String getMax_speed() {
|
||||
return max_speed;
|
||||
}
|
||||
|
||||
public void setMax_speed(String max_speed) {
|
||||
this.max_speed = max_speed;
|
||||
}
|
||||
|
||||
public int getRc_lost_action() {
|
||||
return rc_lost_action;
|
||||
}
|
||||
|
||||
public void setRc_lost_action(int rc_lost_action) {
|
||||
this.rc_lost_action = rc_lost_action;
|
||||
}
|
||||
|
||||
public String getSecurity_takeoff_height() {
|
||||
return security_takeoff_height;
|
||||
}
|
||||
|
||||
public void setSecurity_takeoff_height(String security_takeoff_height) {
|
||||
this.security_takeoff_height = security_takeoff_height;
|
||||
}
|
||||
|
||||
public double getTarget_height() {
|
||||
return target_height;
|
||||
}
|
||||
|
||||
public void setTarget_height(int target_height) {
|
||||
this.target_height = target_height;
|
||||
}
|
||||
|
||||
public double getTarget_latitude() {
|
||||
return target_latitude;
|
||||
}
|
||||
|
||||
public void setTarget_latitude(double target_latitude) {
|
||||
this.target_latitude = target_latitude;
|
||||
}
|
||||
|
||||
public double getTarget_longitude() {
|
||||
return target_longitude;
|
||||
}
|
||||
|
||||
public void setTarget_longitude(double target_longitude) {
|
||||
this.target_longitude = target_longitude;
|
||||
}
|
||||
|
||||
public int getFocus_value() {
|
||||
return focus_value;
|
||||
}
|
||||
|
||||
public void setFocus_value(int focus_value) {
|
||||
this.focus_value = focus_value;
|
||||
}
|
||||
|
||||
public int getFocus_mode() {
|
||||
return focus_mode;
|
||||
}
|
||||
|
||||
public void setFocus_mode(int focus_mode) {
|
||||
this.focus_mode = focus_mode;
|
||||
}
|
||||
|
||||
public int getExposure_value() {
|
||||
return exposure_value;
|
||||
}
|
||||
|
||||
public void setExposure_value(int exposure_value) {
|
||||
this.exposure_value = exposure_value;
|
||||
}
|
||||
|
||||
public int getExposure_mode() {
|
||||
return exposure_mode;
|
||||
}
|
||||
|
||||
public void setExposure_mode(int exposure_mode) {
|
||||
this.exposure_mode = exposure_mode;
|
||||
}
|
||||
|
||||
public boolean isEnable() {
|
||||
return enable;
|
||||
}
|
||||
|
||||
public void setEnable(boolean enable) {
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
public double getZoom_factor() {
|
||||
return zoom_factor;
|
||||
}
|
||||
|
||||
public void setZoom_factor(double zoom_factor) {
|
||||
this.zoom_factor = zoom_factor;
|
||||
}
|
||||
|
||||
public float getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(int height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public String getCamera_type() {
|
||||
return camera_type;
|
||||
}
|
||||
|
||||
public void setCamera_type(String camera_type) {
|
||||
this.camera_type = camera_type;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return locked;
|
||||
}
|
||||
|
||||
public void setLocked(boolean locked) {
|
||||
this.locked = locked;
|
||||
}
|
||||
|
||||
public String getPayload_index() {
|
||||
return payload_index;
|
||||
}
|
||||
|
||||
public void setPayload_index(String payload_index) {
|
||||
this.payload_index = payload_index;
|
||||
}
|
||||
|
||||
public String getH() {
|
||||
return h;
|
||||
}
|
||||
|
||||
public void setH(String h) {
|
||||
this.h = h;
|
||||
}
|
||||
|
||||
public String getSeq() {
|
||||
return seq;
|
||||
}
|
||||
|
||||
public void setSeq(String seq) {
|
||||
this.seq = seq;
|
||||
}
|
||||
|
||||
public String getW() {
|
||||
return w;
|
||||
}
|
||||
|
||||
public void setW(String w) {
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public String getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(String x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public String getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(String y) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public int getCamera_mode() {
|
||||
return camera_mode;
|
||||
}
|
||||
|
||||
public void setCamera_mode(int camera_mode) {
|
||||
this.camera_mode = camera_mode;
|
||||
}
|
||||
|
||||
public BreakPoint getBreak_point() {
|
||||
return break_point;
|
||||
}
|
||||
|
||||
public void setBreak_point(BreakPoint break_point) {
|
||||
this.break_point = break_point;
|
||||
}
|
||||
public int getIdeo_quality() {
|
||||
return ideo_quality;
|
||||
}
|
||||
|
||||
public void setIdeo_quality(int ideo_quality) {
|
||||
this.ideo_quality = ideo_quality;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public int getVideo_quality() {
|
||||
return video_quality;
|
||||
}
|
||||
|
||||
public void setVideo_quality(int video_quality) {
|
||||
this.video_quality = video_quality;
|
||||
}
|
||||
|
||||
public int getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(int result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public String getVideo_type() {
|
||||
return video_type;
|
||||
}
|
||||
|
||||
public void setVideo_type(String video_type) {
|
||||
this.video_type = video_type;
|
||||
}
|
||||
|
||||
|
||||
public static class File {
|
||||
private String fingerprint;
|
||||
private String url;
|
||||
|
||||
public String getFingerprint() {
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
public void setFingerprint(String fingerprint) {
|
||||
this.fingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class BreakPoint {
|
||||
private int index;
|
||||
private double progress;
|
||||
private int state;
|
||||
private int wayline_id;
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public double getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
public void setProgress(double progress) {
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(int state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public int getWayline_id() {
|
||||
return wayline_id;
|
||||
}
|
||||
|
||||
public void setWayline_id(int wayline_id) {
|
||||
this.wayline_id = wayline_id;
|
||||
}
|
||||
}
|
||||
|
||||
public AlternateLandPoint getAlternate_land_point() {
|
||||
return alternate_land_point;
|
||||
}
|
||||
|
||||
public void setAlternate_land_point(AlternateLandPoint alternate_land_point) {
|
||||
this.alternate_land_point = alternate_land_point;
|
||||
}
|
||||
|
||||
public long getExecute_time() {
|
||||
return execute_time;
|
||||
}
|
||||
|
||||
public void setExecute_time(long execute_time) {
|
||||
this.execute_time = execute_time;
|
||||
}
|
||||
|
||||
public int getExit_wayline_when_rc_lost() {
|
||||
return exit_wayline_when_rc_lost;
|
||||
}
|
||||
|
||||
public void setExit_wayline_when_rc_lost(int exit_wayline_when_rc_lost) {
|
||||
this.exit_wayline_when_rc_lost = exit_wayline_when_rc_lost;
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void setFile(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public String getFlight_id() {
|
||||
return flight_id;
|
||||
}
|
||||
|
||||
public void setFlight_id(String flight_id) {
|
||||
this.flight_id = flight_id;
|
||||
}
|
||||
|
||||
public int getFlight_safety_advance_check() {
|
||||
return flight_safety_advance_check;
|
||||
}
|
||||
|
||||
public void setFlight_safety_advance_check(int flight_safety_advance_check) {
|
||||
this.flight_safety_advance_check = flight_safety_advance_check;
|
||||
}
|
||||
|
||||
public int getOut_of_control_action() {
|
||||
return out_of_control_action;
|
||||
}
|
||||
|
||||
public void setOut_of_control_action(int out_of_control_action) {
|
||||
this.out_of_control_action = out_of_control_action;
|
||||
}
|
||||
|
||||
public int getRth_altitude() {
|
||||
return rth_altitude;
|
||||
}
|
||||
|
||||
public void setRth_altitude(int rth_altitude) {
|
||||
this.rth_altitude = rth_altitude;
|
||||
}
|
||||
|
||||
public int getRth_mode() {
|
||||
return rth_mode;
|
||||
}
|
||||
|
||||
public void setRth_mode(int rth_mode) {
|
||||
this.rth_mode = rth_mode;
|
||||
}
|
||||
|
||||
public boolean isTakeoffToPointTask() {
|
||||
return takeoffToPointTask;
|
||||
}
|
||||
|
||||
public void setTakeoffToPointTask(boolean takeoffToPointTask) {
|
||||
this.takeoffToPointTask = takeoffToPointTask;
|
||||
}
|
||||
|
||||
public int getTask_type() {
|
||||
return task_type;
|
||||
}
|
||||
|
||||
public void setTask_type(int task_type) {
|
||||
this.task_type = task_type;
|
||||
}
|
||||
|
||||
public int getWayline_precision_type() {
|
||||
return wayline_precision_type;
|
||||
}
|
||||
|
||||
public void setWayline_precision_type(int wayline_precision_type) {
|
||||
this.wayline_precision_type = wayline_precision_type;
|
||||
}
|
||||
|
||||
public static class AlternateLandPoint {
|
||||
private double latitude;
|
||||
private double longitude;
|
||||
private double safe_land_height;
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public double getSafe_land_height() {
|
||||
return safe_land_height;
|
||||
}
|
||||
|
||||
public void setSafe_land_height(double safe_land_height) {
|
||||
this.safe_land_height = safe_land_height;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
package com.aros.apron.entity;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* 喊话器-音频播放进度通知
|
||||
* Method: speaker_audio_play_start_progress
|
||||
*/
|
||||
public class SpeakerAudioPlayProgress {
|
||||
|
||||
private String bid;
|
||||
private String tid;
|
||||
private Long timestamp;
|
||||
private String method;
|
||||
private Integer needReply;
|
||||
private Data data;
|
||||
|
||||
public String getBid() {
|
||||
return bid;
|
||||
}
|
||||
|
||||
public void setBid(String bid) {
|
||||
this.bid = bid;
|
||||
}
|
||||
|
||||
public String getTid() {
|
||||
return tid;
|
||||
}
|
||||
|
||||
public void setTid(String tid) {
|
||||
this.tid = tid;
|
||||
}
|
||||
|
||||
public Long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(Long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public Integer getNeedReply() {
|
||||
return needReply;
|
||||
}
|
||||
|
||||
public void setNeedReply(Integer needReply) {
|
||||
this.needReply = needReply;
|
||||
}
|
||||
|
||||
public Data getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Data data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
private Integer result;
|
||||
private Output output;
|
||||
|
||||
public Integer getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(Integer result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public Output getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
public void setOutput(Output output) {
|
||||
this.output = output;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Output {
|
||||
@SerializedName("psdk_index")
|
||||
private Integer psdkIndex;
|
||||
|
||||
private String status;
|
||||
private String md5;
|
||||
private Progress progress;
|
||||
|
||||
public Integer getPsdkIndex() {
|
||||
return psdkIndex;
|
||||
}
|
||||
|
||||
public void setPsdkIndex(Integer psdkIndex) {
|
||||
this.psdkIndex = psdkIndex;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getMd5() {
|
||||
return md5;
|
||||
}
|
||||
|
||||
public void setMd5(String md5) {
|
||||
this.md5 = md5;
|
||||
}
|
||||
|
||||
public Progress getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
public void setProgress(Progress progress) {
|
||||
this.progress = progress;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Progress {
|
||||
private Integer percent;
|
||||
|
||||
@SerializedName("step_key")
|
||||
private String stepKey;
|
||||
|
||||
public Integer getPercent() {
|
||||
return percent;
|
||||
}
|
||||
|
||||
public void setPercent(Integer percent) {
|
||||
this.percent = percent;
|
||||
}
|
||||
|
||||
public String getStepKey() {
|
||||
return stepKey;
|
||||
}
|
||||
|
||||
public void setStepKey(String stepKey) {
|
||||
this.stepKey = stepKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
package com.aros.apron.entity;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* 喊话器-TTS播放进度通知
|
||||
* Method: speaker_tts_play_start_progress
|
||||
*/
|
||||
public class SpeakerTTSPlayProgress {
|
||||
|
||||
private String bid;
|
||||
private String tid;
|
||||
private Long timestamp;
|
||||
private String method;
|
||||
private Integer needReply;
|
||||
private Data data;
|
||||
|
||||
public String getBid() {
|
||||
return bid;
|
||||
}
|
||||
|
||||
public void setBid(String bid) {
|
||||
this.bid = bid;
|
||||
}
|
||||
|
||||
public String getTid() {
|
||||
return tid;
|
||||
}
|
||||
|
||||
public void setTid(String tid) {
|
||||
this.tid = tid;
|
||||
}
|
||||
|
||||
public Long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(Long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public Integer getNeedReply() {
|
||||
return needReply;
|
||||
}
|
||||
|
||||
public void setNeedReply(Integer needReply) {
|
||||
this.needReply = needReply;
|
||||
}
|
||||
|
||||
public Data getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Data data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
private Integer result;
|
||||
private Output output;
|
||||
|
||||
public Integer getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(Integer result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public Output getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
public void setOutput(Output output) {
|
||||
this.output = output;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Output {
|
||||
@SerializedName("psdk_index")
|
||||
private Integer psdkIndex;
|
||||
|
||||
private String status;
|
||||
private String md5;
|
||||
private Progress progress;
|
||||
|
||||
public Integer getPsdkIndex() {
|
||||
return psdkIndex;
|
||||
}
|
||||
|
||||
public void setPsdkIndex(Integer psdkIndex) {
|
||||
this.psdkIndex = psdkIndex;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getMd5() {
|
||||
return md5;
|
||||
}
|
||||
|
||||
public void setMd5(String md5) {
|
||||
this.md5 = md5;
|
||||
}
|
||||
|
||||
public Progress getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
public void setProgress(Progress progress) {
|
||||
this.progress = progress;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Progress {
|
||||
private Integer percent;
|
||||
|
||||
@SerializedName("step_key")
|
||||
private String stepKey;
|
||||
|
||||
public Integer getPercent() {
|
||||
return percent;
|
||||
}
|
||||
|
||||
public void setPercent(Integer percent) {
|
||||
this.percent = percent;
|
||||
}
|
||||
|
||||
public String getStepKey() {
|
||||
return stepKey;
|
||||
}
|
||||
|
||||
public void setStepKey(String stepKey) {
|
||||
this.stepKey = stepKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.aros.apron.entity;
|
||||
|
||||
import com.aros.apron.tools.PreferenceUtils;
|
||||
|
||||
public class Synchronizedstatus {
|
||||
// 1. 状态变量:保持原样 (建议用 volatile 保证可见性,虽然 synchronized 内也能保证,但加上更保险)
|
||||
private static volatile boolean FLIGHTTASK_EXECUTE_STATUS = false;
|
||||
|
||||
// 2. 初始化状态变量:用于跟踪自检状态
|
||||
private static volatile boolean INIT_STATUS = false;
|
||||
|
||||
// 3. 初始化运行状态变量:用于跟踪初始化是否正在进行
|
||||
private static volatile boolean INIT_RUNNING = false;
|
||||
|
||||
|
||||
private static volatile boolean isruning=false;
|
||||
|
||||
private static volatile boolean isruningframe=false;
|
||||
|
||||
public static boolean isIsruningframe() {
|
||||
return isruningframe;
|
||||
}
|
||||
|
||||
public static void setIsruningframe(boolean isruningframe) {
|
||||
Synchronizedstatus.isruningframe = isruningframe;
|
||||
}
|
||||
|
||||
// 4. 【新增】专门的锁对象:必须是 Object 类型,且唯一
|
||||
// 这个对象什么都不存,只用来当“钥匙”
|
||||
public static final Object LOCK_OBJ = new Object();
|
||||
|
||||
// 提供 getter/setter 方便访问状态
|
||||
|
||||
|
||||
public static boolean isIsruning() {
|
||||
return isruning;
|
||||
}
|
||||
|
||||
public static void setIsruning(boolean isruning) {
|
||||
Synchronizedstatus.isruning = isruning;
|
||||
}
|
||||
|
||||
public static boolean getFlighttaskExecuteStatus() {
|
||||
return FLIGHTTASK_EXECUTE_STATUS;
|
||||
}
|
||||
|
||||
public static void setFlighttaskExecuteStatus(boolean status) {
|
||||
FLIGHTTASK_EXECUTE_STATUS = status;
|
||||
}
|
||||
|
||||
// 提供 getter/setter 用于初始化状态
|
||||
public static boolean getInitStatus() {
|
||||
return INIT_STATUS;
|
||||
}
|
||||
|
||||
public static void setInitStatus(boolean status) {
|
||||
INIT_STATUS = status;
|
||||
}
|
||||
|
||||
// 提供 getter/setter 用于第一次收到航线指令标志位(持久化存储)
|
||||
public static boolean isFirstMissionReceived() {
|
||||
return PreferenceUtils.getInstance().isFirstMissionReceived();
|
||||
}
|
||||
|
||||
public static void setFirstMissionReceived(boolean status) {
|
||||
PreferenceUtils.getInstance().setFirstMissionReceived(status);
|
||||
}
|
||||
|
||||
// 提供 getter/setter 用于初始化运行状态
|
||||
public static boolean isInitRunning() {
|
||||
return INIT_RUNNING;
|
||||
}
|
||||
|
||||
public static void setInitRunning(boolean running) {
|
||||
INIT_RUNNING = running;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.aros.apron.entity;
|
||||
|
||||
|
||||
public class XTTSParams {
|
||||
|
||||
public String vcn = "xiaoyan";
|
||||
public int language =1 ;
|
||||
public int pitch = 50;
|
||||
public int speed = 50;
|
||||
public int volume = 100;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
package com.aros.apron.manager;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.amazonaws.auth.AWSCredentials;
|
||||
import com.amazonaws.services.s3.AmazonS3;
|
||||
import com.amazonaws.services.s3.AmazonS3Client;
|
||||
import com.amazonaws.services.s3.model.PutObjectRequest;
|
||||
import com.aros.apron.base.BaseManager;
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
import com.aros.apron.tools.PreferenceUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.ObservableEmitter;
|
||||
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
|
||||
import io.reactivex.rxjava3.core.Observer;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class LogUploadManager extends BaseManager {
|
||||
|
||||
private static final String TAG = "LogUploadManager";
|
||||
private static final String BUCKET_NAME = "msdk-log";
|
||||
private static final String DEFAULT_ENDPOINT = "http://192.168.20.90:9000";
|
||||
private static final String DEFAULT_ACCESS_KEY = "aros";
|
||||
private static final String DEFAULT_SECRET_KEY = "Aros2023";
|
||||
|
||||
// 参数别改:临时目录用于快照
|
||||
private static final String TEMP_SNAPSHOT_DIR = "/DJIDemo/cache/temp_upload";
|
||||
|
||||
private LogUploadManager() {}
|
||||
|
||||
private static class LogUploadManagerHolder {
|
||||
private static final LogUploadManager INSTANCE = new LogUploadManager();
|
||||
}
|
||||
|
||||
public static LogUploadManager getInstance() {
|
||||
return LogUploadManagerHolder.INSTANCE;
|
||||
}
|
||||
|
||||
private boolean isUploading;
|
||||
|
||||
public boolean isUploading() {
|
||||
return isUploading;
|
||||
}
|
||||
|
||||
public void uploadTodayLog() {
|
||||
String endpoint = PreferenceUtils.getInstance().getUploadUrl();
|
||||
String accessKey = PreferenceUtils.getInstance().getAccessKey();
|
||||
String secretKey = PreferenceUtils.getInstance().getSecretKey();
|
||||
|
||||
if (TextUtils.isEmpty(endpoint)) {
|
||||
endpoint = DEFAULT_ENDPOINT;
|
||||
}
|
||||
if (TextUtils.isEmpty(accessKey)) {
|
||||
accessKey = DEFAULT_ACCESS_KEY;
|
||||
}
|
||||
if (TextUtils.isEmpty(secretKey)) {
|
||||
secretKey = DEFAULT_SECRET_KEY;
|
||||
}
|
||||
|
||||
LogUtil.log(TAG, "使用MinIO配置: endpoint=" + endpoint + ", accessKey=" + accessKey);
|
||||
uploadTodayLog(endpoint, accessKey, secretKey);
|
||||
}
|
||||
|
||||
public void uploadTodayLog(String endpoint, String accessKey, String secretKey) {
|
||||
if (isUploading) {
|
||||
LogUtil.log(TAG, "日志正在上传中");
|
||||
return;
|
||||
}
|
||||
|
||||
isUploading = true;
|
||||
LogUtil.log(TAG, "开始上传当天日志");
|
||||
|
||||
File todayLogFile = getTodayLogFile();
|
||||
if (todayLogFile == null || !todayLogFile.exists()) {
|
||||
LogUtil.log(TAG, "当天日志文件不存在");
|
||||
isUploading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 关键修改:先创建快照,再上传快照
|
||||
File snapshotFile = null;
|
||||
try {
|
||||
snapshotFile = createSnapshot(todayLogFile);
|
||||
LogUtil.log(TAG, "创建快照成功: " + snapshotFile.getAbsolutePath()
|
||||
+ ", 大小: " + snapshotFile.length());
|
||||
|
||||
uploadLogFile(snapshotFile, endpoint, accessKey, secretKey);
|
||||
|
||||
} catch (IOException e) {
|
||||
LogUtil.log(TAG, "创建快照失败: " + e.getMessage());
|
||||
isUploading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件快照(解决文件写入中上传失败问题)
|
||||
* 参数别改:使用NIO通道拷贝,速度快且稳定
|
||||
*/
|
||||
private File createSnapshot(File source) throws IOException {
|
||||
// 确保临时目录存在
|
||||
File tempDir = new File(Environment.getExternalStorageDirectory() + TEMP_SNAPSHOT_DIR);
|
||||
if (!tempDir.exists()) {
|
||||
tempDir.mkdirs();
|
||||
}
|
||||
|
||||
// 修复:时间戳放中间,后缀用.tmp,避免和原文件.txt混淆
|
||||
// 结果:20260310_1741605600000.tmp
|
||||
String baseName = source.getName(); // 20260310.txt
|
||||
String nameWithoutExt = baseName.substring(0, baseName.lastIndexOf('.')); // 20260310
|
||||
String snapshotName = nameWithoutExt + "_" + System.currentTimeMillis() + ".tmp";
|
||||
|
||||
File snapshot = new File(tempDir, snapshotName);
|
||||
|
||||
// NIO零拷贝(快速)
|
||||
try (FileChannel srcChannel = new FileInputStream(source).getChannel();
|
||||
FileChannel dstChannel = new FileOutputStream(snapshot).getChannel()) {
|
||||
|
||||
long transferred = srcChannel.transferTo(0, srcChannel.size(), dstChannel);
|
||||
LogUtil.log(TAG, "快照拷贝完成: " + transferred + " bytes");
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private File getTodayLogFile() {
|
||||
String logDir = getLogDir();
|
||||
String todayDate = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
String fileName = todayDate + ".txt";
|
||||
return new File(logDir, fileName);
|
||||
}
|
||||
|
||||
private String getLogDir() {
|
||||
return Environment.getExternalStorageDirectory() + "/DJIDemo/cache/log";
|
||||
}
|
||||
|
||||
private void uploadLogFile(File logFile, String endpoint, String accessKey, String secretKey) {
|
||||
AmazonS3 s3 = new AmazonS3Client(new AWSCredentials() {
|
||||
@Override
|
||||
public String getAWSAccessKeyId() {
|
||||
return accessKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAWSSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
});
|
||||
|
||||
Observable.create(new ObservableOnSubscribe<Boolean>() {
|
||||
@Override
|
||||
public void subscribe(ObservableEmitter<Boolean> emitter) throws Exception {
|
||||
try {
|
||||
s3.setEndpoint(endpoint);
|
||||
|
||||
boolean bucketExists = s3.doesBucketExist(BUCKET_NAME);
|
||||
if (!bucketExists) {
|
||||
LogUtil.log(TAG, "创建桶: " + BUCKET_NAME);
|
||||
s3.createBucket(BUCKET_NAME);
|
||||
}
|
||||
|
||||
String objectKey = getObjectKey(logFile.getName());
|
||||
// 注意:这里上传的是快照文件,不是原日志文件
|
||||
LogUtil.log(TAG, "上传文件: " + logFile.getName()
|
||||
+ " 到: " + objectKey
|
||||
+ ", 大小: " + logFile.length());
|
||||
|
||||
s3.putObject(
|
||||
new PutObjectRequest(
|
||||
BUCKET_NAME,
|
||||
objectKey,
|
||||
logFile
|
||||
)
|
||||
);
|
||||
|
||||
emitter.onNext(true);
|
||||
emitter.onComplete();
|
||||
} catch (Exception e) {
|
||||
emitter.onError(e);
|
||||
}
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Observer<Boolean>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {}
|
||||
|
||||
@Override
|
||||
public void onNext(Boolean success) {
|
||||
sendEvent2Server("日志上传成功: ",1);
|
||||
LogUtil.log(TAG, "日志上传成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
sendEvent2Server("日志上传失败: " + e.getMessage(),2);
|
||||
LogUtil.log(TAG, "日志上传失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
// 失败也要清理快照
|
||||
deleteSnapshot(logFile);
|
||||
isUploading = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
isUploading = false;
|
||||
// 关键:上传完成后删除快照,避免堆积
|
||||
deleteSnapshot(logFile);
|
||||
sendEvent2Server("日志上传完成,清理快照",1);
|
||||
LogUtil.log(TAG, "日志上传完成,清理快照");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除快照文件
|
||||
*/
|
||||
private void deleteSnapshot(File snapshot) {
|
||||
if (snapshot != null && snapshot.exists() && snapshot.getName().endsWith(".tmp")) {
|
||||
boolean deleted = snapshot.delete();
|
||||
LogUtil.log(TAG, "快照清理" + (deleted ? "成功" : "失败") + ": " + snapshot.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private String getObjectKey(String snapshotName) {
|
||||
String objectKeyPrefix = PreferenceUtils.getInstance().getObjectKey();
|
||||
if (TextUtils.isEmpty(objectKeyPrefix)) {
|
||||
objectKeyPrefix = "1";
|
||||
}
|
||||
// 修复:从 20260310_1741605600000.tmp 还原为 20260310.txt
|
||||
String realFileName = snapshotName.replaceAll("_\\d+\\.tmp$", ".txt");
|
||||
return objectKeyPrefix + "/" + realFileName;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package com.aros.apron.manager;
|
||||
|
||||
import static com.aros.apron.tools.Utils.getIDJIErrorMsg;
|
||||
import static dji.sdk.keyvalue.key.KeyTools.createKey;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.aros.apron.base.BaseManager;
|
||||
import com.aros.apron.entity.Movement;
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
import com.aros.apron.tools.PreferenceUtils;
|
||||
|
||||
import dji.sdk.keyvalue.key.FlightControllerKey;
|
||||
import dji.sdk.keyvalue.key.KeyTools;
|
||||
import dji.sdk.keyvalue.value.flightcontroller.NavigationSatelliteSystem;
|
||||
import dji.v5.common.callback.CommonCallbacks;
|
||||
import dji.v5.common.error.IDJIError;
|
||||
import dji.v5.manager.KeyManager;
|
||||
|
||||
|
||||
public class NavigationSatelliteSystemManager extends BaseManager {
|
||||
|
||||
|
||||
private NavigationSatelliteSystemManager() {
|
||||
}
|
||||
|
||||
private static class NavigationSatelliteHolder {
|
||||
private static final NavigationSatelliteSystemManager INSTANCE = new NavigationSatelliteSystemManager();
|
||||
}
|
||||
|
||||
public static NavigationSatelliteSystemManager getInstance() {
|
||||
return NavigationSatelliteHolder.INSTANCE;
|
||||
}
|
||||
|
||||
public void initNavigationSatelliteSystem() {
|
||||
Boolean isConnect = KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey.
|
||||
KeyConnection));
|
||||
if (isConnect != null && isConnect) {
|
||||
KeyManager.getInstance().listen(createKey(FlightControllerKey.KeyNavigationSatelliteSystemSource),
|
||||
this, new CommonCallbacks.KeyListener<NavigationSatelliteSystem>() {
|
||||
@Override
|
||||
public void onValueChange(@Nullable NavigationSatelliteSystem navigationSatelliteSystem,
|
||||
@Nullable NavigationSatelliteSystem t1) {
|
||||
if (t1 != null) {
|
||||
LogUtil.log(TAG, "监听卫星导航系统:" + t1.name());
|
||||
Movement.getInstance().setNavigationSatelliteSystem(t1.value());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private int setLTEEnhancedTransmissionTypeTimes;
|
||||
private boolean isLTEEnhancedTransmissionLte;
|
||||
|
||||
public void setNavigationSatelliteSystem() {
|
||||
Boolean isConnect = KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey.
|
||||
KeyConnection));
|
||||
if (isConnect != null && isConnect) {
|
||||
|
||||
KeyManager.getInstance().setValue(KeyTools.createKey(FlightControllerKey.KeyNavigationSatelliteSystemSource),
|
||||
PreferenceUtils.getInstance().getSatelliteSystem() == 1 ?
|
||||
NavigationSatelliteSystem.GPS_GLONASS : NavigationSatelliteSystem.BEIDOU,
|
||||
new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
isLTEEnhancedTransmissionLte = true;
|
||||
LogUtil.log(TAG, "设置卫星系统" + setLTEEnhancedTransmissionTypeTimes + "次成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
LogUtil.log(TAG, "设置卫星系统第" + setLTEEnhancedTransmissionTypeTimes + "次失败:" + getIDJIErrorMsg(error));
|
||||
if (!isLTEEnhancedTransmissionLte) {
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (setLTEEnhancedTransmissionTypeTimes < 20) {
|
||||
setLTEEnhancedTransmissionTypeTimes++;
|
||||
setNavigationSatelliteSystem();
|
||||
}
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,415 @@
|
|||
package com.aros.apron.manager;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.aros.apron.activity.MainActivity;
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
import com.aros.apron.tools.PreferenceUtils;
|
||||
import com.aros.apron.tools.SimplePortScanner;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import dji.sdk.keyvalue.key.DJIKey;
|
||||
import dji.sdk.keyvalue.key.FlightControllerKey;
|
||||
import dji.sdk.keyvalue.value.common.ComponentIndexType;
|
||||
import dji.v5.common.callback.CommonCallbacks;
|
||||
import dji.v5.common.error.IDJIError;
|
||||
import dji.v5.manager.KeyManager;
|
||||
import dji.v5.manager.datacenter.MediaDataCenter;
|
||||
import dji.v5.manager.datacenter.livestream.LiveStreamSettings;
|
||||
import dji.v5.manager.datacenter.livestream.LiveStreamStatus;
|
||||
import dji.v5.manager.datacenter.livestream.LiveStreamStatusListener;
|
||||
import dji.v5.manager.datacenter.livestream.LiveStreamType;
|
||||
import dji.v5.manager.datacenter.livestream.LiveVideoBitrateMode;
|
||||
import dji.v5.manager.datacenter.livestream.StreamQuality;
|
||||
import dji.v5.manager.datacenter.livestream.settings.RtspSettings;
|
||||
import dji.v5.manager.interfaces.ILiveStreamManager;
|
||||
|
||||
/**
|
||||
* RTSP推流管理类
|
||||
* 负责RTSP推流的初始化、启动、停止和状态管理
|
||||
*/
|
||||
public class RtspStreamManager {
|
||||
private static final String TAG = "RtspStreamManager";
|
||||
|
||||
// 线程池和主线程Handler
|
||||
private final ExecutorService streamExecutor = Executors.newSingleThreadExecutor();
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
// 单例模式
|
||||
private static class RtspStreamHolder {
|
||||
private static final RtspStreamManager INSTANCE = new RtspStreamManager();
|
||||
}
|
||||
|
||||
public static RtspStreamManager getInstance() {
|
||||
return RtspStreamHolder.INSTANCE;
|
||||
}
|
||||
|
||||
// 状态管理
|
||||
private final AtomicBoolean isStreaming = new AtomicBoolean(false);
|
||||
private final AtomicInteger retryCount = new AtomicInteger(0);
|
||||
private final AtomicBoolean isInitializing = new AtomicBoolean(false);
|
||||
private final AtomicBoolean isForceStarting = new AtomicBoolean(false);
|
||||
|
||||
// 配置参数
|
||||
private static final int MAX_RETRY_COUNT = 20;
|
||||
private static final long RETRY_DELAY_MS = 5000;
|
||||
private static final long SETTLE_DELAY_MS = 1000;
|
||||
|
||||
// 当前相机索引
|
||||
private int currentCameraIndex = 1; // 1代表PORT_1,2代表FPV
|
||||
|
||||
private RtspStreamManager() {
|
||||
initStreamManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化流管理器
|
||||
*/
|
||||
public void initStreamManager() {
|
||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||
if (liveStreamManager != null) {
|
||||
liveStreamManager.addLiveStreamStatusListener(new LiveStreamStatusListener() {
|
||||
@Override
|
||||
public void onLiveStreamStatusUpdate(LiveStreamStatus status) {
|
||||
if (status != null) {
|
||||
boolean streaming = status.isStreaming();
|
||||
isStreaming.set(streaming);
|
||||
LogUtil.log(TAG, "推流状态: " + streaming + ", 帧率: " + status.getFps() + ", 码率: " + status.getVbps() + ", 延迟: " + status.getRtt());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(IDJIError error) {
|
||||
LogUtil.log(TAG, "推流错误: " + new Gson().toJson(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动RTSP推流
|
||||
*/
|
||||
public void startRtspStream() {
|
||||
if (isInitializing.get() || isForceStarting.get()) {
|
||||
LogUtil.log(TAG, "推流正在初始化中,请勿重复调用");
|
||||
return;
|
||||
}
|
||||
|
||||
isInitializing.set(true);
|
||||
retryCount.set(0);
|
||||
|
||||
streamExecutor.execute(() -> {
|
||||
try {
|
||||
doStartRtspStream();
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG, "启动推流异常: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
isInitializing.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际启动RTSP推流的方法
|
||||
*/
|
||||
private void doStartRtspStream() {
|
||||
// 检查飞行器连接状态
|
||||
Boolean isAircraftConnected = KeyManager.getInstance().getValue(DJIKey.create(FlightControllerKey.KeyConnection));
|
||||
if (isAircraftConnected == null || !isAircraftConnected) {
|
||||
LogUtil.log(TAG, "飞行器未连接,无法启动推流");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查RTSP配置
|
||||
if (!validateRtspConfig()) {
|
||||
LogUtil.log(TAG, "RTSP配置参数不完整");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查相机流状态
|
||||
if (!MainActivity.Companion.getStreamReceive() && retryCount.get() < MAX_RETRY_COUNT) {
|
||||
handleStreamNotReady();
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动推流
|
||||
startStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理相机流未准备好的情况
|
||||
*/
|
||||
private void handleStreamNotReady() {
|
||||
LogUtil.log(TAG, "相机流未准备好,尝试恢复 (重试次数: " + retryCount.get() + ")");
|
||||
|
||||
// 尝试切换视频源
|
||||
mainHandler.post(() -> {
|
||||
MainActivity mainActivity = MainActivity.Companion.getInstance();
|
||||
if (mainActivity != null) {
|
||||
mainActivity.swapVideoSource();
|
||||
}
|
||||
});
|
||||
|
||||
// 延迟重试
|
||||
mainHandler.postDelayed(() -> {
|
||||
streamExecutor.execute(() -> {
|
||||
int count = retryCount.incrementAndGet();
|
||||
if (count < MAX_RETRY_COUNT) {
|
||||
LogUtil.log(TAG, "重试启动推流 (次数: " + count + ")");
|
||||
doStartRtspStream();
|
||||
} else {
|
||||
LogUtil.log(TAG, "达到最大重试次数,强制启动推流");
|
||||
forceStartStream();
|
||||
}
|
||||
});
|
||||
}, RETRY_DELAY_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动推流
|
||||
*/
|
||||
private void startStream() {
|
||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||
if (liveStreamManager == null) {
|
||||
LogUtil.log(TAG, "LiveStreamManager获取失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 配置推流参数
|
||||
configureStreamSettings(liveStreamManager);
|
||||
|
||||
// 等待配置生效
|
||||
try {
|
||||
Thread.sleep(SETTLE_DELAY_MS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动推流
|
||||
if (!liveStreamManager.isStreaming()) {
|
||||
doStartStream(liveStreamManager, false);
|
||||
} else {
|
||||
// 先停止再启动
|
||||
liveStreamManager.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
try {
|
||||
Thread.sleep(SETTLE_DELAY_MS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
doStartStream(liveStreamManager, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
LogUtil.log(TAG, "停止旧流失败: " + new Gson().toJson(error));
|
||||
// 即使停止失败也尝试启动
|
||||
doStartStream(liveStreamManager, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制启动推流
|
||||
*/
|
||||
private void forceStartStream() {
|
||||
isForceStarting.set(true);
|
||||
LogUtil.log(TAG, "开始强制启动推流");
|
||||
|
||||
streamExecutor.execute(() -> {
|
||||
try {
|
||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||
if (liveStreamManager == null) {
|
||||
LogUtil.log(TAG, "LiveStreamManager获取失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 配置推流参数
|
||||
configureStreamSettings(liveStreamManager);
|
||||
|
||||
// 直接启动
|
||||
liveStreamManager.startStream(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
mainHandler.post(() -> {
|
||||
LogUtil.log(TAG, "强制启动RTSP推流成功");
|
||||
isStreaming.set(true);
|
||||
startPortScanner();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
mainHandler.post(() -> {
|
||||
LogUtil.log(TAG, "强制启动RTSP推流失败: " + new Gson().toJson(error));
|
||||
});
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
isForceStarting.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置推流设置
|
||||
*/
|
||||
private void configureStreamSettings(ILiveStreamManager liveStreamManager) {
|
||||
mainHandler.post(() -> {
|
||||
try {
|
||||
LiveStreamSettings.Builder streamSettingBuilder = new LiveStreamSettings.Builder();
|
||||
LiveStreamSettings streamSettings = streamSettingBuilder
|
||||
.setLiveStreamType(LiveStreamType.RTSP)
|
||||
.setRtspSettings(new RtspSettings.Builder()
|
||||
.setPassWord(PreferenceUtils.getInstance().getRtspPassWord())
|
||||
.setPort(Integer.parseInt(PreferenceUtils.getInstance().getRtspPort()))
|
||||
.setUserName(PreferenceUtils.getInstance().getRtspUserName())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
liveStreamManager.setLiveStreamSettings(streamSettings);
|
||||
liveStreamManager.setCameraIndex(currentCameraIndex == 1 ? ComponentIndexType.PORT_1 : ComponentIndexType.FPV);
|
||||
liveStreamManager.setLiveStreamQuality(StreamQuality.FULL_HD);
|
||||
liveStreamManager.setLiveVideoBitrateMode(LiveVideoBitrateMode.AUTO);
|
||||
|
||||
LogUtil.log(TAG, "推流配置完成,相机索引: " + currentCameraIndex);
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG, "配置推流参数异常: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行启动流操作
|
||||
*/
|
||||
private void doStartStream(ILiveStreamManager liveStreamManager, boolean isRestart) {
|
||||
liveStreamManager.startStream(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
mainHandler.post(() -> {
|
||||
LogUtil.log(TAG, "RTSP推流启动成功" + (isRestart ? "(重启)" : ""));
|
||||
isStreaming.set(true);
|
||||
startPortScanner();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
mainHandler.post(() -> {
|
||||
int count = retryCount.incrementAndGet();
|
||||
LogUtil.log(TAG, "启动RTSP推流失败 (次数: " + count + "): " + new Gson().toJson(error));
|
||||
|
||||
if (count < MAX_RETRY_COUNT) {
|
||||
// 延迟重试
|
||||
mainHandler.postDelayed(() -> {
|
||||
streamExecutor.execute(() -> {
|
||||
LogUtil.log(TAG, "重试启动推流 (次数: " + (count + 1) + ")");
|
||||
doStartRtspStream();
|
||||
});
|
||||
}, RETRY_DELAY_MS);
|
||||
} else {
|
||||
LogUtil.log(TAG, "达到最大重试次数,强制启动推流");
|
||||
forceStartStream();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止推流
|
||||
*/
|
||||
public void stopStream() {
|
||||
streamExecutor.execute(() -> {
|
||||
ILiveStreamManager liveStreamManager = MediaDataCenter.getInstance().getLiveStreamManager();
|
||||
if (liveStreamManager != null) {
|
||||
liveStreamManager.stopStream(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
mainHandler.post(() -> {
|
||||
LogUtil.log(TAG, "推流停止成功");
|
||||
isStreaming.set(false);
|
||||
SimplePortScanner.getInstance().stopScan();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
mainHandler.post(() -> {
|
||||
LogUtil.log(TAG, "推流停止失败: " + new Gson().toJson(error));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换相机
|
||||
*/
|
||||
public void switchCamera(int cameraIndex) {
|
||||
if (cameraIndex != 1 && cameraIndex != 2) {
|
||||
LogUtil.log(TAG, "无效的相机索引: " + cameraIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
currentCameraIndex = cameraIndex;
|
||||
LogUtil.log(TAG, "切换相机索引: " + cameraIndex);
|
||||
|
||||
// 如果正在推流,重启推流
|
||||
if (isStreaming.get()) {
|
||||
LogUtil.log(TAG, "切换相机后重启推流");
|
||||
stopStream();
|
||||
mainHandler.postDelayed(this::startRtspStream, SETTLE_DELAY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动端口扫描
|
||||
*/
|
||||
private void startPortScanner() {
|
||||
try {
|
||||
SimplePortScanner.getInstance().startScan();
|
||||
LogUtil.log(TAG, "端口扫描已启动");
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG, "启动端口扫描失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证RTSP配置
|
||||
*/
|
||||
private boolean validateRtspConfig() {
|
||||
return PreferenceUtils.getInstance().getRtspUserName() != null &&
|
||||
PreferenceUtils.getInstance().getRtspPort() != null &&
|
||||
PreferenceUtils.getInstance().getRtspPassWord() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推流状态
|
||||
*/
|
||||
public boolean isStreaming() {
|
||||
return isStreaming.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前相机索引
|
||||
*/
|
||||
public int getCurrentCameraIndex() {
|
||||
return currentCameraIndex;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
package com.aros.apron.manager;
|
||||
|
||||
import static com.aros.apron.tools.Utils.getIDJIErrorMsg;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.aros.apron.base.BaseManager;
|
||||
import com.aros.apron.entity.MessageDown;
|
||||
import com.aros.apron.entity.Movement;
|
||||
import com.aros.apron.entity.XTTSParams;
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
import com.aros.apron.tools.SpeakerProgressReporter;
|
||||
import com.aros.apron.tools.Utils;
|
||||
import com.aros.apron.tools.XTtsPcmGenerator;
|
||||
import com.iflytek.aikit.core.AiListener;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import dji.v5.common.callback.CommonCallbacks;
|
||||
import dji.v5.common.error.IDJIError;
|
||||
import dji.v5.common.recorder.PCMTools;
|
||||
import dji.v5.manager.aircraft.megaphone.FileInfo;
|
||||
import dji.v5.manager.aircraft.megaphone.MegaphoneIndex;
|
||||
import dji.v5.manager.aircraft.megaphone.MegaphoneInfo;
|
||||
import dji.v5.manager.aircraft.megaphone.MegaphoneInfoListener;
|
||||
import dji.v5.manager.aircraft.megaphone.MegaphoneManager;
|
||||
import dji.v5.manager.aircraft.megaphone.PlayMode;
|
||||
import dji.v5.manager.aircraft.megaphone.UploadType;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
|
||||
public class SpeakerManager extends BaseManager {
|
||||
private SpeakerManager() {
|
||||
}
|
||||
private static class SpeakerHolder {
|
||||
private static final SpeakerManager INSTANCE = new SpeakerManager();
|
||||
}
|
||||
public static SpeakerManager getInstance() {
|
||||
return SpeakerHolder.INSTANCE;
|
||||
}
|
||||
private int megaphoneStatus;
|
||||
public void initMegaphoneInfo() {
|
||||
MegaphoneManager.getInstance().addMegaphoneInfoListener(new MegaphoneInfoListener() {
|
||||
@Override
|
||||
public void onUpdateMegaphoneInfo(MegaphoneInfo megaphoneInfo) {
|
||||
if (megaphoneInfo != null) {
|
||||
megaphoneStatus = megaphoneInfo.getStatus().ordinal();
|
||||
}
|
||||
}
|
||||
});
|
||||
MegaphoneManager.getInstance().setMegaphoneIndex(MegaphoneIndex.PORT_2, new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LogUtil.log(TAG,"喊话器位置设置成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
LogUtil.log(TAG,"喊话器位置设置失败:"+getIDJIErrorMsg(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
public void speakerAudioPlayStart(MessageDown message) {
|
||||
if (TextUtils.isEmpty(message.getData().getFile().getUrl())) {
|
||||
sendFailMsg2Server(message, "喊话文件下载地址为空");
|
||||
return;
|
||||
}
|
||||
Request request = new Request.Builder().url(message.getData().getFile().getUrl()).build();
|
||||
new OkHttpClient().newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
sendEvent2Server(".pcm文件下载失败:" + e.toString(), 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
if (response != null) {
|
||||
InputStream is = null;
|
||||
byte[] buf = new byte[2048];
|
||||
int len = 0;
|
||||
FileOutputStream fos = null;
|
||||
// 储存下载文件的目录
|
||||
File dir = new File(Environment.getExternalStorageDirectory().getPath());
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
File file = new File(dir, "voice.pcm");
|
||||
try {
|
||||
is = response.body().byteStream();
|
||||
fos = new FileOutputStream(file);
|
||||
while ((len = is.read(buf)) != -1) {
|
||||
fos.write(buf, 0, len);
|
||||
}
|
||||
fos.flush();
|
||||
sendEvent2Server("喊话.pcm文件下载成功", 1);
|
||||
String opusFilePath = PCMTools.INSTANCE.convertToOpusFileSync(file.getAbsolutePath());
|
||||
|
||||
Movement.getInstance().setStep_key("download");
|
||||
Movement.getInstance().setTTS_status("in_progress");
|
||||
|
||||
FileInfo fileInfo = new FileInfo(UploadType.VOICE_FILE,
|
||||
new File(opusFilePath),
|
||||
null);
|
||||
MegaphoneManager.getInstance().startPushingFileToMegaphone(fileInfo,
|
||||
new CommonCallbacks.CompletionCallbackWithProgress<Integer>() {
|
||||
@Override
|
||||
public void onProgressUpdate(Integer integer) {
|
||||
|
||||
Movement.getInstance().setSpeakerpercent(integer);
|
||||
|
||||
LogUtil.log(TAG, "喊话器内容上传进度:" + integer + "%");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LogUtil.log(TAG, "喊话器内容上传成功");
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MegaphoneManager.getInstance().startPlay(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
Movement.getInstance().setTTS_status("ok");
|
||||
|
||||
sendEvent2Server("喊话器播放音频成功", 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
sendEvent2Server("喊话器播放音频失败:" + getIDJIErrorMsg(error), 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
sendFailMsg2Server(message, "喊话器内容上传失败:" + getIDJIErrorMsg(error));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
sendEvent2Server("喊话.pcm文件下载失败:" + e.toString(), 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void speakerReply(MessageDown message) {
|
||||
MegaphoneManager.getInstance().startPlay(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
sendMsg2Server(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
sendFailMsg2Server(message, "喊话器播放音频失败:" + getIDJIErrorMsg(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void speakerStop(MessageDown message) {
|
||||
MegaphoneManager.getInstance().stopPlay(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
SpeakerProgressReporter.getInstance().stopAudioReport();
|
||||
SpeakerProgressReporter.getInstance().stopTTSReport();
|
||||
sendMsg2Server(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
SpeakerProgressReporter.getInstance().stopAudioReport();
|
||||
SpeakerProgressReporter.getInstance().stopTTSReport();
|
||||
sendMsg2Server(message);
|
||||
sendFailMsg2Server(message, "喊话器停止播放失败:" + getIDJIErrorMsg(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void speakerPlayModeSet(MessageDown message) {
|
||||
MegaphoneManager.getInstance().setPlayMode(message.getData().getPlay_mode() == 0 ?
|
||||
PlayMode.SINGLE : PlayMode.LOOP, new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
Movement.getInstance().setStep_key("change_work_mode");
|
||||
sendMsg2Server(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
sendFailMsg2Server(message, "喊话器播放模式设置失败:" + getIDJIErrorMsg(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void speakerPlayVolumeSet(MessageDown message) {
|
||||
MegaphoneManager.getInstance().setVolume(message.getData().getPlay_volume(), new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
sendMsg2Server(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
sendFailMsg2Server(message, "喊话器音量设置失败:" + getIDJIErrorMsg(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String text;
|
||||
|
||||
public void speakerTTSPlayStart(MessageDown message, int type) {
|
||||
message.getData().getTts().getMd5();
|
||||
XTTSParams params = new XTTSParams();
|
||||
if (type == 0) {
|
||||
text = message.getData().getTts().getText();
|
||||
} else {
|
||||
if (megaphoneStatus == 2) {
|
||||
MegaphoneManager.getInstance().stopPlay(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
|
||||
LogUtil.log(TAG, "终止喊话");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
LogUtil.log(TAG, "终止喊话失败:" + getIDJIErrorMsg(error));
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
params.vcn = message.getData().getType() == 0 ? "xiaofeng" : "xiaoyan";
|
||||
params.language = message.getData().getLanguage() == 0 ? 1 : 2;
|
||||
params.speed = message.getData().getSpeed();
|
||||
params.volume = message.getData().getVolume();
|
||||
}
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
sendFailMsg2Server(message, "喊话失败:喊话内容为空");
|
||||
return;
|
||||
}
|
||||
XTtsPcmGenerator tts = new XTtsPcmGenerator();
|
||||
AiListener listener = tts.buildListener();
|
||||
tts.init(listener);
|
||||
|
||||
File pcmFile = new File(Utils.getSDCardPath() + "/pcm", "tts_output_local.pcm");
|
||||
|
||||
int synthToPcm = tts.synthToPcm(
|
||||
text,
|
||||
params,
|
||||
pcmFile
|
||||
);
|
||||
if (synthToPcm == 0) {
|
||||
sendMsg2Server(message);
|
||||
String opusFilePath = PCMTools.INSTANCE.convertToOpusFileSync(pcmFile.getAbsolutePath());
|
||||
|
||||
FileInfo fileInfo = new FileInfo(UploadType.VOICE_FILE,
|
||||
new File(opusFilePath),
|
||||
null);
|
||||
MegaphoneManager.getInstance().startPushingFileToMegaphone(fileInfo,
|
||||
new CommonCallbacks.CompletionCallbackWithProgress<Integer>() {
|
||||
@Override
|
||||
public void onProgressUpdate(Integer integer) {
|
||||
Movement.getInstance().setTTS_status("in_progress");
|
||||
Movement.getInstance().setSpeakerpercent(integer);
|
||||
Movement.getInstance().setStep_key("upload");
|
||||
LogUtil.log(TAG, "喊话器内容上传进度:" + integer + "%");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LogUtil.log(TAG, "喊话器内容上传成功");
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MegaphoneManager.getInstance().startPlay(new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
Movement.getInstance().setTTS_status("ok");
|
||||
Movement.getInstance().setStep_key("play");
|
||||
sendEvent2Server("喊话器播放TTS音频成功", 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
sendEvent2Server("喊话器播放TTS音频失败:" + getIDJIErrorMsg(error), 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
sendFailMsg2Server(message, "喊话器内容上传失败:" + getIDJIErrorMsg(error));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sendFailMsg2Server(message, "tts合成失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
package com.aros.apron.manager;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import dji.sdk.keyvalue.value.common.ComponentIndexType;
|
||||
import dji.v5.manager.datacenter.MediaDataCenter;
|
||||
import dji.v5.manager.datacenter.camera.StreamInfo;
|
||||
import dji.v5.manager.interfaces.ICameraStreamManager;
|
||||
|
||||
/**
|
||||
* UDP视频流推送管理类
|
||||
* 负责从视频监听器获取码流数据并通过UDP协议推送
|
||||
*/
|
||||
public class UdpStreamManager {
|
||||
private static final String TAG = "UdpStreamManager";
|
||||
|
||||
// 线程池和主线程Handler
|
||||
private final ExecutorService udpExecutor = Executors.newSingleThreadExecutor();
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
// 单例模式
|
||||
private static class UdpStreamHolder {
|
||||
private static final UdpStreamManager INSTANCE = new UdpStreamManager();
|
||||
}
|
||||
|
||||
public static UdpStreamManager getInstance() {
|
||||
return UdpStreamHolder.INSTANCE;
|
||||
}
|
||||
|
||||
// UDP配置
|
||||
private static final String DEFAULT_HOST = "127.0.0.1";
|
||||
private static final int DEFAULT_PORT_1_PORT = 8080;
|
||||
private static final int DEFAULT_FPV_PORT = 8081;
|
||||
private String targetHost = DEFAULT_HOST;
|
||||
private int port1Port = DEFAULT_PORT_1_PORT;
|
||||
private int fpvPort = DEFAULT_FPV_PORT;
|
||||
|
||||
// 状态管理
|
||||
private final AtomicBoolean isStreaming = new AtomicBoolean(false);
|
||||
private final AtomicBoolean isInitialized = new AtomicBoolean(false);
|
||||
|
||||
// UDP相关
|
||||
private DatagramSocket port1UdpSocket;
|
||||
private DatagramSocket fpvUdpSocket;
|
||||
private InetAddress targetAddress;
|
||||
|
||||
// 视频码流监听器
|
||||
private ICameraStreamManager.ReceiveStreamListener port1StreamListener;
|
||||
private ICameraStreamManager.ReceiveStreamListener fpvStreamListener;
|
||||
|
||||
private UdpStreamManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化UDP流管理器
|
||||
* @param host 目标主机地址
|
||||
* @param port1Port PORT_1相机的目标端口
|
||||
* @param fpvPort FPV相机的目标端口
|
||||
*/
|
||||
public void init(String host, int port1Port, int fpvPort) {
|
||||
if (isInitialized.get()) {
|
||||
LogUtil.log(TAG, "UDP流管理器已初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
targetHost = host != null ? host : DEFAULT_HOST;
|
||||
this.port1Port = port1Port > 0 ? port1Port : DEFAULT_PORT_1_PORT;
|
||||
this.fpvPort = fpvPort > 0 ? fpvPort : DEFAULT_FPV_PORT;
|
||||
|
||||
udpExecutor.execute(() -> {
|
||||
try {
|
||||
// 初始化UDP socket
|
||||
port1UdpSocket = new DatagramSocket();
|
||||
fpvUdpSocket = new DatagramSocket();
|
||||
targetAddress = InetAddress.getByName(targetHost);
|
||||
|
||||
// 初始化视频码流监听器
|
||||
initStreamListeners();
|
||||
|
||||
isInitialized.set(true);
|
||||
LogUtil.log(TAG, "UDP流管理器初始化成功,目标地址: " + targetHost + ", PORT_1端口: " + this.port1Port + ", FPV端口: " + this.fpvPort);
|
||||
} catch (SocketException e) {
|
||||
LogUtil.log(TAG, "UDP socket初始化失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} catch (UnknownHostException e) {
|
||||
LogUtil.log(TAG, "无法解析目标地址: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化UDP流管理器(使用默认端口)
|
||||
* @param host 目标主机地址
|
||||
*/
|
||||
public void init(String host) {
|
||||
init(host, DEFAULT_PORT_1_PORT, DEFAULT_FPV_PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化UDP流管理器(使用默认地址和端口)
|
||||
*/
|
||||
public void init() {
|
||||
init(DEFAULT_HOST, DEFAULT_PORT_1_PORT, DEFAULT_FPV_PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视频码流监听器
|
||||
*/
|
||||
private void initStreamListeners() {
|
||||
ICameraStreamManager cameraManager = MediaDataCenter.getInstance().getCameraStreamManager();
|
||||
if (cameraManager == null) {
|
||||
LogUtil.log(TAG, "CameraStreamManager获取失败");
|
||||
return;
|
||||
}
|
||||
|
||||
port1StreamListener=new ICameraStreamManager.ReceiveStreamListener() {
|
||||
@Override
|
||||
public void onReceiveStream(@NonNull byte[] data, int offset, int length, @NonNull StreamInfo info) {
|
||||
if (isStreaming.get()) {
|
||||
// 截取有效数据
|
||||
byte[] streamData = new byte[length];
|
||||
System.arraycopy(data, offset, streamData, 0, length);
|
||||
sendStreamData(streamData, ComponentIndexType.PORT_1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// FPV相机码流监听器
|
||||
fpvStreamListener=new ICameraStreamManager.ReceiveStreamListener() {
|
||||
@Override
|
||||
public void onReceiveStream(@NonNull byte[] data, int offset, int length, @NonNull StreamInfo info) {
|
||||
if (isStreaming.get()) {
|
||||
// 截取有效数据
|
||||
byte[] streamData = new byte[length];
|
||||
System.arraycopy(data, offset, streamData, 0, length);
|
||||
sendStreamData(streamData, ComponentIndexType.FPV);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 注册监听器
|
||||
cameraManager.addReceiveStreamListener(ComponentIndexType.PORT_1, port1StreamListener);
|
||||
cameraManager.addReceiveStreamListener(ComponentIndexType.FPV, fpvStreamListener);
|
||||
|
||||
LogUtil.log(TAG, "视频码流监听器注册成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送码流数据
|
||||
* @param streamData 码流数据
|
||||
* @param componentIndex 相机组件索引
|
||||
*/
|
||||
private void sendStreamData(byte[] streamData, ComponentIndexType componentIndex) {
|
||||
if (targetAddress == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 选择对应的UDP socket和端口
|
||||
DatagramSocket socket = null;
|
||||
int port = 0;
|
||||
|
||||
if (componentIndex == ComponentIndexType.PORT_1) {
|
||||
socket = port1UdpSocket;
|
||||
port = port1Port;
|
||||
} else if (componentIndex == ComponentIndexType.FPV) {
|
||||
socket = fpvUdpSocket;
|
||||
port = fpvPort;
|
||||
}
|
||||
|
||||
if (socket == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 构造UDP数据包
|
||||
// 前4字节表示相机索引(1: PORT_1, 2: FPV)
|
||||
// 接下来4字节表示数据长度
|
||||
// 剩余部分为码流数据
|
||||
int headerSize = 8;
|
||||
byte[] packetData = new byte[headerSize + streamData.length];
|
||||
|
||||
// 写入相机索引
|
||||
int cameraIndex = componentIndex == ComponentIndexType.PORT_1 ? 1 : 2;
|
||||
packetData[0] = (byte) (cameraIndex >> 24);
|
||||
packetData[1] = (byte) (cameraIndex >> 16);
|
||||
packetData[2] = (byte) (cameraIndex >> 8);
|
||||
packetData[3] = (byte) cameraIndex;
|
||||
|
||||
// 写入数据长度
|
||||
packetData[4] = (byte) (streamData.length >> 24);
|
||||
packetData[5] = (byte) (streamData.length >> 16);
|
||||
packetData[6] = (byte) (streamData.length >> 8);
|
||||
packetData[7] = (byte) streamData.length;
|
||||
|
||||
// 写入码流数据
|
||||
System.arraycopy(streamData, 0, packetData, headerSize, streamData.length);
|
||||
|
||||
// 发送UDP数据包
|
||||
DatagramPacket packet = new DatagramPacket(
|
||||
packetData,
|
||||
packetData.length,
|
||||
targetAddress,
|
||||
port
|
||||
);
|
||||
|
||||
socket.send(packet);
|
||||
|
||||
} catch (IOException e) {
|
||||
LogUtil.log(TAG, "发送UDP数据包失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始UDP流推送
|
||||
*/
|
||||
public void startStreaming() {
|
||||
if (!isInitialized.get()) {
|
||||
LogUtil.log(TAG, "UDP流管理器未初始化,请先调用init方法");
|
||||
return;
|
||||
}
|
||||
|
||||
isStreaming.set(true);
|
||||
LogUtil.log(TAG, "UDP流推送已启动");
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止UDP流推送
|
||||
*/
|
||||
public void stopStreaming() {
|
||||
isStreaming.set(false);
|
||||
LogUtil.log(TAG, "UDP流推送已停止");
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭UDP流管理器
|
||||
*/
|
||||
public void close() {
|
||||
stopStreaming();
|
||||
|
||||
// 移除视频码流监听器
|
||||
ICameraStreamManager cameraManager = MediaDataCenter.getInstance().getCameraStreamManager();
|
||||
if (cameraManager != null) {
|
||||
if (port1StreamListener != null) {
|
||||
cameraManager.removeReceiveStreamListener(port1StreamListener);
|
||||
}
|
||||
if (fpvStreamListener != null) {
|
||||
cameraManager.removeReceiveStreamListener(fpvStreamListener);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭UDP socket
|
||||
if (port1UdpSocket != null) {
|
||||
port1UdpSocket.close();
|
||||
port1UdpSocket = null;
|
||||
}
|
||||
if (fpvUdpSocket != null) {
|
||||
fpvUdpSocket.close();
|
||||
fpvUdpSocket = null;
|
||||
}
|
||||
|
||||
isInitialized.set(false);
|
||||
LogUtil.log(TAG, "UDP流管理器已关闭");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置目标地址
|
||||
* @param host 目标主机地址
|
||||
* @param port1Port PORT_1相机的目标端口
|
||||
* @param fpvPort FPV相机的目标端口
|
||||
*/
|
||||
public void setTargetAddress(String host, int port1Port, int fpvPort) {
|
||||
if (host != null) {
|
||||
targetHost = host;
|
||||
}
|
||||
if (port1Port > 0) {
|
||||
this.port1Port = port1Port;
|
||||
}
|
||||
if (fpvPort > 0) {
|
||||
this.fpvPort = fpvPort;
|
||||
}
|
||||
|
||||
udpExecutor.execute(() -> {
|
||||
try {
|
||||
targetAddress = InetAddress.getByName(targetHost);
|
||||
LogUtil.log(TAG, "目标地址已更新: " + targetHost + ", PORT_1端口: " + this.port1Port + ", FPV端口: " + this.fpvPort);
|
||||
} catch (UnknownHostException e) {
|
||||
LogUtil.log(TAG, "无法解析新目标地址: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取UDP流推送状态
|
||||
*/
|
||||
public boolean isStreaming() {
|
||||
return isStreaming.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取UDP流管理器初始化状态
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return isInitialized.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前目标地址
|
||||
*/
|
||||
public String getTargetAddress() {
|
||||
return targetHost + ", PORT_1端口: " + port1Port + ", FPV端口: " + fpvPort;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package com.aros.apron.manager;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
|
||||
import org.videolan.libvlc.LibVLC;
|
||||
import org.videolan.libvlc.Media;
|
||||
import org.videolan.libvlc.MediaPlayer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* RTSP流检测工具(严格匹配LibVLC 3.6.0源码)
|
||||
* 核心:单例、初始化、拉流检测、成功/失败回调(无任何冗余)
|
||||
*/
|
||||
public class VlcRtspManager {
|
||||
private static volatile VlcRtspManager instance;
|
||||
private static final long CHECK_TIMEOUT = 5000L; // 5秒超时
|
||||
|
||||
private LibVLC libVLC;
|
||||
private MediaPlayer mediaPlayer;
|
||||
private Handler handler = new Handler(Looper.getMainLooper());
|
||||
private OnRtspCheckListener checkListener;
|
||||
private boolean isChecking = false;
|
||||
|
||||
// 仅保留成功/失败回调
|
||||
public interface OnRtspCheckListener {
|
||||
void onSuccess(); // 拉流成功
|
||||
void onFailed(); // 拉流失败
|
||||
}
|
||||
|
||||
// 单例
|
||||
private VlcRtspManager() {}
|
||||
public static VlcRtspManager getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (VlcRtspManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new VlcRtspManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 初始化(3.6.0 源码级配置)
|
||||
public void init(Context context) {
|
||||
if (libVLC != null) return;
|
||||
|
||||
List<String> options = new ArrayList<>();
|
||||
options.add("--rtsp-tcp");
|
||||
options.add("--network-caching=500");
|
||||
options.add("--vout=none");
|
||||
options.add("--aout=none");
|
||||
options.add("--quiet");
|
||||
|
||||
libVLC = new LibVLC(context.getApplicationContext(), options);
|
||||
mediaPlayer = new MediaPlayer(libVLC);
|
||||
|
||||
// ====================== 核心修正:3.6.0 事件判定(源码级) ======================
|
||||
// 参考3.6.0源码:MediaPlayer.Event的type是int型,对应EventType枚举的ordinal()
|
||||
mediaPlayer.setEventListener(new MediaPlayer.EventListener() {
|
||||
@Override
|
||||
public void onEvent(MediaPlayer.Event event) {
|
||||
// 3.6.0源码中:
|
||||
// EventType.Playing.ordinal() = 0
|
||||
// EventType.Error.ordinal() = 1
|
||||
// 直接用整型判定,避开枚举引用错误
|
||||
if (event.type == 0) { // Playing事件
|
||||
if (checkListener != null) checkListener.onSuccess();
|
||||
stopCheck();
|
||||
} else if (event.type == 1) { // Error事件
|
||||
if (checkListener != null) checkListener.onFailed();
|
||||
stopCheck();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 检测RTSP流(拉流测试)
|
||||
public void checkRtspStream(String rtspUrl, OnRtspCheckListener listener) {
|
||||
stopCheck();
|
||||
|
||||
this.checkListener = listener;
|
||||
this.isChecking = true;
|
||||
|
||||
// 超时检测
|
||||
handler.postDelayed(() -> {
|
||||
if (isChecking) {
|
||||
if (checkListener != null) checkListener.onFailed();
|
||||
stopCheck();
|
||||
}
|
||||
}, CHECK_TIMEOUT);
|
||||
|
||||
// 拉流(3.6.0 源码级写法)
|
||||
try {
|
||||
Media media = new Media(libVLC, rtspUrl);
|
||||
mediaPlayer.setMedia(media);
|
||||
media.release();
|
||||
mediaPlayer.play();
|
||||
} catch (Exception e) {
|
||||
LogUtil.log("VlcRtspManager", "拉流异常:" + e.getMessage());
|
||||
if (checkListener != null) checkListener.onFailed();
|
||||
stopCheck();
|
||||
}
|
||||
}
|
||||
|
||||
// 停止检测(私有)
|
||||
private void stopCheck() {
|
||||
isChecking = false;
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
|
||||
mediaPlayer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// 释放资源(可选)
|
||||
public void release() {
|
||||
stopCheck();
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer.release();
|
||||
mediaPlayer = null;
|
||||
}
|
||||
if (libVLC != null) {
|
||||
libVLC.release();
|
||||
libVLC = null;
|
||||
}
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,827 @@
|
|||
package com.aros.apron.tools;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.aros.apron.constant.AMSConfig;
|
||||
import com.aros.apron.entity.ArucoMarker;
|
||||
import com.aros.apron.entity.Movement;
|
||||
import com.aros.apron.manager.AlternateLandingManager;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfInt;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.opencv.objdetect.ArucoDetector;
|
||||
import org.opencv.objdetect.DetectorParameters;
|
||||
import org.opencv.objdetect.Dictionary;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 多二维码融合版本(V2.0):
|
||||
* 1. 【关键修改】先用6号二维码进行机头旋转对准,转到位(isYawAligned=true)才进入后续阶段
|
||||
* 2. 旋转阶段(vz=0)不下降,丢失时悬停等待
|
||||
* 3. 【禁用】5号二维码逻辑已注释掉
|
||||
* 4. 旋转完成后:识别1-4号/小码进行水平对准+下降
|
||||
*/
|
||||
public class ApronArucoDetectPort {
|
||||
|
||||
private static final String TAG = "ApronArucoDetect";
|
||||
|
||||
// ========== 原有参数(参数别改) ==========
|
||||
private static double LENS_OFFSET_X = 0;
|
||||
private static double LENS_OFFSET_Y = 0;
|
||||
private static final int CENTER_ERR_MAX = 50;
|
||||
private static final float SLOW_LAND_SPEED = -0.3f;
|
||||
private static final float SLOW_SUPER_SPEED = -0.15f;
|
||||
private static final int PIXEL_TRIGGER = 360;
|
||||
private static final int TRIGGER_FRAME_THRESHOLD = 2;
|
||||
|
||||
// ========== 原有状态 ==========
|
||||
private boolean isTriggerSuccess;
|
||||
private boolean arucoNotFoundTag = false;
|
||||
private boolean isStartAruco = false;
|
||||
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
|
||||
Future<?> lastFuture = null;
|
||||
private String TAG_LOG = getClass().getSimpleName();
|
||||
private boolean triggerToAlternateLandingPoint;
|
||||
long startTime;
|
||||
long endTime;
|
||||
private int sigleMarkerDetectFailsTimes;
|
||||
private boolean dropTimesTag;
|
||||
private int dropTimes = 0;
|
||||
private boolean isDoublePayload;
|
||||
private int trycount = 0;
|
||||
|
||||
public PIDControl pidControlX = null;
|
||||
public PIDControl pidControlY = null;
|
||||
public int checkThrowingErrorsTimes;
|
||||
private int consecutiveTriggerCount = 0;
|
||||
private int frameCounter = 0;
|
||||
|
||||
// ========== 【新增】阶段控制标志 ==========
|
||||
// 偏航是否已对准(6号旋转阶段完成标志)
|
||||
private volatile boolean isYawAligned = false;
|
||||
// 当前激活的降落模式:0=旋转阶段(6号), 1=小码, 2=1-4大码, 3=6号纯下降
|
||||
private int currentLandingMode = 0;
|
||||
|
||||
// ========== 【新增】连续降落条件判断 ==========
|
||||
// 记录上一帧满足的降落条件类型:0=无, 1=单码精准降落(15/17号), 2=几何中心降落
|
||||
private int lastLandingCondition = 0;
|
||||
|
||||
// ========== 标准方法 ==========
|
||||
public int getCheckThrowingErrorsTimes() { return checkThrowingErrorsTimes; }
|
||||
public void setCheckThrowingErrorsTimes(int v) { checkThrowingErrorsTimes = v; }
|
||||
public boolean isTriggerSuccess() { return isTriggerSuccess; }
|
||||
public void setTriggerSuccess(boolean v) { isTriggerSuccess = v; }
|
||||
public boolean isDoublePayload() { return isDoublePayload; }
|
||||
public void setDoublePayload(boolean v) { isDoublePayload = v; }
|
||||
public boolean isStartFastStick() { return startFastStick; }
|
||||
public void setStartFastStick(boolean v) { startFastStick = v; }
|
||||
|
||||
private boolean setMF=false;
|
||||
public boolean isCanLanding() { return canLanding; }
|
||||
public boolean isStartAruco() {
|
||||
return isStartAruco;
|
||||
}
|
||||
|
||||
public void setStartAruco(boolean startAruco) {
|
||||
isStartAruco = startAruco;
|
||||
}
|
||||
public void setCanLanding(boolean v) {
|
||||
startTime = 0;
|
||||
endTime = 0;
|
||||
canLanding = v;
|
||||
}
|
||||
|
||||
private ApronArucoDetectPort() {}
|
||||
private static class OpenCVHelperHolder {
|
||||
private static final ApronArucoDetectPort INSTANCE = new ApronArucoDetectPort();
|
||||
static { INSTANCE.init(); }
|
||||
}
|
||||
public static ApronArucoDetectPort getInstance() { return OpenCVHelperHolder.INSTANCE; }
|
||||
|
||||
public void init() {
|
||||
// 参数别改:PID初始值
|
||||
pidControlX = new PIDControl(0.7f, 0.02f, 0.22f, 0.05f, 2.5f, 0.1f);
|
||||
pidControlY = new PIDControl(0.7f, 0.02f, 0.22f, 0.05f, 2.5f, 0.1f);
|
||||
pidControlX.reset();
|
||||
pidControlY.reset();
|
||||
}
|
||||
|
||||
public boolean startFastStick;
|
||||
public boolean canLanding;
|
||||
|
||||
// ========== 【新增】计算二维码像素宽度 ==========
|
||||
private double calculatePixelWidth(Mat corner) {
|
||||
try {
|
||||
Point[] pts = new Point[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
pts[i] = new Point(p[0], p[1]);
|
||||
}
|
||||
// 计算上下两边长度的平均值作为像素宽度
|
||||
double top = Math.sqrt(Math.pow(pts[1].x - pts[0].x, 2) + Math.pow(pts[1].y - pts[0].y, 2));
|
||||
double bottom = Math.sqrt(Math.pow(pts[2].x - pts[3].x, 2) + Math.pow(pts[2].y - pts[3].y, 2));
|
||||
return (top + bottom) / 2.0;
|
||||
} catch (Exception e) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
// ========== 【核心】主入口:3帧一处理 ==========
|
||||
public void detectArucoTags(int height, int width, byte[] data, Dictionary dictionary) {
|
||||
isTriggerSuccess = true;
|
||||
Movement.getInstance().setVirtualStickEnableReason(2);
|
||||
|
||||
if (isStartAruco || startFastStick) {
|
||||
LogUtil.log(TAG_LOG, "过滤:" + isStartAruco + startFastStick);
|
||||
if (!isStartAruco && startFastStick) {
|
||||
checkThrowingErrorsTimes++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 改为 3 帧一处理(10Hz,接近 DJI 建议上限)
|
||||
int currentFrame = frameCounter++;
|
||||
if (currentFrame % 3 != 0) { // 0处理,1、2跳过
|
||||
return;
|
||||
}
|
||||
if (currentFrame % 30 != 0) { // 0处理,1、2跳过
|
||||
//DroneHelper.getInstance().setGimbalPitchdown();
|
||||
}
|
||||
|
||||
isStartAruco = true;
|
||||
if (lastFuture != null && !lastFuture.isDone()) {
|
||||
lastFuture.cancel(true);
|
||||
}
|
||||
int ultHeight = Movement.getInstance().getUltrasonicHeight();
|
||||
|
||||
if(ultHeight <=10 && setMF==false){
|
||||
//切换mf对焦模式
|
||||
setMF=true;
|
||||
}
|
||||
if(isDoublePayload){
|
||||
if (ultHeight <=5)
|
||||
{
|
||||
LENS_OFFSET_X=-125;
|
||||
LENS_OFFSET_Y=120;
|
||||
}else if(ultHeight<10){
|
||||
LENS_OFFSET_X=-85;
|
||||
LENS_OFFSET_Y=80;
|
||||
} else if (ultHeight<20) {
|
||||
LENS_OFFSET_X=-70;
|
||||
LENS_OFFSET_Y=60;
|
||||
}
|
||||
}else{
|
||||
if (ultHeight <=5)
|
||||
{
|
||||
LENS_OFFSET_X=120;
|
||||
LENS_OFFSET_Y = 100;
|
||||
}else if(ultHeight<10){
|
||||
LENS_OFFSET_X=80;
|
||||
LENS_OFFSET_Y = 80;
|
||||
} else if (ultHeight<20) {
|
||||
LENS_OFFSET_X=60;
|
||||
LENS_OFFSET_Y = 60;
|
||||
}
|
||||
}
|
||||
|
||||
lastFuture = executor.schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// ========== 图像预处理(原有) ==========
|
||||
Mat yuvMat = new Mat(height + height / 2, width, CvType.CV_8UC1);
|
||||
yuvMat.put(0, 0, data);
|
||||
Mat rgbMat = new Mat();
|
||||
Imgproc.cvtColor(yuvMat, rgbMat, Imgproc.COLOR_YUV2BGR_I420);
|
||||
Mat grayImgMat = new Mat();
|
||||
Imgproc.cvtColor(rgbMat, grayImgMat, Imgproc.COLOR_BGR2GRAY);
|
||||
|
||||
MatOfInt ids = new MatOfInt();
|
||||
List<Mat> corners = new ArrayList<>();
|
||||
DetectorParameters parameters = new DetectorParameters();
|
||||
ArucoDetector detector = new ArucoDetector(dictionary, parameters);
|
||||
|
||||
detector.detectMarkers(grayImgMat, corners, ids);
|
||||
|
||||
int ultHeight=Movement.getInstance().getUltrasonicHeight();
|
||||
|
||||
if (!ids.empty()) {
|
||||
trycount = 0;
|
||||
arucoNotFoundTag = false;
|
||||
|
||||
List<ArucoMarker> smallMarkers = new ArrayList<>();
|
||||
List<ArucoMarker> bigMarkers1_4 = new ArrayList<>();
|
||||
// 【禁用】5号逻辑注释掉,但保留变量避免编译错误
|
||||
// List<ArucoMarker> bigMarker5 = new ArrayList<>();
|
||||
List<ArucoMarker> bigMarker6 = new ArrayList<>();
|
||||
|
||||
int[] idArray = ids.toArray();
|
||||
List<Integer> excludeIds = isDoublePayload ?
|
||||
Arrays.asList(12, 11, 21, 14, 13, 22) :
|
||||
Arrays.asList(12, 11, 21, 20, 19, 25);
|
||||
|
||||
// 【新增】记录所有检测到的码的像素信息
|
||||
StringBuilder detectInfo = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < idArray.length; i++) {
|
||||
int id = idArray[i];
|
||||
Mat corner = corners.get(i);
|
||||
double pixelW = calculatePixelWidth(corner);
|
||||
|
||||
if (id >= 11 && id <= 25 && !excludeIds.contains(id)) {
|
||||
smallMarkers.add(new ArucoMarker(id, corner, 0.03f));
|
||||
detectInfo.append(String.format("小码%d(%.0fpx) ", id, pixelW));
|
||||
} else if (id >= 1 && id <= 4) {
|
||||
bigMarkers1_4.add(new ArucoMarker(id, corner, 0.18f));
|
||||
detectInfo.append(String.format("大码%d(%.0fpx) ", id, pixelW));
|
||||
}
|
||||
// 【禁用】5号二维码逻辑已注释
|
||||
/*
|
||||
else if (id == 5) {
|
||||
bigMarker5.add(new ArucoMarker(5, corner, 0.15f));
|
||||
detectInfo.append(String.format("5号(%.0fpx) ", pixelW));
|
||||
}
|
||||
*/
|
||||
else if (id == 6) {
|
||||
bigMarker6.add(new ArucoMarker(6, corner, 0.24f));
|
||||
detectInfo.append(String.format("6号(%.0fpx) ", pixelW));
|
||||
}
|
||||
}
|
||||
// 输出检测到的所有码及其像素宽度
|
||||
if (detectInfo.length() > 0) {
|
||||
LogUtil.log(TAG_LOG, "【检测到】" + detectInfo.toString());
|
||||
}
|
||||
|
||||
// ========== 【关键修改】阶段0:6号旋转阶段(最高优先级) ==========
|
||||
// 只要看到6号且还没对准,就只用6号旋转,不进入其他任何阶段
|
||||
if (!bigMarker6.isEmpty() && !isYawAligned) {
|
||||
currentLandingMode = 0;
|
||||
processMarker6YawOnly(bigMarker6.get(0), rgbMat.width(), rgbMat.height(), ultHeight);
|
||||
}
|
||||
// ========== 【关键】只有旋转完成后(isYawAligned=true),才识别其他码 ==========
|
||||
else if (isYawAligned) {
|
||||
// 优先级1:小码模式(高度<25dm且≥3个码)
|
||||
if (!smallMarkers.isEmpty() && ultHeight < 25) {
|
||||
currentLandingMode = 1;
|
||||
processSmallMarkers(smallMarkers, rgbMat.width(), rgbMat.height(), ultHeight);
|
||||
}
|
||||
// 优先级2:1-4号几何中心下降
|
||||
else if (!bigMarkers1_4.isEmpty()) {
|
||||
currentLandingMode = 2;
|
||||
processBigMarkersCenter(bigMarkers1_4, rgbMat.width(), rgbMat.height(), ultHeight);
|
||||
}
|
||||
// 优先级3:6号纯下降(旋转完成后,没看到1-4和小码时)
|
||||
else if (!bigMarker6.isEmpty()) {
|
||||
currentLandingMode = 3;
|
||||
processLandingOnly(bigMarker6.get(0), rgbMat.width(), rgbMat.height(), ultHeight);
|
||||
}
|
||||
// 旋转完成后丢失其他码,但还有6号:继续用6号下降
|
||||
else {
|
||||
handleLostMarker(ultHeight);
|
||||
}
|
||||
} else {
|
||||
LogUtil.log(TAG_LOG, "【等待6号】未检测到6号二维码,无法开始旋转对准");
|
||||
handleLostMarker(ultHeight);
|
||||
}
|
||||
dropTimesTag = true;
|
||||
} else {
|
||||
// 丢失处理(包含旋转阶段保护)
|
||||
handleLostMarker(ultHeight);
|
||||
}
|
||||
grayImgMat.release();
|
||||
rgbMat.release();
|
||||
yuvMat.release();
|
||||
ids.release();
|
||||
if (!corners.isEmpty()) {
|
||||
for (Mat c : corners) if (c != null) c.release();
|
||||
}
|
||||
isStartAruco = false;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG_LOG, "Exception: " + e);
|
||||
isStartAruco = false;
|
||||
}
|
||||
}
|
||||
}, 0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
// ========== 【新增】6号专用旋转阶段(纯旋转,不下降) ==========
|
||||
private void processMarker6YawOnly(ArucoMarker marker, int imgWidth, int imgHeight, int ultHeight) {
|
||||
Mat corner = marker.getConner();
|
||||
Point[] pts = new Point[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
pts[i] = new Point(p[0], p[1]);
|
||||
}
|
||||
|
||||
// 使用6号的角点计算yaw误差
|
||||
double yawError = calculateYawErrorFromCorners(pts);
|
||||
double absYaw = Math.abs(yawError);
|
||||
|
||||
// 计算6号的像素宽度用于日志
|
||||
double pixelWidth = calculatePixelWidth(corner);
|
||||
|
||||
if (absYaw < 5.0) {
|
||||
// 旋转到位,设置标志,下一帧将进入正常下降流程
|
||||
isYawAligned = true;
|
||||
LogUtil.log(TAG_LOG, String.format("【6号旋转到位】误差=%.1f° pixel=%.0f 进入下降阶段", yawError, pixelWidth));
|
||||
// 发送悬停指令(停止旋转)
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 0f);
|
||||
} else {
|
||||
// 未对准:计算转速(分段)
|
||||
float yawRate;
|
||||
if (absYaw > 100.0) yawRate = 50.0f;
|
||||
else if (absYaw > 50.0) yawRate = 30.0f;
|
||||
else if (absYaw > 30.0) yawRate = 20.0f;
|
||||
else if (absYaw > 20.0) yawRate = 10.0f;
|
||||
else if (absYaw > 10.0) yawRate = 5.0f;
|
||||
else if (absYaw > 5.0) yawRate = 3.0f;
|
||||
else yawRate = 0.0f;
|
||||
|
||||
// 【注意】如果实际测试还是单向转,把下一行改成:yawRate = yawError > 0 ? -yawRate : yawRate;
|
||||
yawRate = yawError > 0 ? yawRate : -yawRate;
|
||||
|
||||
LogUtil.log(TAG_LOG, String.format("【6号旋转中】误差=%.1f° 转速=%.1f pixel=%.0f ult=%d",
|
||||
yawError, yawRate, pixelWidth, ultHeight));
|
||||
|
||||
// 【关键】只旋转,不水平移动,不下降(vz=0)
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, yawRate, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void processSmallMarkers(List<ArucoMarker> markers, int imgWidth, int imgHeight, int ultHeight) {
|
||||
// 【关键修改】必须检测到3个及以上小码才处理,否则进入丢失保护
|
||||
if (markers == null || markers.size() < 3) {
|
||||
LogUtil.log(TAG_LOG, "【小码数量不足】检测到" + (markers == null ? 0 : markers.size()) + "个,需要≥3个");
|
||||
// 数量不足时,如果有6号就用6号下降,否则悬停
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 计算几何中心和统计信息
|
||||
double sumX = 0, sumY = 0;
|
||||
int validCount = 0;
|
||||
double totalPixelWidth = 0;
|
||||
|
||||
ArucoMarker target15 = null; // 单挂目标
|
||||
ArucoMarker target17 = null; // 双挂目标
|
||||
|
||||
for (ArucoMarker marker : markers) {
|
||||
Mat corner = marker.getConner();
|
||||
totalPixelWidth += calculatePixelWidth(corner);
|
||||
|
||||
double cx = 0, cy = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
cx += p[0];
|
||||
cy += p[1];
|
||||
}
|
||||
cx /= 4.0;
|
||||
cy /= 4.0;
|
||||
sumX += cx;
|
||||
sumY += cy;
|
||||
validCount++;
|
||||
|
||||
int id = marker.getId();
|
||||
if (id == 15) target15 = marker;
|
||||
if (id == 17) target17 = marker;
|
||||
}
|
||||
|
||||
if (validCount == 0) return;
|
||||
|
||||
double avgPixelWidth = totalPixelWidth / validCount;
|
||||
double geoCenterX = sumX / validCount;
|
||||
double geoCenterY = sumY / validCount;
|
||||
double offsetX = geoCenterX - imgWidth / 2.0 - LENS_OFFSET_X;
|
||||
double offsetY = geoCenterY - imgHeight / 2.0 + LENS_OFFSET_Y;
|
||||
|
||||
double absX = Math.abs(offsetX);
|
||||
double absY = Math.abs(offsetY);
|
||||
double z = ultHeight / 10.0;
|
||||
|
||||
// 降落条件判断(连续帧)
|
||||
boolean isSpecialLanding = false;
|
||||
double singleErrX = 0, singleErrY = 0;
|
||||
|
||||
// 单挂模式:检查15号
|
||||
if (!isDoublePayload && target15 != null && ultHeight <= 4) {
|
||||
Mat corner = target15.getConner();
|
||||
double cx = 0, cy = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
cx += p[0];
|
||||
cy += p[1];
|
||||
}
|
||||
cx /= 4.0; cy /= 4.0;
|
||||
singleErrX = cx - imgWidth / 2.0 - LENS_OFFSET_X;
|
||||
singleErrY = cy - imgHeight / 2.0 + LENS_OFFSET_Y;
|
||||
|
||||
if (Math.abs(singleErrX) < 60 && Math.abs(singleErrY) < 60) {
|
||||
isSpecialLanding = true;
|
||||
offsetX = singleErrX;
|
||||
offsetY = singleErrY;
|
||||
absX = Math.abs(singleErrX);
|
||||
absY = Math.abs(singleErrY);
|
||||
}
|
||||
}
|
||||
|
||||
// 双挂模式:检查17号
|
||||
if (isDoublePayload && target17 != null && ultHeight <= 4) {
|
||||
Mat corner = target17.getConner();
|
||||
double cx = 0, cy = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
cx += p[0];
|
||||
cy += p[1];
|
||||
}
|
||||
cx /= 4.0; cy /= 4.0;
|
||||
singleErrX = cx - imgWidth / 2.0 - LENS_OFFSET_X;
|
||||
singleErrY = cy - imgHeight / 2.0 + LENS_OFFSET_Y;
|
||||
|
||||
if (Math.abs(singleErrX) < 60 && Math.abs(singleErrY) < 60) {
|
||||
isSpecialLanding = true;
|
||||
offsetX = singleErrX;
|
||||
offsetY = singleErrY;
|
||||
absX = Math.abs(singleErrX);
|
||||
absY = Math.abs(singleErrY);
|
||||
}
|
||||
}
|
||||
|
||||
// 连续帧判断
|
||||
int currentCondition = 0;
|
||||
if (isSpecialLanding) {
|
||||
currentCondition = 1;
|
||||
} else if (ultHeight <= 4 && absX < 60 && absY < 60 && validCount >= 8) {
|
||||
currentCondition = 2;
|
||||
}
|
||||
|
||||
if (currentCondition == 0 || currentCondition != lastLandingCondition) {
|
||||
if (consecutiveTriggerCount > 0) {
|
||||
consecutiveTriggerCount = 0;
|
||||
LogUtil.log(TAG_LOG, "【计数器清零】条件变化 last=" + lastLandingCondition + " curr=" + currentCondition);
|
||||
}
|
||||
lastLandingCondition = currentCondition;
|
||||
}
|
||||
|
||||
if (currentCondition > 0) {
|
||||
consecutiveTriggerCount++;
|
||||
if (consecutiveTriggerCount >= TRIGGER_FRAME_THRESHOLD) {
|
||||
startFastStick = true;
|
||||
consecutiveTriggerCount = 0;
|
||||
lastLandingCondition = 0;
|
||||
|
||||
LogUtil.log(TAG_LOG, currentCondition == 1 ?
|
||||
"【降落触发】单双挂精准条件满足" :
|
||||
"【降落触发】几何中心对准,码数量=" + validCount);
|
||||
|
||||
handler.postDelayed(() -> handler.post(runnable), 300);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double outX = 0, outY = 0, outZ = 0;
|
||||
|
||||
// 分段PID控制(原有逻辑保持不变)
|
||||
if(z <= 0.4){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1750);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1750);
|
||||
if (pidControlX.get_pid()<0){
|
||||
if (pidControlX.get_pid()<-0.125){
|
||||
outX=absX<120?0:-0.125;
|
||||
}else{
|
||||
outX=absX<120?0:pidControlX.get_pid();
|
||||
}
|
||||
}else{
|
||||
if (pidControlX.get_pid()>0.125){
|
||||
outX=absX<120?0:0.125;
|
||||
}else{
|
||||
outX=absX<120?0:pidControlX.get_pid();
|
||||
}
|
||||
}
|
||||
if (pidControlY.get_pid()<0){
|
||||
if (pidControlY.get_pid()<-0.125){
|
||||
outY=absY<120?0:-0.125;
|
||||
}else{
|
||||
outY=absY<120?0:pidControlY.get_pid();
|
||||
}
|
||||
}else{
|
||||
if (pidControlY.get_pid()>0.125){
|
||||
outY=absY<120?0:0.125;
|
||||
}else{
|
||||
outY=absY<120?0:pidControlY.get_pid();
|
||||
}
|
||||
}
|
||||
}else if(z <= 0.7){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1200);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1200);
|
||||
double pidX = pidControlX.get_pid();
|
||||
double pidY = pidControlY.get_pid();
|
||||
|
||||
if (pidX < 0){
|
||||
outX = (pidX < -0.135) ? (absX < 120 ? 0 : -0.135) : (absX < 120 ? 0 : pidX);
|
||||
}else{
|
||||
outX = (pidX > 0.135) ? (absX < 120 ? 0 : 0.135) : (absX < 120 ? 0 : pidX);
|
||||
}
|
||||
|
||||
if (pidY < 0){
|
||||
outY = (pidY < -0.135) ? (absY < 120 ? 0 : -0.135) : (absY < 120 ? 0 : pidY);
|
||||
}else{
|
||||
outY = (pidY > 0.135) ? (absY < 120 ? 0 : 0.135) : (absY < 120 ? 0 : pidY);
|
||||
}
|
||||
outZ = (absX < 250 && absY < 250) ? -0.1 : 0;
|
||||
|
||||
}else if(z <= 1.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1200);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1200);
|
||||
double pidX = pidControlX.get_pid();
|
||||
double pidY = pidControlY.get_pid();
|
||||
|
||||
if (pidX < 0){
|
||||
outX = (pidX < -0.145) ? (absX < 120 ? 0 : -0.145) : (absX < 120 ? 0 : pidX);
|
||||
}else{
|
||||
outX = (pidX > 0.145) ? (absX < 120 ? 0 : 0.145) : (absX < 120 ? 0 : pidX);
|
||||
}
|
||||
|
||||
if (pidY < 0){
|
||||
outY = (pidY < -0.175) ? (absY < 120 ? 0 : -0.175) : (absY < 120 ? 0 : pidY);
|
||||
}else{
|
||||
outY = (pidY > 0.175) ? (absY < 120 ? 0 : 0.175) : (absY < 120 ? 0 : pidY);
|
||||
}
|
||||
outZ = (absX < 200 && absY < 200) ? -0.2 : 0;
|
||||
|
||||
}else if(z <= 1.5){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1450);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1450);
|
||||
double pidX = pidControlX.get_pid();
|
||||
double pidY = pidControlY.get_pid();
|
||||
|
||||
if (pidX < 0){
|
||||
outX = (pidX < -0.185) ? (absX < 120 ? 0 : -0.185) : (absX < 120 ? 0 : pidX);
|
||||
}else{
|
||||
outX = (pidX > 0.185) ? (absX < 120 ? 0 : 0.185) : (absX < 120 ? 0 : pidX);
|
||||
}
|
||||
if (pidY < 0){
|
||||
outY = (pidY < -0.185) ? (absY < 120 ? 0 : -0.185) : (absY < 120 ? 0 : pidY);
|
||||
}else{
|
||||
outY = (pidY > 0.185) ? (absY < 120 ? 0 : 0.185) : (absY < 120 ? 0 : pidY);
|
||||
}
|
||||
outZ = (absX < 180 && absY < 180) ? -0.4 : 0;
|
||||
|
||||
}else if(z <= 2.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1350);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1350);
|
||||
outX = absX < 120 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 120 ? 0 : pidControlY.get_pid();
|
||||
outZ = (absX < 200 && absY < 200) ? -0.4 : 0;
|
||||
|
||||
}else if(z <= 3.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1250);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1250);
|
||||
outX = absX < 120 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 120 ? 0 : pidControlY.get_pid();
|
||||
outZ = (absX < 200 && absY < 200) ? -0.4 : 0;
|
||||
|
||||
}else {
|
||||
pidControlX.setInputFilterAll((float)offsetX/850);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/850);
|
||||
outX = absX < 80 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 80 ? 0 : pidControlY.get_pid();
|
||||
outZ = (absX < 130 && absY < 130) ? -0.4 : 0;
|
||||
}
|
||||
|
||||
LogUtil.log(TAG_LOG, String.format("【执行移动】小码 vx=%.3f vy=%.3f vz=%.3f pixel=%.0f ult=%d errX=%.0f errY=%.0f",
|
||||
outX, outY, outZ, avgPixelWidth, ultHeight, absX, absY));
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight((float)outX, (float)outY, 0, (float)outZ);
|
||||
}
|
||||
|
||||
// ========== 【修改】1-4号几何中心处理(旋转完成后才进入) ==========
|
||||
private void processBigMarkersCenter(List<ArucoMarker> markers, int imgWidth, int imgHeight, int ultHeight) {
|
||||
double sumX = 0, sumY = 0;
|
||||
StringBuilder markerInfo = new StringBuilder();
|
||||
|
||||
for (ArucoMarker marker : markers) {
|
||||
Mat corner = marker.getConner();
|
||||
double cx = 0, cy = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
cx += p[0];
|
||||
cy += p[1];
|
||||
}
|
||||
sumX += cx / 4.0;
|
||||
sumY += cy / 4.0;
|
||||
|
||||
double pixelW = calculatePixelWidth(corner);
|
||||
markerInfo.append(String.format("[ID%d:%.0fpx]", marker.getId(), pixelW));
|
||||
}
|
||||
|
||||
double geoCenterX = sumX / markers.size();
|
||||
double geoCenterY = sumY / markers.size();
|
||||
double offsetX = geoCenterX - imgWidth / 2.0;
|
||||
double offsetY = geoCenterY - imgHeight / 2.0;
|
||||
double absX = Math.abs(offsetX);
|
||||
double absY = Math.abs(offsetY);
|
||||
|
||||
double z = ultHeight / 10.0;
|
||||
double outX = 0, outY = 0, outZ = 0;
|
||||
|
||||
if (z >= 5.0) {
|
||||
pidControlX.setInputFilterAll((float)offsetX/300);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/300);
|
||||
outX = absX < 50 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 50 ? 0 : pidControlY.get_pid();
|
||||
outX = Math.max(-0.20, Math.min(0.20, outX));
|
||||
outY = Math.max(-0.20, Math.min(0.20, outY));
|
||||
outZ = (absX < 100 && absY < 100) ? -0.4 : 0;
|
||||
}else if(z>=4.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/600);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/600);
|
||||
outX = absX < 50 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 50 ? 0 : pidControlY.get_pid();
|
||||
outX = Math.max(-0.20, Math.min(0.20, outX));
|
||||
outY = Math.max(-0.20, Math.min(0.20, outY));
|
||||
outZ = (absX < 120 && absY < 120) ? -0.4 : 0;
|
||||
}else if(z>=2.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/900);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/900);
|
||||
outX = absX < 50 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 50 ? 0 : pidControlY.get_pid();
|
||||
outX = Math.max(-0.20, Math.min(0.20, outX));
|
||||
outY = Math.max(-0.20, Math.min(0.20, outY));
|
||||
outZ = (absX < 140 && absY < 140) ? -0.4 : 0;
|
||||
}else if(z>=1.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1000);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1000);
|
||||
outX = absX < 50 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 50 ? 0 : pidControlY.get_pid();
|
||||
outX = Math.max(-0.20, Math.min(0.20, outX));
|
||||
outY = Math.max(-0.20, Math.min(0.20, outY));
|
||||
outZ = (absX < 150 && absY < 150) ? -0.1 : 0;
|
||||
}else if(z>=0.5){
|
||||
outZ = (absX < 150 && absY < 150) ? -0.1 : 0;
|
||||
}
|
||||
|
||||
LogUtil.log(TAG_LOG, String.format("【执行移动】大码几何中心 vx=%.3f vy=%.3f vz=%.3f ult=%d %s errX=%.0f errY=%.0f",
|
||||
outX, outY, outZ, ultHeight, markerInfo.toString(), absX, absY));
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight((float)outX, (float)outY, 0f, (float)outZ);
|
||||
}
|
||||
|
||||
// ========== 【修改】6号纯下降模式(旋转完成后使用) ==========
|
||||
private void processLandingOnly(ArucoMarker marker, int imgWidth, int imgHeight, int ultHeight) {
|
||||
Mat corner = marker.getConner();
|
||||
Point[] pts = new Point[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
pts[i] = new Point(p[0], p[1]);
|
||||
}
|
||||
|
||||
double pixelWidth = calculatePixelWidth(corner);
|
||||
double cx = 0, cy = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
cx += pts[i].x;
|
||||
cy += pts[i].y;
|
||||
}
|
||||
cx /= 4.0;
|
||||
cy /= 4.0;
|
||||
double offsetX = cx - imgWidth / 2.0;
|
||||
double offsetY = cy - imgHeight / 2.0;
|
||||
|
||||
float vz;
|
||||
|
||||
if (ultHeight >= 50) {
|
||||
vz = -0.7f;
|
||||
} else if(ultHeight<=20){
|
||||
vz = 0.0f;
|
||||
}else{
|
||||
vz=-0.3f;
|
||||
}
|
||||
|
||||
LogUtil.log(TAG_LOG, String.format("【执行移动】6号纯下降 vz=%.3f pixel=%.0f ult=%d errX=%.0f errY=%.0f",
|
||||
vz, pixelWidth, ultHeight, Math.abs(offsetX), Math.abs(offsetY)));
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, vz);
|
||||
}
|
||||
|
||||
private double calculateYawErrorFromCorners(Point[] pts) {
|
||||
double dxTop = pts[1].x - pts[0].x;
|
||||
double dyTop = pts[1].y - pts[0].y;
|
||||
double angleTop = Math.toDegrees(Math.atan2(dyTop, dxTop));
|
||||
double dxBottom = pts[2].x - pts[3].x;
|
||||
double dyBottom = pts[2].y - pts[3].y;
|
||||
double angleBottom = Math.toDegrees(Math.atan2(dyBottom, dxBottom));
|
||||
double yawError = (angleTop + angleBottom) / 2.0;
|
||||
while (yawError > 180) yawError -= 360;
|
||||
while (yawError < -180) yawError += 360;
|
||||
return yawError;
|
||||
}
|
||||
|
||||
// ========== 【修改】handleLostMarker 增加旋转阶段保护 ==========
|
||||
private void handleLostMarker(int ultHeight) {
|
||||
if (!arucoNotFoundTag) {
|
||||
startTime = System.currentTimeMillis();
|
||||
arucoNotFoundTag = true;
|
||||
}
|
||||
endTime = System.currentTimeMillis();
|
||||
long lostDuration = endTime - startTime;
|
||||
|
||||
// 【关键新增】旋转阶段保护:还没对准(isYawAligned=false)时,悬停等待,不下降
|
||||
if (!isYawAligned) {
|
||||
LogUtil.log(TAG_LOG, "【旋转阶段丢失】未对准6号,悬停等待 vz=0 lostDuration=" + lostDuration);
|
||||
// 悬停(不上升也不下降)
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 0f);
|
||||
|
||||
// 10秒还没看到6号,去备降点
|
||||
if (lostDuration > 10000) {
|
||||
LogUtil.log(TAG_LOG, "【旋转超时】10秒未检测到6号,飞往备降点");
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
Movement.getInstance().setAlternate(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(isYawAligned){
|
||||
// 原有逻辑(旋转完成后的丢失处理)
|
||||
if (lostDuration > 1000 && lostDuration <= 12000) {
|
||||
if (ultHeight <= 20) {
|
||||
// 低空丢失:上升
|
||||
LogUtil.log(TAG_LOG, "【执行移动】丢失上升 vz=3.0");
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f);
|
||||
if (dropTimes > Integer.parseInt(AMSConfig.getInstance().getAlternateLandingTimes())) {
|
||||
LogUtil.log(TAG_LOG, "超过复降限制,去备降点");
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
Movement.getInstance().setAlternate(true);
|
||||
return;
|
||||
}
|
||||
if (dropTimesTag) {
|
||||
dropTimesTag = false;
|
||||
dropTimes++;
|
||||
LogUtil.log(TAG_LOG, "复降第:" + dropTimes + "次");
|
||||
}
|
||||
} else {
|
||||
// 高空丢失:下降寻找
|
||||
LogUtil.log(TAG_LOG, "【执行移动】丢失下降 vz=-0.3");
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f);
|
||||
}
|
||||
} else if (lostDuration > 8000) {
|
||||
LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
Movement.getInstance().setAlternate(true);
|
||||
}
|
||||
}
|
||||
// if(lostDuration > 8000) {
|
||||
// LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
||||
// AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
// Movement.getInstance().setAlternate(true);
|
||||
// }
|
||||
}
|
||||
|
||||
private double calculateDistance(Point p1, Point p2) {
|
||||
double dx = p2.x - p1.x;
|
||||
double dy = p2.y - p1.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
public void setDetectedBigMarkers() {
|
||||
startFastStick = false;
|
||||
isStartAruco = false;
|
||||
consecutiveTriggerCount = 0;
|
||||
frameCounter = 0;
|
||||
// 【关键】重置旋转标志,下次降落重新用6号旋转
|
||||
isYawAligned = false;
|
||||
currentLandingMode = 0;
|
||||
lastLandingCondition = 0;
|
||||
}
|
||||
|
||||
private int handlerCallbackCount = 0;
|
||||
private Handler handler = new Handler(Looper.getMainLooper());
|
||||
private Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performOperation();
|
||||
if (handlerCallbackCount < 20) {
|
||||
handler.postDelayed(this, 50);
|
||||
} else {
|
||||
performNextStep();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void performOperation() {
|
||||
LogUtil.log(TAG_LOG, String.format("【执行移动】速降 vz=-4 count=%d/20", handlerCallbackCount));
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -5);
|
||||
handlerCallbackCount++;
|
||||
}
|
||||
|
||||
private void performNextStep() {
|
||||
canLanding = true;
|
||||
handlerCallbackCount = 0;
|
||||
dropTimes = 0;
|
||||
handler.removeCallbacks(runnable);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package com.aros.apron.tools;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* ArUco 双路相机状态管理
|
||||
* volatile 保证内存可见性,防止双路同时工作
|
||||
*/
|
||||
public class ApronArucoStatus {
|
||||
|
||||
private static final ApronArucoStatus INSTANCE = new ApronArucoStatus();
|
||||
|
||||
/**
|
||||
* 当前激活的相机源
|
||||
* volatile 保证多线程可见性
|
||||
*/
|
||||
private volatile CameraSource activeSource = CameraSource.DOWNWARD;
|
||||
|
||||
/**
|
||||
* 复降次数 - AtomicInteger 保证原子性
|
||||
*/
|
||||
private final AtomicInteger tryTimes = new AtomicInteger(0);
|
||||
|
||||
public enum CameraSource {
|
||||
DOWNWARD, // 下视觉 FPV
|
||||
GIMBAL // 云台 PORT_1
|
||||
}
|
||||
|
||||
private ApronArucoStatus() {}
|
||||
|
||||
public static ApronArucoStatus getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否允许处理(核心互斥)
|
||||
*/
|
||||
public boolean canProcess(CameraSource source) {
|
||||
return activeSource == source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换源(立即生效,内存可见)
|
||||
*/
|
||||
public void switchTo(CameraSource source) {
|
||||
this.activeSource = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前激活源
|
||||
*/
|
||||
public CameraSource getActiveSource() {
|
||||
return activeSource;
|
||||
}
|
||||
|
||||
// ========== 复降次数(线程安全) ==========
|
||||
|
||||
public int getTryTimes() {
|
||||
return tryTimes.get();
|
||||
}
|
||||
|
||||
public void setTryTimes(int value) {
|
||||
this.tryTimes.set(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 原子性 +1
|
||||
*/
|
||||
public void incrementTryTimes() {
|
||||
tryTimes.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 原子性 -1
|
||||
*/
|
||||
public void decrementTryTimes() {
|
||||
tryTimes.decrementAndGet();
|
||||
}
|
||||
|
||||
public void resetTryTimes() {
|
||||
tryTimes.set(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
package com.aros.apron.tools;
|
||||
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfInt;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.aros.apron.constant.AMSConfig;
|
||||
import com.aros.apron.entity.ArucoMarker;
|
||||
import com.aros.apron.entity.Movement;
|
||||
import com.aros.apron.manager.AlternateLandingManager;
|
||||
|
||||
import org.opencv.calib3d.Calib3d;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfInt;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.opencv.objdetect.ArucoDetector;
|
||||
import org.opencv.objdetect.DetectorParameters;
|
||||
import org.opencv.objdetect.Dictionary;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 双图保存工具 - 单例版
|
||||
* 持续接收帧,按钮触发保存
|
||||
* 替换为【极致容错】图像增强逻辑,保留增强后图像的保存
|
||||
*/
|
||||
public class DualCaptureHelper {
|
||||
|
||||
private static final String TAG = "DualCapture";
|
||||
private static final String TAG_LOG = "EnhanceError"; // 增强失败日志TAG
|
||||
private static DualCaptureHelper instance;
|
||||
|
||||
private int count = 0;
|
||||
private volatile boolean shouldCapture = false; // 按钮触发标志
|
||||
private Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
// 帧数据缓存(最新一帧)
|
||||
private int lastHeight = 0;
|
||||
private int lastWidth = 0;
|
||||
private byte[] lastFrameData = null;
|
||||
|
||||
private DualCaptureHelper() {}
|
||||
|
||||
public static synchronized DualCaptureHelper getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new DualCaptureHelper();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 持续接收帧(在帧回调里调用,每次都有)
|
||||
* 参数别改:和你 detectArucoTags 一致
|
||||
*/
|
||||
public void onFrame(int height, int width, byte[] yuvData) {
|
||||
// 缓存最新帧
|
||||
this.lastHeight = height;
|
||||
this.lastWidth = width;
|
||||
this.lastFrameData = yuvData;
|
||||
|
||||
// 如果按钮触发了,保存这一帧
|
||||
if (shouldCapture && lastFrameData != null) {
|
||||
shouldCapture = false; // 重置,只保存一次
|
||||
|
||||
final int num = ++count;
|
||||
final String time = new SimpleDateFormat("HHmmss", Locale.CHINA).format(new Date());
|
||||
|
||||
// 子线程保存
|
||||
new Thread(() -> {
|
||||
saveRaw(lastHeight, lastWidth, lastFrameData, time, num);
|
||||
saveEnhanced(lastHeight, lastWidth, lastFrameData, time, num);
|
||||
|
||||
mainHandler.post(() -> {
|
||||
LogUtil.log(TAG, "【完成】第" + num + "组");
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按钮调用:触发下一帧保存
|
||||
* 绑定到你的按钮点击
|
||||
*/
|
||||
public void captureNextFrame() {
|
||||
shouldCapture = true;
|
||||
LogUtil.log(TAG, "【等待】下一帧保存...");
|
||||
}
|
||||
|
||||
/**
|
||||
* 原始图:YUV直接转BGR,无任何处理
|
||||
*/
|
||||
private void saveRaw(int height, int width, byte[] yuvData, String time, int num) {
|
||||
try {
|
||||
Mat yuv = new Mat(height + height / 2, width, CvType.CV_8UC1);
|
||||
yuv.put(0, 0, yuvData);
|
||||
|
||||
Mat bgr = new Mat();
|
||||
Imgproc.cvtColor(yuv, bgr, Imgproc.COLOR_YUV2BGR_I420);
|
||||
yuv.release();
|
||||
|
||||
File dir = getSaveDir();
|
||||
String name = String.format("raw_%s_%03d.jpg", time, num);
|
||||
String path = new File(dir, name).getAbsolutePath();
|
||||
|
||||
Imgcodecs.imwrite(path, bgr, new MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, 95));
|
||||
bgr.release();
|
||||
|
||||
LogUtil.log(TAG, "【原始】" + path);
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG, "【原始失败】" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 增强图:应用【极致容错】图像增强流程
|
||||
*/
|
||||
private void saveEnhanced(int height, int width, byte[] yuvData, String time, int num) {
|
||||
try {
|
||||
Mat yuv = new Mat(height + height / 2, width, CvType.CV_8UC1);
|
||||
yuv.put(0, 0, yuvData);
|
||||
|
||||
Mat gray = new Mat();
|
||||
Imgproc.cvtColor(yuv, gray, Imgproc.COLOR_YUV2GRAY_I420);
|
||||
yuv.release();
|
||||
|
||||
// 替换为极致容错的增强逻辑
|
||||
Mat enhanced = createEnhancedImage(gray);
|
||||
gray.release();
|
||||
|
||||
Mat bgr = new Mat();
|
||||
Imgproc.cvtColor(enhanced, bgr, Imgproc.COLOR_GRAY2BGR);
|
||||
enhanced.release();
|
||||
|
||||
File dir = getSaveDir();
|
||||
String name = String.format("enhanced_%s_%03d.jpg", time, num);
|
||||
String path = new File(dir, name).getAbsolutePath();
|
||||
|
||||
Imgcodecs.imwrite(path, bgr, new MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, 95));
|
||||
bgr.release();
|
||||
|
||||
LogUtil.log(TAG, "【增强】" + path);
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG, "【增强失败】" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 【极致容错】图像增强:全高度段通用 ==========
|
||||
private Mat createEnhancedImage(Mat src) {
|
||||
Mat result = new Mat();
|
||||
try {
|
||||
// 1. 多尺度CLAHE(适应不同亮度)
|
||||
Mat claheMat = new Mat();
|
||||
Imgproc.createCLAHE(2.0, new Size(8, 8)).apply(src, claheMat);
|
||||
|
||||
// 2. 中值滤波去噪(比双边快,保边足够)
|
||||
Mat filtered = new Mat();
|
||||
Imgproc.medianBlur(claheMat, filtered, 5);
|
||||
claheMat.release();
|
||||
|
||||
// 3. 自适应阈值(块大小动态,适应全高度)
|
||||
Mat binary = new Mat();
|
||||
Imgproc.adaptiveThreshold(filtered, binary, 255,
|
||||
Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
Imgproc.THRESH_BINARY, 41, 3); // 块41更大,常数3更敏感
|
||||
filtered.release();
|
||||
|
||||
// 4. 形态学操作(连接断裂边框)
|
||||
Mat morph = new Mat();
|
||||
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));
|
||||
Imgproc.morphologyEx(binary, morph, Imgproc.MORPH_CLOSE, kernel);
|
||||
kernel.release();
|
||||
binary.release();
|
||||
|
||||
// 5. 轻度锐化(突出边缘,不过度)
|
||||
Mat sharpened = new Mat();
|
||||
Mat blurred = new Mat();
|
||||
Imgproc.GaussianBlur(morph, blurred, new Size(0, 0), 3.0);
|
||||
Core.addWeighted(morph, 1.3, blurred, -0.3, 0, sharpened);
|
||||
morph.release();
|
||||
blurred.release();
|
||||
|
||||
return sharpened;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG_LOG, "增强失败: " + e.getMessage());
|
||||
src.copyTo(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 【超高容错】ArUco检测参数配置(保留方法,按需调用) ==========
|
||||
private DetectorParameters createUltraTolerantParams() {
|
||||
DetectorParameters params = new DetectorParameters();
|
||||
|
||||
// 全高度段:4dm=375px, 9dm=190px, 50dm=19px
|
||||
params.set_minMarkerPerimeterRate(0.003f); // 降到0.003,19像素也能检
|
||||
|
||||
// 畸变/反光/模糊宽容
|
||||
params.set_polygonalApproxAccuracyRate(0.12f); // 更松
|
||||
|
||||
params.set_cornerRefinementMethod(1); // SUBPIX
|
||||
params.set_cornerRefinementWinSize(3); // 降到3更快
|
||||
params.set_cornerRefinementMaxIterations(30);
|
||||
params.set_cornerRefinementMinAccuracy(0.12f); // 放宽收敛
|
||||
|
||||
// 阈值范围更大,适应全光照
|
||||
params.set_adaptiveThreshWinSizeMin(3);
|
||||
params.set_adaptiveThreshWinSizeMax(63); // 更大
|
||||
params.set_adaptiveThreshWinSizeStep(10);
|
||||
params.set_adaptiveThreshConstant(3); // 配合预处理
|
||||
|
||||
params.set_minCornerDistanceRate(0.02f);
|
||||
params.set_minMarkerLengthRatioOriginalImg(0.03f); // 更宽容
|
||||
params.set_minDistanceToBorder(1); // 边缘也检
|
||||
|
||||
params.set_perspectiveRemovePixelPerCell(2); // 降到2,小像素精细
|
||||
params.set_perspectiveRemoveIgnoredMarginPerCell(0.2f);
|
||||
|
||||
params.set_maxErroneousBitsInBorderRate(0.6f); // 60%容错
|
||||
|
||||
params.set_detectInvertedMarker(false);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
private File getSaveDir() {
|
||||
File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
|
||||
"DJIDemo/DualCapture");
|
||||
if (!dir.exists()) dir.mkdirs();
|
||||
return dir;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
count = 0;
|
||||
LogUtil.log(TAG, "【重置】");
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
package com.aros.apron.tools;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.aros.apron.base.BaseManager;
|
||||
import com.aros.apron.entity.MissionDataBean;
|
||||
import com.aros.apron.entity.Movement;
|
||||
import com.aros.apron.manager.TakeOffToPointManager;
|
||||
import com.aros.apron.manager.TaskFailManager;
|
||||
import com.dji.wpmzsdk.common.data.Template;
|
||||
import com.dji.wpmzsdk.common.utils.kml.GeoidManager;
|
||||
import com.dji.wpmzsdk.common.utils.kml.GpsUtils;
|
||||
import com.dji.wpmzsdk.manager.WPMZManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import dji.sdk.keyvalue.key.FlightControllerKey;
|
||||
import dji.sdk.keyvalue.key.KeyTools;
|
||||
import dji.sdk.keyvalue.value.common.LocationCoordinate3D;
|
||||
import dji.sdk.wpmz.value.mission.*;
|
||||
import dji.v5.manager.KeyManager;
|
||||
|
||||
/**
|
||||
* 单航点起飞→目标点→返航
|
||||
* 纯 Java、无 Builder、无 WaypointHeadingParam
|
||||
*/
|
||||
public class Generakmzaltheratools extends BaseManager {
|
||||
private Generakmzaltheratools() {}
|
||||
private static class Holder { private static final Generakmzaltheratools INSTANCE = new Generakmzaltheratools(); }
|
||||
public static Generakmzaltheratools getInstance() { return Holder.INSTANCE; }
|
||||
|
||||
public Boolean generateKmz() {
|
||||
LogUtil.log(TAG, "备降点经纬度:" + PreferenceUtils.getInstance().getAlternatePointLat() +
|
||||
"/" + PreferenceUtils.getInstance().getAlternatePointLon());
|
||||
LogUtil.log("qwq","开始生成备降点航线");
|
||||
sendEvent2Server("开始生成备降点航线", 1);
|
||||
LocationCoordinate3D lat = KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey.
|
||||
KeyAircraftLocation3D));
|
||||
|
||||
int retryCount = 0;
|
||||
final int MAX_RETRY = 60;
|
||||
|
||||
while (retryCount < MAX_RETRY) {
|
||||
lat = KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey.KeyAircraftLocation3D));
|
||||
if (lat != null) {
|
||||
break;
|
||||
}
|
||||
retryCount++;
|
||||
sendEvent2Server("生成失败:未获取到飞机当前位置(重试" + retryCount + "次)", 1);
|
||||
if (retryCount >= MAX_RETRY) {
|
||||
sendEvent2Server("生成失败:未获取到飞机当前位置(重试" + MAX_RETRY + "次超时)", 1);
|
||||
TaskFailManager.getInstance().sendTaskFailMsg2Server(-1);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1000); // 休眠1秒
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LogUtil.log("qwq", "获取位置线程被中断");
|
||||
sendEvent2Server("生成失败:获取位置被中断", 1);
|
||||
TaskFailManager.getInstance().sendTaskFailMsg2Server(-1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (lat == null) {
|
||||
LogUtil.log("qwq", "当前位置为空,无法生成航线");
|
||||
sendEvent2Server("生成失败:未获取到飞机当前位置", 1);
|
||||
return false;
|
||||
}
|
||||
if (lat.getLatitude() == null || lat.getLongitude() == null|| lat.getAltitude() == null) {
|
||||
LogUtil.log("qwq", "当前位置坐标无效");
|
||||
sendEvent2Server("生成失败:位置坐标无效", 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
File root = new File("/storage/self/primary/DJIDemo/cache/kmz");
|
||||
if (!root.exists()) root.mkdirs();
|
||||
File kmz = new File(root, "alternate.kmz");
|
||||
LogUtil.log(TAG,"高度:"+lat.getAltitude());
|
||||
|
||||
/* 1. <wpml:mission> 基本信息 */
|
||||
WaylineMission mission = new WaylineMission();
|
||||
mission.setAuthor("aros");
|
||||
double now = System.currentTimeMillis();
|
||||
mission.setCreateTime(now);
|
||||
mission.setUpdateTime(now);
|
||||
|
||||
/* 2. <wpml:missionConfig> 全局策略 */
|
||||
WaylineMissionConfig config = new WaylineMissionConfig();
|
||||
//飞向目标点的模式
|
||||
config.setFlyToWaylineMode(WaylineFlyToWaylineMode.SAFELY);
|
||||
//航线结束后的动作
|
||||
config.setFinishAction(WaylineFinishedAction.AUTO_LAND);
|
||||
config.setExitOnRCLostBehavior(WaylineExitOnRCLostBehavior.GO_ON);
|
||||
config.setExitOnRCLostType(WaylineExitOnRCLostAction.LANDING);
|
||||
|
||||
//全局过度速度
|
||||
config.setGlobalTransitionalSpeed(7.0);
|
||||
//全局返航高度
|
||||
config.setGlobalRTHHeight(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight()));
|
||||
config.setIsGlobalRTHHeightSet(true);
|
||||
//安全起飞高度
|
||||
config.setSecurityTakeOffHeight(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight()));
|
||||
config.setIsSecurityTakeOffHeightSet(true);
|
||||
|
||||
|
||||
|
||||
//为400准备的
|
||||
WaylineDroneInfo droneInfo = new WaylineDroneInfo();
|
||||
droneInfo.setDroneType(WaylineDroneType.PM440);
|
||||
config.setDroneInfo(droneInfo);
|
||||
|
||||
|
||||
//这个航点列表
|
||||
WaylineWaypoint wp1 = new WaylineWaypoint();
|
||||
WaylineWaypoint wp = new WaylineWaypoint();
|
||||
|
||||
//初始点
|
||||
double wp1heighet= GpsUtils.egm96Altitude(lat.getAltitude(),lat.getLatitude(),lat.getLongitude());
|
||||
//double wpheight=GpsUtils.egm96Altitude(data.getTargetHeight(),data.getTargetLatitude(),data.getTargetLongitude());
|
||||
|
||||
//double wp1ellheighet=GpsUtils.wgs84Altitude();
|
||||
double wpellheight=GpsUtils.wgs84Altitude(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight()),Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLat()),Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLon()));
|
||||
|
||||
wp1.setWaypointIndex(0);
|
||||
wp1.setLocation(new WaylineLocationCoordinate2D(lat.getLatitude(), lat.getLongitude()));
|
||||
//wp1.setLocation(new WaylineLocationCoordinate2D(data.getTargetLatitude(), data.getTargetLongitude()));
|
||||
//wp1.setHeight((double)data.getCommanderFlightHeight());
|
||||
wp1.setHeight(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight()));
|
||||
//wp.setUseGlobalFlightHeight(true);
|
||||
wp1.setEllipsoidHeight(wpellheight);
|
||||
|
||||
wp1.setUseStraightLine(true);
|
||||
wp1.setUseGlobalTurnParam(true);
|
||||
wp1.setUseGlobalYawParam(true);
|
||||
wp1.setUseGlobalGimbalHeadingParam(true);
|
||||
wp1.setUseGlobalAutoFlightSpeed(true);
|
||||
wp1.setIsRisky(false);
|
||||
|
||||
|
||||
|
||||
//基础设置
|
||||
wp.setWaypointIndex(1);
|
||||
wp.setLocation(new WaylineLocationCoordinate2D(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLat()), Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointLon())));
|
||||
//wp.setHeight((double)data.getCommanderFlightHeight());
|
||||
wp.setHeight(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight()));
|
||||
//wp.setUseGlobalFlightHeight(true);
|
||||
//椭球高
|
||||
wp.setEllipsoidHeight(wpellheight);
|
||||
|
||||
|
||||
|
||||
wp.setUseStraightLine(true);
|
||||
wp.setUseGlobalTurnParam(true);
|
||||
wp.setUseGlobalYawParam(true);
|
||||
wp.setUseGlobalGimbalHeadingParam(true);
|
||||
wp.setUseGlobalAutoFlightSpeed(true);
|
||||
wp.setIsRisky(false);
|
||||
|
||||
|
||||
List<WaylineWaypoint> wpList = new ArrayList<>();
|
||||
wpList.add(wp1);
|
||||
wpList.add(wp);
|
||||
|
||||
/* 4. <wpml:waypointInfo> */
|
||||
WaylineTemplateWaypointInfo waypointInfo = new WaylineTemplateWaypointInfo();
|
||||
|
||||
waypointInfo.setGlobalFlightHeight(Double.parseDouble(PreferenceUtils.getInstance().getAlternatePointSecurityHeight()));
|
||||
waypointInfo.setIsGlobalFlightHeightSet(true);
|
||||
|
||||
WaylineWaypointYawParam waypointYawParam=new WaylineWaypointYawParam();
|
||||
waypointYawParam.setYawMode(WaylineWaypointYawMode.FOLLOW_WAYLINE);
|
||||
waypointInfo.setGlobalYawParam(waypointYawParam);
|
||||
waypointInfo.setIsTemplateGlobalYawParamSet(true);
|
||||
|
||||
WaylineWaypointGimbalHeadingParam waypointGimbalHeadingParam=new WaylineWaypointGimbalHeadingParam();
|
||||
waypointGimbalHeadingParam.setHeadingMode(WaylineWaypointGimbalHeadingMode.FOLLOW_WAYLINE);
|
||||
waypointGimbalHeadingParam.setYawAngle(0.0);
|
||||
waypointGimbalHeadingParam.setPitchAngle(0.0);
|
||||
waypointInfo.setGlobalGimbalHeadingParam(waypointGimbalHeadingParam);
|
||||
|
||||
waypointInfo.setGlobalTurnMode(WaylineWaypointTurnMode.COORDINATE_TURN);
|
||||
waypointInfo.setIsTemplateGlobalTurnModeSet(true);
|
||||
waypointInfo.setWaypoints(wpList);
|
||||
|
||||
waypointInfo.setUseStraightLine(true);
|
||||
|
||||
|
||||
|
||||
/* 5. <wpml:coordinateParam> */
|
||||
WaylineCoordinateParam coordParam = new WaylineCoordinateParam();
|
||||
coordParam.setCoordinateMode(WaylineCoordinateMode.WGS84);
|
||||
|
||||
coordParam.setAltitudeMode(WaylineAltitudeMode.EGM96);
|
||||
coordParam.setPositioningType(WaylinePositioningType.GPS);
|
||||
|
||||
/* 6. <wpml:template> */
|
||||
Template template = new Template();
|
||||
template.setTemplateId(0);
|
||||
template.setCoordinateParam(coordParam);
|
||||
template.setTransitionalSpeed(10.0);
|
||||
template.setWaypointInfo(waypointInfo);
|
||||
template.setUseGlobalTransitionalSpeed(true);
|
||||
template.setAutoFlightSpeed(10.0);
|
||||
|
||||
//不写就报错
|
||||
template.setPayloadParam(new ArrayList<>());
|
||||
/* 7. 生成文件 */
|
||||
WPMZManager.getInstance().generateKMZFile(kmz.getAbsolutePath(), mission, config, template);
|
||||
//验证这个kmz
|
||||
|
||||
WaylineCheckErrorMsg result= WPMZManager.getInstance().checkValidation(kmz.getAbsolutePath());
|
||||
LogUtil.log("qwq",result.toString());
|
||||
if (result.getValue().isEmpty()) {
|
||||
LogUtil.log("qwq", "KMZ 校验通过");
|
||||
sendEvent2Server("备降点航线生成成功", 1);
|
||||
return true;
|
||||
} else {
|
||||
// 其他错误码照旧报错
|
||||
LogUtil.log("qwq", "校验失败,错误码:" + result.getValue());
|
||||
sendEvent2Server("备降点航线文件生成失败错误码:"+result.getValue(), 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.aros.apron.tools;
|
||||
|
||||
public class Gpsdistance {
|
||||
private static final double EARTH_RADIUS = 6371000;
|
||||
|
||||
/**
|
||||
* 水平距离(Haversine)
|
||||
*/
|
||||
public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
||||
double dLat = Math.toRadians(lat2 - lat1);
|
||||
double dLon = Math.toRadians(lon2 - lon1);
|
||||
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
|
||||
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
return EARTH_RADIUS * c;
|
||||
}
|
||||
|
||||
/**
|
||||
* 三维直线距离(水平 + 高度差)
|
||||
* 参数:经纬度(度),高度(米)
|
||||
*/
|
||||
public static double calculate3DDistance(double lat1, double lon1, double alt1,
|
||||
double lat2, double lon2, double alt2) {
|
||||
// 1. 算水平距离
|
||||
double horizontal = calculateDistance(lat1, lon1, lat2, lon2);
|
||||
|
||||
// 2. 算垂直距离(高度差)
|
||||
double vertical = Math.abs(alt2 - alt1);
|
||||
|
||||
// 3. 勾股定理算斜距
|
||||
return Math.sqrt(horizontal * horizontal + vertical * vertical);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载:直接用 DJI 的 LocationCoordinate3D
|
||||
*/
|
||||
public static double calculate3DDistance(dji.sdk.keyvalue.value.common.LocationCoordinate3D current,
|
||||
dji.sdk.keyvalue.value.common.LocationCoordinate3D target) {
|
||||
return calculate3DDistance(
|
||||
current.getLatitude(), current.getLongitude(), current.getAltitude(),
|
||||
target.getLatitude(), target.getLongitude(), target.getAltitude()
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,81 @@
|
|||
package com.aros.apron.tools;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* 极简版:每3秒检测一次本地8554端口是否开启
|
||||
* 参数别改:回调已强制切到主线程,可直接操作UI
|
||||
*/
|
||||
public class SimplePortScanner {
|
||||
private static final int CHECK_PORT = 8554;
|
||||
private static final String LOCAL_HOST = "127.0.0.1";
|
||||
private static final long SCAN_INTERVAL = 3000L;
|
||||
private static final int CONNECT_TIMEOUT = 500;
|
||||
|
||||
private Timer scanTimer;
|
||||
private OnPortCheckListener checkListener;
|
||||
// 主线程 Handler,用于切换回调到UI线程
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
public interface OnPortCheckListener {
|
||||
void onPortOpen();
|
||||
void onPortClosed();
|
||||
}
|
||||
|
||||
private static class Holder {
|
||||
private static final SimplePortScanner INSTANCE = new SimplePortScanner();
|
||||
}
|
||||
|
||||
public static SimplePortScanner getInstance() {
|
||||
return Holder.INSTANCE;
|
||||
}
|
||||
|
||||
private SimplePortScanner() {}
|
||||
|
||||
public void setOnPortCheckListener(OnPortCheckListener listener) {
|
||||
this.checkListener = listener;
|
||||
}
|
||||
|
||||
public void startScan() {
|
||||
stopScan();
|
||||
scanTimer = new Timer();
|
||||
scanTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
final boolean isOpen = checkPort(LOCAL_HOST, CHECK_PORT);
|
||||
// 关键修改:切到主线程回调
|
||||
mainHandler.post(() -> {
|
||||
if (checkListener != null) {
|
||||
if (isOpen) {
|
||||
checkListener.onPortOpen();
|
||||
} else {
|
||||
checkListener.onPortClosed();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 0, SCAN_INTERVAL);
|
||||
}
|
||||
|
||||
public void stopScan() {
|
||||
if (scanTimer != null) {
|
||||
scanTimer.cancel();
|
||||
scanTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkPort(String host, int port) {
|
||||
try (Socket socket = new Socket()) {
|
||||
socket.connect(new InetSocketAddress(host, port), CONNECT_TIMEOUT);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
package com.aros.apron.tools;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.aros.apron.constant.AMSConfig;
|
||||
import com.aros.apron.entity.Movement;
|
||||
import com.aros.apron.entity.SpeakerAudioPlayProgress;
|
||||
import com.aros.apron.entity.SpeakerTTSPlayProgress;
|
||||
import com.google.gson.Gson;
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 喊话器进度定时上报(使用实体类 SpeakerAudioPlayProgress / SpeakerTTSPlayProgress)
|
||||
*/
|
||||
public class SpeakerProgressReporter {
|
||||
|
||||
private static final String TAG = "SpeakerProgressReporter";
|
||||
private static volatile SpeakerProgressReporter instance;
|
||||
|
||||
private Handler handler;
|
||||
private Runnable audioRunnable;
|
||||
private Runnable ttsRunnable;
|
||||
private static final long INTERVAL = 1000; // 1秒
|
||||
|
||||
private SpeakerProgressReporter() {
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
public static SpeakerProgressReporter getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (SpeakerProgressReporter.class) {
|
||||
if (instance == null) {
|
||||
instance = new SpeakerProgressReporter();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
// ==================== 音频文件播放上报 ====================
|
||||
|
||||
public void startAudioReport(int psdkIndex) {
|
||||
stopAudioReport();
|
||||
LogUtil.log(TAG, "启动音频进度上报,MD5:" + Movement.getInstance().getMd5());
|
||||
|
||||
audioRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
sendAudioProgress(psdkIndex);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// 关键修复:用 audio_status 判断,不是 TTS_status
|
||||
String status = Movement.getInstance().getTTS_status();
|
||||
if (!"ok".equals(status)) {
|
||||
handler.postDelayed(this, INTERVAL);
|
||||
} else {
|
||||
handler.postDelayed(() -> stopAudioReport(), 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handler.post(audioRunnable);
|
||||
}
|
||||
|
||||
public void stopAudioReport() {
|
||||
if (audioRunnable != null) {
|
||||
handler.removeCallbacks(audioRunnable);
|
||||
}
|
||||
LogUtil.log(TAG, "停止音频进度上报");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送音频进度(使用 SpeakerAudioPlayProgress 实体类)
|
||||
*/
|
||||
private void sendAudioProgress(int psdkIndex) {
|
||||
try {
|
||||
if (!MqttManager.getInstance().mqttAndroidClient.isConnected()) return;
|
||||
|
||||
Movement m = Movement.getInstance();
|
||||
|
||||
// 构建 progress
|
||||
SpeakerAudioPlayProgress.Progress progress = new SpeakerAudioPlayProgress.Progress();
|
||||
progress.setPercent(m.getSpeakerpercent());
|
||||
progress.setStepKey(m.getStep_key());
|
||||
|
||||
// 构建 output
|
||||
SpeakerAudioPlayProgress.Output output = new SpeakerAudioPlayProgress.Output();
|
||||
output.setPsdkIndex(psdkIndex);
|
||||
output.setStatus(m.getTTS_status());
|
||||
output.setMd5("bacee8ed225fa346f6da87f67c914728");
|
||||
output.setProgress(progress);
|
||||
|
||||
// 构建 data
|
||||
SpeakerAudioPlayProgress.Data data = new SpeakerAudioPlayProgress.Data();
|
||||
data.setResult(0);
|
||||
data.setOutput(output);
|
||||
|
||||
// 构建根对象
|
||||
SpeakerAudioPlayProgress event = new SpeakerAudioPlayProgress();
|
||||
event.setBid(UUID.randomUUID().toString());
|
||||
event.setTid(UUID.randomUUID().toString());
|
||||
event.setTimestamp(System.currentTimeMillis());
|
||||
event.setMethod("speaker_audio_play_start_progress");
|
||||
event.setNeedReply(1);
|
||||
event.setData(data);
|
||||
|
||||
String json = new Gson().toJson(event);
|
||||
MqttMessage msg = new MqttMessage(json.getBytes("UTF-8"));
|
||||
msg.setQos(0);
|
||||
MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_EVENT, msg);
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== TTS播放上报 ====================
|
||||
|
||||
public void startTTSReport(int psdkIndex) {
|
||||
stopTTSReport();
|
||||
LogUtil.log(TAG, "启动TTS进度上报,MD5:" + Movement.getInstance().getMd5());
|
||||
|
||||
ttsRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
sendTTSProgress(psdkIndex);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
String status = Movement.getInstance().getTTS_status();
|
||||
if (!"ok".equals(status)) {
|
||||
handler.postDelayed(this, INTERVAL);
|
||||
} else {
|
||||
handler.postDelayed(() -> stopTTSReport(), 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handler.post(ttsRunnable);
|
||||
}
|
||||
|
||||
public void stopTTSReport() {
|
||||
if (ttsRunnable != null) {
|
||||
handler.removeCallbacks(ttsRunnable);
|
||||
}
|
||||
LogUtil.log(TAG, "停止TTS进度上报");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送TTS进度(使用 SpeakerTTSPlayProgress 实体类)
|
||||
*/
|
||||
private void sendTTSProgress(int psdkIndex) {
|
||||
try {
|
||||
if (!MqttManager.getInstance().mqttAndroidClient.isConnected()) return;
|
||||
|
||||
Movement m = Movement.getInstance();
|
||||
|
||||
// 构建 progress
|
||||
SpeakerTTSPlayProgress.Progress progress = new SpeakerTTSPlayProgress.Progress();
|
||||
progress.setPercent(m.getSpeakerpercent()); // 复用 percent 字段
|
||||
progress.setStepKey(m.getStep_key()); // TTS 用 step_key
|
||||
|
||||
// 构建 output
|
||||
SpeakerTTSPlayProgress.Output output = new SpeakerTTSPlayProgress.Output();
|
||||
output.setPsdkIndex(psdkIndex);
|
||||
output.setStatus(m.getTTS_status());
|
||||
output.setMd5("bacee8ed225fa346f6da87f67c914728");
|
||||
output.setProgress(progress);
|
||||
|
||||
// 构建 data
|
||||
SpeakerTTSPlayProgress.Data data = new SpeakerTTSPlayProgress.Data();
|
||||
data.setResult(0);
|
||||
data.setOutput(output);
|
||||
|
||||
// 构建根对象
|
||||
SpeakerTTSPlayProgress event = new SpeakerTTSPlayProgress();
|
||||
event.setBid(UUID.randomUUID().toString());
|
||||
event.setTid(UUID.randomUUID().toString());
|
||||
event.setTimestamp(System.currentTimeMillis());
|
||||
event.setMethod("speaker_tts_play_start_progress");
|
||||
event.setNeedReply(1);
|
||||
event.setData(data);
|
||||
|
||||
String json = new Gson().toJson(event);
|
||||
MqttMessage msg = new MqttMessage(json.getBytes("UTF-8"));
|
||||
msg.setQos(0);
|
||||
MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_EVENT, msg);
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
package com.aros.apron.tools;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.aros.apron.constant.AMSConfig;
|
||||
import com.aros.apron.entity.Movement;
|
||||
import com.aros.apron.entity.TakeoffToPointProgress;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import dji.sdk.keyvalue.key.FlightControllerKey;
|
||||
import dji.sdk.keyvalue.key.KeyTools;
|
||||
import dji.sdk.keyvalue.value.common.LocationCoordinate2D;
|
||||
import dji.sdk.keyvalue.value.common.LocationCoordinate3D;
|
||||
import dji.v5.manager.KeyManager;
|
||||
|
||||
/**
|
||||
* 一键起飞进度定时上报器(Handler版,无红线)
|
||||
*/
|
||||
public class TakeoffProgressScheduler {
|
||||
|
||||
private static final String TAG = "TakeoffProgressScheduler";
|
||||
private static volatile TakeoffProgressScheduler instance;
|
||||
|
||||
// Handler 定时器(Android 主线程)
|
||||
private Handler reportHandler;
|
||||
private Runnable reportRunnable;
|
||||
|
||||
// 控制开关
|
||||
private volatile boolean isRunning = false;
|
||||
|
||||
// 轨迹点缓存
|
||||
private List<TakeoffToPointProgress.PlannedPathPoint> pathPoints;
|
||||
|
||||
// 采样间隔(ms)
|
||||
private static final long REPORT_INTERVAL = 2000;
|
||||
|
||||
// 最大轨迹点数
|
||||
private static final int MAX_POINTS = 3600;
|
||||
|
||||
private TakeoffProgressScheduler() {
|
||||
reportHandler = new Handler(Looper.getMainLooper());
|
||||
pathPoints = new ArrayList<>();
|
||||
}
|
||||
|
||||
public static TakeoffProgressScheduler getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (TakeoffProgressScheduler.class) {
|
||||
if (instance == null) {
|
||||
instance = new TakeoffProgressScheduler();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始定时上报(开关打开)
|
||||
*/
|
||||
public void startReporting() {
|
||||
if (isRunning) {
|
||||
LogUtil.log(TAG, "定时上报已在运行中");
|
||||
return;
|
||||
}
|
||||
|
||||
isRunning = true;
|
||||
pathPoints.clear();
|
||||
LogUtil.log(TAG, "启动一键起飞进度定时上报");
|
||||
|
||||
// 定义定时任务(参数别改)
|
||||
reportRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isRunning) return;
|
||||
|
||||
try {
|
||||
// 1. 采样飞机位置(在子线程执行,避免阻塞主线程)
|
||||
new Thread(() -> sampleCurrentLocation()).start();
|
||||
|
||||
// 2. 发送上报
|
||||
sendProgressReport();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
LogUtil.log(TAG, "定时任务异常:" + e.getMessage());
|
||||
}
|
||||
|
||||
// 3. 2秒后再次执行(实现循环)
|
||||
if (isRunning) {
|
||||
reportHandler.postDelayed(this, REPORT_INTERVAL);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 立即执行第一次
|
||||
reportHandler.post(reportRunnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止定时上报(开关关闭)
|
||||
*/
|
||||
public void stopReporting() {
|
||||
if (!isRunning) return;
|
||||
|
||||
isRunning = false;
|
||||
|
||||
// 最后发送一次
|
||||
sendProgressReport();
|
||||
|
||||
reportHandler.removeCallbacks(reportRunnable);
|
||||
|
||||
pathPoints.clear();
|
||||
|
||||
LogUtil.log(TAG, "停止一键起飞进度定时上报");
|
||||
}
|
||||
|
||||
/**
|
||||
* 采样当前飞机位置(参数别改)
|
||||
*/
|
||||
private void sampleCurrentLocation() {
|
||||
try {
|
||||
LocationCoordinate3D locationCoordinate3D=KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey.KeyAircraftLocation3D));
|
||||
|
||||
double lat = locationCoordinate3D.getLatitude();
|
||||
double lng = locationCoordinate3D.getLongitude();
|
||||
double alt = locationCoordinate3D.getAltitude();
|
||||
|
||||
// 过滤无效坐标
|
||||
if (Math.abs(lat) < 0.0001 && Math.abs(lng) < 0.0001) return;
|
||||
|
||||
TakeoffToPointProgress.PlannedPathPoint point = new TakeoffToPointProgress.PlannedPathPoint();
|
||||
point.setLatitude(lat);
|
||||
point.setLongitude(lng);
|
||||
point.setHeight(alt);
|
||||
double remaining_distance=Gpsdistance.calculate3DDistance(lat,lng,alt,Movement.getInstance().getTakeofftargetlatitude(),Movement.getInstance().getTakeofftargetlongitude(),Movement.getInstance().getRtk_takeoff_altitude());
|
||||
double remaining_time=remaining_distance/Movement.getInstance().getSpeed();
|
||||
Movement.getInstance().setTakeoff_remaining_distance((float) remaining_distance);
|
||||
Movement.getInstance().setTakeoff_remaining_time((float) remaining_time);
|
||||
|
||||
pathPoints.add(point);
|
||||
|
||||
// 限制最大点数
|
||||
if (pathPoints.size() > MAX_POINTS) {
|
||||
pathPoints.remove(0);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG, "采样位置失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送进度上报(参数别改)
|
||||
*/
|
||||
public void sendProgressReport() {
|
||||
try {
|
||||
if (!MqttManager.getInstance().mqttAndroidClient.isConnected()) {
|
||||
LogUtil.log(TAG, "MQTT未连接,跳过上报");
|
||||
return;
|
||||
}
|
||||
TakeoffToPointProgress.Data data = new TakeoffToPointProgress.Data();
|
||||
data.setFlightId(PreferenceUtils.getInstance().getFlightId());
|
||||
data.setTrackId(PreferenceUtils.getInstance().getFlightId());
|
||||
data.setStatus(Movement.getInstance().getTakeoff_status());
|
||||
data.setResult(Movement.getInstance().getTakeoff_result());
|
||||
data.setWayPointIndex(Movement.getInstance().getCurrentWaypointIndex());
|
||||
|
||||
//计算一下这个剩余距离
|
||||
data.setRemainingDistance(Movement.getInstance().getTakeoff_remaining_distance());
|
||||
//计算一个这个剩余时间
|
||||
data.setRemainingTime(Movement.getInstance().getTakeoff_remaining_time());
|
||||
|
||||
data.setPlannedPathPoints(new ArrayList<>(pathPoints));
|
||||
|
||||
TakeoffToPointProgress progress = new TakeoffToPointProgress();
|
||||
progress.setBid(UUID.randomUUID().toString());
|
||||
progress.setTid(UUID.randomUUID().toString());
|
||||
progress.setTimestamp(System.currentTimeMillis());
|
||||
progress.setMethod("takeoff_to_point_progress");
|
||||
progress.setNeedReply(1);
|
||||
progress.setData(data);
|
||||
|
||||
String json = new Gson().toJson(progress);
|
||||
MqttMessage mqttMessage = new MqttMessage(json.getBytes("UTF-8"));
|
||||
mqttMessage.setQos(0);
|
||||
MqttManager.getInstance().mqttAndroidClient.publish(AMSConfig.UP_UAV_EVENT, mqttMessage);
|
||||
|
||||
LogUtil.log(TAG, "上报进度,轨迹点:" + pathPoints.size() + " 个");
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
LogUtil.log(TAG, "上报异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 轨迹点实体(参数别改)
|
||||
*/
|
||||
public static class PathPoint {
|
||||
@SerializedName("latitude")
|
||||
private double latitude;
|
||||
|
||||
@SerializedName("longitude")
|
||||
private double longitude;
|
||||
|
||||
@SerializedName("height")
|
||||
private double height;
|
||||
|
||||
public PathPoint(double latitude, double longitude, double height) {
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
public void release() {
|
||||
stopReporting();
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
|
||||
package com.aros.apron.tools;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Locale;
|
||||
|
||||
import dji.v5.common.error.IDJIError;
|
||||
|
||||
public class Utils {
|
||||
public static void printJson(String tag, String json) {
|
||||
if (json == null || json.length() == 0) {
|
||||
Log.e(tag, "JSON is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
int maxLogSize = 4000;
|
||||
for (int i = 0; i <= json.length() / maxLogSize; i++) {
|
||||
int start = i * maxLogSize;
|
||||
int end = (i + 1) * maxLogSize;
|
||||
if (end >= json.length()) {
|
||||
end = json.length();
|
||||
}
|
||||
Log.e(tag, json.substring(start, end));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* //修改变焦数据为从前端拿2-200自己计算然后放入官方的sdk
|
||||
*
|
||||
* @param smallZoomFromWeb
|
||||
* @return
|
||||
*/
|
||||
public static int getbigZoomValue(String smallZoomFromWeb) {
|
||||
int zoomLength = Integer.parseInt(smallZoomFromWeb);
|
||||
int bigZoom = (47549 - 317) / 199 * (zoomLength - 2) + 317;
|
||||
return bigZoom;
|
||||
}
|
||||
|
||||
public static String sHA1(Context context){
|
||||
try {
|
||||
PackageInfo info = null;
|
||||
try {
|
||||
info = context.getPackageManager().getPackageInfo(
|
||||
context.getPackageName(), PackageManager.GET_SIGNATURES);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
byte[] cert = info.signatures[0].toByteArray();
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
byte[] publicKey = md.digest(cert);
|
||||
StringBuffer hexString = new StringBuffer();
|
||||
for (int i = 0; i < publicKey.length; i++) {
|
||||
String appendString = Integer.toHexString(0xFF & publicKey[i])
|
||||
.toUpperCase(Locale.US);
|
||||
if (appendString.length() == 1)
|
||||
hexString.append("0");
|
||||
hexString.append(appendString);
|
||||
hexString.append(":");
|
||||
}
|
||||
String result = hexString.toString();
|
||||
return result.substring(0, result.length()-1);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] getByte(String str){
|
||||
try {
|
||||
byte[] bytes = str.getBytes("UTF-8");
|
||||
return bytes;
|
||||
// 使用bytes
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// 处理异常,比如使用默认字符集
|
||||
byte[] bytes = str.getBytes();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static double parseLatLon(String latLonStr) {
|
||||
// 检查输入字符串是否为空或无效
|
||||
if (latLonStr == null || latLonStr.isEmpty()) {
|
||||
throw new IllegalArgumentException("Input string is null or empty");
|
||||
}
|
||||
|
||||
// 获取最后一个字符,即方向标识符
|
||||
char direction = latLonStr.charAt(latLonStr.length() - 1);
|
||||
|
||||
// 检查方向标识符是否有效
|
||||
if (direction != 'N' && direction != 'S' && direction != 'E' && direction != 'W') {
|
||||
return Double.parseDouble(latLonStr); }
|
||||
|
||||
// 提取数字部分
|
||||
String numberPart = latLonStr.substring(0, latLonStr.length() - 1);
|
||||
|
||||
// 将数字部分转换为Double
|
||||
double number = Double.parseDouble(numberPart);
|
||||
|
||||
// 根据方向标识符调整数值
|
||||
if (direction == 'S' || direction == 'W') {
|
||||
number = -number;
|
||||
}
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
public static String getIDJIErrorMsg(IDJIError idjiError){
|
||||
if (TextUtils.isEmpty(idjiError.description())){
|
||||
return new Gson().toJson(idjiError);
|
||||
}else{
|
||||
return idjiError.description();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getSDCardPath() {
|
||||
String sdCardPathString = "";
|
||||
if (checkSDCard()) {
|
||||
sdCardPathString = Environment.getExternalStorageDirectory().getPath();
|
||||
} else {
|
||||
sdCardPathString = Environment.getExternalStorageDirectory()
|
||||
.getParentFile()
|
||||
.getPath();
|
||||
}
|
||||
return sdCardPathString;
|
||||
}
|
||||
|
||||
public static boolean checkSDCard() {
|
||||
return TextUtils.equals(
|
||||
Environment.MEDIA_MOUNTED,
|
||||
Environment.getExternalStorageState()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package com.aros.apron.tools;
|
||||
|
||||
import com.aros.apron.entity.XTTSParams;
|
||||
import com.iflytek.aikit.core.AeeEvent;
|
||||
import com.iflytek.aikit.core.AiHandle;
|
||||
import com.iflytek.aikit.core.AiHelper;
|
||||
import com.iflytek.aikit.core.AiInput;
|
||||
import com.iflytek.aikit.core.AiListener;
|
||||
import com.iflytek.aikit.core.AiRequest;
|
||||
import com.iflytek.aikit.core.AiResponse;
|
||||
import com.iflytek.aikit.core.AiText;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class XTtsPcmGenerator {
|
||||
|
||||
private static final String TAG = "XTTS";
|
||||
private static final String ABILITY_ID = "e2e44feff";
|
||||
|
||||
private AiHandle aiHandle;
|
||||
private FileOutputStream pcmOut;
|
||||
|
||||
|
||||
public void init(AiListener listener) {
|
||||
AiHelper.getInst().registerListener(ABILITY_ID, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合成文本为 PCM 文件
|
||||
*/
|
||||
public int synthToPcm(
|
||||
String text,
|
||||
XTTSParams params,
|
||||
File pcmFile
|
||||
) {
|
||||
File parent = pcmFile.getParentFile();
|
||||
if (parent != null && !parent.exists()) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
try {
|
||||
pcmOut = new FileOutputStream(pcmFile, false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
|
||||
AiInput.Builder paramBuilder = AiInput.builder();
|
||||
paramBuilder.param("vcn", params.vcn);
|
||||
paramBuilder.param("language", params.language);
|
||||
paramBuilder.param("textEncoding", "UTF-8");
|
||||
paramBuilder.param("pitch", params.pitch);
|
||||
paramBuilder.param("speed", params.speed);
|
||||
paramBuilder.param("volume", params.volume);
|
||||
|
||||
aiHandle = AiHelper.getInst().start(
|
||||
ABILITY_ID,
|
||||
paramBuilder.build(),
|
||||
null
|
||||
);
|
||||
|
||||
if (aiHandle.getCode() != 0) {
|
||||
LogUtil.log(TAG, "start failed: " + aiHandle.getCode());
|
||||
return aiHandle.getCode();
|
||||
}
|
||||
|
||||
AiText aiText = AiText.get("text")
|
||||
.data(text)
|
||||
.valid();
|
||||
|
||||
AiRequest request = AiRequest.builder()
|
||||
.payload(aiText)
|
||||
.build();
|
||||
|
||||
int ret = AiHelper.getInst().write(request, aiHandle);
|
||||
if (ret != 0) {
|
||||
LogUtil.log(TAG, "write failed: " + ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部使用的监听器
|
||||
*/
|
||||
public AiListener buildListener() {
|
||||
return new AiListener() {
|
||||
|
||||
@Override
|
||||
public void onResult(int handleID, List<AiResponse> list, Object usrContext) {
|
||||
if (list == null){
|
||||
LogUtil.log(TAG,"list == null");
|
||||
return;
|
||||
}
|
||||
|
||||
for (AiResponse resp : list) {
|
||||
if ("audio".equals(resp.getKey())) {
|
||||
byte[] pcm = resp.getValue();
|
||||
if (pcm != null && pcm.length > 0) {
|
||||
writePcm(pcm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int handleID, int event, List<AiResponse> eventData, Object usrContext) {
|
||||
if (event == AeeEvent.AEE_EVENT_END.getValue()) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int handleID, int err, String msg, Object usrContext) {
|
||||
LogUtil.log(TAG, "XTTS error: " + err + ", " + msg);
|
||||
close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private synchronized void writePcm(byte[] data) {
|
||||
try {
|
||||
if (pcmOut != null) {
|
||||
pcmOut.write(data);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void close() {
|
||||
try {
|
||||
if (pcmOut != null) {
|
||||
pcmOut.flush();
|
||||
pcmOut.close();
|
||||
pcmOut = null;
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
if (aiHandle != null) {
|
||||
AiHelper.getInst().end(aiHandle);
|
||||
aiHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放引擎(App 退出时调用)
|
||||
*/
|
||||
// public void release() {
|
||||
// AiHelper.getInst().engineUnInit(ABILITY_ID);
|
||||
// }
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package com.aros.apron.tools;
|
||||
|
||||
public class mixrongapron {
|
||||
}
|
||||
Loading…
Reference in New Issue