pjmedia系列之媒体流pjmedia_stream

前面讲过,一个会话可以有多个流,比如音频流、视频流,这里讲一个音频流有哪些东西。

媒体流

struct pjmedia_stream
{
    pjmedia_endpt	    *endpt;	    /**< Media endpoint.	    */
    pjmedia_codec_mgr	    *codec_mgr;	    /**< Codec manager instance.    */
    pjmedia_stream_info	     si;	    /**< Creation parameter.        */
    pjmedia_port	     port;	    /**< Port interface.	    */
    pjmedia_channel	    *enc;	    /**< Encoding channel.	    */
    pjmedia_channel	    *dec;	    /**< Decoding channel.	    */

    pjmedia_transport	    *transport;	    /**< Stream transport.	    */

    pjmedia_codec	    *codec;	    /**< Codec instance being used. */
    pjmedia_codec_param	     codec_param;   /**< Codec param.		    */


    pj_mutex_t		    *jb_mutex;
    pjmedia_jbuf	    *jb;	    /**< Jitter buffer.		    */

    pjmedia_rtcp_session     rtcp;	    /**< RTCP for incoming RTP.	    */
}

 此结构体很庞大,这里省略了大部分成员,其中endpt就是流的上一级端点,transport会指向上一节创建的传输对象。

媒体流创建

在示例程序simpleua.c中,当sip协商成功调用call_on_media_update,在里面会创建并启动流。

    /* 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 UDP media transport */
    pjmedia_transport_media_start(g_med_transport[0], 0, 0, 0, 0);

先看创建流

创建流的流程很多,这里抽取一些关键性代码

1、申请媒体流空间

2、初始化流的若干参数

3、codec管理者及codec相关的操作

4、设置第一组回调put_frame和get_frame,这组回调是音频设备要用的,下一节再讲。

5、创建jitterbuffer,这个后面会单独讲

6、创建编码通道和解码通道

7、调用上一节中提到的媒体传输attach,传入第2组回调on_rx_rtp和on_rx_rtcp

/*
 * Create media stream.
 */
PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt,
					   pj_pool_t *pool,
					   const pjmedia_stream_info *info,
					   pjmedia_transport *tp,
					   void *user_data,
					   pjmedia_stream **p_stream)

{
    pjmedia_stream *stream;
    pjmedia_transport_attach_param att_param;

    /* Allocate the media stream: */
    stream = PJ_POOL_ZALLOC_T(pool, pjmedia_stream);

    /* Init stream: */
    stream->endpt = endpt;
    stream->codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
    stream->user_data = user_data;

    /* Create and initialize codec: */
    status = pjmedia_codec_mgr_alloc_codec( stream->codec_mgr,
					    &info->fmt, &stream->codec);

	stream->port.put_frame = &put_frame;
	stream->port.get_frame = &get_frame;

    /* Create jitter buffer */
    status = pjmedia_jbuf_create(pool, &stream->port.info.name,
				 stream->frame_size,
				 stream->codec_param.info.frm_ptime,
				 jb_max, &stream->jb);


    /* Create decoder channel: */

    status = create_channel( pool, stream, PJMEDIA_DIR_DECODING,
			     info->rx_pt, info, &stream->dec);

    /* Create encoder channel: */
    status = create_channel( pool, stream, PJMEDIA_DIR_ENCODING,
			     info->tx_pt, info, &stream->enc);

    pj_bzero(&att_param, sizeof(att_param));
    att_param.stream = stream;
    att_param.media_type = PJMEDIA_TYPE_AUDIO;
    att_param.user_data = stream;
    pj_sockaddr_cp(&att_param.rem_addr, &info->rem_addr);
    pj_sockaddr_cp(&stream->rem_rtp_addr, &info->rem_addr);
    if (stream->si.rtcp_mux) {
	pj_sockaddr_cp(&att_param.rem_rtcp, &info->rem_addr);    	
    } else if (pj_sockaddr_has_addr(&info->rem_rtcp.addr)) {
	pj_sockaddr_cp(&att_param.rem_rtcp, &info->rem_rtcp);
    }
    att_param.addr_len = pj_sockaddr_get_len(&info->rem_addr);
    att_param.rtp_cb2 = &on_rx_rtp;
    att_param.rtcp_cb = &on_rx_rtcp;

    /* Only attach transport when stream is ready. */
    status = pjmedia_transport_attach2(tp, &att_param);
}

