Mediastreamer2 VoIP 엔진 탐색. 9 부

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

이중 인터콤

Mediastreamer2 VoIP 엔진 탐색. 9 부

과거에 기사 이중 인터콤이 발표되었으며 이것으로 만들겠습니다.

다이어그램은 제목 그림에 표시됩니다. 필터의 하위 체인은 사운드 카드에서 시작되는 전송 경로를 형성합니다. 마이크의 신호 샘플을 제공합니다. 기본적으로 이는 초당 8000개 샘플의 속도로 발생합니다. 미디어 스트리머 오디오 필터가 사용하는 데이터 비트 심도는 16비트입니다(이는 중요하지 않습니다. 원하는 경우 더 높은 비트 심도에서 작동하는 필터를 작성할 수 있습니다). 데이터는 160개의 샘플 블록으로 그룹화됩니다. 따라서 각 블록의 크기는 320바이트입니다. 다음으로 데이터를 생성기의 입력에 공급합니다. 이 생성기는 꺼지면 데이터에 "투명"합니다. 디버깅하는 동안 마이크에 대고 말하는 것이 지겨워질 경우를 대비해 추가했습니다. 생성기를 사용하여 톤 신호로 경로를 "촬영"할 수 있습니다.

생성기 이후 신호는 인코더로 이동하여 µ-law(G.16 표준)에 따라 711비트 샘플을 XNUMX비트 샘플로 변환합니다. 인코더의 출력에는 이미 절반 크기의 데이터 블록이 있습니다. 일반적으로 트래픽을 절약할 필요가 없으면 압축하지 않고 데이터를 전송할 수 있습니다. 그러나 여기서는 인코더를 사용하는 것이 유용합니다. 왜냐하면 Wireshark는 µ-law 또는 a-law에 따라 압축된 경우에만 RTP 스트림에서 오디오를 재생할 수 있기 때문입니다.

인코더 이후에 더 가벼운 데이터 블록은 rtpsend 필터로 전송되어 이를 RTP 패킷에 넣고 필요한 플래그를 설정한 다음 UDP 패킷 형태로 네트워크를 통해 전송할 수 있도록 미디어 스트리머에 제공합니다.

필터의 상위 체인은 수신 경로를 형성하고, 미디어 스트리머가 네트워크에서 수신한 RTP 패킷은 rtprecv 필터에 들어가며, 그 출력에서 ​​데이터 블록 형태로 나타나며, 각 패킷은 수신된 패킷 하나에 해당합니다. 블록에는 페이로드 데이터만 포함되어 있으며 이전 기사에서는 그림에서 녹색으로 표시되었습니다.

다음으로 블록은 디코더 필터로 전송되어 블록에 포함된 단일 바이트 샘플을 선형 16비트 샘플로 변환합니다. 이미 미디어 스트리머 필터로 처리할 수 있습니다. 우리의 경우 헤드셋 스피커에서 재생할 수 있도록 사운드 카드로 보내기만 하면 됩니다.

이제 소프트웨어 구현으로 넘어가겠습니다. 이를 위해 이전에 분리했던 수신기 및 송신기 파일을 결합합니다. 이전에는 포트와 주소에 대해 고정된 설정을 사용했지만 이제는 시작할 때 지정한 설정을 사용할 수 있는 프로그램이 필요합니다. 이를 위해 명령줄 인수를 처리하는 기능을 추가합니다. 그런 다음 연결하려는 인터콤의 IP 주소와 포트를 설정할 수 있습니다.

먼저, 설정을 저장할 프로그램에 구조를 추가해 보겠습니다.

struct _app_vars
{
  int  local_port;              /* Локальный порт. */
  int  remote_port;             /* Порт переговорного устройства на удаленном компьютере. */
  char remote_addr[128];        /* IP-адрес удаленного компьютера. */
  MSDtmfGenCustomTone dtmf_cfg; /* Настройки тестового сигнала генератора. */
};

typedef struct _app_vars app_vars;

프로그램은 vars라는 이러한 유형의 구조를 선언합니다.
다음으로 명령줄 인수를 구문 분석하는 함수를 추가해 보겠습니다.

/* Функция преобразования аргументов командной строки в
* настройки программы. */
void  scan_args(int argc, char *argv[], app_vars *v)
{
    char i;
    for (i=0; i<argc; i++)
    {
        if (!strcmp(argv[i], "--help"))
        {
            char *p=argv[0]; p=p + 2;
            printf("  %s walkie talkienn", p);
            printf("--help      List of options.n");
            printf("--version   Version of application.n");
            printf("--addr      Remote abonent IP address string.n");
            printf("--port      Remote abonent port number.n");
            printf("--lport     Local port number.n");
            printf("--gen       Generator frequency.n");
            exit(0);
        }

        if (!strcmp(argv[i], "--version"))
        {
            printf("0.1n");
            exit(0);
        }

        if (!strcmp(argv[i], "--addr"))
        {
            strncpy(v->remote_addr, argv[i+1], 16);
            v->remote_addr[16]=0;
            printf("remote addr: %sn", v->remote_addr);
        }

        if (!strcmp(argv[i], "--port"))
        {
            v->remote_port=atoi(argv[i+1]);
            printf("remote port: %in", v->remote_port);
        }

        if (!strcmp(argv[i], "--lport"))
        {
            v->local_port=atoi(argv[i+1]);
            printf("local port : %in", v->local_port);
        }

        if (!strcmp(argv[i], "--gen"))
        {
            v -> dtmf_cfg.frequencies[0] = atoi(argv[i+1]);
                printf("gen freq : %in", v -> dtmf_cfg.frequencies[0]);
        }
    }
}

