Skip to content

Commit 3036bed

Browse files
committed
Add hydrasdr_async_rx.c simple and full asynchronous RX example (device setup, streaming, buffer sizing, statistics)
1 parent ee2ff6a commit 3036bed

File tree

2 files changed

+376
-0
lines changed

2 files changed

+376
-0
lines changed

hydrasdr-tools/src/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,8 @@ add_executable(hydrasdr_set_rf_port hydrasdr_set_rf_port.c)
7575
target_link_libraries(hydrasdr_set_rf_port ${TOOLS_LINK_LIBS})
7676
install(TARGETS hydrasdr_set_rf_port
7777
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
78+
79+
add_executable(hydrasdr_async_rx hydrasdr_async_rx.c)
80+
target_link_libraries(hydrasdr_async_rx ${TOOLS_LINK_LIBS})
81+
install(TARGETS hydrasdr_async_rx
82+
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
/**
2+
* Copyright 2025 Benjamin Vernoux <bvernoux@hydrasdr.com>
3+
*
4+
* @file hydrasdr_async_rx.c
5+
* @brief Cross-platform asynchronous streaming example for HydraSDR RFOne.
6+
*
7+
* @details
8+
* This application demonstrates how to:
9+
* 1. Open a HydraSDR device.
10+
* 2. Configure RF parameters (Frequency, Linearity Gain, Bias-T).
11+
* 3. Configure Data parameters (Sample Rate, Sample Type).
12+
* 4. Perform non-blocking (async) streaming to a file.
13+
* 5. Deduce sample buffer sizes dynamically in the callback.
14+
* 6. Monitor Real-time statistics (Time, Rate, MSPS, Drops).
15+
*
16+
* Usage:
17+
* ./hydrasdr_async_rx [-f freq_hz] [-s rate_sps] [-t sample_type] [-g gain] [-b bias_on_off] [-o filename]
18+
*
19+
* Defaults:
20+
* - Frequency: 100 MHz
21+
* - Sample Rate: 2.5 MSPS
22+
* - Gain: 10 (Linearity Profile)
23+
* - Bias Tee: Off
24+
* - Sample Type: INT16 IQ
25+
*
26+
*/
27+
28+
#include <stdio.h>
29+
#include <stdlib.h>
30+
#include <string.h>
31+
#include <stdint.h>
32+
#include <signal.h>
33+
#include <errno.h>
34+
#include <time.h>
35+
36+
/* Include the HydraSDR API definition */
37+
#include "hydrasdr.h"
38+
39+
#define HYDRASDR_ASYNC_RX_VERSION "1.0.0"
40+
41+
// Cross-platform utilities
42+
#ifdef _WIN32
43+
#include <windows.h>
44+
#define SLEEP_MS(ms) Sleep(ms)
45+
typedef volatile long sig_atomic_bool_t;
46+
47+
static double get_time_sec(void)
48+
{
49+
LARGE_INTEGER t, f;
50+
QueryPerformanceCounter(&t);
51+
QueryPerformanceFrequency(&f);
52+
return (double)t.QuadPart / (double)f.QuadPart;
53+
}
54+
#else /* Linux / macOS */
55+
#include <unistd.h>
56+
#include <pthread.h>
57+
#include <sys/time.h>
58+
59+
#define SLEEP_MS(ms) usleep((ms) * 1000)
60+
typedef volatile sig_atomic_t sig_atomic_bool_t;
61+
62+
static double get_time_sec(void)
63+
{
64+
struct timespec ts;
65+
clock_gettime(CLOCK_MONOTONIC, &ts);
66+
return ts.tv_sec + (ts.tv_nsec / 1e9);
67+
}
68+
#endif
69+
70+
// Default settings
71+
#define DEFAULT_FREQ_HZ 100000000ULL /* 100 MHz */
72+
#define DEFAULT_SAMPLERATE 2500000U /* 2.5 MSPS */
73+
#define DEFAULT_SAMPLETYPE HYDRASDR_SAMPLE_INT16_IQ
74+
#define DEFAULT_GAIN 10 /* 0-21 */
75+
#define DEFAULT_FILENAME "capture.bin"
76+
77+
// Global Application State
78+
static struct hydrasdr_device *g_dev = NULL;
79+
static FILE *g_out = NULL;
80+
81+
static sig_atomic_bool_t g_exit_requested = 0;
82+
83+
static volatile uint64_t g_total_bytes = 0;
84+
static volatile uint64_t g_total_dropped_samples = 0;
85+
86+
// Signal Handling
87+
#ifdef _WIN32
88+
BOOL WINAPI
89+
windows_signal_handler(DWORD signum)
90+
{
91+
if (!g_exit_requested)
92+
fprintf(stderr, "\nCaught signal %lu\n", (unsigned long)signum);
93+
94+
g_exit_requested = 1;
95+
return TRUE;
96+
}
97+
#else
98+
static void posix_signal_handler(int signum)
99+
{
100+
if (!g_exit_requested)
101+
fprintf(stderr, "\nCaught signal %d\n", signum);
102+
103+
g_exit_requested = 1;
104+
}
105+
#endif
106+
107+
/**
108+
* @brief Helper to calculate bytes per sample based on API enum.
109+
*
110+
* @param type The hydrasdr_sample_type enum from the transfer struct.
111+
* @return size_t Number of bytes per single sample (I+Q combined if applicable).
112+
*/
113+
static inline size_t bytes_per_sample(enum hydrasdr_sample_type type)
114+
{
115+
switch (type) {
116+
case HYDRASDR_SAMPLE_FLOAT32_IQ: return 8; // 4 bytes I + 4 bytes Q
117+
case HYDRASDR_SAMPLE_FLOAT32_REAL: return 4;
118+
case HYDRASDR_SAMPLE_INT16_IQ: return 4; // 2 bytes I + 2 bytes Q
119+
case HYDRASDR_SAMPLE_INT16_REAL: return 2;
120+
case HYDRASDR_SAMPLE_UINT16_REAL: return 2;
121+
case HYDRASDR_SAMPLE_RAW: return 2; /* Raw device stream */
122+
default: return 2;
123+
}
124+
}
125+
126+
static void print_usage(const char *prog)
127+
{
128+
printf("Usage: %s [options]\n", prog);
129+
printf("\nOptions:\n");
130+
printf(" -f <Hz> Set RF frequency (default: %llu Hz)\n", DEFAULT_FREQ_HZ);
131+
printf(" -s <SPS> Set sample rate (default: %u)\n", DEFAULT_SAMPLERATE);
132+
printf(" -t <type> Set sample type (default: %d = Int16 IQ)\n", DEFAULT_SAMPLETYPE);
133+
printf(" 0=FloatIQ, 1=FloatReal, 2=Int16IQ, 3=Int16Real, 5=Raw\n");
134+
printf(" -g <0-21> Linearity gain (default: %d)\n", DEFAULT_GAIN);
135+
printf(" -b <0/1> Bias-T off/on (default: 0)\n");
136+
printf(" -o <file> Output file (default: %s)\n", DEFAULT_FILENAME);
137+
printf(" -h Show help\n");
138+
}
139+
140+
/**
141+
* @brief Asynchronous Callback Function.
142+
*
143+
* @details This function is invoked by the HydraSDR library thread.
144+
* Critical Section: Execution time must be minimized.
145+
*
146+
* @param transfer Pointer to the transfer structure containing data and metadata.
147+
* @return int 0 to continue streaming, non-zero to request stop (internally).
148+
*/
149+
int rx_callback(hydrasdr_transfer_t *t)
150+
{
151+
if (g_exit_requested)
152+
return 0;
153+
154+
if (t->dropped_samples)
155+
g_total_dropped_samples += t->dropped_samples;
156+
157+
const size_t bps = bytes_per_sample(t->sample_type);
158+
const size_t chunk_bytes = (size_t)t->sample_count * bps;
159+
160+
if (g_out && chunk_bytes > 0) {
161+
const size_t w = fwrite(t->samples, 1, chunk_bytes, g_out);
162+
if (w != chunk_bytes)
163+
fprintf(stderr, "Disk write error\n");
164+
}
165+
166+
g_total_bytes += chunk_bytes;
167+
168+
return 0;
169+
}
170+
171+
/**
172+
* @brief Main Entry Point
173+
*/
174+
175+
int main(int argc, char **argv)
176+
{
177+
uint64_t freq_hz = DEFAULT_FREQ_HZ;
178+
uint32_t samplerate = DEFAULT_SAMPLERATE;
179+
uint8_t gain = DEFAULT_GAIN;
180+
uint8_t bias = 0;
181+
int sample_type = DEFAULT_SAMPLETYPE;
182+
const char *filename = DEFAULT_FILENAME;
183+
184+
// 1. Parse Command Line Arguments (Manual parsing for no deps)
185+
for (int i = 1; i < argc; i++) {
186+
if (!strcmp(argv[i], "-f") && i + 1 < argc) {
187+
freq_hz = strtoull(argv[++i], NULL, 10);
188+
}
189+
else if (!strcmp(argv[i], "-s") && i + 1 < argc) {
190+
samplerate = (uint32_t)strtoul(argv[++i], NULL, 10);
191+
}
192+
else if (!strcmp(argv[i], "-t") && i + 1 < argc) {
193+
sample_type = atoi(argv[++i]);
194+
}
195+
else if (!strcmp(argv[i], "-g") && i + 1 < argc) {
196+
gain = (uint8_t)atoi(argv[++i]);
197+
}
198+
else if (!strcmp(argv[i], "-b") && i + 1 < argc) {
199+
bias = (uint8_t)atoi(argv[++i]);
200+
}
201+
else if (!strcmp(argv[i], "-o") && i + 1 < argc) {
202+
filename = argv[++i];
203+
}
204+
else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
205+
print_usage(argv[0]);
206+
return 0;
207+
}
208+
else {
209+
fprintf(stderr, "Unknown option: %s\n", argv[i]);
210+
print_usage(argv[0]);
211+
return 1;
212+
}
213+
}
214+
215+
// 2. Setup Signal Handling (Ctrl+C)
216+
#ifdef _WIN32
217+
SetConsoleCtrlHandler(windows_signal_handler, TRUE);
218+
#else
219+
signal(SIGINT, posix_signal_handler);
220+
signal(SIGTERM, posix_signal_handler);
221+
signal(SIGABRT, posix_signal_handler);
222+
#endif
223+
224+
printf("HydraSDR Async RX Tool v%s\n", HYDRASDR_ASYNC_RX_VERSION);
225+
226+
// 3. Open Device
227+
// Note: hydrasdr_open opens the first available device
228+
int ret = hydrasdr_open(&g_dev);
229+
if (ret != HYDRASDR_SUCCESS) {
230+
fprintf(stderr, "ERROR: hydrasdr_open failed: %s\n",
231+
hydrasdr_error_name(ret));
232+
return EXIT_FAILURE;
233+
}
234+
printf("[INFO] Device opened.\n");
235+
236+
// 4. Print supported samplerates
237+
uint32_t count = 0;
238+
hydrasdr_get_samplerates(g_dev, &count, 0);
239+
240+
if (count > 0) {
241+
uint32_t *rates = malloc(count * sizeof(uint32_t));
242+
if (rates) {
243+
hydrasdr_get_samplerates(g_dev, rates, count);
244+
printf("Available sample rates:\n");
245+
for (uint32_t i = 0; i < count; i++)
246+
printf(" %u (%.3f MSPS)\n", rates[i], rates[i] / 1e6);
247+
free(rates);
248+
}
249+
}
250+
printf("\n");
251+
252+
// 5. Apply Configuration
253+
254+
// A. Frequency
255+
printf("[CONF] Frequency: %llu Hz\n", (unsigned long long)freq_hz);
256+
ret = hydrasdr_set_freq(g_dev, freq_hz);
257+
if (ret != HYDRASDR_SUCCESS) {
258+
fprintf(stderr, "[ERROR] Failed set frequency: %s\n", hydrasdr_error_name(ret));
259+
goto error;
260+
}
261+
262+
// B. Sample Rate
263+
printf("[CONF] Samplerate: %u SPS\n", samplerate);
264+
ret = hydrasdr_set_samplerate(g_dev, samplerate);
265+
if (ret != HYDRASDR_SUCCESS) {
266+
fprintf(stderr, "[ERROR] Failed set sample rate: %s\n", hydrasdr_error_name(ret));
267+
goto error;
268+
}
269+
270+
// C. Sample Type
271+
if (sample_type < 0 || sample_type >= HYDRASDR_SAMPLE_END) {
272+
fprintf(stderr, "ERROR: Invalid sample type %d\n", sample_type);
273+
goto error;
274+
}
275+
printf("[CONF] Sample type: %d\n", sample_type);
276+
ret = hydrasdr_set_sample_type(g_dev, (enum hydrasdr_sample_type)sample_type);
277+
if (ret != HYDRASDR_SUCCESS) {
278+
fprintf(stderr, "[ERROR] Failed set sample type: %s\n", hydrasdr_error_name(ret));
279+
goto error;
280+
}
281+
282+
// D. Gain (Linearity Mode)
283+
printf("[CONF] Gain: %u\n", gain);
284+
ret = hydrasdr_set_linearity_gain(g_dev, gain);
285+
if (ret != HYDRASDR_SUCCESS) {
286+
fprintf(stderr, "[ERROR] Failed set linearity gain: %s\n", hydrasdr_error_name(ret));
287+
goto error;
288+
}
289+
290+
// E. Bias Tee
291+
printf("[CONF] Bias-T: %u\n", bias);
292+
ret = hydrasdr_set_rf_bias(g_dev, bias);
293+
if (ret != HYDRASDR_SUCCESS) {
294+
fprintf(stderr, "[ERROR] Failed set Bias Tee: %s\n", hydrasdr_error_name(ret));
295+
goto error;
296+
}
297+
if (bias)
298+
printf("[WARN] Bias-T ENABLED.\n");
299+
300+
// 6. Open Output File
301+
g_out = fopen(filename, "wb");
302+
if (!g_out) {
303+
fprintf(stderr, "ERROR: Cannot open '%s': %s\n",
304+
filename, strerror(errno));
305+
goto error;
306+
}
307+
printf("[INFO] Writing to '%s'\n", filename);
308+
309+
// 7. Start Streaming (Async)
310+
printf("[INFO] Starting stream... (Press Ctrl+C to stop)\n");
311+
312+
// Pass 'NULL' as user context (last arg) since we use global vars for this simple example
313+
ret = hydrasdr_start_rx(g_dev, rx_callback, NULL);
314+
if (ret) {
315+
fprintf(stderr, "ERROR: start_rx failed: %s\n",
316+
hydrasdr_error_name(ret));
317+
goto error;
318+
}
319+
320+
// 8. Monitoring Loop
321+
const double t_start = get_time_sec();
322+
double t_last = t_start;
323+
uint64_t last_bytes = 0;
324+
325+
const size_t bps = bytes_per_sample((enum hydrasdr_sample_type)sample_type);
326+
327+
while (!g_exit_requested) {
328+
SLEEP_MS(1000);
329+
330+
if (hydrasdr_is_streaming(g_dev) != HYDRASDR_TRUE) {
331+
fprintf(stderr, "\n[ERROR] Device stopped streaming.\n");
332+
g_exit_requested = 1;
333+
break;
334+
}
335+
336+
double t_now = get_time_sec();
337+
double dt = t_now - t_last;
338+
339+
if (dt >= 1.0 && !g_exit_requested) {
340+
const uint64_t bytes_now = g_total_bytes;
341+
const uint64_t dbytes = bytes_now - last_bytes;
342+
343+
const double inst_msps = (dbytes / (double)bps) / (dt * 1e6);
344+
const double avg_msps = (bytes_now / (double)bps) / ((t_now - t_start) * 1e6);
345+
346+
printf("Time %4.0fs | Inst %5.2f MSPS | Avg %5.2f MSPS | Vol %7.2f MB | Drops %llu\r",
347+
t_now - t_start,
348+
inst_msps,
349+
avg_msps,
350+
bytes_now / (1024.0 * 1024.0),
351+
(unsigned long long)g_total_dropped_samples);
352+
fflush(stdout);
353+
354+
last_bytes = bytes_now;
355+
t_last = t_now;
356+
}
357+
}
358+
359+
printf("\n\n[INFO] Stopping ...\n");
360+
361+
error:
362+
if (g_dev) {
363+
hydrasdr_stop_rx(g_dev);
364+
hydrasdr_close(g_dev);
365+
}
366+
if (g_out)
367+
fclose(g_out);
368+
369+
printf("[INFO] Done.\n");
370+
return ret == HYDRASDR_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE;
371+
}

0 commit comments

Comments
 (0)