Some slight cleanup of the trivial example and fix for correctly letting
threads return / join.
On Sun, May 19, 2013 at 9:18 PM, David Jacobowitz <
[email protected]> wrote:
> Thanks, Dimitri. This helps a lot.
>
> I tried to make some suggested additional comments to the header file,
> which I have attached. You can take them or leave them. I think perhaps for
> a sophisticated c programmer the API is obvious, but for me I needed a bit
> more help.
>
> I have also written a small program that uses the async API to serve as a
> minimalist working example on how to use the library which folks might find
> helpful. Based on your answers below, I just copy the buffer in the
> callback into my own ring of buffers, then work on the buffers in a
> separate thread.
>
> The program just writes captured data directly to disk. It works on my
> computer, but I haven't tested it extensively.
>
>
> Both files are attached.
>
> Regards,
> Dave J
>
>
>
> On Sun, May 19, 2013 at 6:28 AM, Dimitri Stolnikov <[email protected]>wrote:
>
>> Hi David,
>>
>>
>> I am working on a personal project to use SDR techniques to decode
>>> aviation navigation signals (VOR). I've got the signal processing mostly
>>> working from recorded signals, but am now trying to integrate my SW with
>>> the radio in real time.
>>>
>>
>> - What exactly is offset tuning? How is offset tuning different from
>>> tuning to an offset?
>>>
>>
>> For E4000 tuners only this will shift thei 0-IF point outside of RTL
>> sampling bandwidth which may help if you have a wideband (DECT or such)
>> signal you want to sample but don't want it to be distorted by the usual
>> LO-leakage peak in the middle. The drawback is that you might get hit by a
>> image signal.
>>
>>
>> Is this a feature that mostly benefits people who are not going to put
>>> their IF through another mixer? In my application I am already tuning to an
>>> offset, and pulling down a wide enough IF that actually holds many channels
>>> of interest. (VOR channels have 50kHz spacing). I then use a software
>>> mixer/channelizer to choose the channel I want. Am I correct in assuming
>>> that offset tuning is of no use to me?
>>>
>>
>> For narrowband signals you are good to do offset tuning yourself.
>>
>>
>> - regarding AGC, what is the difference between AGC and auto gain?
>>>
>>
>> AGC is for RTL chip, auto gain is for the tuner (if implemented).
>>
>>
>> RTLSDR_API int rtlsdr_set_tuner_gain_mode(**rtlsdr_dev_t *dev, int
>>> manual)
>>>
>>
>> Works for E4000 tuners only AFAIR.
>>
>> Steve: do other tuners provide AGC functions as well?
>>
>>
>> RTLSDR_API int rtlsdr_set_agc_mode(rtlsdr_**dev_t *dev, int on);
>>>
>>> I'm guessing that these affect different AGCs. One for the tuner and one
>>> for the RTL device.
>>>
>>
>> Yes.
>>
>>
>> What are the benefits and costs of having either or both on?
>>>
>>
>> It depends on the application. While in presence of strong interference
>> the tuner auto gain seems to perform well on E4000 devices, the RTL gain
>> from my understanding only scales the signal to optimally use the dynamic
>> range of 8 bits. Within controlled environment (narrowband
>> antenna/LNA/filters) i usually let them off entirely and set the gains for
>> best SNR manually.
>>
>> Historically we have implemented them with some time in between so now
>> both gains have their own setters.
>>
>>
>>
>>> - regarding rtlsdr_read_async(...) and related functions.
>>>
>>> I take it that the library is setting up a ring buffer and calling me
>>> back when it has a new buffer of data for me.
>>>
>>
>> Yes.
>>
>>
>> How long to I have to work with this buffer? Obviously, if I want to
>>> work in real-time I need to keep up with the sample rate. But my
>>> application can afford to throw away buffers since it can decode a few ms
>>> of data from one station and then revisit it much later. However, I'd like
>>> to know how long I have until the buffer gets clobbered. I'm presuming it's
>>> stable until all the n-1 other buffers have been hit.
>>>
>>
>> You should spend as little time as possible inside a callback, this is a
>> libusb requirement. You might copy some buffers to be passed to a worker
>> thread later on and keep skipping buffers inside the callback until your
>> worker thread is ready for the next shot.
>>
>>
>> - generally how fast can the RTL devices tune? I know, this is not an
>>> rtlsdr question per se, but I'm curious. I noticed that when I tune, I get
>>> a delay.
>>>
>>
>> Can't tell, never evaluated that myself. But again, this largely depends
>> on the tuner interactions involved.
>>
>>
>> smidge more documentation. I'd be happy to submit a comments-only patch
>>> if there's interest. :-)
>>>
>>
>> This would be appreciated.
>>
>>
>> Best regards,
>> Dimitri
>>
>
>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <string.h>
#include "rtl-sdr.h"
/*
*
* trivial.c -- a minimalist threaded example of how to use the
* librtlsdr async read functions.
*
* ----------------
* LICENSE:
*
* This program is offered completely license-free for any
* purpose by anyone.
* ----------------
*
*
* Description:
*
* This C program is intended to show the minimal amount of code
* necessary to work with the rtlsdr library asynchronously. The
* tools rtl_fm, rtl_tcp, etc, all do something like this, but they
* may be a little intimidating for casual users who merely
* wants to link against rtlsdr and get "up and running" quickly.
*
* Hopefully this program is useful to someone!
*
*
* Explanation:
*
* The basic structure of this program is that it kicks off two
* threads. One receives callbacks from the rtlsdr library,
* the other does "work" -- the case below, splatting the data
* to a file. The two threads communicate through a simple
* buffer, which is protected by mutexes so that the threads
* to not clobber each other or deadlock.
*
* There are actually two buffers in this program. One is hidden
* from view. The hidden one is behind the scenes inside librtlsdr.
* When you rtlsdr_read_async() is called, that buffer is set up
* with the sizes you specify. Later, when read_async calls back to
* the call back you provide, the callback is given a pointer to
* the element of that buffer that it is safe to copy from. The
* general requirement for libusb and thus rtlsdr is that one
* should not dally with this callback function. Just copy the
* data and return -- which is what this program does.
*
* The second buffer is the one allocated below in the
* ringbuffer_t structure. Our callback function will copy from
* librtlsdr's buffer to a block in this buffer, and then return.
* The "worker" thread will read from a block in this buffer and
* do it's thing, whatever that might be.
*
* It's perhaps a little memory inefficient to use two sets of
* buffers, but because we can't see into librtlsdr/libusb's use
* of them, the best course of action is to have our own buffers
* and minimize the time we are accessing the librtlsdr buffer.
*
*
* Compilation:
*
* Assuming libusb and librtlsdr are where your system expectes
* to find them, and that the headers for librtlsdr are also
* in a normally searched directory, compile with:
*
* gcc -Wall trivial.c -lrtlsdr -lpthread -o trivial.out
*
* Otherwise, add extra -I's and -L's as appropriate! :-)
*
* Regards,
* Davd Jacobowitz
* May 2013
*/
/* hopefully self explanatory! */
#define BUF_COUNT (8)
#define BUF_SIZE (1024*8)
#define MAX_ITERS (500)
#define EXAMP_FILENAME "examples_iq.dat"
#define FREQUENCY (104500000)
#define SAMPLE_RATE (256000)
void *worker_thread_fn(void *f);
void *callback_setup_thread_fn(void *f);
void callback(unsigned char *, uint32_t , void *);
/*
* simple struct to hold everything you need for a
* simple producer/consumer ringbuffer
*/
typedef struct ringbuffer_t {
char ringbuffer[BUF_COUNT][BUF_SIZE];
pthread_mutex_t ringbuffer_mutex;
pthread_cond_t ringbuffer_written_cond;
int reader_unread;
int writer_next;
} ringbuffer_t;
/* globals: device pointer and ringbuffer data struct
* These don't need to be globals and probably should not
* be in a more complex program, and of course, if you
* hope to talk to more than one radio at once.
**/
rtlsdr_dev_t *dev;
ringbuffer_t rbuf;
int main(int argc, char*argv[]) {
pthread_t worker_thread;
pthread_t callback_thread;
memset(&rbuf,0,sizeof(rbuf));
/* open and set up the RTL device */
int fail = rtlsdr_open(&dev,0);
if (fail) { printf("-err- could not open rtl device\n"); exit(-1); };
fail = rtlsdr_set_sample_rate(dev,SAMPLE_RATE);
if (fail) { printf("-err- could not set sample rate\n"); exit(-2); };
fail = rtlsdr_set_center_freq(dev,FREQUENCY);
if (fail) { printf("-err- could not set center freq\n"); exit(-3); };
/* set up the mutex and condition variable. Wait, what is a condition
* variable you ask? It is a variable that one thread can write to
* to wake up another thread that is watching for it. This allows
* the program to sleep the worker thread when there is no data to
* work, and wake it when there is. It is avoids having to spin waiting
* for a lock.
*/
pthread_mutex_init(&rbuf.ringbuffer_mutex, 0);
pthread_cond_init(&rbuf.ringbuffer_written_cond, 0);
/* start the threads */
pthread_create(&callback_thread,NULL,callback_setup_thread_fn,NULL);
pthread_create(&worker_thread,NULL,worker_thread_fn,NULL);
/* this thread, the original program thread, will wait here
* until the worker thread returns. It will wait again for the
* callback thread to return, which should happen shortly after
* since the last thing the worker thread does before returning
* is cancel the async function, which should allow the callback
* thread to end.
*/
pthread_join(worker_thread, NULL);
pthread_join(callback_thread,NULL);
/* pthread_cancel(callback_thread); */
rtlsdr_close(dev);
return 0;
};
void *worker_thread_fn(void *f) {
char done = 0;
int iters = 0;
FILE *ofile;
ofile = fopen(EXAMP_FILENAME,"wb");
if (!ofile) {
printf("-error- could not open output file.\n");
exit(-2);
}
while (!done) {
/* lock --> wait on cond --> use --> decrement --> unlock */
pthread_mutex_lock(&rbuf.ringbuffer_mutex);
if (rbuf.reader_unread ==0) {
/* if there is no data to work on, sleep and wait to be woken by
* the condition variable. The way cond_wait works is interesting.
* It atomically releases the mutex so the other thread can do
* some work and signal it's done. Then it takes back the mutex and
* program flow continues in this thread.
*/
pthread_cond_wait(&rbuf.ringbuffer_written_cond, &rbuf.ringbuffer_mutex);
}
/* otherwise, operate on a buffer of data */
if (rbuf.reader_unread > 0) {
int pos = rbuf.writer_next - rbuf.reader_unread;
if (pos<0) { pos += BUF_COUNT; }
/*
* do something interesting with the data we have collected.
* In the example below we just splat it to a file.
*/
int written = fwrite(rbuf.ringbuffer[pos],1,BUF_SIZE,ofile);
if (written != BUF_SIZE) {
printf("-warn- only %d bytes written of %d buffer\n",written,BUF_SIZE);
}
--rbuf.reader_unread;
}
pthread_mutex_unlock(&rbuf.ringbuffer_mutex);
if (iters++ > MAX_ITERS) {
done = 1;
}
}
fclose(ofile);
/* calling this causes librtlsdr to stop calling back, and the
* callback thread will complete
*/
rtlsdr_cancel_async(dev);
return NULL;
};
void *callback_setup_thread_fn(void *f) {
/* librtlsdr requires call to reset async */
rtlsdr_reset_buffer(dev);
/* this function sets up the callbacks. rtlsdr will call
* the function specified below every time it has a new
* buffer of data. rtlsdr_read_async itself will not
* itself return, however, until it has been "turned
* off" by someone calling rtlsdr_cancel_async().
* (Which is in this case is done just before the worker
* thread exits.
*/
rtlsdr_read_async(dev,&callback,f,BUF_COUNT,BUF_SIZE);
return NULL;
};
/* the actual callback function */
void callback(unsigned char *ibuf, uint32_t len, void *f) {
/*
* lock --> copy --> increment --> signal --> unlock
*/
pthread_mutex_lock(&rbuf.ringbuffer_mutex);
memcpy(rbuf.ringbuffer[rbuf.writer_next],ibuf,len);
rbuf.writer_next = (rbuf.writer_next + 1) % BUF_COUNT;
if (rbuf.reader_unread < BUF_COUNT) {
++rbuf.reader_unread;
} else {
/* in some applications one might want to block the producer
* if the consumer has fallen behind. We're not doing that in
* this example at all. Instead, if the consumer hasn't
* consumed a given buffer, just issue a warning and move
* on. This makes sense in a radio application where the
* incoming data cannot be stopped, so why pretend we can
* stop time and catch up?
*/
printf("-warn- write overflow\n");
};
pthread_cond_signal(&rbuf.ringbuffer_written_cond);
pthread_mutex_unlock(&rbuf.ringbuffer_mutex);
};