为何标明“Windows版”,因为firehood大神已经实现了linux版:通过live555实现H264 RTSP直播

相关文章:

【1】Win7(Windows 7)下用VS2013(Visual Studio 2013)编译live555

【2】RTSP协议分析

【3】windows命名管道

一.基础

live555的学习基本上都是从E:\live555\testProgs中的testOnDemandRTSPServer.cpp示例开始的,这个例子实现了一个最简单的RTSP服务器。文件名中的“OnDemand”意思是:依指令行事,也就是说只有当客户端通过URL主动访问并发送相关指令时,该RTSP服务器才会将文件流化并推送到客户端。这个例子是基于RTP单播的,关于单播可以参考:Qt调用jrtplib实现单播、多播和广播

通过testOnDemandRTSPServer.cpp可以学习一个RTSP服务器的搭建步骤。这里新建一个名为h264LiveMediaServer的Win32控制台工程,新建并添加h264LiveMediaServer.cpp,然后将testOnDemandRTSPServer.cpp拷贝到h264LiveMediaServer.cpp,接着做少量修改,只保留与H.264会话相关的部分,如下所示:

[cpp] view plain copy
  1. #include "liveMedia.hh"
  2. #include "BasicUsageEnvironment.hh"
  3. UsageEnvironment* env;
  4. // True:后启动的客户端总是从当前第一个客户端已经播放到的位置开始播放
  5. // False:每个客户端都从头开始播放影视频文件
  6. Boolean reuseFirstSource = False;
  7. //该函数打印相关信息
  8. static void announceStream(RTSPServer* rtspServer, ServerMediaSession* sms,
  9. char const* streamName, char const* inputFileName);
  10. int main(int argc, char** argv)
  11. {
  12. //创建任务调度器并初始化使用环境
  13. TaskScheduler* scheduler = BasicTaskScheduler::createNew();
  14. env = BasicUsageEnvironment::createNew(*scheduler);
  15. UserAuthenticationDatabase* authDB = NULL;
  16. //创建RTSP服务器,开始监听模客户端的连接
  17. //注意这里的端口号不是默认的554端口,因此访问URL时,需指定该端口号
  18. RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554, authDB);
  19. if (rtspServer == NULL)
  20. {
  21. *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
  22. exit(1);
  23. }
  24. char const* descriptionString
  25. = "Session streamed by \"h264LiveMediaServer\"";
  26. //流名字,媒体名
  27. char const* streamName = "h264ESVideoTest";
  28. //文件名,当客户端输入的流名字为h264ESVideoTest时,实际上打开的是test.264文件。
  29. //这里需要特别注意一点,当通过IDE运行h264LiveMediaServer时,live555推送的是项目工作目录中的视频或音频。工作目录也就是和*.vcxproj同级的目录,
  30. //此时视频应该放在这个目录下。当双击h264LiveMediaServer.exe运行时,视频理所当然的和h264LiveMediaServer.exe放在一个目录。
  31. char const* inputFileName = "480320.264";
  32. //当客户点播时,要输入流名字streamName,告诉RTSP服务器点播的是哪个流。
  33. //创建媒体会话,流名字和文件名的对应关系是通过增加子会话建立起来的。媒体会话对会话描述、会话持续时间、流名字等与会话有关的信息进行管理。
  34. //第2个参数:媒体名、3:媒体信息、4:媒体描述
  35. ServerMediaSession* sms= ServerMediaSession::createNew(*env, streamName, streamName,descriptionString);
  36. //添加264子会话 这里的文件名才是真正要打开文件的名字
  37. //H264VideoFileServerMediaSubsession类派生自FileServerMediaSubsession派生自OnDemandServerMediaSubsession
  38. //而OnDemandServerMediaSubsession和PassiveMediaSubsession共同派生自ServerMediaSubsession
  39. //关于读取文件之类都在这个类中实现的,如果要将点播改为直播就是要新建类继承此类然后添加新的方法
  40. sms->addSubsession(H264VideoFileServerMediaSubsession::createNew(*env, inputFileName, reuseFirstSource));
  41. //为rtspserver添加session
  42. rtspServer->addServerMediaSession(sms);
  43. //答应信息到标准输出
  44. announceStream(rtspServer, sms, streamName, inputFileName);
  45. //试图为RTSP-over-HTTP通道创建一个HTTP服务器.
  46. if (rtspServer->setUpTunnelingOverHTTP(80) || rtspServer->setUpTunnelingOverHTTP(8000) || rtspServer->setUpTunnelingOverHTTP(8080))
  47. {
  48. *env << "\n(We use port " << rtspServer->httpServerPortNum() << " for optional RTSP-over-HTTP tunneling.)\n";
  49. }
  50. else
  51. {
  52. *env << "\n(RTSP-over-HTTP tunneling is not available.)\n";
  53. }
  54. //进入事件循环,对套接字的读取事件和对媒体文件的延时发送操作都在这个循环中完成。
  55. env->taskScheduler().doEventLoop();
  56. return 0;
  57. }
  58. static void announceStream(RTSPServer* rtspServer, ServerMediaSession* sms,
  59. char const* streamName, char const* inputFileName) {
  60. char* url = rtspServer->rtspURL(sms);
  61. UsageEnvironment& env = rtspServer->envir();
  62. env << "\n\"" << streamName << "\" stream, from the file \""
  63. << inputFileName << "\"\n";
  64. env << "Play this stream using the URL \"" << url << "\"\n";
  65. delete[] url;
  66. }

