Привет, Хабр! На дворе День радио, а значит у нас есть отличный повод сделать что-нибудь интересное. На днях мой взгляд упал на пылившийся в углу SDR-приемник, и тут понеслось.
Вообще spectrum painting, или рисование картинок на SDR-спектрограмме — развлечение довольно старое и нехитрое: берем картинку, прогоняем каждую строку через обратное преобразование Фурье и строку за строкой посылаем получившийся сигнал в эфир. Вот, например, готовый проект на гитхабе, который сразу генерирует выходной файл для HackRF или BladeRF. Но вот у меня, скажем, нет ни HackRF, ни чего-либо похожего, а передать картинку хочется, да как-нибудь попроще. Как же быть?
В принципе, можно было бы использовать простенький лабораторный генератор или DDS, но обычно их выходной буфер не превышает нескольких килобайт, в то время, как видео легко может занять десятки мегабайт. Постойте, но ведь у нас под рукой и так есть отличный девайс для воспроизведения длинных файлов — аудиовыход компьютера! Остается взять генератор несущей радиочастоты (в моем случае это 27 МГц) и промодулировать ее записью с компьютера на смесителе.
Смеситель (mixer) — это нелинейный элемент с двумя входами и одним выходом, который складывает или вычитает друг из друга входные частоты. Когда мы хотим послушать радио на 101.6 МГц на карманном приемнике, тот выставляет частоту гетеродина на 101.5 МГц и вычитает ее из несущей при помощи смесителя, понижая частоту до 100 кГц и завершая демодуляцию на ней. Мы будем делать то же самое, только в другую сторону: прибавим 27 МГц несущей к 10-20 кГц аудиосигнала, в котором закодирована картинка. На языке SDR-спектрограмм это означает, что картинка просто сместится вверх по частоте на 27 МГц.
Простейший смеситель можно собрать из диодов. Но мне было лень, и я взял какой-то готовый от MiniCircuits.
Белая коробочка — это и есть смеситель. Слева от него Red Pitaya в роли генератора несущей.
Тут начинаются нюансы. Первый заключается в том, что аудиокарта выдает сигнал только до 20 кГц, а промышленные смесители не оптимизированы для таких низких частот. Чтобы получить приемлемый сигнал на выходе, придется усилить сигналы на входе, что неизбежно приведет к нелинейностям — иными словами, генерации суммарных и разностных частот из самого аудиосигнала:
Это ограничивает рабочий диапазон от 10 до 20 кГц: выше 20 кГц не работает аудиокарта, ниже 10 кГц появляется разностный сигнал от аудиочастот (мы его еще увидим).
Второй нюанс заключается в том, что преобразование Фурье — комплексное, а в частотном, да и временном представлении сигнала существенную роль играют не только интенсивности, но и фазы. На практике чаще всего работают не с комплексными числами, а с IQ-представлением в двух ортогональных квадратурах, повернутых на 90 градусов. Например, в SDR-приемнике стоят два смесителя, которые сбивают сигнал с частотой гетеродина, повернутой на 90 градусов:
А в передатчике HackRF стоит микросхема MAX2839, которая сбивает I и Q компоненты сигнала с двумя ортогональными несущими на двух смесителях, суммирует их выходные сигналы и отправляет их дальше в антенный тракт:
В нашей поделке есть только один смеситель, а значит мы передаем сигнал только в одной квадратуре, то есть без учета фаз. Фурье-преобразование такого сигнала симметрично, а значит, помимо заветной картинки справа от несущей мы также увидим ее зеркальную реплику слева от несущей, и потеряем половину интенсивности. Это не страшно, но сигнал немного ослабит.
Переходим к вычислениям. Давайте передавать картинку стандартным битрейтом в 44100 сэмплов в секунду (профессор Котельников напоминает нам, что для 20 кГц больше и не понадобится), а одну строку картинки будем передавать за 2048 точек. Из-за вышеупомянутых нелинейных эффектов и зеркальной реплики мы можем использовать только четверть из них, но вообще лучше сделать картинку еще меньше. Пусть будет 256 пикселей.
Вот, в принципе, и все. Код очень похож на 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 = np.float32(img.imread(filename)[:,:,0])
# Embed image starting from the given pixel
fftall = np.zeros((ffts.shape[0], nfft))
fftall[:, start_pix:(start_pix+pic.shape[1])] = pic
# 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!
Как я и говорил выше, реальный сигнал без мнимой компоненты дает зеркальное изображение относительно несущей, в точности как при амплитудной модуляции. Хотя оно оказалось заметно тусклее, чем ожидалось, вероятно, из-за нелинейностей. По той же причине появляется шум рядом с несущей (на разностях частот из картинки) и выше 22 кГц (на их второй гармонике).
Выглядит неплохо, но я здорово ускорил это видео. На самом деле передача одного кадра занимает почти 8 секунд! А можно ли сделать что-то более похожее на настоящее видео? Хотя бы пять кадров в секунду? Давайте считать.
Имеет ли смысл увеличивать sampling rate (SR) передатчика? Он определяет максимальную ширину спектра, в то время, как наша картинка занимает ширину Δf. При ширине картинки в W пикселей полная ширина спектра составит
После преобразования Фурье количество точек останется таким же, а значит, на передачу одной строчки изображения уйдет
Ага, то есть время на передачу одной строки вообще не зависит от sampling rate! А значит, увеличивать его никакого смысла нет. Время на передачу всего кадра размером W х H составит
Получается, для T = 0.2 с (5 кадров в секунду) и Δf = 20 — 10 кГц = 10 кГц один кадр не сможет превышать где-то 40 х 40 пикселей. В принципе с этим уже можно работать.
И, наконец, самый главный вопрос: а можно ли действительно смотреть видео на SDR, а не просто проматывать пленку? А почему бы и нет: например, можно вставить в спектр синхроимпульсы перед началом каждого кадра и написать простенький интерфейс для приемника, который будет их распознавать. А можно просто взять другую программу для работы с SDR. Например, SDRAngel перерисовывает спектрограмму заданое количество раз в секунду:
Остаeтся подобрать sampling rate так, чтобы один кадр занимал ровно одну пятую секунды, и чтобы видео не плыло вверх-вниз как на непрогретом ламповом телевизоре. Размер моего кадра составляет 51х32 пикселя, поэтому для преобразования Фурье хватит длины выборки длиной 256 (чуть больше 51х4). В этом случае sampling rate будет равен 256 x 32 x 5 = 40 960 сэмплов в секунду.
Ну что, попробуем?
С Днем Радио! Всем 73!
Комментарии (17)
RichardMerlock
07.05.2024 21:58+11например, можно вставить в спектр синхроимпульсы перед началом каждого кадра и написать простенький интерфейс для приемника, который будет их распознавать.
и в результате переизобрести аналоговое телевидение сверхнизкой чёткости!
checkpoint
07.05.2024 21:58+4Моё почтение! Снимаю шляпу.
PS: Текст песни, по канонам, должен быть на японском, а не на английском. :)
nikolz
07.05.2024 21:58+2Может все таки не "миксер", а "умножитель"; не "сбивает", а "умножает".
Потому, что на диодах сделан именно умножитель. А для выделения нужного диапазона есть соответствующий фильтр.
И если речь о смесителе в приемнике, то это как раз и есть умножитель аналоговых сигналов. Он не " складывает или вычитает друг из друга входные частоты", а умножает входные сигналы.
kbtsiberkin
07.05.2024 21:58+3Тогда уж смеситель, по нашему, хоть он приёмный, хоть передающий :)
nikolz
07.05.2024 21:58согласен, но это фактически умножитель сигналов, а не вычитатель и сумматор частот.
qbertych Автор
07.05.2024 21:58+1Так это один и тот же процесс. Тождество о сумме/произведении косинусов не даст соврать.
nikolz
07.05.2024 21:58Нет, синус разности и косинус суммы частот двух синусоидальных сигналов на выходе умножителя сигналов появляются в простейшем примере, для объяснения на пальцах процесс переноса и в предположении что у нас узкополосный и гармонический сигнал.
Физически это умножение сигналов и реально сигналы на входе имеют произвольную форму. Мы можем представить входные сигналы рядом Фурье. Но умножитель нелинейный элемент и на его выходе не действует принцип суперпозиции.
Кроме того, не существуют физически "частоты" как и их "разность частот" или "сумма частот". Это лишь параметры описания периодических сигналов.
NanoVHF
07.05.2024 21:58+1Классно сделано! Моё почтение! Но, разрешите уточнить, почему у вас все изображения и спектры показаны едва чуть больше уровня шумов?
Oangai
да, ненароком поймаешь такой спектр, кондратья хватит :)
LaoSan
Прилетело НЛО и запилило в эфир этот спектр ))
ssj100
Главное чтоб не диктор не вещал на немецком Контакт