GNURadio + RTL-SDR: приём нескольких каналов одновременно.
Введение.
С появлением дешевых “свистков”, которые способны работать в режиме SDR, у каждого теперь есть возможность вести запись нескольких каналов одновременно. Вопрос, зачем это нужно, я оставлю за скобками этой статьи, так как каждый решает этот вопрос самолично. Я постараюсь донести до массового сознания сам способ.

GNURadio.
Если по железу более менее все понятно, этому посвящена целая ветка обсуждения на форуме[1], то по прикладной части, тема была не так хорошо раскрыта. Ядром моего способа записи является пакет свободного ПО - GNURadio. В релизе Ubuntu 12.10 идёт самая свежая версия GNURadio, поэтому имеет смысл ставить ее из репозитория, а не посредством специального скрипта. Для ранних релизов Ubuntu стоит воспользоваться скриптом[2].

Скрипты.
Я перепробовал несколько способов сборки N приёмников подключенных к одному источнику (rtl-sdr), и на сегодняшний день, пришел к выводу, что самым перспективным является способ сборки цепочки приемников. Прежде всего это связано с масштабируемостью. Во-первых, Gnuradio Companion не позволит создать сколько угодно большую схему, во-вторых для N приёмников придется генерировать свою схему, в-третьих одну цепочку можно развернуть на нескольких серверах.

Первый приёмник непосредственно подключается к источнику, демодулирует сигнал и пробрасывает спектр на второй по протоколу tcp, второй и последующий, не последний, демодулирует свою частоту и пробрасывает спектр дальше. Последний замыкающий приемник только демодулирует сигнал. Специфика этого способа такова, что демодуляторы начнут работать, только после того как спектр начнет пробрасываться от первого до последнего в цепочке приемника. Цепочка может быть распределена по нескольким серверам, в случае если один сервер не справляется со всем спектром. Например, в моем случае 2-х ядерный i3 мог обрабатывать 28 каналов. Для выбранной ширины спектра в 1 МГц, количество каналов может колебаться от 40 (25 кГц) до 160 (6.25 кГц).

Как устроен скрипт, который запускает цепочку я опишу чуть позже. Сначала посмотрим как устроены звенья цепочки и с какими параметрами они запускаются. Для моего примера используются FM-демодуляторы, но никто не мешает сделать универсальные и с помощью параметра переключать модуляции.
 
~$ ./rtlhead.py --help
linux; GNU C++ version 4.6.3; Boost_104601; UHD_003.004.003-254-gce421204

Usage: rtlhead.py: [options]

Options:
-h, --help show this help message and exit
--bfreq=BFREQ Set bfreq [default=439500000]
--srate=SRATE Set srate [default=1024000]
--vol=VOL Set vol [default=500m]
--sql=SQL Set sql [default=-33]
--sport=SPORT Set sport [default=9101]
--xport=XPORT Set xport [default=8101]
--xaddr=XADDR Set xaddr [default=localhost]
--saddr=SADDR Set saddr [default=localhost]
--devarg=DEVARG Set devarg [default=rtl=1]
--ppm=PPM Set ppm [default=75]
--frq=FRQ Set frq [default=439950000]
 
~$ ./rtllink.py --help
Usage: rtllink.py: [options]

Options:
-h, --help show this help message and exit
--bfreq=BFREQ Set bfreq [default=439500000]
--srate=SRATE Set srate [default=1024000]
--sql=SQL Set sql [default=-33]
--vol=VOL Set vol [default=500m]
--xaddr=XADDR Set xaddr [default=localhost]
--sport=SPORT Set sport [default=9102]
--dport=DPORT Set dport [default=9101]
--saddr=SADDR Set saddr [default=localhost]
--daddr=DADDR Set daddr [default=localhost]
--xport=XPORT Set xport [default=8101]
--frq=FRQ Set frq [default=439950000]
 
~$ ./rtltail.py --help
Usage: rtltail.py: [options]

Options:
-h, --help show this help message and exit
--bfreq=BFREQ Set bfreq [default=439500000]
--srate=SRATE Set srate [default=1024000]
--sql=SQL Set sql [default=-33]
--vol=VOL Set vol [default=500m]
--xport=XPORT Set xport [default=8101]
--dport=DPORT Set dport [default=9101]
--xaddr=XADDR Set xaddr [default=localhost]
--daddr=DADDR Set daddr [default=localhost]
--frq=FRQ Set frq [default=439950000]

