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);
};

Reply via email to