The material of the article is taken from my
Tone Detector
In the past
In the old days, when not every family had a TV, and half of them switched channels with the help of pliers, intriguing news appeared in the reviews of the foreign technical press that one and the TV manufacturers equipped their devices with a wireless remote control. From the details, it was known that the remote works without batteries due to the use of an unusual approach - the remote was mechanical and was a hybrid of a musical instrument - a metallophone and a revolver. The revolver drum contained metal cylinders of different lengths, and when the striker hit one of them, the cylinder began to ring at its own frequency. Presumably ultrasound. The electronics in the TV heard this signal and, having determined its frequency, performed the appropriate action - change the channel, change the volume, turn off the TV.
Today we will try to make a reconstruction of this command transmission system, using our knowledge of a media streamer.
To simulate a console, let's use the text of our tone generator example. We will add to it the control of the frequency of the generator from keystrokes and a receiver with a decoder that will output the received commands to the console. After the change, the generator should produce tones of 6 frequencies, with which we will encode commands to increase / decrease the volume, change the channel, turn on / off the TV. The following structure is used to configure the detector:
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 such structures can be passed to the detector, thus one detector can be configured to detect ten two-tone signals. But we will use only six single-tone signals. To transfer settings to the detector, the MS_TONE_DETECTOR_ADD_SCAN method is used.
In order for the detector to notify us that a signal with the desired frequency components has arrived at its input, we must provide it with a callback function that it will launch on such an occasion. This is done using the function ms_filter_set_notify_callback(). As arguments, it receives a pointer to a filter, a pointer to a callback function, a pointer to the data that we would like to pass to the callback function (user data).
When the detector fires, the callback function will receive user data, a pointer to the detector filter, an event identifier, and a structure describing the event:
/** * 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;
The block diagram of signal processing is shown in the title picture.
Well, now the program code itself with comments.
/* Π€Π°ΠΉΠ» 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);
}
}
Compile and run the program. If everything works correctly, then after starting we should get something like this behavior of the program:
$ ./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
We press any keys from "1" to "6", confirming with the "Enter" key, we should get something like this listing:
2
ΠΡΠΏΡΠ°Π²Π»Π΅Π½Π° ΠΊΠΎΠΌΠ°Π½Π΄Π°: 2
ΠΡΠΈΠ½ΡΡΠ° ΠΊΠΎΠΌΠ°Π½Π΄Π°: V-
1
ΠΡΠΏΡΠ°Π²Π»Π΅Π½Π° ΠΊΠΎΠΌΠ°Π½Π΄Π°: 1
ΠΡΠΈΠ½ΡΡΠ° ΠΊΠΎΠΌΠ°Π½Π΄Π°: V+
3
ΠΡΠΏΡΠ°Π²Π»Π΅Π½Π° ΠΊΠΎΠΌΠ°Π½Π΄Π°: 3
ΠΡΠΈΠ½ΡΡΠ° ΠΊΠΎΠΌΠ°Π½Π΄Π°: C+
4
ΠΡΠΏΡΠ°Π²Π»Π΅Π½Π° ΠΊΠΎΠΌΠ°Π½Π΄Π°: 4
ΠΡΠΈΠ½ΡΡΠ° ΠΊΠΎΠΌΠ°Π½Π΄Π°: C-
0
$
We see that command tones are successfully sent and the detector detects them.
In the next article, we will turn to transmitting an audio signal over Ethernet using the RTP protocol and immediately apply it in our remote control.
Source: habr.com