随笔-101  评论-133  文章-4  trackbacks-0
  2019年3月15日
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/DeliaPu/article/details/79199023

RTSP(Real-Time Stream Protocol)协议是一个基于文本的多媒体播放控制协议,属于应用层。RTSP以客户端方式工作,对流媒体提供播放、暂停、后退、前进等操作。该标准由IETF指定,对应的协议是RFC2326。

RTSP作为一个应用层协议,提供了一个可供扩展的框架,使得流媒体的受控和点播变得可能,它主要用来控制具有实时特性的数据的发送,但其本身并不用于传送流媒体数据,而必须依赖下层传输协议(如RTP/RTCP)所提供的服务来完成流媒体数据的传送。RTSP负责定义具体的控制信息、操作方法、状态码,以及描述与RTP之间的交互操作。RTSP媒体服务协议框架如下:


RTSP包含Normal RTSP(数据通过RTP传输,应用厂商有苹果和微软等),以及Real-RTSP(数据通过RDT传输)。本篇我们主要讲Normal RTSP。

RTSP传输的一般是TS、MP4格式的流,其传输一般需要2~3个通道,命令和数据通道分离。使用RTSP协议传输流媒体数据需要有专门的媒体播放器和媒体服务器,也就是需要支持RTSP协议的客户端和服务器。


客户端要播放RTSP媒体流,就需要知道媒体源的URL,RTSP的URL格式一般如下:

rtsp://host[:port]/[abs_path]/content_name

host: 有效的域名或IP地址;

port: 端口号,缺省为554,若为缺省可不填写,否则必须写明。

例如,一个完整的RTSP URL可写为:

rtsp://192.168.1.67:554/test

又如目前市面上常用的海康网络摄像头的RTSP地址格式为:

rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream

示例: rtsp://admin:12345@192.168.1.67:554/h264/ch1/main/av_stream

rtsp://admin:12345@192.168.1.67/mpeg4/ch1/sub/av_stream


RTSP报文

对RTSP协议的使用有了一个大概的了解之后,我们来看一下RTSP报文结构。

RTSP是一种基于文本的协议,用CRLF(回车换行)作为每一行的结束符,其好处是,在使用过程中可以方便地增加自定义参数,也方便抓包分析。从消息传送方向上来分,RTSP的报文有两类:请求报文和响应报文。请求报文是指从客户端向服务器发送的请求(也有少量从服务器向客户端发送的请求),响应报文是指从服务器到客户端的回应。

RTSP请求报文的常用方法与作用:


一次基本的RTSP交互过程如下,C表示客户端,S表示服务端。


首先客户端连接到流媒体服务器并发送一个RTSP描述请求(DESCRIBE request),服务器通过一个SDP(Session DescriptionProtocol)描述来进行反馈(DESCRIBEresponse),反馈信息包括流数量、媒体类型等信息。客户端分析该SDP描述,并为会话中的每一个流发送一个RTSP连接建立请求(SETUPrequest),该命令会告诉服务器用于接收媒体数据的端口,服务器响应该请求(SETUP response)并建立连接之后,就开始传送媒体流(RTP包)到客户端。在播放过程中客户端还可以向服务器发送请求来控制快进、快退和暂停等。最后,客户端可发送一个终止请求(TEARDOWN request)来结束流媒体会话。

下面我们通过具体的消息实例来进一步了解一下RTSP的工作过程:

  • OPTIONS

OPTIONS请求是客户端向服务器询问可用的方法,请求和回复实例如下:

  1. C->S: OPTIONS rtsp://example.com/media.mp4 RTSP/1.0
  2. CSeq: 1
  3. Require: implicit-play
  4. Proxy-Require: gzipped-messages
  5. S->C: RTSP/1.0 200 OK
  6. CSeq: 1
  7. Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE
  • DESCRIBE

客户端向服务器请求媒体资源描述,服务器端通过SDP(Session Description Protocol)格式回应客户端的请求。资源描述中会列出所请求媒体的媒体流及其相关信息,典型情况下,音频和视频分别作为一个媒体流传输。实例如下:

  1. C->S: DESCRIBE rtsp://example.com/media.mp4 RTSP/1.0
  2. CSeq: 2
  3. S->C: RTSP/1.0 200 OK
  4. CSeq: 2
  5. Content-Base: rtsp://example.com/media.mp4
  6. Content-Type: application/sdp
  7. Content-Length: 460
  8. m=video 0 RTP/AVP 96
  9. a=control:streamid=0
  10. a=range:npt=0-7.741000
  11. a=length:npt=7.741000
  12. a=rtpmap:96 MP4V-ES/5544
  13. a=mimetype:string;"video/MP4V-ES"
  14. a=AvgBitRate:integer;304018
  15. a=StreamName:string;"hinted video track"
  16. m=audio 0 RTP/AVP 97
  17. a=control:streamid=1
  18. a=range:npt=0-7.712000
  19. a=length:npt=7.712000
  20. a=rtpmap:97 mpeg4-generic/32000/2
  21. a=mimetype:string;"audio/mpeg4-generic"
  22. a=AvgBitRate:integer;65790
  23. a=StreamName:string;"hinted audio track"
  • SETUP

SETUP请求确定了具体的媒体流如何传输,该请求必须在PLAY请求之前发送。SETUP请求包含媒体流的URL和客户端用于接收RTP数据(audio or video)的端口以及接收RTCP数据(meta information)的端口。服务器端的回复通常包含客户端请求参数的确认,并会补充缺失的部分,比如服务器选择的发送端口。每一个媒体流在发送PLAY请求之前,都要首先通过SETUP请求来进行相应的配置。

  1. C->S: SETUP rtsp://example.com/media.mp4/streamid=0 RTSP/1.0
  2. CSeq: 3
  3. Transport: RTP/AVP;unicast;client_port=8000-8001
  4. S->C: RTSP/1.0 200 OK
  5. CSeq: 3
  6. Transport: RTP/AVP;unicast;client_port=8000-8001;server_port=9000-9001;ssrc=1234ABCD
  7. Session: 12345678
  • PLAY

客户端通过PLAY请求来播放一个或全部媒体流,PLAY请求可以发送一次或多次,发送一次时,URL为包含所有媒体流的地址,发送多次时,每一次请求携带的URL只包含一个相应的媒体流。PLAY请求中可指定播放的range,若未指定,则从媒体流的开始播放到结束,如果媒体流在播放过程中被暂停,则可在暂停处重新启动流的播放。

  1. C->S: PLAY rtsp://example.com/media.mp4 RTSP/1.0
  2. CSeq: 4
  3. Range: npt=5-20
  4. Session: 12345678
  5. S->C: RTSP/1.0 200 OK
  6. CSeq: 4
  7. Session: 12345678
  8. RTP-Info: url=rtsp://example.com/media.mp4/streamid=0;seq=9810092;rtptime=3450012
  • PAUSE

PAUSE请求会暂停一个或所有媒体流,后续可通过PLAY请求恢复播放。PAUSE请求中携带所请求媒体流的URL,若参数range存在,则指明在何处暂停,若该参数不存在,则暂停立即生效,且暂停时长不确定。

  1. C->S: PAUSE rtsp://example.com/media.mp4 RTSP/1.0
  2. CSeq: 5
  3. Session: 12345678
  4. S->C: RTSP/1.0 200 OK
  5. CSeq: 5
  6. Session: 12345678
  • TEARDOWN
