diff --git a/.coveralls.yml b/.coveralls.yml index 912d4d4..a21d5a2 100644 --- a/.coveralls.yml +++ b/.coveralls.yml @@ -1 +1 @@ -repo_token: hZ360i2wZGvYbjQwy6d8iXgS9hd4mhcHr +repo_token: JHXnPIUwlIxL9EWjL7zypPbTStK0teBOu diff --git a/README.md b/README.md index 2354898..3cf4e93 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,15 @@ Download -------- [Latest build](https://github.com/mpromonet/h264_v4l2_rtspserver/releases/latest/) +Before build +------- +The build try to install live555 package using apt-get, however in order to install live555 disabling check of port reuse, you can proceed like this: + + wget http://www.live555.com/liveMedia/public/live555-latest.tar.gz -O - | tar xvzf - + cd live + ./genMakefiles linux + sudo make CPPFLAGS=-DALLOW_RTSP_SERVER_PORT_REUSE=1 install + Build ------- cmake . && make @@ -38,13 +47,6 @@ Build If it fails you will need to install liblivemedia-dev liblog4cpp5-dev. If it still not work you will need to read Makefile. -In order to build live555 disabling check of port reuse, you can proceed like this: - - wget http://www.live555.com/liveMedia/public/live555-latest.tar.gz - | tar xvzf - - cd live - ./genMakefile linux - sudo make CPPFLAGS=-DALLOW_RTSP_SERVER_PORT_REUSE=1 install - Install --------- make install @@ -52,7 +54,7 @@ Install Build Package ------------- cpack . - dpkg -i h264_v4l2_rtspserver*.deb + dpkg -i v4l2rtspserver*.deb Using Raspberry Pi Camera ------------------------- @@ -68,18 +70,18 @@ Using with v4l2loopback ----------------------- For camera providing uncompress format [v4l2tools](https://github.com/mpromonet/v4l2tools) can compress the video to an intermediate virtual V4L2 device [v4l2loopback](https://github.com/umlaeute/v4l2loopback): - /dev/video0 (camera device)-> v4l2compress_h264 -> /dev/video10 (v4l2loopback device) -> h264_v4l2_rtspserver + /dev/video0 (camera device)-> v4l2compress_h264 -> /dev/video10 (v4l2loopback device) -> v4l2rtspserver This workflow could be set using : modprobe v4l2loopback video_nr=10 v4l2compress_h264 /dev/video0 /dev/video10 & - h264_v4l2_rtspserver /dev/video10 & + v4l2rtspserver /dev/video10 & Usage ----- - ./h264_v4l2_rtspserver [-v[v]] [-Q queueSize] [-O file] \ - [-I interface] [-P RTSP port] [-T RTSP/HTTP port] [-m multicast url] [-u unicast url] [-M multicast addr] [-c] [-t timeout] \ + ./v4l2rtspserver [-v[v]] [-Q queueSize] [-O file] \ + [-I interface] [-P RTSP port] [-p RTSP/HTTP port] [-m multicast url] [-u unicast url] [-M multicast addr] [-c] [-t timeout] \ [-r] [-s] [-W width] [-H height] [-F fps] [device1] [device2] -v : verbose -vv : very verbose @@ -88,18 +90,31 @@ Usage RTSP options : -I addr : RTSP interface (default autodetect) -P port : RTSP port (default 8554) - -T port : RTSP over HTTP port (default 0) + -p port : RTSP over HTTP port (default 0) -u url : unicast url (default unicast) -m url : multicast url (default multicast) -M addr : multicast group:port (default is random_address:20000) -c : don't repeat config (default repeat config before IDR frame) -t secs : RTCP expiration timeout (default 65) + -T : send Transport Stream instead of elementary Stream + -S secs : HTTP segment duration (enable HLS & MPEG-DASH) V4L2 options : -r : V4L2 capture using read interface (default use memory mapped buffers) + -w : V4L2 capture using write interface (default use memory mapped buffers) -s : V4L2 capture using live555 mainloop (default use a separated reading thread) - -f : V4L2 capture using current format (-W,-H,-F are ignored) + -f : V4L2 capture using current capture format (-W,-H,-F are ignored) + -fformat : V4L2 capture using format (-W,-H,-F are used) -W width : V4L2 capture width (default 640) -H height: V4L2 capture height (default 480) -F fps : V4L2 capture framerate (default 25) device : V4L2 capture device (default /dev/video0) +Receiving HTTP streams +----------------------- +When v4l2rtspserver is started with '-S' arguments it give access to streams through HTTP. These streams could be reveced : + * for MPEG-DASH with : + MP4Client http://..../unicast.mpd + * for HLS with : + vlc http://..../unicast.m3u8 + gstreamer-launch-1.0 playbin uri=http://.../unicast.m3u8 + diff --git a/inc/HTTPServer.h b/inc/HTTPServer.h index 18f6c23..a9e8d18 100644 --- a/inc/HTTPServer.h +++ b/inc/HTTPServer.h @@ -27,15 +27,16 @@ class HTTPServer : public RTSPServer HTTPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr) : RTSPServer::RTSPClientConnection(ourServer, clientSocket, clientAddr), fClientSessionId(0), fTCPSink(NULL) { } + virtual ~HTTPClientConnection(); private: void sendHeader(const char* contentType, unsigned int contentLength); void streamSource(FramedSource* source); ServerMediaSubsession* getSubsesion(const char* urlSuffix); - void sendM3u8PlayList(char const* urlSuffix); - void sendMpdPlayList(char const* urlSuffix); - void handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* fullRequestStr); + bool sendM3u8PlayList(char const* urlSuffix); + bool sendMpdPlayList(char const* urlSuffix); + virtual void handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* fullRequestStr); static void afterStreaming(void* clientData); private: diff --git a/inc/MJPEGVideoSource.h b/inc/MJPEGVideoSource.h index 0494b4b..82a9888 100644 --- a/inc/MJPEGVideoSource.h +++ b/inc/MJPEGVideoSource.h @@ -40,7 +40,7 @@ class MJPEGVideoSource : public JPEGVideoSource int headerSize = 0; bool headerOk = false; fFrameSize = 0; - + for (unsigned int i = 0; i < frameSize ; ++i) { // SOF @@ -53,13 +53,13 @@ class MJPEGVideoSource : public JPEGVideoSource // DQT if ( (i+5+64) < frameSize && (fTo[i] == 0xFF) && (fTo[i+1] == 0xDB)) { - int quantSize = fTo[i+3]; - int quantIdx = fTo[i+4]; - if (quantIdx < 3) + unsigned int quantSize = fTo[i+3]-4; + unsigned int quantIdx = fTo[i+4]; + if (quantSize*quantIdx+quantSize <= sizeof(m_qTable)) { - if ( quantIdx+1 > m_qTableCount ) - m_qTableCount = quantIdx+1; - memcpy(m_qTable + quantIdx*64, fTo + i + 5, 64); + memcpy(m_qTable + quantSize*quantIdx, fTo + i + 5, quantSize); + if (quantSize*quantIdx+quantSize > m_qTableSize) + m_qTableSize = quantSize*quantIdx+quantSize; } } // End of header @@ -90,9 +90,9 @@ class MJPEGVideoSource : public JPEGVideoSource { length = 0; precision = 0; - if (m_qTableCount > 0) + if (m_qTableSize > 0) { - length = 64*m_qTableCount; + length = m_qTableSize; } return m_qTable; } @@ -100,7 +100,7 @@ class MJPEGVideoSource : public JPEGVideoSource protected: MJPEGVideoSource(UsageEnvironment& env, FramedSource* source) : JPEGVideoSource(env), m_inputSource(source), - m_width(0), m_height(0), m_qTableCount(0), + m_width(0), m_height(0), m_qTableSize(0), m_type(0) { memset(&m_qTable,0,sizeof(m_qTable)); @@ -114,7 +114,7 @@ class MJPEGVideoSource : public JPEGVideoSource FramedSource* m_inputSource; u_int8_t m_width; u_int8_t m_height; - u_int8_t m_qTable[64*3]; - int m_qTableCount; + u_int8_t m_qTable[128*2]; + unsigned int m_qTableSize; u_int8_t m_type; }; diff --git a/inc/ServerMediaSubsession.h b/inc/ServerMediaSubsession.h index 75a88c9..3f6c4ea 100644 --- a/inc/ServerMediaSubsession.h +++ b/inc/ServerMediaSubsession.h @@ -155,7 +155,7 @@ class HLSServerMediaSubsession : public UnicastServerMediaSubsession outputBuffer.append((const char*)m_buffer, frameSize); // remove old buffers - while (m_outputBuffers.size()>3) + while (m_outputBuffers.size()>5) { m_outputBuffers.erase(m_outputBuffers.begin()); } diff --git a/index.html b/index.html new file mode 100644 index 0000000..99ab4b2 --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/src/HTTPServer.cpp b/src/HTTPServer.cpp index bdf873c..884f15e 100644 --- a/src/HTTPServer.cpp +++ b/src/HTTPServer.cpp @@ -13,6 +13,8 @@ #include +#include +#include #include "RTSPServer.hh" #include "RTSPCommon.hh" @@ -71,20 +73,18 @@ ServerMediaSubsession* HTTPServer::HTTPClientConnection::getSubsesion(const char return subsession; } -void HTTPServer::HTTPClientConnection::sendM3u8PlayList(char const* urlSuffix) +bool HTTPServer::HTTPClientConnection::sendM3u8PlayList(char const* urlSuffix) { ServerMediaSubsession* subsession = this->getSubsesion(urlSuffix); if (subsession == NULL) { - handleHTTPCmd_notSupported(); - return; + return false; } float duration = subsession->duration(); if (duration <= 0.0) { - handleHTTPCmd_notSupported(); - return; + return false; } unsigned int startTime = subsession->getCurrentNPT(NULL); @@ -92,7 +92,7 @@ void HTTPServer::HTTPClientConnection::sendM3u8PlayList(char const* urlSuffix) unsigned sliceDuration = httpServer->m_hlsSegment; std::ostringstream os; os << "#EXTM3U\r\n" - << "#EXT-X-ALLOW-CACHE:YES\r\n" + << "#EXT-X-ALLOW-CACHE:NO\r\n" << "#EXT-X-MEDIA-SEQUENCE:" << startTime << "\r\n" << "#EXT-X-TARGETDURATION:" << sliceDuration << "\r\n"; @@ -102,6 +102,7 @@ void HTTPServer::HTTPClientConnection::sendM3u8PlayList(char const* urlSuffix) os << urlSuffix << "?segment=" << (startTime+slice*sliceDuration) << "\r\n"; } + envir() << "send M3u8 playlist:" << urlSuffix <<"\n"; const std::string& playList(os.str()); // send response header @@ -111,22 +112,22 @@ void HTTPServer::HTTPClientConnection::sendM3u8PlayList(char const* urlSuffix) u_int8_t* playListBuffer = new u_int8_t[playList.size()]; memcpy(playListBuffer, playList.c_str(), playList.size()); this->streamSource(ByteStreamMemoryBufferSource::createNew(envir(), playListBuffer, playList.size())); + + return true; } -void HTTPServer::HTTPClientConnection::sendMpdPlayList(char const* urlSuffix) +bool HTTPServer::HTTPClientConnection::sendMpdPlayList(char const* urlSuffix) { ServerMediaSubsession* subsession = this->getSubsesion(urlSuffix); if (subsession == NULL) { - handleHTTPCmd_notSupported(); - return; + return false; } float duration = subsession->duration(); if (duration <= 0.0) { - handleHTTPCmd_notSupported(); - return; + return false; } unsigned int startTime = subsession->getCurrentNPT(NULL); @@ -136,16 +137,13 @@ void HTTPServer::HTTPClientConnection::sendMpdPlayList(char const* urlSuffix) os << "\r\n" << "\r\n" - << "" - << "\r\n"; + << "\r\n"; - for (unsigned int slice=0; slice*sliceDuration\r\n"; - } - os << "\r\n"; + os << "\r\n"; + os << "\r\n"; os << "\r\n"; + envir() << "send MPEG-DASH playlist:" << urlSuffix <<"\n"; const std::string& playList(os.str()); // send response header @@ -155,6 +153,8 @@ void HTTPServer::HTTPClientConnection::sendMpdPlayList(char const* urlSuffix) u_int8_t* playListBuffer = new u_int8_t[playList.size()]; memcpy(playListBuffer, playList.c_str(), playList.size()); this->streamSource(ByteStreamMemoryBufferSource::createNew(envir(), playListBuffer, playList.size())); + + return true; } @@ -173,15 +173,48 @@ void HTTPServer::HTTPClientConnection::handleHTTPCmd_StreamingGET(char const* ur streamName.assign(url.substr(0,pos)); ext.assign(url.substr(pos+1)); } + bool ok; if (ext == "mpd") { // MPEG-DASH Playlist - this->sendMpdPlayList(streamName.c_str()); + ok = this->sendMpdPlayList(streamName.c_str()); } else { // HLS Playlist - this->sendM3u8PlayList(streamName.c_str()); + ok = this->sendM3u8PlayList(streamName.c_str()); + } + + if (!ok) + { + // send local files + size_t pos = url.find_last_of("/"); + if (pos != std::string::npos) + { + url.erase(pos); + } + if (url.empty()) + { + url = "index.html"; + ext = "html"; + } + if (ext=="js") ext ="javascript"; + std::ifstream file(url.c_str()); + if (file.is_open()) + { + envir() << "send file:" << url.c_str() <<"\n"; + std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + std::string mime("text/"); + mime.append(ext); + this->sendHeader(mime.c_str(), content.size()); + this->streamSource(ByteStreamMemoryBufferSource::createNew(envir(), (u_int8_t*)content.c_str(), content.size())); + ok = true; + } + } + + if (!ok) + { + handleHTTPCmd_notSupported(); } } else @@ -220,15 +253,15 @@ void HTTPServer::HTTPClientConnection::handleHTTPCmd_StreamingGET(char const* ur { // For some reason, we do not know the size of the requested range. We can't handle this request: handleHTTPCmd_notSupported(); - return; } + else + { + // send response header + this->sendHeader("video/mp2t", numBytes); - // send response header - this->sendHeader("video/mp2t", numBytes); - - // stream body - this->streamSource(subsession->getStreamSource(streamToken)); - + // stream body + this->streamSource(subsession->getStreamSource(streamToken)); + } } } @@ -241,6 +274,17 @@ void HTTPServer::HTTPClientConnection::afterStreaming(void* clientData) clientConnection->fIsActive = False; // will cause the object to get deleted at the end of handling the request } else { // We're no longer handling a request; delete the object now: -// delete clientConnection; +// delete clientConnection; } } + +HTTPServer::HTTPClientConnection::~HTTPClientConnection() +{ + if (fTCPSink != NULL) + { + FramedSource* oldSource = fTCPSink->source(); + fTCPSink->stopPlaying(); + Medium::close(fTCPSink); + Medium::close(oldSource); + } +} diff --git a/src/main.cpp b/src/main.cpp index d461d73..16da658 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -173,11 +173,11 @@ int main(int argc, char** argv) bool repeatConfig = true; int timeout = 65; bool muxTS = false; - unsigned int hlsSegment = 10; + unsigned int hlsSegment = 0; // decode parameters int c = 0; - while ((c = getopt (argc, argv, "v::Q:O:" "I:P:p:m:u:M:ct:TS:" "rwsf::F:W:H:" "h")) != -1) + while ((c = getopt (argc, argv, "v::Q:O:" "I:P:p:m:u:M:ct:TS::" "rwsf::F:W:H:" "h")) != -1) { switch (c) { @@ -194,7 +194,7 @@ int main(int argc, char** argv) case 'c': repeatConfig = false; break; case 't': timeout = atoi(optarg); break; case 'T': muxTS = true; break; - case 'S': hlsSegment = atoi(optarg); muxTS=true; break; + case 'S': hlsSegment = optarg ? atoi(optarg) : 5; muxTS=true; break; // V4L2 case 'r': ioTypeIn = V4l2DeviceFactory::IOTYPE_READ; break; case 'w': ioTypeOut = V4l2DeviceFactory::IOTYPE_READ; break; @@ -208,13 +208,13 @@ int main(int argc, char** argv) default: { std::cout << argv[0] << " [-v[v]] [-Q queueSize] [-O file]" << std::endl; - std::cout << "\t [-I interface] [-P RTSP port] [-T RTSP/HTTP port] [-m multicast url] [-u unicast url] [-M multicast addr] [-c] [-t timeout]" << std::endl; - std::cout << "\t [-r] [-w] [-s] [-W width] [-H height] [-F fps] [device] [device]" << std::endl; + std::cout << "\t [-I interface] [-P RTSP port] [-p RTSP/HTTP port] [-m multicast url] [-u unicast url] [-M multicast addr] [-c] [-t timeout] [-T] [-S[duration]]" << std::endl; + std::cout << "\t [-r] [-w] [-s] [-f[format] [-W width] [-H height] [-F fps] [device] [device]" << std::endl; std::cout << "\t -v : verbose" << std::endl; std::cout << "\t -vv : very verbose" << std::endl; std::cout << "\t -Q length : Number of frame queue (default "<< queueSize << ")" << std::endl; std::cout << "\t -O output : Copy captured frame to a file or a V4L2 device" << std::endl; - std::cout << "\t RTSP/RTP options :" << std::endl; + std::cout << "\t RTSP/RTP options :" << std::endl; std::cout << "\t -I addr : RTSP interface (default autodetect)" << std::endl; std::cout << "\t -P port : RTSP port (default "<< rtspPort << ")" << std::endl; std::cout << "\t -p port : RTSP over HTTP port (default "<< rtspOverHTTPPort << ")" << std::endl; @@ -222,19 +222,19 @@ int main(int argc, char** argv) std::cout << "\t -m url : multicast url (default " << murl << ")" << std::endl; std::cout << "\t -M addr : multicast group:port (default is random_address:20000)" << std::endl; std::cout << "\t -c : don't repeat config (default repeat config before IDR frame)" << std::endl; - std::cout << "\t -t secs : RTCP expiration timeout (default " << timeout << ")" << std::endl; + std::cout << "\t -t timeout: RTCP expiration timeout in seconds (default " << timeout << ")" << std::endl; std::cout << "\t -T : send Transport Stream instead of elementary Stream" << std::endl; - std::cout << "\t -S secs : HTTP segment duration (enable HLS & MPEG-DASH)" << std::endl; - std::cout << "\t V4L2 options :" << std::endl; + std::cout << "\t -S[duration]: enable HLS & MPEG-DASH with segment duration in seconds (default 5)" << std::endl; + std::cout << "\t V4L2 options :" << std::endl; std::cout << "\t -r : V4L2 capture using read interface (default use memory mapped buffers)" << std::endl; std::cout << "\t -w : V4L2 capture using write interface (default use memory mapped buffers)"<< std::endl; std::cout << "\t -s : V4L2 capture using live555 mainloop (default use a reader thread)" << std::endl; std::cout << "\t -f : V4L2 capture using current capture format (-W,-H,-F are ignored)" << std::endl; - std::cout << "\t -f format : V4L2 capture using format (-W,-H,-F are used)" << std::endl; + std::cout << "\t -fformat : V4L2 capture using format (-W,-H,-F are used)" << std::endl; std::cout << "\t -W width : V4L2 capture width (default "<< width << ")" << std::endl; std::cout << "\t -H height : V4L2 capture height (default "<< height << ")" << std::endl; std::cout << "\t -F fps : V4L2 capture framerate (default "<< fps << ")" << std::endl; - std::cout << "\t device : V4L2 capture device (default "<< dev_name << ")" << std::endl; + std::cout << "\t device : V4L2 capture device (default "<< dev_name << ")" << std::endl; exit(0); } } @@ -351,9 +351,13 @@ int main(int argc, char** argv) } // Create Unicast Session - if (muxTS) + if (hlsSegment > 0) { addSession(rtspServer, baseUrl+url, HLSServerMediaSubsession::createNew(*env,replicator,rtpFormat, hlsSegment)); + struct in_addr ip; + ip.s_addr = ourIPAddress(*env); + LOG(NOTICE) << "HLS http://" << inet_ntoa(ip) << ":" << rtspPort << "/" << baseUrl+url << ".m3u8"; + LOG(NOTICE) << "MPEG-DASH http://" << inet_ntoa(ip) << ":" << rtspPort << "/" << baseUrl+url << ".mpd"; } else {