Изучаем GNU Radio при помощи микрофона
Прошлая статья про SDR и GNU Radio показала, что тема интересна сообществу. Учитывая, что про пакет GNU Radio информации на русском языке почти нет, да и на английском не всё понятно, я решил описать свои опыты с GNU Radio.
Про SDR и GNU Radio я уже писал в предыдущей статье. Напомню, моя цель показать, как перехватывать и даже излучать радио сигнал для управления устройствами умного дома (да и вообще IoT). Считаю важным привлечь внимание к безопасности в IoT. Но до этого нам ещё далеко… Для начала разобраться бы с GNU Radio!
SDR-приёмник есть не у всех, и мне показалось, что будет полезным показать, что можно сделать с GNU Radio с тем, что есть у каждого — а именно с микрофоном вашего ПК и наушниками.
Под катом несколько интересных экзерсисов со звуком.
Звуки вокруг
Итак, начнём с простого: изучения спектра звуков вокруг. Будем считать, что установку пакета GNU Radio или запуск подготовленного образа Ubuntu/Windows с GNU Radio вы произвели. Если нет, то на сайте GNU Radio скачайте образ на базе Ubuntu.
Начнем с создания простого проекта захвата звука с микрофона. Для этого добавим блок Audio Source, выставим samp_rate 48000 (многие карты работатю на 44100 Гц, а не на 48000 Гц).
Если обратиться к теореме Котельникова-Найквиста-Шеннона, то станет понятно, что частота дискретизации должна в 2 раза превышать верхнюю частоту полезного сигнала. Итак, 24 кГц — уже ближе. Неплохо об этом написано тут.
Вообще числа 48 кГц и 44.1 кГц — дань прошлому. Они связаны с количеством кадров в секунду и строками развёртки в древних системах записи видео, потом с форматом CD. В наши дни эти значения так и осталось стандартами.
Вернёмся к нашей схеме GNU Radio. К Audio Source подключим WX FFT. Не забываем QT GUI заменить на WX GUI в top_block, а так же изменить тип входа на Float. А теперь будем бегать по квартире, издавая разные звуки. Школьная физика нам много рассказала о том, что мы увидим, но все это забыто, да и своими глазами увидеть всегда интересней, чем просто прочесть в учебнике.
Первое, что мы увидим — ничего не видно! Вся значимая часть спектра сосредоточена в первых 2 или 3 кГц. Ну, ок, давайте добавим между источником и WX FFT новый блок: Rational Resampler — он позволит изменить Sample Rate, оставив только нужную часть спектра. Для удобства заведём новый блок Variable (переменную), назовём resamp и зададим значение, например, 15. В блок ресемплера в поле Decimation впишем имя этой переменной. Теперь на WX FFT будет подаваться сигнал с Sample Rate равным samp_rate/resamp, т.е. в этом блоке в поле Sample Rate нужно вставить именно это выражение. Теперь наш сигнал будет простираться до 1.5 кГц, что уже лучше. Ну, теперь всё видно!
Итак, начните с ноты ля 440 Гц. Проверим свой голос на частоту чистоту. Сразу интересная находка (напомню, мы это должны знать из школы): мы издаем не одну частоту, а набор кратных частот — тон и обертоны. У кого есть дома пианино или другой инструмент, можно попробовать и их. Вот так нота ля первой октавы выглядит на пианино.
А вот ля малой октавы. Всё как по учебнику — пиков стало в 2 раза больше, низший пик стал в 2 раза ниже (220 Гц)
Кратные частоты присутствуют не случайно. Ведь в случае пианино/гитары/… (струнных) звук извлекается колебанием струны. А у струны два закреплённых конца, т.е. могут излучаться только моды с длинной волны λ/2 = L*n, где n=1,2,3…
Аналогично с голосом. Связки позволяют менять характеристики гортани, которая выступает резонатором (да простят меня любители анатомии за неточности названий). Опять же, стенки закреплены, там у колебаний узловые точки, т.е. опять же та же формула для возбуждаемых мод.