结束会话请求,该请求会停止所有媒体流,并释放服务器上的相关会话数据。
  1. C->S: TEARDOWN rtsp://example.com/media.mp4 RTSP/1.0
  2. CSeq: 8
  3. Session: 12345678
  4. S->C: RTSP/1.0 200 OK
  5. CSeq: 8
  • GET_PARAMETER
检索指定URI数据中的参数值。不携带消息体的GET_PARAMETER可用来测试服务器端或客户端是否可通(类似ping的功能)。
  1. S->C: GET_PARAMETER rtsp://example.com/media.mp4 RTSP/1.0
  2. CSeq: 9
  3. Content-Type: text/parameters
  4. Session: 12345678
  5. Content-Length: 15
  6. packets_received
  7. jitter
  8. C->S: RTSP/1.0 200 OK
  9. CSeq: 9
  10. Content-Length: 46
  11. Content-Type: text/parameters
  12. packets_received: 10
  13. jitter: 0.3838
  • SET_PARAMETER

用于设置指定媒体流的参数。

  1. C->S: SET_PARAMETER rtsp://example.com/media.mp4 RTSP/1.0
  2. CSeq: 10
  3. Content-length: 20
  4. Content-type: text/parameters
  5. barparam: barstuff
  6. S->C: RTSP/1.0 451 Invalid Parameter
  7. CSeq: 10
  8. Content-length: 10
  9. Content-type: text/parameters
  10. barparam
  • REDIRECT

重定向请求,用于服务器通知客户端新的服务地址,客户端需要向这个新地址重新发起请求。重定向请求中可能包含Range参数,指明重定向生效的时间。客户端若需向新服务地址发起请求,必须先teardown当前会话,再向指定的新主机setup一个新的会话。

  1. S->C: REDIRECT rtsp://example.com/media.mp4 RTSP/1.0
  2. CSeq: 11
  3. Location: rtsp://bigserver.com:8001
  4. Range: clock=19960213T143205Z-
  • ANNOUNCE

ANNOUNCE请求有两个用途:(1)C->S:客户端向服务器端发布URL指定的媒体信息描述;(2) S->C:实时更新对话描述。若媒体表示中新增了一个媒体流,例如在直播过程中,则整个媒体表示的description都要被重新发送,而不是只发送新增部分。

  1. C->S: ANNOUNCE rtsp://example.com/media.mp4 RTSP/1.0
  2. CSeq: 7
  3. Date: 23 Jan 1997 15:35:06 GMT
  4. Session: 12345678
  5. Content-Type: application/sdp
  6. Content-Length: 332
  7. v=0
  8. o=mhandley 2890844526 2890845468 IN IP4 126.16.64.4
  9. s=SDP Seminar
  10. i=A Seminar on the session description protocol
  11. u=http://www.cs.ucl.ac.uk/staff/M.Handley/sdp.03.ps
  12. e=mjh@isi.edu (Mark Handley)
  13. c=IN IP4 224.2.17.12/127
  14. t=2873397496 2873404696
  15. a=recvonly
  16. m=audio 3456 RTP/AVP 0
  17. m=video 2232 RTP/AVP 31
  18. S->C: RTSP/1.0 200 OK
  19. CSeq: 7
  • RECORD

请求录制指定范围的媒体数据,请求中可指定录制的起止时间戳;若未指定时间范围,则使用presentation description中的开始和结束时间,这种情况下,如果会话已开始,则立即启动录制操作。

  1. C->S: RECORD rtsp://example.com/media.mp4 RTSP/1.0
  2. CSeq: 6
  3. Session: 12345678
  4. S->C: RTSP/1.0 200 OK
  5. CSeq: 6
  6. Session: 12345678
以上就是RTSP中常用的命令及其实例介绍。最后,来看一段实际使用的RTSP命令交互过程,该过程是通过PC对海康摄像头视频流的拉取和播放,并通过Wireshark抓取客户端的数据得到的:
  1. OPTIONS rtsp://10.3.8.202:554 RTSP/1.0
  2. CSeq: 2
  3. User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
  4. RTSP/1.0 200 OK
  5. CSeq: 2
  6. Public: OPTIONS, DESCRIBE, PLAY, PAUSE, SETUP, TEARDOWN, SET_PARAMETER, GET_PARAMETER
  7. Date: Mon, Jan 29 2018 16:56:47 GMT
  8. DESCRIBE rtsp://10.3.8.202:554 RTSP/1.0
  9. CSeq: 3
  10. User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
  11. Accept: application/sdp
  12. RTSP/1.0 401 Unauthorized
  13. CSeq: 3
  14. WWW-Authenticate: Digest realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", stale="FALSE"
  15. Date: Mon, Jan 29 2018 16:56:47 GMT
  16. DESCRIBE rtsp://10.3.8.202:554 RTSP/1.0
  17. CSeq: 4
  18. Authorization: Digest username="admin", realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", uri="rtsp://10.3.8.202:554", response="3fc4b15d7a923fc36f32897e3cee69aa"
  19. User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
  20. Accept: application/sdp
  21. RTSP/1.0 200 OK
  22. CSeq: 4
  23. Content-Type: application/sdp
  24. Content-Base: rtsp://10.3.8.202:554/
  25. Content-Length: 551
  26. v=0
  27. o=- 1517245007527432 1517245007527432 IN IP4 10.3.8.202
  28. s=Media Presentation
  29. e=NONE
  30. b=AS:5050
  31. t=0 0
  32. a=control:rtsp://10.3.8.202:554/
  33. m=video 0 RTP/AVP 96
  34. c=IN IP4 0.0.0.0
  35. b=AS:5000
  36. a=recvonly
  37. a=x-dimensions:2048,1536
  38. a=control:rtsp://10.3.8.202:554/trackID=1
  39. a=rtpmap:96 H264/90000
  40. a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AMp2oCAAwabgICAoAAAMAAgAAAwBlCA==,aO48gA==
  41. a=Media_header:MEDIAINFO=494D4B48010200000400000100000000000000000000000000000000000000000000000000000000;
  42. a=appversion:1.0
  43. SETUP rtsp://10.3.8.202:554/trackID=1 RTSP/1.0
  44. CSeq: 5
  45. Authorization: Digest username="admin", realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", uri="rtsp://10.3.8.202:554/", response="ddfbf3e268ae954979407369a104a620"
  46. User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
  47. Transport: RTP/AVP;unicast;client_port=57844-57845
  48. RTSP/1.0 200 OK
  49. CSeq: 5
  50. Session: 1273222592;timeout=60
  51. Transport: RTP/AVP;unicast;client_port=57844-57845;server_port=8218-8219;ssrc=5181c73a;mode="play"
  52. Date: Mon, Jan 29 2018 16:56:47 GMT
  53. PLAY rtsp://10.3.8.202:554/ RTSP/1.0
  54. CSeq: 6
  55. Authorization: Digest username="admin", realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", uri="rtsp://10.3.8.202:554/", response="b5abf0b230de4b49d6c6d42569f88e91"
  56. User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
  57. Session: 1273222592
  58. Range: npt=0.000-
  59. RTSP/1.0 200 OK
  60. CSeq: 6
  61. Session: 1273222592
  62. RTP-Info: url=rtsp://10.3.8.202:554/trackID=1;seq=65373;rtptime=3566398668
  63. Date: Mon, Jan 29 2018 16:56:47 GMT
  64. GET_PARAMETER rtsp://10.3.8.202:554/ RTSP/1.0
  65. CSeq: 7
  66. Authorization: Digest username="admin", realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", uri="rtsp://10.3.8.202:554/", response="bb2309dcd083b25991c13e165673687b"
  67. User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
  68. Session: 1273222592
  69. RTSP/1.0 200 OK
  70. CSeq: 7
  71. Date: Mon, Jan 29 2018 16:56:47 GMT
  72. TEARDOWN rtsp://10.3.8.202:554/ RTSP/1.0
  73. CSeq: 8
  74. Authorization: Digest username="admin", realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", uri="rtsp://10.3.8.202:554/", response="e08a15c27d3daac14fd4b4bcab424a5e"
  75. User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
  76. Session: 1273222592
  77. RTSP/1.0 200 OK
  78. CSeq: 8
  79. Session: 1273222592
  80. Date: Mon, Jan 29 2018 16:57:03 GMT
