Utforska Mediastreamer2 VoIP-motorn. Del 6

Materialet i artikeln är hämtat från min zen kanal.

Sänder en ljudsignal via RTP-ström

Utforska Mediastreamer2 VoIP-motorn. Del 6

Förr artikeln Vi har satt ihop en fjärrkontrollkrets från en tongenerator och en tondetektor som arbetar inom samma program. I den här artikeln kommer vi att lära oss hur man använder RTP-protokollet (RFC 3550 - RTP: Ett transportprotokoll för realtidsapplikationer) för att ta emot/sända en ljudsignal över ett Ethernet-nätverk.

RTP-protokoll (Realtidsprotokoll) översatt betyder realtidsprotokoll, det används för att överföra ljud, video, data, allt som kräver överföring i realtid. Låt oss ta en ljudsignal som ett exempel. Protokollets flexibilitet är sådan att det låter dig sända en ljudsignal med en förutbestämd kvalitet.

Överföringen utförs med UDP-paket, vilket innebär att paketförlust är helt acceptabelt under överföring. Varje paket innehåller ett speciellt RTP-huvud och ett datablock för den överförda signalen. Rubriken innehåller en slumpmässigt vald signalkällas identifierare, information om vilken typ av signal som sänds och ett unikt paketsekvensnummer så att paketen kan ordnas i rätt ordning vid avkodning, oavsett i vilken ordning de levererades av nätverk. Rubriken kan även innehålla ytterligare information, den så kallade förlängningen, som gör att huvudet kan anpassas för användning i en specifik applikationsuppgift.

Datablocket innehåller nyttolasten för paketet. Den interna organisationen av innehållet beror på typen av belastning, det kan vara prover av en monosignal, en stereosignal, en videobildlinje, etc.

Lasttypen indikeras med ett sju-bitars nummer. Rekommendation RFC3551 (RTP-profil för ljud- och videokonferenser med minimal kontroll) fastställer flera typer av last; motsvarande tabell ger en beskrivning av lasttyperna och innebörden av de koder som de är betecknade med. Vissa koder är inte strikt bundna till någon typ av belastning, de kan användas för att beteckna en godtycklig belastning.

Storleken på ett datablock begränsas ovan av den maximala paketstorlek som kan överföras på ett givet nätverk utan segmentering (MTU-parameter). I allmänhet är detta inte mer än 1500 byte. För att öka mängden data som överförs per sekund kan du alltså öka paketstorleken upp till en viss punkt, och då måste du öka frekvensen för att skicka paket. I en mediastreamer är detta en konfigurerbar inställning. Som standard är det 50 Hz, d.v.s. 50 paket per sekund. Vi kommer att kalla sekvensen av överförda RTP-paket för en RTP-ström.

För att börja sända data mellan källan och mottagaren räcker det att sändaren känner till mottagarens IP-adress och portnumret som den använder för att ta emot. De där. utan några preliminära procedurer börjar källan att överföra data, och mottagaren är i sin tur redo att omedelbart ta emot och bearbeta den. Enligt standarden måste portnumret som används för att sända eller ta emot en RTP-ström vara jämnt.

I situationer där det är omöjligt att veta mottagarens adress i förväg används servrar där mottagare lämnar sin adress, och sändaren kan begära det genom att hänvisa till något unikt namn på mottagaren.

I de fall där kvaliteten på kommunikationskanalen eller mottagarens kapacitet är okänd, organiseras en återkopplingskanal genom vilken mottagaren kan informera sändaren om dess kapacitet, antalet paket den missat, etc. Denna kanal använder RTCP-protokollet. Formatet för paket som sänds i denna kanal definieras i RFC 3605. Relativt lite data sänds över denna kanal, 200...300 byte per sekund, så i allmänhet är dess närvaro inte betungande. Portnumret till vilket RTCP-paket skickas måste vara udda och ett större än portnumret som RTP-strömmen kommer från. I vårt exempel kommer vi inte att använda denna kanal, eftersom mottagarens och kanalens kapacitet uppenbarligen överstiger våra, hittills blygsamma, behov.

I vårt program kommer dataöverföringskretsen, till skillnad från föregående exempel, att delas upp i två delar: en sändningsväg och en mottagningsväg. För varje del kommer vi att göra vår egen klockkälla, som visas i titelbilden.

Envägskommunikation mellan dem kommer att utföras med RTP-protokollet. I det här exemplet behöver vi inte ett externt nätverk, eftersom både sändaren och mottagaren kommer att finnas på samma dator - paketen kommer att resa inuti den.

För att upprätta en RTP-ström använder mediastreamern två filter: MS_RTP_SEND och MS_RTP_RECV. Den första sänder den andra och tar emot RTP-strömmen. För att dessa filter ska fungera måste de skicka en pekare till ett RTP-sessionsobjekt, som antingen kan konvertera en ström av datablock till en ström av RTP-paket eller göra tvärtom. Eftersom det interna dataformatet för mediastreamern inte matchar dataformatet för RTP-paketet, innan du överför data till MS_RTP_SEND, måste du använda ett kodningsfilter som omvandlar 16-bitars ljudsignalsampler till åttabitars kodade enligt u-lag (mu-lag). På mottagningssidan utför avkodarfiltret motsatt funktion.

