День Радио: смотрим видео на радиоспектре через SDR

cmqhb6ote79txbptwbjkrbalaic.jpeg

Привет, Хабр! На дворе День радио, а значит у нас есть отличный повод сделать что-нибудь интересное. На днях мой взгляд упал на пылившийся в углу SDR-приемник, и тут понеслось.

Вообще spectrum painting, или рисование картинок на SDR-спектрограмме — развлечение довольно старое и нехитрое: берем картинку, прогоняем каждую строку через обратное преобразование Фурье и строку за строкой посылаем получившийся сигнал в эфир. Вот, например, готовый проект на гитхабе, который сразу генерирует выходной файл для HackRF или BladeRF. Но вот у меня, скажем, нет ни HackRF, ни чего-либо похожего, а передать картинку хочется, да как-нибудь попроще. Как же быть?

В принципе, можно было бы использовать простенький лабораторный генератор или DDS, но обычно их выходной буфер не превышает нескольких килобайт, в то время, как видео легко может занять десятки мегабайт. Постойте, но ведь у нас под рукой и так есть отличный девайс для воспроизведения длинных файлов — аудиовыход компьютера! Остается взять генератор несущей радиочастоты (в моем случае это 27 МГц) и промодулировать ее записью с компьютера на радиомиксере.

Миксер — это нелинейный элемент с двумя входами и одним выходом, который складывает или вычитает друг из друга входные частоты. Когда мы хотим послушать радио на 101.6 МГц на карманном приемнике, тот выставляет частоту гетеродина на 101.5 МГц и вычитает ее из несущей при помощи миксера, понижая частоту до 100 кГц и завершая демодуляцию на ней. Мы будем делать то же самое, только в другую сторону: прибавим 27 МГц несущей к 10–20 кГц аудиосигнала, в котором закодирована картинка. На языке SDR-спектрограмм это означает, что картинка просто сместится вверх по частоте на 27 МГц.

vcwxbghazepbn1aqgbnr-pkgjqw.jpeg

Простейший миксер можно собрать из диодов. Но мне было лень, и я взял какой-то готовый от MiniCircuits.

zqiwgwjn8zkvwqkwgvo6nugxqqm.jpeg
Белая коробочка — это и есть миксер. Слева от него Red Pitaya в роли генератора несущей.

Тут начинаются нюансы. Первый заключается в том, что аудиокарта выдает сигнал только до 20 кГц, а промышленные миксеры не оптимизированы для таких низких частот. Чтобы получить приемлемый сигнал на выходе, придется усилить сигналы на входе, что неизбежно приведет к нелинейностям — иными словами, генерации суммарных и разностных частот из самого аудиосигнала:

o9sbusywgyevay6tuywhxlz67_o.png

Это ограничивает рабочий диапазон от 10 до 20 кГц: выше 20 кГц не работает аудиокарта, ниже 10 кГц появляется разностный сигнал от аудиочастот (мы его еще увидим).

Второй нюанс заключается в том, что преобразование Фурье — комплексное, а в частотном, да и временном представлении сигнала существенную роль играют не только интенсивности, но и фазы. На практике чаще всего работают не с комплексными числами, а с IQ-представлением в двух ортогональных квадратурах, повернутых на 90 градусов. Например, в SDR-приемнике стоят два миксера, которые сбивают сигнал с частотой гетеродина, повернутой на 90 градусов:

oxs0vdmdxulppxz1cya3ne_vnpc.png

А в передатчике HackRF стоит микросхема MAX2839, которая сбивает I и Q компоненты сигнала с двумя ортогональными несущими на двух миксерах, суммирует их выходные сигналы и отправляет их дальше в антенный тракт:

nzin-ukyoe5sdryefmi0xktmtpk.png

В нашей поделке есть только один миксер, а значит мы передаем сигнал только в одной квадратуре, то есть без учета фаз. Фурье-преобразование такого сигнала симметрично, а значит, помимо заветной картинки справа от несущей мы также увидим ее зеркальную реплику слева от несущей, и потеряем половину интенсивности. Это не страшно, но сигнал немного ослабит.

Переходим к вычислениям. Давайте передавать картинку стандартным битрейтом в 44100 сэмплов в секунду (профессор Котельников напоминает нам, что для 20 кГц больше и не понадобится), а одну строку картинки будем передавать за 2048 точек. Из-за вышеупомянутых нелинейных эффектов и зеркальной реплики мы можем использовать только четверть из них, но вообще лучше сделать картинку еще меньше. Пусть будет 256 пикселей.

