Merge pull request #396 from DSheirer/394-p25-bitstream-output

394 P25 bit stream output
This commit is contained in:
Denny Sheirer
2018-08-18 03:52:36 -04:00
committed by GitHub
10 changed files with 275 additions and 62 deletions

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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<Dibit>, 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<ReusableByteBuffer> 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<ReusableByteBuffer> listener)
{
mBufferListener = listener;
}
/**
* Removes the listener from receiving buffers from this assembler
*/
@Override
public void removeBufferListener(Listener<ReusableByteBuffer> listener)
{
mBufferListener = null;
}
@Override
public boolean hasBufferListeners()
{
return mBufferListener != null;
}
}

View File

@@ -476,9 +476,6 @@ public class DecoderFactory
*/
public static EnumSet<DecoderType> 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);
}
}

View File

@@ -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<ReusableComplexBuffer>
IReusableComplexBufferListener, Listener<ReusableComplexBuffer>, IReusableByteBufferProvider
{
private double mSampleRate;
private Broadcaster<Dibit> mDibitBroadcaster = new Broadcaster<>();
private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300);
private P25MessageProcessor mMessageProcessor;
private AliasList mAliasList;
private Listener<SourceEvent> 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<Dibit> getDibitBroadcaster()
{
return mDibitBroadcaster;
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public void setBufferListener(Listener<ReusableByteBuffer> listener)
{
mByteBufferAssembler.setBufferListener(listener);
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public void removeBufferListener(Listener<ReusableByteBuffer> listener)
{
mByteBufferAssembler.removeBufferListener(listener);
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public boolean hasBufferListeners()
{
return mByteBufferAssembler.hasBufferListeners();
}
protected double getSymbolRate()

View File

@@ -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);
}
/**

View File

@@ -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<Complex> listener)

View File

@@ -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);
}
/**

View File

@@ -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<Complex> listener)

View File

@@ -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<Dibit>
{
private final static Logger mLog = LoggerFactory.getLogger(P25MessageFramer.class);
@@ -77,7 +74,7 @@ public class P25MessageFramer implements Listener<Dibit>
private PLLPhaseInversionDetector mInversionDetector180;
private MultiSyncPatternMatcher mMatcher;
private List<P25MessageAssembler> mAssemblers = new ArrayList<P25MessageAssembler>();
private P25MessageAssembler mMessageAssembler = new P25MessageAssembler();
private Listener<Message> mListener;
private AliasList mAliasList;
@@ -104,13 +101,9 @@ public class P25MessageFramer implements Listener<Dibit>
@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<Dibit>
});
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<Dibit>
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<Dibit>
@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<Dibit>
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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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<Message>
{
private final static Logger mLog =
LoggerFactory.getLogger( P25MessageProcessor.class );
private final static Logger mLog = LoggerFactory.getLogger( P25MessageProcessor.class );
private Listener<Message> mMessageListener;

View File

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