Mediastreamer2 VoIP 엔진 탐색. 6 부

글의 소재는 제 글에서 가져왔습니다. 젠 채널.

RTP 스트림을 통해 오디오 신호 전송

Mediastreamer2 VoIP 엔진 탐색. 6 부

과거에 기사 우리는 동일한 프로그램 내에서 작동하는 톤 발생기와 톤 감지기로 원격 제어 회로를 조립했습니다. 이 기사에서는 RTP 프로토콜(RFC 3550 - RTP: 실시간 애플리케이션을 위한 전송 프로토콜) 이더넷 네트워크를 통해 오디오 신호를 수신/전송합니다.

RTP 프로토콜(실시간 프로토콜) 번역은 실시간 프로토콜을 의미하며 오디오, 비디오, 데이터 등 실시간 전송이 필요한 모든 것을 전송하는 데 사용됩니다. 오디오 신호를 예로 들어보겠습니다. 프로토콜의 유연성 덕분에 미리 결정된 품질로 오디오 신호를 전송할 수 있습니다.

전송은 UDP 패킷을 사용하여 수행됩니다. 이는 전송 중에 패킷 손실이 허용된다는 것을 의미합니다. 각 패킷에는 특수 RTP 헤더와 전송된 신호의 데이터 블록이 포함되어 있습니다. 헤더에는 무작위로 선택된 신호 소스 식별자, 전송되는 신호 유형에 대한 정보 및 고유한 패킷 시퀀스 번호가 포함되어 있어 패킷이 전달된 순서에 관계없이 디코딩 시 올바른 순서로 배열될 수 있습니다. 회로망. 헤더에는 추가 정보(확장)가 포함될 수도 있습니다. 이를 통해 헤더를 특정 애플리케이션 작업에 사용하도록 조정할 수 있습니다.

데이터 블록에는 패킷의 페이로드가 포함됩니다. 콘텐츠의 내부 구성은 로드 유형에 따라 다르며 모노 신호, 스테레오 신호, 비디오 이미지 라인 등의 샘플이 될 수 있습니다.

로드 유형은 3551비트 숫자로 표시됩니다. 권장사항 RFCXNUMX(최소한의 제어로 오디오 및 비디오 회의를 위한 RTP 프로필)은 여러 유형의 하중을 설정하며 해당 표에는 하중 유형에 대한 설명과 해당 하중이 지정된 코드의 의미가 나와 있습니다. 일부 코드는 모든 유형의 하중에 엄격하게 연결되지 않으며 임의의 하중을 지정하는 데 사용할 수 있습니다.

데이터 블록의 크기는 분할(MTU 매개변수) 없이 특정 네트워크에서 전송할 수 있는 최대 패킷 크기에 의해 위에서 제한됩니다. 일반적으로 이는 1500바이트를 넘지 않습니다. 따라서 초당 전송되는 데이터의 양을 늘리려면 패킷 크기를 일정 수준까지 늘릴 수 있으며, 그 이후에는 패킷 전송 빈도를 늘려야 합니다. 미디어 스트리머에서 이는 구성 가능한 설정입니다. 기본적으로 50Hz입니다. 초당 50패킷. 전송된 RTP 패킷의 시퀀스를 RTP 스트림이라고 부르겠습니다.

소스와 수신기 간에 데이터 전송을 시작하려면 송신기가 수신기의 IP 주소와 수신에 사용하는 포트 번호를 아는 것으로 충분합니다. 저것들. 사전 절차 없이 소스는 데이터 전송을 시작하고, 수신자는 이를 즉시 수신하고 처리할 준비가 됩니다. 표준에 따르면 RTP 스트림을 전송하거나 수신하는 데 사용되는 포트 번호는 짝수여야 합니다.

수신자의 주소를 미리 알 수 없는 상황에서는 수신자가 자신의 주소를 남겨두는 서버를 이용하며, 송신자는 수신자의 고유한 이름을 참조하여 이를 요청할 수 있다.

통신 채널의 품질이나 수신기의 성능을 알 수 없는 경우 수신기가 송신기에 자신의 성능, 놓친 패킷 수 등을 알릴 수 있는 피드백 채널이 구성됩니다. 이 채널은 RTCP 프로토콜을 사용합니다. 이 채널에서 전송되는 패킷 형식은 RFC 3605에 정의되어 있습니다. 이 채널을 통해 초당 200~300바이트의 비교적 적은 데이터가 전송되므로 일반적으로 그 존재는 부담스럽지 않습니다. RTCP 패킷이 전송되는 포트 번호는 홀수여야 하며 RTP 스트림이 들어오는 포트 번호보다 XNUMX 커야 합니다. 이 예에서는 수신기와 채널의 기능이 지금까지의 적당한 요구 사항을 분명히 초과하므로 이 채널을 사용하지 않습니다.

우리 프로그램에서 데이터 전송 회로는 이전 예와 달리 전송 경로와 수신 경로의 두 부분으로 나뉩니다. 각 부분에 대해 제목 그림에 표시된 대로 자체 클럭 소스를 만듭니다.

이들 간의 단방향 통신은 RTP 프로토콜을 사용하여 수행됩니다. 이 예에서는 송신기와 수신기가 모두 동일한 컴퓨터에 위치하므로 외부 네트워크가 필요하지 않습니다. 즉, 패킷이 내부로 이동합니다.

RTP 스트림을 설정하기 위해 미디어 스트리머는 MS_RTP_SEND 및 MS_RTP_RECV라는 두 가지 필터를 사용합니다. 첫 번째는 두 번째를 전송하고 RTP 스트림을 수신합니다. 이러한 필터가 작동하려면 데이터 블록 스트림을 RTP 패킷 스트림으로 변환하거나 그 반대로 변환할 수 있는 RTP 세션 객체에 대한 포인터를 전달해야 합니다. 미디어 스트리머의 내부 데이터 형식이 RTP 패킷의 데이터 형식과 일치하지 않으므로 데이터를 MS_RTP_SEND로 전송하기 전에 16비트 오디오 신호 샘플을 RTP 패킷의 데이터 형식에 따라 인코딩된 XNUMX비트로 변환하는 인코더 필터를 사용해야 합니다. 유법(mu법). 수신측에서는 디코더 필터가 반대 기능을 수행합니다.

다음은 그림에 표시된 구성표를 구현하는 프로그램의 텍스트입니다(include 지시문이 제거되기 전의 # 기호를 포함하는 것을 잊지 마세요).

/* Файл 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);
}
}

우리는 컴파일하고 실행합니다. 프로그램은 이전 예와 같이 작동하지만 데이터는 RTP 스트림을 통해 전송됩니다.

다음 기사에서는 이 프로그램을 수신기와 송신기라는 두 개의 독립적인 응용 프로그램으로 나누어 서로 다른 터미널에서 실행할 것입니다. 동시에 TShark 프로그램을 사용하여 RTP 패킷을 분석하는 방법을 알아봅니다.

출처 : habr.com

코멘트를 추가