Материал статьи взят с моего дзен-канала.


Обнаружитель тонального сигнала


В прошлой статье мы создали измеритель уровня сигнала. В этой мы научимся обнаруживать тональный сигнал.



В старые времена, когда не в каждой семье был телевизор, и у половины из них каналы переключались с помощью пассатижей, в обзорах иностранной технической прессы появилась интригующая новость, что один и производителей телевизоров снабдил свои аппараты пультом дистанционного беспроводного управления. Из подробностей было известно, что пульт работает без батареек благодаря использованию необычного подхода — пульт был механический и представлял из себя гибрид музыкального инструмента — металлофона и револьвера. В барабане револьвера находились металлические цилиндры, разные по длине, и когда боёк ударял по одному из них, цилиндр начинал звенеть на своей собственной частоте. Предположительно на ультразвуке. Электроника в телевизоре слышала этот сигнал и определив его частоту выполняла соответствующее действие — переключить канал, изменить громкость, выключить телевизор.


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


Для имитации пульта, воспользуемся текстом нашего примера тонального генератора. Мы добавим в него управление частотой генератора от нажатий с клавиатуры и приемник с декодером, который будет выводить в консоль принятые команды. После изменения, генератор должен выдавать тональные сигналы 6 частот, которыми мы будем кодировать команды увеличения/уменьшения громкости, смены канала, включения/выключения телевизора. Для настройки детектора используется структура:


struct _MSToneDetectorDef{  
     char tone_name[8];     
     int frequency; /**<Expected frequency of the tone*/ 
     int min_duration; /**<Min duration of the tone in milliseconds */ 
     float min_amplitude; /**<Minimum amplitude of the tone, 1.0 corresponding to the normalized 0dbm level */
};

typedef struct _MSToneDetectorDef MSToneDetectorDef;

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


Чтобы детектор мог известить нас о том, что на его вход поступил сигнал с искомыми частотными составляющими, мы должны ему предоставить функцию обратного вызова, которую он запустит по такому случаю. Это делается с помощью функции ms_filter_set_notify_callback(). В качестве аргументов она получает указатель на фильтр, указатель на функцию обратного вызова, указатель на данные, которые бы мы хотели передать функции обратного вызова (данные пользователя).


При срабатывании детектора функция обратного вызова получит данные пользователя, указатель на фильтр детектора, идентификатор события, и структуру описывающую событие:



/** * Structure carried as argument of the MS_TONE_DETECTOR_EVENT**/
struct _MSToneDetectorEvent{ 
      char tone_name[8];       /* Имя тона которое мы ему назначили при настройке детектора. */
      uint64_t tone_start_time;   /* Время в миллисекундах, когда тон был обнаружен. */
};

typedef struct _MSToneDetectorEvent MSToneDetectorEvent;

Структурная схема обработки сигнала показана в заглавной картинке.


Ну а теперь сам код программы с комментариями.


/* Файл mstest4.c Имитатор пульта управления и приемника. */
#include <mediastreamer2/msfilter.h>
#include <mediastreamer2/msticker.h>
#include <mediastreamer2/dtmfgen.h>
#include <mediastreamer2/mssndcard.h>
#include <mediastreamer2/msvolume.h>
#include <mediastreamer2/mstonedetector.h>

/* Подключаем заголовочный файл с функциями управления событиями
 * медиастримера. */
#include <mediastreamer2/mseventqueue.h>

/* Функция обратного вызова, она будет вызвана фильтром, как только он
 * обнаружит совпадение характеристик входного сигнала с заданными. */
static void tone_detected_cb(void *data, MSFilter *f, unsigned int event_id,
        MSToneDetectorEvent *ev)
{
    printf("                      Принята команда: %s\n", ev->tone_name);
}

