package com.radio.codec2talkie.protocol; import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; import android.util.Log; import com.radio.codec2talkie.MainActivity; import com.radio.codec2talkie.R; import com.radio.codec2talkie.protocol.message.TextMessage; import com.radio.codec2talkie.protocol.position.Position; import com.radio.codec2talkie.settings.PreferenceKeys; import com.radio.codec2talkie.tools.AudioTools; import com.radio.codec2talkie.tools.StorageTools; import com.radio.codec2talkie.transport.Transport; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; public class Recorder implements Protocol { private static final String TAG = MainActivity.class.getSimpleName(); private static final int ROTATION_DELAY_MS = 5000; private File _storage; private FileOutputStream _activeStream; private Timer _fileRotationTimer; final Protocol _childProtocol; int _codec2ModeId; private String _prevSrcCallsign; private String _prevDstCallsign; private final SharedPreferences _sharedPreferences; private ProtocolCallback _parentProtocolCallback; public Recorder(Protocol childProtocol, SharedPreferences sharedPreferences) { _childProtocol = childProtocol; _sharedPreferences = sharedPreferences; _prevSrcCallsign = null; _prevDstCallsign = null; } @Override public void initialize(Transport transport, Context context, ProtocolCallback protocolCallback) throws IOException { _parentProtocolCallback = protocolCallback; _storage = StorageTools.getStorage(context); _childProtocol.initialize(transport, context, _protocolCallback); String codec2ModeName = _sharedPreferences.getString(PreferenceKeys.CODEC2_MODE, context.getResources().getStringArray(R.array.codec2_modes)[0]); _codec2ModeId = AudioTools.extractCodec2ModeId(codec2ModeName); } @Override public int getPcmAudioRecordBufferSize() { return _childProtocol.getPcmAudioRecordBufferSize(); } @Override public void sendCompressedAudio(String src, String dst, int codec2Mode, byte[] frame) throws IOException { rotateIfNewSrcOrDstCallsign(src, dst); writeToFile(src, dst, codec2Mode, frame); _childProtocol.sendCompressedAudio(src, dst, codec2Mode, frame); } @Override public void sendTextMessage(TextMessage textMessage) throws IOException { _childProtocol.sendTextMessage(textMessage); } @Override public void sendPcmAudio(String src, String dst, int codec, short[] pcmFrame) throws IOException { _childProtocol.sendPcmAudio(src, dst, codec, pcmFrame); } @Override public void sendData(String src, String dst, String path, byte[] dataPacket) throws IOException { _childProtocol.sendData(src, dst, path, dataPacket); } @Override public boolean receive() throws IOException { return _childProtocol.receive(); } ProtocolCallback _protocolCallback = new ProtocolCallback() { @Override protected void onReceivePosition(Position position) { _parentProtocolCallback.onReceivePosition(position); } @Override protected void onReceivePcmAudio(String src, String dst, int codec, short[] pcmFrame) { _parentProtocolCallback.onReceivePcmAudio(src, dst, codec, pcmFrame); } @Override protected void onReceiveCompressedAudio(String src, String dst, int codec2Mode, byte[] audioFrames) { rotateIfNewSrcOrDstCallsign(src, dst); _parentProtocolCallback.onReceiveCompressedAudio(src, dst, codec2Mode, audioFrames); writeToFile(src, dst, codec2Mode, audioFrames); } @Override protected void onReceiveTextMessage(TextMessage textMessage) { _parentProtocolCallback.onReceiveTextMessage(textMessage); } @Override protected void onReceiveData(String src, String dst, String path, byte[] data) { _parentProtocolCallback.onReceiveData(src, dst, path, data); } @Override protected void onReceiveSignalLevel(short rssi, short snr) { _parentProtocolCallback.onReceiveSignalLevel(rssi, snr); } @Override protected void onReceiveTelemetry(int batVoltage) { _parentProtocolCallback.onReceiveTelemetry(batVoltage); } @Override protected void onReceiveLog(String logData) { _parentProtocolCallback.onReceiveLog(logData); } @Override protected void onTransmitPcmAudio(String src, String dst, int codec, short[] frame) { _parentProtocolCallback.onTransmitPcmAudio(src, dst, codec, frame); } @Override protected void onTransmitCompressedAudio(String src, String dst, int codec, byte[] frame) { _parentProtocolCallback.onTransmitCompressedAudio(src, dst, codec, frame); } @Override protected void onTransmitTextMessage(TextMessage textMessage) { _parentProtocolCallback.onTransmitTextMessage(textMessage); } @Override protected void onTransmitPosition(Position position) { _parentProtocolCallback.onTransmitPosition(position); } @Override protected void onTransmitData(String src, String dst, String path, byte[] data) { _parentProtocolCallback.onTransmitData(src, dst, path, data); } @Override protected void onTransmitLog(String logData) { _parentProtocolCallback.onTransmitLog(logData); } @Override protected void onProtocolRxError() { _parentProtocolCallback.onProtocolRxError(); } @Override protected void onProtocolTxError() { _parentProtocolCallback.onProtocolTxError(); } }; @Override public void sendPosition(Position position) throws IOException { _childProtocol.sendPosition(position); } @Override public void flush() throws IOException { _childProtocol.flush(); } @Override public void close() { _childProtocol.close(); } private void writeToFile(String src, String dst, int codec2Mode, byte[] rawData) { stopRotationTimer(); createStreamIfNotExists(src, dst, codec2Mode); writeToStream(rawData); startRotationTimer(); } private void writeToStream(byte[] rawData) { try { if (_activeStream != null) { _activeStream.write(rawData); } } catch (IOException e) { e.printStackTrace(); } } private void createStreamIfNotExists(String src, String dst, int codec2Mode) { if (_activeStream == null) { try { Date date = new Date(); File newDirectory = new File(_storage, getNewDirectoryName(date)); if (!newDirectory.exists() && !newDirectory.mkdirs()) { Log.e(TAG, "Failed to create directory for voicemails"); } File newAudioFile = new File(newDirectory, getNewFileName(date, src, dst, codec2Mode)); _activeStream = new FileOutputStream(newAudioFile); } catch (FileNotFoundException e) { e.printStackTrace(); } } } private String getNewDirectoryName(Date date) { SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd", Locale.US); return df.format(date); } private String getNewFileName(Date date, String src, String dst, int codec2Mode) { int mode = codec2Mode; if (mode == -1) { mode = _codec2ModeId; } SimpleDateFormat tf = new SimpleDateFormat("HHmmss", Locale.ENGLISH); String codec2mode = String.format(Locale.ENGLISH, "%02d", mode); String fileName = codec2mode + "_" + tf.format(date); if (src != null && dst != null) { fileName += "_" + src + "_" + dst; } fileName += ".c2"; return fileName; } private void rotateIfNewSrcOrDstCallsign(String newSrcCallsign, String newDstCallsign) { if (!TextUtils.equals(_prevSrcCallsign, newSrcCallsign) || !TextUtils.equals(_prevDstCallsign, newDstCallsign)) { _prevSrcCallsign = newSrcCallsign; _prevDstCallsign = newDstCallsign; if (_activeStream != null) { try { _activeStream.close(); } catch (IOException e) { e.printStackTrace(); } _activeStream = null; } } } private void startRotationTimer() { _fileRotationTimer = new Timer(); _fileRotationTimer.schedule(new TimerTask() { @Override public void run() { if (_activeStream != null) { try { _activeStream.close(); } catch (IOException e) { e.printStackTrace(); } } _activeStream = null; } }, ROTATION_DELAY_MS); } private void stopRotationTimer() { try { if (_fileRotationTimer != null) { _fileRotationTimer.cancel(); _fileRotationTimer.purge(); } } catch (IllegalStateException ignored) {} } }