diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/DibitToByteBufferAssembler.java b/src/main/java/io/github/dsheirer/dsp/symbol/DibitToByteBufferAssembler.java
new file mode 100644
index 00000000..edc34e28
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/symbol/DibitToByteBufferAssembler.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * sdr-trunk
+ * Copyright (C) 2014-2018 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see
+ *
+ ******************************************************************************/
+package io.github.dsheirer.dsp.symbol;
+
+import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
+import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
+import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Assembles reusable byte buffers from an incoming stream of Dibits.
+ */
+public class DibitToByteBufferAssembler implements Listener, IReusableByteBufferProvider
+{
+ private final static Logger mLog = LoggerFactory.getLogger(DibitToByteBufferAssembler.class);
+
+ private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("DibitToByteBufferAssembler");
+ private ReusableByteBuffer mCurrentBuffer;
+ private int mBufferPointer;
+ private int mBufferSize;
+ private byte mCurrentByte;
+ private int mDibitCount;
+ private Listener mBufferListener;
+
+ /**
+ * Constructs an assembler to produce reusable byte buffers of the specified size
+ *
+ * @param bufferSize
+ */
+ public DibitToByteBufferAssembler(int bufferSize)
+ {
+ mBufferSize = bufferSize;
+ getNextBuffer();
+ }
+
+ /**
+ * Broadcasts the current buffer to the registered listener and creates a new buffer, resetting
+ * the buffer pointer to zero so that new dibits can be loaded.
+ */
+ private void getNextBuffer()
+ {
+ if(mCurrentBuffer != null && mBufferListener != null)
+ {
+ mBufferListener.receive(mCurrentBuffer);
+ }
+
+ mCurrentBuffer = mBufferQueue.getBuffer(mBufferSize);
+ mBufferPointer = 0;
+ }
+
+ @Override
+ public void receive(Dibit dibit)
+ {
+ mCurrentByte <<= 2;
+
+ switch(dibit)
+ {
+ case D01_PLUS_3:
+ mCurrentByte |= 0x01;
+ break;
+ case D00_PLUS_1:
+ //no-op - value is already 00
+ break;
+ case D10_MINUS_1:
+ mCurrentByte |= 0x02;
+ break;
+ case D11_MINUS_3:
+ mCurrentByte |= 0x03;
+ break;
+ }
+
+ mDibitCount++;
+
+ if(mDibitCount >= 4)
+ {
+ mCurrentBuffer.getBytes()[mBufferPointer++] = mCurrentByte;
+ mCurrentByte = 0x00;
+ mDibitCount = 0;
+
+ if(mBufferPointer >= mBufferSize)
+ {
+ getNextBuffer();
+ }
+ }
+ }
+
+ /**
+ * Registers the listener to receive fully assembled byte buffers from this assembler.
+ */
+ @Override
+ public void setBufferListener(Listener listener)
+ {
+ mBufferListener = listener;
+ }
+
+ /**
+ * Removes the listener from receiving buffers from this assembler
+ */
+ @Override
+ public void removeBufferListener(Listener listener)
+ {
+ mBufferListener = null;
+ }
+
+ @Override
+ public boolean hasBufferListeners()
+ {
+ return mBufferListener != null;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
index 0218aed5..7a53ce4d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
+++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
@@ -476,9 +476,6 @@ public class DecoderFactory
*/
public static EnumSet getBitstreamDecoders()
{
-// return EnumSet.of(DecoderType.P25_PHASE1);
-
- //None of the decoders currently support bitstream output.
- return EnumSet.noneOf(DecoderType.class);
+ return EnumSet.of(DecoderType.P25_PHASE1);
}
}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25Decoder.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25Decoder.java
index 0fe9f4a1..786bf482 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/P25Decoder.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25Decoder.java
@@ -16,19 +16,26 @@
package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.alias.AliasList;
+import io.github.dsheirer.dsp.symbol.Dibit;
+import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
+import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
+import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
+import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.ISourceEventProvider;
import io.github.dsheirer.source.SourceEvent;
public abstract class P25Decoder extends Decoder implements ISourceEventListener, ISourceEventProvider,
- IReusableComplexBufferListener, Listener
+ IReusableComplexBufferListener, Listener, IReusableByteBufferProvider
{
private double mSampleRate;
+ private Broadcaster mDibitBroadcaster = new Broadcaster<>();
+ private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300);
private P25MessageProcessor mMessageProcessor;
private AliasList mAliasList;
private Listener mSourceEventListener;
@@ -40,6 +47,42 @@ public abstract class P25Decoder extends Decoder implements ISourceEventListener
mAliasList = aliasList;
mMessageProcessor = new P25MessageProcessor(mAliasList);
mMessageProcessor.setMessageListener(getMessageListener());
+ mDibitBroadcaster.addListener(mByteBufferAssembler);
+ }
+
+ /**
+ * Assembler for packaging Dibit stream into reusable byte buffers.
+ */
+ protected Broadcaster getDibitBroadcaster()
+ {
+ return mDibitBroadcaster;
+ }
+
+ /**
+ * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
+ */
+ @Override
+ public void setBufferListener(Listener listener)
+ {
+ mByteBufferAssembler.setBufferListener(listener);
+ }
+
+ /**
+ * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
+ */
+ @Override
+ public void removeBufferListener(Listener listener)
+ {
+ mByteBufferAssembler.removeBufferListener(listener);
+ }
+
+ /**
+ * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
+ */
+ @Override
+ public boolean hasBufferListeners()
+ {
+ return mByteBufferAssembler.hasBufferListeners();
}
protected double getSymbolRate()
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FM.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FM.java
index a67eeed5..b9a9cb4d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FM.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FM.java
@@ -71,12 +71,20 @@ public class P25DecoderC4FM extends P25Decoder
mQPSKDemodulator = new DQPSKDecisionDirectedDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
+ if(mMessageFramer != null)
+ {
+ getDibitBroadcaster().removeListener(mMessageFramer);
+ mMessageFramer.dispose();
+ }
+
//The Costas Loop receives symbol-inversion correction requests when detected.
//The PLL gain monitor receives sync detect/loss signals from the message framer
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop, mPLLGainMonitor);
mMessageFramer.setListener(getMessageProcessor());
mMessageFramer.setSampleRate(sampleRate);
- mQPSKDemodulator.setSymbolListener(mMessageFramer);
+
+ mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
+ getDibitBroadcaster().addListener(mMessageFramer);
}
/**
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java
index c69a44e6..ea3ff3d8 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java
@@ -78,7 +78,8 @@ public class P25DecoderC4FMInstrumented extends P25DecoderC4FM
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
- instrumented.setSymbolListener(mMessageFramer);
+ instrumented.setSymbolListener(getDibitBroadcaster());
+ getDibitBroadcaster().addListener(mMessageFramer);
}
public void setComplexSymbolListener(Listener listener)
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java
index 4f2356dd..be99d126 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java
@@ -75,10 +75,17 @@ public class P25DecoderLSM extends P25Decoder
//The Costas Loop receives symbol-inversion correction requests when detected.
//The PLL gain monitor receives sync detect/loss signals from the message framer
+ if(mMessageFramer != null)
+ {
+ getDibitBroadcaster().removeListener(mMessageFramer);
+ mMessageFramer.dispose();
+ }
+
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop, mPLLGainMonitor);
mMessageFramer.setListener(getMessageProcessor());
mMessageFramer.setSampleRate(sampleRate);
- mQPSKDemodulator.setSymbolListener(mMessageFramer);
+ mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
+ getDibitBroadcaster().addListener(mMessageFramer);
}
/**
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java
index 7997ae9f..8099d31e 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java
@@ -79,7 +79,8 @@ public class P25DecoderLSMInstrumented extends P25DecoderLSM
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
- instrumented.setSymbolListener(mMessageFramer);
+ instrumented.setSymbolListener(getDibitBroadcaster());
+ getDibitBroadcaster().addListener(mMessageFramer);
}
public void setComplexSymbolListener(Listener listener)
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer.java
index 4f6579b1..54e00ea9 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer.java
@@ -35,9 +35,6 @@ import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.ArrayList;
-import java.util.List;
-
public class P25MessageFramer implements Listener
{
private final static Logger mLog = LoggerFactory.getLogger(P25MessageFramer.class);
@@ -77,7 +74,7 @@ public class P25MessageFramer implements Listener
private PLLPhaseInversionDetector mInversionDetector180;
private MultiSyncPatternMatcher mMatcher;
- private List mAssemblers = new ArrayList();
+ private P25MessageAssembler mMessageAssembler = new P25MessageAssembler();
private Listener mListener;
private AliasList mAliasList;
@@ -104,13 +101,9 @@ public class P25MessageFramer implements Listener
@Override
public void syncDetected()
{
- for(P25MessageAssembler assembler : mAssemblers)
+ if(!mMessageAssembler.isActive())
{
- if(!assembler.isActive())
- {
- assembler.setActive(true);
- break;
- }
+ mMessageAssembler.setActive(true);
}
}
@@ -122,17 +115,6 @@ public class P25MessageFramer implements Listener
});
mMatcher.add(mPrimarySyncDetector);
-
- /**
- * We use two message assemblers to catch any sync detections, so that
- * if we have a false trigger then we still have a chance to catch the
- * valid message. If we have two false triggers before the arrival of
- * the actual message sync, then the third or subsequent real or false
- * sync pattern will produce a debug message indicating no assemblers
- * are available..
- */
- mAssemblers.add(new P25MessageAssembler());
- mAssemblers.add(new P25MessageAssembler());
}
public P25MessageFramer(AliasList aliasList, IPhaseLockedLoop phaseLockedLoop, ISyncDetectListener syncDetectListener)
@@ -172,12 +154,7 @@ public class P25MessageFramer implements Listener
public void dispose()
{
- for(P25MessageAssembler a : mAssemblers)
- {
- a.dispose();
- }
-
- mAssemblers.clear();
+ mMessageAssembler.dispose();
mListener = null;
mAliasList = null;
@@ -201,16 +178,13 @@ public class P25MessageFramer implements Listener
@Override
public void receive(Dibit symbol)
{
- for(P25MessageAssembler assembler : mAssemblers)
+ if(mMessageAssembler.isActive())
{
- if(assembler.isActive())
- {
- assembler.receive(symbol);
+ mMessageAssembler.receive(symbol);
- if(assembler.complete())
- {
- assembler.reset();
- }
+ if(mMessageAssembler.complete())
+ {
+ mMessageAssembler.reset();
}
}
@@ -742,13 +716,9 @@ public class P25MessageFramer implements Listener
mPhaseLockedLoop.correctInversion(mPllCorrection);
/* Since we detected a sync pattern, start a message assembler */
- for(P25MessageAssembler assembler : mAssemblers)
+ if(!mMessageAssembler.isActive())
{
- if(!assembler.isActive())
- {
- assembler.setActive(true);
- break;
- }
+ mMessageAssembler.setActive(true);
}
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageProcessor.java
index 51d3978d..c6e5c6be 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageProcessor.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageProcessor.java
@@ -1,3 +1,18 @@
+/*******************************************************************************
+ * sdr-trunk
+ * Copyright (C) 2014-2018 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see
+ *
+ ******************************************************************************/
package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.alias.AliasList;
@@ -13,8 +28,7 @@ import java.util.HashMap;
public class P25MessageProcessor implements Listener
{
- private final static Logger mLog =
- LoggerFactory.getLogger( P25MessageProcessor.class );
+ private final static Logger mLog = LoggerFactory.getLogger( P25MessageProcessor.class );
private Listener mMessageListener;
diff --git a/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java b/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java
index a1f000a7..697a85c5 100644
--- a/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java
+++ b/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java
@@ -45,6 +45,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class BinaryRecorder extends Module implements IReusableByteBufferListener
{
private final static Logger mLog = LoggerFactory.getLogger(BinaryRecorder.class);
+ private static final int MAX_RECORDING_BYTE_SIZE = 524288; //500 kB
private ContinuousReusableBufferProcessor mBufferProcessor =
new ContinuousReusableBufferProcessor<>(500, 50);
@@ -53,6 +54,7 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene
private Path mBaseRecordingPath;
private String mRecordingIdentifier;
private BinaryWriter mBinaryWriter = new BinaryWriter();
+ private int mBytesRecordedCounter;
/**
* Constructs a binary recorder.
@@ -115,7 +117,7 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene
StringBuilder sb = new StringBuilder();
sb.append(TimeStamp.getTimeStamp("_"));
sb.append("_");
- sb.append(mRecordingIdentifier);
+ sb.append(mRecordingIdentifier.trim());
sb.append(".bits");
return mBaseRecordingPath.resolve(sb.toString());
@@ -148,22 +150,62 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene
public void start(Path path) throws IOException
{
- mCurrentPath = path;
- mWritableByteChannel = Files.newByteChannel(path,
- EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
+ synchronized(this)
+ {
+ mCurrentPath = path;
+ mWritableByteChannel = Files.newByteChannel(path,
+ EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
+ mLog.info("Binary (bitstream) recording started: " + mCurrentPath.toString());
+ }
}
public void stop() throws IOException
{
- if(mWritableByteChannel != null)
+ synchronized(this)
{
- mWritableByteChannel.close();
- }
+ if(mWritableByteChannel != null)
+ {
+ mWritableByteChannel.close();
+ }
- mWritableByteChannel = null;
- mCurrentPath = null;
+ mWritableByteChannel = null;
+ mCurrentPath = null;
+ }
}
+ /**
+ * Closes the current recording and starts a new recording. This method is normally used
+ * when the current recording size has reached the maximum threshold.
+ */
+ private void cycleRecording()
+ {
+ synchronized(this)
+ {
+ try
+ {
+ if(mWritableByteChannel != null)
+ {
+ mWritableByteChannel.close();
+ }
+
+ mCurrentPath = getRecordingPath();
+ mWritableByteChannel = Files.newByteChannel(mCurrentPath,
+ EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
+ mLog.info("Binary (bitstream) recording started: " + mCurrentPath.toString());
+ }
+ catch(IOException ioe)
+ {
+ mLog.error("Error while cycling a max-size bit stream recorder", ioe);
+ }
+
+ mBytesRecordedCounter = 0;
+ }
+ }
+
+ /**
+ * Primary receive method for incoming byte buffers
+ * @param reusableComplexBuffers to record
+ */
@Override
public void receive(List reusableComplexBuffers)
{
@@ -174,7 +216,12 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene
try
{
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer.getSamplesCopy());
- mWritableByteChannel.write(byteBuffer);
+ mBytesRecordedCounter += mWritableByteChannel.write(byteBuffer);
+
+ if(mBytesRecordedCounter > MAX_RECORDING_BYTE_SIZE)
+ {
+ cycleRecording();
+ }
}
catch(IOException ioe)
{