目录
一、ORTP库的源码分析
1、ORTP库概览
(1)库本身没有main,提供一堆功能函数,都在src目录下
(2)库的使用给了案例,有main,在src/tests目录下
(3)相关数据结构和头文件在include/ortp目录下
(4)ortp实现了rtp和rtcp协议,前者负责传输,后者负责控制和同步协调
2、ORTP库的使用案例
我们可以使用sourceinsight软件建立工程来分析该源码包:
(1)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;
}
(2)ortp_init及av_profile_init
void ortp_init()
{
if (ortp_initialized) return;
ortp_initialized++;
#ifdef WIN32
win32_init_sockets();
#endif
av_profile_init(&av_profile);
ortp_global_stats_reset();
init_random_number_generator();
#ifdef HAVE_SRTP
if (srtp_init() != err_status_ok) {
ortp_fatal("Couldn't initialize SRTP library.");
}
err_reporting_init("oRTP");
#endif
ortp_message("oRTP-" ORTP_VERSION " initialized.");
}
void av_profile_init(RtpProfile *profile)
{
rtp_profile_clear_all(profile);
profile->name="AV profile";
rtp_profile_set_payload(profile,0,&payload_type_pcmu8000);
rtp_profile_set_payload(profile,1,&payload_type_lpc1016);
rtp_profile_set_payload(profile,3,&payload_type_gsm);
rtp_profile_set_payload(profile,7,&payload_type_lpc);
rtp_profile_set_payload(profile,4,&payload_type_g7231);
rtp_profile_set_payload(profile,8,&payload_type_pcma8000);
rtp_profile_set_payload(profile,9,&payload_type_g722);
rtp_profile_set_payload(profile,10,&payload_type_l16_stereo);
rtp_profile_set_payload(profile,11,&payload_type_l16_mono);
rtp_profile_set_payload(profile,18,&payload_type_g729);
rtp_profile_set_payload(profile,31,&payload_type_h261);
rtp_profile_set_payload(profile,32,&payload_type_mpv);
rtp_profile_set_payload(profile,34,&payload_type_h263);
rtp_profile_set_payload(profile,96,&payload_type_h264);
}
(3)ortp_scheduler_init和ORTP调度器:一个任务中完成多个会话的发送和接收,类似于select
(4)rtp_session_new和rtp的会话管理
3、rtp的session
(1)rtp通过会话来管理数据发送和接收,会话的本质是一个结构体,管理很多信息
(2)创建会话用rtp_session_new
(3)rtp发送用rtp_session_send_with_ts
(4)底层真正干活的还是socket接口那一套,参考rtpsession_inet.c
4、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发送实验源码分析(使用sourceinsight软件)
1、了解H264的基本组成与工作原理
参考学习:
https://blog.csdn.net/special00/article/details/82533768
https://blog.csdn.net/zhang_danf/article/details/51037572
h264包在传输的时候,如果包太大,会被分成多个片。NALU头会被如下的2个自己代替。
The FU indicator octet has the following format:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
别被名字吓到这个格式就是上面提到的RTP h264负载类型,Type为FU-A
The FU header has the following format:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S bit为1表示分片的NAL开始,当它为1时,E不能为1
E bit为1表示结束,当它为1,S不能为1
R bit保留位
Type就是NALU头中的Type,取1-23的那个值
2、发送函数的重点讲解
/** 发送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,//由于只发一帧所以可忽略包头,跳过前面四个字节的分隔符(00 00 00 01)
&buffer[4],
valid_len,
g_userts);
}
else if (valid_len > MAX_RTP_PKT_LENGTH)//这个最大长度是协议规定的
{
//切分为很多个包发送,每个包前要对头进行处理,如第一个包
valid_len -= 1;//减掉NALU头部的一个字节(|F 1bit|NRI 3bit| Type 4bit |)
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;//4字节的分隔符加上1字节的NALU头部,故从数组buffer[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;
//最高位置1表示分片的NAL开始,即S位
}
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;//分片的NAL结束,即E位
sendBytes = rtp_session_send_with_ts(session,
&buffer[pos-2],
iSendLen+2,
g_userts);
t++;
}
}
}
g_userts += DefaultTimestampIncrement;//timestamp increase,DefaultTimestampIncrement是发送的时间间隔
return len;
}
3、可能的拓展方向
(1)裁剪sample到最简化
(2)修改一些参数做实验(譬如每包字节数、IP地址、端口号等)
三、VLC的sdp文件解析和本季总结
1、SDP文件格式
详解参考(已经有人写的特别详细了,我就没必要整理了):
https://blog.csdn.net/tongjing524/article/details/49635065
2、第三季总结
不要陷入技术娱乐思维,钻牛角尖,工作还是以解决问题为主,局部深入。
注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来,并且引用了部分他人博客的内容,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。