前言
由于hi3516a海思自带的开发应用程序是通过摄像头接口或HDMI接口获取视频数据后并进行存储。然而,在实际应用中,多是获取数据后直接通过网络把数据发送出去。那么本文章将开始学习hi3516a获取数据后通过网线和RTP协议把数据实时发送出去。
背景:hi3516a开发板通过HDMI接口获取BT1120数据后进行压缩,并通过RTP协议进行实时的视频直播。
硬件平台:hi3516a
软件平台:Hi3516A_SDK_V1.0.5.0
视频数据接口:HDMI
无私分享,从我做起!
源码解析
下面首先看主程序的源码,源码来源于网络,一步一步进行分析。
int main(int argc, char* argv[])
{
int s32MainFd,temp;
struct timespec ts = { 0, 200000000 }; //200000000ns=200000us=200ms=0.2s
pthread_t id;
ringmalloc(1920*1080);//分配缓冲区并进行初始化
printf("RTSP server START\n");
PrefsInit();//设置服务器信息全局变量,获取主机name
printf("listen for client connecting...\n");
signal(SIGINT, IntHandl); //异常中止信号处理
s32MainFd = tcp_listen(SERVER_RTSP_PORT_DEFAULT); //以非阻塞方式侦听554端口
printf("s32MainFd=%d\r\n",s32MainFd);
if (ScheduleInit() == ERR_FATAL)
//线程的结构体进行初始化,创建处理主线程schedule_do,schedule_do里调用sched[i].play_action把数据发送出去
{
fprintf(stderr,"Fatal: Can't start scheduler %s, %i \nServer is aborting.\n", __FILE__, __LINE__);
return 0;
}
RTP_port_pool_init(RTP_DEFAULT_PORT); //554,初始化10个RTP port
pthread_create(&id,NULL,SAMPLE_VENC_1080P_CLASSIC_RTSP,NULL); //海思芯片内部获取编码后数据
while (!g_s32Quit)
{
nanosleep(&ts, NULL);
EventLoop(s32MainFd);//RTSP服务器连接处理函数入口
}
sleep(2);
ringfree();
printf("The Server quit!\n");
return 0;
}
(1)void ringmalloc(int size)
其中ringmalloc(1920*1080);是分配缓冲区并进行初始化,我的hdmi数据数据分辨率是1920X1080p,下面看下ringmalloc()函数源码。主要是分配64个1920X1080p的fifo空间并初始化一些参数。
/* 环形缓冲区的地址编号计算函数,如果到达唤醒缓冲区的尾部,将绕回到头部。
环形缓冲区的有效地址编号为:0到(NMAX-1)
*/
void ringmalloc(int size)
{
int i;
for(i =0; i<NMAX; i++) //64
{
ringfifo[i].buffer = malloc(size);
ringfifo[i].size = 0;
ringfifo[i].frame_type = 0;
printf("FIFO INFO:idx:%d,len:%d,ptr:%x\n",i,ringfifo[i].size,(int)(ringfifo[i].buffer));
}
iput = 0; /* 环形缓冲区的当前放入位置 */
iget = 0; /* 缓冲区的当前取出位置 */
n = 0; /* 环形缓冲区中的元素总数量 */
}
(2)void PrefsInit()
接着查看PrefsInit()函数,主要是设置服务器信息全局变量,获取主机name。
void PrefsInit()
{
int l;
//设置服务器信息全局变量
stPrefs.port = SERVER_RTSP_PORT_DEFAULT; //554
gethostname(stPrefs.hostname,sizeof(stPrefs.hostname));
l=strlen(stPrefs.hostname);
if (getdomainname(stPrefs.hostname+l+1,sizeof(stPrefs.hostname)-l)!=0)
{
stPrefs.hostname[l]='.';
}
#ifdef RTSP_DEBUG
printf("-----------------------------------\n");
printf("\thostname is: %s\n", stPrefs.hostname);
printf("\trtsp listening port is: %d\n", stPrefs.port);
printf("\tInput rtsp://hostIP:%d/test.264 to play this\n",stPrefs.port);
printf("\n");
#endif
}
(3)s32MainFd = tcp_listen(SERVER_RTSP_PORT_DEFAULT);
接下来,s32MainFd = tcp_listen(SERVER_RTSP_PORT_DEFAULT);//以非阻塞方式侦听554端口,554 是rtsp的默认端口号;具体网络编程这一块的知识参考传送门。
int tcp_listen(unsigned short port)
{
int f;
int on=1;
struct sockaddr_in s;
int v = 1;
/*创建套接字*/
if((f = socket(AF_INET, SOCK_STREAM, 0))<0) //AF_INET ipv4 SOCK_STREAM 流模式,tcp
{
fprintf(stderr, "socket() error in tcp_listen.\n");
return -1;
}
/*设置socket的可选参数*/
setsockopt(f, SOL_SOCKET, SO_REUSEADDR, (char *) &v, sizeof(int));
s.sin_family = AF_INET;
s.sin_addr.s_addr = htonl(INADDR_ANY);
s.sin_port = htons(port);
/*绑定socket*/
if(bind(f, (struct sockaddr *)&s, sizeof(s)))
{
fprintf(stderr, "bind() error in tcp_listen");
return -1;
}
//设置为非阻塞方式
if(ioctl(f, FIONBIO, &on) < 0)
{
fprintf(stderr, "ioctl() error in tcp_listen.\n");
return -1;
}
/*监听*/
if(listen(f, SOMAXCONN) < 0)
{
fprintf(stderr, "listen() error in tcp_listen.\n");
return -1;
}
return f;
}
(4)int ScheduleInit()
接下来是便是调用ScheduleInit()函数,该函数对线程的结构体进行初始化,创建处理主线程schedule_do,schedule_do里调用sched[i].play_action把数据发送出去。ScheduleInit()函数是本文章分析的重点,下面来详细分析该函数。
int ScheduleInit()
{
int i;
pthread_t thread=0;
/*初始化数据*/
for(i=0; i<MAX_CONNECTION; ++i) //MAX_CONNECTION=10,最大允许连接10个客户端
{
sched[i].rtp_session=NULL;
sched[i].play_action=NULL;
sched[i].valid=0;
sched[i].BeginFrame=0;
}
/*创建处理主线程*/
pthread_create(&thread,NULL,schedule_do,NULL); //创建schedule_do线程
return 0;
}
schedule_do线程的源码如下:
void *schedule_do(void *arg)
{
int i=0;
struct ti