基本的原理在这里
可以看下加深认知
http://blog.csdn.net/rembass/article/details/17653273
1.live555的编译及测试(windows)
http://blog.csdn.net/hjl240/article/details/48159243
..\UsageEnvironment\include
..\liveMedia\include
..\groupsock\include
..\BasicUsageEnvironment\include
记住mediaServer项目也一样的配置,一样的添加头文件,一样的添加源码
按照这个文章的配置编译了一把出现如下错误
结果是如下
在各个项目预编译器里面添加_CRT_SECURE_NO_WARNINGS
依然有错误
添加_WINSOCK_DEPRECATED_NO_WARNINGS
int BitVector::get_expGolombSigned() { unsigned codeNum = get_expGolomb(); if ((codeNum&1) == 0) { // even return -(codeNum/2); //错误 C4146 一元负运算符应用于无符号类型,结果仍为无符号类型 } else { // odd return (codeNum+1)/2; } }
还有一个选择打开和关闭SDL检查的位置就是:项目属性->配置属性->C/C++->SDL检查,选否
编译正常通过,生产如下文件
此时编译mediaServer,会出现大量的链接错误
在MediaServer的ive555MediaServer.cpp添加
#ifdef _DEBUG #pragma comment (lib, "../Debug/BasicUsageEnvironment.lib") #pragma comment (lib, "../Debug/groupsock.lib") #pragma comment (lib, "../Debug/liveMedia.lib") #pragma comment (lib, "../Debug/UsageEnvironment.lib") #else #pragma comment (lib, "../Release/BasicUsageEnvironment.lib") #pragma comment (lib, "../Release/groupsock.lib") #pragma comment (lib, "../Release/liveMedia.lib") #pragma comment (lib, "../Release/UsageEnvironment.lib") #endif
依然报错
1>------ 已启动生成: 项目: mediaServer, 配置: Debug Win32 ------ 1>BasicUsageEnvironment.lib(BasicUsageEnvironment.obj) : error LNK2019: 无法解析的外部符号 _initializeWinsockIfNecessary,该符号在函数 "protected: __thiscall BasicUsageEnvironment::BasicUsageEnvironment(class TaskScheduler &)" (??0BasicUsageEnvironment@@IAE@AAVTaskScheduler@@@Z) 中被引用 1>groupsock.lib(GroupsockHelper.obj) : error LNK2001: 无法解析的外部符号 _initializeWinsockIfNecessary 1>groupsock.lib(NetAddress.obj) : error LNK2019: 无法解析的外部符号 _our_inet_addr,该符号在函数 "public: __thiscall NetAddressList::NetAddressList(char const *)" (??0NetAddressList@@QAE@PBD@Z) 中被引用 1>groupsock.lib(GroupsockHelper.obj) : error LNK2001: 无法解析的外部符号 _our_inet_addr 1>groupsock.lib(GroupsockHelper.obj) : error LNK2019: 无法解析的外部符号 _our_srandom,该符号在函数 "unsigned int __cdecl ourIPAddress(class UsageEnvironment &)" (?ourIPAddress@@YAIAAVUsageEnvironment@@@Z) 中被引用 1>groupsock.lib(GroupsockHelper.obj) : error LNK2019: 无法解析的外部符号 _our_random,该符号在函数 "unsigned int __cdecl chooseRandomIPv4SSMAddress(class UsageEnvironment &)" (?chooseRandomIPv4SSMAddress@@YAIAAVUsageEnvironment@@@Z) 中被引用 1>liveMedia.lib(ProxyServerMediaSession.obj) : error LNK2001: 无法解析的外部符号 _our_random 1>liveMedia.lib(RTCP.obj) : error LNK2001: 无法解析的外部符号 _our_random 1>liveMedia.lib(RTPSink.obj) : error LNK2001: 无法解析的外部符号 _our_random 1>liveMedia.lib(GenericMediaServer.obj) : error LNK2019: 无法解析的外部符号 _our_random32,该符号在函数 "protected: class GenericMediaServer::ClientSession * __thiscall GenericMediaServer::createNewClientSessionWithId(void)" (?createNewClientSessionWithId@GenericMediaServer@@IAEPAVClientSession@1@XZ) 中被引用 1>liveMedia.lib(RTPSink.obj) : error LNK2001: 无法解析的外部符号 _our_random32 1>liveMedia.lib(RTPSource.obj) : error LNK2001: 无法解析的外部符号 _our_random32 1>G:\opencode\live555\live\Debug\mediaServer.exe : fatal error LNK1120: 5 个无法解析的外部命令 ========== 生成: 成功 0 个,失败 1 个,最新 4 个,跳过 0 个 ==========
开始只导入了C++文件和h文件,groupsock下有inet.c,liveMedia下有rtcp_from_spec.c,必须添加到工程,切记切记!
编译通过
运行一把
只支持提示的格式,其他的格式不支持的,截一个播放片段的页面,后面的就不截了!
2.应用验证
很多人在网上找应用,找不到参考的列子,要吗就是例子乱七八糟,根本不知道如何运行,或者一堆错,浪费时间,还没有得到应用的使用方法
实际上官方给了不少列子的http://www.live555.com/liveMedia/#testProgs 这些例子都包含在下载的源码里面
作为RTSP client它提供了两个例子testRTSPClient和openRTSP
作为RTSP server提供了一个例子testOnDemandRTSPServer
运行一把,提示使用方法
日志
G:\opencode\live555\live\Debug>testProgs.exe rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream Created new TCP socket 128 for connection Connecting to 192.168.1.68, port 554 on socket 128... ...remote connection opened Sending request: DESCRIBE rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream RTSP/1.0 CSeq: 2 User-Agent: testProgs.exe (LIVE555 Streaming Media v2017.07.18) Accept: application/sdp Received 952 new bytes of response data. Received a complete DESCRIBE response: RTSP/1.0 200 OK CSeq: 2 Content-Type: application/sdp Content-Base: rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/ Content-Length: 793 v=0 o=- 1504884932064984 1504884932064984 IN IP4 192.168.1.68 s=Media Presentation e=NONE b=AS:5100 t=0 0 a=control:rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/ m=video 0 RTP/AVP 96 c=IN IP4 0.0.0.0 b=AS:5000 a=recvonly a=x-dimensions:1920,1080 a=control:rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/trackID=1 a=rtpmap:96 H264/90000 a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AKpWoHgCJ+WEAAAf9AAFfkAQ=,aO48gA== m=audio 0 RTP/AVP 8 c=IN IP4 0.0.0.0 b=AS:50 a=recvonly a=control:rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/trackID=2 a=rtpmap:8 PCMA/8000 a=Media_header:MEDIAINFO=494D4B48010200000400000111710110401F000000FA000000000000000000000000000000000000; a=appversion:1.0 [URL:"rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"]: Got a SDP description: v=0 o=- 1504884932064984 1504884932064984 IN IP4 192.168.1.68 s=Media Presentation e=NONE b=AS:5100 t=0 0 a=control:rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/ m=video 0 RTP/AVP 96 c=IN IP4 0.0.0.0 b=AS:5000 a=recvonly a=x-dimensions:1920,1080 a=control:rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/trackID=1 a=rtpmap:96 H264/90000 a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AKpWoHgCJ+WEAAAf9AAFfkAQ=,aO48gA== m=audio 0 RTP/AVP 8 c=IN IP4 0.0.0.0 b=AS:50 a=recvonly a=control:rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/trackID=2 a=rtpmap:8 PCMA/8000 a=Media_header:MEDIAINFO=494D4B48010200000400000111710110401F000000FA000000000000000000000000000000000000; a=appversion:1.0 [URL:"rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"]: Initiated the "video/H264" subsession (client ports 60736-60737) Sending request: SETUP rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/trackID=1 RTSP/1.0 CSeq: 3 User-Agent: testProgs.exe (LIVE555 Streaming Media v2017.07.18) Transport: RTP/AVP;unicast;client_port=60736-60737 Received 204 new bytes of response data. Received a complete SETUP response: RTSP/1.0 200 OK CSeq: 3 Session: 1072111189;timeout=60 Transport: RTP/AVP;unicast;client_port=60736-60737;server_port=8370-8371;ssrc=7812147b;mode="play" Date: Fri, Sep 08 2017 15:35:32 GMT [URL:"rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"]: Set up the "video/H264" subsession (client ports 60736-60737) [URL:"rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"]: Created a data sink for the "video/H264" subsession [URL:"rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"]: Initiated the "audio/PCMA" subsession (client ports 60738-60739) Sending request: SETUP rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/trackID=2 RTSP/1.0 CSeq: 4 User-Agent: testProgs.exe (LIVE555 Streaming Media v2017.07.18) Transport: RTP/AVP;unicast;client_port=60738-60739 Session: 1072111189 Received 204 new bytes of response data. Received a complete SETUP response: RTSP/1.0 200 OK CSeq: 4 Session: 1072111189;timeout=60 Transport: RTP/AVP;unicast;client_port=60738-60739;server_port=8354-8355;ssrc=7902bf6e;mode="play" Date: Fri, Sep 08 2017 15:35:32 GMT [URL:"rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"]: Set up the "audio/PCMA" subsession (client ports 60738-60739) [URL:"rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"]: Created a data sink for the "audio/PCMA" subsession Sending request: PLAY rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/ RTSP/1.0 CSeq: 5 User-Agent: testProgs.exe (LIVE555 Streaming Media v2017.07.18) Session: 1072111189 Range: npt=0.000- Received 313 new bytes of response data. Received a complete PLAY response: RTSP/1.0 200 OK CSeq: 5 Session: 1072111189 RTP-Info: url=rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/trackID=1;seq=379;rtptime=3863118866,url=rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/ Date: Fri, Sep 08 2017 15:35:32 GMT [URL:"rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"]: Started playing session... Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; audio/PCMA: Received 320 bytes. Presentation time: 1504856199.712277! Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; video/H264: Received 20 bytes. Presentation time: 1504856199.713578! Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; video/H264: Received 4 bytes. Presentation time: 1504856199.713578! Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; video/H264: Received 5 bytes. Presentation time: 1504856199.713578! MultiFramedRTPSource::doGetNextFrame1(): The total received frame size exceeds the client's buffer size (100000). 41578 bytes of trailing data will be dropped! Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; video/H264: Received 100000 bytes (with 41578 bytes truncated). Presentation time: 1504856199.713578! Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; audio/PCMA: Received 320 bytes. Presentation time: 1504856199.752277! Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; audio/PCMA: Received 320 bytes. Presentation time: 1504856199.792277! Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; audio/PCMA: Received 320 bytes. Presentation time: 1504856199.832277! Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; video/H264: Received 12511 bytes. Presentation time: 1504856199.793578! Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; video/H264: Received 9392 bytes. Presentation time: 1504856199.833578! Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; audio/PCMA: Received 320 bytes. Presentation time: 1504856199.872277! Stream "rtsp://admin:Wz123456@192.168.1.68:554/h264/ch1/main/av_stream/"; audio/PCMA: Received 320 bytes. Presentation time: 1504856199.912277!
拉到音频流和视频流
源码分析
int main(int argc, char** argv) { // Begin by setting up our usage environment: TaskScheduler* scheduler = BasicTaskScheduler::createNew(); UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler); // We need at least one "rtsp://" URL argument: if (argc < 2) { usage(*env, argv[0]); return 1; } // There are argc-1 URLs: argv[1] through argv[argc-1]. Open and start streaming each one: for (int i = 1; i <= argc-1; ++i) { openURL(*env, argv[0], argv[i]); } // All subsequent activity takes place within the event loop: env->taskScheduler().doEventLoop(&eventLoopWatchVariable); // This function call does not return, unless, at some point in time, "eventLoopWatchVariable" gets set to something non-zero. return 0; // If you choose to continue the application past this point (i.e., if you comment out the "return 0;" statement above), // and if you don't intend to do anything more with the "TaskScheduler" and "UsageEnvironment" objects, // then you can also reclaim the (small) memory used by these objects by uncommenting the following code: /* env->reclaim(); env = NULL; delete scheduler; scheduler = NULL; */ }
主要代码在两句话
for (int i = 1; i <= argc-1; ++i) { openURL(*env, argv[0], argv[i]); } env->taskScheduler().doEventLoop(&eventLoopWatchVariable);
主要在这一个函数里面实现
void openURL(UsageEnvironment& env, char const* progName, char const* rtspURL) { // Begin by creating a "RTSPClient" object. Note that there is a separate "RTSPClient" object for each stream that we wish // to receive (even if more than stream uses the same "rtsp://" URL). RTSPClient* rtspClient = ourRTSPClient::createNew(env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL, progName); if (rtspClient == NULL) { env << "Failed to create a RTSP client for URL \"" << rtspURL << "\": " << env.getResultMsg() << "\n"; return; } ++rtspClientCount; // Next, send a RTSP "DESCRIBE" command, to get a SDP description for the stream. // Note that this command - like all RTSP commands - is sent asynchronously; we do not block, waiting for a response. // Instead, the following function call returns immediately, and we handle the RTSP response later, from within the event loop: rtspClient->sendDescribeCommand(continueAfterDESCRIBE); }
2.live555的编译及测试(linux)
下载解压
tar -xvf live555-latest.tar.gz
./genMakefiles linux-64bit
选的 对应的是config.linux-64bit文件的配置
user@g1060server:~/mjl/algo/live$ make cd liveMedia ; make make[1]: 正在进入目录 `/home/user/mjl/algo/live/liveMedia' c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 Media.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 MediaSource.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 FramedSource.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 FramedFileSource.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 FramedFilter.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 ByteStreamFileSource.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 ByteStreamMultiFileSource.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 ByteStreamMemoryBufferSource.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 BasicUDPSource.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 DeviceSource.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 AudioInputDevice.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 WAVAudioFileSource.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 MPEG1or2Demux.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 MPEG1or2DemuxedElementaryStream.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 MPEGVideoStreamFramer.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 MPEG1or2VideoStreamFramer.cpp MPEG1or2VideoStreamFramer.cpp: In member function ‘unsigned int MPEG1or2VideoStreamParser::parseSlice()’: MPEG1or2VideoStreamFramer.cpp:463:18: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast] << (void*)next4Bytes << "\n"; ^ c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 MPEG1or2VideoStreamDiscreteFramer.cpp c++ -c -Iinclude -I../UsageEnvironment/include -I../groupsock/include -m64 -fPIC -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -DBSD=1 MPEG4VideoStreamFramer.cpp MPEG4VideoStreamFramer.cpp: In member function ‘unsigned int MPEG4VideoStreamParser::parseVideoObjectPlane()’: MPEG4VideoStreamFramer.cpp:655:19: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
查看所有的库文件,默认生成静态库,没有动态库
user@g1060server:~/mjl/algo/live$ find ./ -name *.a ./UsageEnvironment/libUsageEnvironment.a ./BasicUsageEnvironment/libBasicUsageEnvironment.a ./liveMedia/libliveMedia.a ./groupsock/libgroupsock.a user@g1060server:~/mjl/algo/live$ find ./ -name *.so user@g1060server:~/mjl/algo/live$
测试拉流的数据
user@g1060server:~/mjl/algo/live/testProgs$ ./testRTSPClient Usage: ./testRTSPClient <rtsp-url-1> ... <rtsp-url-N> (where each <rtsp-url-i> is a "rtsp://" URL) user@g1060server:~/mjl/algo/live/testProgs$ ./testRTSPClient rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream Created new TCP socket 3 for connection Connecting to 192.168.3.229, port 554 on socket 3... ...remote connection opened Sending request: DESCRIBE rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream RTSP/1.0 CSeq: 2 User-Agent: ./testRTSPClient (LIVE555 Streaming Media v2017.07.18) Accept: application/sdp Received 789 new bytes of response data. Received a complete DESCRIBE response: RTSP/1.0 200 OK CSeq: 2 Content-Type: application/sdp Content-Base: rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/ Content-Length: 628 v=0 o=- 1509489480558527 1509489480558527 IN IP4 192.168.3.229 s=Media Presentation e=NONE b=AS:5050 t=0 0 a=control:rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/ m=video 0 RTP/AVP 96 c=IN IP4 0.0.0.0 b=AS:5000 a=recvonly a=x-dimensions:1920,1080 a=control:rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/trackID=1 a=rtpmap:96 H264/90000 a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AKpWoHgCJ+WbgICAgQA==,aO48gA== a=Media_header:MEDIAINFO=494D4B48010200000400000100000000000000000000000000000000000000000000000000000000; a=appversion:1.0 [URL:"rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"]: Got a SDP description: v=0 o=- 1509489480558527 1509489480558527 IN IP4 192.168.3.229 s=Media Presentation e=NONE b=AS:5050 t=0 0 a=control:rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/ m=video 0 RTP/AVP 96 c=IN IP4 0.0.0.0 b=AS:5000 a=recvonly a=x-dimensions:1920,1080 a=control:rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/trackID=1 a=rtpmap:96 H264/90000 a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AKpWoHgCJ+WbgICAgQA==,aO48gA== a=Media_header:MEDIAINFO=494D4B48010200000400000100000000000000000000000000000000000000000000000000000000; a=appversion:1.0 [URL:"rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"]: Initiated the "video/H264" subsession (client ports 47872-47873) Sending request: SETUP rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/trackID=1 RTSP/1.0 CSeq: 3 User-Agent: ./testRTSPClient (LIVE555 Streaming Media v2017.07.18) Transport: RTP/AVP;unicast;client_port=47872-47873 Received 204 new bytes of response data. Received a complete SETUP response: RTSP/1.0 200 OK CSeq: 3 Session: 2057492403;timeout=60 Transport: RTP/AVP;unicast;client_port=47872-47873;server_port=8314-8315;ssrc=5db62837;mode="play" Date: Tue, Oct 31 2017 22:38:00 GMT [URL:"rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"]: Set up the "video/H264" subsession (client ports 47872-47873) [URL:"rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"]: Created a data sink for the "video/H264" subsession Sending request: PLAY rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/ RTSP/1.0 CSeq: 4 User-Agent: ./testRTSPClient (LIVE555 Streaming Media v2017.07.18) Session: 2057492403 Range: npt=0.000- Received 212 new bytes of response data. Received a complete PLAY response: RTSP/1.0 200 OK CSeq: 4 Session: 2057492403 RTP-Info: url=rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/trackID=1;seq=48023;rtptime=2121100274 Date: Tue, Oct 31 2017 22:38:00 GMT [URL:"rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"]: Started playing session... Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 16 bytes. Presentation time: 1509459854.583901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 4 bytes. Presentation time: 1509459854.583901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 5 bytes. Presentation time: 1509459854.583901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 39452 bytes. Presentation time: 1509459854.583901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 18056 bytes. Presentation time: 1509459854.649901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 16 bytes. Presentation time: 1509459854.683901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 4 bytes. Presentation time: 1509459854.683901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 5 bytes. Presentation time: 1509459854.683901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 36213 bytes. Presentation time: 1509459854.683901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 18184 bytes. Presentation time: 1509459854.749901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17697 bytes. Presentation time: 1509459854.783901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17606 bytes. Presentation time: 1509459854.816901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17251 bytes. Presentation time: 1509459854.849901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17527 bytes. Presentation time: 1509459854.883901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 18058 bytes. Presentation time: 1509459854.949901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17679 bytes. Presentation time: 1509459854.983901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17784 bytes. Presentation time: 1509459855.016901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17766 bytes. Presentation time: 1509459855.049901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17719 bytes. Presentation time: 1509459855.083901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 18027 bytes. Presentation time: 1509459855.149901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17820 bytes. Presentation time: 1509459855.183901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17731 bytes. Presentation time: 1509459855.216901!
linux 编译so库
user@g1060server:~/mjl/algo/live$
./genMakefiles config.linux-with-shared-libraries
cat: config.config.linux-with-shared-libraries: 没有那个文件或目录
cat: config.config.linux-with-shared-libraries: 没有那个文件或目录
cat: config.config.linux-with-shared-libraries: 没有那个文件或目录
cat: config.config.linux-with-shared-libraries: 没有那个文件或目录
cat: config.config.linux-with-shared-libraries: 没有那个文件或目录
cat: config.config.linux-with-shared-libraries: 没有那个文件或目录
cat: config.config.linux-with-shared-libraries: 没有那个文件或目录
cat: config.config.linux-with-shared-libraries: 没有那个文件或目录
mv config.linux-with-shared-libraries linux-with-shared-libraries
user@g1060server:~/mjl/algo/live$ ./genMakefiles linux-with-shared-libraries
user@g1060server:~/mjl/algo/live$ make
cd liveMedia ; make
make[1]: 正在进入目录 `/home/user/mjl/algo/live/liveMedia'
live555的提取RTP包的问题
根据业务的要求,希望一路摄像头能够给很多路提供实时视频流查看,以及提供给其他解码器解码,或者必要的时候自己解码
我们知道一个摄像头一般能支持4-5直接访问已经不错了,要是大于这个数字,一般请求就没有应答
所以我们只能在软件上实现处理,第一个访问,软件直接去拉一路,后面访问的都是软件拉取那一路的转发,或者解码后的转发
看了一下live555的功能 testRTSPClient里面出来的好像是RTP包
于是测试了一把,发现得到的包,直接扔进解码器里面不能出任何结果
所以就怀疑是RTP包的问题,于是抓包和下面日志得到的长度对比,完全不对
Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 18027 bytes. Presentation time: 1509459855.149901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17820 bytes. Presentation time: 1509459855.183901! Stream "rtsp://admin:jyzbyj306@192.168.3.229:554/h264/ch1/main/av_stream/"; video/H264: Received 17731 bytes. Presentation time: 1509459855.216901!
也就是testRTSPClient里面的这段代码(蓝色部分是我添加的回调),数据完全不对
void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned /*durationInMicroseconds*/) { msStreamCallback((char*)fReceiveBuffer, frameSize); //QTSS_ReflectRTPData(fRelaySession, (char*)fReceiveBuffer, frameSize, fSubsession.trackIndex()); // We've just received a frame of data. (Optionally) print out information about it: #ifdef DEBUG_PRINT_EACH_RECEIVED_FRAME if (fStreamId != NULL) envir() << "Stream \"" << fStreamId << "\"; "; envir() << fSubsession.mediumName() << "/" << fSubsession.codecName() << ":\tReceived " << frameSize << " bytes"; if (numTruncatedBytes > 0) envir() << " (with " << numTruncatedBytes << " bytes truncated)"; char uSecsStr[6 + 1]; // used to output the 'microseconds' part of the presentation time sprintf(uSecsStr, "%06u", (unsigned)presentationTime.tv_usec); // envir() << ".\tPresentation time: " << (int)presentationTime.tv_sec << "." << uSecsStr; fSubsession.rtpSource(); if (fSubsession.rtpSource() != NULL && !fSubsession.rtpSource()->hasBeenSynchronizedUsingRTCP()) { fSubsession.rtpSource()->envir() << "!"; // mark the debugging output to indicate that this presentation time is not RTCP-synchronized } #ifdef DEBUG_PRINT_NPT envir() << "\tNPT: " << fSubsession.getNormalPlayTime(presentationTime); #endif envir() << "\n"; #endif // Then continue, to request the next frame of data: continuePlaying(); } Boolean DummySink::continuePlaying() { if (fSource == NULL) return False; // sanity check (should not happen) // Request the next frame of data from our input source. "afterGettingFrame()" will get called later, when it arrives: fSource->getNextFrame(fReceiveBuffer, DUMMY_SINK_RECEIVE_BUFFER_SIZE, afterGettingFrame, this, onSourceClosure, this); return True; }
所以这个程序对原始的rtp做了处理,具体是什么处理不太清楚,不过从网上的资料来看,原始的RTP好像不能从哪里直接拿
为了找到他的数据处理过程,就看他的回调如何处理
fSource->getNextFrame
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; fMaxSize = maxSize; 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(); }
fAfterGettingFunc = afterGettingFunc;
把afterGettingFunc保存进了成员变量,并且调用了doGetNextFrame();
而doGetNextFrame()是一个虚函数,也就是说谁继承,谁实现
我们关心的RTPSource正好是FramedSource的子类,可是依然没实现doGetNextFrame()
class RTPSource: public FramedSource {
而MultiFramedRTPSource实现了doGetNextFrame()方法
class MultiFramedRTPSource: public RTPSource { protected: MultiFramedRTPSource(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat, unsigned rtpTimestampFrequency, BufferedPacketFactory* packetFactory = NULL); // virtual base class virtual ~MultiFramedRTPSource(); virtual Boolean processSpecialHeader(BufferedPacket* packet, unsigned& resultSpecialHeaderSize); // Subclasses redefine this to handle any special, payload format // specific header that follows the RTP header. virtual Boolean packetIsUsableInJitterCalculation(unsigned char* packet, unsigned packetSize); // The default implementation returns True, but this can be redefined protected: Boolean fCurrentPacketBeginsFrame; Boolean fCurrentPacketCompletesFrame; protected: // redefined virtual functions: virtual void doStopGettingFrames(); private: // redefined virtual functions: virtual void doGetNextFrame(); virtual void setPacketReorderingThresholdTime(unsigned uSeconds);
void MultiFramedRTPSource::doGetNextFrame() { if (!fAreDoingNetworkReads) { // Turn on background read handling of incoming packets: fAreDoingNetworkReads = True; TaskScheduler::BackgroundHandlerProc* handler = (TaskScheduler::BackgroundHandlerProc*)&networkReadHandler; fRTPInterface.startNetworkReading(handler); } fSavedTo = fTo; fSavedMaxSize = fMaxSize; fFrameSize = 0; // for now fNeedDelivery = True; doGetNextFrame1(); }
void MultiFramedRTPSource::doGetNextFrame1() { while (fNeedDelivery) { // If we already have packet data available, then deliver it now. Boolean packetLossPrecededThis; BufferedPacket* nextPacket = fReorderingBuffer->getNextCompletedPacket(packetLossPrecededThis); if (nextPacket == NULL) break; fNeedDelivery = False; if (nextPacket->useCount() == 0) { // Before using the packet, check whether it has a special header // that needs to be processed: unsigned specialHeaderSize; if (!processSpecialHeader(nextPacket, specialHeaderSize)) { // Something's wrong with the header; reject the packet: fReorderingBuffer->releaseUsedPacket(nextPacket); fNeedDelivery = True; continue; } nextPacket->skip(specialHeaderSize); } // Check whether we're part of a multi-packet frame, and whether // there was packet loss that would render this packet unusable: if (fCurrentPacketBeginsFrame) { if (packetLossPrecededThis || fPacketLossInFragmentedFrame) { // We didn't get all of the previous frame. // Forget any data that we used from it: fTo = fSavedTo; fMaxSize = fSavedMaxSize; fFrameSize = 0; } fPacketLossInFragmentedFrame = False; } else if (packetLossPrecededThis) { // We're in a multi-packet frame, with preceding packet loss fPacketLossInFragmentedFrame = True; } if (fPacketLossInFragmentedFrame) { // This packet is unusable; reject it: fReorderingBuffer->releaseUsedPacket(nextPacket); fNeedDelivery = True; continue; } // The packet is usable. Deliver all or part of it to our caller: unsigned frameSize; nextPacket->use(fTo, fMaxSize, frameSize, fNumTruncatedBytes, fCurPacketRTPSeqNum, fCurPacketRTPTimestamp, fPresentationTime, fCurPacketHasBeenSynchronizedUsingRTCP, fCurPacketMarkerBit); fFrameSize += frameSize; if (!nextPacket->hasUsableData()) { // We're completely done with this packet now fReorderingBuffer->releaseUsedPacket(nextPacket); } if (fCurrentPacketCompletesFrame && fFrameSize > 0) { // We have all the data that the client wants. if (fNumTruncatedBytes > 0) { envir() << "MultiFramedRTPSource::doGetNextFrame1(): The total received frame size exceeds the client's buffer size (" << fSavedMaxSize << "). " << fNumTruncatedBytes << " bytes of trailing data will be dropped!\n"; } // Call our own 'after getting' function, so that the downstream object can consume the data: if (fReorderingBuffer->isEmpty()) { // Common case optimization: There are no more queued incoming packets, so this code will not get // executed again without having first returned to the event loop. Call our 'after getting' function // directly, because there's no risk of a long chain of recursion (and thus stack overflow): afterGetting(this); } else { // Special case: Call our 'after getting' function via the event loop. nextTask() = envir().taskScheduler().scheduleDelayedTask(0, (TaskFunc*)FramedSource::afterGetting, this); } } else { // This packet contained fragmented data, and does not complete // the data that the client wants. Keep getting data: fTo += frameSize; fMaxSize -= frameSize; fNeedDelivery = True; } } }
//TODO::中间分析环节
live555的底层读取流也是用简单的socket实现
Boolean RTPInterface::handleRead(unsigned char* buffer, unsigned bufferMaxSize, unsigned& bytesRead, struct sockaddr_in& fromAddress, int& tcpSocketNum, unsigned char& tcpStreamChannelId, Boolean& packetReadWasIncomplete) { packetReadWasIncomplete = False; // by default Boolean readSuccess; if (fNextTCPReadStreamSocketNum < 0) { // Normal case: read from the (datagram) 'groupsock': tcpSocketNum = -1; readSuccess = fGS->handleRead(buffer, bufferMaxSize, bytesRead, fromAddress); } else { // Read from the TCP connection: tcpSocketNum = fNextTCPReadStreamSocketNum; tcpStreamChannelId = fNextTCPReadStreamChannelId; bytesRead = 0; unsigned totBytesToRead = fNextTCPReadSize; if (totBytesToRead > bufferMaxSize) totBytesToRead = bufferMaxSize; unsigned curBytesToRead = totBytesToRead; int curBytesRead; while ((curBytesRead = readSocket(envir(), fNextTCPReadStreamSocketNum, &buffer[bytesRead], curBytesToRead, fromAddress)) > 0) { bytesRead += curBytesRead; if (bytesRead >= totBytesToRead) break; curBytesToRead -= curBytesRead; } fNextTCPReadSize -= bytesRead; if (fNextTCPReadSize == 0) { // We've read all of the data that we asked for readSuccess = True; } else if (curBytesRead < 0) { // There was an error reading the socket bytesRead = 0; readSuccess = False; } else { // We need to read more bytes, and there was not an error reading the socket packetReadWasIncomplete = True; return True; } fNextTCPReadStreamSocketNum = -1; // default, for next time } if (readSuccess && fAuxReadHandlerFunc != NULL) { // Also pass the newly-read packet data to our auxilliary handler: (*fAuxReadHandlerFunc)(fAuxReadHandlerClientData, buffer, bytesRead); } return readSuccess; }
其中的
while ((curBytesRead = readSocket(envir(), fNextTCPReadStreamSocketNum, &buffer[bytesRead], curBytesToRead, fromAddress)) > 0)
就是读取流的代码,可以看到它的底层实现
int readSocket(UsageEnvironment& env, int socket, unsigned char* buffer, unsigned bufferSize, struct sockaddr_in& fromAddress) { SOCKLEN_T addressSize = sizeof fromAddress; int bytesRead = recvfrom(socket, (char*)buffer, bufferSize, 0, (struct sockaddr*)&fromAddress, &addressSize); if (bytesRead < 0) { //##### HACK to work around bugs in Linux and Windows: int err = env.getErrno(); if (err == 111 /*ECONNREFUSED (Linux)*/ #if defined(__WIN32__) || defined(_WIN32) // What a piece of crap Windows is. Sometimes // recvfrom() returns -1, but with an 'errno' of 0. // This appears not to be a real error; just treat // it as if it were a read of zero bytes, and hope // we don't have to do anything else to 'reset' // this alleged error: || err == 0 || err == EWOULDBLOCK #else || err == EAGAIN #endif || err == 113 /*EHOSTUNREACH (Linux)*/) { // Why does Linux return this for datagram sock? fromAddress.sin_addr.s_addr = 0; return 0; } //##### END HACK socketErr(env, "recvfrom() error: "); } else if (bytesRead == 0) { // "recvfrom()" on a stream socket can return 0 if the remote end has closed the connection. Treat this as an error: return -1; } return bytesRead; }
int bytesRead = recvfrom(socket, (char*)buffer, bufferSize, 0, (struct sockaddr*)&fromAddress, &addressSize);
可谓一目了然