구문 분석 결과, 명령줄 인수는 vars 구조의 필드에 배치됩니다. 응용 프로그램의 주요 기능은 필터에서 전송 및 수신 경로를 수집하는 것입니다. 티커를 연결한 후 제어는 무한 루프로 전환되며, 생성기 주파수가 XNUMX이 아닌 것으로 설정된 경우 테스트 생성기를 다시 시작합니다. 멈추지 않고 작동합니다.

발전기는 설계상 이러한 재시작이 필요하며 어떤 이유로 16초 이상 지속되는 신호를 생성할 수 없습니다. 지속 시간은 32비트 숫자로 지정된다는 점에 유의해야 합니다.

전체 프로그램은 다음과 같습니다:

/* Файл mstest8.c Имитатор переговорного устройства. */

#include <mediastreamer2/mssndcard.h>
#include <mediastreamer2/dtmfgen.h>
#include <mediastreamer2/msrtp.h>

/* Подключаем файл общих функций. */
#include "mstest_common.c"

/*----------------------------------------------------------*/
struct _app_vars
{
    int  local_port;              /* Локальный порт. */
    int  remote_port;             /* Порт переговорного устройства на удаленном компьютере. */
    char remote_addr[128];        /* IP-адрес удаленного компьютера. */
    MSDtmfGenCustomTone dtmf_cfg; /* Настройки тестового сигнала генератора. */
};

typedef struct _app_vars app_vars;

/*----------------------------------------------------------*/
/* Создаем дуплексную RTP-сессию. */
RtpSession* create_duplex_rtp_session(app_vars v)
{
    RtpSession *session = create_rtpsession (v.local_port, v.local_port + 1, FALSE, RTP_SESSION_SENDRECV);
    rtp_session_set_remote_addr_and_port(session, v.remote_addr, v.remote_port, v.remote_port + 1);
    rtp_session_set_send_payload_type(session, PCMU);
    return session;
}

/*----------------------------------------------------------*/
/* Функция преобразования аргументов командной строки в
* настройки программы. */
void  scan_args(int argc, char *argv[], app_vars *v)
{
    char i;
    for (i=0; i<argc; i++)
    {
        if (!strcmp(argv[i], "--help"))
        {
            char *p=argv[0]; p=p + 2;
            printf("  %s walkie talkienn", p);
            printf("--help      List of options.n");
            printf("--version   Version of application.n");
            printf("--addr      Remote abonent IP address string.n");
            printf("--port      Remote abonent port number.n");
            printf("--lport     Local port number.n");
            printf("--gen       Generator frequency.n");
            exit(0);
        }

        if (!strcmp(argv[i], "--version"))
        {
            printf("0.1n");
            exit(0);
        }

        if (!strcmp(argv[i], "--addr"))
        {
            strncpy(v->remote_addr, argv[i+1], 16);
            v->remote_addr[16]=0;
            printf("remote addr: %sn", v->remote_addr);
        }

        if (!strcmp(argv[i], "--port"))
        {
            v->remote_port=atoi(argv[i+1]);
            printf("remote port: %in", v->remote_port);
        }

        if (!strcmp(argv[i], "--lport"))
        {
            v->local_port=atoi(argv[i+1]);
            printf("local port : %in", v->local_port);
        }

        if (!strcmp(argv[i], "--gen"))
        {
            v -> dtmf_cfg.frequencies[0] = atoi(argv[i+1]);
                printf("gen freq : %in", v -> dtmf_cfg.frequencies[0]);
        }
    }
}