Nedan är texten till programmet som implementerar schemat som visas i figuren (symbolerna # innan inkluderingsdirektiven har tagits bort, glöm inte att inkludera dem):

/* Файл mstest6.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/msrtp.h>
#include <ortp/rtpsession.h>
#include <ortp/payloadtype.h>
/* Подключаем заголовочный файл с функциями управления событиями
* медиастримера.*/
include <mediastreamer2/mseventqueue.h>
#define PCMU 0
/* Функция обратного вызова, она будет вызвана фильтром, как только он
обнаружит совпадение характеристик входного сигнала с заданными. */
static void tone_detected_cb(void *data, MSFilter *f, unsigned int event_id,
MSToneDetectorEvent *ev)
{
printf("Принята команда: %sn", ev->tone_name);
}
/*----------------------------------------------------------------------------*/
/* Функция регистрации типов полезных нагрузок. */
void register_payloads(void)
{
/*Регистрируем типы нагрузок в таблице профилей. Позднее, по индексу
взятому из заголовка RTP-пакета из этой таблицы будут извлекаться
параметры нагрузки, необходимые для декодирования данных пакета. */
rtp_profile_set_payload (&av_profile, PCMU, &payload_type_pcm8000);
}
/*----------------------------------------------------------------------------*/
/* Эта функция создана из функции create_duplex_rtpsession() в audiostream.c
медиастримера2. */
static RtpSession *
create_rtpsession (int loc_rtp_port, int loc_rtcp_port,
bool_t ipv6, RtpSessionMode mode)
{
RtpSession *rtpr;
rtpr = rtp_session_new ((int) mode);
rtp_session_set_scheduling_mode (rtpr, 0);
rtp_session_set_blocking_mode (rtpr, 0);
rtp_session_enable_adaptive_jitter_compensation (rtpr, TRUE);
rtp_session_set_symmetric_rtp (rtpr, TRUE);
rtp_session_set_local_addr (rtpr, ipv6 ? "::" : "0.0.0.0", loc_rtp_port,
loc_rtcp_port);
rtp_session_signal_connect (rtpr, "timestamp_jump",
(RtpCallback) rtp_session_resync, 0);
rtp_session_signal_connect (rtpr, "ssrc_changed",
(RtpCallback) rtp_session_resync, 0);
rtp_session_set_ssrc_changed_threshold (rtpr, 0);
rtp_session_set_send_payload_type(rtpr, PCMU);
/* По умолчанию выключаем RTCP-сессию, так как наш пульт не будет использовать её. */
rtp_session_enable_rtcp (rtpr, FALSE);
return rtpr;
}
/*----------------------------------------------------------------------------*/
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);
/* Подключаем к фильтру функцию обратного вызова. */
ms_filter_set_notify_callback(detector,
(MSFilterNotifyFunc)tone_detected_cb, NULL);
/* Создаем массив, каждый элемент которого описывает характеристику
* одного из тонов, который требуется обнаруживать: Текстовое имя
* данного элемента, частота в герцах, длительность в миллисекундах,
* минимальный уровень относительно 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]);
}
/* Создаем фильтры кодера и декодера */
MSFilter *encoder = ms_filter_create_encoder("PCMU");
MSFilter *decoder=ms_filter_create_decoder("PCMU");
/* Регистрируем типы нагрузки. */
register_payloads();
/* Создаем RTP-сессию передатчика. */
RtpSession *tx_rtp_session = create_rtpsession (8010, 8011, FALSE, RTP_SESSION_SENDONLY);
rtp_session_set_remote_addr_and_port(tx_rtp_session,"127.0.0.1", 7010, 7011);
rtp_session_set_send_payload_type(tx_rtp_session, PCMU);
MSFilter *rtpsend = ms_filter_new(MS_RTP_SEND_ID);
ms_filter_call_method(rtpsend, MS_RTP_SEND_SET_SESSION, tx_rtp_session);
/* Создаем RTP-сессию приемника. */
MSFilter *rtprecv = ms_filter_new(MS_RTP_RECV_ID);
RtpSession *rx_rtp_session = create_rtpsession (7010, 7011, FALSE, RTP_SESSION_RECVONLY);
ms_filter_call_method(rtprecv, MS_RTP_RECV_SET_SESSION, rx_rtp_session);
/* Создаем источники тактов - тикеры. */
MSTicker *ticker_tx = ms_ticker_new();
MSTicker *ticker_rx = ms_ticker_new();
/* Соединяем фильтры передатчика. */
ms_filter_link(voidsource, 0, dtmfgen, 0);
ms_filter_link(dtmfgen, 0, volume, 0);
ms_filter_link(volume, 0, encoder, 0);
ms_filter_link(encoder, 0, rtpsend, 0);
/* Соединяем фильтры приёмника. */
ms_filter_link(rtprecv, 0, decoder, 0);
ms_filter_link(decoder, 0, detector, 0);
ms_filter_link(detector, 0, snd_card_write, 0);
/* Подключаем источник тактов. */
ms_ticker_attach(ticker_tx, voidsource);
ms_ticker_attach(ticker_rx, rtprecv);
/* Настраиваем структуру, управляющую выходным сигналом генератора. */
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);
}
/* Укладываем тред в спячку на 20мс, чтобы другие треды
* приложения получили время на работу. */
ms_usleep(20000);
}
}

Vi kompilerar och kör. Programmet kommer att fungera som i föregående exempel, men data kommer att överföras via en RTP-ström.

I nästa artikel kommer vi att dela upp det här programmet i två oberoende applikationer - en mottagare och en sändare och starta dem i olika terminaler. Samtidigt kommer vi att lära oss hur man analyserar RTP-paket med hjälp av programmet TShark.

Källa: will.com

Lägg en kommentar