基本RTSP服务器

对于一个基本的RTSP服务器而言,只需要定义好封装的包装格式,建立socket链接,定义RTSP询问、交互格式(SDP)即可。在socket链接部分,有两种方法,一种是基于TCP一种是基于UDP,在阻塞情况下,只需要在服务器端做轮询操作即可。实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
WSADATA wsaData;
//启动socket
/*
Param1:MAKEWORD 请求的winsock版本
Param2:指向WSADATA的指针
*/
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("PC Server Socket Start Up Error \n");
return -1;
}

int serverSockfd;
/*
创建socket,这里由于我做的是RTSP服务器,所以做了封装,
但基本函数就是 SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
*/
serverSockfd = createTcpSocket();
if (serverSockfd < 0)
{
WSACleanup();
printf("failed to create tcp socket\n");
return -1;
}
//bind
if (bindSocketAddr(serverSockfd, "0.0.0.0", SERVER_PORT) < 0)
{
printf("failed to bind addr\n");
return -1;
}
//listen
if (listen(serverSockfd, 10) < 0)
{
printf("failed to listen\n");
return -1;
}

printf("%s rtsp://127.0.0.1:%d\n",__FILE__, SERVER_PORT);
//循环接收
while (true) {
int clientSockfd;
char clientIp[40];
int clientPort;

clientSockfd = acceptClient(serverSockfd, clientIp, &clientPort);
if (clientSockfd < 0)
{
printf("failed to accept client\n");
return -1;
}

printf("accept client;client ip:%s,client port:%d\n", clientIp, clientPort);

doClient(clientSockfd, clientIp, clientPort);
}
//注意释放顺序
closesocket(serverSockfd);
WSACleanup();
return 0;

在上面的基本RTSP服务器例子中,核心是doclient,也就是如何处理接收到的RTSP命令。核心是拆分行,并与协议做比对
这里不再赘述。

高性能RTSP服务器

何所谓“高性能”

  • 在读取文件方面就需要定义一个线程池
  • 实现快速重启,结束后快速释放端口
  • 定义IO时间,创建只读的回调函数
  • 阻塞很少,绝大部分都是回调函数
  • 可以支持epoll poller select
  • 设置了定时器,所有的调度都是基于定时器实现的。对于不同的线程,定时器都是独立的,需要设置一个定时器的定时器,保证同步,在WIN系统中,定时器回调由子线程托管,非win系统通过select网络模型。

main函数流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int main() {

srand(time(NULL));//时间初始化
/*
createNew->EventScheduler
进行socket初始化
开启select网络模型
创建时间管理器,并开启下面调用
TimerManager->TimerManager::readCallback->TimerManager::handleRead
在handle内部对定时器是否超时以及超时后是否停止,做出处理。使用timer.handleEvent()来判断是否停止
Timer::handleEvent()-> TimerEvent::handleEvent()-> TimerEvent::mTimeoutCallback(mArg);
在其中的
*/
EventScheduler* scheduler = EventScheduler::createNew(EventScheduler::POLLER_SELECT);// 创建事件调度器,使用select作为IO多路复用模型

ThreadPool* threadPool = ThreadPool::createNew(1);//
MediaSessionManager* sessMgr = MediaSessionManager::createNew();// 创建会话管理器
UsageEnvironment* env = UsageEnvironment::createNew(scheduler, threadPool);//将线程池和时间调度器打包成环境

Ipv4Address rtspAddr("127.0.0.1", 8554);
RtspServer* rtspServer = RtspServer::createNew(env, sessMgr, rtspAddr);//环境、会话管理器、rtsp地址创建,rtsp服务器

LOGI("----------session init start------");
{
MediaSession* session = MediaSession::createNew("test");
MediaSource* source = H264FileMediaSource::createNew(env, "D:/code/C++/RTSP_server/data/video4.h264");
Sink* sink = H264FileSink::createNew(env, source);
session->addSink(MediaSession::TrackId0, sink);

source = AACFileMeidaSource::createNew(env, "D:/code/C++/RTSP_server/data/video5.aac");
sink = AACFileSink::createNew(env, source);
session->addSink(MediaSession::TrackId1, sink);

sessMgr->addSession(session);
}
LOGI("----------session init end------");


rtspServer->start();

env->scheduler()->loop();
return 0;

”流“部分模块模块

AAC音频与H264视频发送

在视频与音频的发送都定义成了两个部分,其一是source,其二是sink。前者用于将文件转换成码流,后者用于封装发送

在AAC结构体构造的时候就会指定打包好的环境,并且封装好handletask函数等待回调getFrameFromAACFile(uint8_t* buf, int size)parseAdtsHeader(uint8_t* in, struct AdtsHeader* res)

sink过程中需要知道帧率,并按照帧率对音频进行发送。

视频发送基本流程是和音频差不多的,这里不介绍了

session发送

session同样分成两个部分,一部分用于生成session,即将视频和音频组成一路track。另一部分是session的管理类。

在生成session部分,有组合流,生成交互SDP,生成RtpInstance,以及设置发送包的回调函数。并配置了多播地址的随机生成

管理session部分,分成添加session 移除session 和获得session

”事件管理“部分模块

事件类别

  • IOEvent
  • TriggerEvent
  • TimerEvent

timermanager

select框架


本站由 Edison.Chen 创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

undefined