pjlib系列完结后,开始进入pjmedia系列。pjmedia是多媒体栈,可以把它按照抽象的分解大小分解为几个对象,下面通过simpleua.c示例代码解析这些对象和数据流。
从对象关系来看:
1、 pjmedia_endpt,代表一个媒体端点,端点可以理解为一个节点,可以是服务器或者客户端,一个设备一般只会有唯一一个端点,而且在初始化的时候创建。
2、pjmedia_session,代表一次会话,一个会话可以只有两个,也可以有多个参与者会议。
3、pjmedia_stream,代表一个流,在于一方进行通讯时,一般可以有音频流、视频流。
4、pjmedia_channel,只有一个方向的媒体流,也就是说,对应音频流,要发送通道和接收通道两个channel。
以上是范围从大到小的媒体对象,下面介绍其它对象。
5、pjmedia_transport,传输对象,封装了所有从网络接收数据和发送数据到网络的操作,可以是UDP、SRTP、ICE等传输方式。
6、pjmedia_snd_port,音频设备对象,最终的音频裸流都要在设备上播放,从麦克风采集,此对象封装所有设备操作。
从数据流来看:
左边为最底层,音频设备的播放和采集,通过回调接口,调用stream.c生产或消化数据,最后通过transport传输接口进行发送接收。当然,其中还夹杂着前后处理、编解码、抖动缓冲区等,后面的章节会对各个对象及相关的音频处理进行描述。
最后来分析一下实例代码simpleua.c中对pjmedia的使用方法。
初始化
在main函数中,前半部分主要是对sip的初始化,对pjmedia的初始化大概从358行开始
1、创建媒体端点
static pjmedia_endpt *g_med_endpt; /* Media endpoint. */
#if PJ_HAS_THREADS
status = pjmedia_endpt_create(&cp.factory, NULL, 1, &g_med_endpt);
#else
status = pjmedia_endpt_create(&cp.factory,
pjsip_endpt_get_ioqueue(g_endpt),
0, &g_med_endpt);
#endif
这里我们默认看有多线程的流程。
2、创建udp网络接口
static pjmedia_transport *g_med_transport[MAX_MEDIA_CNT];
/*
* Create media transport used to send/receive RTP/RTCP socket.
* One media transport is needed for each call. Application may
* opt to re-use the same media transport for subsequent calls.
*/
for (i = 0; i < PJ_ARRAY_SIZE(g_med_transport); ++i) {
status = pjmedia_transport_udp_create3(g_med_endpt, AF, NULL, NULL,
RTP_PORT + i*2, 0,
&g_med_transport[i]);
可以看出,虽然初始化还没有建立媒体通讯,但是预先创建了若干传输对象。
3、loop处理sip事件
/* Loop until one call is completed */
for (;!g_complete;) {
pj_time_val timeout = {0, 10};
pjsip_endpt_handle_events(g_endpt, &timeout);
}
初始化的最后是一个循环等待处理sip对象的操作。
建立媒体通讯
当sip协商成功后,则要开始媒体通讯,发生在函数call_on_media_update
1、创建并启动流对象
static pjmedia_stream *g_med_stream; /* Call's audio stream. */
/* Create stream info based on the media audio SDP. */
status = pjmedia_stream_info_from_sdp(&stream_info, inv->dlg->pool,
g_med_endpt,
local_sdp, remote_sdp, 0);
/* Create new audio media stream, passing the stream info, and also the
* media socket that we created earlier.
*/
status = pjmedia_stream_create(g_med_endpt, inv->dlg->pool, &stream_info,
g_med_transport[0], NULL, &g_med_stream);
/* Start the audio stream */
status = pjmedia_stream_start(g_med_stream);
这个实例并没有创建session,而是直接创建流对象。先从sip协商的sdp中获取媒体信息,然后根据这些信息创建流对象,并紧接着启动流。这里只有音频流,视频流暂不纳入分析范围。
2、启动网络传输
/* Start the UDP media transport */
pjmedia_transport_media_start(g_med_transport[0], 0, 0, 0, 0);
前面讲到,初始化的时候预创建了若干传输对象,但是并没有启动,等到协商成功后,才启动网络传输。
3、创建音频设备对象
static pjmedia_snd_port *g_snd_port; /* Sound device. */
/* Get the media port interface of the audio stream.
* Media port interface is basicly a struct containing get_frame() and
* put_frame() function. With this media port interface, we can attach
* the port interface to conference bridge, or directly to a sound
* player/recorder device.
*/
pjmedia_stream_get_port(g_med_stream, &media_port);
/* Create sound port */
pjmedia_snd_port_create(inv->pool,
PJMEDIA_AUD_DEFAULT_CAPTURE_DEV,
PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV,
PJMEDIA_PIA_SRATE(&media_port->info),/* clock rate */
PJMEDIA_PIA_CCNT(&media_port->info),/* channel count */
PJMEDIA_PIA_SPF(&media_port->info), /* samples per frame*/
PJMEDIA_PIA_BITS(&media_port->info),/* bits per sample */
0,
&g_snd_port);
status = pjmedia_snd_port_connect(g_snd_port, media_port);
这里先从流对象取出媒体接口,其中的媒体接口包含了数据回调,这些后面分析数据流再重点讲,然后创建并启动音频设备对象。
结束销毁
/* Destroy audio ports. Destroy the audio port first
* before the stream since the audio port has threads
* that get/put frames to the stream.
*/
if (g_snd_port)
pjmedia_snd_port_destroy(g_snd_port);
/* Destroy streams */
if (g_med_stream)
pjmedia_stream_destroy(g_med_stream);
/* Destroy media transports */
for (i = 0; i < MAX_MEDIA_CNT; ++i) {
if (g_med_transport[i])
pjmedia_transport_close(g_med_transport[i]);
}
/* Deinit pjmedia endpoint */
if (g_med_endpt)
pjmedia_endpt_destroy(g_med_endpt);
/* Deinit pjsip endpoint */
if (g_endpt)
pjsip_endpt_destroy(g_endpt);
/* Release pool */
if (pool)
pj_pool_release(pool);
当主线程退出loop时,则销毁所有创建的对象。