当前位置: 首页 > 工具软件 > oRTP > 使用案例 >

第三季2:ORTP库与RTP发送实验的源码分析

穆乐逸
2023-12-01

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

本文首先分析ORTP库的组成,然后以rtpsend.c为例说明ORTP库的使用方法,最后分析第三季1中的RTP发送实验的源码。

一、ORTP库的源码分析

1、ORTP库概览

(1)库提供一堆功能函数(本身没有main),在ortp-master/src目录下。

root@ubuntu:/home/xjh/iot/hisi_development/ortp/ortp-master/src# ls
avprofile.c  jitterctl.c  master               ortp_srtp.c    rtcp.c        rtpsession.c       rtptimer.c   sessionset.c  system             utils.h
b64.c        jitterctl.h  netsim.c             payloadtype.c  rtcpparse.c   rtpsession_inet.c  rtptimer.h   str_utils.c   telephonyevents.c  zrtp.c
dll_entry.c  logging.c    ortp.c               port.c         rtpparse.c    rtpsession_priv.h  scheduler.c  stun.c        tests
event.c      Makefile.am  ortp-config-win32.h  posixtimer.c   rtpprofile.c  rtpsignaltable.c   scheduler.h  stun_udp.c    utils.c
root@ubuntu:/home/xjh/iot/hisi_development/ortp/ortp-master/src#

(2)库的使用给了案例(有main),在ortp-master/src/tests目录下。

root@ubuntu:/home/xjh/iot/hisi_development/ortp/ortp-master/src/tests# ls
Makefile.am  mrtpsend.c    rtprecv.c  rtpsend_stupid.c  tevmrtprecv.c  tevrtpsend.c  win_sender
mrtprecv.c   rtpmemtest.c  rtpsend.c  test_timer.c      tevrtprecv.c   win_receiver
root@ubuntu:/home/xjh/iot/hisi_development/ortp/ortp-master/src/tests# 

(3)相关数据结构和头文件,在ortp-master/include/ortp目录下。

root@ubuntu:/home/xjh/iot/hisi_development/ortp/ortp-master/include/ortp# ls
b64.h    logging.h    ortp.h       payloadtype.h  rtcp.h  rtpprofile.h  rtpsignaltable.h  str_utils.h  stun_udp.h         zrtp.h
event.h  Makefile.am  ortp_srtp.h  port.h         rtp.h   rtpsession.h  sessionset.h      stun.h       telephonyevents.h
root@ubuntu:/home/xjh/iot/hisi_development/ortp/ortp-master/include/ortp#

(4)ortp实现了rtp和rtcp协议,前者负责传输,后者负责控制和同步协调。 

2、ORTP库的使用案例分析

这里以ortp-master/src/tests/rtpsend.c文件为例进行说明,该文件内容如下:

#include <ortp/ortp.h>
#include <signal.h>
#include <stdlib.h>

#ifndef _WIN32 
#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#endif

int runcond=1;

void stophandler(int signum)
{
	runcond=0;
}

static const char *help="usage: rtpsend	filename dest_ip4addr dest_port [ --with-clockslide <value> ] [ --with-jitter <milliseconds>]\n";