int main()
{
    ms_init();

    /* Создаем экземпляры фильтров. */
    MSFilter  *voidsource = ms_filter_new(MS_VOID_SOURCE_ID);
    MSFilter  *dtmfgen = ms_filter_new(MS_DTMF_GEN_ID);
    MSFilter  *volume = ms_filter_new(MS_VOLUME_ID);
    MSSndCard *card_playback =
        ms_snd_card_manager_get_default_card(ms_snd_card_manager_get());
    MSFilter  *snd_card_write = ms_snd_card_create_writer(card_playback);
    MSFilter  *detector = ms_filter_new(MS_TONE_DETECTOR_ID);

    /* Очищаем массив находящийся внутри детектора тонов, он описывает
     * особые приметы разыскиваемых сигналов.*/
    ms_filter_call_method(detector, MS_TONE_DETECTOR_CLEAR_SCANS, 0);

    /* Создаем источник тактов - тикер. */
    MSTicker *ticker=ms_ticker_new();

    /* Соединяем фильтры в цепочку. */
    ms_filter_link(voidsource, 0, dtmfgen, 0);
    ms_filter_link(dtmfgen, 0, volume, 0);
    ms_filter_link(volume, 0, detector, 0);
    ms_filter_link(detector, 0, snd_card_write, 0);

    /* Подключаем к фильтру функцию обратного вызова. */
    ms_filter_set_notify_callback(detector,
            (MSFilterNotifyFunc)tone_detected_cb, NULL);

    /* Подключаем источник тактов. */
    ms_ticker_attach(ticker,voidsource);

    /* Создаем массив, каждый элемент которого описывает характеристику
     * одного из тонов, который требуется обнаруживать: Текстовое имя
     * данного элемента, частота в герцах, длительность в миллисекундах,
     * минимальный уровень относительно 0,775В. */  
    MSToneDetectorDef  scan[6]=
    {
        {"V+",  440, 100, 0.1}, /* Команда "Увеличить громкость". */
        {"V-",  540, 100, 0.1}, /* Команда "Уменьшить громкость". */
        {"C+",  640, 100, 0.1}, /* Команда "Увеличить номер канала". */
        {"C-",  740, 100, 0.1}, /* Команда "Уменьшить номер канала". */
        {"ON",  840, 100, 0.1}, /* Команда "Включить телевизор". */
        {"OFF", 940, 100, 0.1}  /* Команда "Выключить телевизор". */
    };

    /* Передаем в детектор тонов приметы сигналов. */
    int i;
    for (i = 0; i < 6; i++)
    {
        ms_filter_call_method(detector, MS_TONE_DETECTOR_ADD_SCAN,
                &scan[i]);
    }

    /* Настраиваем структуру, управляющую выходным сигналом генератора.*/
    MSDtmfGenCustomTone dtmf_cfg;
    dtmf_cfg.tone_name[0] = 0;
    dtmf_cfg.duration = 1000;
    dtmf_cfg.frequencies[0] = 440;
    /* Будем генерировать один тон, частоту второго тона установим в 0.*/
    dtmf_cfg.frequencies[1] = 0;
    dtmf_cfg.amplitude = 1.0;
    dtmf_cfg.interval = 0.;
    dtmf_cfg.repeat_count = 0.;

    /* Организуем цикл сканирования нажатых клавиш. Ввод нуля завершает
     * цикл и работу программы. */
    char key='9';
    printf("Нажмите клавишу команды, затем ввод.\n"
        "Для завершения программы введите 0.\n");
    while(key != '0')
    {
        key = getchar();
        if ((key >= 49) && (key <= 54))
        {
                printf("Отправлена команда: %c\n", key);
            /* Устанавливаем частоту генератора в соответствии с
             * кодом нажатой клавиши.*/
            dtmf_cfg.frequencies[0] = 440 + 100*(key-49);

            /* Включаем звуковой генератор c обновленной частотой. */
            ms_filter_call_method(dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM,
                    (void*)&dtmf_cfg);
        }
        ms_usleep(20000);
    }
}

Компилируем и запускаем программу. Если все работает правильно, то после запуска мы должны получить примерно такое поведение программы:


$ ./mstest4
ALSA lib conf.c:4738:(snd_config_expand) Unknown parameters 0
ALSA lib control.c:954:(snd_ctl_open_noupdate) Invalid CTL default:0
ortp-warning-Could not attach mixer to card: Invalid argument
ALSA lib conf.c:4738:(snd_config_expand) Unknown parameters 0
ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Unknown PCM default:0
ALSA lib conf.c:4738:(snd_config_expand) Unknown parameters 0
ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Unknown PCM default:0
ortp-warning-Strange, sound card Intel 82801AA-ICH does not seems to be capable of anything, retrying with plughw...
Нажмите клавишу команды, затем ввод.
Для завершения программы введите 0.
ortp-warning-alsa_set_params: periodsize:256 Using 256
ortp-warning-alsa_set_params: period:8 Using 8

Нажимаем любые клавиши от "1" до "6", подтверждая клавишей "Enter", должен получаться примерно такой листинг:



2
Отправлена команда: 2
                      Принята команда: V-
1
Отправлена команда: 1
                      Принята команда: V+
3
Отправлена команда: 3
                      Принята команда: C+
4
Отправлена команда: 4
                      Принята команда: C-
0
$

Мы видим, что тоны команд успешно отправляются и детектор их обнаруживает.


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