LINUX.ORG.RU

Python vs PIPE в задаче RTL-SDR

 , , , ,


1

3

Доброго времени суток!

Ставлю эксперимент с донглом rlt-sdr, задача - получить код, который непрерывно читает пачки сэмплов с донгла.

Пробую реализовать следующим образом: запустить при помощи Popen процесс rtl_sdr с выходом на stdout, затем - бесконечно захватывать чанки нужного размера.

Пробую:

import time
from subprocess import Popen, PIPE

fs = 240e3
ts = 1. / fs
chunk_size = 1024 * 2
n_chunks = 256

with Popen(['rtl_sdr', '-f', '433e6', '-s', str(fs), '-d', '0', '-'], stdout=PIPE) as rtl_sdr:

    previous_t = time.time()
    for i in range(n_chunks):

        t = time.time()
        approx_ts = 2 * (t - previous_t) / chunk_size  # coefficient 2 is for two IQ components
        previous_t = t

        print(f'~ts = {1e6 * approx_ts:0.6f} us\t'
              f'error = {1e6 * (approx_ts - ts):0.6f} us ({100. * (approx_ts - ts) / ts:0.6f}%)')

        buffer = rtl_sdr.stdout.read(chunk_size)

Код начинает выдавать последовательности типа

~ts = 0.008382 us	error = -4.158285 us (-99.798834%)
~ts = 0.006519 us	error = -4.160147 us (-99.843538%)
~ts = 0.008615 us	error = -4.158052 us (-99.793246%)
~ts = 0.006752 us	error = -4.159915 us (-99.837950%)

Каким образом промежутки между rtl_sdr.stdout.read-ами могут быть меньше, чем период дискретизации, умноженный на количество принимаемых сэмплов?!

В рамках эксперимента начинают брать чанки бОльше, параметр chunk_size = 1024 * 256:

~ts = 4.168489 us	error = 0.001822 us (0.043733%)
~ts = 4.164660 us	error = -0.002007 us (-0.048162%)
~ts = 4.165357 us	error = -0.001310 us (-0.031442%)

О, чудо! Нормальные показатели. Для референса пробую тоже самое при помощи https://github.com/roger-/pyrtlsdr:

import time
from rtlsdr import RtlSdr
from contextlib import closing

fs = 240e3
ts = 1. / fs
chunk_size = 1024 * 2
n_chunks = 256

with closing(RtlSdr()) as sdr:

    # configure device
    sdr.sample_rate = fs
    sdr.center_freq = 433e6
    sdr.gain = 'auto'

    previous_t = time.time()
    for i in range(n_chunks):

        t = time.time()
        approx_ts = (t - previous_t) / chunk_size  # coefficient 2 is for two IQ components
        previous_t = t

        print(f'~ts = {1e6 * approx_ts:0.6f} us\t'
              f'error = {1e6 * (approx_ts - ts):0.6f} us ({100. * (approx_ts - ts) / ts:0.6f}%)')

        buffer = sdr.read_samples(chunk_size)

И показатели нормальные:

~ts = 4.217378 us	error = 0.050711 us (1.217069%)
~ts = 4.208996 us	error = 0.042329 us (1.015903%)
~ts = 4.207715 us	error = 0.041049 us (0.985169%)

даже для chunk_size = 1024 * 2!

Вопрос: что за фигня у меня с Popen? Откуда у него данных больше, чем физически должен выдавать девайс и только при случае маленького буфера в read???!!

Чем меня не устраивает подход с pyrtlsdr? Тем, что я (пока) уверен, что после каждого read_samples я буду получать случайную начальную фазу, и, как следствие, читаемые чанки будут некогерентными.

Призываю радиолюбителей и технарей!

Ответ на: комментарий от anonymous

Благодарю за наводку, но пока не вижу разницы, у них так же popen только с shell=True...

omegatype ★★★
() автор топика
Ответ на: комментарий от anonymous

скорее буфер в самом устройстве.

тс, попробуй предварительно, перед циклом, читать какое-нибудь адекватное (1024 * 256?) количество данных, чтобы очистить аппаратный буфер. поищи даташиты, чтобы точно узнать его размер или может быть его как-нибудь через rtl_sdr можно зафлашить.

anonymous
()
Ответ на: комментарий от anonymous

ну тут то они через биндинги к librtlsdr работают т.е. пайплайн получается [буфер девайса]->[буфер либы]

а если через выхлоп rtl_sdr то пайплайн [буфер девайса]->[буфер либы]->[буфер stdout]

anonymous
()
Ответ на: комментарий от anonymous

буфер либы или системный буфер или пайп или буфер драйвера тоже откуда-то изначально данные берут. если бы девайс их отдавал с частотой семплирования, то и они бы с этой частотой отдавали. хотя про драйвер хз, кстати.

можно копнуть, конечно, и в libsdr, как они там это делают, но это уже пусть тс сам делает, если не найдёт другого способа.

кста, питоновский модуль вроде ничего так на первый взгляд, там асинхронщина даже какая-то есть (я, правда, сильно не вникал), так что, тс, подумай, может всё-таки лучше его взять?

anonymous
()
Ответ на: комментарий от anonymous

если бы девайс их отдавал с частотой семплирования, то и они бы с этой частотой отдавали.

это же usb, там bulk режим передачи используется.

anonymous
()

Судя по исходникам librtlsdr, там действительно какой-то аппартаный буфер.

int rtlsdr_reset_buffer(rtlsdr_dev_t *dev)
{
	if (!dev)
		return -1;

	rtlsdr_write_reg(dev, USBB, USB_EPA_CTL, 0x1002, 2);
	rtlsdr_write_reg(dev, USBB, USB_EPA_CTL, 0x0000, 2);

	return 0;
}
Это же какой-нибудь rtl2832u, поди?

Makhno
()
Ответ на: комментарий от anonymous

Что мне было бы интересно понять, так как это чанки возвращаются _быстрее_ частоты сэмплирования?! Ведь все буферы и проч. должны увеличивать задержку прихода данных -> оцениваемое время сэмплирования должно быть всегда больше расчетного, но не меньше... ?

omegatype ★★★
() автор топика
Ответ на: комментарий от omegatype

с ацп данные не могут поступать физически быстрее, чем на частоте семплирования. в то время, пока ты не читаешь из устройства, данные собираются в буфер. его делают как раз для того, чтобы не пропустить данные, если, например, программа не может своевременно читать. буферы лишь сдвигают во времени момент передачи данных, но в среднем не замедляют и не ускоряют её.

другими словами, в буфере просто накапливаются данные. аппаратный буфер, обычно, наполняется автоматически в то время, пока ты не читаешь из устройства. при первом чтении ты получаешь готовые данные из буфера, а не напрямую из ацп, отсюда и большая скорость. сам буфер при этом очищается и при последующих чтениях ты получаешь данные напрямую с ацп.

то есть при первом чтении ты получишь данные за размер_буфера/(размер_семпла*частота) предыдущих секунд, если в течении этого времени ты не читал из устройства.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.