如何测试可参考【1】,测试结果如下所示:


二.实现

在通过live555实现H264 RTSP直播中,博主是通过FIFO队列实现的,FIFO队列实际上是Linux下的命名管道,而Windows下也有命名管道,因此在Windows中的流程图如下所示:

关于Windows命名管道详见【3】。

这里不使用命名管道来实现,而是直接读取本地H264文件,分解成StartCode+NALU内存块,然后拷贝到Live555 Server。这样一来,就很容易改成命名管道的形式,命名管道的客户端只需读取本地H264文件,分解成StartCode(0x000001或0x00000001)+NALU内存块,并写入管道,命名管道服务器端(在Live555 Server中)读取管道数据,并拷贝到Live555 Server。

通过“基础”中的分析可以得出,想实现自定义服务器,需要将sms->addSubsession(H264VideoFileServerMediaSubsession::createNew(*env, inputFileName,reuseFirstSource)),中的H264VideoFileServerMediaSubsession替换成自己的子会话。H264VideoFileServerMediaSubsession类在其createNewStreamSource(unsigned /*clientSessionId*/, unsigned& estBitrate)函数中调用了ByteStreamFileSource::createNew(envir(), fFileName),而frame的获取正是在ByteStreamFileSource类中的doGetNextFrame()函数中实现的。因此,这里需要继承H264VideoFileServerMediaSubsession和ByteStreamFileSource类,并重写其中的createNewStreamSource和doGetNextFrame函数。

代码如下所示:

h264LiveFramedSource.hh

[cpp] view plain copy
  1. #ifndef _H264LIVEFRAMEDSOURCE_HH
  2. #define _H264LIVEFRAMEDSOURCE_HH
  3. #include <ByteStreamFileSource.hh>
  4. class H264LiveFramedSource : public ByteStreamFileSource
  5. {
  6. public:
  7. static H264LiveFramedSource* createNew(UsageEnvironment& env, unsigned preferredFrameSize = 0, unsigned playTimePerFrame = 0);
  8. protected:
  9. H264LiveFramedSource(UsageEnvironment& env, unsigned preferredFrameSize, unsigned playTimePerFrame);
  10. ~H264LiveFramedSource();
  11. private:
  12. //重定义虚函数
  13. virtual void doGetNextFrame();
  14. };
  15. #endif

h264LiveFramedSource.cpp