vhh4gwawymeknfsjf-7qoxs2zje.png

Вот, в принципе, и все. Код очень похож на spectrum painter


А вот и сам код
import numpy as np
import scipy
import imageio.v2 as img

filename = 'img.png'
sr = 44100 # Hz
nfft = 2048
start_pix = nfft-256-1-256 # from which the image will be embedded

# Load the image
pic = img.imread(filename)

# Repeat each given line given amount of times
ffts = np.flipud(pic[:,:,0])**2

# Embed image starting from the given pixel
fftall = np.zeros((ffts.shape[0], nfft))
fftall[:, start_pix:(start_pix+pic.shape[1])] = ffts

# Generate random phase vectors for the FFT bins
# This is important to prevent high peaks in the output
phases = 2*np.pi*np.random.rand(*fftall.shape)
rffts = fftall * np.exp(1j*phases)

# Perform the FFT per image line
ifft = np.fft.ifft(np.fft.ifftshift(rffts, axes=1), axis=1) / np.sqrt(float(nfft))

# Concatenate lines to form the final signal, take one quadrature, and normalize
signal = normalize(np.real(ifft.flatten()))
signal_norm = np.float32(signal / max(signal.max(), -signal.min()))

# Write the signal to a wav file
scipy.io.wavfile.write(f'out.wav', sr, signal_norm)

Ну что, пора запустить Doom? А вот и нет: Doom запускают программисты. Инженеры запускают Bad Apple!

ywgpfua0qwiipw1feliociiltxm.gif


Что еще видно в спектре

Как я и говорил выше, реальный сигнал без мнимой компоненты дает небольшое зеркальное изображение относительно несущей, в точности как при амплитудной модуляции. Хотя здесь оно оказывается заметно тусклее, вероятно, из-за нелинейностей. По той же причине появляется шум рядом с несущей (на разностях частот из картинки) и выше 22 кГц (на их второй гармонике).

ljc2weokns1v7fe2u8ntjwhbjki.jpeg

Выглядит неплохо, но я здорово ускорил это видео. На самом деле передача одного кадра занимает почти 8 секунд! А можно ли сделать что-то более похожее на настоящее видео? Хотя бы пять кадров в секунду? Давайте считать.

Имеет ли смысл увеличивать sampling rate (SR) передатчика? Он определяет максимальную ширину спектра, в то время, как наша картинка занимает ширину Δf. При ширине картинки в W пикселей полная ширина спектра составит

8kgcqskabqze6jofhpqnrgbi8ug.png

После преобразования Фурье количество точек останется таким же, а значит, на передачу одной строчки изображения уйдет

izw244djr4men4nrckas462tyfo.png

Ага, то есть время не передачу одной строки вообще не зависит от sampling rate! А значит, увеличивать его никакого смысла нет. Время на передачу всего кадра размером W х H составит

vw_4fqk7xrikmuqasdpt-uqsun8.png

Получается, для T = 0.2 с (5 кадров в секунду) и Δf = 20 — 10 кГц = 10 кГц один кадр не сможет превышать где-то 40×40 пикселей. В принципе с этим уже можео работать.

И, наконец, самый главный вопрос:, а можно ли действительно смотреть видео на SDR, а не просто проматывать пленку? А почему бы и нет: например, можно вставить в спектр синхроимпульсы перед началом каждого кадра и написать простенький интерфейс для приемника, который будет их распознавать. А можно просто взять другую программу для работы с SDR. Например, SDRAngel рисует спектрограмму заданое количество раз в секунду:

1rjfgq9cuqm_f718isr_iirkegy.jpeg

Остаeтся подобрать sampling rate так, чтобы мы видели ровно 5 кадров в секунду. Размер моего кадра составляет 51×32 пикселя, поэтому для преобразования Фурье хватит длины выборки длиной 256 (чуть больще 51×4). В этом случае sampling rate будет равен 256×32 x 5 = 40 960 сэмплов в секунду.

hpxqrl_fadrsciuvwtr4p9wb2fa.png

Ну что, попробуем?

С Днем Радио! Всем 73!

© Habrahabr.ru