2016년 10월 7일 금요일

PJSIP Media 흐름의 이해


이 문서는 https://trac.pjsip.org/repos/wiki/media-flow를 번역한 것입니다.
2016년 10월 7일  신C




1.Media Interconnection

위 다이어그램의 주요 빌딩블럭은 아래 구성요소로 이루어져 있다.


2. Setting up Media Interconnection

미디어의 상호작용은 아래 순서로 설정된다.
  1. 어플리케이션 초기화시 어플리케이션은 Sound Device Port와 Conference Bridge를 생성한다. 이 두 오브젝트는 일반적으로 어플리케이션의 생명주기와 같이한다.
  2. 전화걸기나 전화수신을 위한 설정을 할때, 어플리 케이션은 media transport instance를 만든다.(일반적으로 UDP Media transport). 이 media transport를 통해 어플리케이션은 INVITE session에 들어가는 local SDP에 전송주소와 포트를 설정한다.
  3. 통화에서 offer/answer session 이 만들어 지면(그리고 on_media_update() 콜백인 호출되고), 어플리케이션은 INVITE session에 들어있는 local와 remote SDP를 이용하여 Media session Info를 생성한다.
  4. 어플리케이션은 Media session Info로 부터 Media session을 만든다. 이 오브젝트는 이전에 만든 Media Transport를 지정한다. 이 과정에서 코덱 세팅과 Media Session Info의 파라미터를 통해 Media Stream들을 만들고 Media Stream과  Media Trasport를 연결한다.
  5. 어플리케이션이 Media session으로 부터 Media Audio Stream을 가져와 이 스트림을 conference bridge에 등록한다.
  6. 어플리케이션은 bridge의 Media Stream slot을 slot Zero와 같은 다른 슬롯에 연결한다. slot zero는 일반적으로 sound device에 연결된다.
    소스- media state가 변경되면 on_call_media_state() 콜백함수가 호출된다. 이 함수는 연결된 음성을 sound device, file 또는 loop로 연결해 준다.
    pjsua_app.c : on_call_media_state() -> on_call_audio_state()  
    > call_conf_slot = pjsua_call_info->media[media_index].stream.aud.conf_slot;
    > pjsua_conf_connect(call_conf_slot, 0) :스피커,
    >> pjsua_conf_connect(0, call_conf_slot) : 마이크
    >> pjmedia_conf_connect_port()
      > src_port->listener_slots[src_port->listener_cnt] = sink_slot;
    Conference Bridge에서 소스포트의 listener_slots 를 참조하여 sink_slot에 음성을 전달한다.


3.Media Timing

모든 미디어 플로우는 sound device의 타이밍에 따르게 된다. 특히 playback 콜백이 그렇다.


3.1 The Main Flow(Playback callback)

  1. sound device가 스피커로 재생할 프레임이 추가로 필요하면, sound device abstraction은 play_cb() 콜백을 호출할 것이다. 이 함수는 sound device가 생성될 때 등록된다.
  2. sound device는 play_cb() 콜백을 downstream port의 pjmedia_port_get_frame() 호출로 전환한다. 이런 상황은 Conference Bridge 에서 발생한다.
  3. conference bridge으로 pjmedia_port_get_frame()의 호출은 conference bridge내 모든포트를 위해 다른 pjmedia_port_get_frame 호출을 야기한다. 필요에 따라 신호를 섞어 주고 pjmedia_port_put_frame()을 한번더 호출하므로 섞인 신호를 전달한다. bridge가 이 모든 것을 처리한 후, 섞인 신호는 slot zero를 위해 원래의 pjmedia_port_get_frame() 호출으로 반환된다. 그리고 반환된 신호는 sound device에 전달된다.
  4. conference bridge에 의한 media stream port의 pjmedia_port_get_frame() 호출은  jitter buffer로 부터 하나의 프레임을 가져오도록 만든다. 그리고  설정된 코덱을 이용해 디코딩한다. 이 후 PCM 프레임을 호출한 함수에 리턴 한다. jitter buffer는 다른 흐름에서 채워지는 것을 주의하라.(네트워크 소켓의 poll 에서 채워짐)
  5. conference bridge에 의한 media stream port의 pjmedia_port_put_frame() 호출은 스트림에 설정된 코덱으로 인코딩하도록 만든다. 그리고 RTP 패킷으로 만든다.RTCP 세션을 업데이트하고 RTCP 전송을 조정한다. RTP/RTCP 패킷은 media_trasport로 전달되고 media_trasport는 이 패킷을 네트워크로 전송한다.


3.2 Recording Callback

