org.refcodes.codec.ModemDecoderImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of refcodes-codec Show documentation
Show all versions of refcodes-codec Show documentation
Artifact with encoding and decoding (not in terms of encryption/decryption)
implementations (codecs) such as BASE64 encoding / decoding.
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// /////////////////////////////////////////////////////////////////////////////
// This code is copyright (c) by Siegfried Steiner, Munich, Germany and licensed
// under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// -----------------------------------------------------------------------------
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// -----------------------------------------------------------------------------
// Apache License, v2.0 ("http://www.apache.org/licenses/LICENSE-2.0")
// -----------------------------------------------------------------------------
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////
package org.refcodes.codec;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.refcodes.component.OpenException;
import org.refcodes.component.Openable;
import org.refcodes.data.LoopSleepTime;
import org.refcodes.io.AbstractByteReceiver;
import org.refcodes.io.AbstractReceiver;
import org.refcodes.io.ByteProvider;
import org.refcodes.io.ByteReceiver;
import org.refcodes.io.ByteReceiverDecorator;
import org.refcodes.io.ShortProvider;
import org.refcodes.io.ShortReceiver;
import org.refcodes.io.ShortReceiverDecorator;
public class ModemDecoderImpl extends AbstractByteReceiver implements ModemDecoder {
// /////////////////////////////////////////////////////////////////////////
// STATICS:
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// CONSTANTS:
// /////////////////////////////////////////////////////////////////////////
private enum SignalStatus {
HIGH, LOW, SILENCE, UNKNOWN
}
private static final int DECODER_DATA_BUFFER_SIZE = 8;
// /////////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////////
protected ModemMetrics _modemMetrics;
protected LinkedBlockingQueue _datagramQueue = new LinkedBlockingQueue( AbstractReceiver.DATAGRAM_QUEUE_SIZE );
protected ByteReceiver _byteReceiver;
protected ShortReceiver _shortReceiver;
private DemodulatorStatus _decoderStatus = DemodulatorStatus.IDLE;
private DemodulatorStatus _decoderPausedStatus = DemodulatorStatus.IDLE;
private ShortBuffer _signalBuffer;
private ShortBuffer _frameBuffer;
private StringBuffer _bitBuffer;
private int _currentBit = 0;
private int _signalEnd = 0;
private int _signalPointer = 0;
private ByteBuffer _dataBuffer;
private int _dataLength = 0;
// /////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS:
// /////////////////////////////////////////////////////////////////////////
public ModemDecoderImpl( ModemMetrics aModemMetrics, ByteProvider aByteProvider ) {
this( aModemMetrics, new ByteReceiverDecorator( aByteProvider ) );
}
public ModemDecoderImpl( ModemMetrics aModemMetrics, ShortProvider aShortProvider ) {
this( aModemMetrics, new ShortReceiverDecorator( aShortProvider ) );
}
public ModemDecoderImpl( ModemMetrics aModemMetrics, ShortReceiver aShortReceiver ) {
this( aModemMetrics );
_shortReceiver = aShortReceiver;
}
public ModemDecoderImpl( ModemMetrics aModemMetrics, ByteReceiver aByteReceiver ) {
this( aModemMetrics );
_byteReceiver = aByteReceiver;
}
protected ModemDecoderImpl( ModemMetrics aModemMetrics ) {
_modemMetrics = aModemMetrics;
if ( !_decoderPausedStatus.equals( DemodulatorStatus.IDLE ) ) {
_decoderStatus = _decoderPausedStatus;
}
else {
_decoderStatus = DemodulatorStatus.SEARCHING_SIGNAL;
}
initSignalBuffer();
initFrameBuffer();
initDataBuffer();
}
// /////////////////////////////////////////////////////////////////////////
// INJECTION:
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc}
*/
@Override
public byte[] readDatagrams() {
return readDatagrams( -1 );
}
public boolean hasDatagram() {
if ( _datagramQueue.size() > 0 ) {
return true;
}
while ( _decoderStatus != DemodulatorStatus.IDLE && _datagramQueue.size() == 0 ) {
synchronized ( _signalBuffer ) {
doDecodeBuffer();
switch ( _decoderStatus ) {
case IDLE:
stop();
break;
case SEARCHING_SIGNAL:
case SEARCHING_START_BIT:
doSearchCycle();
break;
case DECODING:
doDecodeCycle();
break;
}
}
}
return _datagramQueue.size() > 0;
}
/**
* {@inheritDoc}
*/
@Override
public byte[] readDatagrams( int aBlockSize ) {
int size = aBlockSize;
if ( size == -1 ) {
size = _datagramQueue.size();
}
while ( _decoderStatus != DemodulatorStatus.IDLE && _datagramQueue.size() < aBlockSize ) {
synchronized ( _signalBuffer ) {
doDecodeBuffer();
switch ( _decoderStatus ) {
case IDLE:
stop();
break;
case SEARCHING_SIGNAL:
case SEARCHING_START_BIT:
doSearchCycle();
break;
case DECODING:
doDecodeCycle();
break;
}
}
}
if ( size > 0 ) {
byte[] theData = new byte[size];
for ( int i = 0; i < theData.length; i++ ) {
try {
theData[i] = _datagramQueue.take();
}
catch ( InterruptedException ignore ) {}
}
return theData;
}
throw new NoSuchElementException( "No more elements of block size <" + size + "> in queue with size <" + _datagramQueue.size() + ">!" );
}
/**
* {@inheritDoc}
*/
@Override
public byte readDatagram() throws OpenException, InterruptedException {
if ( _datagramQueue.size() > 0 ) {
return _datagramQueue.take();
}
while ( _decoderStatus != DemodulatorStatus.IDLE && _datagramQueue.size() == 0 ) {
synchronized ( _signalBuffer ) {
doDecodeBuffer();
switch ( _decoderStatus ) {
case IDLE:
stop();
break;
case SEARCHING_SIGNAL:
case SEARCHING_START_BIT:
doSearchCycle();
break;
case DECODING:
doDecodeCycle();
break;
}
}
}
if ( _datagramQueue.size() > 0 ) {
return _datagramQueue.take();
}
// return null;
throw new NoSuchElementException( "No more elements in queue with size <" + _datagramQueue.size() + ">!" );
}
/**
* {@inheritDoc}
*/
@Override
public DemodulatorStatus getDemodulatorStatus() {
return _decoderStatus;
}
// /////////////////////////////////////////////////////////////////////////
// HOOKS:
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// DECODER:
// /////////////////////////////////////////////////////////////////////////
// public byte readDatagram() throws OpenException, InterruptedException {
// return _datagramQueue.take();
// }
private int doDecodeBuffer() {
synchronized ( _signalBuffer ) {
short[] theData = null;
short[] theMonoData;
int theBlockSize = _signalBuffer.capacity() - _signalEnd;
if ( theBlockSize <= 0 ) {
theBlockSize = _signalBuffer.capacity() - _signalEnd + _signalPointer;
if ( theBlockSize <= 0 ) {
return 0;
}
}
if ( theBlockSize > _datagramQueue.remainingCapacity() ) {
theBlockSize = _datagramQueue.remainingCapacity();
}
try {
if ( _modemMetrics.getModulationFormat() == ModulationFormat.SHORT && _shortReceiver != null && _shortReceiver.hasDatagram() ) {
theData = _shortReceiver.readDatagrams( theBlockSize );
}
}
catch ( InterruptedException | OpenException e ) {
return 0;
}
switch ( _modemMetrics.getChannelSelector() ) {
case MONO:
theMonoData = theData;
break;
case STEREO:
theMonoData = toMono( theData );
break;
default:
throw new IllegalStateException( "The Modem-Metric's Channel-Selector is not set but must be set to one of the following: " + Arrays.toString( ChannelSelector.values() ) );
}
if ( theMonoData != null ) {
// |--> Buffer nearly full?
if ( _signalEnd + theMonoData.length > _signalBuffer.capacity() ) {
// |--> Remove processed data:
if ( (_signalEnd + theMonoData.length) - _signalPointer <= _signalBuffer.capacity() ) {
purge();
}
// Remove processed data <--|
// |--> Panic, no data accepted:
else {
return (_signalBuffer.capacity() - (_signalEnd + theMonoData.length));
}
// Panic, no data accepted <--|
}
// Buffer nearly full <--|
_signalBuffer.position( _signalEnd );
_signalBuffer.put( theMonoData );
_signalEnd += theMonoData.length;
}
return (_signalBuffer.capacity() - _signalEnd);
}
}
private void clear() {
synchronized ( _signalBuffer ) {
initSignalBuffer();
_signalEnd = 0;
_signalPointer = 0;
_signalBuffer.capacity();
}
}
private void purge() {
if ( _signalPointer <= _signalEnd ) {
short[] currentData = _signalBuffer.array();
short[] remainingData = new short[_signalEnd - _signalPointer];
for ( int i = 0; i < remainingData.length; i++ ) {
remainingData[i] = currentData[_signalPointer + i];
}
initSignalBuffer();
_signalBuffer.put( remainingData );
_signalBuffer.rewind();
_signalPointer = 0;
_signalEnd = remainingData.length;
}
else {
clear();
}
}
private short[] getFrame( int aIndex ) {
_signalBuffer.position( aIndex );
initFrameBuffer();
for ( int j = 0; j < _modemMetrics.toSamplesPerBit(); j++ ) {
_frameBuffer.put( j, _signalBuffer.get() );
}
return _frameBuffer.array();
}
private void flush() {
if ( _dataLength > 0 ) {
byte[] data = new byte[_dataLength];
for ( int i = 0; i < _dataLength; i++ ) {
data[i] = _dataBuffer.get( i );
}
initDataBuffer();
_dataLength = 0;
// _byteSender.writeDatagrams( data );
for ( byte eData : data ) {
try {
_datagramQueue.offer( eData, LoopSleepTime.MAX.getMillis(), TimeUnit.MILLISECONDS );
}
catch ( InterruptedException ignore ) {}
}
}
}
private void doSearchCycle() {
if ( _signalPointer <= _signalEnd - _modemMetrics.toSamplesPerBit() ) {
short[] frameData = getFrame( _signalPointer );
int freq = toFrequencyZeroCrossing( frameData );
SignalStatus state = toState( freq, toRootMeanSquared( frameData ) );
if ( state.equals( SignalStatus.HIGH ) && _decoderStatus.equals( DemodulatorStatus.SEARCHING_SIGNAL ) ) {
// |--> Pre-carrier bit, seek start bit:
nextStatus();
// Pre-carrier bit, seek start bit <--|
}
if ( _decoderStatus.equals( DemodulatorStatus.SEARCHING_START_BIT ) && freq == _modemMetrics.getModemMode().getLowerFrequency() && state.equals( SignalStatus.LOW ) ) {
_signalPointer += (_modemMetrics.toSamplesPerBit() / 2);
nextStatus();
return;
}
_signalPointer++;
}
else {
purge(); // get rid of data that is already processed
flush();
_decoderStatus = DemodulatorStatus.IDLE;
}
}
private void doDecodeCycle() {
if ( _signalPointer <= _signalEnd - _modemMetrics.toSamplesPerBit() ) {
short[] frameData = getFrame( _signalPointer );
double rms = toRootMeanSquared( frameData );
int freq = toFrequencyZeroCrossing( frameData );
SignalStatus state = toState( freq, rms );
// |--> Start bit:
if ( _currentBit == 0 && state.equals( SignalStatus.LOW ) ) {
_bitBuffer = new StringBuffer();
_currentBit++;
}
// Start bit <--|
// |--> Post-carrier bits; seek transmission start:
else if ( _currentBit == 0 && state.equals( SignalStatus.HIGH ) ) {
_decoderStatus = DemodulatorStatus.SEARCHING_START_BIT;
}
// Post-carrier bits; seek transmission start <--|
// |--> End bit:
else if ( _currentBit == 9 && state.equals( SignalStatus.HIGH ) ) {
try {
_dataBuffer.put( (byte) Integer.parseInt( _bitBuffer.toString(), 2 ) );
_dataLength++;
// |--> Data buffer is full, flush and clear:
if ( _dataLength == _dataBuffer.capacity() ) {
flush();
}
// Data buffer is full, flush and clear <--|
}
catch ( Exception e ) {
e.printStackTrace();
}
_currentBit = 0;
}
// End bit <--|
else if ( _currentBit > 0 && _currentBit < 9 && (state.equals( SignalStatus.HIGH ) || state.equals( SignalStatus.LOW )) ) {
_bitBuffer.insert( 0, (state.equals( SignalStatus.HIGH ) ? 1 : 0) );
_currentBit++;
}
else {
// corrupted data, clear bit buffer
_bitBuffer = new StringBuffer();
_currentBit = 0;
_decoderStatus = DemodulatorStatus.SEARCHING_START_BIT;
}
_signalPointer += _modemMetrics.toSamplesPerBit();
}
else {
purge(); // get rid of data that is already processed
flush();
_decoderStatus = DemodulatorStatus.IDLE;
_decoderPausedStatus = DemodulatorStatus.DECODING;
}
}
// /////////////////////////////////////////////////////////////////////
// HELPER:
// /////////////////////////////////////////////////////////////////////
private void initSignalBuffer() {
_signalBuffer = ShortBuffer.allocate( _modemMetrics.getSampleRate().getValue() );
}
private void initFrameBuffer() {
_frameBuffer = ShortBuffer.allocate( _modemMetrics.toSamplesPerBit() );
}
private void initDataBuffer() {
_dataBuffer = ByteBuffer.allocate( DECODER_DATA_BUFFER_SIZE );
}
// /////////////////////////////////////////////////////////////////////////
// HELPER:
// /////////////////////////////////////////////////////////////////////////
private void stop() {}
private void nextStatus() {
switch ( _decoderStatus ) {
case IDLE:
_decoderStatus = DemodulatorStatus.SEARCHING_SIGNAL;
break;
case SEARCHING_SIGNAL:
_decoderStatus = DemodulatorStatus.SEARCHING_START_BIT;
break;
case SEARCHING_START_BIT:
_decoderStatus = DemodulatorStatus.DECODING;
break;
case DECODING:
_decoderStatus = DemodulatorStatus.IDLE;
break;
}
}
private short[] toMono( short[] aData ) {
short[] theMonoData = new short[aData.length / 2];
for ( int i = 0; i < aData.length - 1; i += 2 ) {
int theAverage = ((int) (aData[i] + aData[i + 1])) / 2;
theMonoData[i / 2] = (short) theAverage;
}
return theMonoData;
}
// @formatter:off
/*
public int appendSignal( byte[] data ) throws OpenException {
return appendSignal( NumericalUtility.toShorts( data ) );
}
public int setSignal( byte[] data ) throws OpenException {
allocateBufferData(); // reset data buffer
clearSignal();
return appendSignal( NumericalUtility.toShorts( data ) );
}
public int setSignal( short[] data ) throws OpenException {
allocateBufferData(); // reset data buffer
clearSignal();
return appendSignal( data );
}
*/
// @formatter:on
private int toFrequencyZeroCrossing( short[] aData ) {
int theSamples = aData.length;
int theCrossing = 0;
for ( int i = 0; i < theSamples - 1; i++ ) {
if ( (aData[i] > 0 && aData[i + 1] <= 0) || (aData[i] < 0 && aData[i + 1] >= 0) ) {
theCrossing++;
}
}
double theSecondsRecorded = (double) theSamples / (double) _modemMetrics.getSampleRate().getValue();
double theCycles = theCrossing / 2;
double theFrequency = theCycles / theSecondsRecorded;
return (int) Math.round( theFrequency );
}
private double toRootMeanSquared( short[] aData ) {
double theMillisecs = 0;
for ( int i = 0; i < aData.length; i++ ) {
theMillisecs += aData[i] * aData[i];
}
theMillisecs /= aData.length;
return Math.sqrt( theMillisecs );
}
private SignalStatus toState( int frequency, double rms ) {
SignalStatus state = SignalStatus.UNKNOWN;
if ( rms <= _modemMetrics.getModulationFormat().getSilenceThreshold() ) {
state = SignalStatus.SILENCE;
}
else if ( frequency <= _modemMetrics.toLowerFrequencyUpperThreshold() ) {
state = SignalStatus.LOW;
}
else if ( frequency <= _modemMetrics.toHigherFrequencyUpperThreshold() ) {
state = SignalStatus.HIGH;
}
return state;
}
// /////////////////////////////////////////////////////////////////////////
// INNER CLASSES:
// /////////////////////////////////////////////////////////////////////////
/**
* Vanilla plain implementation of the {@link ModemByteDecoderProvider}
* interface to be used with {@link ByteProvider} ({@link ByteReceiver})
* instances.
*/
public static class ModemByteDecoderProviderImpl extends ModemDecoderImpl implements ModemByteDecoderProvider {
// /////////////////////////////////////////////////////////////////////
// CONSTRUCTORS:
// /////////////////////////////////////////////////////////////////////
public ModemByteDecoderProviderImpl( ModemMetrics aModemMetrics ) {
super( aModemMetrics );
}
// /////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////
@Override
public void open( ByteProvider aConnection ) throws OpenException {
if ( _modemMetrics.getModulationFormat() != ModulationFormat.BYTE ) {
throw new IllegalArgumentException( "When configuring a modulation format other than <" + ModulationFormat.BYTE + ">, then you must not pass a Byte-Provider." );
}
if ( aConnection instanceof ByteReceiver ) {
_byteReceiver = (ByteReceiver) aConnection;
}
else {
_byteReceiver = new ByteReceiverDecorator( aConnection );
}
if ( !_byteReceiver.isOpened() ) {
if ( _byteReceiver instanceof Openable ) {
((Openable) _byteReceiver).open();
}
else {
throw new OpenException( "The provided connection is in status <" + _byteReceiver.getConnectionStatus() + "> but does not provide the <" + Openable.class.getName() + "> interface." );
}
}
open();
}
}
/**
* Vanilla plain implementation of the {@link ModemByteDecoderProvider}
* interface to be used with {@link ByteProvider} ({@link ByteReceiver})
* instances.
*/
public static class ModemShortDecoderProviderImpl extends ModemDecoderImpl implements ModemShortDecoderProvider {
// /////////////////////////////////////////////////////////////////////
// CONSTRUCTORS:
// /////////////////////////////////////////////////////////////////////
public ModemShortDecoderProviderImpl( ModemMetrics aModemMetrics ) {
super( aModemMetrics );
}
// /////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////
@Override
public void open( ShortProvider aConnection ) throws OpenException {
if ( _modemMetrics.getModulationFormat() != ModulationFormat.SHORT ) {
throw new IllegalArgumentException( "When configuring a modulation format other than <" + ModulationFormat.SHORT + ">, then you must not pass a Short-Provider." );
}
if ( aConnection instanceof ShortReceiver ) {
_shortReceiver = (ShortReceiver) aConnection;
}
else {
_shortReceiver = new ShortReceiverDecorator( aConnection );
}
if ( !_shortReceiver.isOpened() ) {
if ( _shortReceiver instanceof Openable ) {
((Openable) _shortReceiver).open();
}
else {
throw new OpenException( "The provided connection is in status <" + _shortReceiver.getConnectionStatus() + "> but does not provide the <" + Openable.class.getName() + "> interface." );
}
}
open();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy