Materiał artykułu pochodzi z mojego .
Detektor sygnału tonowego
W poprzedniej Stworzyliśmy miernik poziomu sygnału. W tym artykule nauczymy się wykrywać sygnał tonowy.
![]()
W dawnych czasach, gdy nie w każdej rodzinie był telewizor, a połowa z nich zmieniała kanały za pomocą szczypiec, w recenzjach zagranicznej prasy technicznej pojawiła się intrygująca wiadomość, że jeden z producentów telewizorów wyposażył swoje urządzenia w zdalnego pilota bezprzewodowego. Z szczegółów wiadomo było, że pilot działa bez baterii dzięki zastosowaniu niezwykłego podejścia — pilot był mechaniczny i stanowił hybrydę instrumentu muzycznego — metalofonu i rewolweru. W bębnie rewolwera znajdowały się cylindry metalowe, różnej długości, a kiedy iglica uderzała w jeden z nich, cylindry zaczynały dzwonić na swojej własnej częstotliwości. Przypuszczalnie na ultradźwiękach. Elektronika w telewizorze słyszała ten sygnał i po określeniu jego częstotliwości podejmowała odpowiednie działanie — zmieniała kanał, regulowała głośność, wyłączała telewizor.
Dziś spróbujemy zrekonstruować ten system przesyłania komend, wykorzystując naszą wiedzę o mediastreamerze.
Aby zasymulować pilota, posłużymy się tekstem naszego przykładu tonowego generatora. Dodamy do niego sterowanie częstotliwością generatora z naciśnięć klawiszy oraz odbiornik z dekoderem, który będzie wyświetlał w konsoli odbierane komendy. Po zmianach, generator powinien wydawać tonowe sygnały 6 częstotliwości, którymi będziemy kodować komendy zwiększenia/zmniejszenia głośności, zmiany kanału, włączania/wyłączania telewizora. Do ustawienia detektora używana jest struktura:
struct _MSToneDetectorDef{
char tone_name[8];
int frequency;
/**<Oczekiwana częstotliwość tonu*/
int min_duration;
/**<Minimalny czas trwania tonu w milisekundach*/
float min_amplitude;
/**<Minimalna amplituda tonu, 1.0 odpowiada poziomowi 0dbm*/
};
typedef struct _MSToneDetectorDef MSToneDetectorDef;Do detektora można przesłać 10 takich struktur, dzięki czemu jeden detektor można ustawić na wykrywanie dziesięciu sygnałów dwutonowych. Jednak my będziemy używać tylko sześciu sygnałów tonowych. Do przesyłania ustawień do detektora używa się metody MS_TONE_DETECTOR_ADD_SCAN.
Aby detektor mógł nas powiadomić, że na jego wejście dotarł sygnał z poszukiwanymi częstotliwościami, musimy mu dostarczyć funkcję zwrotną, którą uruchomi w tym przypadku. Robi się to za pomocą funkcji ms_filter_set_notify_callback(). Jako argumenty przyjmuje wskaźnik na filtr, wskaźnik na funkcję zwrotną oraz wskaźnik na dane, które chcielibyśmy przekazać funkcji zwrotnej (dane użytkownika).
Kiedy detektor zadziała, funkcja zwrotna otrzyma dane użytkownika, wskaźnik na filtr detektora, identyfikator zdarzenia oraz strukturę opisującą zdarzenie:
/** * 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;
Schemat strukturalny przetwarzania sygnału został pokazany na obrazku tytułowym.
A teraz sam kod programu z komentarzami.
/* Файл 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(" Принята команда: %sn", 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("Отправлена команда: %cn", 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);
}
}Komplilujemy i uruchamiamy program. Jeśli wszystko działa poprawnie, to po uruchomieniu powinniśmy otrzymać mniej więcej takie zachowanie programu:
$ ./mstest4
ALSA lib conf.c:4738:(snd_config_expand) Nieznane parametry 0
ALSA lib control.c:954:(snd_ctl_open_noupdate) Nieprawidłowy CTL default:0
ortp-warning-Nie można podłączyć miksera do karty: Nieprawidłowy argument
ALSA lib conf.c:4738:(snd_config_expand) Nieznane parametry 0
ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Nieznane PCM default:0
ALSA lib conf.c:4738:(snd_config_expand) Nieznane parametry 0
ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Nieznane PCM default:0
ortp-warning-Dziwne, karta dźwiękowa Intel 82801AA-ICH nie wydaje się być zdolna do czegokolwiek, próba ponowna z plughw...
Naciśnij klawisz polecenia, a następnie Enter.
Aby zakończyć program, wpisz 0.
ortp-warning-alsa_set_params: periodsize:256 Używając 256
ortp-warning-alsa_set_params: period:8 Używając 8
Naciskamy dowolne klawisze od „1” do „6”, potwierdzając klawiszem „Enter”, powinien wynikać mniej więcej taki listing:
2
Wysłano polecenie: 2
Odebrano polecenie: V-
1
Wysłano polecenie: 1
Odebrano polecenie: V+
3
Wysłano polecenie: 3
Odebrano polecenie: C+
4
Wysłano polecenie: 4
Odebrano polecenie: C-
0
$Widok, że tony poleceń są pomyślnie wysyłane i detektor je rozpoznaje.
W następnym artykule zajmiemy się przesyłaniem sygnału dźwiękowego przez sieć Ethernet za pomocą protokołu RTP i zastosujemy go w naszym zdalnym pilocie.
Źródło: habr.com