posted @ 2019-03-15 15:13 lfc 阅读(29) | 评论 (0)编辑 收藏
  2018年12月12日
一、windows下编译

   不推荐,主要是编译太慢。

二、linux下编译
      请参考:https://blog.csdn.net/u010311952/article/details/80658999
     
      我使用的ffmpeg版本是3.2.4,系统ubuntu 14.04,配置命令如下:
./configure --disable-static --enable-shared --enable-cross-compile --cross-prefix=i686-w64-mingw32- --target-os=mingw32 --arch=x86 --enable-gpl --enable-version3 --enable-runtime-cpudetect --prefix=/home/lfc/ffmpeg-win32/out --disable-avdevice --disable-avfilter
注:编译成static库也试过,make install时候提示莫名错误,而且vc下使用还得依赖一些gcc、mingw的库,所以放弃了。

后记
      如果只需要使用ffmpeg,而不需要在上面增加自己的东西的话,可以直接使用别人编译好的:
      https://ffmpeg.zeranoe.com/builds
posted @ 2018-12-12 13:06 lfc 阅读(27) | 评论 (0)编辑 收藏
  2018年6月1日
这段时间断断续续的在搞Onvif server,走了不少弯路,跌跌撞撞走过来,现在对接onvif test tool和海康NVR都成功了,顺便做一下记录,也供其他刚入门的朋友参考。
1、gsoap工具
其实这真的就是个工具,用于从onvif的描述文件(*wsdl等)生成框架代码(c/c++),把繁琐的xml描述变成结构体,把服务变成api调用,并提供一些封装好的工具soap_xxx。
有人喜欢这种工具(像我),也有人对它深恶痛之(主要还是因为编译出来的可执行文件大吧)。群上有高手是通过抓IPC的包,然后自己写简单的网络通信程序来发送抓包内容来实现的,简单粗暴,不过本人不喜欢这种方式,我更偏向于用gsoap工具,虽然刚开始会被它自动生成的结构体搞混,可是仔细对照协议或抓包数据来看,脉络还是很清晰的,而且这样的代码不容易犯错,更方便维护。
至于如何用gsop生产框架代码,网上有很多介绍,我自己也曾经生成过,不过后面是直接用别人生产好的^_^
2、哪些API需要实现?
我用onvif test tool调试的话,只需要实现下面几个API即可:
1)__wsdd__Probe
2)__tds__GetDeviceInformation
3)__tds__GetCapabilities
4)__tds__GetServices
5)__trt__GetProfiles
6)__trt__GetStreamUri
这么多API要实现,对于初学者来说是一件很困难的事,要是有类似live555这种开源工程供参考就好了。遗憾的是没有,幸好网上零零散散的有几个人分享的源码(最后我会列出来),虽然不尽完善,有的还有bug,不过刚好足够开个头。
后面要接海康NVR,发现又得多实现几个API(有的还没参考代码):
1)__tds__GetSystemDateAndTime
2)__tds__SetSystemDateAndTime
3)__tds__GetNetworkInterfaces
4)__trt__GetVideoEncoderConfiguration
5)__trt__GetVideoEncoderConfigurationOptions
3、总结:
Onvif其实有点像我们产品的参数配置协议(不够要复杂多了),一路开发过来,感觉有以下几点体会:
1)像我做onvif server端,手上要准备好一个IPC,电脑要装好onvif test tool工具,高手还会用Wireshark工具(我曾经用过,不够觉得没有onvif test tool工具好用)
2)gsoap里面的结构体,很多成员都需要调用soap_malloc申请的,注意申请后养成memset的好习惯,否则可能会碰到怪问题(我曾经在Ubuntu下测试都是好好的,交叉编译到arm上就出问题)
3)可以先在Ubuntu下测试,测试好了再移植到arm上
4)填写结构体成员时,一定要小心、小心、小心(重要的事说三遍),onvif不是什么高深的东西,但绝对是繁琐,需要耐心的东西。
4、干货
说了那么多虚的,来得实际的。下面是我开发过程中参考过的资料:
https://blog.csdn.net/ghostyu/article/details/8208428
https://blog.csdn.net/max_min_go/article/details/17964643
http://www.itnotepad.cn/Home/Article/num/31.html
https://wenku.baidu.com/view/510b1105a58da0116d174906.html
https://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl
上面都是我认为比较好的开发onvif server端的帖子,不过再好的帖子都比不上源码:
1)个人觉得框架写得比较好的,我也是基于他这个框架来完善的
http://www.pudn.com/Download/item/id/2481300.html
2)实现了比较多API的参考代码(从源码上看跟上面的某个贴有对应关系的)
http://www.pudn.com/Download/item/id/2836334.html
posted @ 2018-06-01 18:23 lfc 阅读(300) | 评论 (0)编辑 收藏
  2018年1月9日


起因: 不小新把记录了公司服务器IP,账号,密码的文件提交到了git
方法:

    git reset --hard <commit_id>

    git push origin HEAD --force



其他:

    根据–soft –mixed –hard,会对working tree和index和HEAD进行重置:
    git reset –mixed:此为默认方式,不带任何参数的git reset,即时这种方式,它回退到某个版本,只保留源码,回退commit和index信息
    git reset –soft:回退到某个版本,只回退了commit的信息,不会恢复到index file一级。如果还要提交,直接commit即可
    git reset –hard:彻底回退到某个版本,本地的源码也会变为上一个版本的内容


    HEAD 最近一个提交
    HEAD^ 上一次
    <commit_id> 每次commit的SHA1值. 可以用git log 看到,也可以在页面上commit标签页里找到.

commit合并:
http://www.douban.com/note/318248317/
posted @ 2018-01-09 10:51 lfc 阅读(35) | 评论 (0)编辑 收藏
  2017年2月10日
参考以下帖子:
http://blog.csdn.net/vblittleboy/article/details/20121341

作了一下改动,修正了bug,提高适用性:
#include <stdlib.h>
#include 
<stdio.h>
#include 
<string.h>
#include 
<math.h>
 
#include 
<libavutil/opt.h>
#include 
<libavutil/mathematics.h>
#include 
<libavformat/avformat.h>
 
FILE 
* fp_in = NULL;
FILE 
* fp_out = NULL;
 
static 
int frame_count;
 