到这里就可以知道数据的流向,先从pjmedia_transport通过ioqueue接收到音频数据,然后通过几层回调,最终调用到stream的on_rx_rtp。

再看启动流,没有太多东西,就是设置两个标志位。

PJ_DEF(pj_status_t) pjmedia_stream_start(pjmedia_stream *stream)
{
    if (stream->enc && (stream->dir & PJMEDIA_DIR_ENCODING)) {
	stream->enc->paused = 0;
    }

    if (stream->dec && (stream->dir & PJMEDIA_DIR_DECODING)) {
	stream->dec->paused = 0;
    } 

    return PJ_SUCCESS;
}

on_rx_rtp

当音频数据流到on_rx_rtp时

1、更新RTP session参数

2、解码

3、插入到jitterbuffer

/*
 * This callback is called by stream transport on receipt of packets
 * in the RTP socket.
 */
static void on_rx_rtp( pjmedia_tp_cb_param *param)
{
    pjmedia_stream *stream = (pjmedia_stream*) param->user_data;
    void *pkt = param->pkt;
    pj_ssize_t bytes_read = param->size;
    pjmedia_channel *channel = stream->dec;
    const pjmedia_rtp_hdr *hdr;
    const void *payload;
    unsigned payloadlen;
    pjmedia_rtp_status seq_st;
    pj_status_t status;
    pj_bool_t pkt_discarded = PJ_FALSE;

    /* Update RTP and RTCP session. */
    status = pjmedia_rtp_decode_rtp(&channel->rtp, pkt, (int)bytes_read,
				    &hdr, &payload, &payloadlen);

    /* Update RTP session (also checks if RTP session can accept
     * the incoming packet.
     */
    pjmedia_rtp_session_update2(&channel->rtp, hdr, &seq_st,
			        hdr->pt != stream->rx_event_pt);

	/* Parse the payload. */
	status = pjmedia_codec_parse(stream->codec, (void*)payload,
				     payloadlen, &ts, &count, frames);

	/* Put each frame to jitter buffer. */
	for (i=0; i<count; ++i) {
	    unsigned ext_seq;
	    pj_bool_t discarded;

	    ext_seq = (unsigned)(frames[i].timestamp.u64 / ts_span);
	    pjmedia_jbuf_put_frame2(stream->jb, frames[i].buf, frames[i].size,
				    frames[i].bit_info, ext_seq, &discarded);
	    if (discarded)
		pkt_discarded = PJ_TRUE;
	}
}

总结

