Merge pull request #67 from d3m3vilurr/trusdx

Support audio streaming over CAT of (tr)uSDX
pull/74/head
NØBOY 2023-08-15 23:18:03 -04:00 zatwierdzone przez GitHub
commit 68e1c79b6a
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
12 zmienionych plików z 528 dodań i 16 usunięć

Wyświetl plik

@ -65,6 +65,12 @@ android {
// version '3.18.1'
// }
// }
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
namespace 'com.bg7yoz.ft8cn'
}
@ -88,4 +94,5 @@ dependencies {
implementation files('src/libs/MPAndroidChartv_3.1.0.jar')
implementation files('src/libs/nanohttpd-2.2.0.jar')
implementation files('src/libs/osmdroid-android-6.1.14.aar')//
}
implementation files('src/libs/resample-release.aar')
}

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -50,4 +50,5 @@ FlexRadio 6000 series,00,00,12
FX-4CR,00,115200,7
Qrp Labs QDX,00,9600,7
UA3REO Wolf SDR(DIGU),00,4800,15
UA3REO Wolf SDR(USB),00,4800,16
UA3REO Wolf SDR(USB),00,4800,16
(tr)uSDX,00,115200,17

Wyświetl plik

@ -79,6 +79,7 @@ import com.bg7yoz.ft8cn.rigs.Yaesu38Rig;
import com.bg7yoz.ft8cn.rigs.Yaesu38_450Rig;
import com.bg7yoz.ft8cn.rigs.Yaesu39Rig;
import com.bg7yoz.ft8cn.rigs.YaesuDX10Rig;
import com.bg7yoz.ft8cn.rigs.TrUSDXRig;
import com.bg7yoz.ft8cn.spectrum.SpectrumListener;
import com.bg7yoz.ft8cn.timer.OnUtcTimer;
import com.bg7yoz.ft8cn.timer.UtcTimer;
@ -354,13 +355,26 @@ public class MainViewModel extends ViewModel {
//创建发射对象回调发射前发射后、QSL成功后。
ft8TransmitSignal = new FT8TransmitSignal(databaseOpr, new OnDoTransmitted() {
private boolean needControlSco() {
if (GeneralVariables.connectMode == ConnectMode.NETWORK) {
return false;
}
if (GeneralVariables.controlMode != ControlMode.CAT) {
return true;
}
if (baseRig != null && !baseRig.supportWaveOverCAT()) {
return true;
}
return false;
}
@Override
public void onBeforeTransmit(Ft8Message message, int functionOder) {
if (GeneralVariables.controlMode == ControlMode.CAT
|| GeneralVariables.controlMode == ControlMode.RTS
|| GeneralVariables.controlMode == ControlMode.DTR) {
if (baseRig != null) {
if (GeneralVariables.connectMode != ConnectMode.NETWORK) stopSco();
if (needControlSco()) stopSco();
baseRig.setPTT(true);
}
}
@ -378,7 +392,7 @@ public class MainViewModel extends ViewModel {
|| GeneralVariables.controlMode == ControlMode.DTR) {
if (baseRig != null) {
baseRig.setPTT(false);
if (GeneralVariables.connectMode != ConnectMode.NETWORK) startSco();
if (needControlSco()) startSco();
}
}
}
@ -396,6 +410,31 @@ public class MainViewModel extends ViewModel {
}
}
}
@Override
public boolean supportTransmitOverCAT() {
if (GeneralVariables.controlMode != ControlMode.CAT) {
return false;
}
if (baseRig == null) {
return false;
}
if (!baseRig.isConnected() || !baseRig.supportWaveOverCAT()) {
return false;
}
return true;
}
@Override
public void onTransmitOverCAT(Ft8Message msg) {
if (!supportTransmitOverCAT()) {
return;
}
sendWaveDataRunnable.baseRig = baseRig;
sendWaveDataRunnable.message = msg;
sendWaveDataThreadPool.execute(sendWaveDataRunnable);
}
}, new OnTransmitSuccess() {//当通联成功时
@Override
public void doAfterTransmit(QSLRecord qslRecord) {
@ -599,7 +638,14 @@ public class MainViewModel extends ViewModel {
}
baseRig.setControlMode(GeneralVariables.controlMode);
CableConnector connector = new CableConnector(context, port, GeneralVariables.baudRate
, GeneralVariables.controlMode);
, GeneralVariables.controlMode, baseRig);
connector.setOnCableDataReceived(new CableConnector.OnCableDataReceived() {
@Override
public void OnWaveReceived(int bufferLen, float[] buffer) {
Log.i(TAG, "call hamRecorder.doOnWaveDataReceived");
hamRecorder.doOnWaveDataReceived(bufferLen, buffer);
}
});
baseRig.setOnRigStateChanged(onRigStateChanged);
baseRig.setConnector(connector);
connector.connect();
@ -713,13 +759,6 @@ public class MainViewModel extends ViewModel {
*
*/
private void connectRig() {
if ((GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK)
|| (GeneralVariables.instructionSet == InstructionSet.ICOM
&& GeneralVariables.connectMode == ConnectMode.NETWORK)) {
hamRecorder.setDataFromLan();
} else {
hamRecorder.setDataFromMic();
}
baseRig = null;
//此处判断是用什么类型的电台ICOM,YAESU 2,YAESU 3
switch (GeneralVariables.instructionSet) {
@ -773,9 +812,23 @@ public class MainViewModel extends ViewModel {
break;
case InstructionSet.WOLF_SDR_USB:
baseRig = new Wolf_sdr_450Rig(true);
case InstructionSet.TRUSDX:
baseRig = new TrUSDXRig();//(tr)uSDX
break;
}
if ((GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK)
|| (GeneralVariables.instructionSet == InstructionSet.ICOM
&& GeneralVariables.connectMode == ConnectMode.NETWORK)) {
hamRecorder.setDataFromLan();
} else {
if (GeneralVariables.controlMode != ControlMode.CAT || baseRig == null || !baseRig.supportWaveOverCAT()) {
hamRecorder.setDataFromMic();
} else {
hamRecorder.setDataFromLan();
}
}
mutableIsFlexRadio.postValue(GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK);
}
@ -918,4 +971,4 @@ public class MainViewModel extends ViewModel {
}
}
}
}

