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) {