int main(int argc, char *argv[])
{
	RtpSession *session;//定义一个会话
	unsigned char buffer[160];
	int i;
	FILE *infile;
	char *ssrc;
	uint32_t user_ts=0;
	int clockslide=0;
	int jitter=0;
	if (argc<4){
		printf("%s", help);
		return -1;
	}
	for(i=4;i<argc;i++){
		if (strcmp(argv[i],"--with-clockslide")==0){
			i++;
			if (i>=argc) {
				printf("%s", help);
				return -1;
			}
			clockslide=atoi(argv[i]);
			ortp_message("Using clockslide of %i milisecond every 50 packets.",clockslide);
		}else if (strcmp(argv[i],"--with-jitter")==0){
			ortp_message("Jitter will be added to outgoing stream.");
			i++;
			if (i>=argc) {
				printf("%s", help);
				return -1;
			}
			jitter=atoi(argv[i]);
		}
	}
	
	ortp_init();
	ortp_scheduler_init();
	ortp_set_log_level_mask(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR);
	session=rtp_session_new(RTP_SESSION_SENDONLY);	
	
	rtp_session_set_scheduling_mode(session,1);
	rtp_session_set_blocking_mode(session,1);
	rtp_session_set_connected_mode(session,TRUE);
	rtp_session_set_remote_addr(session,argv[2],atoi(argv[3]));
	rtp_session_set_payload_type(session,0);
	
	ssrc=getenv("SSRC");
	if (ssrc!=NULL) {
		printf("using SSRC=%i.\n",atoi(ssrc));
		rtp_session_set_ssrc(session,atoi(ssrc));
	}
		
	#ifndef _WIN32
	infile=fopen(argv[1],"r");
	#else
	infile=fopen(argv[1],"rb");
	#endif

	if (infile==NULL) {
		perror("Cannot open file");
		return -1;
	}

	signal(SIGINT,stophandler);
	while( ((i=fread(buffer,1,160,infile))>0) && (runcond) )
	{
		rtp_session_send_with_ts(session,buffer,i,user_ts);
		user_ts+=160;
		if (clockslide!=0 && user_ts%(160*50)==0){
			ortp_message("Clock sliding of %i miliseconds now",clockslide);
			rtp_session_make_time_distorsion(session,clockslide);
		}
		/*this will simulate a burst of late packets */
		if (jitter && (user_ts%(8000)==0)) {
			struct timespec pausetime, remtime;
			ortp_message("Simulating late packets now (%i milliseconds)",jitter);
			pausetime.tv_sec=jitter/1000;
			pausetime.tv_nsec=(jitter%1000)*1000000;
			while(nanosleep(&pausetime,&remtime)==-1 && errno==EINTR){
				pausetime=remtime;
			}
		}
	}

	fclose(infile);
	rtp_session_destroy(session);
	ortp_exit();
	ortp_global_stats_display();

	return 0;
}

(1)RTP中的会话(session)

RTP通过会话来管理数据发送和接收。

会话的本质,是RtpSession这个结构体类型的实例化(或者这个类型的一个变量),比如这里就定义了一个RtpSession类型的指针变量session。

创建会话用rtp_session_new函数,发送数据包用rtp_session_send_with_ts函数,而底层真正干活的还是socket接口那一套,参考rtpsession_inet.c文件。

(2)ortp_init函数

该函数主要用来初始化ORTP库,其内容如下:

/**
 *	Initialize the oRTP library. You should call this function first before using
 *	oRTP API.
**/
void ortp_init()
{
	if (ortp_initialized) return;
	ortp_initialized++;
//省略部分
	av_profile_init(&av_profile);//函数返回时,
	ortp_global_stats_reset();
	init_random_number_generator();

//省略部分
	ortp_message("oRTP-" ORTP_VERSION " initialized.");
}

其中主要是av_profile_init函数,我们来重点分析这个函数。此函数主要用来初始化RtpProfile类型的变量av_profile,该变量记录着可以识别哪些profile,当需要识别新的profile时,要在该函数里添加相应的内容。这就是在博文第三季1:ORTP库的移植与视频的实时传输第二节第2点中进行那样操作的原因。

av_profile变量的数据类型,即RtpProfile类型的定义如下:

/**
 * The RTP profile is a table RTP_PROFILE_MAX_PAYLOADS entries to make the matching
 * between RTP payload type number and the PayloadType that defines the type of
 * media.
**/
struct _RtpProfile
{
	char *name;
	PayloadType *payload[RTP_PROFILE_MAX_PAYLOADS];
};

 av_profile_init函数内容如下:

void av_profile_init(RtpProfile *profile)
{
	rtp_profile_clear_all(profile);
	profile->name="AV profile";//名字是不变的
	//省略部分代码
    //即profile->payload[32]= &payload_type_mpv
	rtp_profile_set_payload(profile,32,&payload_type_mpv);
    //即profile->payload[34]= &payload_type_h263
	rtp_profile_set_payload(profile,34,&payload_type_h263);
    //即profile->payload[96]= &payload_type_h264
	rtp_profile_set_payload(profile,96,&payload_type_h264);//这个就是后来添加的
}