Wyświetl plik

@ -74,10 +74,30 @@ public class BaseRigConnector {
onConnectReceiveData=receiveData;
}
public void sendWaveData(byte[] data){
float[] waveFloat=new float[data.length/2];
for (int i = 0; i <waveFloat.length ; i++) {
waveFloat[i]=readShortBigEndianData(data,i*2)/32768.0f;
}
sendWaveData(waveFloat);
}
public void sendWaveData(float[] data){
//留给网络方式发送音频流
}
public void receiveWaveData(byte[] data){
float[] waveFloat=new float[data.length/2];
for (int i = 0; i <waveFloat.length ; i++) {
waveFloat[i]=readShortBigEndianData(data,i*2)/32768.0f;
}
receiveWaveData(waveFloat);
}
public void receiveWaveData(float[] data){
}
public OnConnectReceiveData getOnConnectReceiveData() {
return onConnectReceiveData;
}
@ -100,4 +120,17 @@ public class BaseRigConnector {
public boolean isConnected(){
return connected;
}
/**
* Short
*
* @param data
* @param start
* @return Int16
*/
public static short readShortBigEndianData(byte[] data, int start) {
if (data.length - start < 2) return 0;
return (short) ((short) data[start] & 0xff
| ((short) data[start + 1] & 0xff) << 8);
}
}

Wyświetl plik