[cpp] view plain copy
  1. #include "h264LiveFramedSource.hh"
  2. #include "GroupsockHelper.hh"
  3. #include "spsdecode.h"
  4. int findStartCode(unsigned char *buf, int zeros_in_startcode)
  5. {
  6. int info;
  7. int i;
  8. info = 1;
  9. for (i = 0; i < zeros_in_startcode; i++)
  10. if (buf[i] != 0)
  11. info = 0;
  12. if (buf[i] != 1)
  13. info = 0;
  14. return info;
  15. }
  16. //此处的NALU包括StartCode
  17. int getNextNalu(FILE* inpf, unsigned char* buf)
  18. {
  19. int pos = 0;
  20. int startCodeFound = 0;
  21. int info2 = 0;
  22. int info3 = 0;
  23. while (!feof(inpf) && (buf[pos++] = fgetc(inpf)) == 0);
  24. while (!startCodeFound)
  25. {
  26. if (feof(inpf))
  27. {
  28. return pos - 1;
  29. }
  30. buf[pos++] = fgetc(inpf);
  31. info3 = findStartCode(&buf[pos - 4], 3);
  32. startCodeFound=(info3 == 1);
  33. if (info3 != 1)
  34. info2 = findStartCode(&buf[pos - 3], 2);
  35. startCodeFound = (info2 == 1 || info3 == 1);
  36. }
  37. if (info2)
  38. {
  39. fseek(inpf, -3, SEEK_CUR);
  40. return pos - 3;
  41. }
  42. if (info3)
  43. {
  44. fseek(inpf, -4, SEEK_CUR);
  45. return pos - 4;
  46. }
  47. }
  48. FILE * inpf;
  49. unsigned char* inBuf;
  50. int inLen;
  51. int nFrameRate;
  52. H264LiveFramedSource::H264LiveFramedSource(UsageEnvironment& env, unsigned preferredFrameSize, unsigned playTimePerFrame)
  53. : ByteStreamFileSource(env, 0, preferredFrameSize, playTimePerFrame)
  54. {
  55. char *fname = "480320.264";
  56. inpf = NULL;
  57. inpf = fopen(fname, "rb");
  58. inBuf = (unsigned char*)calloc(1024 * 100, sizeof(char));
  59. inLen = 0;
  60. inLen = getNextNalu(inpf, inBuf);
  61. // 读取SPS帧
  62. unsigned int nSpsLen = inLen - 4;
  63. unsigned char *pSps = (unsigned char*)malloc(nSpsLen);
  64. memcpy(pSps, inBuf + 4, nSpsLen);
  65. // 解码SPS,获取视频图像宽、高信息
  66. int width = 0, height = 0, fps = 0;
  67. h264_decode_sps(pSps, nSpsLen, width, height, fps);
  68. nFrameRate = 0;
  69. if (fps)
  70. nFrameRate = fps;
  71. else
  72. nFrameRate = 25;
  73. }
  74. H264LiveFramedSource* H264LiveFramedSource::createNew(UsageEnvironment& env, unsigned preferredFrameSize, unsigned playTimePerFrame)
  75. {
  76. H264LiveFramedSource* newSource = new H264LiveFramedSource(env, preferredFrameSize, playTimePerFrame);
  77. return newSource;
  78. }
  79. H264LiveFramedSource::~H264LiveFramedSource()
  80. {
  81. free(inBuf);
  82. fclose(inpf);
  83. }
  84. // This function is called when new frame data is available from the device.
  85. // We deliver this data by copying it to the 'downstream' object, using the following parameters (class members):
  86. // 'in' parameters (these should *not* be modified by this function):
  87. //     fTo: The frame data is copied to this address.
  88. //         (Note that the variable "fTo" is *not* modified.  Instead,
  89. //          the frame data is copied to the address pointed to by "fTo".)
  90. //     fMaxSize: This is the maximum number of bytes that can be copied
  91. //         (If the actual frame is larger than this, then it should
  92. //          be truncated, and "fNumTruncatedBytes" set accordingly.)
  93. // 'out' parameters (these are modified by this function):
  94. //     fFrameSize: Should be set to the delivered frame size (<= fMaxSize).
  95. //     fNumTruncatedBytes: Should be set iff the delivered frame would have been
  96. //         bigger than "fMaxSize", in which case it's set to the number of bytes
  97. //         that have been omitted.
  98. //     fPresentationTime: Should be set to the frame's presentation time
  99. //         (seconds, microseconds).  This time must be aligned with 'wall-clock time' - i.e., the time that you would get
  100. //         by calling "gettimeofday()".
  101. //     fDurationInMicroseconds: Should be set to the frame's duration, if known.
  102. //         If, however, the device is a 'live source' (e.g., encoded from a camera or microphone), then we probably don't need
  103. //         to set this variable, because - in this case - data will never arrive 'early'.
  104. void H264LiveFramedSource::doGetNextFrame()
  105. {
  106. fFrameSize = inLen;
  107. if (fFrameSize > fMaxSize)
  108. {
  109. fNumTruncatedBytes = fFrameSize - fMaxSize;
  110. fFrameSize = fMaxSize;
  111. }
  112. else
  113. {
  114. fNumTruncatedBytes = 0;
  115. }
  116. memmove(fTo, inBuf, fFrameSize);
  117. inLen = 0;
  118. inLen = getNextNalu(inpf, inBuf);
  119. gettimeofday(&fPresentationTime, NULL);//时间戳
  120. fDurationInMicroseconds = 1000000 / nFrameRate;//控制播放速度
  121. //表示延迟0秒后再执行afterGetting函数,也可以直接用afterGetting(this)
  122. nextTask() = envir().taskScheduler().scheduleDelayedTask(0, (TaskFunc*)FramedSource::afterGetting, this);
  123. }

