Skip to content

Commit 08cd92a

Browse files
committed
Add BT disconnect callback and split frames in player
1 parent 9b83574 commit 08cd92a

File tree

3 files changed

+110
-69
lines changed

3 files changed

+110
-69
lines changed

codec2talkie/src/main/java/com/radio/codec2talkie/Codec2Player.java

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package com.radio.codec2talkie;
22

3+
import android.bluetooth.BluetoothAdapter;
4+
import android.bluetooth.BluetoothDevice;
35
import android.bluetooth.BluetoothSocket;
6+
import android.content.BroadcastReceiver;
7+
import android.content.Context;
8+
import android.content.Intent;
9+
import android.content.IntentFilter;
410
import android.media.AudioAttributes;
511
import android.media.AudioFormat;
612
import android.media.AudioRecord;
713
import android.media.AudioTrack;
814
import android.media.MediaRecorder;
915
import android.os.Handler;
1016
import android.os.Message;
17+
import android.util.Log;
1118

1219
import java.io.IOException;
1320
import java.io.InputStream;
@@ -36,6 +43,7 @@ public class Codec2Player extends Thread {
3643

3744
private final int AUDIO_SAMPLE_SIZE = 8000;
3845
private final int SLEEP_IDLE_DELAY_MS = 20;
46+
private final int POST_PLAY_DELAY_MS = 1000;
3947

4048
private final int RX_TIMEOUT = 100;
4149
private final int TX_TIMEOUT = 2000;
@@ -52,7 +60,9 @@ public class Codec2Player extends Thread {
5260
private UsbSerialPort _usbPort;
5361

5462
private int _audioBufferSize;
63+
private int _audioEncodedBufferSize;
5564

65+
private boolean _isRunning = true;
5666
private boolean _isRecording = false;
5767
private int _currentStatus = PLAYER_DISCONNECT;
5868

@@ -96,7 +106,7 @@ public Codec2Player(Handler onPlayerStateChanged, int codec2Mode) {
96106
AUDIO_SAMPLE_SIZE,
97107
AudioFormat.CHANNEL_IN_MONO,
98108
AudioFormat.ENCODING_PCM_16BIT,
99-
3 * _audioRecorderMinBufferSize);
109+
10 * _audioRecorderMinBufferSize);
100110
_audioRecorder.startRecording();
101111

102112
int _audioPlayerMinBufferSize = AudioTrack.getMinBufferSize(
@@ -106,15 +116,15 @@ public Codec2Player(Handler onPlayerStateChanged, int codec2Mode) {
106116
_audioPlayer = new AudioTrack.Builder()
107117
.setAudioAttributes(new AudioAttributes.Builder()
108118
.setUsage(AudioAttributes.USAGE_MEDIA)
109-
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
119+
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
110120
.build())
111121
.setAudioFormat(new AudioFormat.Builder()
112122
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
113123
.setSampleRate(AUDIO_SAMPLE_SIZE)
114124
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
115125
.build())
116126
.setTransferMode(AudioTrack.MODE_STREAM)
117-
.setBufferSizeInBytes(3 * _audioPlayerMinBufferSize)
127+
.setBufferSizeInBytes(10 * _audioPlayerMinBufferSize)
118128
.build();
119129
_audioPlayer.play();
120130
}
@@ -154,11 +164,15 @@ public void startRecording() {
154164
_isRecording = true;
155165
}
156166

167+
public void stopRunning() {
168+
_isRunning = false;
169+
}
170+
157171
private void setCodecModeInternal(int codecMode) {
158172
_codec2Con = Codec2.create(codecMode);
159173

160174
_audioBufferSize = Codec2.getSamplesPerFrame(_codec2Con);
161-
int _audioEncodedBufferSize = Codec2.getBitsSize(_codec2Con); // returns number of bytes
175+
_audioEncodedBufferSize = Codec2.getBitsSize(_codec2Con); // returns number of bytes
162176

163177
_recordAudioBuffer = new short[_audioBufferSize];
164178
_recordAudioEncodedBuffer = new char[_audioEncodedBufferSize];
@@ -167,12 +181,7 @@ private void setCodecModeInternal(int codecMode) {
167181

168182
_loopbackBuffer = ByteBuffer.allocateDirect(1024 * _audioEncodedBufferSize);
169183

170-
_kissProcessor = new KissProcessor(
171-
_audioEncodedBufferSize,
172-
CSMA_PERSISTENCE,
173-
CSMA_SLOT_TIME,
174-
TX_TAIL_10MS_UNITS,
175-
_kissCallback);
184+
_kissProcessor = new KissProcessor(CSMA_PERSISTENCE, CSMA_SLOT_TIME, TX_TAIL_10MS_UNITS, _kissCallback);
176185
}
177186

178187
private final KissCallback _kissCallback = new KissCallback() {
@@ -182,8 +191,14 @@ protected void onSend(byte[] data) throws IOException {
182191
}
183192

184193
@Override
185-
protected void onReceive(byte[] audioData) {
186-
decodeAndPlayAudio(audioData);
194+
protected void onReceive(byte[] data) {
195+
// split by audio frame and play
196+
byte [] audioFrame = new byte[_audioEncodedBufferSize];
197+
for (int i = 0; i < data.length; i += _audioEncodedBufferSize) {
198+
for (int j = 0; j < _audioEncodedBufferSize; j++)
199+
audioFrame[j] = data[i + j];
200+
decodeAndPlayAudio(audioFrame);
201+
}
187202
}
188203
};
189204

@@ -197,16 +212,16 @@ private void sendRawDataToModem(byte[] data) throws IOException {
197212
} else {
198213
if (_btOutputStream != null)
199214
_btOutputStream.write(data);
200-
if (_usbPort != null) {
215+
else if (_usbPort != null) {
201216
_usbPort.write(data, TX_TIMEOUT);
202217
}
203218
}
204219
}
205220

206221
private void decodeAndPlayAudio(byte[] data) {
207-
notifyAudioLevel(_playbackAudioBuffer, false);
208222
Codec2.decode(_codec2Con, _playbackAudioBuffer, data);
209223
_audioPlayer.write(_playbackAudioBuffer, 0, _audioBufferSize);
224+
notifyAudioLevel(_playbackAudioBuffer, false);
210225
}
211226

212227
private void notifyAudioLevel(short [] pcmAudioSamples, boolean isTx) {
@@ -240,9 +255,10 @@ private boolean processLoopbackPlayback() {
240255
}
241256

242257
private void recordAudio() throws IOException {
258+
setStatus(PLAYER_RECORDING, 0);
259+
notifyAudioLevel(_recordAudioBuffer, true);
243260
_audioRecorder.read(_recordAudioBuffer, 0, _audioBufferSize);
244261
Codec2.encode(_codec2Con, _recordAudioBuffer, _recordAudioEncodedBuffer);
245-
notifyAudioLevel(_recordAudioBuffer, true);
246262

247263
byte [] frame = new byte[_recordAudioEncodedBuffer.length];
248264

@@ -263,10 +279,11 @@ private boolean playAudio() throws IOException {
263279
bytesRead = _btInputStream.read(_rxDataBuffer);
264280
}
265281
}
266-
if (_usbPort != null) {
282+
else if (_usbPort != null) {
267283
bytesRead = _usbPort.read(_rxDataBuffer, RX_TIMEOUT);
268284
}
269285
if (bytesRead > 0) {
286+
setStatus(PLAYER_PLAYING, 0);
270287
_kissProcessor.receive(Arrays.copyOf(_rxDataBuffer, bytesRead));
271288
return true;
272289
}
@@ -315,42 +332,40 @@ private void cleanup() {
315332
Codec2.destroy(_codec2Con);
316333
}
317334

318-
private void setStatus(int status) {
335+
private void setStatus(int status, int delayMs) {
319336
if (status != _currentStatus) {
320337
_currentStatus = status;
321338
Message msg = Message.obtain();
322339
msg.what = status;
323-
_onPlayerStateChanged.sendMessage(msg);
340+
_onPlayerStateChanged.sendMessageDelayed(msg, delayMs);
324341
}
325342
}
326343

327344
@Override
328345
public void run() {
329346
setPriority(Thread.MAX_PRIORITY);
330347
try {
348+
setStatus(PLAYER_LISTENING, 0);
331349
if (!_isLoopbackMode) {
332350
_kissProcessor.initialize();
333351
}
334-
while (true) {
352+
while (_isRunning) {
335353
processRecordPlaybackToggle();
336354

337355
// recording
338356
if (_audioRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
339357
recordAudio();
340-
setStatus(PLAYER_RECORDING);
341358
} else {
342359
// playback
343-
if (playAudio()) {
344-
setStatus(PLAYER_PLAYING);
345-
// idling
346-
} else {
360+
if (!playAudio()) {
361+
// idling
347362
try {
348-
Thread.sleep(SLEEP_IDLE_DELAY_MS);
349363
if (_currentStatus != PLAYER_LISTENING) {
350364
notifyAudioLevel(null, false);
351365
notifyAudioLevel(null, true);
352366
}
353-
setStatus(PLAYER_LISTENING);
367+
setStatus(PLAYER_LISTENING, POST_PLAY_DELAY_MS);
368+
Thread.sleep(SLEEP_IDLE_DELAY_MS);
354369
} catch (InterruptedException e) {
355370
e.printStackTrace();
356371
}
@@ -361,7 +376,7 @@ public void run() {
361376
e.printStackTrace();
362377
}
363378

379+
setStatus(PLAYER_DISCONNECT, 0);
364380
cleanup();
365-
setStatus(PLAYER_DISCONNECT);
366381
}
367382
}

codec2talkie/src/main/java/com/radio/codec2talkie/MainActivity.java

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
import android.Manifest;
88
import android.annotation.SuppressLint;
9+
import android.bluetooth.BluetoothDevice;
10+
import android.content.BroadcastReceiver;
11+
import android.content.Context;
912
import android.content.Intent;
13+
import android.content.IntentFilter;
1014
import android.content.pm.PackageManager;
1115
import android.graphics.Color;
1216
import android.graphics.PorterDuff;
@@ -55,6 +59,7 @@ public class MainActivity extends AppCompatActivity {
5559
private Spinner _spinnerCodec2Mode;
5660
private ProgressBar _progressRxLevel;
5761
private ProgressBar _progressTxLevel;
62+
private CheckBox _checkBoxLoopback;
5863

5964
private Codec2Player _codec2Player;
6065

@@ -78,8 +83,10 @@ protected void onCreate(Bundle savedInstanceState) {
7883
_spinnerCodec2Mode.setSelection(CODEC2_DEFAULT_MODE_POS);
7984
_spinnerCodec2Mode.setOnItemSelectedListener(onCodecModeSelectedListener);
8085

81-
CheckBox checkBoxLoopback = findViewById(R.id.checkBoxLoopback);
82-
checkBoxLoopback.setOnCheckedChangeListener(onLoopbackCheckedChangeListener);
86+
_checkBoxLoopback = findViewById(R.id.checkBoxLoopback);
87+
_checkBoxLoopback.setOnCheckedChangeListener(onLoopbackCheckedChangeListener);
88+
89+
registerReceiver(onBluetoothDisconnected, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
8390

8491
if (requestPermissions()) {
8592
startUsbConnectActivity();
@@ -147,6 +154,15 @@ public void onNothingSelected(AdapterView<?> parent) {
147154
}
148155
};
149156

157+
private final BroadcastReceiver onBluetoothDisconnected = new BroadcastReceiver() {
158+
@Override
159+
public void onReceive(Context context, Intent intent) {
160+
if (_codec2Player != null && SocketHandler.getSocket() != null) {
161+
_codec2Player.stopRunning();
162+
}
163+
}
164+
};
165+
150166
private final View.OnTouchListener onBtnPttTouchListener = new View.OnTouchListener() {
151167
@Override
152168
public boolean onTouch(View v, MotionEvent event) {
@@ -190,7 +206,8 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in
190206
@Override
191207
public void handleMessage(Message msg) {
192208
if (msg.what == Codec2Player.PLAYER_DISCONNECT) {
193-
_textStatus.setText("DISC");
209+
_textStatus.setText("STOP");
210+
_checkBoxLoopback.setChecked(false);
194211
Toast.makeText(getBaseContext(), "Disconnected from modem", Toast.LENGTH_SHORT).show();
195212
startUsbConnectActivity();
196213
}
@@ -214,6 +231,17 @@ else if (msg.what == Codec2Player.PLAYER_TX_LEVEL) {
214231
}
215232
};
216233

234+
private void startPlayer(boolean isUsb) throws IOException {
235+
_codec2Player = new Codec2Player(onPlayerStateChanged, CODEC2_DEFAULT_MODE);
236+
if (isUsb) {
237+
_codec2Player.setUsbPort(UsbPortHandler.getPort());
238+
} else {
239+
_codec2Player.setSocket(SocketHandler.getSocket());
240+
}
241+
_spinnerCodec2Mode.setSelection(CODEC2_DEFAULT_MODE_POS);
242+
_codec2Player.start();
243+
}
244+
217245
@Override
218246
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
219247
super.onActivityResult(requestCode, resultCode, data);
@@ -222,25 +250,23 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
222250
finish();
223251
} else if (resultCode == RESULT_OK) {
224252
_textConnInfo.setText(data.getStringExtra("name"));
225-
_codec2Player = new Codec2Player(onPlayerStateChanged, CODEC2_DEFAULT_MODE);
226253
try {
227-
_codec2Player.setSocket(SocketHandler.getSocket());
254+
startPlayer(false);
228255
} catch (IOException e) {
229256
e.printStackTrace();
230257
}
231-
_spinnerCodec2Mode.setSelection(CODEC2_DEFAULT_MODE_POS);
232-
_codec2Player.start();
233258
}
234259
}
235260
if (requestCode == REQUEST_CONNECT_USB) {
236261
if (resultCode == RESULT_CANCELED) {
237262
startBluetoothConnectActivity();
238263
} else if (resultCode == RESULT_OK) {
239264
_textConnInfo.setText(data.getStringExtra("name"));
240-
_codec2Player = new Codec2Player(onPlayerStateChanged, CODEC2_DEFAULT_MODE);
241-
_codec2Player.setUsbPort(UsbPortHandler.getPort());
242-
_spinnerCodec2Mode.setSelection(CODEC2_DEFAULT_MODE_POS);
243-
_codec2Player.start();
265+
try {
266+
startPlayer(true);
267+
} catch (IOException e) {
268+
e.printStackTrace();
269+
}
244270
}
245271
}
246272
}

0 commit comments

Comments
 (0)