Привет, Хабр! На дворе День радио, а значит у нас есть отличный повод сделать что-нибудь интересное. На днях мой взгляд упал на пылившийся в углу 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)


  1. Oangai
    07.05.2024 21:58
    +3

    да, ненароком поймаешь такой спектр, кондратья хватит :)


    1. LaoSan
      07.05.2024 21:58

      Прилетело НЛО и запилило в эфир этот спектр ))


    1. ssj100
      07.05.2024 21:58
      +5

      Главное чтоб не диктор не вещал на немецком Контакт


  1. RichardMerlock
    07.05.2024 21:58
    +11

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

    и в результате переизобрести аналоговое телевидение сверхнизкой чёткости!


    1. VT100
      07.05.2024 21:58
      +2

      Уже. SSTV (Slow Scan TV). Там, правда, замедляют скорость ради чёткости.


  1. alekseypro
    07.05.2024 21:58
    +8

    DOOM на спектре уже запускали? ))


  1. checkpoint
    07.05.2024 21:58
    +4

    Моё почтение! Снимаю шляпу.

    PS: Текст песни, по канонам, должен быть на японском, а не на английском. :)


    1. qbertych Автор
      07.05.2024 21:58

      Спасибо! Нравится мне английская версия, что поделать :)


  1. nikolz
    07.05.2024 21:58
    +2

    Может все таки не "миксер", а "умножитель"; не "сбивает", а "умножает".

    Потому, что на диодах сделан именно умножитель. А для выделения нужного диапазона есть соответствующий фильтр.

    И если речь о смесителе в приемнике, то это как раз и есть умножитель аналоговых сигналов. Он не  " складывает или вычитает друг из друга входные частоты", а умножает входные сигналы.


    1. kbtsiberkin
      07.05.2024 21:58
      +3

      Тогда уж смеситель, по нашему, хоть он приёмный, хоть передающий :)


      1. nikolz
        07.05.2024 21:58

        согласен, но это фактически умножитель сигналов, а не вычитатель и сумматор частот.


        1. qbertych Автор
          07.05.2024 21:58
          +1

          Так это один и тот же процесс. Тождество о сумме/произведении косинусов не даст соврать.


          1. nikolz
            07.05.2024 21:58

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

            Физически это умножение сигналов и реально сигналы на входе имеют произвольную форму. Мы можем представить входные сигналы рядом Фурье. Но умножитель нелинейный элемент и на его выходе не действует принцип суперпозиции.

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


      1. qbertych Автор
        07.05.2024 21:58

        Да, по-русски это смеситель. Поправил.


  1. NanoVHF
    07.05.2024 21:58
    +1

    Классно сделано! Моё почтение! Но, разрешите уточнить, почему у вас все изображения и спектры показаны едва чуть больше уровня шумов?


    1. qbertych Автор
      07.05.2024 21:58

      Спасибо! Всё для лучшего контраста картинок. Выше по интенсивности там все равно ничего нет, только несущая.


      1. NanoVHF
        07.05.2024 21:58

        Как пример, для лучшего контраста С/Ш бы не менее 15...20дб