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


Создаем тональный генератор


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


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


Схема звукового генератора


Читаем схему слева направо, именно в этом направлении у нас движется поток данных. Стрелки тоже на это намекают. Прямоугольниками обозначены фильтры, которые обрабатывают блоки данных и выставляют результат на выход. Внутри прямоугольника указана его роль и чуть ниже заглавными буквами тип фильтра. Стрелки, соединяющие прямоугольники, это очереди данных, по которым блоки данных доставляются от фильтра к фильтру. В общем случае, входов и выходов у фильтра может быть немало.


Начинается все с источника тактов, который задает темп, в котором происходит обсчет данных в фильтрах. По его такту, каждый фильтр обрабатывает все блоки данных которые находятся у него на входе. И выставляет блоки с результатом на выход — в очередь. Сначала выполняет обсчет самый ближний к источнику тактов фильтр, затем фильтры подключенные к его выходам (выходов может быть много) и так далее. После того, как последний фильтр в цепочке закончит обсчет, выполнение останавливается до того момента, пока не поступит новый такт. Такты, по умолчанию, следуют с интервалом 10 миллисекунд.


Вернемся к нашей схеме. Такты поступают на вход источника тишины, это фильтр, занят тем, что на каждый такт генерирует на своем выходе блок данных содержащих нули. Если рассматривать этот блок как блок звуковых отсчетов, то это ни что иное как тишина. На первый взгляд кажется странным, генерировать блоки данных с тишиной — ведь её нельзя услышать, но эти блоки необходимы для работы генератора звукового сигнала. Генератор, использует эти блоки как чистый лист бумаги, записывая в них звуковые отсчеты. В обычном своем состоянии, генератор выключен и просто пробрасывает входные блоки на выход. Таким образом, блоки тишины проходят без изменений через всю схему слева направо, попадая в звуковую карту. Которая беззвучно забирает блоки из очереди подключенной к её входу.


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


/* Файл mstest2.c */
#include <mediastreamer2/msfilter.h>
#include <mediastreamer2/msticker.h>
#include <mediastreamer2/dtmfgen.h>
#include <mediastreamer2/mssndcard.h>
int main()
{
    ms_init();

    /* Создаем экземпляры фильтров. */
    MSFilter  *voidsource = ms_filter_new(MS_VOID_SOURCE_ID);
    MSFilter  *dtmfgen = ms_filter_new(MS_DTMF_GEN_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);

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

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

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

   /* Включаем звуковой генератор. */
   char key='1';
   ms_filter_call_method(dtmfgen, MS_DTMF_GEN_PLAY, (void*)&key);

   /* Даем, время, чтобы все блоки данных были получены звуковой картой.*/
   ms_sleep(2);   
}

После инициализации медиастримера, выполняется создание трех фильтров: voidsource, dtmfgen, snd_card_write. Создается источник тактового сигнала.


Затем нужно выполнить соединение фильтров в соответствии с нашей схемой, причем источник тактов нужно подключать последним, так как после этого сразу начнется работа схемы. Если подключить источник тактов к незаконченной схеме, то может случится так, что медиастример аварийно завершится, если обнаружит в цепочке хотя бы один фильтр у которого все входы или все выходы "висят в воздухе" (не подключены).


Соединение фильтров выполняется с помощью функции


ms_filter_link(src, src_out, dst, dst_in)

где первый аргумент это указатель на фильтр-источник, второй аргумент — номер выхода источника (обратите внимание, что входы и выходы нумеруются начиная с нуля). Третий аргумент это указатель на фильтр-приемник, четвертый — номер входа приемника.


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


Мы будем генерировать двутональный (DTMF) сигнал соответствующий нажатию на телефоне кнопки "1". Для этого мы с помощью функции ms_filter_call_method() вызываем метод MS_DTMF_GEN_PLAY, передавая ему в качестве аргумента указатель на код, которому должен соответствовать воспроизводимый сигнал.


Остается программу скомпилировать:


$ gcc mstest2.c -o mstest2 `pkg-config mediastreamer --libs --cflags`

И запустить:


$ ./mstest2

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


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