pjmedia_stream是pjmedia最复杂的结构体,大部分的操作都在这个对象里进行,其中两组回调是连接数据流的关键,一组跟网络绑定,一组跟设备绑定,使得整个媒体流可以串起来。本节先介绍这些,pjmedia_stream还有很多子模块,比如jitterbuffer,后面再单独介绍。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
接口12:更新视频媒体通道 功能描述:创建一个媒体通道来进行视频通话 函数原形:public static int pnas_pjsua_media_video_update(int call_id, int call_type, pj_str_t* caller_udn ,nas_remote_video_info remote_video_info, pnas_video_info_others *video_info_others) 参数说明:call_type ------ 会话媒体类型,一是用于是否需要硬编解码,二是重传功能需要 caller_udn ------- 用户的号码,用于视频学习包的发送(已废弃使用) pnas_remote_video_info是一个结构体,具体传递参数如下: Unsigned media_type ----- 媒体类型, 这里只能等于2 pj_str_t remote_ip ----- 呼叫对方的IP int local_rtp_port ----- 本地传输视频RTP的端口 int remote_rtp_port ----- 对方接收本地视频RTP到的端口 int remote_rtcp_port ----- 对方接收本地视频RTCP到的端口 pjmedia_dir locat_audio_dir --- { 值为0,待用; 值为1,只编码,会话只发送视频; 值为2,只解码,会话只接收视频; 值为3,编解码,会话支持同时编码和解码视频; } PNAS_VIDEO_CODEC_NAME vid_codec_name --- 编解码器(比如H264) PNAS_VIDEO_FPS fps ----- 视频帧率 PNAS_VIDEO_SIZE video_size ----- 视频分辨率 video_info_others是一个结构体,具体传递参数如下: int cap_dev --- 用于切换哪一个摄像头,是使用后置、前置还是外置摄像头 返回值:底层返回的值 PJ_ERRNO_PARAM_INVALID=170009 ----- 传入的参数为空或参数不符合要求, 比如媒体类型为音频、没有值或为空 PJ_ERRNO_POOL_ERROR=170014 ----- 创建内存池失败 PJ_ERRNO_AUDIO_STREAM_UPDATE=170015 --- 更新视频媒体通道失败 PJ_ERRNO_VIDEO_CHANNEL_UPDATE=170018 --- 不能更新视频媒体通道,失败 PJ_SUCCESS = 0 ----- 成功 其它说明:根据传入的media_type ,local_rtp_port等参数去查找对应创建的call_media会话端口而更新对应的视频通道。call_type类型要看pnas设计文档,值是跟sip设计文档中不一样的,如果很低层用到这个类型的话,请参考设计文档把值修改过来。
根据您提供的代码,我进行了修改并解释了相应的部分: ```c typedef struct pjmedia_sdp_rtcp_attr { unsigned port; pj_str_t net_type; pj_str_t addr_type; pj_str_t addr; } pjmedia_sdp_rtcp_attr; PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_rtcp(const pjmedia_sdp_attr *attr, pjmedia_sdp_rtcp_attr *rtcp); PJ_DECL(pjmedia_sdp_attr*) pjmedia_sdp_attr_create_rtcp(pj_pool_t *pool, const pj_sockaddr *a); unsigned count = 7; // 属性数组中属性的数量 pjmedia_sdp_attr* attr_array[7]; // 属性数组 pjmedia_sdp_rtcp_attr rtcp_attr; // 要删除的 RTCP 属性实例 // 使用合适的方式为 attr_array 和 rtcp_attr 赋值 // 调用函数进行属性删除 pj_status_t status = PJ_ENOTFOUND; // 初始化为找不到属性,以防删除前没有匹配的属性 for (unsigned i = 0; i < count; i++) { pjmedia_sdp_rtcp_attr rtcp; if (pjmedia_sdp_attr_get_rtcp(attr_array[i], &rtcp) == PJ_SUCCESS) { // 找到 RTCP 属性 if (strcmp(rtcp.addr_type.ptr, "rtp") == 0) { // 删除 RTCP 属性 status = pjmedia_sdp_attr_remove(&count, attr_array, attr_array[i]); break; } } } if (status == PJ_SUCCESS) { // 属性删除成功 printf("RTCP attribute removed successfully.\n"); } else if (status == PJ_ENOTFOUND) { // 找不到要删除的 RTCP 属性 printf("RTCP attribute not found.\n"); } else { // 其他错误状态 printf("Error removing RTCP attribute.\n"); } ``` 在上述示例中,我们遍历属性数组中的每个属性,并将其传递给 `pjmedia_sdp_attr_get_rtcp` 函数来获取相关的 RTCP 属性信息。然后,我们检查获取到的 RTCP 属性的地址类型是否为 "rtp",如果是,则调用 `pjmedia_sdp_attr_remove` 函数删除该属性。请根据实际需求进行适当修改。 希望这可以帮助您!如果还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值