Khám phá công cụ VoIP Mediastreamer2. Phần 6

Tài liệu của bài viết được lấy từ tài liệu của tôi kênh thiền.

Truyền tín hiệu âm thanh qua luồng RTP

Khám phá công cụ VoIP Mediastreamer2. Phần 6

Cuối cùng Bài viết Chúng tôi đã lắp ráp một mạch điều khiển từ xa từ bộ tạo âm và bộ dò âm hoạt động trong cùng một chương trình. Trong bài viết này chúng ta sẽ tìm hiểu cách sử dụng giao thức RTP (RFC 3550 - RTP: Giao thức truyền tải cho các ứng dụng thời gian thực) để nhận/truyền tín hiệu âm thanh qua mạng Ethernet.

Giao thức RTP (Giao thức thời gian thực) được dịch có nghĩa là giao thức thời gian thực, nó được sử dụng để truyền âm thanh, video, dữ liệu, mọi thứ cần truyền trong thời gian thực. Hãy lấy tín hiệu âm thanh làm ví dụ. Tính linh hoạt của giao thức cho phép bạn truyền tín hiệu âm thanh với chất lượng được xác định trước.

Việc truyền được thực hiện bằng cách sử dụng các gói UDP, điều đó có nghĩa là việc mất gói có thể chấp nhận được trong quá trình truyền. Mỗi gói chứa một tiêu đề RTP đặc biệt và một khối dữ liệu của tín hiệu được truyền. Tiêu đề chứa mã định danh nguồn tín hiệu được chọn ngẫu nhiên, thông tin về loại tín hiệu được truyền và số thứ tự gói duy nhất để các gói có thể được sắp xếp theo đúng thứ tự khi giải mã, bất kể thứ tự chúng được phân phối bởi mạng. Tiêu đề cũng có thể chứa thông tin bổ sung, được gọi là phần mở rộng, cho phép tiêu đề được điều chỉnh để sử dụng trong một tác vụ ứng dụng cụ thể.

Khối dữ liệu chứa tải trọng của gói. Việc tổ chức nội dung bên trong phụ thuộc vào loại tải, nó có thể là mẫu của tín hiệu đơn âm, tín hiệu âm thanh nổi, dòng hình ảnh video, v.v.

Loại tải được biểu thị bằng số bảy bit. Khuyến nghị RFC3551 (Cấu hình RTP cho hội nghị âm thanh và video với khả năng kiểm soát tối thiểu) thiết lập một số loại tải; bảng tương ứng cung cấp mô tả về các loại tải và ý nghĩa của các mã mà chúng được chỉ định. Một số mã không bị ràng buộc chặt chẽ với bất kỳ loại tải nào; chúng có thể được sử dụng để chỉ định một tải tùy ý.

Kích thước của khối dữ liệu bị giới hạn ở trên bởi kích thước gói tối đa có thể được truyền trên một mạng nhất định mà không cần phân đoạn (tham số MTU). Nói chung, đây không quá 1500 byte. Do đó, để tăng lượng dữ liệu được truyền mỗi giây, bạn có thể tăng kích thước gói lên đến một điểm nhất định và sau đó bạn sẽ cần tăng tần suất gửi gói. Trong trình phát đa phương tiện, đây là cài đặt có thể định cấu hình. Theo mặc định là 50 Hz, tức là 50 gói mỗi giây. Chúng ta sẽ gọi chuỗi các gói RTP được truyền là luồng RTP.

Để bắt đầu truyền dữ liệu giữa nguồn và máy thu, việc máy phát biết địa chỉ IP của máy thu và số cổng mà nó sử dụng để nhận là đủ. Những thứ kia. mà không cần bất kỳ thủ tục sơ bộ nào, nguồn bắt đầu truyền dữ liệu và đến lượt người nhận, sẵn sàng nhận và xử lý dữ liệu đó ngay lập tức. Theo tiêu chuẩn, số cổng được sử dụng để truyền hoặc nhận luồng RTP phải là số chẵn.

Trong trường hợp không thể biết trước địa chỉ của người nhận, máy chủ sẽ được sử dụng khi người nhận để lại địa chỉ của họ và người phát có thể yêu cầu địa chỉ đó bằng cách tham khảo một số tên duy nhất của người nhận.

Trong trường hợp không xác định được chất lượng của kênh liên lạc hoặc khả năng của bộ thu, một kênh phản hồi sẽ được tổ chức qua đó bộ thu có thể thông báo cho bộ phát về khả năng của nó, số lượng gói bị bỏ lỡ, v.v. Kênh này sử dụng giao thức RTCP. Định dạng của các gói được truyền trong kênh này được xác định trong RFC 3605. Tương đối ít dữ liệu được truyền qua kênh này, 200..300 byte mỗi giây, vì vậy nhìn chung, sự hiện diện của nó không gây gánh nặng. Số cổng mà gói RTCP được gửi tới phải là số lẻ và lớn hơn số cổng mà luồng RTP đến. Trong ví dụ của chúng tôi, chúng tôi sẽ không sử dụng kênh này, vì khả năng của bộ thu và kênh rõ ràng vượt quá nhu cầu khiêm tốn cho đến nay của chúng tôi.

Trong chương trình của chúng tôi, mạch truyền dữ liệu, không giống như ví dụ trước, sẽ được chia thành hai phần: đường truyền và đường nhận. Đối với mỗi phần, chúng tôi sẽ tạo nguồn đồng hồ của riêng mình, như trong hình tiêu đề.

Giao tiếp một chiều giữa chúng sẽ được thực hiện bằng giao thức RTP. Trong ví dụ này, chúng ta không cần mạng bên ngoài vì cả máy phát và máy thu sẽ được đặt trên cùng một máy tính - các gói sẽ di chuyển bên trong nó.

Để thiết lập luồng RTP, bộ truyền phát phương tiện sử dụng hai bộ lọc: MS_RTP_SEND và MS_RTP_RECV. Cái đầu tiên truyền cái thứ hai và nhận luồng RTP. Để các bộ lọc này hoạt động, chúng cần chuyển một con trỏ tới đối tượng phiên RTP, đối tượng này có thể chuyển đổi luồng khối dữ liệu thành luồng gói RTP hoặc làm ngược lại. Do định dạng dữ liệu bên trong của bộ truyền phát đa phương tiện không khớp với định dạng dữ liệu của gói RTP nên trước khi truyền dữ liệu sang MS_RTP_SEND, bạn cần sử dụng bộ lọc mã hóa để chuyển đổi các mẫu tín hiệu âm thanh 16 bit thành mã hóa XNUMX bit theo quy định. u-law (mu-luật). Ở phía nhận, bộ lọc giải mã thực hiện chức năng ngược lại.

Dưới đây là văn bản của chương trình thực hiện lược đồ được hiển thị trong hình (ký hiệu # trước lệnh include đã bị xóa, đừng quên đưa chúng vào):

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

Chúng tôi biên dịch và chạy. Chương trình sẽ hoạt động như trong ví dụ trước, nhưng dữ liệu sẽ được truyền qua luồng RTP.

Trong bài viết tiếp theo, chúng tôi sẽ chia chương trình này thành hai ứng dụng độc lập - một máy thu và một máy phát và khởi chạy chúng trên các thiết bị đầu cuối khác nhau. Đồng thời, chúng ta sẽ tìm hiểu cách phân tích các gói RTP bằng chương trình TShark.

Nguồn: www.habr.com

Thêm một lời nhận xét