Теперь несколько слов, о том как устроен скрипт. На вход скрипта подается файл конфигурации каналов цепочки, которые предполагается писать. Для корректного закрытия wav файлов придется использовать xmlrpc протокол и маленький скрипт (я выбрал perl, мне так проще), который будет с заданным интервалом ротировать файлы. Звено цепочки открывает свои tcp порты для xmlrpc и проброса спектра (за искл. замыкающего). Звуковые файлы WAV я записываю в директории /tmp, которая смонтирована у меня непосредственно в оперативной памяти. После запуска скрипта, с заданным интервалом файлы ротируются и можно приступать к их обработке.

Конфигурация каналов:
~$ cat matrix4.txt
439837500,-22,1.0,FUAO
439912500,-22,1.0,FVAO
439750000,-22,1.0,1OK
439950000,-22,1.0,2OK

Скрипт запуска цепочки:
#!/bin/bash
i=1
rtl=$1
cfg=$2
srate=$3
bfreq=$4
ppm=$5
dur=$6

nums=$(cat $cfg | wc -l)

test -e rtlhead.py || exit -1
test -e rtllink.py || exit -1
test -e rtltail.py || exit -1

sdrdir=/tmp/matrix$rtl$nums
pids=/tmp/matrix$rtl$nums.pid
mkdir -p $sdrdir

while read line; do
frq=$(echo $line | cut -d, -f1)
sql=$(echo $line | cut -d, -f2)
vol=$(echo $line | cut -d, -f3)
tag=$(echo $line | cut -d, -f4)
jack="jack_$tag"
let xport=8000+100*rtl+i
let sport=9000+100*rtl+i
let dport=sport-1
if [ $i -eq 1 ]; then
opts="--srate=$srate --bfreq=$bfreq --devarg=rtl=$rtl --ppm=$ppm"
opts="$opts --frq=$frq --vol=$vol --sql=$sql"
opts="$opts --xport=$xport --sport=$sport"
echo $opts
./rtlhead.py $opts & echo $! > $pids
sleep 10
fi
if [ $i -gt 1 -a $i -lt $nums ]; then
opts="--srate=$srate --bfreq=$bfreq"
opts="$opts --frq=$frq --vol=$vol --sql=$sql"
opts="$opts --xport=$xport --sport=$sport --dport=$dport"
echo $opts
./rtllink.py $opts & echo $! >> $pids
sleep 1
fi
if [ $i -eq $nums ]; then
opts="--srate=$srate --bfreq=$bfreq"
opts="$opts --frq=$frq --vol=$vol --sql=$sql"
opts="$opts --xport=$xport --dport=$dport"
echo $opts
./rtltail.py $opts & echo $! >> $pids
fi
let i=i+1
done < $cfg

sleep 15

i=1

while read line; do
tag=$(echo $line | cut -d, -f4)
let xport=8000+100*rtl+i
./ctl.pl $dur $xport $sdrdir $tag & echo $! >> $pids
let i=i+1
done < $cfg

Скрипт ротирования звуковых файлов:

#!/usr/bin/perl -w
use DateTime;
use RPC::XML;
use RPC::XML::Client;

$RPC::XML::ALLOW_NIL = 1;
$sleeps=$ARGV[0];
$xmlport=$ARGV[1];
$prefix=$ARGV[2];
$freq=$ARGV[3];

$server_url = "http://localhost:$xmlport";
$server = RPC::XML::Client->new($server_url);

for (;;)
{
$dt = DateTime->now;
$dt->set_time_zone( 'Europe/Moscow' );
$ymd=$dt->ymd;
$hms=$dt->hms('-');
$server->send_request('set_fname',"$prefix/$ymd"."_"."$hms"."_".$freq.".wav");
$server->send_request('set_rec', 1);
sleep($sleeps);
$server->send_request('set_rec', 0);
}

Обработка файлов.