//其中payload_type_h264定义如下
PayloadType payload_type_h264={
    TYPE( PAYLOAD_VIDEO),
    CLOCK_RATE(90000),
    BITS_PER_SAMPLE(0),
    ZERO_PATTERN(NULL),
    PATTERN_LENGTH(0),
    NORMAL_BITRATE(256000),
    MIME_TYPE ("H264"),
    CHANNELS(0)
};

(3)ortp_scheduler_init函数

该函数是ORTP调度器或者说仲裁机构(一段代码),功能是在一个任务中完成多个会话的发送和接收,类似于select。

3、ORTP的一些细节

(1)port.c中对OS的常用机制(任务创建和销毁、进程管理和信号量等)进行了封装,便于将ortp移植到不同平台中。

(2)utils.c中实现了一个双向链表。

(3)str_util.c中实现了一个队列管理。

(4)rtpparse.c和rtcpparse.c文件实现了解析收到的rtp数据包的部分。

(5)jitterctl.c中实现了jitter buffer来防抖。jitter buffer技术是ip 音视频通信里相对比较高级的主题,jitter buffer模块好坏通常是衡量一个voip客户端/服务器好坏的技术点之一,尤其是在网络抖动比较严重,如3g, wifi环境,数据包的rtt值不均衡往往会导致语音卡顿,丢字等现象,jitter buffer 模块通过缓存一段数据包,把数据包重排,并均匀的送给播放端,一个好的jitter buffer实现通长是动态调整缓存大小的,在网络延迟大,抖动严重时会动态增加缓存大小,在网络恢复时动态减小缓存大小以减少端到端的播放延迟。

二、RTP发送实验的源码分析

RTP发送实验的源码,是指博文第三季1:ORTP库的移植与视频的实时传输中提到的修改后的两个文件:mpp/sample/venc/sample_venc.c、mpp/sample/common/sample_common_venc.c

使用ORTP库的sample和第二季的sample,它们的步骤相同,只是在“ 如何处理编码得到的码流 ”这个问题上不同。第二季的sample中是将码流保存为裸流文件,而使用 ORTP库的 sample是将码流数据以包的形式在网络上传输。因此修改点就在第二季的sample中将码流保存为裸流文件的代码处。

 博文第三季1:ORTP库的移植与视频的实时传输中提到,修改点主要有以下两处。

(1)在SAMPLE_COMM_VENC_GetVencStreamProc函数的step2之前,添加下面代码:

/******************************************************************************
* funciton : get stream from each channels and save them
******************************************************************************/
HI_VOID* SAMPLE_COMM_VENC_GetVencStreamProc(HI_VOID *p)
{
    //省略部分代码

   #if ORTP_ENABLE
    /***rtp init****/
    pRtpSession = rtpInit( LOCAL_HOST_IP ,8080);  
    if (pRtpSession==NULL)   
    {   
        printf( "error rtpInit" ); 
        exit(-1);  
        return  0;   
    } 
   #endif
    /******************************************
     step 2:  Start to get streams of each channel.
     ***********************************************/
    
    //省略部分代码

上面调用了rtpInit函数,这个函数主要用来初始化RTP及其他参数,函数内容如下。分析可知,它和ortp-master/src/tests/rtpsend.c文件中main函数部分代码很类似。

/**  初始化   
 *     
 *   主要用于对ortp以及其它参数进行初始化   
 *   @param:  char * ipStr 目的端IP地址描述串   
 *   @param:  int port 目的端RTP监听端口   
 *   @return:  RtpSession * 返回指向RtpSession对象的指针,如果为NULL,则初始化失败   
 *   @note:      
 */   
RtpSession * rtpInit( char  * ipStr, int  port)
{
    RtpSession *session; 
    char  *ssrc;
    printf("********oRTP for H.264 Init********\n");

    ortp_init();
    ortp_scheduler_init();
    ortp_set_log_level_mask(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR);
    session=rtp_session_new(RTP_SESSION_SENDONLY);	

    rtp_session_set_scheduling_mode(session,1);
    rtp_session_set_blocking_mode(session,0);
    //rtp_session_set_connected_mode(session,TRUE);
    rtp_session_set_remote_addr(session,ipStr,port);
    rtp_session_set_payload_type(session,Y_PLOAD_TYPE);

    ssrc=getenv("SSRC");
    if (ssrc!=NULL) {
        printf("using SSRC=%i.\n",atoi(ssrc));
        // 设置输出流的SSRC。不做此步的话将会给个随机值 
        rtp_session_set_ssrc(session,atoi(ssrc));
    }
    return  session;
}

(2)修改SAMPLE_COMM_VENC_SaveH264函数,如下所示:

/******************************
* funciton : save H264 stream
*******************************/
HI_S32 SAMPLE_COMM_VENC_SaveH264(FILE* fpH264File, VENC_STREAM_S *pstStream)
{
    HI_S32 i;
    for (i = 0; i < pstStream->u32PackCount; i++)
    {
        #if ORTP_ENABLE//这里修改过
            rtpSend(pRtpSession,pstStream->pstPack[i].pu8Addr, pstStream->pstPack[i].u32Len);
        #else
            fwrite(pstStream->pstPack[i].pu8Addr+pstStream->pstPack[i].u32Offset,pstStream->pstPack[i].u32Len-pstStream->pstPack[i].u32Offset, 1, fpH264File);
            fflush(fpH264File);
        #endif
    }
    return HI_SUCCESS;
}

上面调用了rtpSend函数,这个函数主要用来发送RTP数据包。该函数位于修改后的/sample_comm_venc.c文件中,内容如下。

/**  发送rtp数据包   
 *     
 *   主要用于发送rtp数据包   
 *   @param:  RtpSession *session RTP会话对象的指针   
 *   @param:  const char *buffer 要发送的数据的缓冲区地址   
 *   @param: int len 要发送的数据长度   
 *   @return:  int 实际发送的数据包数目   
 *   @note:     如果要发送的数据包长度大于BYTES_PER_COUNT,本函数内部会进行分包处理   
 */   
int  rtpSend(RtpSession *session, char  *buffer,  int  len)
{  
    int  sendBytes = 0; 
    int status;       
    uint32_t valid_len=len-4;
    unsigned char NALU=buffer[4];
     
    //如果数据小于MAX_RTP_PKT_LENGTH字节,直接发送:单一NAL单元模式
    if(valid_len <= MAX_RTP_PKT_LENGTH)
    {
        sendBytes = rtp_session_send_with_ts(session,
                                             &buffer[4],
                                             valid_len,
                                             g_userts);
    }
    else if (valid_len > MAX_RTP_PKT_LENGTH)
    {
        //切分为很多个包发送,每个包前要对头进行处理,如第一个包
        valid_len -= 1;
        int k=0,l=0;
        k=valid_len/MAX_RTP_PKT_LENGTH;
        l=valid_len%MAX_RTP_PKT_LENGTH;
        int t=0;
        int pos=5;
        if(l!=0)
        {
            k=k+1;
        }
        while(t<k)//||(t==k&&l>0))
        {
            if(t<(k-1))//(t<k&&l!=0)||(t<(k-1))&&(l==0))//(0==t)||(t<k&&0!=l))
            {
                buffer[pos-2]=(NALU & 0x60)|28;
                buffer[pos-1]=(NALU & 0x1f);
                if(0==t)
                {
                    buffer[pos-1]|=0x80;
                }
                sendBytes = rtp_session_send_with_ts(session,
                                                     &buffer[pos-2],
                                                     MAX_RTP_PKT_LENGTH+2,
                                                     g_userts);
                t++;
                pos+=MAX_RTP_PKT_LENGTH;
            }
            else //if((k==t&&l>0)||((t==k-1)&&l==0))
            {
                int iSendLen;
                if(l>0)
                {
                    iSendLen=valid_len-t*MAX_RTP_PKT_LENGTH;
                }
                else
                    iSendLen=MAX_RTP_PKT_LENGTH;
                buffer[pos-2]=(NALU & 0x60)|28;
                buffer[pos-1]=(NALU & 0x1f);
                buffer[pos-1]|=0x40;
                sendBytes = rtp_session_send_with_ts(session,
                                                     &buffer[pos-2],
                                                     iSendLen+2,
                                                     g_userts);
                t++;
            }
        }
    }

    g_userts += DefaultTimestampIncrement;//timestamp increase
    return  len;
}

三、可拓展的内容

(1)将这个sample裁剪到最简化。

(2)修改一些参数做实验(譬如每包字节数、IP地址、端口号等)。

 类似资料: