Project

General

Profile

Feature #2016 » connectionRTSP.c

nano, 12/01/2014 03:14 PM

 
/*
* $Id: connectionRTSP.c,v 1.21 2010/08/03 10:46:41 schmirl Exp $
*/

#include <ctype.h>
#include <time.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <vdr/thread.h>
#include <vdr/recording.h>

#include "server/connectionRTSP.h"
#include "server/server.h"
#include "server/setup.h"

cConnectionRTSP::cConnectionRTSP(void):
cServerConnection("RTSP"),
m_Status(hsRequest),
m_Channel(NULL)
{
Dprintf("constructor hsRequest\n");
m_Apid[0] = m_Apid[1] = 0;
m_Dpid[0] = m_Dpid[1] = 0;
}

cConnectionRTSP::~cConnectionRTSP()
{
SetStreamer(NULL);
}

bool cConnectionRTSP::Command(char *Cmd)
{
Dprintf("command %s\n", Cmd);
switch (m_Status) {
case hsRequest:
// parse METHOD PATH[?QUERY] VERSION
{
char *p, *q, *v;
p = strchr(Cmd, ' ');
if (p) {
*p = 0;
v = strchr(++p, ' ');
if (v) {
*v = 0;
SetHeader("REQUEST_METHOD", Cmd);
q = strchr(p, '?');
if (q) {
*q = 0;
SetHeader("QUERY_STRING", q + 1);
while (q++) {
char *n = strchr(q, '&');
if (n)
*n = 0;
char *e = strchr(q, '=');
if (e)
*e++ = 0;
else
e = n ? n : v;
m_Params.insert(tStrStr(q, e));
q = n;
}
}
else
SetHeader("QUERY_STRING", "");
SetHeader("PATH_INFO", p);
m_Status = hsHeaders;
return true;
}
}
}
return false;

case hsHeaders:
if (*Cmd == '\0') {
m_Status = hsBody;
return ProcessRequest();
}
else if (isspace(*Cmd)) {
; //TODO: multi-line header
}
else {
// convert header name to CGI conventions:
// uppercase, '-' replaced with '_', prefix "RTSP_"
char *p;
for (p = Cmd; *p != 0 && *p != ':'; p++) {
if (*p == '-')
*p = '_';
else
*p = toupper(*p);
}
if (*p == ':') {
*p = 0;
p = skipspace(++p);
SetHeader(Cmd, p, "RTSP_");
}
}
return true;
default:
// skip additional blank lines
if (*Cmd == '\0')
return true;
break;
}
return false; // ??? shouldn't happen
}

bool cConnectionRTSP::ProcessRequest(void)
{
// keys for Headers() hash
const static std::string REQUEST_METHOD("REQUEST_METHOD");
const static std::string PATH_INFO("PATH_INFO");

Dprintf("process\n");

tStrStrMap::const_iterator it;
it = m_Params.find("apid");
if (it != m_Params.end())
m_Apid[0] = atoi(it->second.c_str());
it = m_Params.find("dpid");
if (it != m_Params.end())
m_Dpid[0] = atoi(it->second.c_str());

tStrStrMap::const_iterator it_method = Headers().find(REQUEST_METHOD);
tStrStrMap::const_iterator it_pathinfo = Headers().find(PATH_INFO);
if (it_method == Headers().end() || it_pathinfo == Headers().end()) {
// should never happen
esyslog("streamdev-server connectionRTSP: Missing method or pathinfo");
} else if (it_method->second.compare("SETUP") == 0 && ProcessURI(it_pathinfo->second)) {
if (m_Channel != NULL) {
if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.RTSPPriority)) {
cStreamdevLiveStreamer* liveStreamer = new cStreamdevLiveStreamer(this, m_Channel, StreamdevServerSetup.RTSPPriority, stTSPIDS, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL);
if (liveStreamer->GetDevice()) {
SetStreamer(liveStreamer);
if (!SetDSCP())
LOG_ERROR_STR("unable to set DSCP sockopt");
if (ISRADIO(m_Channel)) {
return RtspResponse(200, false, "audio/mpeg");
} else {
return RtspResponse(200, false, "video/mpeg");
}
}
SetStreamer(NULL);
delete liveStreamer;
}
return RtspResponse(503, true);
}
else {
return RtspResponse(404, true);
}
} else if (it_method->second.compare("PLAY") == 0 && ProcessURI(it_pathinfo->second)) {
if (m_Channel != NULL) {
if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.RTSPPriority)) {
if (ISRADIO(m_Channel)) {
return RtspResponse(200, true, "audio/mpeg");
} else {
return RtspResponse(200, true, "video/mpeg");
}
}
return RtspResponse(503, true);
}
else {
return RtspResponse(404, true);
}
}
else if (it_method->second.compare("TEARDOWN") == 0 && ProcessURI(it_pathinfo->second)) {
}
else if (it_method->second.compare("OPTIONS") == 0 && ProcessURI(it_pathinfo->second)) {
}
else if (it_method->second.compare("DESCRIBE") == 0 && ProcessURI(it_pathinfo->second)) {
}

return RtspResponse(400, true);
}

static const char *AAA[] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static const char *MMM[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

bool cConnectionRTSP::RtspResponse(int Code, bool Last, const char* ContentType, const char* Headers, ...)
{
va_list ap;
va_start(ap, Headers);
#if APIVERSNUM >= 10728
cString headers = cString::vsprintf(Headers, ap);
#else
cString headers = cString::sprintf(Headers, ap);
#endif
va_end(ap);

bool rc;
if (Last)
DeferClose();
switch (Code)
{
case 200: rc = Respond("RTSP/1.0 200 OK"); break;
case 206: rc = Respond("RTSP/1.0 206 Partial Content"); break;
case 400: rc = Respond("RTSP/1.0 400 Bad Request"); break;
case 401: rc = Respond("RTSP/1.0 401 Authorization Required"); break;
case 404: rc = Respond("RTSP/1.0 404 Not Found"); break;
case 416: rc = Respond("RTSP/1.0 416 Requested range not satisfiable"); break;
case 503: rc = Respond("RTSP/1.0 503 Service Unavailable"); break;
default: rc = Respond("RTSP/1.0 500 Internal Server Error");
}
if (rc && ContentType)
rc = Respond("Content-Type: %s", true, ContentType);

if (rc)
rc = Respond("Connection: close")
&& Respond("Pragma: no-cache")
&& Respond("Cache-Control: no-cache")
&& Respond("Server: VDR-%s / streamdev-server-%s", true, VDRVERSION, VERSION);

time_t t = time(NULL);
struct tm *gmt = gmtime(&t);
if (rc && gmt) {
char buf[] = "Date: AAA, DD MMM YYYY HH:MM:SS GMT";
if (snprintf(buf, sizeof(buf), "Date: %s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", AAA[gmt->tm_wday], gmt->tm_mday, MMM[gmt->tm_mon], gmt->tm_year + 1900, gmt->tm_hour, gmt->tm_min, gmt->tm_sec) == sizeof(buf) - 1)
rc = Respond(buf);
}

if (rc && strlen(Headers) > 0)
rc = Respond(headers);

tStrStrMap::iterator it = m_Params.begin();
while (rc && it != m_Params.end()) {
static const char DLNA_POSTFIX[] = ".dlna.org";
if (it->first.rfind(DLNA_POSTFIX) + sizeof(DLNA_POSTFIX) - 1 == it->first.length())
rc = Respond("%s: %s", true, it->first.c_str(), it->second.c_str());
++it;
}
return rc && Respond("");
}

void cConnectionRTSP::Flushed(void)
{
if (m_Status != hsBody)
return;

if (Streamer()) {
Dprintf("streamer start\n");
Streamer()->Start(this);
m_Status = hsFinished;
}
else {
// should never be reached
esyslog("streamdev-server cConnectionRTSP::Flushed(): no job to do");
m_Status = hsFinished;
}
}

bool cConnectionRTSP::ProcessURI(const std::string& PathInfo)
{
std::string filespec, fileext;
size_t file_pos = PathInfo.rfind('/');

if (file_pos != std::string::npos) {
size_t ext_pos = PathInfo.rfind('.');
if (ext_pos != std::string::npos) {
// file extension including leading .
fileext = PathInfo.substr(ext_pos);
const char *ext = fileext.c_str();
// ignore dummy file extensions
if (strcasecmp(ext, ".ts") == 0 ||
strcasecmp(ext, ".vdr") == 0 ||
strcasecmp(ext, ".vob") == 0) {
size_t ext_end = ext_pos;
if (ext_pos > 0)
ext_pos = PathInfo.rfind('.', ext_pos - 1);
if (ext_pos == std::string::npos)
ext_pos = ext_end;
fileext = PathInfo.substr(ext_pos, ext_end - ext_pos);
}
}
// file basename with leading / stripped off
filespec = PathInfo.substr(file_pos + 1, ext_pos - file_pos - 1);
}
if (fileext.length() > 5) {
//probably not an extension
filespec += fileext;
fileext.clear();
}

// Streamtype with leading / stripped off
std::string type = PathInfo.substr(1, PathInfo.find_first_of("/;", 1) - 1);
const char* pType = type.c_str();
if (strcasecmp(pType, "TS") == 0) {
//m_StreamType = stTS;
}
Dprintf("before channelfromstring: type(%s) filespec(%s) fileext(%s)\n", type.c_str(), filespec.c_str(), fileext.c_str());

if ((m_Channel = ChannelFromString(filespec.c_str(), &m_Apid[0], &m_Dpid[0])) != NULL) {
Dprintf("Channel found. Apid/Dpid is %d/%d\n", m_Apid[0], m_Dpid[0]);
return true;
} else
return false;
}

cString cConnectionRTSP::ToText(char Delimiter) const
{
cString str = cServerConnection::ToText(Delimiter);
return Streamer() ? cString::sprintf("%s%c%s", *str, Delimiter, *Streamer()->ToText()) : str;
}
(3-3/5)