/*----------------------------------------------------------*/
int main(int argc, char *argv[])
{
    /* Устанавливаем настройки по умолчанию. */
    app_vars vars={5004, 7010, "127.0.0.1", {0}};

    /* Устанавливаем настройки настройки программы в
     * соответствии с аргументами командной строки. */
    scan_args(argc, argv, &vars);

    ms_init();

    /* Создаем экземпляры фильтров передающего тракта. */
    MSSndCard *snd_card =
        ms_snd_card_manager_get_default_card(ms_snd_card_manager_get());
    MSFilter *snd_card_read = ms_snd_card_create_reader(snd_card);
    MSFilter *dtmfgen = ms_filter_new(MS_DTMF_GEN_ID);
    MSFilter *rtpsend = ms_filter_new(MS_RTP_SEND_ID);

    /* Создаем фильтр кодера. */
    MSFilter *encoder = ms_filter_create_encoder("PCMU");

    /* Регистрируем типы нагрузки. */
    register_payloads();

    /* Создаем дуплексную RTP-сессию. */
    RtpSession* rtp_session= create_duplex_rtp_session(vars);
    ms_filter_call_method(rtpsend, MS_RTP_SEND_SET_SESSION, rtp_session);

    /* Соединяем фильтры передатчика. */
    ms_filter_link(snd_card_read, 0, dtmfgen, 0);
    ms_filter_link(dtmfgen, 0, encoder, 0);
    ms_filter_link(encoder, 0, rtpsend, 0);

    /* Создаем фильтры приемного тракта. */
    MSFilter *rtprecv = ms_filter_new(MS_RTP_RECV_ID);
    ms_filter_call_method(rtprecv, MS_RTP_RECV_SET_SESSION, rtp_session);

    /* Создаем фильтр декодера, */
    MSFilter *decoder=ms_filter_create_decoder("PCMU");

    /* Создаем фильтр звуковой карты. */
    MSFilter *snd_card_write = ms_snd_card_create_writer(snd_card);

    /* Соединяем фильтры приёмного тракта. */
    ms_filter_link(rtprecv, 0, decoder, 0);
    ms_filter_link(decoder, 0,  snd_card_write, 0);

    /* Создаем источник тактов - тикер. */
    MSTicker *ticker = ms_ticker_new();

    /* Подключаем источник тактов. */
    ms_ticker_attach(ticker, snd_card_read);
    ms_ticker_attach(ticker, rtprecv);

    /* Если настройка частоты генератора отлична от нуля, то запускаем генератор. */   
    if (vars.dtmf_cfg.frequencies[0])
    {
        /* Настраиваем структуру, управляющую выходным сигналом генератора. */
        vars.dtmf_cfg.duration = 10000;
        vars.dtmf_cfg.amplitude = 1.0;
    }

    /* Организуем цикл перезапуска генератора. */
    while(TRUE)
    {
        if(vars.dtmf_cfg.frequencies[0])
        {
            /* Включаем звуковой генератор. */
            ms_filter_call_method(dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM,
                    (void*)&vars.dtmf_cfg);
        }
        /* Укладываем тред в спячку на 20мс, чтобы другие треды
         * приложения получили время на работу. */
        ms_usleep(20000);
    }
}

컴파일해보자. 그런 다음 프로그램을 두 대의 컴퓨터에서 실행할 수 있습니다. 아니면 내가 지금 하는 것처럼. 다음 인수를 사용하여 TShark를 시작합니다.

$ sudo tshark -i lo -f "udp dst port 7010" -P -V -O RTP -o rtp.heuristic_rtp:TRUE -x

콘솔의 실행 필드에 캡처 시작에 대한 메시지만 표시된다면 이는 좋은 신호입니다. 이는 우리 포트가 다른 프로그램에 의해 점유되지 않을 가능성이 높다는 것을 의미합니다. 다른 터미널에서는 다음 포트 번호를 지정하여 "원격" 인터콤을 시뮬레이션하는 프로그램 인스턴스를 시작합니다.

$ ./mstest8 --port 9010 --lport 7010

프로그램 텍스트에서 볼 수 있듯이 기본 IP 주소는 127.0.0.1(로컬 루프백)입니다.

다른 터미널에서는 로컬 장치를 시뮬레이션하는 프로그램의 두 번째 인스턴스를 시작합니다. 내장 테스트 생성기가 작동할 수 있도록 추가 인수를 사용합니다.

$ ./mstest8  --port 7010 --lport 9010 --gen 440

이 순간, "원격" 장치를 향해 전송된 패킷은 TShark가 있는 콘솔에서 깜박이기 시작해야 하며 컴퓨터 스피커에서 지속적인 신호음이 들립니다.

모든 것이 작성된 대로 발생했다면 프로그램의 두 번째 복사본을 다시 시작하지만 키와 인수 "—gen 440"은 사용하지 않습니다. 이제 당신은 발전기 역할을 하게 될 것입니다. 그런 다음 마이크에 소리를 내면 스피커나 헤드폰에서 해당 소리를 들을 수 있습니다. 음향적 자기 여기가 발생할 수도 있으며, 스피커 볼륨을 낮추면 효과가 사라집니다.

두 대의 컴퓨터에서 실행하고 IP 주소에 대해 혼동하지 않았다면 양방향 디지털 품질 음성 통신이라는 동일한 결과가 나올 것입니다.

다음 기사에서는 자체 필터(플러그인)를 작성하는 방법을 배우게 됩니다. 이 기술 덕분에 오디오 및 비디오뿐만 아니라 다른 특정 영역에서도 미디어 스트리머를 사용할 수 있습니다.

출처 : habr.com

코멘트를 추가