(синее — спектр «тишины», зелёное — спектр хлопка)
Ой, тут прямо все частоты видны (обратите внимание на шкалу частот и уровень сигнала в сравнении с графиками выше!). Логично, ведь хлопок — это кратковременное повышение давления (очень резкое), т.е. почти δ-функция, а её спектр содержит все частоты.
Это кстати можно использовать. Кто помнит из 90-ых такие брелоки для поиска ключей, которые на свист начинали звуки издавать? Удобная штука была. Так вот они на хлопок тоже реагировали, т.к. в звуке хлопка есть в том числе и частота, соответствующая свисту.
Спектр алфавита
А теперь давайте послушаем себя. Произносите разные буквы алфавита и смотрите на спектр (лучше без ресемплинга, чтоб видеть, какие частоты используются в диапазоне 0-20 кГц). Человек использует несколько видов генерации звуков: губные (начинаются с размыкания губ, что создаёт много частот сразу), гортанные, зубные, язычные и носовые. Особенно интересно выглядят шипящие и свистящие (это гортанные и зубные) — их спектр очень широк и в основном располагается в спектре > 2.5 кГц (у звука «с» даже > 5 кГц). Это объясняет, почему слова с этим звуком плохо слышны по телефону (в былые времена полоса пропускания была на уровне 3 кГц или даже ниже — попробуйте добавить Low Pass фильтр и направить результат в Audio Sink с наушниками — результат будет напоминать старый добрый проводной телефон).
У кого есть дети, послушайте их — они сильно звонче, их голос содержит много высоких частот. Именно поэтому их голос по телефону всегда странный, а понять их частенько просто невозможно (искажения голоса сильно больше, чем у взрослого).
Кстати, я попробовал выдавить из себя самую высокую ноту. Ну, выше 700 Гц я не взял. Сын взял 1200 Гц! Я такой ограниченности от себя не ожидал — это же меньше 5% слышимого мной спектра. Почувствовал себя ущербным…
Поговорим с дельфинами
Ну ок, мы не можем так высоко звучать, так воспользуемся этой полосой для другого. Теперь попробуем сделать передачу голоса по ультразвуку. Поставим Low Pass фильтр (низких частот) после блока Audio Source, умножим на косинус 15 кГц (тем самым перенесём наш сигнал на 15 кГц выше) и отправим в динамик (он так себе с этим справляется). Но такой файл можно записать, используя File Sink. Проиграв такой файл, сторонний слушатель разобрать, что там сказано, не сможет. Шпионская игра на уровне 2 класса.
А теперь произведём обратное — восстановим закодированный сигнал. Обычный микрофон ноута плоховато воспринимает высокие частоты, а вот из файла — легко (для наших-то тестов достаточно). Голос после восстановления весьма неплохо воспринимается.
Примерно таким способом можно даже данные передавать: вот пример сетевого интерфейса на базе ультразвуковой передачи.
Данный пример показывает, что с GNU Radio и звуковой картой можно делать уже немало!
Обзор блоков GNU Radio
В GNU Radio очень много разных блоков, и разобраться в них не просто. Причём самое сложное — понять, что тут вообще есть. Ниже я приведу лишь наиболее популярные блоки, используемые «в быту».
Математические операции:
Add — сложение двух сигналов
Mulitly — перемножение двух сигналов (например, для АМ)
Add Const — прибавить (вычесть) константу (например, при демодуляции АМ убрать DC-составляющую)
Multiply Const — умножение сигнала на константу (для усиления, например)
Char/Integer/Float/Complex to Char/Integer/Float/Complex — преобразование типов данных
Источники:
Constant Source — источник, выдающий всегда одно и то же значение
Signal Source — источник, выдающий синусоидальный сигнал (для синтетического сигнала)
Audio Source/Sink — захват со звуковой карты или вывод в неё
File Source/Sink — чтение из файла (используйте Throttle для ограничения скорости чтения до нужного Sample Rate) и запись в файл
Wav File Source/Sink — чтение/запись WAV-файла
TCP/UDP Source/Sink — возможность стыковать проект с сетевым ПО посредством TCP или UDP сокетов
osmocom Source/Sink — приём данных от RTL-SDR или HackRF One или передача (для HackRF One)
Vector Source — источник последовательности чисел
Фильтры и модуляция:
Low/High/Band Pass Filter — низкочастотный/высокочастотный/полосовой фильтры
Frequency Xlating FIR Filter — совмещает сдвиг частоты и Low Pass фильтр для выделения нужной полосы частот
AM/FM/GFSK/… Mod/Demod — различные модуляторы и демодуляторы
Семплинг:
Rational Resampler — позволяет преобразовать входящую последовательность отсчётов из одного Sample Rate в другой путём децимации и интерполяции (используется для «подгона» под нужный Sample Rate)
Throttle — ограничение скорости подачи отсчётов до нужного Sample Rate (если в проекте нед ни одного блока, ограничивающего скорость обработки)
Delay — задерживает поток на нужное число отсчётов
WX GUI FFT Sink — графический вывод спектра сигнала
WX GUI Waterfall Sink — вывод спектральной мощности в режиме «водопада» (по оси X — частота, по Y — время, Z (цвет) — амплитуда). Полезно для поиска частоты излучения редко передающего источника в заданном диапазоне
WX GUI Constellation Sink — вывод фазовой диаграммы сигнала (разность фаз между колебаниями действительной и мнимой частей сигнала)
WX GUI Scope Sink — осциллограф
Разное:
Variable — переменная, позволяет использовать переменные вместо чисел в множестве блоков
Selector — мультиплексор, в сочетании с WX Slider позволит «на лету» переключать входы и выходы
Pack/Unpack K bits — преобразует из байтов 0/1 в последовательность из K бит и наоборот из байта в последовательность байт 0/1, соответствующую битам (удобно для кодирования/декодирования пакетов — байты 0/1 можно умножать на несущую, например)
Что ещё нужно знать о GNU Radio Companion (GRC)?
Все блоки в GRC, имеющие входы или выходы, требуют определения типа данных. Тип выхода одного блока должен быть таким же, как и входной тип связанного с ним блока. Выходные данные из одного блока можно передать в несколько блоков (т.е. нарисовать несколько связей). Но в один вход может входить только один поток!
Большинство блоков имеют минимальную документацию во вкладке в окне настроек, а так же в сносках к параметрам. Хотя обычно этого недостаточно. Немного устаревшая дока есть тут.
Все параметры у блоков могут быть выражениями Python. Т.е. вместо числа можно подставить формулу с участием переменных, определённых блоками Variable.
Часто хочется «покрутить» параметры проекта путём изменения значений переменных. Для этого используйте WX Slider или аналогичный компонент UI, используя его имя вместо нужной переменной. После запуска проекта в UI появится слайдер. Это удобно для перестройки частоты или параметров фильтров.
Ошибки в типе связей или в параметрах блока подсвечиваются красным. Они так же блокируют запуск проекта (в настройках блока, подсвеченного красным можно прочесть о проблеме). Есть run-time ошибки, которые проявляются уже при запуске проекта — лог внизу окна GRC подскажет, в чём проблема.
Некоторые блоки требуют целого числа в качестве параметра. Подстановка формулы может привести к несоответствию типов. Используйте Python-овскую функцию int() для преобразования к целому.
Для отключения блока используйте Disable/Enable. Это позволит не удалять блок, а просто исключить его из проекта на время с сохранением всех введённых параметров.
Все схемы в GRC преобразуются в скрипты на Python, что позволяет их потом изменять, а так же автоматизировать какие-то процессы минуя GRC.
Часто удобно сохранить звук/радио сигнал в файл для дальнейшего анализа. Для этого используйте блок File sink. Обратите внимание, что при чтении файла нужно помнить использованный формат данных при записи (тип числа), а так же Sample Rate. Рекомендую включать эти значения в имя файла помимо описания самого записываемого сигнала — это позволит не забыть, как его считать потом.
При проигрывании из файла обязательно используйте блок Throttle для ограничения скорости считывания. Это не требуется, если в схеме есть другой блок, который физически ограничивает скорость чтения данных. Например, блок Audio Sink итак ограничивает скорость подаваемых ему данных указанным в его настройках Sample Rate.
Для поиска нужного блока используйте значок лупы в панели инструментов. Двойной клик по названию в списке доступных блоком добавит блок в проект. Вместо этого можно перетащить его из списка в нужное место в проекте.
Старайтесь удобно располагать блоки в проекте. Это ни на что не влияет, кроме читабельности.
Надеюсь, данная статья побудит кого-то на эксперименты со звуком.
Software Defined Radio — как это работает? Часть 4
В третьей части было рассказано, как получить доступ к SDR-приемнику посредством языка Python. Сейчас мы познакомимся с программой GNU Radio — системой, позволяющей создать достаточно сложную конфигурацию радиоустройства, не написав ни единой строчки кода.
Для примера рассмотрим задачу параллельного приема нескольких FM-станций на один приемник. В качестве приемника будем использовать все тот же RTL SDR V3.
Продолжение под катом.
Установка
Для начала работы GNU Radio необходимо установить, дистрибутив для Windows можно скачать здесь. Система эта кроссплатформенная, версии есть также под Linux и под OSX (вроде бы GNU Radio успешно запускали и на Raspberry Pi, но 100% гарантии дать не могу).
По сути, GNU Radio это целый фреймворк для цифровой обработки сигналов, в котором программа «собирается» из отдельных модулей. Есть большое количество уже готовых блоков, при желании также можно создавать свои собственные. Сами модули написаны на С++, а для взаимодействия блоков друг с другом используется Python. Желающие могут посмотреть на API более подробно, но на практике это, скорее всего, не пригодится — все действия можно делать визуально в программе GNU Radio Companion.
Система ориентирована на обработку потоков данных, так что каждый блок обычно имеет вход и выход. Далее, соединяя блоки в редакторе, мы получаем готовую систему. Сам интерфейс GNU Radio довольно простой, сложность состоит в понимании того, что делает тот или иной блок. Как говорилось ранее, низкоуровневая работа с SDR имеет высокий порог входа и требует некоторого знания в DSP и математике. Но мы рассмотрим простую задачу, для которой никаких специальных знаний не потребуется. Итак, приступим.
Начало работы
Запускаем GNU Radio Companion, создаем новый проект, тип проекта выбираем WX GUI, добавляем на экран и соединяем два блока, как показано на скриншоте.
Мы видим два типа блоков — Source (источник) и Sink (выход, «слив»). RTL-SDR — это наш приемник, FFT GUI — это виртуальный спектроанализатор.
Переменную Sample Rate устанавливаем в 2048000, это частота дискретизации нашего приемника. Частоту RTL-SDR оставляем по умолчанию равной 100МГц.
Запускаем проект — все работает, мы видим спектр FM-станций. Первая программа для GNU Radio готова!
Если мы посмотрим лог, то увидим такие строки.
Generating: ‘D:\\MyProjects\\GNURadio\\top_block.py’
Executing: C:\Python27\python.exe -u D:\MyProjects\GNURadio\top_block.py
Да, мы можем посмотреть файл top_block.py, который сгенерил нам GNU Radio Companion. Истинные джедаи могут писать непосредственно в Python, но требуемый код, как мы видим, довольно большой. Мы же создали его за 1 минуту.
Впрочем, если убрать громоздкую инициализацию, то мы увидим, что ключевых строк кода не так уж много.
Так что в принципе, это можно написать вручную. Но мышью оно все-таки быстрее. Хотя возможность поменять код иногда может пригодиться, если захочется добавить какую-то нестандартную логику.
Принимаем FM-радио
Теперь попробуем принять одну из станций. Как было видно из скриншотов, центральная частота приемника 100МГц и ширина полосы пропускания около 2МГц. На спектре мы видим две станции, на 100.1МГц и 100.7МГц соответственно.
Первым шагом необходимо перенести спектр станции в центр, сейчас он отстоит вправо на 100КГц. Для этого вспоминаем школьную формулу умножения косинусов — в результате будет две частоты, сумма и разность — нужная станция сдвинется в центр, что нам и нужно (а лишнее мы потом отфильтруем).
Создаем две переменные для хранения частоты freq_center=100000000 и freq_1=100100000, также добавляем генератор сигналов с частотой freq_center — freq_1.
Т.к. система построена на базе Python, то в полях ввода параметров мы можем использовать выражения, что достаточно удобно.
Схема в итоге должна выглядеть так:
Теперь необходимо добавить сразу несколько блоков — уменьшить тактовую частоту входного сигнала (она равна 2048КГц), отфильтровать сигнал, подать его на FM-декодер, затем еще раз уменьшить тактовую частоту до 48КГц.
Результат показан на картинке:
Считаем внимательно. Мы делим тактовую частоту 2048КГц в 4 раза блоком Rational Resampler (получаем 512КГц), затем после Low Pass фильтра стоит WBFM-декодер с децимацией 10 (получаем 51.2КГц). В принципе, этот сигнал уже можно подать на звуковую карту, но высота тона будет чуть отличаться. Еще раз меняем тактовую частоту в 48/51, в результате будет тактовая частота 48.2КГц, разницей уже можно пренебречь.
Второй важный момент — тип входов. С приемника поступает комплексный IQ-сигнал (входы-выходы синего цвета), с FM-декодера выходит вещественный сигнал — входы и выходы желтого цвета. Если перепутать, ничего не заработает. Подробнее уже было на Хабре, нам достаточно понять общий принцип.
В общем, запускаем, убеждаемся что все работает. Можно запустить программу и слушать радио. Мы пойдем дальше — у нас же все-таки Software Defined радио — добавим одновременный прием второй станции.
Многоканальный прием
Второй приемник добавляется любимым программистским методом — Ctrl+C/Ctrl+V. Добавляем переменную freq_2, копируем блоки и соединяем их точно также.
Результат вполне сюрреалистичный — две FM-станции можно слушать одновременно. Тем же самым методом (Ctrl+V) можно добавить и третью станцию.
Запись
Слушать две станции оригинально, но на практике мало полезно. Сделаем что-то более нужное, например добавим запись звука в отдельные файлы. Это может быть достаточно удобно — с одного физического приемника можно параллельно записывать несколько каналов.
Добавим к каждому выходу компонент File Sink, как показано на скриншоте.
Windows-версия почему-то требует абсолютные пути файлов, иначе запись не работает. Запускаем, убеждаемся что все нормально. Размер сохраняемых файлов довольно большой, т.к. по умолчанию записывается формат float. Запись в формате int оставлю читателям в качестве домашнего задания.
Получившиеся файлы можно открыть в Cool Edit и убедиться, что звук записался нормально.
Разумеется, число записываемых каналов можно увеличить, оно ограничено только полосой пропускания приемника и мощностью компьютера. Кроме File Sink можно использовать и UDP Sink, так что программу можно использовать для трансляции по сети.
Запуск из командной строки
И последнее. Если использовать программу автономно, например для многоканальной записи, то UI в принципе и не нужен. В верхнем левом блоке Options меняем параметр Run Options на No UI. Запускаем программу еще раз, убеждаемся что все работает. Теперь сохраняем сгенерированный файл top_block.py — мы можем просто запускать его из командной строки, например из bat-файла или из консоли.
Если кому интересно, сгенерированный файл сохранен под спойлером.
Удобно и то, что система является кросс-платформенной, и получившаяся программа может работать на Linux, Windows и OSX.
Заключение
Можно сказать, что GNU Radio достаточно сложная система, не в плане рисования блоков конечно, а в плане понимания того, как все это работает. Но какие-то несложные вещи сделать вполне посильно и интересно. GNU Radio также удобно использовать как «виртуальную лабораторию» для обучения — к любой части схемы можно подключить виртуальный осциллограф или спектроанализатор и посмотреть, как выглядит сигнал.
Если не будет каких-то отдельных пожеланий, тему SDR-приема наверно можно закрыть — все основные моменты уже рассмотрены, да и количество просмотров от первой к третьей части падает почти по экспоненте (хотя еще можно написать про передачу, но оно требует более дорогого «железа» для тестов чем RTL SDR). Надеюсь все же, что некоторое понимание того как это работает, у читателей осталось. Ну и всем удачных экспериментов.
















