mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
1981 lines
49 KiB
Java
1981 lines
49 KiB
Java
/*******************************************************************************
|
|
* SDR Trunk
|
|
* Copyright (C) 2014 Dennis Sheirer
|
|
*
|
|
* Java version based on librtlsdr
|
|
* Copyright (C) 2012-2013 by Steve Markgraf <steve@steve-m.de>
|
|
* Copyright (C) 2012 by Dimitri Stolnikov <horiz0n@gmx.net>
|
|
*
|
|
* 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 source.tuner.rtl;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.charset.Charset;
|
|
import java.text.DecimalFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
import java.util.concurrent.LinkedTransferQueue;
|
|
import java.util.concurrent.ScheduledFuture;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
import javax.swing.JPanel;
|
|
import javax.usb.UsbDisconnectedException;
|
|
import javax.usb.UsbException;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.usb4java.Device;
|
|
import org.usb4java.DeviceDescriptor;
|
|
import org.usb4java.DeviceHandle;
|
|
import org.usb4java.LibUsb;
|
|
import org.usb4java.LibUsbException;
|
|
import org.usb4java.Transfer;
|
|
import org.usb4java.TransferCallback;
|
|
|
|
import sample.Broadcaster;
|
|
import sample.Listener;
|
|
import sample.adapter.ByteSampleAdapter;
|
|
import sample.complex.ComplexBuffer;
|
|
import source.SourceException;
|
|
import source.tuner.TunerController;
|
|
import source.tuner.TunerType;
|
|
import source.tuner.frequency.FrequencyChangeEvent;
|
|
import source.tuner.frequency.FrequencyChangeEvent.Attribute;
|
|
import buffer.FloatAveragingBuffer;
|
|
import controller.ResourceManager;
|
|
import controller.ThreadPoolManager;
|
|
import controller.ThreadPoolManager.ThreadType;
|
|
|
|
public abstract class RTL2832TunerController extends TunerController
|
|
{
|
|
private final static Logger mLog =
|
|
LoggerFactory.getLogger( RTL2832TunerController.class );
|
|
|
|
public final static int INT_NULL_VALUE = -1;
|
|
public final static long LONG_NULL_VALUE = -1l;
|
|
public final static double DOUBLE_NULL_VALUE = -1.0D;
|
|
public final static int TWO_TO_22_POWER = 4194304;
|
|
|
|
public final static byte USB_INTERFACE = (byte)0x0;
|
|
public final static byte BULK_ENDPOINT_IN = (byte)0x81;
|
|
public final static byte CONTROL_ENDPOINT_IN =
|
|
(byte)( LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR );
|
|
public final static byte CONTROL_ENDPOINT_OUT =
|
|
(byte)( LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR );
|
|
|
|
public final static long TIMEOUT_US = 1000000l; //uSeconds
|
|
|
|
public final static byte REQUEST_ZERO = (byte)0;
|
|
|
|
public final static int TRANSFER_BUFFER_POOL_SIZE = 16;
|
|
|
|
public final static byte EEPROM_ADDRESS = (byte)0xA0;
|
|
|
|
public final static byte[] sFIR_COEFFICIENTS =
|
|
{
|
|
(byte)0xCA, (byte)0xDC, (byte)0xD7, (byte)0xD8, (byte)0xE0,
|
|
(byte)0xF2, (byte)0x0E, (byte)0x35, (byte)0x06, (byte)0x50,
|
|
(byte)0x9C, (byte)0x0D, (byte)0x71, (byte)0x11, (byte)0x14,
|
|
(byte)0x71, (byte)0x74, (byte)0x19, (byte)0x41, (byte)0xA5
|
|
};
|
|
|
|
public static final SampleRate DEFAULT_SAMPLE_RATE =
|
|
SampleRate.RATE_0_960MHZ;
|
|
|
|
protected Device mDevice;
|
|
protected DeviceDescriptor mDeviceDescriptor;
|
|
protected DeviceHandle mDeviceHandle;
|
|
|
|
private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE;
|
|
|
|
private BufferProcessor mBufferProcessor;
|
|
|
|
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
|
|
|
|
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
|
|
|
private LinkedTransferQueue<byte[]> mFilledBuffers =
|
|
new LinkedTransferQueue<byte[]>();
|
|
|
|
private SampleRateMonitor mSampleRateMonitor;
|
|
private AtomicInteger mSampleCounter = new AtomicInteger();
|
|
private double mSampleRateAverageSum;
|
|
private int mSampleRateAverageCount;
|
|
private static final DecimalFormat mDecimalFormatter =
|
|
new DecimalFormat( "###,###,###.0" );
|
|
private static final DecimalFormat mPercentFormatter =
|
|
new DecimalFormat( "###.00" );
|
|
|
|
protected int mOscillatorFrequency = 28800000; //28.8 MHz
|
|
public int mBufferSize = 131072;
|
|
|
|
protected Descriptor mDescriptor;
|
|
private ThreadPoolManager mThreadPoolManager;
|
|
|
|
/**
|
|
* Abstract tuner controller device. Use the static getTunerClass() method
|
|
* to determine the tuner type, and construct the corresponding child
|
|
* tuner controller class for that tuner type.
|
|
*/
|
|
public RTL2832TunerController( Device device,
|
|
DeviceDescriptor deviceDescriptor,
|
|
ThreadPoolManager threadPoolManager,
|
|
long minTunableFrequency,
|
|
long maxTunableFrequency ) throws SourceException
|
|
{
|
|
super( minTunableFrequency, maxTunableFrequency );
|
|
|
|
mThreadPoolManager = threadPoolManager;
|
|
|
|
mDevice = device;
|
|
mDeviceDescriptor = deviceDescriptor;
|
|
}
|
|
|
|
public void init() throws SourceException
|
|
{
|
|
mDeviceHandle = new DeviceHandle();
|
|
|
|
int result = LibUsb.open( mDevice, mDeviceHandle );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
mDeviceHandle = null;
|
|
|
|
throw new SourceException( "libusb couldn't open RTL2832 usb "
|
|
+ "device [" + LibUsb.errorName( result ) + "]" );
|
|
}
|
|
|
|
claimInterface( mDeviceHandle );
|
|
|
|
try
|
|
{
|
|
setSampleRate( DEFAULT_SAMPLE_RATE );
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
throw new SourceException( "RTL2832 Tuner Controller - couldn't "
|
|
+ "set default sample rate", e );
|
|
}
|
|
|
|
byte[] eeprom = null;
|
|
|
|
try
|
|
{
|
|
/* Read the contents of the 256-byte EEPROM */
|
|
eeprom = readEEPROM( mDeviceHandle, (short)0, 256 );
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
mLog.error( "error while reading the EEPROM device descriptor", e );
|
|
}
|
|
|
|
try
|
|
{
|
|
mDescriptor = new Descriptor( eeprom );
|
|
|
|
if( eeprom == null )
|
|
{
|
|
mLog.error( "eeprom byte array was null - constructed "
|
|
+ "empty descriptor object" );
|
|
}
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
mLog.error( "error while constructing device descriptor using "
|
|
+ "descriptor byte array " +
|
|
( eeprom == null ? "[null]" : Arrays.toString( eeprom )), e );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Claims the USB interface. Attempts to detach the active kernel driver
|
|
* if one is currently attached.
|
|
*/
|
|
public static void claimInterface( DeviceHandle handle ) throws SourceException
|
|
{
|
|
if( handle != null )
|
|
{
|
|
int result = LibUsb.kernelDriverActive( handle, USB_INTERFACE );
|
|
|
|
if( result == 1 )
|
|
{
|
|
result = LibUsb.detachKernelDriver( handle, USB_INTERFACE );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
mLog.error( "failed attempt to detach kernel driver [" +
|
|
LibUsb.errorName( result ) + "]" );
|
|
|
|
throw new SourceException( "couldn't detach kernel driver "
|
|
+ "from device" );
|
|
}
|
|
}
|
|
|
|
result = LibUsb.claimInterface( handle, USB_INTERFACE );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
throw new SourceException( "couldn't claim usb interface [" +
|
|
LibUsb.errorName( result ) + "]" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new SourceException( "couldn't claim usb interface - no "
|
|
+ "device handle" );
|
|
}
|
|
}
|
|
|
|
public static void releaseInterface( DeviceHandle handle )
|
|
throws SourceException
|
|
{
|
|
int result = LibUsb.releaseInterface( handle, USB_INTERFACE );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
throw new SourceException( "couldn't release interface [" +
|
|
LibUsb.errorName( result ) + "]" );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Descriptor contains all identifiers and labels parsed from the EEPROM.
|
|
*
|
|
* May return null if unable to get 256 byte eeprom descriptor from tuner
|
|
* or if the descriptor doesn't begin with byte values of 0x28 and 0x32
|
|
* meaning it is a valid (and can be parsed) RTL2832 descriptor
|
|
*/
|
|
public Descriptor getDescriptor()
|
|
{
|
|
if( mDescriptor != null && mDescriptor.isValid() )
|
|
{
|
|
return mDescriptor;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public void setSamplingMode( SampleMode mode ) throws LibUsbException
|
|
{
|
|
switch( mode )
|
|
{
|
|
case QUADRATURE:
|
|
/* Set intermediate frequency to 0 Hz */
|
|
setIFFrequency( 0 );
|
|
|
|
/* Enable I/Q ADC Input */
|
|
writeDemodRegister( mDeviceHandle,
|
|
Page.ZERO,
|
|
(short)0x08,
|
|
(short)0xCD,
|
|
1 );
|
|
|
|
/* Enable zero-IF mode */
|
|
writeDemodRegister( mDeviceHandle,
|
|
Page.ONE,
|
|
(short)0xB1,
|
|
(short)0x1B,
|
|
1 );
|
|
|
|
/* Set default i/q path */
|
|
writeDemodRegister( mDeviceHandle,
|
|
Page.ZERO,
|
|
(short)0x06,
|
|
(short)0x80,
|
|
1 );
|
|
break;
|
|
case DIRECT:
|
|
default:
|
|
throw new LibUsbException( "QUADRATURE mode is the only mode "
|
|
+ "currently supported", LibUsb.ERROR_NOT_SUPPORTED );
|
|
}
|
|
}
|
|
|
|
public void setIFFrequency( int frequency ) throws LibUsbException
|
|
{
|
|
long ifFrequency = ( (long)TWO_TO_22_POWER * (long)frequency ) /
|
|
(long)mOscillatorFrequency * -1;
|
|
|
|
/* Write byte 2 (high) */
|
|
writeDemodRegister( mDeviceHandle,
|
|
Page.ONE,
|
|
(short)0x19,
|
|
(short)( Long.rotateRight( ifFrequency, 16 ) & 0x3F ),
|
|
1 );
|
|
|
|
/* Write byte 1 (middle) */
|
|
writeDemodRegister( mDeviceHandle,
|
|
Page.ONE,
|
|
(short)0x1A,
|
|
(short)( Long.rotateRight( ifFrequency, 8 ) & 0xFF ),
|
|
1 );
|
|
|
|
/* Write byte 0 (low) */
|
|
writeDemodRegister( mDeviceHandle,
|
|
Page.ONE,
|
|
(short)0x1B,
|
|
(short)( ifFrequency & 0xFF ),
|
|
1 );
|
|
}
|
|
|
|
public abstract void initTuner( boolean controlI2CRepeater )
|
|
throws UsbException;
|
|
|
|
/**
|
|
* Provides a unique identifier to use in distinctly identifying this
|
|
* tuner from among other tuners of the same type, so that we can fetch a
|
|
* tuner configuration from the settings manager for this specific tuner.
|
|
*
|
|
* @return serial number of the device
|
|
*/
|
|
public String getUniqueID()
|
|
{
|
|
if( mDescriptor != null && mDescriptor.hasSerial() )
|
|
{
|
|
return mDescriptor.getSerial();
|
|
}
|
|
else
|
|
{
|
|
int serial =
|
|
( 0xFF & mDeviceDescriptor.iSerialNumber() );
|
|
|
|
return "SER#" + serial;
|
|
}
|
|
}
|
|
|
|
public abstract JPanel getEditor( ResourceManager resourceManager );
|
|
|
|
public abstract void setSampleRateFilters( int sampleRate )
|
|
throws SourceException;
|
|
|
|
public abstract TunerType getTunerType();
|
|
|
|
public static TunerType identifyTunerType( Device device )
|
|
throws SourceException
|
|
{
|
|
DeviceHandle handle = new DeviceHandle();
|
|
|
|
int reason = LibUsb.open( device, handle );
|
|
|
|
if( reason != LibUsb.SUCCESS )
|
|
{
|
|
throw new SourceException( "couldn't open device - check permissions"
|
|
+ " (udev.rule) [" + LibUsb.errorName( reason ) + "]" );
|
|
}
|
|
|
|
TunerType tunerClass = TunerType.UNKNOWN;
|
|
|
|
try
|
|
{
|
|
claimInterface( handle );
|
|
|
|
/* Perform a dummy write to see if the device needs reset */
|
|
boolean resetRequired = false;
|
|
|
|
try
|
|
{
|
|
writeRegister( handle,
|
|
Block.USB,
|
|
Address.USB_SYSCTL.getAddress(),
|
|
0x09,
|
|
1 );
|
|
}
|
|
catch( LibUsbException e )
|
|
{
|
|
|
|
if( e.getErrorCode() < 0 )
|
|
{
|
|
mLog.error( "error performing dummy write - attempting "
|
|
+ "device reset", e );
|
|
|
|
resetRequired = true;
|
|
}
|
|
else
|
|
{
|
|
throw new SourceException( "error performing dummy write "
|
|
+ "to device [" + LibUsb.errorName(
|
|
e.getErrorCode() ) + "]", e );
|
|
}
|
|
}
|
|
|
|
if( resetRequired )
|
|
{
|
|
reason = LibUsb.resetDevice( handle );
|
|
|
|
try
|
|
{
|
|
writeRegister( handle,
|
|
Block.USB,
|
|
Address.USB_SYSCTL.getAddress(),
|
|
0x09,
|
|
1 );
|
|
}
|
|
catch( LibUsbException e2 )
|
|
{
|
|
mLog.error( "device reset attempted, but lost device handle. "
|
|
+ "Try restarting the application to use this device" );
|
|
|
|
throw new SourceException( "couldn't reset device" );
|
|
}
|
|
}
|
|
|
|
/* Initialize the baseband */
|
|
initBaseband( handle );
|
|
|
|
enableI2CRepeater( handle, true );
|
|
|
|
boolean controlI2CRepeater = false;
|
|
|
|
/* Test for each tuner type until we find the correct one */
|
|
if( isTuner( TunerTypeCheck.E4K, handle, controlI2CRepeater ) )
|
|
{
|
|
tunerClass = TunerType.ELONICS_E4000;
|
|
}
|
|
else if( isTuner( TunerTypeCheck.FC0013, handle, controlI2CRepeater ) )
|
|
{
|
|
tunerClass = TunerType.FITIPOWER_FC0013;
|
|
}
|
|
else if( isTuner( TunerTypeCheck.R820T, handle, controlI2CRepeater ) )
|
|
{
|
|
tunerClass = TunerType.RAFAELMICRO_R820T;
|
|
}
|
|
else if( isTuner( TunerTypeCheck.R828D, handle, controlI2CRepeater ) )
|
|
{
|
|
tunerClass = TunerType.RAFAELMICRO_R828D;
|
|
}
|
|
else if( isTuner( TunerTypeCheck.FC2580, handle, controlI2CRepeater ) )
|
|
{
|
|
tunerClass = TunerType.FCI_FC2580;
|
|
}
|
|
else if( isTuner( TunerTypeCheck.FC0012, handle, controlI2CRepeater ) )
|
|
{
|
|
tunerClass = TunerType.FITIPOWER_FC0012;
|
|
}
|
|
|
|
enableI2CRepeater( handle, false );
|
|
|
|
releaseInterface( handle );
|
|
|
|
LibUsb.close( handle );
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
mLog.error( "error while determining tuner type", e );
|
|
}
|
|
|
|
return tunerClass;
|
|
}
|
|
|
|
/**
|
|
* Releases the USB interface
|
|
*/
|
|
public void release()
|
|
{
|
|
try
|
|
{
|
|
if( mBufferProcessor.isRunning() )
|
|
{
|
|
mBufferProcessor.stop();
|
|
}
|
|
|
|
LibUsb.releaseInterface( mDeviceHandle, USB_INTERFACE );
|
|
|
|
LibUsb.exit( null );
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
mLog.error( "attempt to release USB interface failed", e );
|
|
}
|
|
}
|
|
|
|
public void resetUSBBuffer() throws LibUsbException
|
|
{
|
|
writeRegister( mDeviceHandle, Block.USB, Address.USB_EPA_CTL.getAddress(), 0x1002, 2 );
|
|
writeRegister( mDeviceHandle, Block.USB, Address.USB_EPA_CTL.getAddress(), 0x0000, 2 );
|
|
}
|
|
|
|
public static void initBaseband( DeviceHandle handle ) throws LibUsbException
|
|
{
|
|
/* Initialize USB */
|
|
writeRegister( handle, Block.USB, Address.USB_SYSCTL.getAddress(), 0x09, 1 );
|
|
writeRegister( handle, Block.USB, Address.USB_EPA_MAXPKT.getAddress(), 0x0002, 2 );
|
|
writeRegister( handle, Block.USB, Address.USB_EPA_CTL.getAddress(), 0x1002, 2 );
|
|
|
|
/* Power on demod */
|
|
writeRegister( handle, Block.SYS, Address.DEMOD_CTL_1.getAddress(), 0x22, 1 );
|
|
writeRegister( handle, Block.SYS, Address.DEMOD_CTL.getAddress(), 0xE8, 1 );
|
|
|
|
/* Reset demod */
|
|
writeDemodRegister( handle, Page.ONE, (short)0x01, 0x14, 1 ); //Bit 3 = soft reset
|
|
writeDemodRegister( handle, Page.ONE, (short)0x01, 0x10, 1 );
|
|
|
|
/* Disable spectrum inversion and adjacent channel rejection */
|
|
writeDemodRegister( handle, Page.ONE, (short)0x15, 0x00, 1 );
|
|
writeDemodRegister( handle, Page.ONE, (short)0x16, 0x0000, 2 );
|
|
|
|
/* Clear DDC shift and IF frequency registers */
|
|
writeDemodRegister( handle, Page.ONE, (short)0x16, 0x00, 1 );
|
|
writeDemodRegister( handle, Page.ONE, (short)0x17, 0x00, 1 );
|
|
writeDemodRegister( handle, Page.ONE, (short)0x18, 0x00, 1 );
|
|
writeDemodRegister( handle, Page.ONE, (short)0x19, 0x00, 1 );
|
|
writeDemodRegister( handle, Page.ONE, (short)0x1A, 0x00, 1 );
|
|
writeDemodRegister( handle, Page.ONE, (short)0x1B, 0x00, 1 );
|
|
|
|
/* Set FIR coefficients */
|
|
for( int x = 0; x < sFIR_COEFFICIENTS.length; x++ )
|
|
{
|
|
writeDemodRegister( handle,
|
|
Page.ONE,
|
|
(short)( 0x1C + x ),
|
|
sFIR_COEFFICIENTS[ x ],
|
|
1 );
|
|
}
|
|
|
|
/* Enable SDR mode, disable DAGC (bit 5) */
|
|
writeDemodRegister( handle, Page.ZERO, (short)0x19, 0x05, 1 );
|
|
|
|
/* Init FSM state-holding register */
|
|
writeDemodRegister( handle, Page.ONE, (short)0x93, 0xF0, 1 );
|
|
writeDemodRegister( handle, Page.ONE, (short)0x94, 0x0F, 1 );
|
|
|
|
/* Disable AGC (en_dagc, bit 0) (seems to have no effect) */
|
|
writeDemodRegister( handle, Page.ONE, (short)0x11, 0x00, 1 );
|
|
|
|
/* Disable RF and IF AGC loop */
|
|
writeDemodRegister( handle, Page.ONE, (short)0x04, 0x00, 1 );
|
|
|
|
/* Disable PID filter */
|
|
writeDemodRegister( handle, Page.ZERO, (short)0x61, 0x60, 1 );
|
|
|
|
/* opt_adc_iq = 0, default ADC_I/ADC_Q datapath */
|
|
writeDemodRegister( handle, Page.ZERO, (short)0x06, 0x80, 1 );
|
|
|
|
/* Enable Zero-if mode (en_bbin bit),
|
|
* DC cancellation (en_dc_est),
|
|
* IQ estimation/compensation (en_iq_comp, en_iq_est) */
|
|
writeDemodRegister( handle, Page.ONE, (short)0xB1, 0x1B, 1 );
|
|
|
|
/* Disable 4.096 MHz clock output on pin TP_CK0 */
|
|
writeDemodRegister( handle, Page.ZERO, (short)0x0D, 0x83, 1 );
|
|
}
|
|
|
|
protected void deinitBaseband( DeviceHandle handle )
|
|
throws IllegalArgumentException, UsbDisconnectedException, UsbException
|
|
{
|
|
writeRegister( handle, Block.SYS, Address.DEMOD_CTL.getAddress(), 0x20, 1);
|
|
}
|
|
|
|
/**
|
|
* Sets the General Purpose Input/Output (GPIO) register bit
|
|
*
|
|
* @param handle - USB tuner device
|
|
* @param bitMask - bit mask with one for targeted register bits and zero
|
|
* for the non-targeted register bits
|
|
* @param enabled - true to set the bit and false to clear the bit
|
|
* @throws UsbDisconnectedException - if the tuner device is disconnected
|
|
* @throws UsbException - if there is a USB error while communicating with
|
|
* the device
|
|
*/
|
|
protected static void setGPIOBit( DeviceHandle handle,
|
|
byte bitMask,
|
|
boolean enabled ) throws LibUsbException
|
|
{
|
|
//Get current register value
|
|
int value = readRegister( handle, Block.SYS, Address.GPO.getAddress(), 1 );
|
|
|
|
//Update the masked bits
|
|
if( enabled )
|
|
{
|
|
value |= bitMask;
|
|
}
|
|
else
|
|
{
|
|
value &= ~bitMask;
|
|
}
|
|
|
|
//Write the change back to the device
|
|
writeRegister( handle, Block.SYS, Address.GPO.getAddress(), value, 1 );
|
|
}
|
|
|
|
/**
|
|
* Enables GPIO Output
|
|
* @param handle - usb tuner device
|
|
* @param bitMask - mask containing one bit value in targeted bit field(s)
|
|
* @throws UsbDisconnectedException
|
|
* @throws UsbException
|
|
*/
|
|
protected static void setGPIOOutput( DeviceHandle handle, byte bitMask )
|
|
throws LibUsbException
|
|
{
|
|
//Get current register value
|
|
int value = readRegister( handle, Block.SYS, Address.GPD.getAddress(), 1 );
|
|
|
|
//Mask the value and rewrite it
|
|
writeRegister( handle, Block.SYS, Address.GPO.getAddress(),
|
|
value & ~bitMask, 1 );
|
|
|
|
//Get current register value
|
|
value = readRegister( handle, Block.SYS, Address.GPOE.getAddress(), 1 );
|
|
|
|
//Mask the value and rewrite it
|
|
writeRegister( handle, Block.SYS, Address.GPOE.getAddress(),
|
|
value | bitMask, 1 );
|
|
}
|
|
|
|
protected static void enableI2CRepeater( DeviceHandle handle,
|
|
boolean enabled )
|
|
throws LibUsbException
|
|
{
|
|
Page page = Page.ONE;
|
|
short address = 1;
|
|
int value;
|
|
|
|
if( enabled )
|
|
{
|
|
value = 0x18; //ON
|
|
}
|
|
else
|
|
{
|
|
value = 0x10; //OFF
|
|
}
|
|
|
|
writeDemodRegister( handle, page, address, value, 1 );
|
|
}
|
|
|
|
protected boolean isI2CRepeaterEnabled() throws SourceException
|
|
{
|
|
int register = readDemodRegister( mDeviceHandle, Page.ONE, (short)0x1, 1 );
|
|
|
|
return register == 0x18;
|
|
}
|
|
|
|
protected static int readI2CRegister( DeviceHandle handle,
|
|
byte i2CAddress,
|
|
byte i2CRegister,
|
|
boolean controlI2CRepeater )
|
|
throws LibUsbException
|
|
{
|
|
short address = (short)( i2CAddress & 0xFF );
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect( 1 );
|
|
buffer.put( i2CRegister );
|
|
buffer.rewind();
|
|
|
|
ByteBuffer data = ByteBuffer.allocateDirect( 1 );
|
|
|
|
if( controlI2CRepeater )
|
|
{
|
|
enableI2CRepeater( handle, true );
|
|
|
|
write( handle, address, Block.I2C, buffer );
|
|
read( handle, address, Block.I2C, data );
|
|
|
|
enableI2CRepeater( handle, false );
|
|
}
|
|
else
|
|
{
|
|
write( handle, address, Block.I2C, buffer );
|
|
read( handle, address, Block.I2C, data );
|
|
}
|
|
|
|
return (int)( data.get() & 0xFF );
|
|
}
|
|
|
|
protected void writeI2CRegister( DeviceHandle handle,
|
|
byte i2CAddress,
|
|
byte i2CRegister,
|
|
byte value,
|
|
boolean controlI2CRepeater ) throws LibUsbException
|
|
{
|
|
|
|
short address = (short)( i2CAddress & 0xFF );
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect( 2 );
|
|
buffer.put( i2CRegister );
|
|
buffer.put( value );
|
|
|
|
buffer.rewind();
|
|
|
|
if( controlI2CRepeater )
|
|
{
|
|
enableI2CRepeater( handle, true );
|
|
write( handle, address, Block.I2C, buffer );
|
|
enableI2CRepeater( handle, false );
|
|
}
|
|
else
|
|
{
|
|
write( handle, address, Block.I2C, buffer );
|
|
}
|
|
}
|
|
|
|
protected static void writeDemodRegister( DeviceHandle handle,
|
|
Page page,
|
|
short address,
|
|
int value,
|
|
int length ) throws LibUsbException
|
|
{
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect( length );
|
|
buffer.order( ByteOrder.BIG_ENDIAN );
|
|
|
|
if( length == 1 )
|
|
{
|
|
buffer.put( (byte)( value & 0xFF ) );
|
|
}
|
|
else if( length == 2 )
|
|
{
|
|
buffer.putShort( (short)( value & 0xFFFF ) );
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalArgumentException( "Cannot write value greater "
|
|
+ "than 16 bits to the register - length [" + length + "]" );
|
|
}
|
|
|
|
short index = (short)( 0x10 | page.getPage() );
|
|
|
|
short newAddress = (short)( address << 8 | 0x20 );
|
|
|
|
write( handle, newAddress, index, buffer );
|
|
|
|
readDemodRegister( handle, Page.TEN, (short)1, length );
|
|
}
|
|
|
|
protected static int readDemodRegister( DeviceHandle handle,
|
|
Page page,
|
|
short address,
|
|
int length ) throws LibUsbException
|
|
{
|
|
short index = page.getPage();
|
|
short newAddress = (short)( ( address << 8 ) | 0x20 );
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect( length );
|
|
|
|
read( handle, newAddress, index, buffer );
|
|
buffer.order( ByteOrder.LITTLE_ENDIAN );
|
|
|
|
if( length == 2 )
|
|
{
|
|
return (int)( buffer.getShort() & 0xFFFF );
|
|
}
|
|
else
|
|
{
|
|
return (int)( buffer.get() & 0xFF );
|
|
}
|
|
}
|
|
|
|
protected static void writeRegister( DeviceHandle handle,
|
|
Block block,
|
|
short address,
|
|
int value,
|
|
int length ) throws LibUsbException
|
|
{
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect( length );
|
|
buffer.order( ByteOrder.BIG_ENDIAN );
|
|
|
|
if( length == 1 )
|
|
{
|
|
buffer.put( (byte)( value & 0xFF ) ) ;
|
|
}
|
|
else if( length == 2 )
|
|
{
|
|
buffer.putShort( (short)value );
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalArgumentException( "Cannot write value greater "
|
|
+ "than 16 bits to the register - length [" + length + "]" );
|
|
}
|
|
|
|
buffer.rewind();
|
|
|
|
write( handle, address, block, buffer );
|
|
}
|
|
|
|
protected static int readRegister( DeviceHandle handle,
|
|
Block block,
|
|
short address,
|
|
int length ) throws LibUsbException
|
|
{
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect( 2 );
|
|
|
|
read( handle, address, block, buffer );
|
|
|
|
buffer.order( ByteOrder.LITTLE_ENDIAN );
|
|
|
|
if( length == 2 )
|
|
{
|
|
return (int)( buffer.getShort() & 0xFFFF );
|
|
}
|
|
else
|
|
{
|
|
return (int)( buffer.get() & 0xFF );
|
|
}
|
|
}
|
|
|
|
/**
|
|
*/
|
|
protected static void write( DeviceHandle handle,
|
|
short address,
|
|
Block block,
|
|
ByteBuffer buffer ) throws LibUsbException
|
|
{
|
|
write( handle, address, block.getWriteIndex(), buffer );
|
|
}
|
|
|
|
protected static void write( DeviceHandle handle,
|
|
short value,
|
|
short index,
|
|
ByteBuffer buffer ) throws LibUsbException
|
|
{
|
|
if( handle != null )
|
|
{
|
|
int transferred = LibUsb.controlTransfer( handle,
|
|
CONTROL_ENDPOINT_OUT,
|
|
REQUEST_ZERO,
|
|
value,
|
|
index,
|
|
buffer,
|
|
TIMEOUT_US );
|
|
|
|
if( transferred < 0 )
|
|
{
|
|
throw new LibUsbException( "error writing byte buffer",
|
|
transferred );
|
|
}
|
|
else if( transferred != buffer.capacity() )
|
|
{
|
|
throw new LibUsbException( "transferred bytes [" +
|
|
transferred + "] is not what was expected [" +
|
|
buffer.capacity() + "]", transferred );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new LibUsbException( "device handle is null",
|
|
LibUsb.ERROR_NO_DEVICE );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs a control type read
|
|
*/
|
|
protected static void read( DeviceHandle handle,
|
|
short address,
|
|
short index,
|
|
ByteBuffer buffer ) throws LibUsbException
|
|
{
|
|
if( handle != null )
|
|
{
|
|
int transferred = LibUsb.controlTransfer( handle,
|
|
CONTROL_ENDPOINT_IN,
|
|
REQUEST_ZERO,
|
|
address,
|
|
index,
|
|
buffer,
|
|
TIMEOUT_US );
|
|
|
|
if( transferred < 0 )
|
|
{
|
|
throw new LibUsbException( "read error", transferred );
|
|
}
|
|
else if( transferred != buffer.capacity() )
|
|
{
|
|
throw new LibUsbException( "transferred bytes [" +
|
|
transferred + "] is not what was expected [" +
|
|
buffer.capacity() + "]", transferred );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new LibUsbException( "device handle is null",
|
|
LibUsb.ERROR_NO_DEVICE );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads byte array from index at the address.
|
|
*
|
|
* @return big-endian byte array (needs to be swapped to be usable)
|
|
*
|
|
*/
|
|
protected static void read( DeviceHandle handle,
|
|
short address,
|
|
Block block,
|
|
ByteBuffer buffer ) throws LibUsbException
|
|
{
|
|
read( handle, address, block.getReadIndex(), buffer );
|
|
}
|
|
|
|
/**
|
|
* Tests if the specified tuner type is contained in the usb tuner device.
|
|
*
|
|
* @param type - tuner type to test for
|
|
* @param handle - handle to the usb tuner device
|
|
* @param controlI2CRepeater - indicates if the method should control the
|
|
* I2C repeater independently
|
|
*
|
|
* @return - true if the device is the specified tuner type
|
|
*/
|
|
protected static boolean isTuner( TunerTypeCheck type,
|
|
DeviceHandle handle,
|
|
boolean controlI2CRepeater )
|
|
{
|
|
try
|
|
{
|
|
if( type == TunerTypeCheck.FC0012 ||
|
|
type == TunerTypeCheck.FC2580 )
|
|
{
|
|
/* Initialize the GPIOs */
|
|
setGPIOOutput( handle, (byte)0x20 );
|
|
|
|
/* Reset tuner before probing */
|
|
setGPIOBit( handle, (byte)0x20, true );
|
|
setGPIOBit( handle, (byte)0x20, false );
|
|
}
|
|
|
|
int value = readI2CRegister( handle, type.getI2CAddress(),
|
|
type.getCheckAddress(), controlI2CRepeater );
|
|
|
|
if( type == TunerTypeCheck.FC2580 )
|
|
{
|
|
return ( ( value & 0x7F ) == type.getCheckValue() );
|
|
}
|
|
else
|
|
{
|
|
return ( value == type.getCheckValue() );
|
|
}
|
|
}
|
|
catch( LibUsbException e )
|
|
{
|
|
//Do nothing ... it's not the specified tuner
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public int getCurrentSampleRate() throws SourceException
|
|
{
|
|
return mSampleRate.getRate();
|
|
}
|
|
|
|
public int getSampleRate() throws SourceException
|
|
{
|
|
try
|
|
{
|
|
int high= readDemodRegister( mDeviceHandle, Page.ONE, (short)0x9F, 2 );
|
|
int low = readDemodRegister( mDeviceHandle, Page.ONE, (short)0xA1, 2 );
|
|
|
|
int ratio = Integer.rotateLeft( high, 16 ) | low;
|
|
|
|
int rate = (int)( mOscillatorFrequency * TWO_TO_22_POWER / ratio );
|
|
|
|
SampleRate sampleRate = SampleRate.getClosest( rate );
|
|
|
|
/* If we're not currently set to this rate, set it as the current rate */
|
|
if( sampleRate.getRate() != rate )
|
|
{
|
|
setSampleRate( sampleRate );
|
|
|
|
return sampleRate.getRate();
|
|
}
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
throw new SourceException( "RTL2832 Tuner Controller - cannot get "
|
|
+ "current sample rate", e );
|
|
}
|
|
|
|
return DEFAULT_SAMPLE_RATE.getRate();
|
|
}
|
|
|
|
public void setSampleRate( SampleRate sampleRate ) throws SourceException
|
|
{
|
|
/* Write high-order 16-bits of sample rate ratio to demod register */
|
|
writeDemodRegister( mDeviceHandle, Page.ONE, (short)0x9F,
|
|
sampleRate.getRatioHighBits(), 2 );
|
|
|
|
/* Write low-order 16-bits of sample rate ratio to demod register */
|
|
writeDemodRegister( mDeviceHandle, Page.ONE, (short)0xA1,
|
|
sampleRate.getRatioLowBits(), 2 );
|
|
|
|
/* Set sample rate correction to 0 */
|
|
setSampleRateFrequencyCorrection( 0 );
|
|
|
|
/* Reset the demod for the changes to take effect */
|
|
writeDemodRegister( mDeviceHandle, Page.ONE, (short)0x01, 0x14, 1 );
|
|
writeDemodRegister( mDeviceHandle, Page.ONE, (short)0x01, 0x10, 1 );
|
|
|
|
/* Apply any tuner specific sample rate filter settings */
|
|
setSampleRateFilters( sampleRate.getRate() );
|
|
|
|
mSampleRate = sampleRate;
|
|
|
|
mFrequencyController.setSampleRate( sampleRate.getRate() );
|
|
|
|
if( mSampleRateMonitor != null )
|
|
{
|
|
mSampleRateMonitor.setSampleRate( mSampleRate.getRate() );
|
|
}
|
|
}
|
|
|
|
public void setSampleRateFrequencyCorrection( int ppm ) throws SourceException
|
|
{
|
|
int offset = -ppm * TWO_TO_22_POWER / 1000000;
|
|
|
|
writeDemodRegister( mDeviceHandle,
|
|
Page.ONE,
|
|
(short)0x3F,
|
|
( offset & 0xFF ),
|
|
1 );
|
|
writeDemodRegister( mDeviceHandle,
|
|
Page.ONE,
|
|
(short)0x3E,
|
|
( Integer.rotateRight( offset, 8 ) & 0xFF ),
|
|
1 );
|
|
/* Test to retune controller to apply frequency correction */
|
|
try
|
|
{
|
|
mFrequencyController.setFrequency( mFrequencyController.getFrequency() );
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
throw new SourceException( "couldn't set sample rate frequency correction", e );
|
|
}
|
|
}
|
|
|
|
public int getSampleRateFrequencyCorrection() throws UsbException
|
|
{
|
|
int high = readDemodRegister( mDeviceHandle, Page.ONE, (short)0x3E, 1 );
|
|
int low = readDemodRegister( mDeviceHandle, Page.ONE, (short)0x3F, 1 );
|
|
|
|
return ( Integer.rotateLeft( high, 8 ) | low );
|
|
}
|
|
|
|
/**
|
|
* Returns contents of the 256-byte EEPROM. The contents are as follows:
|
|
*
|
|
* 256-byte EEPROM (in hex):
|
|
* 00/01 - 2832 Signature
|
|
* 03/02 - 0BDA Vendor ID
|
|
* 05/04 - 2832 Product ID
|
|
* 06 - A5 (has serial id?)
|
|
* 07 - 16 (bit field - bit 0 = remote wakeup, bit 1 = IR enabled
|
|
* 08 - 02 or 12
|
|
* 10/09 0310 ETX(0x03) plus label length (includes length and ETX bytes)
|
|
* 12/11 First UTF-16 character
|
|
* 14/13 Second UTF-16 character ...
|
|
*
|
|
* Label 1: vendor
|
|
* Label 2: product
|
|
* Label 3: serial
|
|
* Label 4,5 ... (user defined)
|
|
*/
|
|
public byte[] readEEPROM( DeviceHandle handle, short offset, int length )
|
|
throws IllegalArgumentException
|
|
{
|
|
if( offset + length > 256 )
|
|
{
|
|
throw new IllegalArgumentException( "cannot read more than 256 "
|
|
+ "bytes from EEPROM - requested to read to byte [" +
|
|
( offset + length ) + "]" );
|
|
}
|
|
|
|
byte[] data = new byte[ length ];
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect( 1 );
|
|
|
|
try
|
|
{
|
|
/* Tell the RTL-2832 to address the EEPROM */
|
|
writeRegister( handle, Block.I2C, EEPROM_ADDRESS, (byte)offset, 1 );
|
|
}
|
|
catch( LibUsbException e )
|
|
{
|
|
mLog.error( "usb error while attempting to set read address to "
|
|
+ "EEPROM register, prior to reading the EEPROM device "
|
|
+ "descriptor", e );
|
|
}
|
|
|
|
for( int x = 0; x < length; x++ )
|
|
{
|
|
try
|
|
{
|
|
read( handle, EEPROM_ADDRESS, Block.I2C, buffer );
|
|
data[ x ] = buffer.get();
|
|
buffer.rewind();
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
mLog.error( "error while reading eeprom byte [" + x + "/" +
|
|
length + "] aborting eeprom read and returning partially "
|
|
+ "filled descriptor byte array", e );
|
|
x = length;
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Writes a single byte to the 256-byte EEPROM using the specified offset.
|
|
*
|
|
* Note: introduce a 5 millisecond delay between each successive write to
|
|
* the EEPROM or subsequent writes may fail.
|
|
*/
|
|
public void writeEEPROMByte( DeviceHandle handle, byte offset, byte value )
|
|
throws IllegalArgumentException, UsbDisconnectedException, UsbException
|
|
{
|
|
if( offset < 0 || offset > 255 )
|
|
{
|
|
throw new IllegalArgumentException( "RTL2832 Tuner Controller - "
|
|
+ "EEPROM offset must be within range of 0 - 255" );
|
|
}
|
|
|
|
int offsetAndValue = Integer.rotateLeft( ( 0xFF & offset ), 8 ) |
|
|
( 0xFF & value );
|
|
|
|
writeRegister( handle, Block.I2C, EEPROM_ADDRESS, offsetAndValue, 2 );
|
|
}
|
|
|
|
public enum Address
|
|
{
|
|
USB_SYSCTL( 0x2000 ),
|
|
USB_CTRL( 0x2010 ),
|
|
USB_STAT( 0x2014 ),
|
|
USB_EPA_CFG( 0x2144 ),
|
|
USB_EPA_CTL( 0x2148 ),
|
|
USB_EPA_MAXPKT( 0x2158 ),
|
|
USB_EPA_MAXPKT_2( 0x215A ),
|
|
USB_EPA_FIFO_CFG( 0x2160 ),
|
|
DEMOD_CTL( 0x3000 ),
|
|
GPO( 0x3001 ),
|
|
GPI( 0x3002 ),
|
|
GPOE( 0x3003 ),
|
|
GPD( 0x3004 ),
|
|
SYSINTE( 0x3005 ),
|
|
SYSINTS( 0x3006 ),
|
|
GP_CFG0( 0x3007 ),
|
|
GP_CFG1( 0x3008 ),
|
|
SYSINTE_1( 0x3009 ),
|
|
SYSINTS_1( 0x300A ),
|
|
DEMOD_CTL_1( 0x300B ),
|
|
IR_SUSPEND( 0x300C );
|
|
|
|
private int mAddress;
|
|
|
|
private Address( int address )
|
|
{
|
|
mAddress = address;
|
|
}
|
|
|
|
public short getAddress()
|
|
{
|
|
return (short)mAddress;
|
|
}
|
|
}
|
|
|
|
public enum Page
|
|
{
|
|
ZERO( 0x0 ),
|
|
ONE( 0x1 ),
|
|
TEN( 0xA );
|
|
|
|
private int mPage;
|
|
|
|
private Page( int page )
|
|
{
|
|
mPage = page;
|
|
}
|
|
|
|
public byte getPage()
|
|
{
|
|
return (byte)( mPage & 0xFF );
|
|
}
|
|
}
|
|
|
|
public enum SampleMode
|
|
{
|
|
QUADRATURE, DIRECT;
|
|
}
|
|
|
|
public enum Block
|
|
{
|
|
DEMOD( 0 ),
|
|
USB( 1 ),
|
|
SYS( 2 ),
|
|
TUN( 3 ),
|
|
ROM( 4 ),
|
|
IR( 5 ),
|
|
I2C( 6 ); //I2C controller
|
|
|
|
private int mValue;
|
|
|
|
private Block( int value )
|
|
{
|
|
mValue = value;
|
|
}
|
|
|
|
public int getValue()
|
|
{
|
|
return mValue;
|
|
}
|
|
|
|
/**
|
|
* Returns the value left shifted 8 bits
|
|
*/
|
|
public short getReadIndex()
|
|
{
|
|
return (short)Integer.rotateLeft( mValue, 8 );
|
|
}
|
|
|
|
public short getWriteIndex()
|
|
{
|
|
return (short)( getReadIndex() | 0x10 );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sample rates supported by the RTL-2832.
|
|
*
|
|
* Formula to calculate the ratio value:
|
|
*
|
|
* ratio = ( ( crystal_frequency * 2^22 ) / sample_rate ) & ~3
|
|
*
|
|
* Default crystal_frequency is 28,800,000
|
|
*
|
|
* This produces a 32-bit value that has to be set in 2 x 16-bit registers.
|
|
* Place the high 16-bit value in ratioMSB and the low 16-bit value in
|
|
* ratioLSB. Use integer for these values to avoid sign-extension issues.
|
|
*
|
|
* Mask the value with 0xFFFF when setting the register.
|
|
*/
|
|
public enum SampleRate
|
|
{
|
|
/* Note: sample rates below 1.0MHz are subject to aliasing */
|
|
RATE_0_240MHZ( 0x0DFC, 0x0000, 240000, "0.240 MHz" ),
|
|
RATE_0_288MHZ( 0x08FC, 0x0000, 288000, "0.288 MHz" ),
|
|
RATE_0_912MHZ( 0x07E4, 0x0000, 912000, "0.912 MHz" ),
|
|
RATE_0_960MHZ( 0x0778, 0x0000, 960000, "0.960 MHz" ),
|
|
RATE_1_200MHZ( 0x05F4, 0x0000, 1200000, "1.200 MHz" ),
|
|
RATE_1_440MHZ( 0x04FC, 0x0000, 1440000, "1.440 MHz" ),
|
|
RATE_1_680MHZ( 0x0448, 0x0000, 1680000, "1.680 MHz" ),
|
|
RATE_1_824MHZ( 0x03F0, 0x0000, 1824000, "1.824 MHz" ),
|
|
RATE_2_016MHZ( 0x038C, 0x0000, 2016000, "2.016 MHz" ),
|
|
RATE_2_208MHZ( 0x0340, 0x0000, 2208000, "2.208 MHz" ),
|
|
RATE_2_400MHZ( 0x02FC, 0x0000, 2400000, "2.400 MHz" ),
|
|
RATE_2_640MHZ( 0x02B4, 0x8000, 2640000, "2.640 MHz" ),
|
|
RATE_2_880MHZ( 0x027C, 0x0000, 2880000, "2.880 MHz" );
|
|
|
|
private int mRatioHigh;
|
|
private int mRatioLow;
|
|
private int mRate;
|
|
private String mLabel;
|
|
|
|
private SampleRate( int ratioHigh,
|
|
int ratioLow,
|
|
int rate,
|
|
String label )
|
|
{
|
|
mRatioHigh = ratioHigh;
|
|
mRatioLow = ratioLow;
|
|
mRate = rate;
|
|
mLabel = label;
|
|
}
|
|
|
|
public int getRatioHighBits()
|
|
{
|
|
return mRatioHigh;
|
|
}
|
|
|
|
public int getRatioLowBits()
|
|
{
|
|
return mRatioLow;
|
|
}
|
|
|
|
public int getRate()
|
|
{
|
|
return mRate;
|
|
}
|
|
|
|
public String getLabel()
|
|
{
|
|
return mLabel;
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
return mLabel;
|
|
}
|
|
|
|
/**
|
|
* Returns the sample rate that is equal to the argument or the next
|
|
* higher sample rate
|
|
* @param sampleRate
|
|
* @return
|
|
*/
|
|
public static SampleRate getClosest( int sampleRate )
|
|
{
|
|
for( SampleRate rate: values () )
|
|
{
|
|
if( rate.getRate() >= sampleRate )
|
|
{
|
|
return rate;
|
|
}
|
|
}
|
|
|
|
return DEFAULT_SAMPLE_RATE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a sample listener. If the buffer processing thread is
|
|
* not currently running, starts it running in a new thread.
|
|
*/
|
|
public void addListener( Listener<ComplexBuffer> listener )
|
|
{
|
|
mComplexBufferBroadcaster.addListener( listener );
|
|
|
|
if( mBufferProcessor == null )
|
|
{
|
|
mBufferProcessor = new BufferProcessor( mThreadPoolManager );
|
|
}
|
|
|
|
if( !mBufferProcessor.isRunning() )
|
|
{
|
|
Thread thread = new Thread( mBufferProcessor );
|
|
thread.setDaemon( true );
|
|
thread.setName( "RTL2832 Sample Processor" );
|
|
|
|
thread.start();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes the sample listener. If this is the last registered listener,
|
|
* shuts down the buffer processing thread.
|
|
*/
|
|
public void removeListener( Listener<ComplexBuffer> listener )
|
|
{
|
|
mComplexBufferBroadcaster.removeListener( listener );
|
|
|
|
if( !mComplexBufferBroadcaster.hasListeners() )
|
|
{
|
|
mBufferProcessor.stop();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Buffer processing thread. Fetches samples from the RTL2832 Tuner and
|
|
* dispatches them to all registered listeners
|
|
*/
|
|
public class BufferProcessor implements Runnable, TransferCallback
|
|
{
|
|
private ScheduledFuture<?> mSampleDispatcherTask;
|
|
private CopyOnWriteArrayList<Transfer> mTransfers;
|
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
|
private ThreadPoolManager mThreadPoolManager;
|
|
|
|
public BufferProcessor( ThreadPoolManager manager )
|
|
{
|
|
mThreadPoolManager = manager;
|
|
}
|
|
|
|
@Override
|
|
public void run()
|
|
{
|
|
if( mRunning.compareAndSet( false, true ) )
|
|
{
|
|
try
|
|
{
|
|
setSampleRate( mSampleRate );
|
|
|
|
resetUSBBuffer();
|
|
}
|
|
catch( SourceException e )
|
|
{
|
|
mLog.error( "couldn't start buffer processor", e );
|
|
|
|
mRunning.set( false );
|
|
}
|
|
|
|
prepareTransfers();
|
|
|
|
for( Transfer transfer: mTransfers )
|
|
{
|
|
int result = LibUsb.submitTransfer( transfer );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
mLog.error( "error submitting transfer [" +
|
|
LibUsb.errorName( result ) + "]" );
|
|
break;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
mSampleDispatcherTask = mThreadPoolManager.scheduleFixedRate(
|
|
ThreadType.SOURCE_SAMPLE_PROCESSING, new BufferDispatcher(),
|
|
20, TimeUnit.MILLISECONDS );
|
|
}
|
|
catch( NullPointerException npe )
|
|
{
|
|
mLog.error( "NPE! = tpm null: " + ( mThreadPoolManager == null ), npe );
|
|
}
|
|
|
|
|
|
while( mRunning.get() )
|
|
{
|
|
ByteBuffer completed = ByteBuffer.allocateDirect( 4 );
|
|
|
|
int result = LibUsb.handleEventsTimeoutCompleted(
|
|
null, TIMEOUT_US, completed.asIntBuffer() );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
mLog.error( "error handling events for libusb" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops the sample fetching thread
|
|
*/
|
|
public void stop()
|
|
{
|
|
if( mRunning.compareAndSet( true, false ) )
|
|
{
|
|
cancel();
|
|
|
|
if( mSampleDispatcherTask != null )
|
|
{
|
|
mThreadPoolManager.cancel( mSampleDispatcherTask );
|
|
mSampleDispatcherTask = null;
|
|
mFilledBuffers.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicates if this thread is running
|
|
*/
|
|
public boolean isRunning()
|
|
{
|
|
return mRunning.get();
|
|
}
|
|
|
|
@Override
|
|
public void processTransfer( Transfer transfer )
|
|
{
|
|
if( transfer.status() == LibUsb.TRANSFER_COMPLETED )
|
|
{
|
|
ByteBuffer buffer = transfer.buffer();
|
|
|
|
byte[] data = new byte[ transfer.actualLength() ];
|
|
|
|
buffer.get( data );
|
|
|
|
buffer.rewind();
|
|
|
|
mFilledBuffers.add( data );
|
|
|
|
mSampleCounter.addAndGet( transfer.actualLength() );
|
|
|
|
if( !isRunning() )
|
|
{
|
|
LibUsb.cancelTransfer( transfer );
|
|
}
|
|
}
|
|
|
|
switch( transfer.status() )
|
|
{
|
|
case LibUsb.TRANSFER_COMPLETED:
|
|
/* resubmit the transfer */
|
|
int result = LibUsb.submitTransfer( transfer );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
mLog.error( "couldn't resubmit buffer transfer to tuner" );
|
|
LibUsb.freeTransfer( transfer );
|
|
mTransfers.remove( transfer );
|
|
}
|
|
break;
|
|
case LibUsb.TRANSFER_CANCELLED:
|
|
/* free the transfer and remove it */
|
|
LibUsb.freeTransfer( transfer );
|
|
mTransfers.remove( transfer );
|
|
break;
|
|
default:
|
|
/* unexpected error */
|
|
mLog.error( "transfer error [" +
|
|
getTransferStatus( transfer.status() ) +
|
|
"] transferred actual: " + transfer.actualLength() );
|
|
}
|
|
}
|
|
|
|
private void cancel()
|
|
{
|
|
for( Transfer transfer: mTransfers )
|
|
{
|
|
LibUsb.cancelTransfer( transfer );
|
|
}
|
|
|
|
ByteBuffer completed = ByteBuffer.allocateDirect( 4 );
|
|
|
|
int result = LibUsb.handleEventsTimeoutCompleted(
|
|
null, TIMEOUT_US, completed.asIntBuffer() );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
mLog.error( "error handling usb events during cancel [" +
|
|
LibUsb.errorName( result ) + "]" );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prepares (allocates) a set of transfer buffers for use in
|
|
* transferring data from the tuner via the bulk interface
|
|
*/
|
|
private void prepareTransfers() throws LibUsbException
|
|
{
|
|
mTransfers = new CopyOnWriteArrayList<Transfer>();
|
|
|
|
for( int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++ )
|
|
{
|
|
Transfer transfer = LibUsb.allocTransfer();
|
|
|
|
if( transfer == null )
|
|
{
|
|
throw new LibUsbException( "couldn't allocate transfer",
|
|
LibUsb.ERROR_NO_MEM );
|
|
}
|
|
|
|
final ByteBuffer buffer =
|
|
ByteBuffer.allocateDirect( mBufferSize );
|
|
|
|
LibUsb.fillBulkTransfer( transfer,
|
|
mDeviceHandle,
|
|
BULK_ENDPOINT_IN,
|
|
buffer,
|
|
BufferProcessor.this,
|
|
"Buffer #" + x,
|
|
TIMEOUT_US );
|
|
|
|
mTransfers.add( transfer );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
|
|
* array and broadcasts the array to all registered listeners
|
|
*/
|
|
public class BufferDispatcher implements Runnable
|
|
{
|
|
@Override
|
|
public void run()
|
|
{
|
|
try
|
|
{
|
|
ArrayList<byte[]> buffers = new ArrayList<byte[]>();
|
|
|
|
mFilledBuffers.drainTo( buffers );
|
|
|
|
for( byte[] buffer: buffers )
|
|
{
|
|
float[] samples = mSampleAdapter.convert( buffer );
|
|
|
|
mComplexBufferBroadcaster.broadcast( new ComplexBuffer( samples ) );
|
|
}
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
mLog.error( "error duing rtl2832 buffer dispatcher run", e );
|
|
}
|
|
}
|
|
}
|
|
|
|
public static String getTransferStatus( int status )
|
|
{
|
|
switch( status )
|
|
{
|
|
case 0:
|
|
return "TRANSFER COMPLETED (0)";
|
|
case 1:
|
|
return "TRANSFER ERROR (1)";
|
|
case 2:
|
|
return "TRANSFER TIMED OUT (2)";
|
|
case 3:
|
|
return "TRANSFER CANCELLED (3)";
|
|
case 4:
|
|
return "TRANSFER STALL (4)";
|
|
case 5:
|
|
return "TRANSFER NO DEVICE (5)";
|
|
case 6:
|
|
return "TRANSFER OVERFLOW (6)";
|
|
default:
|
|
return "UNKNOWN TRANSFER STATUS (" + status + ")";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Averages the sample rate over a 10-second period. The count is for the
|
|
* number of bytes received from the tuner. There are two bytes for each
|
|
* sample. So, we divide by 20 to get the average sample rate.
|
|
*/
|
|
public class SampleRateMonitor implements Runnable
|
|
{
|
|
private static final int BUFFER_SIZE = 5;
|
|
private int mTargetSampleRate;
|
|
private int mSampleRateMinimum;
|
|
private int mSampleRateMaximum;
|
|
private int mNewTargetSampleRate;
|
|
private FloatAveragingBuffer mRateErrorBuffer =
|
|
new FloatAveragingBuffer( BUFFER_SIZE );
|
|
private AtomicBoolean mRateChanged = new AtomicBoolean();
|
|
|
|
public SampleRateMonitor( int sampleRate )
|
|
{
|
|
setTargetRate( sampleRate );
|
|
}
|
|
|
|
public void setSampleRate( int sampleRate )
|
|
{
|
|
mNewTargetSampleRate = sampleRate;
|
|
mRateChanged.set( true );
|
|
}
|
|
|
|
private void setTargetRate( int rate )
|
|
{
|
|
mTargetSampleRate = rate;
|
|
mSampleRateMinimum = (int)( (float)mTargetSampleRate * 0.95f );
|
|
mSampleRateMaximum = (int)( (float)mTargetSampleRate * 1.05f );
|
|
|
|
for( int x = 0; x < BUFFER_SIZE; x++ )
|
|
{
|
|
mRateErrorBuffer.get( 0 );
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void run()
|
|
{
|
|
if( mRateChanged.compareAndSet( true, false ) )
|
|
{
|
|
setTargetRate( mNewTargetSampleRate );
|
|
|
|
/* Reset the sample counter */
|
|
mSampleCounter.set( 0 );
|
|
|
|
mLog.info( "monitor reset for new sample rate [" + mTargetSampleRate + "]" );
|
|
}
|
|
else
|
|
{
|
|
int count = mSampleCounter.getAndSet( 0 );
|
|
|
|
float current = (float)count / 20.0f;
|
|
|
|
/**
|
|
* Only accept values +/- 5% of target rate
|
|
*/
|
|
float average;
|
|
|
|
if( mSampleRateMinimum < current && current < mSampleRateMaximum )
|
|
{
|
|
average = mRateErrorBuffer.get( (float)mTargetSampleRate - current );
|
|
|
|
/* broadcast an actual sample rate update */
|
|
if( mFrequencyController != null )
|
|
{
|
|
mFrequencyController.broadcastFrequencyChangeEvent(
|
|
new FrequencyChangeEvent(
|
|
Attribute.SAMPLE_RATE_ERROR, (int)average ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
average = mTargetSampleRate;
|
|
}
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.append( "[" );
|
|
if( mDescriptor != null )
|
|
{
|
|
sb.append( mDescriptor.getSerial() );
|
|
}
|
|
else
|
|
{
|
|
sb.append( "DESCRIPTOR IS NULL" );
|
|
}
|
|
sb.append( "] sample rate current [" );
|
|
sb.append( mDecimalFormatter.format( current ) );
|
|
sb.append( " Hz " );
|
|
sb.append( mPercentFormatter.format( 100.0f * ( current / (float)mTargetSampleRate ) ) );
|
|
sb.append( "% ] error [" );
|
|
sb.append( average );
|
|
sb.append( " Hz ] target " );
|
|
sb.append( mDecimalFormatter.format( mTargetSampleRate ) );
|
|
|
|
mLog.info( sb.toString() );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* RTL2832 EEPROM byte array descriptor parsing class
|
|
*/
|
|
public class Descriptor
|
|
{
|
|
private byte[] mData;
|
|
private ArrayList<String> mLabels = new ArrayList<String>();
|
|
|
|
public Descriptor( byte[] data )
|
|
{
|
|
if( data != null )
|
|
{
|
|
mData = data;
|
|
}
|
|
else
|
|
{
|
|
data = new byte[ 256 ];
|
|
}
|
|
|
|
getLabels();
|
|
}
|
|
|
|
public boolean isValid()
|
|
{
|
|
return mData[ 0 ] == (byte)0x28 &&
|
|
mData[ 1 ] == (byte)0x32;
|
|
}
|
|
|
|
public String getVendorID()
|
|
{
|
|
int id = Integer.rotateLeft( ( 0xFF & mData[ 3] ), 8 ) |
|
|
( 0xFF & mData[ 2 ] );
|
|
|
|
return String.format("%04X", id );
|
|
}
|
|
|
|
public String getVendorLabel()
|
|
{
|
|
return mLabels.get( 0 );
|
|
}
|
|
|
|
public String getProductID()
|
|
{
|
|
int id = Integer.rotateLeft( ( 0xFF & mData[ 5] ), 8 ) |
|
|
( 0xFF & mData[ 4 ] );
|
|
|
|
return String.format("%04X", id );
|
|
}
|
|
|
|
public String getProductLabel()
|
|
{
|
|
return mLabels.get( 1 );
|
|
}
|
|
|
|
public boolean hasSerial()
|
|
{
|
|
return mData[ 6 ] == (byte)0xA5;
|
|
}
|
|
|
|
public String getSerial()
|
|
{
|
|
return mLabels.get( 2 );
|
|
}
|
|
|
|
public boolean remoteWakeupEnabled()
|
|
{
|
|
byte mask = (byte)0x01;
|
|
|
|
return ( mData[ 7 ] & mask ) == mask;
|
|
}
|
|
|
|
public boolean irEnabled()
|
|
{
|
|
byte mask = (byte)0x02;
|
|
|
|
return ( mData[ 7 ] & mask ) == mask;
|
|
}
|
|
|
|
private void getLabels()
|
|
{
|
|
mLabels.clear();
|
|
|
|
int start = 0x09;
|
|
|
|
while( start < 256 )
|
|
{
|
|
start = getLabel( start );
|
|
}
|
|
}
|
|
|
|
private int getLabel( int start )
|
|
{
|
|
/* Validate length and check second byte for ETX (0x03) */
|
|
if( start > 254 || mData[ start + 1 ] != (byte)0x03 )
|
|
{
|
|
return 256;
|
|
}
|
|
|
|
/* Get label length, including the length and ETX bytes */
|
|
int length = 0xFF & mData[ start ];
|
|
|
|
if( start + length > 255 )
|
|
{
|
|
return 256;
|
|
}
|
|
|
|
/* Get the label bytes */
|
|
byte[] data = Arrays.copyOfRange( mData, start + 2, start + length );
|
|
|
|
/* Translate the bytes as UTF-16 Little Endian and store the label */
|
|
String label = new String( data, Charset.forName( "UTF-16LE" ) );
|
|
|
|
mLabels.add( label );
|
|
|
|
return start + length;
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.append( "RTL-2832 EEPROM Descriptor\n" );
|
|
|
|
sb.append( "Vendor: " );
|
|
sb.append( getVendorID() );
|
|
sb.append( " [" );
|
|
sb.append( getVendorLabel() );
|
|
sb.append( "]\n" );
|
|
|
|
sb.append( "Product: " );
|
|
sb.append( getProductID() );
|
|
sb.append( " [" );
|
|
sb.append( getProductLabel() );
|
|
sb.append( "]\n" );
|
|
|
|
sb.append( "Serial: " );
|
|
if( hasSerial() )
|
|
{
|
|
sb.append( "yes [" );
|
|
sb.append( getSerial() );
|
|
sb.append( "]\n" );
|
|
}
|
|
else
|
|
{
|
|
sb.append( "no\n" );
|
|
}
|
|
|
|
sb.append( "Remote Wakeup Enabled: " );
|
|
sb.append( ( remoteWakeupEnabled() ? "yes" : "no" ) );
|
|
sb.append( "\n" );
|
|
|
|
sb.append( "IR Enabled: " );
|
|
sb.append( ( irEnabled() ? "yes" : "no" ) );
|
|
sb.append( "\n" );
|
|
|
|
if( mLabels.size() > 3 )
|
|
{
|
|
sb.append( "Additional Labels: " );
|
|
|
|
for( int x = 3; x < mLabels.size(); x++ )
|
|
{
|
|
sb.append( " [" );
|
|
sb.append( mLabels.get( x ) );
|
|
sb.append( "\n" );
|
|
}
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
public class USBEventHandlingThread implements Runnable
|
|
{
|
|
/** If thread should abort. */
|
|
private volatile boolean mAbort;
|
|
|
|
/**
|
|
* Aborts the event handling thread.
|
|
*/
|
|
public void abort()
|
|
{
|
|
mAbort = true;
|
|
}
|
|
|
|
@Override
|
|
public void run()
|
|
{
|
|
while ( !mAbort )
|
|
{
|
|
int result = LibUsb.handleEventsTimeout( null, 1000 );
|
|
|
|
if ( result != LibUsb.SUCCESS )
|
|
{
|
|
mAbort = true;
|
|
|
|
mLog.error( "error handling usb events [" +
|
|
LibUsb.errorName( result ) + "]" );
|
|
|
|
throw new LibUsbException("Unable to handle USB "
|
|
+ "events", result );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum TunerTypeCheck
|
|
{
|
|
E4K( 0xC8, 0x02, 0x40 ),
|
|
FC0012( 0xC6, 0x00, 0xA1 ),
|
|
FC0013( 0xC6, 0x00, 0xA3 ),
|
|
FC2580( 0xAC, 0x01, 0x56 ),
|
|
R820T( 0x34, 0x00, 0x69 ),
|
|
R828D( 0x74, 0x00, 0x69 );
|
|
|
|
private int mI2CAddress;
|
|
private int mCheckAddress;
|
|
private int mCheckValue;
|
|
|
|
private TunerTypeCheck( int i2c, int address, int value )
|
|
{
|
|
mI2CAddress = i2c;
|
|
mCheckAddress = address;
|
|
mCheckValue = value;
|
|
}
|
|
|
|
public byte getI2CAddress()
|
|
{
|
|
return (byte)mI2CAddress;
|
|
}
|
|
|
|
public byte getCheckAddress()
|
|
{
|
|
return (byte)mCheckAddress;
|
|
}
|
|
|
|
public byte getCheckValue()
|
|
{
|
|
return (byte)mCheckValue;
|
|
}
|
|
}
|
|
|
|
}
|