Open KeyserSoze-ND opened 2 months ago
This smells like a double-free. For a quick fix note that closeStream
will also deactivateStream
and that dropping the reference to Device
will also closeStream
implicitly -- you should be okay to just del sdr
.
edit: actually __del__
will close
and that in turn will unmake
(as seen in the trace), streams are not implicitly closed. Strange then where unmake fails. SoapyRTLSDR's destructor has nothing but a rtlsdr_close(dev)
call.
I tried the following:
def main():
args = {'driver': 'rtlsdr'}
sdr = SoapySDR.Device(args)
sdr.setSampleRate(SoapySDR.SOAPY_SDR_RX, 0, 2.048e6)
sdr.setFrequency(SoapySDR.SOAPY_SDR_RX, 0, 100e6)
stream = sdr.setupStream(SoapySDR.SOAPY_SDR_RX, SoapySDR.SOAPY_SDR_CF32)
sdr.activateStream(stream)
time.sleep(1)
#sdr.deactivateStream(stream)
sdr.closeStream(stream)
print("Stream closed")
time.sleep(1)
if __name__ == "__main__":
while True:
main()
This fails the same way.
I also tried this:
def main():
args = {'driver': 'rtlsdr'}
sdr = SoapySDR.Device(args)
sdr.setSampleRate(SoapySDR.SOAPY_SDR_RX, 0, 2.048e6)
sdr.setFrequency(SoapySDR.SOAPY_SDR_RX, 0, 100e6)
stream = sdr.setupStream(SoapySDR.SOAPY_SDR_RX, SoapySDR.SOAPY_SDR_CF32)
sdr.activateStream(stream)
time.sleep(1)
del sdr
print("Stream closed")
time.sleep(1)
if __name__ == "__main__":
while True:
main()
Interestingly, this does not crash but just hangs. The python kernel stops responding. (CTRL+C does not work etc.)
I guess the first example might keep the sdr
Device around and the second tries to force a close
with an active stream, which won't work as the rtlsdr async is still running.
All around your examples are valid and what should be expected to work from a Python perspective.
There are no clues why the unmake
would fail after some tries. We likely need to try if this also appears in a similar C example and then debug the state, perhaps with gcc. I'll try to run this code on Linux and see if it can be reproduced. I hope it's not some Windows pecularity.
I can't reproduce this in Linux, seems to be particular to Windows or your setup. Do you have WSL? Can you reproduce this in WSL? Are you up to trying the same example in C? That would make out or exclude Python as the culprit.
I do not have WSL but VSCode has an extension for it so I will try to reproduce this in WSL. I never used this library in C but I can also try this with C while I am at it.
Attached is the C code I ran in WSL (Debian). I do not run and capture samples repeatedly but just once.
#include <SoapySDR/Device.h>
#include <SoapySDR/Formats.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
int main(void) {
SoapySDRKwargs args = SoapySDRKwargs_fromString("driver=rtlsdr");
SoapySDRDevice *sdr = SoapySDRDevice_make(&args);
if (sdr == NULL) {
fprintf(stderr, "Error: Failed to initialize RTL-SDR device.\n");
return EXIT_FAILURE;
}
int result = SoapySDRDevice_setSampleRate(sdr, SOAPY_SDR_RX, 0, 1e6);
if (result != 0) {
fprintf(stderr, "Error: Failed to set sample rate.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
result = SoapySDRDevice_setFrequency(sdr, SOAPY_SDR_RX, 0, 100e6, NULL);
if (result != 0) {
fprintf(stderr, "Error: Failed to set frequency.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
SoapySDRKwargs streamArgs = SoapySDRKwargs_fromString("");
SoapySDRKwargs_set(&streamArgs, "buffers", "4");
SoapySDRKwargs_set(&streamArgs, "bufflen", "16384");
size_t channels[1] = {0};
size_t numChans = 1;
const char *format = "CS16";
int stream_initialized = 0;
SoapySDRStream *rxStream = SoapySDRDevice_setupStream(sdr, SOAPY_SDR_RX, format, channels, numChans, &streamArgs);
if (rxStream == NULL) {
fprintf(stderr, "Error: Failed to set up stream.\n");
SoapySDRDevice_unmake(sdr);
SoapySDRKwargs_clear(&streamArgs);
return EXIT_FAILURE;
}
SoapySDRKwargs_clear(&streamArgs);
stream_initialized = 1;
result = SoapySDRDevice_activateStream(sdr, rxStream, 0, 0, 0);
if (result != 0) {
fprintf(stderr, "Error: Failed to activate stream.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
int numElems = 1024;
int16_t *buff = (int16_t *)malloc(numElems * 2 * sizeof(int16_t));
if (buff == NULL) {
fprintf(stderr, "Error: Failed to allocate buffer.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
void *buffs[] = {buff};
long long timeNs = 0;
result = SoapySDRDevice_readStream(sdr, rxStream, buffs, numElems, NULL, &timeNs, 1000000); // Timeout of 1 second
if (result < 0) {
fprintf(stderr, "Error: Failed to read stream. Error code: %d\n", result);
free(buff);
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
} else if (result == 0) {
fprintf(stderr, "Warning: No samples read from the stream.\n");
} else {
printf("Captured %d samples successfully.\n", result);
}
if (stream_initialized) {
SoapySDRDevice_deactivateStream(sdr, rxStream, 0, 0);
SoapySDRDevice_closeStream(sdr, rxStream);
}
free(buff);
SoapySDRDevice_unmake(sdr);
printf("Stream closed.\n");
return EXIT_SUCCESS;
}
When this code runs, I get the following error:
Found Rafael Micro R820T tuner
[INFO] Opening Generic RTL2832U OEM :: df47a2b4edae...
Found Rafael Micro R820T tuner
Exact sample rate is: 1000000.026491 Hz
[R82XX] PLL not locked!
[INFO] Using format CS16.
Allocating 15 zero-copy buffers
Segmentation fault (core dumped)
I also run using gdb
to backtrace and then I receive the errors as:
Thread 1 "simple_sdr" received signal SIGSEGV, Segmentation fault.
0x00007ffff7a34238 in SoapyRTLSDR::acquireReadBuffer(SoapySDR::Stream*, unsigned long&, void const**, int&, long long&, long) () from /usr/local/lib/SoapySDR/modules0.8-3/librtlsdrSupport.so
(gdb) backtrace
#0 0x00007ffff7a34238 in SoapyRTLSDR::acquireReadBuffer(SoapySDR::Stream*, unsigned long&, void const**, int&, long long&, long) () from /usr/local/lib/SoapySDR/modules0.8-3/librtlsdrSupport.so
#1 0x00007ffff7a33d93 in SoapyRTLSDR::readStream(SoapySDR::Stream*, void* const*, unsigned long, int&, long long&, long) () from /usr/local/lib/SoapySDR/modules0.8-3/librtlsdrSupport.so
#2 0x00007ffff7f8b464 in SoapySDRDevice_readStream () from /usr/local/lib/libSoapySDR.so.0.8-3
#3 0x0000555555555588 in main () at simple_sdr.c:61
Despite these errors, rtl_test
runs without any issues. I’ve tried it on another machine and observed the same problem.
Would you mind testing the previous Python code I posted in a Windows environment to see if the issue is reproducible? This might help narrow down if it's environment-specific. Thank you for your time and assistance!
What you are seeing there is different. Fix it in line 61 with
int flags = 0;
result = SoapySDRDevice_readStream(sdr, rxStream, buffs, numElems, &flags, &timeNs, 1000000); // Timeout of 1 second
Note that flags
is an output and can't be NULL
, https://github.com/pothosware/SoapySDR/blob/master/include/SoapySDR/Device.h#L392
Thanks for that. I fixed the code as you suggested and it works now. Then I made it so that it is in a while loop and repeatedly runs and captures data. In this case, I observed no errors, so it runs without issue in C as opposed to Python.
Interestingly, while doing so, I realized the SoapySDRDevice_deactivateStream
or SoapySDRDevice_deactivateStream
is unnecessary for repeated runs. As an example, this code runs without any issues:
#include <SoapySDR/Device.h>
#include <SoapySDR/Formats.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
int main(void) {
SoapySDRKwargs args = SoapySDRKwargs_fromString("driver=rtlsdr");
SoapySDRDevice *sdr = SoapySDRDevice_make(&args);
if (sdr == NULL) {
fprintf(stderr, "Error: Failed to initialize RTL-SDR device.\n");
return EXIT_FAILURE;
}
int result = SoapySDRDevice_setSampleRate(sdr, SOAPY_SDR_RX, 0, 1e6);
if (result != 0) {
fprintf(stderr, "Error: Failed to set sample rate.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
result = SoapySDRDevice_setFrequency(sdr, SOAPY_SDR_RX, 0, 95e6, NULL);
if (result != 0) {
fprintf(stderr, "Error: Failed to set frequency.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
SoapySDRKwargs streamArgs = SoapySDRKwargs_fromString("");
SoapySDRKwargs_set(&streamArgs, "buffers", "4");
SoapySDRKwargs_set(&streamArgs, "bufflen", "65536"); // Increase buffer size
size_t channels[1] = {0};
size_t numChans = 1;
const char *format = "CS16";
SoapySDRStream *rxStream = SoapySDRDevice_setupStream(sdr, SOAPY_SDR_RX, format, channels, numChans, &streamArgs);
if (rxStream == NULL) {
fprintf(stderr, "Error: Failed to set up stream.\n");
SoapySDRDevice_unmake(sdr);
SoapySDRKwargs_clear(&streamArgs);
return EXIT_FAILURE;
}
SoapySDRKwargs_clear(&streamArgs);
result = SoapySDRDevice_activateStream(sdr, rxStream, 0, 0, 0);
if (result != 0) {
fprintf(stderr, "Error: Failed to activate stream.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
int numElems = 1024;
int16_t *buff = (int16_t *)malloc(numElems * 4 * sizeof(int16_t));
if (buff == NULL) {
fprintf(stderr, "Error: Failed to allocate buffer.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
void *buffs[] = {buff};
long long timeNs = 0;
while (1) {
int flags = 0;
result = SoapySDRDevice_readStream(sdr, rxStream, buffs, numElems, &flags, &timeNs, 1000000); // Timeout of 1 second
if (result == SOAPY_SDR_TIMEOUT) {
fprintf(stderr, "Timeout occurred while reading the stream. Retrying...\n");
continue; // Retry reading
} else if (result == SOAPY_SDR_OVERFLOW) {
fprintf(stderr, "Overflow occurred. Samples were dropped.\n");
continue; // Retry reading
} else if (result < 0) {
fprintf(stderr, "Error: Failed to read stream. Error code: %d\n", result);
break; // Exit the loop on fatal errors
} else if (result == 0) {
fprintf(stderr, "Warning: No samples read from the stream.\n");
} else {
printf("Captured %d samples successfully.\n", result);
}
usleep(100000);// This is necessary, otherwise stuck in timeout.
}
}
Similarly, I then changed the python code I proposed in the first place:
import os
import ctypes
os.add_dll_directory(r'C:\PothosSDR\bin')
ctypes.windll.LoadLibrary(r'C:\PothosSDR\\bin\rtlsdr.dll')
import SoapySDR
import time
import numpy as np
import faulthandler
faulthandler.enable()
def initialize(args):
sdr = SoapySDR.Device(args)
sdr.setSampleRate(SoapySDR.SOAPY_SDR_RX, 0, 2.048e6)
sdr.setFrequency(SoapySDR.SOAPY_SDR_RX, 0, 100e6)
stream = sdr.setupStream(SoapySDR.SOAPY_SDR_RX, SoapySDR.SOAPY_SDR_CF32)
return sdr,stream
def read_data(sdr,stream):
sdr.activateStream(stream)
buff = np.zeros((1024,),dtype=np.complex64)
sr = sdr.readStream(stream, [buff], len(buff))
print(sr.ret)
time.sleep(1) # Read data etc. and do processing
print('I READ THE DATA')
# Unnecessary?
def close_all(sdr,stream):
sdr.deactivateStream(stream)
sdr.closeStream(stream)
#del sdr # Ensure cleanup
print("Stream closed")
time.sleep(1)
if __name__ == "__main__":
args = {'driver': 'rtlsdr'}
sdr,stream = initialize(args)
while True:
read_data(sdr,stream)
The code above runs without any issues which is great.
However, then the next question is that why the sdr.deactivate(stream)
or sdr.closeStream(stream)
exist in the first place if this can run without it even being called. What do they do?
Just for completeness, I also tried to close the stream every time after I got some samples. Below is the code for that where I initialize the stream, read data and close everything again and again:
#include <SoapySDR/Device.h>
#include <SoapySDR/Formats.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
int main(void) {
while (1) {
SoapySDRKwargs args = SoapySDRKwargs_fromString("driver=rtlsdr");
SoapySDRDevice *sdr = SoapySDRDevice_make(&args);
if (sdr == NULL) {
fprintf(stderr, "Error: Failed to initialize RTL-SDR device.\n");
return EXIT_FAILURE;
}
int result = SoapySDRDevice_setSampleRate(sdr, SOAPY_SDR_RX, 0, 1e6);
if (result != 0) {
fprintf(stderr, "Error: Failed to set sample rate.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
result = SoapySDRDevice_setFrequency(sdr, SOAPY_SDR_RX, 0, 95e6, NULL);
if (result != 0) {
fprintf(stderr, "Error: Failed to set frequency.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
SoapySDRKwargs streamArgs = SoapySDRKwargs_fromString("");
SoapySDRKwargs_set(&streamArgs, "buffers", "4");
SoapySDRKwargs_set(&streamArgs, "bufflen", "65536");
size_t channels[1] = {0};
size_t numChans = 1;
const char *format = "CS16";
SoapySDRStream *rxStream = SoapySDRDevice_setupStream(sdr, SOAPY_SDR_RX, format, channels, numChans, &streamArgs);
if (rxStream == NULL) {
fprintf(stderr, "Error: Failed to set up stream.\n");
SoapySDRDevice_unmake(sdr);
SoapySDRKwargs_clear(&streamArgs);
return EXIT_FAILURE;
}
SoapySDRKwargs_clear(&streamArgs);
result = SoapySDRDevice_activateStream(sdr, rxStream, 0, 0, 0);
if (result != 0) {
fprintf(stderr, "Error: Failed to activate stream.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
int numElems = 4096;
int16_t *buff = (int16_t *)malloc(numElems * 4 * sizeof(int16_t));
if (buff == NULL) {
fprintf(stderr, "Error: Failed to allocate buffer.\n");
SoapySDRDevice_unmake(sdr);
return EXIT_FAILURE;
}
void *buffs[] = {buff};
long long timeNs = 0;
// Enter a loop to continuously capture samples
int flags = 0;
result = SoapySDRDevice_readStream(sdr, rxStream, buffs, numElems, &flags, &timeNs, 1000000); // Timeout of 1 second
if (result == SOAPY_SDR_TIMEOUT) {
fprintf(stderr, "Timeout occurred while reading the stream. Retrying...\n");
continue; // Retry reading
} else if (result == SOAPY_SDR_OVERFLOW) {
fprintf(stderr, "Overflow occurred. Samples were dropped.\n");
continue; // Retry reading
} else if (result < 0) {
fprintf(stderr, "Error: Failed to read stream. Error code: %d\n", result);
break; // Exit the loop on fatal errors
} else if (result == 0) {
fprintf(stderr, "Warning: No samples read from the stream.\n");
} else {
printf("Captured %d samples successfully.\n", result);
}
SoapySDRDevice_deactivateStream(sdr, rxStream, 0, 0);
fprintf(stderr, "1\n");
SoapySDRDevice_closeStream(sdr, rxStream);
fprintf(stderr, "2\n");
free(buff);
fprintf(stderr, "3\n");
SoapySDRDevice_unmake(sdr);
fprintf(stderr, "4\n");
printf("Stream closed.\n");
usleep(100000); // Add a small delay between captures (100ms)
}
return EXIT_SUCCESS;
}
There are no issues for this code in WSL (Debian) in Win11. But the initial Python code I proposed fails for some reason (in Win11).
There are no guarantees that closeStream()
will implicitly call deactivateStream()
I think. It's always safer to issue all needed calls.
The open /activate /deactivate /close sequence is needed to set up /clean up (hardware) resources. Depending on the SDR device they will perform different things and might not strictly be needed.
I'd only close /reopen a device if I want to free the hardware, e.g. when the program keeps running but is done with the SDR. I'd use de/activate every time I pause the stream, i.e. when I don't want samples to be contiguous.
Ok, we now have narrowed the bug down to Python on Windows doing something unexpected with memory locations. That's complicated to debug I guess. I'd trace the call graph or maybe easier pepper the SoapyRTLSDR source with debug prints and then follow the sequence of calls in the terminal. Some call must be duplicate or with wrong arguments.
I'm encountering a
Windows fatal exception: access violation error
when using SoapyRTLSDR on Windows 11 Home 64-bit. The crash occurs when trying to interact with the RTL-SDR device (NooElec R820T2 SDR & DVB-T) using SoapySDR, and it seems to be a bug in the SoapyRTLSDR implementation on Windows.The crash happens silently, but after adding the Python
faulthandler
library, I was able to trace the exact point of failure.Environment:
SoapySDRUtil output:
The following python script can be used to reproduce the error:
Expected behaviour: The program should run without crashing, and the SDR device should be properly deactivated and closed at each iteration.
Actual behaviour: The program crashes with a "Windows fatal exception: access violation error" in the
SoapySDR.py
library when closing the stream and cleaning up the SDR device. Here is the error traceback captured usingfaulthandler
:Additional Information:
Possible Cause:
unmake
.Please let me know if you need further information, and I can provide more details.