mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Merge pull request #396 from DSheirer/394-p25-bitstream-output
394 P25 bit stream output
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user