341 lines
12 KiB
C++
341 lines
12 KiB
C++
#include <cerrno>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <mist/defines.h>
|
|
#include <mist/stream.h>
|
|
#include <string>
|
|
|
|
#include <mist/bitfields.h>
|
|
#include <mist/util.h>
|
|
|
|
#include "input_dtsc.h"
|
|
|
|
namespace Mist{
|
|
inputDTSC::inputDTSC(Util::Config *cfg) : Input(cfg){
|
|
capa["name"] = "DTSC";
|
|
capa["desc"] = "Load DTSC files as Video on Demand sources, or dtsc:// URLs from other "
|
|
"MistServer instances for live sources. This is the optimal method to pull live "
|
|
"sources from other MistServer (or compatible) instances.";
|
|
capa["priority"] = 9;
|
|
capa["source_match"].append("/*.dtsc");
|
|
capa["source_match"].append("dtsc://*");
|
|
capa["always_match"].append("dtsc://*"); // can be said to always-on mode
|
|
capa["source_file"] = "$source";
|
|
capa["codecs"][0u][0u].append("H264");
|
|
capa["codecs"][0u][0u].append("H263");
|
|
capa["codecs"][0u][0u].append("VP6");
|
|
capa["codecs"][0u][0u].append("theora");
|
|
capa["codecs"][0u][1u].append("AAC");
|
|
capa["codecs"][0u][1u].append("MP3");
|
|
capa["codecs"][0u][1u].append("vorbis");
|
|
|
|
JSON::Value option;
|
|
option["arg"] = "integer";
|
|
option["long"] = "buffer";
|
|
option["short"] = "b";
|
|
option["help"] = "Live stream DVR buffer time in ms";
|
|
option["value"].append(50000);
|
|
config->addOption("bufferTime", option);
|
|
capa["optional"]["DVR"]["name"] = "Buffer time (ms)";
|
|
capa["optional"]["DVR"]["help"] =
|
|
"The target available buffer time for this live stream, in milliseconds. This is the time "
|
|
"available to seek around in, and will automatically be extended to fit whole keyframes as "
|
|
"well as the minimum duration needed for stable playback.";
|
|
capa["optional"]["DVR"]["option"] = "--buffer";
|
|
capa["optional"]["DVR"]["type"] = "uint";
|
|
capa["optional"]["DVR"]["default"] = 50000;
|
|
/*LTS-START*/
|
|
option.null();
|
|
option["arg"] = "integer";
|
|
option["long"] = "segment-size";
|
|
option["short"] = "S";
|
|
option["help"] = "Target time duration in milliseconds for segments";
|
|
option["value"].append(1900);
|
|
config->addOption("segmentsize", option);
|
|
capa["optional"]["segmentsize"]["name"] = "Segment size (ms)";
|
|
capa["optional"]["segmentsize"]["help"] = "Target time duration in milliseconds for segments.";
|
|
capa["optional"]["segmentsize"]["option"] = "--segment-size";
|
|
capa["optional"]["segmentsize"]["type"] = "uint";
|
|
capa["optional"]["segmentsize"]["default"] = 1900;
|
|
/*LTS-END*/
|
|
}
|
|
|
|
bool inputDTSC::needsLock(){
|
|
return config->getString("input").substr(0, 7) != "dtsc://" && config->getString("input") != "-";
|
|
}
|
|
|
|
void parseDTSCURI(const std::string &src, std::string &host, uint16_t &port,
|
|
std::string &password, std::string &streamName){
|
|
host = "";
|
|
port = 4200;
|
|
password = "";
|
|
streamName = "";
|
|
std::deque<std::string> matches;
|
|
if (Util::stringScan(src, "%s:%s@%s/%s", matches)){
|
|
host = matches[0];
|
|
port = atoi(matches[1].c_str());
|
|
password = matches[2];
|
|
streamName = matches[3];
|
|
return;
|
|
}
|
|
// Using default streamname
|
|
if (Util::stringScan(src, "%s:%s@%s", matches)){
|
|
host = matches[0];
|
|
port = atoi(matches[1].c_str());
|
|
password = matches[2];
|
|
return;
|
|
}
|
|
// Without password
|
|
if (Util::stringScan(src, "%s:%s/%s", matches)){
|
|
host = matches[0];
|
|
port = atoi(matches[1].c_str());
|
|
streamName = matches[2];
|
|
return;
|
|
}
|
|
// Using default port
|
|
if (Util::stringScan(src, "%s@%s/%s", matches)){
|
|
host = matches[0];
|
|
password = matches[1];
|
|
streamName = matches[2];
|
|
return;
|
|
}
|
|
// Default port, no password
|
|
if (Util::stringScan(src, "%s/%s", matches)){
|
|
host = matches[0];
|
|
streamName = matches[1];
|
|
return;
|
|
}
|
|
// No password, default streamname
|
|
if (Util::stringScan(src, "%s:%s", matches)){
|
|
host = matches[0];
|
|
port = atoi(matches[1].c_str());
|
|
return;
|
|
}
|
|
// Default port and streamname
|
|
if (Util::stringScan(src, "%s@%s", matches)){
|
|
host = matches[0];
|
|
password = matches[1];
|
|
return;
|
|
}
|
|
// Default port and streamname, no password
|
|
if (Util::stringScan(src, "%s", matches)){
|
|
host = matches[0];
|
|
return;
|
|
}
|
|
}
|
|
|
|
void inputDTSC::parseStreamHeader(){
|
|
while (srcConn.connected() && config->is_active){
|
|
srcConn.spool();
|
|
if (srcConn.Received().available(8)){
|
|
if (srcConn.Received().copy(4) == "DTCM" || srcConn.Received().copy(4) == "DTSC"){
|
|
// Command message
|
|
std::string toRec = srcConn.Received().copy(8);
|
|
unsigned long rSize = Bit::btohl(toRec.c_str() + 4);
|
|
if (!srcConn.Received().available(8 + rSize)){
|
|
nProxy.userClient.keepAlive();
|
|
Util::sleep(100);
|
|
continue; // abort - not enough data yet
|
|
}
|
|
// Ignore initial DTCM message, as this is a "hi" message from the server
|
|
if (srcConn.Received().copy(4) == "DTCM"){
|
|
srcConn.Received().remove(8 + rSize);
|
|
}else{
|
|
std::string dataPacket = srcConn.Received().remove(8 + rSize);
|
|
DTSC::Packet metaPack(dataPacket.data(), dataPacket.size());
|
|
myMeta.reinit(metaPack);
|
|
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
|
|
it != myMeta.tracks.end(); it++){
|
|
continueNegotiate(it->first, true);
|
|
}
|
|
break;
|
|
}
|
|
}else{
|
|
INFO_MSG("Received a wrong type of packet - '%s'", srcConn.Received().copy(4).c_str());
|
|
break;
|
|
}
|
|
}else{
|
|
Util::sleep(100);
|
|
nProxy.userClient.keepAlive();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool inputDTSC::openStreamSource(){
|
|
std::string source = config->getString("input");
|
|
if (source == "-"){
|
|
srcConn.open(fileno(stdout), fileno(stdin));
|
|
return true;
|
|
}
|
|
if (source.find("dtsc://") == 0){source.erase(0, 7);}
|
|
std::string host;
|
|
uint16_t port;
|
|
std::string password;
|
|
std::string streamName;
|
|
parseDTSCURI(source, host, port, password, streamName);
|
|
std::string givenStream = config->getString("streamname");
|
|
if (streamName == ""){streamName = givenStream;}
|
|
srcConn.open(host, port, true);
|
|
if (!srcConn.connected()){return false;}
|
|
JSON::Value prep;
|
|
prep["cmd"] = "play";
|
|
prep["version"] = "MistServer " PACKAGE_VERSION;
|
|
prep["stream"] = streamName;
|
|
srcConn.SendNow("DTCM");
|
|
char sSize[4] ={0, 0, 0, 0};
|
|
Bit::htobl(sSize, prep.packedSize());
|
|
srcConn.SendNow(sSize, 4);
|
|
prep.sendTo(srcConn);
|
|
return true;
|
|
}
|
|
|
|
void inputDTSC::closeStreamSource(){srcConn.close();}
|
|
|
|
bool inputDTSC::checkArguments(){
|
|
if (!needsLock()){
|
|
return true;
|
|
}else{
|
|
if (!config->getString("streamname").size()){
|
|
if (config->getString("output") == "-"){
|
|
std::cerr << "Output to stdout not yet supported" << std::endl;
|
|
return false;
|
|
}
|
|
}else{
|
|
if (config->getString("output") != "-"){
|
|
std::cerr << "File output in player mode not supported" << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// open File
|
|
inFile = DTSC::File(config->getString("input"));
|
|
if (!inFile){return false;}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool inputDTSC::needHeader(){
|
|
if (!needsLock()){return false;}
|
|
return Input::needHeader();
|
|
}
|
|
|
|
bool inputDTSC::readHeader(){
|
|
if (!inFile){return false;}
|
|
if (inFile.getMeta().moreheader < 0 || inFile.getMeta().tracks.size() == 0){
|
|
DEBUG_MSG(DLVL_FAIL, "Missing external header file");
|
|
return false;
|
|
}
|
|
myMeta = DTSC::Meta(inFile.getMeta());
|
|
DEBUG_MSG(DLVL_DEVEL, "Meta read in with %lu tracks", myMeta.tracks.size());
|
|
return true;
|
|
}
|
|
|
|
void inputDTSC::getNext(bool smart){
|
|
if (!needsLock()){
|
|
thisPacket.reInit(srcConn);
|
|
while (config->is_active){
|
|
if (thisPacket.getVersion() == DTSC::DTCM){
|
|
nProxy.userClient.keepAlive();
|
|
std::string cmd;
|
|
thisPacket.getString("cmd", cmd);
|
|
if (cmd == "reset"){
|
|
// Read next packet
|
|
thisPacket.reInit(srcConn);
|
|
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
|
|
DTSC::Meta newMeta;
|
|
newMeta.reinit(thisPacket);
|
|
// Detect new tracks
|
|
std::set<unsigned int> newTracks;
|
|
for (std::map<unsigned int, DTSC::Track>::iterator it = newMeta.tracks.begin();
|
|
it != newMeta.tracks.end(); it++){
|
|
if (!myMeta.tracks.count(it->first)){newTracks.insert(it->first);}
|
|
}
|
|
|
|
for (std::set<unsigned int>::iterator it = newTracks.begin(); it != newTracks.end(); it++){
|
|
INFO_MSG("Reset: adding track %d", *it);
|
|
myMeta.tracks[*it] = newMeta.tracks[*it];
|
|
continueNegotiate(*it, true);
|
|
}
|
|
|
|
// Detect removed tracks
|
|
std::set<unsigned int> deletedTracks;
|
|
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
|
|
it != myMeta.tracks.end(); it++){
|
|
if (!newMeta.tracks.count(it->first)){deletedTracks.insert(it->first);}
|
|
}
|
|
|
|
for (std::set<unsigned int>::iterator it = deletedTracks.begin();
|
|
it != deletedTracks.end(); it++){
|
|
INFO_MSG("Reset: deleting track %d", *it);
|
|
myMeta.tracks.erase(*it);
|
|
}
|
|
thisPacket.reInit(srcConn); // read the next packet before continuing
|
|
}else{
|
|
myMeta = DTSC::Meta();
|
|
}
|
|
}else{
|
|
thisPacket.reInit(srcConn); // read the next packet before continuing
|
|
}
|
|
continue; // parse the next packet before returning
|
|
}else if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
|
|
DTSC::Meta newMeta;
|
|
newMeta.reinit(thisPacket);
|
|
std::set<unsigned int> newTracks;
|
|
for (std::map<unsigned int, DTSC::Track>::iterator it = newMeta.tracks.begin();
|
|
it != newMeta.tracks.end(); it++){
|
|
if (!myMeta.tracks.count(it->first)){newTracks.insert(it->first);}
|
|
}
|
|
|
|
for (std::set<unsigned int>::iterator it = newTracks.begin(); it != newTracks.end(); it++){
|
|
INFO_MSG("New header: adding track %d (%s)", *it, newMeta.tracks[*it].type.c_str());
|
|
myMeta.tracks[*it] = newMeta.tracks[*it];
|
|
continueNegotiate(*it, true);
|
|
}
|
|
thisPacket.reInit(srcConn); // read the next packet before continuing
|
|
continue; // parse the next packet before returning
|
|
}
|
|
// We now know we have either a data packet, or an error.
|
|
if (!thisPacket.getTrackId()){
|
|
if (thisPacket.getVersion() == DTSC::DTSC_V2){
|
|
WARN_MSG("Received bad packet for stream %s: %llu@%llu", streamName.c_str(),
|
|
thisPacket.getTrackId(), thisPacket.getTime());
|
|
}else{
|
|
// All types except data packets are handled above, so if it's not a V2 data packet, we assume corruption
|
|
WARN_MSG("Invalid packet header for stream %s", streamName.c_str());
|
|
}
|
|
}
|
|
return; // we have a packet
|
|
}
|
|
}else{
|
|
if (smart){
|
|
inFile.seekNext();
|
|
}else{
|
|
inFile.parseNext();
|
|
}
|
|
thisPacket = inFile.getPacket();
|
|
}
|
|
}
|
|
|
|
void inputDTSC::seek(int seekTime){
|
|
inFile.seek_time(seekTime);
|
|
initialTime = 0;
|
|
playUntil = 0;
|
|
}
|
|
|
|
void inputDTSC::trackSelect(std::string trackSpec){
|
|
selectedTracks.clear();
|
|
long long unsigned int index;
|
|
while (trackSpec != ""){
|
|
index = trackSpec.find(' ');
|
|
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
|
if (index != std::string::npos){
|
|
trackSpec.erase(0, index + 1);
|
|
}else{
|
|
trackSpec = "";
|
|
}
|
|
}
|
|
inFile.selectTracks(selectedTracks);
|
|
}
|
|
}// namespace Mist
|