h264LiveVideoServerMediaSubssion.hh

[cpp] view plain copy
  1. #ifndef _H264LIVEVIDEOSERVERMEDIASUBSSION_HH
  2. #define _H264LIVEVIDEOSERVERMEDIASUBSSION_HH
  3. #include "H264VideoFileServerMediaSubsession.hh"
  4. class H264LiveVideoServerMediaSubssion : public H264VideoFileServerMediaSubsession {
  5. public:
  6. static H264LiveVideoServerMediaSubssion* createNew(UsageEnvironment& env, Boolean reuseFirstSource);
  7. protected:
  8. H264LiveVideoServerMediaSubssion(UsageEnvironment& env, Boolean reuseFirstSource);
  9. ~H264LiveVideoServerMediaSubssion();
  10. protected:
  11. //重定义虚函数
  12. FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate);
  13. };
  14. #endif

h264LiveVideoServerMediaSubssion.cpp

[cpp] view plain copy
  1. #include "h264LiveVideoServerMediaSubssion.hh"
  2. #include "h264LiveFramedSource.hh"
  3. #include "H264VideoStreamFramer.hh"
  4. H264LiveVideoServerMediaSubssion* H264LiveVideoServerMediaSubssion::createNew(UsageEnvironment& env, Boolean reuseFirstSource)
  5. {
  6. return new H264LiveVideoServerMediaSubssion(env, reuseFirstSource);
  7. }
  8. H264LiveVideoServerMediaSubssion::H264LiveVideoServerMediaSubssion(UsageEnvironment& env, Boolean reuseFirstSource)
  9. : H264VideoFileServerMediaSubsession(env, 0, reuseFirstSource)
  10. {
  11. }
  12. H264LiveVideoServerMediaSubssion::~H264LiveVideoServerMediaSubssion()
  13. {
  14. }
  15. FramedSource* H264LiveVideoServerMediaSubssion::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate)
  16. {
  17. //estimate bitrate:估计的比特率,记得根据需求修改
  18. estBitrate = 1000; // kbps
  19. //创建视频源
  20. H264LiveFramedSource* liveSource = H264LiveFramedSource::createNew(envir());
  21. if (liveSource == NULL)
  22. {
  23. return NULL;
  24. }
  25. //为视频流创建Framer
  26. return H264VideoStreamFramer::createNew(envir(), liveSource);
  27. }