int main(int argc, char **argv)
{
    
int ret;
    AVCodec 
*audio_codec;
    AVCodecContext 
*c;
    AVFrame 
*frame;
    AVPacket pkt 
= { 0 }; // data and size must be 0;
    
int got_output;
 
    
/* Initialize libavcodec, and register all codecs and formats. */
    av_register_all();
    avcodec_register_all();
    
//avdevice_register_all();
 
    audio_codec 
= avcodec_find_encoder(AV_CODEC_ID_AAC);
    c 
= avcodec_alloc_context3(audio_codec);
//    c->strict_std_compliance =FF_COMPLIANCE_EXPERIMENTAL;   
    c
->codec_id = AV_CODEC_ID_AAC;
    c
->sample_fmt = AV_SAMPLE_FMT_S16;
    c
->sample_rate = 44100;
    c
->channels = 2;
    c
->channel_layout = AV_CH_LAYOUT_STEREO;
    c
->bit_rate = 64000;
 
    
/* open the codec */
    ret 
= avcodec_open2(c, audio_codec, NULL);
    
if (ret < 0) {
        fprintf(stderr, 
"Could not open video codec: %s\n", av_err2str(ret));
        
exit(1);
    }
 
    
/* allocate and init a re-usable frame */
#
if 0
    frame 
= avcodec_alloc_frame();
#
else
    frame 
= av_frame_alloc();
#endif
    
if (!frame) {
        fprintf(stderr, 
"Could not allocate video frame\n");
        
exit(1);
    }
 
 
    frame
->nb_samples = c->frame_size;
    frame
->format = c->sample_fmt;
    frame
->channels = c->channels;
    frame
->channel_layout = c->channel_layout;
#
if 0
    frame
->linesize[0= 4096;
    frame
->extended_data = frame->data[0= av_malloc((size_t)frame->linesize[0]);
#
else
    ret 
= av_frame_get_buffer(frame, 0);
    
if (ret < 0) {
        fprintf(stderr, 
"Could not allocate an audio frame.\n");
        
exit(1);
    }
    printf(
"----nb_samples= %d, linesize= %d\n", frame->nb_samples, frame->linesize[0]);
#endif

    av_init_packet(
&pkt);

    fp_in 
= fopen("in.wav","rb");
    fp_out
= fopen("out.aac","wb");
 
    
//printf("frame->nb_samples = %d\n",frame->nb_samples);
     
    
while(1)
    {
        frame_count
++;
        bzero(frame
->data[0],frame->linesize[0]);
        ret 
= fread(frame->data[0],frame->linesize[0],1,fp_in);
        
if(ret <= 0)
        {
            printf(
"read over !\n");
            break;
        }
        ret 
= avcodec_encode_audio2(c, &pkt, frame, &got_output);
        
if (ret < 0) {
            fprintf(stderr, 
"Error encoding audio frame: %s\n", av_err2str(ret));
            
exit(1);
        }
     
        
if(got_output > 0)
        {
            
//printf("pkt.size = %d\n",pkt.size);
            fwrite(pkt.data,pkt.size,
1,fp_out);
            av_free_packet(
&pkt);
        }
 
        #
if 0
        
if(frame_count > 10)
        {
            printf(
"break @@@@@@@@@@@@\n");
            break;
        }
        #endif
    }
 
    avcodec_close(c);
    av_free(c);
#
if 0
    avcodec_free_frame(
&frame);
#
else
    av_frame_free(
&frame);
#endif
    fclose(fp_in);
    fclose(fp_out);
 
    return 
0;
}

另外,建议看一下ffmpeg自带的例程,很有参考价值(特别是需要用到重采样功能):
doc/examples/transcode_aac.c

注:
    aac编码用到了libfdk_aac库,详细请参考:
http://trac.ffmpeg.org/wiki/Encode/AAC
posted @ 2017-02-10 11:24 lfc 阅读(340) | 评论 (0)编辑 收藏
  2017年2月8日
基础搞明白了,那么live555的RTSP服务器,又是如何创建、启动,如何和Source和Sink建立联系的呢?

主程序中会调用类似下面的代码,创建RTSP服务器:
  // Create the RTSP server:
  RTSPServer
* rtspServer = RTSPServer::createNew(*env, 554, authDB);
  
if (rtspServer == NULL) {
    
*env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
    
exit(1);
  }

父类GenericMediaServer被构建,后续承担基础的服务:
RTSPServer::RTSPServer(UsageEnvironment& env,
               
int ourSocket, Port ourPort,
               UserAuthenticationDatabase
* authDatabase,
               unsigned reclamationSeconds)
  : GenericMediaServer(env, ourSocket, ourPort, reclamationSeconds),
    fHTTPServerSocket(
-1), fHTTPServerPort(0),
    fClientConnectionsForHTTPTunneling(
NULL), // will get created if needed
    fTCPStreamingDatabase(HashTable::create(ONE_WORD_HASH_KEYS)),
    fPendingRegisterOrDeregisterRequests(HashTable::create(ONE_WORD_HASH_KEYS)),
    fRegisterOrDeregisterRequestCounter(
0), fAuthDB(authDatabase), fAllowStreamingRTPOverTCP(True) {
}

GenericMediaServer会创建一个后台任务,由于监听Client的连接:
GenericMediaServer
::GenericMediaServer(UsageEnvironment
& env, int ourSocket, Port ourPort,
             unsigned reclamationSeconds)
  : Medium(env),
    fServerSocket(ourSocket), fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),
    fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),
    fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),
    fClientSessions(HashTable::create(STRING_HASH_KEYS)) {
  ignoreSigPipeOnSocket(fServerSocket); 
// so that clients on the same host that are killed don't also kill us

  
// Arrange to handle connections from others:
  env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, 
incomingConnectionHandler, this); //创建后台任务用于监听client的connect
}

收到Client的连接请求后,incomingConnectionHandler函数会被调用。

void GenericMediaServer::incomingConnectionHandlerOnSocket(int serverSocket) {
  struct sockaddr_in clientAddr;
  SOCKLEN_T clientAddrLen 
= sizeof clientAddr;
  
int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
  
if (clientSocket < 0) {
    
int err = envir().getErrno();
    
if (err != EWOULDBLOCK) {
      envir().setResultErrMsg(
"accept() failed: ");
    }
    return;
  }

  
// Create a new object for handling this connection:
  (void)createNewClientConnection(clientSocket, clientAddr);
}

live555会为每个连接调用createNewClientConnection创建一个ClientConnection

RTSPServer::RTSPClientConnection
::RTSPClientConnection(RTSPServer
& ourServer, int clientSocket, struct sockaddr_in clientAddr)
  : 
GenericMediaServer::ClientConnection
    
fIsActive((ourServer, clientSocket, clientAddr),
    fOurRTSPServer(ourServer), fClientInputSocket(fOurSocket), fClientOutputSocket(fOurSocket),
True), fRecursionCount(0), fOurSessionCookie(NULL) {
  resetRequestBuffer();
}

GenericMediaServer::ClientConnection会创建一个后台任务,由于监听connect上的Request:
GenericMediaServer::ClientConnection
::ClientConnection(GenericMediaServer
& ourServer, int clientSocket, struct sockaddr_in clientAddr)
  : fOurServer(ourServer), fOurSocket(clientSocket), fClientAddr(clientAddr) {
  
// Add ourself to our 'client connections' table:
  fOurServer.fClientConnections->Add((char const*)this, this);

  
// Arrange to handle incoming requests:
  resetRequestBuffer();
  envir().taskScheduler()
    .setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, 
incomingRequestHandler, this); //创建后台任务用于监听connect上的Request
}

