在上次的 RTSP 协议详解 中,把 RTSP 协议本身简单介绍了,这次就来说说如何实现一个简单的 RTSP 服务器。
Live555 Live555 是我们经常用的 C++ 媒体库,它支持非常多的媒体服务协议,实现了对多种音视频编码格式的音视频数据的数据流化、接收和处理等支持。
它也是现在为数不多的可用库之一,它的代码比较繁琐,但是胜在简单,多数人拿到之后就能简单上手了,因此也非常适合拿来练手,以及改写。
一个简单的 RTSP/H264 实现 我们拿 testProgs/testOnDemandRTSPServer.cpp
作为例子。
首先创建 RTSP 服务本身:
1 2 3 4 5 6 7 8 9 10 11 TaskScheduler* scheduler = BasicTaskScheduler::createNew (); env = BasicUsageEnvironment::createNew (*scheduler); UserAuthenticationDatabase* authDB = NULL ; # 这里如果不需要认证的话,可以去掉 authDB = new UserAuthenticationDatabase; authDB->addUserRecord ("username1" , "password1" ); # 监听 554 端口,标准的 RTSP 端口 RTSPServer* rtspServer = RTSPServer::createNew (*env, 554 , authDB);
然后添加 H264 文件作为视频源,如果不太理解,可以联想上面创建了 HTTP 服务器,而下面则在服务里面添加了 HTTP 路由资源的实现。
1 2 3 4 5 6 7 8 char const * streamName = "h264ESVideoTest" ;char const * inputFileName = "test.264" ;ServerMediaSession* sms = ServerMediaSession::createNew (*env, streamName, streamName, descriptionString); sms->addSubsession (H264VideoFileServerMediaSubsession ::createNew (*env, inputFileName, reuseFirstSource)); rtspServer->addServerMediaSession (sms);
基于摄像头的视频流 在这里,你需要注意到,Live555 的 RSTP 服务器是基于文件去做的,如果你的视频源不是一个静态的文件,那你就需要自己去实现 ServerMediaSubsession
了。
目前,我搜索到的方案主要有两种,一种方案是利用 Linux 的命名管道 fifo
来进行数据的传输,这样就可以在不实现 ServerMediaSubsession
的情况下直接使用。
具体就是用 Linux 的 mkfifo
命令,或者系统 API 调用创建一个管道,然后就可以使用文件的的 API 来进行读写(不得不赞叹 Linux 的精巧之处,一切皆文件)。
1 mkfifo [OPTION]... NAME...
然而,我没有进行测试,不过看各种论坛上的提问以及经验总结来看,这种方案虽然简洁,但是性能堪忧,并且会造成很大的延迟。
那么,另一种方案就呼之欲出了,其实新版本中,Live555 已经给出了解决方案的例子,就在 liveMedia/DeviceSource.cpp
中。
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 DeviceSource* DeviceSource::createNew (UsageEnvironment& env, DeviceParameters params) { return new DeviceSource (env, params); } EventTriggerId DeviceSource::eventTriggerId = 0 ; unsigned DeviceSource::referenceCount = 0 ;DeviceSource::DeviceSource (UsageEnvironment& env, DeviceParameters params) : FramedSource (env), fParams (params) { if (referenceCount == 0 ) { } ++referenceCount; envir ().taskScheduler ().turnOnBackgroundReadHandling (...); if (eventTriggerId == 0 ) { eventTriggerId = envir ().taskScheduler ().createEventTrigger (deliverFrame0); } } DeviceSource::~DeviceSource () { --referenceCount; if (referenceCount == 0 ) { envir ().taskScheduler ().deleteEventTrigger (eventTriggerId); eventTriggerId = 0 ; } } void DeviceSource::doGetNextFrame () { if (0 ) { handleClosure (); return ; } if (0 ) { deliverFrame (); } } void DeviceSource::deliverFrame0 (void * clientData) { ((DeviceSource*)clientData)->deliverFrame (); } void DeviceSource::deliverFrame () { if (!isCurrentlyAwaitingData ()) return ; u_int8_t * newFrameDataStart = (u_int8_t *)0xDEADBEEF ; unsigned newFrameSize = 0 ; if (newFrameSize > fMaxSize) { fFrameSize = fMaxSize; fNumTruncatedBytes = newFrameSize - fMaxSize; } else { fFrameSize = newFrameSize; } gettimeofday (&fPresentationTime, NULL ); memmove (fTo, newFrameDataStart, fFrameSize); FramedSource::afterGetting (this ); } void signalNewFrameData () { TaskScheduler* ourScheduler = NULL ; DeviceSource* ourDevice = NULL ; if (ourScheduler != NULL ) { ourScheduler->triggerEvent (DeviceSource::eventTriggerId, ourDevice); } }
目前我用这种方式,采用芯片硬编码,能获取在内网 1080P 30fps h.264 1000ms 以下的延迟。当然了,实验代码写得比较粗糙,需要进一步优化了,这里就不放出来了,相信总体思路还是对的。
Ref https://blog.csdn.net/xwu122930/article/details/78962234 https://blog.csdn.net/u012459903/article/details/103099705 https://blog.csdn.net/weixin_33700350/article/details/86010562
首发于 Github issues: https://github.com/xizhibei/blog/issues/156 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA) 进行许可
作者:习之北 (@xizhibei)
原链接:https://blog.xizhibei.me/zh-cn/2020/12/20/a-simple-implementation-of-rtsp-server/