还需在h264LiveMediaServer.cpp中做相应的修改

[cpp] view plain copy
  1. #include "liveMedia.hh"
  2. #include "BasicUsageEnvironment.hh"
  3. #include "h264LiveVideoServerMediaSubssion.hh"
  4. UsageEnvironment* env;
  5. // True:后启动的客户端总是从当前第一个客户端已经播放到的位置开始播放
  6. // False:每个客户端都从头开始播放影视频文件
  7. Boolean reuseFirstSource = True;
  8. //该函数打印相关信息
  9. static void announceStream(RTSPServer* rtspServer, ServerMediaSession* sms, char const* streamName);
  10. int main(int argc, char** argv)
  11. {
  12. //创建任务调度器并初始化使用环境
  13. TaskScheduler* scheduler = BasicTaskScheduler::createNew();
  14. env = BasicUsageEnvironment::createNew(*scheduler);
  15. UserAuthenticationDatabase* authDB = NULL;
  16. //创建RTSP服务器,开始监听模客户端的连接
  17. //注意这里的端口号不是默认的554端口,因此访问URL时,需指定该端口号
  18. RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554, authDB);
  19. if (rtspServer == NULL)
  20. {
  21. *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
  22. exit(1);
  23. }
  24. char const* descriptionString = "Session streamed by \"h264LiveMediaServer\"";
  25. //流名字,媒体名
  26. char const* streamName = "h264ESVideoTest";
  27. //当客户点播时,要输入流名字streamName,告诉RTSP服务器点播的是哪个流。
  28. //创建媒体会话,流名字和文件名的对应关系是通过增加子会话建立起来的。媒体会话对会话描述、会话持续时间、流名字等与会话有关的信息进行管理。
  29. //第2个参数:媒体名、3:媒体信息、4:媒体描述
  30. ServerMediaSession* sms= ServerMediaSession::createNew(*env, streamName, streamName ,descriptionString);
  31. //修改为自己实现的H264LiveVideoServerMediaSubssion
  32. sms->addSubsession(H264LiveVideoServerMediaSubssion::createNew(*env, reuseFirstSource));
  33. //为rtspserver添加session
  34. rtspServer->addServerMediaSession(sms);
  35. //答应信息到标准输出
  36. announceStream(rtspServer, sms, streamName);
  37. //进入事件循环,对套接字的读取事件和对媒体文件的延时发送操作都在这个循环中完成。
  38. env->taskScheduler().doEventLoop();
  39. return 0;
  40. }
  41. static void announceStream(RTSPServer* rtspServer, ServerMediaSession* sms,char const* streamName)
  42. {
  43. char* url = rtspServer->rtspURL(sms);
  44. UsageEnvironment& env = rtspServer->envir();
  45. env << "\n\"" << streamName << "\" stream\"\n";
  46. env << "Play this stream using the URL \"" << url << "\"\n";
  47. delete[] url;
  48. }

关于spsdecode.h,详见: H.264(H264)解码SPS获取分辨率和帧率

三.测试

参考链接:http://blog.csdn.net/firehood_/article/details/16844397