위 흐름은 스피커를 쪽의 특정 방향에 대해서만 기술했다. 마이크로부터 나오는 흐름은 어떨까?
  1. 마이크로 부터 오디오 프레임 하나를 캡춰하면 rec_cb() 콜백을 호출하여 이를 알린다. 이 콜백함수는 sound device가 생성될 때 등록된다.
  2. sound device 포트는 rec_cb() 콜백을 downstream 포트의 pjmedia_port_put_frame() 으로 전환한다. 이런상황은 Conference Bridge에서 발생한다.
  3. pjmedia_port_put_frame() 함수가 conference bridge에서 호출되면, bridge는 PCM 프레임을 내부 버퍼(프레임 큐)에 저장한다. 저장된 버퍼는 bridge가 모든 포트로부터 프레임을 가져와 믹싱한 후 main 흐름(위 bridge의 pjmedia_port_get_frame  호출)에 전달된다.
잠재적인 문제점:
이상적으로 rec_cb()와 play_cb()는 sound_device에 의해 교대로 호출되어야 한다. 하지만 불행하게도 이것을 항상 보장해주지는 않는다. 많은 저사양 사운드카드는 rec_cb() 콜백을 연속적으로 여러번 호출하고 이후에 play_cb()를 연속적으로 호출하는 것이 일반적이다. 이런 상황을 보완하기 위해, 내부 sound device 큐 버퍼를 8 프레임 정도들어 갈 수 있도록 충분히 크게 만든다. 이 설정은 conference.c 파일의 RX_BUF_COUNT 매크로를 변경할 수 있다. 아~주 안좋은 사운드카드는 8번 이상의 연속적인 rec_cb()/play_cb() 호출을 할 수도 있는데. 이런 경우 RX_BUF_COUNT를 늘려야 한다.


3.3 incoming RTP/RTCP Packets

수신되는 RTP/RTCP 패킷은 위에서 설명한 흐름에 의해 좌우되지 않고 다른 흐름(“thread)에서 의해 전단된다. 바로 소켓 디스크립터를 폴링하는 흐름이다.


PJMEDIA의 UDP Media Transport 에서 RTP와 RTCP 소캣을 IOQueue에 등록한다. 여러개의  media transport 인스턴스는 하나의 IOQueue에 등록되고, ioqueue의 폴링은 모든 media transport에 수신되는 패킷을 체크한다.


어플리케이션은 ioqueue 인스턴스를 어디에 둘지에 따라 다른 전략을 선택할 수 있다.
  • 어플리케이션은 Media Endpoint가 내부 IOQUEUE를 초기화하고 IOQUEUE를 폴링하기 위한 하나 이상의 worker thread를 시작한다. 이 전략은 분리된 스레드에 의해 media 소켓을 폴링하므로 추천방법 중 하나이다. (PJSUA-API의 기본 설정임).
  • 어플리케이션은 SIP와 media 소켓을 위해 하나의 IOQUEUE를 이용할 수 있다. 그리고 하나의 스레드에서 모든 것을 폴링한다. 예를 들면 메인 스레드에서 가능하다. 이 전력을 선택하려면, 어플리케이션은 media endpoint 생성시 사용될 IOQueue 인스턴스를 지정하고 worker thread를 비활성화해야 한다. 이 전략은 스레드 수를 최소로 해야 하는 소규모 시스템에 효율적이다.


수신되는 RTP 패킷의 흐름은 아래와 같다.

  1. Media Endpoint의 내부 worker thread는 모든 media transport가 등록된 IOQueue를 폴링한다.
  2. 패킷이 수신되면, 폴링함수는 UDP media transport의 on_rx_rtp() 콜백함수를 수행한다. 이 콜백함수는 호출전에 UDP media transport 에 의해 ioqueue에 등록되었다.
  3. on_rx_rtp() 콜백함수는 media stream port에 수신된 RTP 패킷을 보고한다. 이 media stream은 어플리케이션이 세션을 초기화 하는동안 UDP media transport에 연결되었다.
  4. media stream은 내부 RTP 세션을 이용해 RTP 패킷을 분해하고, RX 통계를 업데이트한다. 사용된 코덱에 따라 페이로드를 디코딩하고(하나의 패킷에 여러 프레임이 포함될 수도 있다.), jitter buffer에 전달한다.
  5. 수신된 패킷 처리는 여기서 멈춘다. 이후 jitter buffer에 저장된 프레임은 main 흐름(pjmedia_port_get_frame()함수 호출되는 흐름)에 의해 빠져 나가게 된다.

댓글 없음:

댓글 쓰기