当Connect上收到来自Client的请求后,incomingRequestHandler函数会被调用。

void GenericMediaServer::ClientConnection::incomingRequestHandler() {
  struct sockaddr_in dummy; 
// 'from' address, meaningless in this case

  
int bytesRead = readSocket(envir(), fOurSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy);
  handleRequestBytes(bytesRead);
}

void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
.
    
Boolean parseSucceeded = parseRTSPRequestString((char*)fRequestBuffer, fLastCRLF+2 - fRequestBuffer,    
                            cmdName, sizeof cmdName,
                            urlPreSuffix, sizeof urlPreSuffix,
                            urlSuffix, sizeof urlSuffix,
                            cseq, sizeof cseq,
                            sessionIdStr, sizeof sessionIdStr,
                            contentLength);

.
      } 
else if (strcmp(cmdName, "DESCRIBE"== 0) {    //处理DESCRIBE request
        handleCmd_DESCRIBE(urlPreSuffix, urlSuffix, (char 
const*)fRequestBuffer);
      } 
else if (strcmp(cmdName, "SETUP"== 0) {   //处理SETUP request
            clientSession 
= (RTSPServer::RTSPClientSession*)fOurRTSPServer.createNewClientSessionWithId();
      }
        
if (clientSession != NULL) {
          clientSession
->handleCmd_SETUP(this, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
        }
      } 
else if (strcmp(cmdName, "TEARDOWN"== 0
         || strcmp(cmdName, 
"PLAY"== 0
         || strcmp(cmdName, 
"PAUSE"== 0
         || strcmp(cmdName, 
"GET_PARAMETER"== 0
         || strcmp(cmdName, 
"SET_PARAMETER"== 0) {
        
if (clientSession != NULL) {
          clientSession
->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);  //处理PLAY请求等

        }
.
}

具体RTSP协议下的Request和Respond不作详细介绍,这里只关注流程部分:
1、先看handleCmd_SETUP
void RTSPServer::RTSPClientSession
::handleCmd_SETUP(RTSPServer::RTSPClientConnection
* ourClientConnection,
          char 
const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {

fstreamStates = new struct streamState[fNumStreamStates];  //每个会话的状态,用streamState来管理
....
    subsession
->getStreamParameters(fOurSessionId, ourClientConnection->fClientAddr.sin_addr.s_addr,
                    clientRTPPort, clientRTCPPort,
                    fStreamStates[trackNum].tcpSocketNum, rtpChannelId, rtcpChannelId,
                    destinationAddress, destinationTTL, fIsMulticast,
                    serverRTPPort, serverRTCPPort,
                    fStreamStates[trackNum].streamToken);
.
}

getStreamParmeters这个函数很重要,它将完成source,RTPSink的创建工作,并将其与客户端建立联系
void OnDemandServerMediaSubsession
::getStreamParameters(unsigned clientSessionId,
              netAddressBits clientAddress,
              Port 
const& clientRTPPort,
              Port 
const& clientRTCPPort,
              
int tcpSocketNum,
              unsigned char rtpChannelId,
              unsigned char rtcpChannelId,
              netAddressBits
& destinationAddress,
              u_int8_t
& /*destinationTTL*/,
              
Boolean& isMulticast,
              Port
& serverRTPPort,
              Port
& serverRTCPPort,
              void
*& streamToken) {
.
    FramedSource* mediaSource
      
= createNewStreamSource(clientSessionId, streamBitrate);

.
    rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);

.
    streamToken = fLastStreamToken
      
= new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink, 
            streamBitrate, mediaSource,
            rtpGroupsock, rtcpGroupsock);}

.

2、再来看handleCmd_PLAY
void RTSPServer::RTSPClientSession
::handleCmd_PLAY(RTSPServer::RTSPClientConnection
* ourClientConnection,
         ServerMediaSubsession
* subsession, char const* fullRequestStr) {
.
  
// Now, start streaming:
  
for (i = 0; i < fNumStreamStates; ++i) {
    
if (subsession == NULL /* means: aggregated operation */
    || subsession 
== fStreamStates[i].subsession) {
      unsigned short rtpSeqNum 
= 0;
      unsigned rtpTimestamp 
= 0;
      
if (fStreamStates[i].subsession == NULL) continue;
      fStreamStates[i].subsession
->startStream(fOurSessionId, 
                           fStreamStates[i].streamToken,
                           (TaskFunc
*)noteClientLiveness, this,
                           rtpSeqNum, rtpTimestamp,
                           RTSPServer::RTSPClientConnection::handleAlternativeRequestByte, ourClientConnection);
.
}

void OnDemandServerMediaSubsession::startStream(unsigned clientSessionId,
                        void
* streamToken,
                        TaskFunc
* rtcpRRHandler,
                        void
* rtcpRRHandlerClientData,
                        unsigned short
& rtpSeqNum,
                        unsigned
& rtpTimestamp,
                        ServerRequestAlternativeByteHandler
* serverRequestAlternativeByteHandler,
                        void
* serverRequestAlternativeByteHandlerClientData) {
.
  
if (streamState != NULL) {
    streamState
->startPlaying(destinations, clientSessionId,   
                  rtcpRRHandler, rtcpRRHandlerClientData,
                  serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
.
}

void StreamState
::startPlaying(Destinations
* dests, unsigned clientSessionId,
           TaskFunc
* rtcpRRHandler, void* rtcpRRHandlerClientData,
           ServerRequestAlternativeByteHandler
* serverRequestAlternativeByteHandler,
           void
* serverRequestAlternativeByteHandlerClientData) {
.
  
if (!fAreCurrentlyPlaying && fMediaSource != NULL) {
    
if (fRTPSink != NULL) {
      fRTPSink
->startPlaying(*fMediaSource, afterPlayingStreamState, this);
      fAreCurrentlyPlaying 
= True;
.
}

Boolean MediaSink::startPlaying(MediaSource& source,
                afterPlayingFunc
* afterFunc,
                void
* afterClientData) {
.
return continuePlaying();
}

Boolean H264or5VideoRTPSink::continuePlaying() {
  
// First, check whether we have a 'fragmenter' class set up yet.
  // If not, create it now:
  
if (fOurFragmenter == NULL) {
    fOurFragmenter 
= new H264or5Fragmenter(fHNumber, envir(), fSource, OutPacketBuffer::maxSize,   
                       ourMaxPacketSize() 
- 12/*RTP hdr size*/);
  } 
else {
    fOurFragmenter
->reassignInputSource(fSource);
  }
  fSource 
= fOurFragmenter;

  
// Then call the parent class's implementation:
  return MultiFramedRTPSink::continuePlaying();
}

Boolean MultiFramedRTPSink::continuePlaying() {
  
// Send the first packet.
  
// (This will also schedule any future sends.)
  buildAndSendPacket(
True);
  return 
True;
}

绕了好大一个圈,终于到达MultiFrameRTPSink的continuePlaying了,从现在开始,它将循环的获取RTSP服务器需要的RTP数据包,直到收到停止命令。
posted @ 2017-02-08 16:47 lfc 阅读(179) | 评论 (0)编辑 收藏
  2017年1月20日
以下只作为个人总结,只作记录用,如果想系统的分析live555,建议阅读以下帖子,或阅读源码:
http://blog.csdn.net/niu_gao/article/details/6906055

一、概念
live555类似于Gstreamer和DirectShow架构,分Source、Filter、Sink的概念,例如,测试程序testOnDemandRTSPServer中,流化H264的pipeline如下(通过H264VideoFileServerMediaSubsession自动构建):

【Source】
ByteStreamFileSource->H264or5VideoStreamParser(MPEGVideoStreamParser)->H264VideoStreamFramer(H264or5VideoStreamFramer)

直接与Sink打交道的是H264VideoStreamFramer(通过H264VideoFileServerMediaSubsession的createNewStreamSource),其它Parser、FileSource是H264VideoStreamFramer自动构建(按需要)

【Sink】
H264or5Fragmenter->H264VideoRTPSink(H264or5VideoRTPSink)->VideoRTPSink->MultiFramedRTPSink

H264or5Fragmenter与上面的H264or5VideoStreamFramer打交道,获取Source的Frame数据后分段成RTP包输出。

二、数据流动
1、先来看数据的输入
1)首先,Sink下会创建缓冲,用于存放从Source获取的数据,存放缓冲的指针就是大家比较熟悉的fTo,Sink通过一系列的类,把指针传递下去,具体是:
H264or5Fragmenter->MPEGVideoStreamFramer->MPEGVideoStreamParser

最终从Sink传递到Parser中,相关的代码片段是:
H264or5Fragmenter::H264or5Fragmenter
{
fInputBuffer 
= new unsignedchar[fInputBufferSize];
}

void H264or5Fragmenter::doGetNextFrame()
{
fInputSource
->getNextFrame(&fInputBuffer[1], fInputBufferSize - 1,  
afterGettingFrame, this,
FramedSource::handleClosure, this);
}

MPEGVideoStreamFramer::doGetNextFrame()
{
  fParser
->registerReadInterest(fTo, fMaxSize);
  continueReadProcessing();
}

void MPEGVideoStreamParser::registerReadInterest(unsigned char
* to,
                         unsigned maxSize) {
  fStartOfFrame 
= fTo = fSavedTo = to
  fLimit 
= to + maxSize;
  fNumTruncatedBytes 
= fSavedNumTruncatedBytes = 0;
}

2)或许你注意到,fTo还没传递到最终的Source(ByteStreamFileSource),那是因为ByteStreamFileSource是由Parser来访问的,而Parser本身会建立Buffer用于存储从ByteStreamFileSource读取的数据,再把分析出来的NAL写入fTo(来自Sink),所以你就可以理解为什么fTo只到达了Parser,而不到达ByteStreamFileSource了吧。

相关代码如下:
StreamParser::StreamParser
{
  fBank[
0= new unsigned char[BANK_SIZE];
  fBank[
1= new unsigned char[BANK_SIZE];
}

StreamParser::ensureValidBytes1
{
  unsigned maxNumBytesToRead 
= BANK_SIZE - fTotNumValidBytes; 
  fInputSource
->getNextFrame(&curBank()[fTotNumValidBytes], 
                 maxNumBytesToRead,
                 afterGettingBytes, this,
                 onInputClosure, this);
}

unsigned H264or5VideoStreamParser::parse()
{
saveXBytes(Y);
}

class MPEGVideoStreamParser: 
public StreamParser 
{
  
// Record "byte" in the current output frame:
  void saveByte(u_int8_t 
byte) {
    
if (fTo >= fLimit) { // there's no space left
      ++fNumTruncatedBytes;
      return;
    }

    
*fTo++ = byte;
  }
}

2、再来看数据的输出
1)上面输入的数据最终去到H264or5Fragmenter,这里说明一下:
H264or5Fragmenter还是FrameSource(在H264or5VideoRTSPSink.cpp内定义),用于连接H264VideoRTSPSink和H264VideoStreamFramer;H264or5Fragmenter的doGetNextFrame实现,会把从Source获取到的数据,按照RTP协议的要求进行分段,保存到Sink的fOutBuf中。

具体代码如下:
MultiFramedRTPSink::MultiFramedRTPSink(UsageEnvironment& env,
                       Groupsock
* rtpGS,
                       unsigned char rtpPayloadType,
                       unsigned rtpTimestampFrequency,
                       char 
const* rtpPayloadFormatName,
                       unsigned numChannels)
  : RTPSink(env, rtpGS, rtpPayloadType, rtpTimestampFrequency,
        rtpPayloadFormatName, numChannels),
    fOutBuf(
NULL), fCurFragmentationOffset(0), fPreviousFrameEndedFragmentation(False),
    fOnSendErrorFunc(
NULL), fOnSendErrorData(NULL) {
  setPacketSizes((RTP_PAYLOAD_PREFERRED_SIZE), (RTP_PAYLOAD_MAX_SIZE)); 
//sihid
}

void MultiFramedRTPSink::setPacketSizes(unsigned preferredPacketSize,
                    unsigned maxPacketSize) {
  
if (preferredPacketSize > maxPacketSize || preferredPacketSize == 0) return;
      
// sanity check

  delete fOutBuf;
  fOutBuf 
= new OutPacketBuffer(preferredPacketSize, maxPacketSize);
  fOurMaxPacketSize 
= maxPacketSize; // save value, in case subclasses need it
}

Boolean MultiFramedRTPSink::continuePlaying() {
  
// Send the first packet.
  
// (This will also schedule any future sends.)
  buildAndSendPacket(
True);
  return 
True;
}

void MultiFramedRTPSink::buildAndSendPacket(
Boolean isFirstPacket) {
..
  packFrame();
}

void MultiFramedRTPSink::packFrame() {
  
// Get the next frame.
..
    
// Normal case: we need to read a new frame from the source
    
if (fSource == NULL) return;
    fSource
->getNextFrame(fOutBuf->curPtr(), fOutBuf->totalBytesAvailable(),
              afterGettingFrame, this, ourHandleClosure, this);
}
void H264or5Fragmenter::doGetNextFrame() {
  
if (fNumValidDataBytes == 1) {
    
// We have no NAL unit data currently in the buffer.  Read a new one:
    fInputSource
->getNextFrame(&fInputBuffer[1], fInputBufferSize - 1,  //Sink调用Source的getNextFrame获取数据
                   afterGettingFrame, this,
                   FramedSource::handleClosure, this);
  } 
else {
..
memmove(fTo, 
&fInputBuffer[1], fNumValidDataBytes - 1);
..
memmove(fTo, fInputBuffer, fMaxSize);
..
memmove(fTo, 
&fInputBuffer[fCurDataOffset-numExtraHeaderBytes], numBytesToSend);
..
}

看到了吧,数据的输入操作,其实是由Sink(MultiFramedRTPSink)发起的,当Sink需要获取数据时,通过调用Source的getNextFrame操作(具体由Source的doGetNextFrame操作来实现),经过一系列的类操作(Source->Filter->Sink),获取到Sink想要的数据。

2)到目前为止,终于可以构建出完整的Pipeline了:ByteStreamFileSource->H264or5VideoStreamParser(MPEGVideoStreamParser)->H264VideoStreamFramer(H264or5VideoStreamFramer)->H264or5Fragmenter->H264VideoRTPSink(H264or5VideoRTPSink)->VideoRTPSink->MultiFramedRTPSink

三、设计思想

1、对于Buffer
上面的Pipeline中,有几处Buffer,对于实时性要求比较高的应用,有必要理清buffer的数量和控制buffer的大小
1)StreamParser会产生buffer,大小是BANK_SIZE(150000),那是因为StreamParser的前端是无格式的ByteStream,后面是完整的一帧数据(NAL),需要通过Parser来处理;
2)H264or5Fragmenter会产生buffer,用于StreamParser存放分析之后的数据(NAL),并生成后端RTPSink需要的RTP包;
3)MultiFramedRTPSink会产生buffer,用于Fragmenter存放分段之后的数据(RTP),以供RTSP服务器使用;

2、对于fTo
fTo,顾名思义,就是To的buffer指针,也就是后端提供的buffer指针;那fTo是在什么时候赋值的呢,答案在这里:

void FramedSource::getNextFrame(unsigned char* to, unsigned maxSize,
                afterGettingFunc
* afterGettingFunc,
                void
* afterGettingClientData,
                onCloseFunc
* onCloseFunc,
                void
* onCloseClientData) {
  
// Make sure we're not already being read:
  if (fIsCurrentlyAwaitingData) {
    envir() 
<< "FramedSource[" << this << "]::getNextFrame(): attempting to read more than once at the same time!\n";
    envir().internalError();
  }

  fTo 
= to//把Sink的bufer赋给Source的fTo sihid
  fMaxSize 
= maxSize; //设置FrameSource的MaxSize sihid
  fNumTruncatedBytes 
= 0// by default; could be changed by doGetNextFrame()
  fDurationInMicroseconds 
= 0// by default; could be changed by doGetNextFrame()
  fAfterGettingFunc 
= afterGettingFunc;
  fAfterGettingClientData 
= afterGettingClientData;
  fOnCloseFunc 
= onCloseFunc;
  fOnCloseClientData 
= onCloseClientData;
  fIsCurrentlyAwaitingData 
= True;

  doGetNextFrame();
}

另外,MPEGVideoStreamFramer还会通过其它方式传递fTo给MPEGVideoStreamParser:

MPEGVideoStreamFramer::doGetNextFrame()
{
  fParser->registerReadInterest(fTo, fMaxSize);
  continueReadProcessing();
}


void MPEGVideoStreamParser::registerReadInterest(unsigned char* to,unsigned maxSize) {
  fStartOfFrame = fTo = fSavedTo = to;
  fLimit = to + maxSize;
  fNumTruncatedBytes = fSavedNumTruncatedBytes = 0;
}

原因是MPEGVideoStreamParser(StreamParser)不是FrameSource,所以只能提供另外的API来获取fTo
当Sink需要数据时,会调用Source的getNextFrame方法,同时把自身的buffer通过参数(to)传递给Source,保存在Source的fTo中。

3、对于fSource
Boolean MediaSink::startPlaying(MediaSource& source,
                afterPlayingFunc
* afterFunc,
                void
* afterClientData) {
  
// Make sure we're not already being played:
  if (fSource != NULL) {
    envir().setResultMsg(
"This sink is already being played");
    return 
False;
  }

  
// Make sure our source is compatible:
  
if (!sourceIsCompatibleWithUs(source)) {
    envir().setResultMsg(
"MediaSink::startPlaying(): source is not compatible!");
    return 
False;
  }
  fSource 
= (FramedSource*)&source; 

  fAfterFunc 
= afterFunc;
  fAfterClientData 
= afterClientData;
  return continuePlaying();
}

Sink的fSource,在startPlaying中被设置为createNewStreamSource返回的H264VideoStreamFramer。

随后在continuePlaying(H264or5VideoRTPSink实现)方法中被修改为H264or5Fragmenter,同时通过reassignInputSource函数记录H264VideoStreamFramer为fInputSource,这样就把fSource和fInputSource区分出来。

Boolean H264or5VideoRTPSink::continuePlaying() {
  
// First, check whether we have a 'fragmenter' class set up yet.
  // If not, create it now:
  
if (fOurFragmenter == NULL) {
    fOurFragmenter 
= new H264or5Fragmenter(fHNumber, envir(), fSource, OutPacketBuffer::maxSize, ourMaxPacketSize() - 12/*RTP hdr size*/);
  } 
else {
    fOurFragmenter
->reassignInputSource(fSource); 
  }
  fSource 
= fOurFragmenter; 

  
// Then call the parent class's implementation:  return MultiFramedRTPSink::continuePlaying();
}

class FramedFilter: public FramedSource {
public:
  FramedSource* inputSource() const { return fInputSource; }
  void reassignInputSource(FramedSource* newInputSource) { fInputSource = newInputSource; }
  // Call before destruction if you want to prevent the destructor from closing the input source
  void detachInputSource();
protected:
  FramedFilter(UsageEnvironment& env, FramedSource* inputSource);
     // abstract base class
  virtual ~FramedFilter();
protected:
  // Redefined virtual functions (with default 'null' implementations):
  virtual char const* MIMEtype() const;
  virtual void getAttributes() const;
  virtual void doStopGettingFrames();
protected:
  FramedSource* fInputSource; //输入文件对应的Source
};

为什么要这么做?因为RTPSink需要的数据,需要在H264VideoStreamFramer输出数据的基础上,通过H264or5Fragmenter封装成RTP包,所以多出了H264or5Fragmenter这个东西(类似于前面的H264or5VideoStreamParser)。

4、对于fInputSource

class FramedFilter下定义,被H264or5VideoStreamFramer和H264or5Fragmenter所继承。可是,fInputSource在这两个类下,指向的内容是不同的。
1)H264or5VideoStreamFramer下指向ByteStreamFileSource,具体见以下代码:
FramedSource* H264VideoFileServerMediaSubsession::createNewStreamSource(unsigned /*clientSessionId*/, unsigned& estBitrate) {
  estBitrate 
= 500// kbps, estimate

  
// Create the video source:
  
ByteStreamFileSource* fileSource = ByteStreamFileSource::createNew(envir(), fFileName);
  
if (fileSource == NULL) return NULL;
  fFileSize 
= fileSource->fileSize();

  
// Create a framer for the Video Elementary Stream:
  return H264VideoStreamFramer::createNew(envir(), 
fileSource); //把ByteStreamFileSource记录到H264VideoStreamFramer的fInputSource

}

H264or5VideoStreamFramer
::H264or5VideoStreamFramer(
int hNumber, UsageEnvironment& env, FramedSource* inputSource,
               
Boolean createParser, Boolean includeStartCodeInOutput)
  : MPEGVideoStreamFramer(env, inputSource),
    fHNumber(hNumber),
    fLastSeenVPS(
NULL), fLastSeenVPSSize(0),
    fLastSeenSPS(
NULL), fLastSeenSPSSize(0),
    fLastSeenPPS(
NULL), fLastSeenPPSSize(0) {
.
}

MPEGVideoStreamFramer::MPEGVideoStreamFramer(UsageEnvironment& env,
                         FramedSource
* inputSource)
  : FramedFilter(env, inputSource),
    fFrameRate(
0.0/* until we learn otherwise */,
    fParser(
NULL) {
  reset();
}

2)H264or5Fragmenter下指向H264VideoStreamFramer,具体见以下代码:
Boolean H264or5VideoRTPSink::continuePlaying() {
  
// First, check whether we have a 'fragmenter' class set up yet.
  // If not, create it now:
  
if (fOurFragmenter == NULL) {
    fOurFragmenter 
= new H264or5Fragmenter(fHNumber, envir(), fSource, OutPacketBuffer::maxSize,    //OutPacketBuffer::maxSize决定了fInputBufferSize. sihid
                       ourMaxPacketSize() 
- 12/*RTP hdr size*/);
  } 
else {
    fOurFragmenter
->reassignInputSource(fSource); //把fSource(对应H264VideoStreamFramer)保存到fOurFragmenter(对应H264or5Fragmenter)的fInputSource
  }
  fSource 
= fOurFragmenter;

  
// Then call the parent class's implementation:
  return MultiFramedRTPSink::continuePlaying();
}
posted @ 2017-01-20 12:15 lfc 阅读(614) | 评论 (0)编辑 收藏
  2016年7月14日
【libxml2-2.9.2】

./configure --host=arm-linux-gnueabi --prefix=/home/luofc/work/tools/gcc-linaro-arm-linux-gnueabi-4.6.3-2012.02-20120201_linux/arm-linux-gnueabi --with-python=/home/luofc/work/tools/libxml2-2.9.2/python
make;make install

【Python-2.7.3】

patch -p1 < ../Python-2.7.3-xcompile.patch
./configure --host=arm-linux-gnueabi --prefix=/home/luofc/python
make HOSTPYTHON=./hostpython HOSTPGEN=./Parser/hostpgen BLDSHARED="arm-linux-gnueabi-gcc -shared" CROSS_COMPILE=arm-linux-gnueabi- CROSS_COMPILE_TARGET=yes
make;make install

【libplist】
PKG_CONFIG_PATH=/home/luofc/work/tools/gcc-linaro-arm-linux-gnueabi-4.6.3-2012.02-20120201_linux/arm-linux-gnueabi/lib/pkgconfig ./configure --host=arm-linux-gnueabi --prefix=/home/luofc/libimobiledevice/libplist LDFLAGS="-L/home/luofc/python/lib"
make;make install

【libusbmuxd】
PKG_CONFIG_PATH=/home/luofc/work/tools/gcc-linaro-arm-linux-gnueabi-4.6.3-2012.02-20120201_linux/arm-linux-gnueabi/lib/pkgconfig:/home/luofc/libimobiledevice/libplist/lib/pkgconfig ./configure --host=arm-linux-gnueabi --prefix=/home/luofc/libimobiledevice/libusbmuxd
make;make install

【usbmuxd】
PKG_CONFIG_PATH=/home/luofc/work/tools/gcc-linaro-arm-linux-gnueabi-4.6.3-2012.02-20120201_linux/arm-linux-gnueabi/lib/pkgconfig:/home/luofc/libimobiledevice/libplist/lib/pkgconfig ./configure --host=arm-linux-gnueabi --prefix=/home/luofc/libimobiledevice/usbmuxd --without-preflight
make;make install
posted @ 2016-07-14 10:07 lfc 阅读(447) | 评论 (0)编辑 收藏
  2016年6月23日
1.host-m4-1.4.15

In file includedfrom clean-temp.h:22:0,

from clean-temp.c:23:

./stdio.h:456:1:error: 'gets' undeclared here (not in a function)

_GL_WARN_ON_USE(gets, "gets is a security hole - use fgets instead");

解决方法:

参考链接:

http://www.civilnet.cn/talk/browse.php?topicno=78555,2楼.

找到:host-m4-1.4.15/lib/stdio.h,然后对stdio.h文件做出如下改动,必要时连同stdio.in.h一起修改:

[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. <span style="font-family:Arial;font-size:12px;"># Begin patch  
  2. === modified file 'grub-core/gnulib/stdio.in.h'  
  3. --- grub-core/gnulib/stdio.in.h 2010-09-20 10:35:33 +0000  
  4. +++ grub-core/gnulib/stdio.in.h 2012-07-04 15:18:15 +0000  
  5. @@ -140,8 +140,10 @@  
  6.  /* It is very rare that the developer ever has full control of stdin,  
  7.     so any use of gets warrants an unconditional warning.  Assume it is  
  8.     always declared, since it is required by C89.  */  
  9. +#if defined gets  
  10.  #undef gets  
  11.  _GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");  
  12. +#endif  



2.host-autoconf-2.65

conftest.c:14625:must be after `@defmac' to use `@defmacx'

make[3]: ***[autoconf.info] Error 1

make[3]: Leavingdirectory`//opt/Android/a23androidSRC/lichee/out/linux/common/buildroot/build/host-autoconf-2.65/doc'

make[2]: ***[install-recursive] Error 1

make[2]: Leavingdirectory`/opt/Android/a23androidSRC/lichee/out/linux/common/buildroot/build/host-autoconf-2.65'

make[1]: ***[install] Error 2

make[1]: Leavingdirectory`/opt/Android/a23androidSRC/lichee/out/linux/common/buildroot/build/host-autoconf-2.65'

make: ***[/opt/Android/a23androidSRC/lichee/out/linux/common/buildroot/build/host-autoconf-2.65/.stamp_host_installed]Error 2

解决方法如下:

参考链接:

http://gnu-autoconf.7623.n7.nabble.com/compile-error-conftest-c-14625-must-be-after-defmac-to-use-defmacx-td18843.html

2楼有个补丁文件:

[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. --- autoconf-2.65/doc/autoconf.texi 2009-11-05 10:42:15.000000000 +0800  
  2. +++ autoconf-2.65/doc/autoconf.texi.new 2013-05-28 05:41:09.243770263 +0800  
  3. @@ -15,7 +15,7 @@  
  4.  @c The ARG is an optional argument.  To be used for macro arguments in  
  5.  @c their documentation (@defmac).  
  6.  @macro ovar{varname}  
  7. -@r{[}@var{\varname\}@r{]}@c  
  8. +@r{[}@var{\varname\}@r{]}  
  9.  @end macro  
  10.    
  11.  @c @dvar(ARG, DEFAULT)  
  12. @@ -23,7 +23,7 @@  
  13.  @c The ARG is an optional argument, defaulting to DEFAULT.  To be used  
  14.  @c for macro arguments in their documentation (@defmac).  
  15.  @macro dvar{varname, default}  
  16. -@r{[}@var{\varname\} = @samp{\default\}@r{]}@c  
  17. +@r{[}@var{\varname\} = @samp{\default\}@r{]}  
  18.  @end macro  
  19.    
  20.  @c Handling the indexes with Texinfo yields several different problems.  

根据这个补丁文件修改即可,直接修改源代码包,下次编译就不会再提示这个错误了。

3.host-makedevs

/opt/Android/a23androidSRC/lichee/out/linux/common/buildroot/build/host-makedevs/makedevs.c:374:6: error: variable ‘ret’ set but not used [-Werror=unused-but-set-variable]
  int ret = EXIT_SUCCESS;
      ^
cc1: all warnings being treated as errors

直接修改makedevs.c文件:

最后一行,return 0;

修改为:return ret;

源代码位置:./buildroot/package/makedevs/makedevs.c
posted @ 2016-06-23 16:48 lfc 阅读(269) | 评论 (0)编辑 收藏
  2016年5月18日
     摘要: Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->static int mpegts_read_packet(AVFormatContext *s, AVPacket *pkt){xxxx &n...  阅读全文
posted @ 2016-05-18 11:30 lfc 阅读(242) | 评论 (0)编辑 收藏
仅列出标题  下一页