通过live555实现H264 RTSP直播(Windows版)相关推荐

  1. 通过live555实现H264 RTSP直播

    转载自:http://blog.csdn.net/firehood_/article/details/16844397 前面的文章中介绍了<H264视频通过RTMP流直播>,下面将介绍一下 ...

  2. live555 android 直播,通过live555实现H264 RTSP直播

    前面的文章中介绍了<H264视频通过RTMP流直播>,下面将介绍一下如何将H264实时视频通过RTSP直播. 实现思路是将视频流发送给live555, 由live555来实现H264数据流 ...

  3. LIVE555再学习 -- live555实现RTSP直播服务器 分析

    上一篇文章 讲到了 live555实现RTSP直播服务器,但是篇幅有点长,没有来得及对源码进行分析. 这篇文章就好好看看,源码部分这次参看Linux版本下的 通过live555实现H264 RTSP直 ...

  4. LIVE555再学习 -- live555实现RTSP直播服务器

    分析完 testOnDemandRTSPServer 和 testH264VideoStreamer 的源码.我们现在就可以做相关的项目工程. 我之前写过一个,参看: DM368开发 -- 编码并实时 ...

  5. Windows平台真实时毫秒级4K H264/H265直播技术方案探讨

    背景 在刚提出4K视频的时候,大多数人都觉得没有必要,4K的出现,意味着更高的硬件规格和传输要求,1080P看的很爽.很清晰,完全满足了日常的需求.随着电视的尺寸越来越大,原本1080P成像已经无法满 ...

  6. Windows平台RTMP/RTSP直播推送模块设计和使用说明

    开发背景 好多开发者一直反馈,Windows平台,做个推屏或者推摄像头,推RTMP或者RTSP出去,不知道哪些功能是必须的,哪些设计是可有可无的,还有就是,不知道如何选技术方案,以下是基于我们设计的W ...

  7. flv + livego + obs 实现简易直播平台(windows版+Linux版)

    直播运行效果: Windows版 1. 前端编写(网页播放视频功能) flvjs是b站开源出来的前端流媒体播放器 下载链接:https://github.com/saysmy/flvjs-pr354 ...

  8. [live555]rtsp直播基于live555的实现

    一直很想做流媒体的直播,最近花时间看了有关live555的有关代码,这里隆重的推荐两篇: http://blog.csdn.net/nkmnkm (道长的文章,分析的很不错) http://blog. ...

  9. 麒麟操作系统|Linux下低延时RTMP|RTSP直播播放实现

    背景 国产操作系统多为以Linux为基础二次开发的操作系统.2014年4月8日起,美国微软公司停止了对Windows XP SP3操作系统提供服务支持,这引起了社会和广大用户的广泛关注和对信息安全的担 ...

最新文章

  1. 你知道Java的四种引用类型吗?
  2. pandas.dataframe用法总结 何时返回dataframe 何时返回series
  3. Eclipse Class Decompiler---Java反编译插件
  4. Java数据结构和算法:哈夫曼树
  5. SAP财务报表不平之分析
  6. JZOJ 4910. 【NOIP2017模拟12.3】子串
  7. win8.1 php mysql,win8.1(64位) apache2.4.3+php5.6.3+mysql5.6安装
  8. 剑指 offer 编程题 C++ 版总结(下)
  9. 吴恩达机器学习笔记十三之推荐系统
  10. 主要的css hack
  11. python telnet登录发送命令_Telnet发送命令,然后读取响应
  12. linux x64系统android开发环境搭建
  13. 【vedio】html 视频有声音没有图像
  14. (详细)华为荣耀3C H30-TL00的Usb调试模式在哪里开启的步骤
  15. xlistview的使用
  16. 如何评价一个新技术——以Docker为例
  17. The word ‘localhost‘ is not correctly spelled 这个问题怎么解决
  18. OAException Application FND Message Name FND NO DATABASE
  19. 【转】MT7688学习笔记(6)——OpenWrt下串口编程
  20. 轮滑运动相关html网页,轮滑运动进校园

热门文章

  1. matlab 单元,MATLAB单元阵列
  2. git 分支合并_教你玩转Git-分支合并
  3. 点击php文件显示下载文件,求助 为什么编的下载文件代码,打开后下的全是php文件...
  4. C语言实验——最小公倍数和最大公约数_JAVA
  5. faster rcnn可视化(修改demo.py保存网络中间结果)
  6. Java 7中的Try-with-resources
  7. 聊一聊log4j2配置文件log4j2.xml
  8. 《JAVA与模式》之策略模式
  9. 图解JVM垃圾回收算法
  10. 用WinEdt打开.tex文件显示error reading错误