Общий алгоритм обработки файлов сводится к разрезанию файла на кусочки, в которых присутствует передача и сортировка их по частотам и времени приёма. Для этого я написал еще один скрипт, который вызывается планировщиком cron каждые n минут (например, 3) и выполняет обработку файлов. На выходе получаются директории с разбиением по времени и частотам. Для удобства я использую утилиту mp3splt, поэтому wav файлы сначала перегоняются в mp3 (это просто удобный способ).

#!/bin/bash

PATH=/home/anton/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

export PATH

sdr_pref=$1
ch_num=$2
cd $sdr_pref

prefix=$HOME/scanner/sdr

nwav=$(ls | grep wav | wc -l)

[ $nwav -lt $ch_num ] && exit 0

let nwav=nwav-ch_num

for wavf in $(ls | grep wav | head -$nwav); do

i=0
dur=$(soxi -D $wavf | cut -d. -f1)
modt=$(stat -c %Y $wavf)
let opent=modt-dur
echo "Duration of $wavf is $dur, modify time is $modt and open time is $opent."

fname=${wavf%.*}
mp3f="$fname.mp3"
frq=$(echo $wavf | cut -d_ -f3 | cut -d. -f1)

echo "Filename is $fname, mp3 - $mp3f, frq - $frq."

lame -b 16 $wavf $mp3f

echo "Laming $wavf to $mp3f."

mp3splt -N -s -p rm -o ${opent}_@m-@s_@M-@S $mp3f

echo "Spliting $mp3f."

for mp3f1 in $(ls | grep ${opent}); do

let i=i+1
rect=""
fname1=""
fname=${mp3f1%.*}
ms=$(echo $fname | cut -d_ -f2 | cut -d"-" -f1)
ss=$(echo $fname | cut -d_ -f2 | cut -d"-" -f2)
me=$(echo $fname | cut -d_ -f3 | cut -d"-" -f1)
se=$(echo $fname | cut -d_ -f3 | cut -d"-" -f2)

echo "ms=$ms, ss=$ss, me=$me, se=$se"
if [ "$ms" == "00" -a "$ss" == "00" ]; then

[ ${me:0:1} == "0" ] && me=${me:1:1}
[ ${se:0:1} == "0" ] && se=${se:1:1}
let rect=opent+me*60+se
else
[ ${ms:0:1} == "0" ] && ms=${ms:1:1}
[ ${ss:0:1} == "0" ] && ss=${ss:1:1}
let rect=opent+ms*60+ss
fi

echo "Processing $mp3f1"
fname1=$(date -d@$rect "+%Y-%m-%d_%Hh%Mm%Ss_${frq}.mp3")

dir1=$(date -d@$rect "+%Y%m%d")
hh=$(date -d@$rect "+%H")
mp3path=$prefix/$dir1/$frq/$hh
mkdir -p $mp3path

echo "Filename $mp3path/$fname1."

sox $mp3f1 $mp3path/$fname1 silence -l 1 0.3 1% -1 0.5 1%
mp3f1d=$(soxi -D $mp3path/$fname1 | cut -d. -f1)

[ $mp3f1d -lt 2 ] && rm $mp3path/$fname1

rm $mp3f1
done

if [ $i -eq 0 ]; then

echo "No splitpoints found ;-("

fname1=$(date -d@$opent "+%Y-%m-%d_%Hh%Mm%Ss_${frq}.mp3")

dir1=$(date -d@$opent "+%Y%m%d")
hh=$(date -d@$opent "+%H")
mp3path=$prefix/$dir1/$frq/$hh
mkdir -p $prefix/$dir1/$frq/$hh

echo "Filename $mp3path/$fname1. Soxing from $mp3f."

sox $mp3f $mp3path/$fname1 silence -l 1 0.3 1% -1 0.5 1%
mp3f1d=$(soxi -D $mp3path/$fname1 | cut -d. -f1)

[ $mp3f1d -lt 2 ] && rm $mp3path/$fname1

fi

rm $mp3f
rm $wavf
done

Строка для планировщика cron:

*/3 * * * * $HOME/procf.sh /tmp/matrix04 4 >> /tmp/proc04.log

Работа с несколькими свистками.

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

#!/bin/bash

$HOME/matrix_start2.sh 0 matrix4.txt 1024000 439500000 11 600

sleep 30
$HOME/matrix_start2.sh 1 matrix10.txt 1024000 172500000 73 600