From a621a31aa9852fbae82473b1874a24758a10af9d Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 24 Jun 2018 09:50:36 -0400 Subject: [PATCH] [WIP] Updates the sample adapters to natively use ReusableBuffers. Refactors a MixerReader instance from the RealMixerSource so that it can be reused for both real and complex data outputs. Updates RealMixerSource to use refactored MixerReader and use a default sample rate of 8 kHz. Updates the ComplexMixer to use the refactored MixerReader. Updates the FCD 1/2 tuners for compatibility with the new ComplexMixer. Removes several unused classes. --- ...dapter.java => AbstractSampleAdapter.java} | 11 +- .../sample/adapter/ByteSampleAdapter.java | 52 ---- .../sample/adapter/ChannelShortAdapter.java | 87 ------ .../sample/adapter/ComplexSampleAdapter.java | 39 +++ .../sample/adapter/ComplexShortAdapter.java | 54 ++-- .../ComplexToFloatSampleConverter.java | 57 ---- .../adapter/FloatArrayToComplexAdapter.java | 61 ---- .../sample/adapter/ISampleAdapter.java | 27 -- .../adapter/RealChannelShortAdapter.java | 91 ++++++ .../sample/adapter/RealSampleAdapter.java | 39 +++ .../sample/adapter/RealShortAdapter.java | 73 +++++ .../dsheirer/sample/adapter/ShortAdapter.java | 62 ---- .../github/dsheirer/sample/adapter/Test.java | 25 -- .../dsheirer/source/mixer/ComplexMixer.java | 207 +++---------- .../dsheirer/source/mixer/MixerManager.java | 10 +- .../dsheirer/source/mixer/MixerReader.java | 283 ++++++++++++++++++ .../source/mixer/RealMixerSource.java | 242 ++++----------- .../source/tuner/fcd/FCDTunerController.java | 8 +- .../fcd/proplusV2/FCD2TunerController.java | 4 +- 19 files changed, 672 insertions(+), 760 deletions(-) rename src/main/java/io/github/dsheirer/sample/adapter/{IComplexSampleAdapter.java => AbstractSampleAdapter.java} (70%) delete mode 100644 src/main/java/io/github/dsheirer/sample/adapter/ByteSampleAdapter.java delete mode 100644 src/main/java/io/github/dsheirer/sample/adapter/ChannelShortAdapter.java create mode 100644 src/main/java/io/github/dsheirer/sample/adapter/ComplexSampleAdapter.java delete mode 100644 src/main/java/io/github/dsheirer/sample/adapter/ComplexToFloatSampleConverter.java delete mode 100644 src/main/java/io/github/dsheirer/sample/adapter/FloatArrayToComplexAdapter.java delete mode 100644 src/main/java/io/github/dsheirer/sample/adapter/ISampleAdapter.java create mode 100644 src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java create mode 100644 src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java create mode 100644 src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java delete mode 100644 src/main/java/io/github/dsheirer/sample/adapter/ShortAdapter.java delete mode 100644 src/main/java/io/github/dsheirer/sample/adapter/Test.java create mode 100644 src/main/java/io/github/dsheirer/source/mixer/MixerReader.java diff --git a/src/main/java/io/github/dsheirer/sample/adapter/IComplexSampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/AbstractSampleAdapter.java similarity index 70% rename from src/main/java/io/github/dsheirer/sample/adapter/IComplexSampleAdapter.java rename to src/main/java/io/github/dsheirer/sample/adapter/AbstractSampleAdapter.java index 99d07eeb..b2db661c 100644 --- a/src/main/java/io/github/dsheirer/sample/adapter/IComplexSampleAdapter.java +++ b/src/main/java/io/github/dsheirer/sample/adapter/AbstractSampleAdapter.java @@ -15,14 +15,9 @@ ******************************************************************************/ package io.github.dsheirer.sample.adapter; -import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.buffer.ReusableBuffer; -public interface IComplexSampleAdapter +public abstract class AbstractSampleAdapter { - /** - * Converts the sample byte array into complex float samples, loading the converted float samples into the buffer. - * @param samples - * @param reusableComplexBuffer - */ - void convertAndLoad(byte[] samples, ReusableComplexBuffer reusableComplexBuffer); + public abstract T convert(byte[] sampleBytes); } diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ByteSampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ByteSampleAdapter.java deleted file mode 100644 index 9955c1a2..00000000 --- a/src/main/java/io/github/dsheirer/sample/adapter/ByteSampleAdapter.java +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 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.sample.adapter; - -public class ByteSampleAdapter implements ISampleAdapter -{ - private final static float[] LOOKUP_VALUES; - - /** - * Creates a static lookup table that converts the 8-bit valued range - * from 0 - 255 into scaled float values of -1.0 to 0 to 1.0 - */ - static - { - LOOKUP_VALUES = new float[ 256 ]; - - for( int x = 0; x < 256; x++ ) - { - LOOKUP_VALUES[ x ] = (float)( x - 127 ) / 128.0f; - } - } - - @Override - public float[] convert( byte[] samples ) - { - float[] convertedSamples = new float[ samples.length ]; - int pointer = 0; - - for( byte sample: samples ) - { - /* Convert byte value into float from the lookup table */ - convertedSamples[ pointer++ ] = LOOKUP_VALUES[ ( sample & 0xFF ) ]; - } - - return convertedSamples; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ChannelShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ChannelShortAdapter.java deleted file mode 100644 index 379087b4..00000000 --- a/src/main/java/io/github/dsheirer/sample/adapter/ChannelShortAdapter.java +++ /dev/null @@ -1,87 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 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.sample.adapter; - -import io.github.dsheirer.source.mixer.MixerChannel; -import org.apache.commons.lang3.Validate; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -/** - * Converts 16-bit little endian byte data into an array of floats - */ -public class ChannelShortAdapter implements ISampleAdapter -{ - private ShortToFloatMap mMap = new ShortToFloatMap(); - private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; - private MixerChannel mMixerChannel; - - public ChannelShortAdapter( MixerChannel channel ) - { - /* Only use with LEFT/RIGHT channels */ - Validate.isTrue(channel != MixerChannel.MONO); - - mMixerChannel = channel; - } - - @Override - public float[] convert( byte[] samples ) - { - float[] processed = new float[ samples.length / 4 ]; - - int pointer = 0; - - /* Wrap byte array in a byte buffer so we can process them as shorts */ - ByteBuffer buffer = ByteBuffer.wrap( samples ); - - /* Set endian to correct byte ordering */ - buffer.order( mByteOrder ); - - while( buffer.hasRemaining() ) - { - if( mMixerChannel == MixerChannel.LEFT ) - { - processed[ pointer ] = mMap.get( buffer.getShort() ); - - /* Throw away the right channel */ - buffer.getShort(); - } - else - { - /* Throw away the left channel */ - buffer.getShort(); - - processed[ pointer ] = mMap.get( buffer.getShort() ); - } - - pointer++; - } - - return processed; - } - - /** - * Set byte interpretation to little or big endian. Defaults to LITTLE - * endian - */ - public void setByteOrder( ByteOrder order ) - { - mByteOrder = order; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ComplexSampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ComplexSampleAdapter.java new file mode 100644 index 00000000..21846439 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/adapter/ComplexSampleAdapter.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * 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.sample.adapter; + +import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; + +public abstract class ComplexSampleAdapter extends AbstractSampleAdapter +{ + private ReusableComplexBufferQueue mReusableBufferQueue; + + /** + * Constructs a real sample adapter + * + * @param debugName to use for debug logging + */ + public ComplexSampleAdapter(String debugName) + { + mReusableBufferQueue = new ReusableComplexBufferQueue(debugName); + } + + protected ReusableComplexBuffer getBuffer(int size) + { + return mReusableBufferQueue.getBuffer(size); + } +} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java index 6a3c80ac..254af929 100644 --- a/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java +++ b/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java @@ -16,43 +16,61 @@ package io.github.dsheirer.sample.adapter; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.FloatBuffer; /** - * Converts 16-bit little endian byte data into an array of complex floats + * Converts 16-bit/2-byte little endian byte data into a reusable buffer of complex float sample data */ -public class ComplexShortAdapter implements IComplexSampleAdapter +public class ComplexShortAdapter extends ComplexSampleAdapter { + private final static Logger mLog = LoggerFactory.getLogger(ComplexShortAdapter.class); private ShortToFloatMap mMap = new ShortToFloatMap(); - private FloatBuffer mFloatBuffer; private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; + private ByteBuffer mByteBuffer; + + /** + * Constructs a real sample adapter + * + * @param debugName to use for debug logging + */ + public ComplexShortAdapter(String debugName) + { + super(debugName); + } @Override - public void convertAndLoad(byte[] samples, ReusableComplexBuffer reusableComplexBuffer) + public ReusableComplexBuffer convert(byte[] samples) { - int sampleCount = samples.length / 2; + ReusableComplexBuffer reusableBuffer = getBuffer(samples.length / 2); + float[] convertedSamples = reusableBuffer.getSamples(); - if(mFloatBuffer == null || mFloatBuffer.capacity() != sampleCount) - { - mFloatBuffer = FloatBuffer.allocate(sampleCount); - } + int pointer = 0; - mFloatBuffer.rewind(); - - /* Wrap byte array in a byte buffer so we can process them as shorts */ - ByteBuffer buffer = ByteBuffer.wrap(samples); + mByteBuffer = ByteBuffer.wrap(samples); /* Set endian to correct byte ordering */ - buffer.order(mByteOrder); + mByteBuffer.order(mByteOrder); - while(buffer.hasRemaining()) + while(mByteBuffer.hasRemaining()) { - mFloatBuffer.put(mMap.get(buffer.getShort())); + convertedSamples[pointer] = mMap.get(mByteBuffer.getShort()); + pointer++; } - reusableComplexBuffer.reloadFrom(mFloatBuffer, System.currentTimeMillis()); + reusableBuffer.incrementUserCount(); + return reusableBuffer; + } + + /** + * Set byte interpretation to little or big endian. Defaults to LITTLE + * endian + */ + public void setByteOrder(ByteOrder order) + { + mByteOrder = order; } } diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ComplexToFloatSampleConverter.java b/src/main/java/io/github/dsheirer/sample/adapter/ComplexToFloatSampleConverter.java deleted file mode 100644 index 6c013b44..00000000 --- a/src/main/java/io/github/dsheirer/sample/adapter/ComplexToFloatSampleConverter.java +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 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.sample.adapter; - -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.complex.Complex; -import io.github.dsheirer.source.mixer.MixerChannel; - -import java.util.List; - -/** - * Receives complex samples and send either the left channel or the right - * channel to the short sample listener - */ -public class ComplexToFloatSampleConverter implements Listener> -{ - private MixerChannel mChannel; - private Listener mListener; - - public ComplexToFloatSampleConverter( Listener listener, - MixerChannel channel ) - { - mListener = listener; - mChannel = channel; - } - - @Override - public void receive( List samples ) - { - for( Complex sample: samples ) - { - mListener.receive( mChannel == MixerChannel.LEFT ? - sample.left() : - sample.right() ); - } - } - - public Listener getListener() - { - return mListener; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/FloatArrayToComplexAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/FloatArrayToComplexAdapter.java deleted file mode 100644 index 70af8237..00000000 --- a/src/main/java/io/github/dsheirer/sample/adapter/FloatArrayToComplexAdapter.java +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 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.sample.adapter; - -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.complex.Complex; - -public class FloatArrayToComplexAdapter implements Listener -{ - private Listener mListener; - - @Override - public void receive( Float[] samples ) - { - if( samples != null ) - { - for( int x = 0; x < samples.length; x += 2 ) - { - if( mListener != null && - samples[ x ] != null && - samples[ x + 1 ] != null ) - { - mListener.receive( new Complex( samples[ x ], - samples[ x + 1 ] ) ); - } - } - - samples = null; - } - } - - public void setListener( Listener listener ) - { - mListener = listener; - } - - public void removeListener( Listener listener ) - { - mListener = null; - } - - public void dispose() - { - mListener = null; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ISampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ISampleAdapter.java deleted file mode 100644 index ec80a8ae..00000000 --- a/src/main/java/io/github/dsheirer/sample/adapter/ISampleAdapter.java +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 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.sample.adapter; - -/* - * Adapter to convert byte data received from a data stream into float[] - * samples - */ -public interface ISampleAdapter -{ - public abstract float[] convert( byte[] samples ); -} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java new file mode 100644 index 00000000..77b528e3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/adapter/RealChannelShortAdapter.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * 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.sample.adapter; + +import io.github.dsheirer.sample.buffer.ReusableBuffer; +import io.github.dsheirer.source.mixer.MixerChannel; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Adapter for converting stereo/2-channel short sample data to single channel float samples. + */ +public class RealChannelShortAdapter extends RealSampleAdapter +{ + private final static Logger mLog = LoggerFactory.getLogger(RealChannelShortAdapter.class); + private ShortToFloatMap mMap = new ShortToFloatMap(); + private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; + private MixerChannel mMixerChannel; + private ByteBuffer mByteBuffer; + + public RealChannelShortAdapter(MixerChannel channel, String debugName) + { + super(debugName); + + /* Only use with LEFT/RIGHT channels */ + Validate.isTrue(channel != MixerChannel.MONO); + + mMixerChannel = channel; + } + + @Override + public ReusableBuffer convert(byte[] samples) + { + ReusableBuffer reusableBuffer = getBuffer(samples.length / 4); + float[] convertedSamples = reusableBuffer.getSamples(); + + mByteBuffer = ByteBuffer.wrap(samples); + + /* Set endian to correct byte ordering */ + mByteBuffer.order(mByteOrder); + + int pointer = 0; + + while(mByteBuffer.hasRemaining()) + { + if(mMixerChannel == MixerChannel.LEFT) + { + convertedSamples[pointer++] = mMap.get(mByteBuffer.getShort()); + + //Throw away the right channel + mByteBuffer.getShort(); + } + else + { + //Throw away the left channel + mByteBuffer.getShort(); + + convertedSamples[pointer++] = mMap.get(mByteBuffer.getShort()); + } + } + + reusableBuffer.incrementUserCount(); + return reusableBuffer; + } + + /** + * Set byte interpretation to little or big endian. Defaults to LITTLE + * endian + */ + public void setByteOrder(ByteOrder order) + { + mByteOrder = order; + } +} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java new file mode 100644 index 00000000..afbe0c7f --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/adapter/RealSampleAdapter.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * 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.sample.adapter; + +import io.github.dsheirer.sample.buffer.ReusableBuffer; +import io.github.dsheirer.sample.buffer.ReusableBufferQueue; + +public abstract class RealSampleAdapter extends AbstractSampleAdapter +{ + private ReusableBufferQueue mReusableBufferQueue; + + /** + * Constructs a real sample adapter + * + * @param debugName to use for debug logging + */ + public RealSampleAdapter(String debugName) + { + mReusableBufferQueue = new ReusableBufferQueue(debugName); + } + + protected ReusableBuffer getBuffer(int size) + { + return mReusableBufferQueue.getBuffer(size); + } +} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java new file mode 100644 index 00000000..7d1da2aa --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/adapter/RealShortAdapter.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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.sample.adapter; + +import io.github.dsheirer.sample.buffer.ReusableBuffer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Converts 16-bit/2-byte little endian byte data into a reusable buffer of real float sample data + */ +public class RealShortAdapter extends RealSampleAdapter +{ + private ByteBuffer mByteBuffer; + private ShortToFloatMap mMap = new ShortToFloatMap(); + private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; + + /** + * Constructs a real sample adapter + * + * @param debugName to use for debug logging + */ + public RealShortAdapter(String debugName) + { + super(debugName); + } + + @Override + public ReusableBuffer convert(byte[] samples) + { + ReusableBuffer reusableBuffer = getBuffer(samples.length / 2); + float[] convertedSamples = reusableBuffer.getSamples(); + + int pointer = 0; + + mByteBuffer = ByteBuffer.wrap(samples); + + /* Set endian to correct byte ordering */ + mByteBuffer.order(mByteOrder); + + while(mByteBuffer.hasRemaining()) + { + convertedSamples[pointer] = mMap.get(mByteBuffer.getShort()); + pointer++; + } + + reusableBuffer.incrementUserCount(); + return reusableBuffer; + } + + /** + * Set byte interpretation to little or big endian. Defaults to LITTLE + * endian + */ + public void setByteOrder(ByteOrder order) + { + mByteOrder = order; + } +} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ShortAdapter.java deleted file mode 100644 index 6faeb20a..00000000 --- a/src/main/java/io/github/dsheirer/sample/adapter/ShortAdapter.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 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.sample.adapter; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -/** - * Converts 16-bit little endian byte data into an array of floats - */ -public class ShortAdapter implements ISampleAdapter -{ - private ShortToFloatMap mMap = new ShortToFloatMap(); - private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; - - @Override - public float[] convert( byte[] samples ) - { - float[] processed = new float[ samples.length / 2 ]; - - int pointer = 0; - - /* Wrap byte array in a byte buffer so we can process them as shorts */ - ByteBuffer buffer = ByteBuffer.wrap( samples ); - - /* Set endian to correct byte ordering */ - buffer.order( mByteOrder ); - - while( buffer.hasRemaining() ) - { - processed[ pointer ] = mMap.get( buffer.getShort() ); - - pointer++; - } - - return processed; - } - - /** - * Set byte interpretation to little or big endian. Defaults to LITTLE - * endian - */ - public void setByteOrder( ByteOrder order ) - { - mByteOrder = order; - } -} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/Test.java b/src/main/java/io/github/dsheirer/sample/adapter/Test.java deleted file mode 100644 index cec98e4a..00000000 --- a/src/main/java/io/github/dsheirer/sample/adapter/Test.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.dsheirer.sample.adapter; - -import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Test -{ - private final static Logger mLog = - LoggerFactory.getLogger( HackRFTunerController.class ); - - public static void main( String[] args ) - { - mLog.debug( "Starting" ); - - ShortToFloatMap map1 = new ShortToFloatMap(); - - for( short x = -32768; x < -32758; x++ ) - { - mLog.debug( "X:" + x + " value:" + map1.get( x ) ); - } - mLog.debug( "Finished" ); - } - -} diff --git a/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java b/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java index 9d83fdac..51c68e44 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java +++ b/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java @@ -16,69 +16,74 @@ package io.github.dsheirer.source.mixer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.adapter.IComplexSampleAdapter; +import io.github.dsheirer.sample.adapter.ComplexSampleAdapter; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; -import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue; -import io.github.dsheirer.source.SourceException; +import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.TargetDataLine; -import java.util.concurrent.atomic.AtomicBoolean; public class ComplexMixer { private final static Logger mLog = LoggerFactory.getLogger(ComplexMixer.class); - - private long mFrequency = 0; - - private BufferReader mBufferReader = new BufferReader(); - private TargetDataLine mTargetDataLine; - private AudioFormat mAudioFormat; + private MixerReader mMixerReader; private String mName; - private int mBytesPerFrame = 0; - private IComplexSampleAdapter mSampleAdapter; - private Listener mListener; - private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ComplexMixer"); /** * Complex Mixer - constructs a reader on the mixer/sound card target data line using the specified audio - * format (sample size, sample rate ) and broadcasts complex (I/Q) sample buffers to all registered listeners. - * Reads sample buffers sized to 10% of the sample rate specified in audio format. + * format (sample size, sample rate ) and broadcasts complex (I/Q) sample buffers to the registered listener. * * @param targetDataLine - mixer or sound card to be used * @param format - audio format * @param name - token name to use for this mixer * @param complexSampleAdapter - adapter to convert byte array data read from the mixer into ComplexBuffer. - * @param listener to receive generated complex buffers */ public ComplexMixer(TargetDataLine targetDataLine, AudioFormat format, String name, - IComplexSampleAdapter complexSampleAdapter, Listener listener) + ComplexSampleAdapter complexSampleAdapter) { - mTargetDataLine = targetDataLine; + mMixerReader = new MixerReader<>(format, targetDataLine, complexSampleAdapter); mName = name; - mAudioFormat = format; - mSampleAdapter = complexSampleAdapter; - mListener = listener; + } + + /** + * Sets the buffer size in bytes per buffer for each read interval. + * @param bufferSize in bytes + */ + public void setBufferSize(int bufferSize) + { + mMixerReader.setBufferSize(bufferSize); } public void start() { - if(!mBufferReader.isRunning()) - { - Thread thread = new Thread(mBufferReader); - thread.setDaemon(true); - thread.setName(mName + " Complex Sample Reader"); - thread.start(); - } + mMixerReader.start(); } public void stop() { - mBufferReader.stop(); + mMixerReader.stop(); + } + + public void setBufferListener(Listener listener) + { + mMixerReader.setBufferListener(listener); + } + + public void removeBufferListener() + { + mMixerReader.removeBufferListener(); + } + + public void setSourceEventListener(Listener listener) + { + mMixerReader.setSourceEventListener(listener); + } + + public void removeSourceEventListener() + { + mMixerReader.removeSourceEventListener(); } @Override @@ -87,144 +92,8 @@ public class ComplexMixer return mName; } - public int getSampleRate() - { - if(mTargetDataLine != null) - { - return (int)mTargetDataLine.getFormat().getSampleRate(); - } - else - { - return 0; - } - } - - /** - * Returns the frequency of this source. Default is 0. - */ - public long getFrequency() throws SourceException - { - return mFrequency; - } - - /** - * Specify the frequency that will be returned from this source. This may - * be useful if you are streaming an external audio source in through the - * sound card and you want to specify a frequency for that source - */ - public void setFrequency(long frequency) - { - mFrequency = frequency; - } - - /** - * Reader thread. Performs blocking read against the mixer target data - * line, converts the samples to an array of floats using the specified - * adapter. Dispatches float arrays to all registered listeners. - */ - public class BufferReader implements Runnable - { - private AtomicBoolean mRunning = new AtomicBoolean(); - - @Override - public void run() - { - if(mRunning.compareAndSet(false, true)) - { - if(mTargetDataLine == null) - { - mRunning.set(false); - mLog.error("ComplexMixerSource - mixer target data line is null"); - } - else - { - mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8; - - if(mBytesPerFrame == AudioSystem.NOT_SPECIFIED) - { - mBytesPerFrame = 2; - } - - //Set buffer size to 1/10 second of samples - int sampleCount = (int)(mAudioFormat.getSampleRate() * 0.10) * 2; - int bufferSize = sampleCount * mBytesPerFrame; - - //We'll reuse the same buffer for each read - byte[] buffer = new byte[bufferSize]; - - try - { - mTargetDataLine.open(mAudioFormat); - mTargetDataLine.start(); - } - catch(LineUnavailableException e) - { - mLog.error("ComplexMixerSource - mixer target data line not available to read data from", e); - mRunning.set(false); - } - - while(mRunning.get()) - { - try - { - //Blocking read - waits until the buffer fills - mTargetDataLine.read(buffer, 0, buffer.length); - - /* Dispatch samples to registered listeners */ - if(mListener != null) - { - ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(sampleCount); - - //Convert samples and load into reusable buffer - mSampleAdapter.convertAndLoad(buffer, reusableComplexBuffer); - - mListener.receive(reusableComplexBuffer); - } - } - catch(Exception e) - { - mLog.error("error while reading from the mixer target data line", e); - mRunning.set(false); - } - } - } - - /* Close the data line if it is still open */ - if(mTargetDataLine != null && mTargetDataLine.isOpen()) - { - mTargetDataLine.close(); - } - } - } - - /** - * Stops the reader thread - */ - public void stop() - { - if(mRunning.compareAndSet(true, false)) - { - mTargetDataLine.stop(); - mTargetDataLine.close(); - } - } - - /** - * Indicates if the reader thread is running - */ - public boolean isRunning() - { - return mRunning.get(); - } - } - public void dispose() { - if(mBufferReader != null) - { - mBufferReader.stop(); - } - - mListener = null; + mMixerReader.dispose(); } } diff --git a/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java b/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java index fad3a366..508269db 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java +++ b/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java @@ -16,8 +16,8 @@ package io.github.dsheirer.source.mixer; import io.github.dsheirer.audio.AudioFormats; -import io.github.dsheirer.sample.adapter.ChannelShortAdapter; -import io.github.dsheirer.sample.adapter.ShortAdapter; +import io.github.dsheirer.sample.adapter.RealChannelShortAdapter; +import io.github.dsheirer.sample.adapter.RealShortAdapter; import io.github.dsheirer.source.config.SourceConfigMixer; import io.github.dsheirer.source.config.SourceConfiguration; import io.github.dsheirer.source.tuner.MixerTunerDataLine; @@ -90,7 +90,7 @@ public class MixerManager if(dataLine != null) { return new RealMixerSource(dataLine, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO, - new ShortAdapter()); + new RealShortAdapter("RealMixerSource - Mono")); } } catch(LineUnavailableException e) @@ -100,7 +100,7 @@ public class MixerManager } } - else + else //STEREO { DataLine.Info info = new DataLine.Info(TargetDataLine.class, AudioFormats.PCM_SIGNED_8KHZ_16BITS_STEREO); @@ -114,7 +114,7 @@ public class MixerManager if(dataLine != null) { return new RealMixerSource(dataLine, AudioFormats.PCM_SIGNED_8KHZ_16BITS_STEREO, - new ChannelShortAdapter(mixerConfig.getChannel())); + new RealChannelShortAdapter(mixerConfig.getChannel(), "RealMixerSource-Stereo")); } } catch(LineUnavailableException e) diff --git a/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java b/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java new file mode 100644 index 00000000..87315c22 --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java @@ -0,0 +1,283 @@ +/******************************************************************************* + * 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.source.mixer; + +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.adapter.AbstractSampleAdapter; +import io.github.dsheirer.sample.buffer.ReusableBuffer; +import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.source.heartbeat.HeartbeatManager; +import io.github.dsheirer.util.ThreadPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.TargetDataLine; +import java.util.Arrays; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Target Data Line sample data processor. Performs blocking read against the mixer target data line, converts + * the samples to an array of floats using the specified adapter. Dispatches float arrays to the registered + * buffer listener. + */ +public class MixerReader implements Runnable +{ + private final static Logger mLog = LoggerFactory.getLogger(MixerReader.class); + + private static final long BUFFER_PROCESSING_INTERVAL_MS = 50; //50 milliseconds, 20 reads per second + private TargetDataLine mTargetDataLine; + private byte[] mBuffer; + private int mBufferSize; + private AtomicBoolean mRunning = new AtomicBoolean(); + private ScheduledFuture mScheduledFuture; + private int mBytesRead; + private AbstractSampleAdapter mSampleAdapter; + private Listener mReusableBufferListener; + private Listener mSourceEventListener; + private AudioFormat mAudioFormat; + private HeartbeatManager mHeartbeatManager; + + public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, + AbstractSampleAdapter abstractSampleAdapter, HeartbeatManager heartbeatManager) + { + mTargetDataLine = targetDataLine; + mAudioFormat = audioFormat; + mSampleAdapter = abstractSampleAdapter; + mHeartbeatManager = heartbeatManager; + + /* Set buffer size to 1/10 second of samples */ + mBufferSize = (int)(mAudioFormat.getSampleRate() * 0.1) * mAudioFormat.getFrameSize(); + } + + public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, + AbstractSampleAdapter abstractSampleAdapter) + { + this(audioFormat, targetDataLine, abstractSampleAdapter, null); + } + + /** + * Sets the size of buffers to use. This reader will read from target data line 20 times per second. + * @param bytesPerBuffer to size for each buffer read. + */ + public void setBufferSize(int bytesPerBuffer) + { + mBufferSize = bytesPerBuffer; + mBuffer = new byte[mBufferSize]; + } + + /** + * Opens the source mixer target data line + * + * @throws LineUnavailableException if the target data line is null or unavailable (from the OS). + */ + private void openTargetDataLine() throws LineUnavailableException + { + if(mTargetDataLine == null) + { + throw new LineUnavailableException("Source Mixer TargetDataLine is null"); + } + + mBuffer = new byte[mBufferSize]; + + mTargetDataLine.open(mAudioFormat); + mTargetDataLine.start(); + } + + /** + * Closes the mixer source target data line + */ + private void closeTargetDataLine() + { + if(mTargetDataLine != null && mTargetDataLine.isOpen()) + { + mTargetDataLine.close(); + } + } + + /** + * Starts the sample processing reader thread + */ + public void start() + { + if(mRunning.compareAndSet(false, true)) + { + try + { + openTargetDataLine(); + } + catch(LineUnavailableException e) + { + mLog.error("Unable to open mixer source target data line", e); + mRunning.set(false); + return; + } + + //Cancel the scheduled thread if it wasn't cancelled previously ... this shouldn't happen + if(mScheduledFuture != null) + { + mScheduledFuture.cancel(true); + mScheduledFuture = null; + } + + mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this, + 0, BUFFER_PROCESSING_INTERVAL_MS, TimeUnit.MILLISECONDS); + } + else + { + mLog.warn("Attempt to start an already running BufferReader instance - this shouldn't happen"); + } + } + + /** + * Stops the sample processing reader thread + */ + public void stop() + { + if(mRunning.compareAndSet(true, false)) + { + if(mScheduledFuture != null) + { + mScheduledFuture.cancel(true); + mScheduledFuture = null; + } + + closeTargetDataLine(); + } + else + { + mLog.warn("Attempt to stop an already stopped BufferReader instance - this shouldn't happen"); + } + } + + /** + * Sample processing thread. This method runs each time the timer fires. + */ + @Override + public void run() + { + if(mRunning.get()) + { + if(mHeartbeatManager != null) + { + mHeartbeatManager.broadcast(); + } + + try + { + mBytesRead = 0; + + //Blocking read - waits until the buffer fills + mBytesRead = mTargetDataLine.read(mBuffer, 0, mBuffer.length); + + if(mBytesRead == mBuffer.length) + { + //Sample adapter automatically sets initial listener count to one. + T reusableBuffer = mSampleAdapter.convert(mBuffer); + + if(reusableBuffer != null && mReusableBufferListener != null) + { + mReusableBufferListener.receive(reusableBuffer); + } + } + else if(mBytesRead > 0) + { + //Sample adapter automatically sets initial listener count to one. + T reusableBuffer = mSampleAdapter.convert(Arrays.copyOf(mBuffer, mBytesRead)); + + if(reusableBuffer != null && mReusableBufferListener != null) + { + mReusableBufferListener.receive(reusableBuffer); + } + } + } + catch(Exception e) + { + mLog.error("MixerSource - error while reading from the mixer target data line", e); + stop(); + } + } + } + + /** + * Indicates if the reader thread is running + */ + public boolean isRunning() + { + return mRunning.get(); + } + + /** + * Sets the listener to receive sample data in reusable buffers. + */ + public void setBufferListener(Listener listener) + { + mReusableBufferListener = listener; + } + + /** + * Removes the listener from receiving sample data. + */ + public void removeBufferListener() + { + mReusableBufferListener = null; + } + + /** + * Registers a listener to receive source events + * + * @param listener + */ + public void setSourceEventListener(Listener listener) + { + mSourceEventListener = listener; + } + + /** + * Removes a listener from receiving source events + */ + public void removeSourceEventListener() + { + mSourceEventListener = null; + } + + /** + * Current source event listener + */ + public Listener getSourceEventListener() + { + return mSourceEventListener; + } + + /** + * Sample rate specified for the underlying target data line. + */ + public double getSampleRate() + { + return mAudioFormat.getSampleRate(); + } + + public void dispose() + { + stop(); + mHeartbeatManager = null; + mReusableBufferListener = null; + mSourceEventListener = null; + } +} diff --git a/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java b/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java index 70b996c9..5cb6a7fb 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java +++ b/src/main/java/io/github/dsheirer/source/mixer/RealMixerSource.java @@ -16,38 +16,18 @@ package io.github.dsheirer.source.mixer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.adapter.ISampleAdapter; +import io.github.dsheirer.sample.adapter.AbstractSampleAdapter; import io.github.dsheirer.sample.buffer.ReusableBuffer; -import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; -import io.github.dsheirer.sample.buffer.ReusableBufferQueue; -import io.github.dsheirer.sample.real.RealBuffer; import io.github.dsheirer.source.RealSource; import io.github.dsheirer.source.SourceEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.TargetDataLine; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicBoolean; public class RealMixerSource extends RealSource { - private final static Logger mLog = LoggerFactory.getLogger(RealMixerSource.class); - private ReusableBufferBroadcaster mBufferBroadcaster = new ReusableBufferBroadcaster<>(); - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealMixerSource"); - private long mFrequency = 0; - private int mBufferSize = 16384; - - private BufferReader mBufferReader = new BufferReader(); - private TargetDataLine mTargetDataLine; - private AudioFormat mAudioFormat; - private int mBytesPerFrame = 0; - private ISampleAdapter mSampleAdapter; - - private CopyOnWriteArrayList> mSampleListeners = new CopyOnWriteArrayList>(); + private long mFrequency; + private MixerReader mMixerReader; /** * Real Mixer Source - constructs a reader on the mixer/sound card target @@ -58,89 +38,94 @@ public class RealMixerSource extends RealSource * @param targetDataLine - mixer or sound card to be used * @param format - audio format * @param sampleAdapter - adapter to convert byte array data read from the - * mixer into float array data. Can optionally invert the channel data if - * the left/right stereo channels are inverted. + * mixer into float array data. */ - public RealMixerSource(TargetDataLine targetDataLine, AudioFormat format, ISampleAdapter sampleAdapter) + public RealMixerSource(TargetDataLine targetDataLine, AudioFormat format, AbstractSampleAdapter sampleAdapter) { - mTargetDataLine = targetDataLine; - mAudioFormat = format; - mSampleAdapter = sampleAdapter; + mMixerReader = new MixerReader(format, targetDataLine, sampleAdapter, getHeartbeatManager()); } + /** + * Registers a listener to receive source events + * + * @param listener + */ @Override public void setSourceEventListener(Listener listener) { - //Not implemented + mMixerReader.setSourceEventListener(listener); } + /** + * Removes a listener from receiving source events + */ @Override public void removeSourceEventListener() { - //Not implemented + mMixerReader.removeSourceEventListener(); } + /** + * Current source event listener + */ @Override public Listener getSourceEventListener() { - //Not implemented - return null; + return mMixerReader.getSourceEventListener(); } @Override public void reset() { + stop(); } @Override public void start() { + if(!mMixerReader.isRunning()) + { + mMixerReader.start(); + } } @Override public void stop() { - } - - public void setListener(Listener listener) - { - mBufferBroadcaster.addListener(listener); - - /* If this is the first listener, start the reader thread */ - if(!mBufferReader.isRunning()) + if(mMixerReader.isRunning()) { - Thread thread = new Thread(mBufferReader); - thread.setDaemon(true); - thread.setName("Sample Reader"); - thread.start(); - } - } - - public void removeListener(Listener listener) - { - mBufferBroadcaster.removeListener(listener); - - /* If this is the last listener, stop the reader thread */ - if(mSampleListeners.isEmpty()) - { - mBufferReader.stop(); - } - } - - public double getSampleRate() - { - if(mTargetDataLine != null) - { - return mTargetDataLine.getFormat().getSampleRate(); - } - else - { - return 0; + mMixerReader.stop(); } } /** - * Returns the frequency of this source. Default is 0. + * Sets the listener to receive sample data in reusable buffers. + */ + public void setListener(Listener listener) + { + mMixerReader.setBufferListener(listener); + } + + /** + * Removes the listener from receiving sample data. + * @param listener + */ + public void removeListener(Listener listener) + { + mMixerReader.removeBufferListener(); + } + + /** + * Sample rate specified for the underlying target data line. + */ + @Override + public double getSampleRate() + { + return mMixerReader.getSampleRate(); + } + + /** + * Returns the frequency of this source. Default is 0 if the frequency hasn't been set. */ public long getFrequency() { @@ -148,131 +133,18 @@ public class RealMixerSource extends RealSource } /** - * Specify the frequency that will be returned from this source. This may - * be useful if you are streaming an external audio source in through the - * sound card and you want to specify a frequency for that source + * Specify the frequency that will be returned from this source. This may be useful if you are streaming an + * external audio source in through the sound card and you want to specify a frequency for that source */ public void setFrequency(long frequency) { mFrequency = frequency; } - /** - * Reader thread. Performs blocking read against the mixer target data - * line, converts the samples to an array of floats using the specified - * adapter. Dispatches float arrays to all registered listeners. - */ - public class BufferReader implements Runnable - { - private AtomicBoolean mRunning = new AtomicBoolean(); - - @Override - public void run() - { - if(mRunning.compareAndSet(false, true)) - { - getHeartbeatManager().broadcast(); - - if(mTargetDataLine == null) - { - mRunning.set(false); - - mLog.error("ComplexMixerSource - mixer target data line" - + " is null"); - } - else - { - mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8; - - if(mBytesPerFrame == AudioSystem.NOT_SPECIFIED) - { - mBytesPerFrame = 2; - } - - /* Set buffer size to 1/10 second of samples */ - mBufferSize = (int)(mAudioFormat.getSampleRate() - * 0.05) * mBytesPerFrame; - - /* We'll reuse the same buffer for each read */ - byte[] buffer = new byte[mBufferSize]; - - try - { - mTargetDataLine.open(mAudioFormat); - - mTargetDataLine.start(); - } - catch(LineUnavailableException e) - { - mLog.error("ComplexMixerSource - mixer target data line" - + "not available to read data from", e); - - mRunning.set(false); - } - - while(mRunning.get()) - { - try - { - /* Blocking read - waits until the buffer fills */ - mTargetDataLine.read(buffer, 0, buffer.length); - - /* Convert samples to float array */ -//TODO: change this sample adapter over to use reusable buffers and move the buffer queue into the adapter - float[] samples = mSampleAdapter.convert(buffer); - - /* Dispatch samples to registered listeners */ - ReusableBuffer reusableBuffer = mReusableBufferQueue.getBuffer(samples.length); - reusableBuffer.reloadFrom(samples, System.currentTimeMillis()); - reusableBuffer.incrementUserCount(); - mBufferBroadcaster.broadcast(reusableBuffer); - } - catch(Exception e) - { - mLog.error("ComplexMixerSource - error while reading" - + "from the mixer target data line", e); - - mRunning.set(false); - } - } - } - - /* Close the data line if it is still open */ - if(mTargetDataLine != null && mTargetDataLine.isOpen()) - { - mTargetDataLine.close(); - } - } - } - - /** - * Stops the reader thread - */ - public void stop() - { - if(mRunning.compareAndSet(true, false)) - { - mTargetDataLine.stop(); - mTargetDataLine.close(); - } - } - - /** - * Indicates if the reader thread is running - */ - public boolean isRunning() - { - return mRunning.get(); - } - } @Override public void dispose() { - if(mBufferReader != null) - { - mBufferReader.stop(); - mSampleListeners.clear(); - } + mMixerReader.dispose(); } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java index 002bcc63..7693e5af 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java @@ -1,6 +1,6 @@ /******************************************************************************* * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * 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 @@ -88,7 +88,11 @@ public abstract class FCDTunerController extends TunerController } mComplexMixer = new ComplexMixer( mixerTDL.getTargetDataLine(), audioFormat, tunerName, - new ComplexShortAdapter(), mReusableBufferBroadcaster); + new ComplexShortAdapter(mixerTDL.getMixerTunerType().getDisplayString())); + + mComplexMixer.setBufferSize(audioFormat.getFrameSize() * (int)(audioFormat.getSampleRate() * 0.1)); + + mComplexMixer.setBufferListener(mReusableBufferBroadcaster); } /** diff --git a/src/main/java/io/github/dsheirer/source/tuner/fcd/proplusV2/FCD2TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/fcd/proplusV2/FCD2TunerController.java index c8e71140..dfb81b67 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/fcd/proplusV2/FCD2TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/fcd/proplusV2/FCD2TunerController.java @@ -1,6 +1,6 @@ /******************************************************************************* * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * 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 @@ -45,7 +45,7 @@ public class FCD2TunerController extends FCDTunerController public FCD2TunerController(MixerTunerDataLine mixerTDL, Device device, DeviceDescriptor descriptor) { super(mixerTDL, device, descriptor, MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getDisplayString(), - MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY, MixerTunerType.FUNCUBE_DONGLE_PRO.getAudioFormat()); + MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY, MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat()); } public void init() throws SourceException