mirror of
https://github.com/mpromonet/v4l2rtspserver
synced 2024-11-17 21:25:40 +00:00
continue to work on HLS using memory buffers
This commit is contained in:
parent
16ac9205d4
commit
8a593ab42f
@ -10,11 +10,13 @@
|
|||||||
#ifndef SERVER_MEDIA_SUBSESSION
|
#ifndef SERVER_MEDIA_SUBSESSION
|
||||||
#define SERVER_MEDIA_SUBSESSION
|
#define SERVER_MEDIA_SUBSESSION
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sys/stat.h>
|
#include <map>
|
||||||
|
|
||||||
// live555
|
// live555
|
||||||
#include <liveMedia.hh>
|
#include <liveMedia.hh>
|
||||||
@ -87,92 +89,136 @@ class UnicastServerMediaSubsession : public OnDemandServerMediaSubsession , publ
|
|||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
// ServerMediaSubsession for HLS
|
// ServerMediaSubsession for HLS
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
class HLSServerMediaSubsession : public UnicastServerMediaSubsession
|
||||||
class HLSSink : public MediaSink
|
|
||||||
{
|
{
|
||||||
public:
|
class HLSSink : public MediaSink
|
||||||
static HLSSink* createNew(UsageEnvironment& env, unsigned int bufferSize)
|
{
|
||||||
{
|
public:
|
||||||
return new HLSSink(env, bufferSize);
|
static HLSSink* createNew(UsageEnvironment& env, unsigned int bufferSize, unsigned int sliceDuration)
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
HLSSink(UsageEnvironment& env, unsigned bufferSize) : MediaSink(env), m_bufferSize(bufferSize), m_slice(0), m_firstslice(0)
|
|
||||||
{
|
|
||||||
m_buffer = new unsigned char[m_bufferSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~HLSSink()
|
|
||||||
{
|
|
||||||
delete[] m_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
virtual Boolean continuePlaying()
|
|
||||||
{
|
|
||||||
Boolean ret = False;
|
|
||||||
if (fSource != NULL)
|
|
||||||
{
|
{
|
||||||
fSource->getNextFrame(m_buffer, m_bufferSize,
|
return new HLSSink(env, bufferSize, sliceDuration);
|
||||||
afterGettingFrame, this,
|
|
||||||
onSourceClosure, this);
|
|
||||||
ret = True;
|
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
}
|
protected:
|
||||||
|
HLSSink(UsageEnvironment& env, unsigned bufferSize, unsigned int sliceDuration) : MediaSink(env), m_bufferSize(bufferSize), m_refTime(0), m_sliceDuration(sliceDuration)
|
||||||
static void afterGettingFrame(void* clientData, unsigned frameSize,
|
|
||||||
unsigned numTruncatedBytes,
|
|
||||||
struct timeval presentationTime,
|
|
||||||
unsigned /*durationInMicroseconds*/)
|
|
||||||
{
|
|
||||||
HLSSink* sink = (HLSSink*)clientData;
|
|
||||||
sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime)
|
|
||||||
{
|
|
||||||
if (numTruncatedBytes > 0)
|
|
||||||
{
|
{
|
||||||
envir() << "FileSink::afterGettingFrame(): The input frame data was too large for our buffer size \n";
|
m_buffer = new unsigned char[m_bufferSize];
|
||||||
}
|
}
|
||||||
if (m_os.is_open())
|
|
||||||
|
virtual ~HLSSink()
|
||||||
{
|
{
|
||||||
if (m_slice != (presentationTime.tv_sec/10))
|
delete[] m_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
virtual Boolean continuePlaying()
|
||||||
|
{
|
||||||
|
Boolean ret = False;
|
||||||
|
if (fSource != NULL)
|
||||||
{
|
{
|
||||||
m_os.close();
|
fSource->getNextFrame(m_buffer, m_bufferSize,
|
||||||
|
afterGettingFrame, this,
|
||||||
|
onSourceClosure, this);
|
||||||
|
ret = True;
|
||||||
}
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
if (!m_os.is_open())
|
|
||||||
|
static void afterGettingFrame(void* clientData, unsigned frameSize,
|
||||||
|
unsigned numTruncatedBytes,
|
||||||
|
struct timeval presentationTime,
|
||||||
|
unsigned durationInMicroseconds)
|
||||||
{
|
{
|
||||||
m_slice = presentationTime.tv_sec/10;
|
HLSSink* sink = (HLSSink*)clientData;
|
||||||
if (m_firstslice == 0)
|
sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime)
|
||||||
|
{
|
||||||
|
if (numTruncatedBytes > 0)
|
||||||
{
|
{
|
||||||
m_firstslice = m_slice;
|
envir() << "FileSink::afterGettingFrame(): The input frame data was too large for our buffer size \n";
|
||||||
|
// realloc a bigger buffer
|
||||||
|
m_bufferSize += numTruncatedBytes;
|
||||||
|
delete[] m_buffer;
|
||||||
|
m_buffer = new unsigned char[m_bufferSize];
|
||||||
}
|
}
|
||||||
std::ostringstream os;
|
else
|
||||||
os << m_slice << ".ts";
|
{
|
||||||
m_os.open(os.str().c_str());
|
// append buffer to slice buffer
|
||||||
|
if (m_refTime == 0)
|
||||||
|
{
|
||||||
|
m_refTime = presentationTime.tv_sec;
|
||||||
|
}
|
||||||
|
unsigned int slice = (presentationTime.tv_sec-m_refTime)/m_sliceDuration;
|
||||||
|
std::string& outputBuffer = m_outputBuffers[slice];
|
||||||
|
outputBuffer.append((const char*)m_buffer, frameSize);
|
||||||
|
|
||||||
|
// remove old buffers
|
||||||
|
while (m_outputBuffers.size()>5)
|
||||||
|
{
|
||||||
|
m_outputBuffers.erase(m_outputBuffers.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continuePlaying();
|
||||||
}
|
}
|
||||||
if (m_os.is_open())
|
|
||||||
|
public:
|
||||||
|
unsigned int getHLSBufferSize(unsigned int slice)
|
||||||
{
|
{
|
||||||
m_os.write((char*)m_buffer, frameSize);
|
unsigned int size = 0;
|
||||||
|
std::map<unsigned int,std::string>::iterator it = m_outputBuffers.find(slice);
|
||||||
|
if (it != m_outputBuffers.end())
|
||||||
|
{
|
||||||
|
size = it->second.size();
|
||||||
|
}
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
continuePlaying();
|
const char* getHLSBuffer(unsigned int slice)
|
||||||
}
|
{
|
||||||
|
const char* content = NULL;
|
||||||
private:
|
std::map<unsigned int,std::string>::iterator it = m_outputBuffers.find(slice);
|
||||||
unsigned char * m_buffer;
|
if (it != m_outputBuffers.end())
|
||||||
unsigned int m_bufferSize;
|
{
|
||||||
std::ofstream m_os;
|
content = it->second.c_str();
|
||||||
public:
|
}
|
||||||
unsigned int m_slice;
|
return content;
|
||||||
unsigned int m_firstslice;
|
}
|
||||||
};
|
|
||||||
|
unsigned int firstTime()
|
||||||
class HLSServerMediaSubsession : public OnDemandServerMediaSubsession , public BaseServerMediaSubsession
|
{
|
||||||
{
|
unsigned int firstTime = 0;
|
||||||
|
if (m_outputBuffers.size() != 0)
|
||||||
|
{
|
||||||
|
firstTime = m_outputBuffers.begin()->first;
|
||||||
|
}
|
||||||
|
return firstTime*m_sliceDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int duration()
|
||||||
|
{
|
||||||
|
unsigned int duration = 0;
|
||||||
|
if (m_outputBuffers.size() != 0)
|
||||||
|
{
|
||||||
|
duration = m_outputBuffers.rbegin()->first - m_outputBuffers.begin()->first;
|
||||||
|
}
|
||||||
|
return (duration)*m_sliceDuration;
|
||||||
|
}
|
||||||
|
unsigned int getSliceDuration()
|
||||||
|
{
|
||||||
|
return m_sliceDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned char * m_buffer;
|
||||||
|
unsigned int m_bufferSize;
|
||||||
|
std::map<unsigned int,std::string> m_outputBuffers;
|
||||||
|
unsigned int m_refTime;
|
||||||
|
unsigned int m_sliceDuration;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static HLSServerMediaSubsession* createNew(UsageEnvironment& env, StreamReplicator* replicator, const std::string& format)
|
static HLSServerMediaSubsession* createNew(UsageEnvironment& env, StreamReplicator* replicator, const std::string& format)
|
||||||
{
|
{
|
||||||
@ -181,57 +227,48 @@ class HLSServerMediaSubsession : public OnDemandServerMediaSubsession , public B
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
HLSServerMediaSubsession(UsageEnvironment& env, StreamReplicator* replicator, const std::string& format)
|
HLSServerMediaSubsession(UsageEnvironment& env, StreamReplicator* replicator, const std::string& format)
|
||||||
: OnDemandServerMediaSubsession(env, False), BaseServerMediaSubsession(replicator), m_format(format)
|
: UnicastServerMediaSubsession(env, replicator, format)
|
||||||
{
|
{
|
||||||
// Create a source
|
// Create a source
|
||||||
FramedSource* source = replicator->createStreamReplica();
|
FramedSource* source = replicator->createStreamReplica();
|
||||||
FramedSource* videoSource = createSource(env, source, format);
|
FramedSource* videoSource = createSource(env, source, format);
|
||||||
|
|
||||||
// Start Playing the Sink
|
// Start Playing the HLS Sink
|
||||||
m_videoSink = HLSSink::createNew(env, 65535);
|
m_hlsSink = HLSSink::createNew(env, OutPacketBuffer::maxSize, 10);
|
||||||
m_videoSink->startPlaying(*videoSource, NULL, NULL);
|
m_hlsSink->startPlaying(*videoSource, NULL, NULL);
|
||||||
|
}
|
||||||
|
virtual ~HLSServerMediaSubsession()
|
||||||
|
{
|
||||||
|
Medium::close(m_hlsSink);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate)
|
virtual float getCurrentNPT(void* streamToken)
|
||||||
{
|
{
|
||||||
FramedSource* source = m_replicator->createStreamReplica();
|
return (m_hlsSink->firstTime());
|
||||||
return createSource(envir(), source, m_format);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource)
|
|
||||||
{
|
|
||||||
return createSink(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, m_format);
|
|
||||||
}
|
}
|
||||||
|
virtual float duration() const
|
||||||
virtual char const* getAuxSDPLine(RTPSink* rtpSink,FramedSource* inputSource);
|
{
|
||||||
virtual float duration() const {
|
return (m_hlsSink->duration());
|
||||||
std::cout << "duration " << (m_videoSink->m_slice - m_videoSink->m_firstslice)*10 << std::endl;
|
|
||||||
return (m_videoSink->m_slice - m_videoSink->m_firstslice)*10;
|
|
||||||
}
|
}
|
||||||
virtual void seekStream(unsigned clientSessionId, void* streamToken, double& seekNPT, double streamDuration, u_int64_t& numBytes)
|
virtual void seekStream(unsigned clientSessionId, void* streamToken, double& seekNPT, double streamDuration, u_int64_t& numBytes)
|
||||||
{
|
{
|
||||||
m_slice = seekNPT / 10;
|
m_slice = seekNPT / m_hlsSink->getSliceDuration();
|
||||||
seekNPT = m_slice*10;
|
seekNPT = m_slice * m_hlsSink->getSliceDuration();
|
||||||
std::ostringstream os;
|
numBytes = m_hlsSink->getHLSBufferSize(m_slice);
|
||||||
os << m_slice+m_videoSink->m_firstslice << ".ts";
|
std::cout << "seek seekNPT:" << seekNPT << " slice:" << m_slice << " numBytes:" << numBytes << std::endl;
|
||||||
struct stat sb;
|
|
||||||
int statResult = stat(os.str().c_str(), &sb);
|
|
||||||
if (statResult == 0)
|
|
||||||
{
|
|
||||||
numBytes = sb.st_size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
virtual FramedSource* getStreamSource(void* streamToken)
|
virtual FramedSource* getStreamSource(void* streamToken)
|
||||||
{
|
{
|
||||||
std::ostringstream os;
|
unsigned int size = m_hlsSink->getHLSBufferSize(m_slice);
|
||||||
os << m_slice+m_videoSink->m_firstslice << ".ts";
|
u_int8_t* content = new u_int8_t[size];
|
||||||
return ByteStreamFileSource::createNew(envir(), os.str().c_str());
|
memcpy(content, m_hlsSink->getHLSBuffer(m_slice), size);
|
||||||
|
return ByteStreamMemoryBufferSource::createNew(envir(), content, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const std::string m_format;
|
|
||||||
unsigned int m_slice;
|
unsigned int m_slice;
|
||||||
HLSSink * m_videoSink;
|
HLSSink * m_hlsSink;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -145,10 +145,3 @@ char const* UnicastServerMediaSubsession::getAuxSDPLine(RTPSink* rtpSink,FramedS
|
|||||||
return this->getAuxLine(dynamic_cast<V4L2DeviceSource*>(m_replicator->inputSource()), rtpSink->rtpPayloadType());
|
return this->getAuxLine(dynamic_cast<V4L2DeviceSource*>(m_replicator->inputSource()), rtpSink->rtpPayloadType());
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
// ServerMediaSubsession for Unicast
|
|
||||||
// -----------------------------------------
|
|
||||||
char const* HLSServerMediaSubsession::getAuxSDPLine(RTPSink* rtpSink,FramedSource* inputSource)
|
|
||||||
{
|
|
||||||
return this->getAuxLine(dynamic_cast<V4L2DeviceSource*>(m_replicator->inputSource()), rtpSink->rtpPayloadType());
|
|
||||||
}
|
|
||||||
|
240
src/main.cpp
240
src/main.cpp
@ -13,20 +13,17 @@
|
|||||||
** -------------------------------------------------------------------------*/
|
** -------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "RTSPServer.hh"
|
#include "RTSPServer.hh"
|
||||||
#include "RTSPCommon.hh"
|
#include "RTSPCommon.hh"
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#ifndef _BYTE_STREAM_MEMORY_BUFFER_SOURCE_HH
|
|
||||||
#include "ByteStreamMemoryBufferSource.hh"
|
#include "ByteStreamMemoryBufferSource.hh"
|
||||||
#endif
|
|
||||||
#ifndef _TCP_STREAM_SINK_HH
|
|
||||||
#include "TCPStreamSink.hh"
|
#include "TCPStreamSink.hh"
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// RTSP server supporting live HLS
|
||||||
|
// -----------------------------------------
|
||||||
class HLSServer : public RTSPServer
|
class HLSServer : public RTSPServer
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -34,102 +31,100 @@ class HLSServer : public RTSPServer
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HLSClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
|
HLSClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
|
||||||
: RTSPServer::RTSPClientConnection(ourServer, clientSocket, clientAddr), fClientSessionId(0), fStreamSource(NULL), fPlaylistSource(NULL), fTCPSink(NULL) {
|
: RTSPServer::RTSPClientConnection(ourServer, clientSocket, clientAddr), fClientSessionId(0), fTCPSink(NULL) {
|
||||||
}
|
}
|
||||||
|
|
||||||
~HLSClientConnection() {
|
~HLSClientConnection() {
|
||||||
if (fTCPSink != NULL) fTCPSink->stopPlaying();
|
if (fTCPSink != NULL)
|
||||||
Medium::close(fPlaylistSource);
|
{
|
||||||
Medium::close(fStreamSource);
|
fTCPSink->stopPlaying();
|
||||||
Medium::close(fTCPSink);
|
Medium::close(fTCPSink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void sendPlayList(char const* urlSuffix)
|
void sendHeader(const char* contentType, unsigned int contentLength)
|
||||||
{
|
{
|
||||||
// First, make sure that the named file exists, and is streamable:
|
// Construct our response:
|
||||||
ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlSuffix);
|
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
|
||||||
if (session == NULL) {
|
|
||||||
std::cout << "============no session==============" << std::endl;
|
|
||||||
handleHTTPCmd_notFound();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// To be able to construct a playlist for the requested file, we need to know its duration:
|
|
||||||
float duration = session->duration();
|
|
||||||
if (duration <= 0.0) {
|
|
||||||
std::cout << "============no duration==============" << std::endl;
|
|
||||||
handleHTTPCmd_notSupported();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, construct the playlist. It will consist of a prefix, one or more media file specifications, and a suffix:
|
|
||||||
unsigned const maxIntLen = 10; // >= the maximum possible strlen() of an integer in the playlist
|
|
||||||
char const* const playlistPrefixFmt =
|
|
||||||
"#EXTM3U\r\n"
|
|
||||||
"#EXT-X-ALLOW-CACHE:YES\r\n"
|
|
||||||
"#EXT-X-MEDIA-SEQUENCE:%d\r\n"
|
|
||||||
"#EXT-X-TARGETDURATION:%d\r\n";
|
|
||||||
unsigned const playlistPrefixFmt_maxLen = strlen(playlistPrefixFmt) + maxIntLen;
|
|
||||||
|
|
||||||
char const* const playlistMediaFileSpecFmt =
|
|
||||||
"#EXTINF:%d,\r\n"
|
|
||||||
"%s?segment=%d,%d\r\n";
|
|
||||||
unsigned const playlistMediaFileSpecFmt_maxLen = strlen(playlistMediaFileSpecFmt) + maxIntLen + strlen(urlSuffix) + 2*maxIntLen;
|
|
||||||
|
|
||||||
// Figure out the 'target duration' that will produce a playlist that will fit in our response buffer. (But make it at least 10s.)
|
|
||||||
unsigned const playlistMaxSize = 10000;
|
|
||||||
unsigned const mediaFileSpecsMaxSize = playlistMaxSize - (playlistPrefixFmt_maxLen /*+ playlistSuffixFmt_maxLen*/);
|
|
||||||
unsigned const maxNumMediaFileSpecs = mediaFileSpecsMaxSize/playlistMediaFileSpecFmt_maxLen;
|
|
||||||
|
|
||||||
unsigned targetDuration = (unsigned)(duration/maxNumMediaFileSpecs + 1);
|
|
||||||
if (targetDuration < 10) targetDuration = 10;
|
|
||||||
|
|
||||||
unsigned int startTime = 0;
|
|
||||||
char* playlist = new char[playlistMaxSize];
|
|
||||||
char* s = playlist;
|
|
||||||
sprintf(s, playlistPrefixFmt, startTime,targetDuration);
|
|
||||||
s += strlen(s);
|
|
||||||
|
|
||||||
unsigned durSoFar = startTime;
|
|
||||||
while (1) {
|
|
||||||
unsigned dur = targetDuration < duration ? targetDuration : (unsigned)duration;
|
|
||||||
duration -= dur;
|
|
||||||
sprintf(s, playlistMediaFileSpecFmt, dur, urlSuffix, durSoFar, dur);
|
|
||||||
s += strlen(s);
|
|
||||||
if (duration < 1.0) break;
|
|
||||||
|
|
||||||
durSoFar += dur;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned playlistLen = s - playlist;
|
|
||||||
|
|
||||||
// Construct our response:
|
|
||||||
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
|
|
||||||
"HTTP/1.1 200 OK\r\n"
|
"HTTP/1.1 200 OK\r\n"
|
||||||
"%s"
|
"%s"
|
||||||
"Server: LIVE555 Streaming Media v%s\r\n"
|
"Server: LIVE555 Streaming Media v%s\r\n"
|
||||||
|
"Content-Type: %s\r\n"
|
||||||
"Content-Length: %d\r\n"
|
"Content-Length: %d\r\n"
|
||||||
"Content-Type: application/vnd.apple.mpegurl\r\n"
|
|
||||||
"\r\n",
|
"\r\n",
|
||||||
dateHeader(),
|
dateHeader(),
|
||||||
LIVEMEDIA_LIBRARY_VERSION_STRING,
|
LIVEMEDIA_LIBRARY_VERSION_STRING,
|
||||||
playlistLen);
|
contentType,
|
||||||
|
contentLength);
|
||||||
|
|
||||||
// Send the response header now, because we're about to add more data (the playlist):
|
// Send the response header
|
||||||
send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
|
send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
|
||||||
fResponseBuffer[0] = '\0'; // We've already sent the response. This tells the calling code not to send it again.
|
fResponseBuffer[0] = '\0'; // We've already sent the response. This tells the calling code not to send it again.
|
||||||
|
}
|
||||||
|
|
||||||
|
void streamSource(FramedSource* source)
|
||||||
|
{
|
||||||
|
if (fTCPSink != NULL)
|
||||||
|
{
|
||||||
|
fTCPSink->stopPlaying();
|
||||||
|
Medium::close(fTCPSink);
|
||||||
|
fTCPSink = NULL;
|
||||||
|
}
|
||||||
|
if (source != NULL)
|
||||||
|
{
|
||||||
|
fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
|
||||||
|
fTCPSink->startPlaying(*source, afterStreaming, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendPlayList(char const* urlSuffix)
|
||||||
|
{
|
||||||
|
// First, make sure that the named file exists, and is streamable:
|
||||||
|
ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlSuffix);
|
||||||
|
if (session == NULL) {
|
||||||
|
handleHTTPCmd_notFound();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Then, send the playlist. Because it's large, we don't do so using "send()", because that might not send it all at once.
|
// To be able to construct a playlist for the requested file, we need to know its duration:
|
||||||
// Instead, we stream the playlist over the TCP socket:
|
float duration = session->duration();
|
||||||
if (fPlaylistSource != NULL) { // sanity check
|
if (duration <= 0.0) {
|
||||||
if (fTCPSink != NULL) fTCPSink->stopPlaying();
|
handleHTTPCmd_notSupported();
|
||||||
Medium::close(fPlaylistSource);
|
return;
|
||||||
}
|
}
|
||||||
fPlaylistSource = ByteStreamMemoryBufferSource::createNew(envir(), (u_int8_t*)playlist, playlistLen);
|
|
||||||
if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
|
ServerMediaSubsessionIterator iter(*session);
|
||||||
fTCPSink->startPlaying(*fPlaylistSource, afterStreaming, this);
|
ServerMediaSubsession* subsession = iter.next();
|
||||||
|
if (subsession == NULL) {
|
||||||
|
handleHTTPCmd_notSupported();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int startTime = subsession->getCurrentNPT(NULL);
|
||||||
|
unsigned sliceDuration = 10;
|
||||||
|
std::ostringstream os;
|
||||||
|
os << "#EXTM3U\r\n"
|
||||||
|
<< "#EXT-X-ALLOW-CACHE:YES\r\n"
|
||||||
|
<< "#EXT-X-MEDIA-SEQUENCE:" << startTime << "\r\n"
|
||||||
|
<< "#EXT-X-TARGETDURATION:" << sliceDuration << "\r\n";
|
||||||
|
|
||||||
|
for (unsigned int slice=0; slice*sliceDuration<duration; slice++)
|
||||||
|
{
|
||||||
|
os << "#EXTINF:" << sliceDuration << ",\r\n";
|
||||||
|
os << urlSuffix << "?segment=" << (startTime+slice*sliceDuration) << "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& playList(os.str());
|
||||||
|
|
||||||
|
// send response header
|
||||||
|
this->sendHeader("application/vnd.apple.mpegurl", playList.size());
|
||||||
|
|
||||||
|
// stream body
|
||||||
|
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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* /*fullRequestStr*/) {
|
void handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* /*fullRequestStr*/) {
|
||||||
@ -138,8 +133,8 @@ class HLSServer : public RTSPServer
|
|||||||
do {
|
do {
|
||||||
char const* questionMarkPos = strrchr(urlSuffix, '?');
|
char const* questionMarkPos = strrchr(urlSuffix, '?');
|
||||||
if (questionMarkPos == NULL) break;
|
if (questionMarkPos == NULL) break;
|
||||||
unsigned offsetInSeconds, durationInSeconds;
|
unsigned offsetInSeconds;
|
||||||
if (sscanf(questionMarkPos, "?segment=%u,%u", &offsetInSeconds, &durationInSeconds) != 2) break;
|
if (sscanf(questionMarkPos, "?segment=%u", &offsetInSeconds) != 1) break;
|
||||||
|
|
||||||
char* streamName = strDup(urlSuffix);
|
char* streamName = strDup(urlSuffix);
|
||||||
streamName[questionMarkPos-urlSuffix] = '\0';
|
streamName[questionMarkPos-urlSuffix] = '\0';
|
||||||
@ -173,42 +168,21 @@ class HLSServer : public RTSPServer
|
|||||||
|
|
||||||
// Seek the stream source to the desired place, with the desired duration, and (as a side effect) get the number of bytes:
|
// Seek the stream source to the desired place, with the desired duration, and (as a side effect) get the number of bytes:
|
||||||
double dOffsetInSeconds = (double)offsetInSeconds;
|
double dOffsetInSeconds = (double)offsetInSeconds;
|
||||||
u_int64_t numBytes;
|
u_int64_t numBytes = 0;
|
||||||
subsession->seekStream(fClientSessionId, streamToken, dOffsetInSeconds, (double)durationInSeconds, numBytes);
|
subsession->seekStream(fClientSessionId, streamToken, dOffsetInSeconds, 0.0, numBytes);
|
||||||
unsigned numTSBytesToStream = (unsigned)numBytes;
|
|
||||||
std::cout << "numTSBytesToStream:" << numTSBytesToStream << std::endl;
|
|
||||||
|
|
||||||
if (numTSBytesToStream == 0) {
|
if (numBytes == 0) {
|
||||||
// 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();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct our response:
|
// send response header
|
||||||
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
|
this->sendHeader("video/mp2t", numBytes);
|
||||||
"HTTP/1.1 200 OK\r\n"
|
|
||||||
"%s"
|
|
||||||
"Server: LIVE555 Streaming Media v%s\r\n"
|
|
||||||
"Content-Length: %d\r\n"
|
|
||||||
"Content-Type: text/plain; charset=ISO-8859-1\r\n"
|
|
||||||
"\r\n",
|
|
||||||
dateHeader(),
|
|
||||||
LIVEMEDIA_LIBRARY_VERSION_STRING,
|
|
||||||
numTSBytesToStream);
|
|
||||||
// Send the response now, because we're about to add more data (from the source):
|
|
||||||
send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
|
|
||||||
fResponseBuffer[0] = '\0'; // We've already sent the response. This tells the calling code not to send it again.
|
|
||||||
|
|
||||||
// Ask the media source to deliver - to the TCP sink - the desired data:
|
// stream body
|
||||||
if (fStreamSource != NULL) { // sanity check
|
this->streamSource(subsession->getStreamSource(streamToken));
|
||||||
if (fTCPSink != NULL) fTCPSink->stopPlaying();
|
|
||||||
Medium::close(fStreamSource);
|
|
||||||
}
|
|
||||||
fStreamSource = subsession->getStreamSource(streamToken);
|
|
||||||
if (fStreamSource != NULL) {
|
|
||||||
if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
|
|
||||||
fTCPSink->startPlaying(*fStreamSource, afterStreaming, this);
|
|
||||||
}
|
|
||||||
} while(0);
|
} while(0);
|
||||||
|
|
||||||
delete[] streamName;
|
delete[] streamName;
|
||||||
@ -218,8 +192,8 @@ class HLSServer : public RTSPServer
|
|||||||
this->sendPlayList(urlSuffix);
|
this->sendPlayList(urlSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void afterStreaming(void* clientData) {
|
static void afterStreaming(void* clientData)
|
||||||
std::cout << "afterStreaming" << std::endl;
|
{
|
||||||
HLSServer::HLSClientConnection* clientConnection = (HLSServer::HLSClientConnection*)clientData;
|
HLSServer::HLSClientConnection* clientConnection = (HLSServer::HLSClientConnection*)clientData;
|
||||||
// Arrange to delete the 'client connection' object:
|
// Arrange to delete the 'client connection' object:
|
||||||
if (clientConnection->fRecursionCount > 0) {
|
if (clientConnection->fRecursionCount > 0) {
|
||||||
@ -227,33 +201,27 @@ class HLSServer : public RTSPServer
|
|||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
u_int32_t fClientSessionId;
|
u_int32_t fClientSessionId;
|
||||||
FramedSource* fStreamSource;
|
|
||||||
ByteStreamMemoryBufferSource* fPlaylistSource;
|
|
||||||
TCPStreamSink* fTCPSink;
|
TCPStreamSink* fTCPSink;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static HLSServer* createNew(UsageEnvironment& env, Port rtspPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds) {
|
static HLSServer* createNew(UsageEnvironment& env, Port rtspPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds) {
|
||||||
int ourSocket = setUpOurSocket(env, rtspPort);
|
int ourSocket = setUpOurSocket(env, rtspPort);
|
||||||
if (ourSocket == -1) return NULL;
|
if (ourSocket == -1) return NULL;
|
||||||
|
return new HLSServer(env, ourSocket, rtspPort, authDatabase, reclamationTestSeconds);
|
||||||
return new HLSServer(env, ourSocket, rtspPort, authDatabase, reclamationTestSeconds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HLSServer(UsageEnvironment& env, int ourSocket, Port rtspPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds)
|
HLSServer(UsageEnvironment& env, int ourSocket, Port rtspPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds)
|
||||||
: RTSPServer(env, ourSocket, rtspPort, authDatabase, reclamationTestSeconds) {
|
: RTSPServer(env, ourSocket, rtspPort, authDatabase, reclamationTestSeconds) {
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~HLSServer() {
|
|
||||||
}
|
|
||||||
|
|
||||||
RTSPServer::RTSPClientConnection* createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) {
|
RTSPServer::RTSPClientConnection* createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) {
|
||||||
return new HLSClientConnection(*this, clientSocket, clientAddr);
|
return new HLSClientConnection(*this, clientSocket, clientAddr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -561,12 +529,14 @@ int main(int argc, char** argv)
|
|||||||
rtcpPortNum+=2;
|
rtcpPortNum+=2;
|
||||||
|
|
||||||
}
|
}
|
||||||
// Create Unicast Session
|
// Create Unicast Session
|
||||||
addSession(rtspServer, baseUrl+url, UnicastServerMediaSubsession::createNew(*env,replicator,rtpFormat));
|
|
||||||
|
|
||||||
if (muxTS)
|
if (muxTS)
|
||||||
{
|
{
|
||||||
addSession(rtspServer, "hls", HLSServerMediaSubsession::createNew(*env,replicator,rtpFormat));
|
addSession(rtspServer, baseUrl+url, HLSServerMediaSubsession::createNew(*env,replicator,rtpFormat));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addSession(rtspServer, baseUrl+url, UnicastServerMediaSubsession::createNew(*env,replicator,rtpFormat));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (out)
|
if (out)
|
||||||
|
Loading…
Reference in New Issue
Block a user