mirror of
https://github.com/mpromonet/v4l2rtspserver
synced 2024-11-19 15:25:34 +00:00
Merge branch 'master' of https://github.com/mpromonet/v4l2rtspserver
This commit is contained in:
commit
85c7fd3f6a
@ -1 +1 @@
|
|||||||
repo_token: hZ360i2wZGvYbjQwy6d8iXgS9hd4mhcHr
|
repo_token: JHXnPIUwlIxL9EWjL7zypPbTStK0teBOu
|
||||||
|
43
README.md
43
README.md
@ -31,6 +31,15 @@ Download
|
|||||||
--------
|
--------
|
||||||
[Latest build](https://github.com/mpromonet/h264_v4l2_rtspserver/releases/latest/)
|
[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
|
Build
|
||||||
-------
|
-------
|
||||||
cmake . && make
|
cmake . && make
|
||||||
@ -38,13 +47,6 @@ Build
|
|||||||
If it fails you will need to install liblivemedia-dev liblog4cpp5-dev.
|
If it fails you will need to install liblivemedia-dev liblog4cpp5-dev.
|
||||||
If it still not work you will need to read Makefile.
|
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
|
Install
|
||||||
---------
|
---------
|
||||||
make install
|
make install
|
||||||
@ -52,7 +54,7 @@ Install
|
|||||||
Build Package
|
Build Package
|
||||||
-------------
|
-------------
|
||||||
cpack .
|
cpack .
|
||||||
dpkg -i h264_v4l2_rtspserver*.deb
|
dpkg -i v4l2rtspserver*.deb
|
||||||
|
|
||||||
Using Raspberry Pi Camera
|
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):
|
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 :
|
This workflow could be set using :
|
||||||
|
|
||||||
modprobe v4l2loopback video_nr=10
|
modprobe v4l2loopback video_nr=10
|
||||||
v4l2compress_h264 /dev/video0 /dev/video10 &
|
v4l2compress_h264 /dev/video0 /dev/video10 &
|
||||||
h264_v4l2_rtspserver /dev/video10 &
|
v4l2rtspserver /dev/video10 &
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
./h264_v4l2_rtspserver [-v[v]] [-Q queueSize] [-O file] \
|
./v4l2rtspserver [-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] \
|
[-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]
|
[-r] [-s] [-W width] [-H height] [-F fps] [device1] [device2]
|
||||||
-v : verbose
|
-v : verbose
|
||||||
-vv : very verbose
|
-vv : very verbose
|
||||||
@ -88,18 +90,31 @@ Usage
|
|||||||
RTSP options :
|
RTSP options :
|
||||||
-I addr : RTSP interface (default autodetect)
|
-I addr : RTSP interface (default autodetect)
|
||||||
-P port : RTSP port (default 8554)
|
-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)
|
-u url : unicast url (default unicast)
|
||||||
-m url : multicast url (default multicast)
|
-m url : multicast url (default multicast)
|
||||||
-M addr : multicast group:port (default is random_address:20000)
|
-M addr : multicast group:port (default is random_address:20000)
|
||||||
-c : don't repeat config (default repeat config before IDR frame)
|
-c : don't repeat config (default repeat config before IDR frame)
|
||||||
-t secs : RTCP expiration timeout (default 65)
|
-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 :
|
V4L2 options :
|
||||||
-r : V4L2 capture using read interface (default use memory mapped buffers)
|
-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)
|
-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)
|
-W width : V4L2 capture width (default 640)
|
||||||
-H height: V4L2 capture height (default 480)
|
-H height: V4L2 capture height (default 480)
|
||||||
-F fps : V4L2 capture framerate (default 25)
|
-F fps : V4L2 capture framerate (default 25)
|
||||||
device : V4L2 capture device (default /dev/video0)
|
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
|
||||||
|
|
||||||
|
@ -27,15 +27,16 @@ class HTTPServer : public RTSPServer
|
|||||||
HTTPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
|
HTTPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
|
||||||
: RTSPServer::RTSPClientConnection(ourServer, clientSocket, clientAddr), fClientSessionId(0), fTCPSink(NULL) {
|
: RTSPServer::RTSPClientConnection(ourServer, clientSocket, clientAddr), fClientSessionId(0), fTCPSink(NULL) {
|
||||||
}
|
}
|
||||||
|
virtual ~HTTPClientConnection();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void sendHeader(const char* contentType, unsigned int contentLength);
|
void sendHeader(const char* contentType, unsigned int contentLength);
|
||||||
void streamSource(FramedSource* source);
|
void streamSource(FramedSource* source);
|
||||||
ServerMediaSubsession* getSubsesion(const char* urlSuffix);
|
ServerMediaSubsession* getSubsesion(const char* urlSuffix);
|
||||||
void sendM3u8PlayList(char const* urlSuffix);
|
bool sendM3u8PlayList(char const* urlSuffix);
|
||||||
void sendMpdPlayList(char const* urlSuffix);
|
bool sendMpdPlayList(char const* urlSuffix);
|
||||||
void handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* fullRequestStr);
|
virtual void handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* fullRequestStr);
|
||||||
static void afterStreaming(void* clientData);
|
static void afterStreaming(void* clientData);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -40,7 +40,7 @@ class MJPEGVideoSource : public JPEGVideoSource
|
|||||||
int headerSize = 0;
|
int headerSize = 0;
|
||||||
bool headerOk = false;
|
bool headerOk = false;
|
||||||
fFrameSize = 0;
|
fFrameSize = 0;
|
||||||
|
|
||||||
for (unsigned int i = 0; i < frameSize ; ++i)
|
for (unsigned int i = 0; i < frameSize ; ++i)
|
||||||
{
|
{
|
||||||
// SOF
|
// SOF
|
||||||
@ -53,13 +53,13 @@ class MJPEGVideoSource : public JPEGVideoSource
|
|||||||
// DQT
|
// DQT
|
||||||
if ( (i+5+64) < frameSize && (fTo[i] == 0xFF) && (fTo[i+1] == 0xDB))
|
if ( (i+5+64) < frameSize && (fTo[i] == 0xFF) && (fTo[i+1] == 0xDB))
|
||||||
{
|
{
|
||||||
int quantSize = fTo[i+3];
|
unsigned int quantSize = fTo[i+3]-4;
|
||||||
int quantIdx = fTo[i+4];
|
unsigned int quantIdx = fTo[i+4];
|
||||||
if (quantIdx < 3)
|
if (quantSize*quantIdx+quantSize <= sizeof(m_qTable))
|
||||||
{
|
{
|
||||||
if ( quantIdx+1 > m_qTableCount )
|
memcpy(m_qTable + quantSize*quantIdx, fTo + i + 5, quantSize);
|
||||||
m_qTableCount = quantIdx+1;
|
if (quantSize*quantIdx+quantSize > m_qTableSize)
|
||||||
memcpy(m_qTable + quantIdx*64, fTo + i + 5, 64);
|
m_qTableSize = quantSize*quantIdx+quantSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// End of header
|
// End of header
|
||||||
@ -90,9 +90,9 @@ class MJPEGVideoSource : public JPEGVideoSource
|
|||||||
{
|
{
|
||||||
length = 0;
|
length = 0;
|
||||||
precision = 0;
|
precision = 0;
|
||||||
if (m_qTableCount > 0)
|
if (m_qTableSize > 0)
|
||||||
{
|
{
|
||||||
length = 64*m_qTableCount;
|
length = m_qTableSize;
|
||||||
}
|
}
|
||||||
return m_qTable;
|
return m_qTable;
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ class MJPEGVideoSource : public JPEGVideoSource
|
|||||||
protected:
|
protected:
|
||||||
MJPEGVideoSource(UsageEnvironment& env, FramedSource* source) : JPEGVideoSource(env),
|
MJPEGVideoSource(UsageEnvironment& env, FramedSource* source) : JPEGVideoSource(env),
|
||||||
m_inputSource(source),
|
m_inputSource(source),
|
||||||
m_width(0), m_height(0), m_qTableCount(0),
|
m_width(0), m_height(0), m_qTableSize(0),
|
||||||
m_type(0)
|
m_type(0)
|
||||||
{
|
{
|
||||||
memset(&m_qTable,0,sizeof(m_qTable));
|
memset(&m_qTable,0,sizeof(m_qTable));
|
||||||
@ -114,7 +114,7 @@ class MJPEGVideoSource : public JPEGVideoSource
|
|||||||
FramedSource* m_inputSource;
|
FramedSource* m_inputSource;
|
||||||
u_int8_t m_width;
|
u_int8_t m_width;
|
||||||
u_int8_t m_height;
|
u_int8_t m_height;
|
||||||
u_int8_t m_qTable[64*3];
|
u_int8_t m_qTable[128*2];
|
||||||
int m_qTableCount;
|
unsigned int m_qTableSize;
|
||||||
u_int8_t m_type;
|
u_int8_t m_type;
|
||||||
};
|
};
|
||||||
|
@ -155,7 +155,7 @@ class HLSServerMediaSubsession : public UnicastServerMediaSubsession
|
|||||||
outputBuffer.append((const char*)m_buffer, frameSize);
|
outputBuffer.append((const char*)m_buffer, frameSize);
|
||||||
|
|
||||||
// remove old buffers
|
// remove old buffers
|
||||||
while (m_outputBuffers.size()>3)
|
while (m_outputBuffers.size()>5)
|
||||||
{
|
{
|
||||||
m_outputBuffers.erase(m_outputBuffers.begin());
|
m_outputBuffers.erase(m_outputBuffers.begin());
|
||||||
}
|
}
|
||||||
|
17
index.html
Normal file
17
index.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<html>
|
||||||
|
<link rel="shortcut icon" href="about:blank"/>
|
||||||
|
<body>
|
||||||
|
<video controls id="video"></video>
|
||||||
|
<script src="https://cdn.jsdelivr.net/hls.js/latest/hls.js" ></script>
|
||||||
|
<script>
|
||||||
|
if (Hls.isSupported()) {
|
||||||
|
var video = document.getElementById("video");
|
||||||
|
var hls = new Hls();
|
||||||
|
hls.loadSource("unicast.m3u8");
|
||||||
|
hls.attachMedia(video);
|
||||||
|
hls.on(Hls.Events.MANIFEST_PARSED, function() { video.play(); });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -13,6 +13,8 @@
|
|||||||
|
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "RTSPServer.hh"
|
#include "RTSPServer.hh"
|
||||||
#include "RTSPCommon.hh"
|
#include "RTSPCommon.hh"
|
||||||
@ -71,20 +73,18 @@ ServerMediaSubsession* HTTPServer::HTTPClientConnection::getSubsesion(const char
|
|||||||
return subsession;
|
return subsession;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTTPServer::HTTPClientConnection::sendM3u8PlayList(char const* urlSuffix)
|
bool HTTPServer::HTTPClientConnection::sendM3u8PlayList(char const* urlSuffix)
|
||||||
{
|
{
|
||||||
ServerMediaSubsession* subsession = this->getSubsesion(urlSuffix);
|
ServerMediaSubsession* subsession = this->getSubsesion(urlSuffix);
|
||||||
if (subsession == NULL)
|
if (subsession == NULL)
|
||||||
{
|
{
|
||||||
handleHTTPCmd_notSupported();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float duration = subsession->duration();
|
float duration = subsession->duration();
|
||||||
if (duration <= 0.0)
|
if (duration <= 0.0)
|
||||||
{
|
{
|
||||||
handleHTTPCmd_notSupported();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int startTime = subsession->getCurrentNPT(NULL);
|
unsigned int startTime = subsession->getCurrentNPT(NULL);
|
||||||
@ -92,7 +92,7 @@ void HTTPServer::HTTPClientConnection::sendM3u8PlayList(char const* urlSuffix)
|
|||||||
unsigned sliceDuration = httpServer->m_hlsSegment;
|
unsigned sliceDuration = httpServer->m_hlsSegment;
|
||||||
std::ostringstream os;
|
std::ostringstream os;
|
||||||
os << "#EXTM3U\r\n"
|
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-MEDIA-SEQUENCE:" << startTime << "\r\n"
|
||||||
<< "#EXT-X-TARGETDURATION:" << sliceDuration << "\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";
|
os << urlSuffix << "?segment=" << (startTime+slice*sliceDuration) << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
envir() << "send M3u8 playlist:" << urlSuffix <<"\n";
|
||||||
const std::string& playList(os.str());
|
const std::string& playList(os.str());
|
||||||
|
|
||||||
// send response header
|
// send response header
|
||||||
@ -111,22 +112,22 @@ void HTTPServer::HTTPClientConnection::sendM3u8PlayList(char const* urlSuffix)
|
|||||||
u_int8_t* playListBuffer = new u_int8_t[playList.size()];
|
u_int8_t* playListBuffer = new u_int8_t[playList.size()];
|
||||||
memcpy(playListBuffer, playList.c_str(), playList.size());
|
memcpy(playListBuffer, playList.c_str(), playList.size());
|
||||||
this->streamSource(ByteStreamMemoryBufferSource::createNew(envir(), playListBuffer, 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);
|
ServerMediaSubsession* subsession = this->getSubsesion(urlSuffix);
|
||||||
if (subsession == NULL)
|
if (subsession == NULL)
|
||||||
{
|
{
|
||||||
handleHTTPCmd_notSupported();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float duration = subsession->duration();
|
float duration = subsession->duration();
|
||||||
if (duration <= 0.0)
|
if (duration <= 0.0)
|
||||||
{
|
{
|
||||||
handleHTTPCmd_notSupported();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int startTime = subsession->getCurrentNPT(NULL);
|
unsigned int startTime = subsession->getCurrentNPT(NULL);
|
||||||
@ -136,16 +137,13 @@ void HTTPServer::HTTPClientConnection::sendMpdPlayList(char const* urlSuffix)
|
|||||||
|
|
||||||
os << "<?xml version='1.0' encoding='UTF-8'?>\r\n"
|
os << "<?xml version='1.0' encoding='UTF-8'?>\r\n"
|
||||||
<< "<MPD type='dynamic' xmlns='urn:mpeg:DASH:schema:MPD:2011' profiles='urn:mpeg:dash:profile:full:2011' minimumUpdatePeriod='PT"<< sliceDuration <<"S' minBufferTime='" << sliceDuration << "'>\r\n"
|
<< "<MPD type='dynamic' xmlns='urn:mpeg:DASH:schema:MPD:2011' profiles='urn:mpeg:dash:profile:full:2011' minimumUpdatePeriod='PT"<< sliceDuration <<"S' minBufferTime='" << sliceDuration << "'>\r\n"
|
||||||
<< "<Period start='PT0S'><AdaptationSet segmentAlignment='true'><Representation mimeType='video/mp2t' codecs='' >"
|
<< "<Period start='PT0S'><AdaptationSet segmentAlignment='true'><Representation mimeType='video/mp2t' codecs='' >\r\n";
|
||||||
<< "<SegmentList duration='" << sliceDuration << "' startNumber='" << startTime << "' >\r\n";
|
|
||||||
|
|
||||||
for (unsigned int slice=0; slice*sliceDuration<duration; slice++)
|
os << "<SegmentTemplate duration='" << sliceDuration << "' media='" << urlSuffix << "?segment=$Number$' startNumber='" << startTime << "' />\r\n";
|
||||||
{
|
os << "</Representation></AdaptationSet></Period>\r\n";
|
||||||
os << "<SegmentURL media='" << urlSuffix << "?segment=" << (startTime+slice*sliceDuration) << "' />\r\n";
|
|
||||||
}
|
|
||||||
os << "</SegmentList></Representation></AdaptationSet></Period>\r\n";
|
|
||||||
os << "</MPD>\r\n";
|
os << "</MPD>\r\n";
|
||||||
|
|
||||||
|
envir() << "send MPEG-DASH playlist:" << urlSuffix <<"\n";
|
||||||
const std::string& playList(os.str());
|
const std::string& playList(os.str());
|
||||||
|
|
||||||
// send response header
|
// send response header
|
||||||
@ -155,6 +153,8 @@ void HTTPServer::HTTPClientConnection::sendMpdPlayList(char const* urlSuffix)
|
|||||||
u_int8_t* playListBuffer = new u_int8_t[playList.size()];
|
u_int8_t* playListBuffer = new u_int8_t[playList.size()];
|
||||||
memcpy(playListBuffer, playList.c_str(), playList.size());
|
memcpy(playListBuffer, playList.c_str(), playList.size());
|
||||||
this->streamSource(ByteStreamMemoryBufferSource::createNew(envir(), playListBuffer, 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));
|
streamName.assign(url.substr(0,pos));
|
||||||
ext.assign(url.substr(pos+1));
|
ext.assign(url.substr(pos+1));
|
||||||
}
|
}
|
||||||
|
bool ok;
|
||||||
if (ext == "mpd")
|
if (ext == "mpd")
|
||||||
{
|
{
|
||||||
// MPEG-DASH Playlist
|
// MPEG-DASH Playlist
|
||||||
this->sendMpdPlayList(streamName.c_str());
|
ok = this->sendMpdPlayList(streamName.c_str());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// HLS Playlist
|
// 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<char>(file)), std::istreambuf_iterator<char>());
|
||||||
|
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
|
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:
|
// For some reason, we do not know the size of the requested range. We can't handle this request:
|
||||||
handleHTTPCmd_notSupported();
|
handleHTTPCmd_notSupported();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// send response header
|
||||||
|
this->sendHeader("video/mp2t", numBytes);
|
||||||
|
|
||||||
// send response header
|
// stream body
|
||||||
this->sendHeader("video/mp2t", numBytes);
|
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
|
clientConnection->fIsActive = False; // will cause the object to get deleted at the end of handling the request
|
||||||
} else {
|
} else {
|
||||||
// We're no longer handling a request; delete the object now:
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
28
src/main.cpp
28
src/main.cpp
@ -173,11 +173,11 @@ int main(int argc, char** argv)
|
|||||||
bool repeatConfig = true;
|
bool repeatConfig = true;
|
||||||
int timeout = 65;
|
int timeout = 65;
|
||||||
bool muxTS = false;
|
bool muxTS = false;
|
||||||
unsigned int hlsSegment = 10;
|
unsigned int hlsSegment = 0;
|
||||||
|
|
||||||
// decode parameters
|
// decode parameters
|
||||||
int c = 0;
|
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)
|
switch (c)
|
||||||
{
|
{
|
||||||
@ -194,7 +194,7 @@ int main(int argc, char** argv)
|
|||||||
case 'c': repeatConfig = false; break;
|
case 'c': repeatConfig = false; break;
|
||||||
case 't': timeout = atoi(optarg); break;
|
case 't': timeout = atoi(optarg); break;
|
||||||
case 'T': muxTS = true; 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
|
// V4L2
|
||||||
case 'r': ioTypeIn = V4l2DeviceFactory::IOTYPE_READ; break;
|
case 'r': ioTypeIn = V4l2DeviceFactory::IOTYPE_READ; break;
|
||||||
case 'w': ioTypeOut = V4l2DeviceFactory::IOTYPE_READ; break;
|
case 'w': ioTypeOut = V4l2DeviceFactory::IOTYPE_READ; break;
|
||||||
@ -208,13 +208,13 @@ int main(int argc, char** argv)
|
|||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
std::cout << argv[0] << " [-v[v]] [-Q queueSize] [-O file]" << std::endl;
|
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 [-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] [-W width] [-H height] [-F fps] [device] [device]" << 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 -v : verbose" << std::endl;
|
||||||
std::cout << "\t -vv : very 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 -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 -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 -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 port (default "<< rtspPort << ")" << std::endl;
|
||||||
std::cout << "\t -p port : RTSP over HTTP port (default "<< rtspOverHTTPPort << ")" << 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 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 -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 -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 -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 -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 V4L2 options :" << std::endl;
|
||||||
std::cout << "\t -r : V4L2 capture using read interface (default use memory mapped buffers)" << 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 -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 -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 : 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 -W width : V4L2 capture width (default "<< width << ")" << std::endl;
|
||||||
std::cout << "\t -H height : V4L2 capture height (default "<< height << ")" << 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 -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);
|
exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,9 +351,13 @@ int main(int argc, char** argv)
|
|||||||
|
|
||||||
}
|
}
|
||||||
// Create Unicast Session
|
// Create Unicast Session
|
||||||
if (muxTS)
|
if (hlsSegment > 0)
|
||||||
{
|
{
|
||||||
addSession(rtspServer, baseUrl+url, HLSServerMediaSubsession::createNew(*env,replicator,rtpFormat, hlsSegment));
|
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
|
else
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user