@ -4,6 +4,7 @@ import android.content.Context;
import android.util.Log;
import com.bg7yoz.ft8cn.database.ControlMode;
import com.bg7yoz.ft8cn.rigs.BaseRig;
import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager;
/**
@ -14,13 +15,18 @@ import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager;
*/
public class CableConnector extends BaseRigConnector {
private static final String TAG="CableConnector";
public interface OnCableDataReceived {
void OnWaveReceived(int bufferLen,float[] buffer);
}
private final CableSerialPort cableSerialPort;
private final BaseRig cableConnectedRig;
private OnCableDataReceived onCableDataReceived;
public CableConnector(Context context,CableSerialPort.SerialPort serialPort, int baudRate
, int controlMode) {
, int controlMode, BaseRig cableConnectedRig) {
super(controlMode);
this.cableConnectedRig = cableConnectedRig;
cableSerialPort= new CableSerialPort(context,serialPort,baudRate,getOnConnectorStateChanged());
cableSerialPort.ioListener=new SerialInputOutputManager.Listener() {
@Override
@ -61,6 +67,32 @@ public class CableConnector extends BaseRigConnector {
cableSerialPort.sendData(command);//以CAT指令发送PTT
}
@Override
public void sendWaveData(byte[] data) {
sendData(data);
}
@Override
public void sendWaveData(float[] data) {
// TODO float to byte
byte[] wave = new byte[data.length * 2];
sendWaveData(data);
}
@Override
public void receiveWaveData(float[] data){
Log.i(TAG, "received wave data");
if (onCableDataReceived!=null){
Log.i(TAG, "call onCableDataReceived.OnWaveReceived");
onCableDataReceived.OnWaveReceived(data.length,data);
}
}
public void setOnCableDataReceived(OnCableDataReceived onCableDataReceived) {
this.onCableDataReceived = onCableDataReceived;
}
@Override
public void connect() {
super.connect();
@ -69,6 +101,7 @@ public class CableConnector extends BaseRigConnector {
@Override
public void disconnect() {
cableConnectedRig.onDisconnecting();
super.disconnect();
cableSerialPort.disconnect();
}

Wyświetl plik

@ -20,6 +20,7 @@ import com.bg7yoz.ft8cn.Ft8Message;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.R;
import com.bg7yoz.ft8cn.connector.ConnectMode;
import com.bg7yoz.ft8cn.database.ControlMode;
import com.bg7yoz.ft8cn.database.DatabaseOpr;
import com.bg7yoz.ft8cn.log.QSLRecord;
import com.bg7yoz.ft8cn.rigs.BaseRigOperation;
@ -413,6 +414,32 @@ public class FT8TransmitSignal {
return;
}
if (GeneralVariables.controlMode == ControlMode.CAT) {
Log.d(TAG, "playFT8Signal: try to transmit over CAT");
if (onDoTransmitted != null) {//处理音频数据可以给ICOM的网络模式发送
if (onDoTransmitted.supportTransmitOverCAT()) {
onDoTransmitted.onTransmitOverCAT(msg);
long now = System.currentTimeMillis();
while (isTransmitting) {//等待音频数据包发送完毕再退出以触发afterTransmitting
try {
Thread.sleep(1);
long current = System.currentTimeMillis() - now;
if (current > 13000) {//实际发射的时长
isTransmitting = false;
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.d(TAG, "playFT8Signal: transmitting over CAT is finished.");
afterPlayAudio();
return;
}
}
}
//进入声卡模式
float[] buffer;

Wyświetl plik

@ -11,4 +11,6 @@ public interface OnDoTransmitted {
void onBeforeTransmit(Ft8Message message,int functionOder);
void onAfterTransmit(Ft8Message message, int functionOder);
void onTransmitByWifi(Ft8Message message);
boolean supportTransmitOverCAT();
void onTransmitOverCAT(Ft8Message message);
}

Wyświetl plik

@ -130,4 +130,11 @@ public abstract class BaseRig {
public boolean isPttOn() {
return isPttOn;
}
public boolean supportWaveOverCAT() {
return false;
}
public void onDisconnecting() {
}
}

Wyświetl plik

@ -18,5 +18,5 @@ public class InstructionSet {
public static final int KENWOOD_TS2000=14;//建武TS2000,发射指令是TX0;
public static final int WOLF_SDR_DIGU=15;//UA3REO Wolf SDR,DIG-U模式兼容YAESU 450D;
public static final int WOLF_SDR_USB=16;//UA3REO Wolf SDR,USB模式兼容YAESU 450D;
public static final int TRUSDX=17;//(tr)USDX;
}

Wyświetl plik

@ -37,6 +37,11 @@ public class KenwoodTK90RigConstant {
private static final String TS590_READ_FREQ = "FA;";//KENWOOD 读频率
private static final String TS590_READ_METERS = "RM;";//KENWOOD 读METER
// (tr)uSDX extensions
private static final String TRUSDX_STREAMING_OFF="UA0;";
private static final String TRUSDX_STREAMING_ON_SPEAKER_ON="UA1;";
private static final String TRUSDX_STREAMING_ON_SPEAKER_OFF="UA2;";
private static final String TRUSDX_STREAMING_AUDIO="US";
@ -132,4 +137,19 @@ public class KenwoodTK90RigConstant {
return TS590_READ_FREQ.getBytes();
}
public static byte[] setTrUSDXStreaming(boolean on) {
if (on) {
return TRUSDX_STREAMING_ON_SPEAKER_OFF.getBytes();
} else {
return TRUSDX_STREAMING_OFF.getBytes();
}
}
public static byte[] setTrUSDXPTTState(boolean on) {
if (on) {
return ";TX0;".getBytes();
} else {
return ";RX;".getBytes();
}
}
}

Wyświetl plik

@ -0,0 +1,329 @@
package com.bg7yoz.ft8cn.rigs;
import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT;
import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY;
import android.os.Handler;
import android.util.Log;
import com.bg7yoz.ft8cn.Ft8Message;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.R;
import com.bg7yoz.ft8cn.database.ControlMode;
import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8;
import com.bg7yoz.ft8cn.ui.ToastMessage;
import com.jackz314.resample.Resample;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
/**
* (tr)uSDX, fork from KENWOOD TS590.
*/
public class TrUSDXRig extends BaseRig {
private static final String TAG = "TrUSDXRig";
private static final int rxSampling = 7812;
private static final int txSampling = 11520;
private final StringBuilder buffer = new StringBuilder();
private final ByteArrayOutputStream rxStreamBuffer = new ByteArrayOutputStream();
private Timer readFreqTimer = new Timer();
private int swr=0;
private int alc=0;
private boolean alcMaxAlert = false;
private boolean swrAlert = false;
private boolean rxStreaming = false;
private TimerTask readTask() {
return new TimerTask() {
@Override
public void run() {
try {
if (!isConnected()) {
readFreqTimer.cancel();
readFreqTimer.purge();
readFreqTimer = null;
return;
}
if (isPttOn()){
clearBufferData();
}else {
readFreqFromRig();//读频率
}
} catch (Exception e) {
Log.e(TAG, "readFreq error:" + e.getMessage());
}
}
};
}
/**
*
*/
private void clearBufferData() {
buffer.setLength(0);
}
@Override
public void setPTT(boolean on) {
super.setPTT(on);
if (getConnector() != null) {
switch (getControlMode()) {
case ControlMode.CAT:
if (on) {
rxStreaming = false;
}
getConnector().setPttOn(KenwoodTK90RigConstant.setTrUSDXPTTState(on));
break;
case ControlMode.RTS:
case ControlMode.DTR:
getConnector().setPttOn(on);
break;
}
}
}
@Override
public boolean isConnected() {
if (getConnector() == null) {
return false;
}
return getConnector().isConnected();
}
@Override
public void setUsbModeToRig() {
if (getConnector() != null) {
getConnector().sendData(KenwoodTK90RigConstant.setTS590OperationUSBMode());
}
}
@Override
public void setFreqToRig() {
if (getConnector() != null) {
getConnector().sendData(KenwoodTK90RigConstant.setTS590OperationFreq(getFreq()));
}
}
@Override
public void onReceiveData(byte[] data) {
byte[] remain = data;
String s = new String(data);
while (s.contains(";")) { // ;
// TODO apply effective way
int idx = s.indexOf(";");
byte[] cutted = Arrays.copyOf(remain, idx);
remain = Arrays.copyOfRange(remain, idx + 1, remain.length);
s = new String(remain);
if (rxStreaming) {
onReceivedWaveData(cutted, true);
rxStreaming = false;
} else {
buffer.append(new String(cutted));
//开始分析数据
Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString());
clearBufferData();//清一下缓存
if (yaesu3Command == null) {
continue;
}
String cmd=yaesu3Command.getCommandID();
if (cmd.equalsIgnoreCase("FA")) {//频率
long tempFreq=Yaesu3Command.getFrequency(yaesu3Command);
if (tempFreq!=0) {//如果tempFreq==0说明频率不正常
setFreq(Yaesu3Command.getFrequency(yaesu3Command));
}
}else if (cmd.equalsIgnoreCase("US")){
rxStreaming = true;
byte[] wave = Arrays.copyOfRange(cutted, 2, cutted.length);
onReceivedWaveData(wave);
}
}
}
if (remain.length <= 0) {
return;
}
if (rxStreaming) {
onReceivedWaveData(remain);
} else if (remain.length >= 2 && remain[0] == 0x55 && remain[1] == 0x53) {// US
clearBufferData();
rxStreaming = true;
byte[] wave = Arrays.copyOfRange(remain, 2, remain.length);
onReceivedWaveData(wave);
} else {
buffer.append(s);
}
}
private void showAlert() {
if (swr >= KenwoodTK90RigConstant.ts_590_swr_alert_max) {
if (!swrAlert) {
swrAlert = true;
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert));
}
} else {
swrAlert = false;
}
if (alc > KenwoodTK90RigConstant.ts_590_alc_alert_max) {//网络模式下不警告ALC
if (!alcMaxAlert) {
alcMaxAlert = true;
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert));
}
} else {
alcMaxAlert = false;
}
}
@Override
public void readFreqFromRig() {
if (getConnector() != null) {
clearBufferData();//清空一下缓存
// force reset
getConnector().sendData(KenwoodTK90RigConstant.setTrUSDXPTTState(false));
getConnector().sendData(KenwoodTK90RigConstant.setTS590ReadOperationFreq());
}
}
@Override
public String getName() {
return "(tr)uSDX";
}
@Override
public boolean supportWaveOverCAT() {
return true;
}
@Override
public void onDisconnecting() {
if (getConnector() != null) {
clearBufferData();
getConnector().sendData(KenwoodTK90RigConstant.setTrUSDXStreaming(false));
}
}
public void onReceivedWaveData(byte[] data) {
onReceivedWaveData(data, false);
}
public void onReceivedWaveData(byte[] data, boolean force) {
if (data.length == 0) {
return;
}
if (getConnector() == null) {
return;
}
Resample rxResample = new Resample(Resample.ConverterType.SRC_LINEAR, 1, rxSampling, 12000);
rxStreamBuffer.write(data, 0, data.length);
if (rxStreamBuffer.size() >= 256 || force) {
byte[] resampled = rxResample.processCopy(toWaveSamples8To16(rxStreamBuffer.toByteArray()));
rxStreamBuffer.reset();
getConnector().receiveWaveData(resampled);
}
rxResample.close();
}
@Override
public void sendWaveData(Ft8Message message) {
if (getConnector() == null) {
return;
}
float[] wave = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency()
// ,txSampling);
, 24000);
if (wave == null){
setPTT(false);
return;
}
byte[] pcm16 = toWaveFloatToPCM16(wave);
Resample txResample = new Resample(Resample.ConverterType.SRC_SINC_FASTEST, 1, 24000, txSampling);
byte[] resampled = txResample.processCopy(pcm16);
txResample.close();
byte[] pcm8 = toWaveSamples16To8(resampled);
for (int i = 0; i < pcm8.length; i++) {
if (pcm8[i] == 0x3B) pcm8[i] = 0x3A; // ; to :
}
while (pcm8.length > 0) {
if (pcm8.length <= 256) {
getConnector().sendData(pcm8);
break;
} else {
getConnector().sendData(Arrays.copyOfRange(pcm8, 0, 256));
pcm8 = Arrays.copyOfRange(pcm8, 256, pcm8.length);
}
}
}
private static byte[] toWaveSamples8To16(byte[] in) {
ByteBuffer buf = ByteBuffer.allocate(in.length * 2);
for (int i = 0; i < in.length; i++) {
short v = (short)(((short)in[i] - 128) << 8);
buf.putShort(v);
}
return buf.array();
}
private static byte[] toWaveFloatToPCM16(float[] in) {
ByteBuffer buf = ByteBuffer.allocate(in.length * 2);
buf.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < in.length; i++) {
float x = in[i];
short v = (short)(x * 32767.0f);
buf.putShort(v);
}
return buf.array();
}
private static byte[] toWaveFloatToPCM8(float[] in) {
byte[] out = new byte[in.length];
for (int i = 0; i < in.length; i++) {
float x = in[i];
short v = (short)(x * 32767.0f);
out[i] = (byte)((byte)(v >> 8) + 128);
}
return out;
}
private static byte[] toWaveSamples16To8(byte[] in) {
byte[] out = new byte[in.length / 2];
for (int i = 0; i < out.length; i++) {
short v = readShortBigEndianData(in, i * 2);
//short v = (short)(((short)in[i*2] & 0xFF) << 8 | (short)in[i*2+1] & 0xFF);
out[i] = (byte)(((byte)(v >> 8)) + 128);
}
return out;
}
public TrUSDXRig() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (getConnector()!=null){
getConnector().sendData(KenwoodTK90RigConstant.setTS590VFOMode());
getConnector().sendData(KenwoodTK90RigConstant.setTrUSDXStreaming(true));
}
}
},START_QUERY_FREQ_DELAY-500);
readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT);
}
/**
* Short
*
* @param data
* @param start
* @return Int16
*/
public static short readShortBigEndianData(byte[] data, int start) {
if (data.length - start < 2) return 0;
return (short) ((short) data[start] & 0xff
| ((short) data[start + 1] & 0xff) << 8);
}
}