Added SRT input, MP4 output subtitle support, DASH and HLS output subtitle support

This commit is contained in:
Ramoe 2017-11-23 10:18:28 +01:00 committed by Thulinma
parent b9e261e1ef
commit e09cd5308e
6 changed files with 374 additions and 133 deletions

View file

@ -382,6 +382,8 @@ makeInput(Folder folder)#LTS
makeInput(Balancer balancer)#LTS makeInput(Balancer balancer)#LTS
makeInput(RTSP rtsp)#LTS makeInput(RTSP rtsp)#LTS
makeInput(SRT srt)#LTS
######################################## ########################################
# MistServer - Outputs # # MistServer - Outputs #
######################################## ########################################

145
src/input/input_srt.cpp Normal file
View file

@ -0,0 +1,145 @@
#include "input_srt.h"
namespace Mist{
InputSrt::InputSrt(Util::Config *cfg) : Input(cfg){
capa["name"] = "SRT";
capa["decs"] = "Enables SRT Input";
capa["source_match"].append("/*.srt");
capa["source_match"].append("/*.vtt");
capa["priority"] = 9ll;
capa["codecs"][0u][0u].append("subtitle");
}
bool InputSrt::preRun(){
fileSource.close();
fileSource.open(config->getString("input").c_str());
if (!fileSource.is_open()){
FAIL_MSG("Could not open file %s: %s", config->getString("input").c_str(), strerror(errno));
return false;
}
return true;
}
bool InputSrt::checkArguments(){
if (config->getString("input") == "-"){
FAIL_MSG("Reading from standard input not yet supported");
return false;
}else{
preRun();
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-"){
FAIL_MSG("Writing to standard output not yet supported");
return false;
}
}else{
if (config->getString("output") != "-"){
FAIL_MSG("File output in player mode not supported");
return false;
}
}
return true;
}
bool InputSrt::readHeader(){
if (!fileSource.good()){return false;}
myMeta.tracks[1].trackID = 1;
myMeta.tracks[1].type = "meta";
myMeta.tracks[1].codec = "subtitle";
getNext();
while (thisPacket){
myMeta.update(thisPacket);
getNext();
}
// outputting dtsh file
myMeta.toFile(config->getString("input") + ".dtsh");
return true;
}
void InputSrt::getNext(bool smart){
bool hasPacket = false;
thisPacket.null();
std::string line;
uint32_t index;
uint32_t timestamp;
uint32_t duration;
int lineNr = 0;
std::string data;
while (std::getline(fileSource, line)){// && !line.empty()){
INFO_MSG("");
INFO_MSG("reading line: %s", line.c_str());
if (line.size() >= 7 && line.substr(0, 7) == "WEBVTT"){
vtt = true;
std::getline(fileSource, line);
lineNr++;
}
lineNr++;
INFO_MSG("linenr: %d", lineNr);
if (line.empty() || (line.size() == 1 && line.at(0) == '\r')){
static JSON::Value thisPack;
thisPack.null();
thisPack["trackid"] = 1;
thisPack["bpos"] = (long long)fileSource.tellg();
thisPack["data"] = data;
thisPack["index"] = index;
thisPack["time"] = timestamp;
thisPack["duration"] = duration;
// Write the json value to lastpack
std::string tmpStr = thisPack.toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
lineNr = 0;
if (vtt){lineNr++;}
return;
}else{
if (lineNr == 1){
index = atoi(line.c_str());
}else if (lineNr == 2){
// timestamp
int from_hour = 0;
int from_min = 0;
int from_sec = 0;
int from_ms = 0;
int to_hour = 0;
int to_min = 0;
int to_sec = 0;
int to_ms = 0;
sscanf(line.c_str(), "%d:%d:%d,%d --> %d:%d:%d,%d", &from_hour, &from_min, &from_sec,
&from_ms, &to_hour, &to_min, &to_sec, &to_ms);
timestamp =
(from_hour * 60 * 60 * 1000) + (from_min * 60 * 1000) + (from_sec * 1000) + from_ms;
duration = ((to_hour * 60 * 60 * 1000) + (to_min * 60 * 1000) + (to_sec * 1000) + to_ms) -
timestamp;
}else{
// subtitle
if (data.size() > 1){data.append("\n");}
data.append(line);
}
}
}
thisPacket.null();
}
void InputSrt::seek(int seekTime){fileSource.seekg(0, fileSource.beg);}
void InputSrt::trackSelect(std::string trackSpec){
// we only have one track..
selectedTracks.clear();
selectedTracks.insert(1);
}
}// namespace Mist

34
src/input/input_srt.h Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include "input.h"
#include <fstream>
#include <mist/dtsc.h>
#include <string>
namespace Mist{
class InputSrt : public Input{
public:
InputSrt(Util::Config *cfg);
protected:
std::ifstream fileSource;
bool checkArguments();
bool readHeader();
bool preRun();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool vtt = false;
FILE * inFile;
};
}
typedef Mist::InputSrt mistIn;

View file

@ -158,6 +158,7 @@ namespace Mist {
MP4::SMHD smhdBox; MP4::SMHD smhdBox;
minfBox.setContent(smhdBox, 2); minfBox.setContent(smhdBox, 2);
} }
mdiaBox.setContent(minfBox, 2); mdiaBox.setContent(minfBox, 2);
trakBox.setContent(mdiaBox, 1); trakBox.setContent(mdiaBox, 1);
moovBox.setContent(trakBox, 3); moovBox.setContent(trakBox, 3);
@ -183,6 +184,7 @@ namespace Mist {
tfdtBox.setBaseMediaDecodeTime(Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime()); tfdtBox.setBaseMediaDecodeTime(Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime());
trafBox.setContent(tfdtBox, 1); trafBox.setContent(tfdtBox, 1);
MP4::TRUN trunBox; MP4::TRUN trunBox;
if (Trk.type == "video"){ if (Trk.type == "video"){
uint32_t headSize = 0; uint32_t headSize = 0;
if (Trk.codec == "H264"){ if (Trk.codec == "H264"){
@ -371,6 +373,8 @@ namespace Mist {
uint64_t vidInitTrack = 0; uint64_t vidInitTrack = 0;
uint64_t lastAudTime = 0; uint64_t lastAudTime = 0;
uint64_t audInitTrack = 0; uint64_t audInitTrack = 0;
uint64_t subInitTrack = 0;
/// \TODO DASH pretends there is only one audio/video track, and then prints them all using the same timing information. This is obviously wrong if the tracks are not in sync. /// \TODO DASH pretends there is only one audio/video track, and then prints them all using the same timing information. This is obviously wrong if the tracks are not in sync.
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it ++){ for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it ++){
if ((it->second.codec == "H264" || it->second.codec == "HEVC") && it->second.lastms > lastVidTime){ if ((it->second.codec == "H264" || it->second.codec == "HEVC") && it->second.lastms > lastVidTime){
@ -381,8 +385,12 @@ namespace Mist {
lastAudTime = it->second.lastms; lastAudTime = it->second.lastms;
audInitTrack = it->first; audInitTrack = it->first;
} }
if(it->second.codec == "subtitle"){
subInitTrack = it->first;
}
} }
std::stringstream r; std::stringstream r;
r << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl; r << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
r << "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "; r << "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" ";
if (myMeta.vod){ if (myMeta.vod){
@ -456,6 +464,20 @@ namespace Mist {
} }
r << " </AdaptationSet>" << std::endl; r << " </AdaptationSet>" << std::endl;
} }
if(subInitTrack){
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
if(it->second.codec == "subtitle"){
subInitTrack = it->first;
std::string lang = (it->second.lang == "" ? "unknown" : it->second.lang);
r << "<AdaptationSet group=\"3\" mimeType=\"text/vtt\" lang=\"" << lang << "\">";
r << " <Representation id=\"caption_en"<< it->first << "\" bandwidth=\"256\">";
r << " <BaseURL>../../" << streamName << ".vtt?track=" << it->first << "</BaseURL>";
r << " </Representation></AdaptationSet>" << std::endl;
}
}
}
r << " </Period>" << std::endl; r << " </Period>" << std::endl;
r << "</MPD>" << std::endl; r << "</MPD>" << std::endl;
@ -474,6 +496,8 @@ namespace Mist {
capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("AC3"); capa["codecs"][0u][1u].append("AC3");
capa["codecs"][0u][1u].append("MP3"); capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][2u].append("subtitle");
capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "dash/video/mp4"; capa["methods"][0u]["type"] = "dash/video/mp4";
capa["methods"][0u]["priority"] = 8ll; capa["methods"][0u]["priority"] = 8ll;

View file

@ -1,5 +1,6 @@
#include "output_hls.h" #include "output_hls.h"
#include <mist/stream.h> #include <mist/stream.h>
#include <mist/langcodes.h> /*LTS*/
#include <unistd.h> #include <unistd.h>
namespace Mist { namespace Mist {

View file

@ -86,6 +86,12 @@ namespace Mist {
} }
} }
if (thisTrack.type == "meta"){
tmpRes += 12 //NMHD Box
+ 16//STSD
+ 64; //tx3g Box
}
if (!fragmented){ if (!fragmented){
//Unfortunately, for our STTS and CTTS boxes, we need to loop through all parts of the track //Unfortunately, for our STTS and CTTS boxes, we need to loop through all parts of the track
uint64_t sttsCount = 1; uint64_t sttsCount = 1;
@ -131,7 +137,6 @@ namespace Mist {
return res; return res;
} }
///\todo This function does not indicate errors anywhere... maybe fix this... ///\todo This function does not indicate errors anywhere... maybe fix this...
std::string OutProgressiveMP4::DTSCMeta2MP4Header(uint64_t & size, int fragmented){ std::string OutProgressiveMP4::DTSCMeta2MP4Header(uint64_t & size, int fragmented){
if (myMeta.live){ if (myMeta.live){
@ -146,7 +151,6 @@ namespace Mist {
//Keeps track of the total size of the mdat box //Keeps track of the total size of the mdat box
uint64_t mdatSize = 0; uint64_t mdatSize = 0;
//Start actually creating the header //Start actually creating the header
//MP4 Files always start with an FTYP box. Constructor sets default values //MP4 Files always start with an FTYP box. Constructor sets default values
@ -162,7 +166,6 @@ namespace Mist {
//Keep track of the current index within the moovBox //Keep track of the current index within the moovBox
unsigned int moovOffset = 0; unsigned int moovOffset = 0;
//Construct with duration of -1, as this is the default for fragmented //Construct with duration of -1, as this is the default for fragmented
MP4::MVHD mvhdBox(-1); MP4::MVHD mvhdBox(-1);
//Then override it only when we are not sending a fragmented file //Then override it only when we are not sending a fragmented file
@ -224,6 +227,9 @@ namespace Mist {
MP4::MINF minfBox; MP4::MINF minfBox;
size_t minfOffset = 0; size_t minfOffset = 0;
MP4::STBL stblBox;
unsigned int stblOffset = 0;
//Add a track-type specific box to the MINF box //Add a track-type specific box to the MINF box
if (thisTrack.type == "video"){ if (thisTrack.type == "video"){
MP4::VMHD vmhdBox; MP4::VMHD vmhdBox;
@ -232,6 +238,10 @@ namespace Mist {
}else if (thisTrack.type == "audio"){ }else if (thisTrack.type == "audio"){
MP4::SMHD smhdBox; MP4::SMHD smhdBox;
minfBox.setContent(smhdBox, minfOffset++); minfBox.setContent(smhdBox, minfOffset++);
}else{
//create nmhd box
MP4::NMHD nmhdBox;
minfBox.setContent(nmhdBox, minfOffset++);
} }
//Add the mandatory DREF (dataReference) box //Add the mandatory DREF (dataReference) box
@ -240,10 +250,6 @@ namespace Mist {
dinfBox.setContent(drefBox, 0); dinfBox.setContent(drefBox, 0);
minfBox.setContent(dinfBox, minfOffset++); minfBox.setContent(dinfBox, minfOffset++);
MP4::STBL stblBox;
size_t stblOffset = 0;
//Add STSD box //Add STSD box
MP4::STSD stsdBox(0); MP4::STSD stsdBox(0);
if (thisTrack.type == "video"){ if (thisTrack.type == "video"){
@ -252,9 +258,16 @@ namespace Mist {
}else if (thisTrack.type == "audio"){ }else if (thisTrack.type == "audio"){
MP4::AudioSampleEntry sampleEntry(thisTrack); MP4::AudioSampleEntry sampleEntry(thisTrack);
stsdBox.setEntry(sampleEntry, 0); stsdBox.setEntry(sampleEntry, 0);
} }else if (thisTrack.type == "meta"){
stblBox.setContent(stsdBox, stblOffset++); INFO_MSG("add subtitlesample\n");
MP4::TextSampleEntry sampleEntry(thisTrack);
MP4::FontTableBox ftab;
sampleEntry.setFontTableBox(ftab);
stsdBox.setEntry(sampleEntry, 0);
}
stblBox.setContent(stsdBox, stblOffset++);
//Add STTS Box //Add STTS Box
//note: STTS is empty when fragmented //note: STTS is empty when fragmented
@ -267,7 +280,6 @@ namespace Mist {
MP4::CTTS cttsBox; MP4::CTTS cttsBox;
cttsBox.setVersion(0); cttsBox.setVersion(0);
MP4::CTTSEntry tmpEntry; MP4::CTTSEntry tmpEntry;
tmpEntry.sampleCount = 0; tmpEntry.sampleCount = 0;
tmpEntry.sampleOffset = thisTrack.parts[0].getOffset(); tmpEntry.sampleOffset = thisTrack.parts[0].getOffset();
@ -290,6 +302,10 @@ namespace Mist {
//Update the counter //Update the counter
sttsCounter.rbegin()->first++; sttsCounter.rbegin()->first++;
if(thisTrack.type == "meta"){
partSize += 2;
}
stszBox.setEntrySize(partSize, part); stszBox.setEntrySize(partSize, part);
size += partSize; size += partSize;
@ -338,7 +354,6 @@ namespace Mist {
} }
stblBox.setContent(stscBox, stblOffset++); stblBox.setContent(stscBox, stblOffset++);
//Create STCO Box (either stco or co64) //Create STCO Box (either stco or co64)
//note: 64bit boxes will never be used in fragmented //note: 64bit boxes will never be used in fragmented
//note: Inserting empty values on purpose here, will be fixed later. //note: Inserting empty values on purpose here, will be fixed later.
@ -377,7 +392,6 @@ namespace Mist {
//initial offset length ftyp, length moov + 8 //initial offset length ftyp, length moov + 8
uint64_t dataOffset = ftypBox.boxedSize() + moovBox.boxedSize() + 8; uint64_t dataOffset = ftypBox.boxedSize() + moovBox.boxedSize() + 8;
std::map <size_t, MP4::STCO> checkStcoBoxes; std::map <size_t, MP4::STCO> checkStcoBoxes;
std::map <size_t, MP4::CO64> checkCO64Boxes; std::map <size_t, MP4::CO64> checkCO64Boxes;
@ -423,6 +437,10 @@ namespace Mist {
} }
dataSize += thisTrack.parts[temp.index].getSize(); dataSize += thisTrack.parts[temp.index].getSize();
if(thisTrack.type == "meta"){
dataSize += 2;
}
//add next keyPart to sortSet //add next keyPart to sortSet
if (temp.index + 1< thisTrack.parts.size()){//Only create new element, when there are new elements to be added if (temp.index + 1< thisTrack.parts.size()){//Only create new element, when there are new elements to be added
temp.time += thisTrack.parts[temp.index].getDuration(); temp.time += thisTrack.parts[temp.index].getDuration();
@ -473,6 +491,11 @@ namespace Mist {
DTSC::Track & thisTrack = myMeta.tracks[temp.trackID]; DTSC::Track & thisTrack = myMeta.tracks[temp.trackID];
uint64_t partSize = thisTrack.parts[temp.index].getSize(); uint64_t partSize = thisTrack.parts[temp.index].getSize();
//add 2 bytes in front of the subtitle that contains the length of the subtitle.
if(myMeta.tracks[temp.trackID].codec == "subtitle"){
partSize += 2;
}
//record where we are //record where we are
seekPoint = temp.time; seekPoint = temp.time;
//substract the size of this fragment from byteStart //substract the size of this fragment from byteStart
@ -482,7 +505,6 @@ namespace Mist {
return; return;
} }
byteStart -= partSize; byteStart -= partSize;
//otherwise, set currPos to where we are now and continue //otherwise, set currPos to where we are now and continue
@ -758,6 +780,7 @@ namespace Mist {
realTime = 0; realTime = 0;
} }
} }
/*LTS-END*/ /*LTS-END*/
//Make sure we start receiving data after this function //Make sure we start receiving data after this function
@ -920,8 +943,7 @@ namespace Mist {
char * dataPointer = 0; char * dataPointer = 0;
unsigned int len = 0; unsigned int len = 0;
thisPacket.getString("data", dataPointer, len); thisPacket.getString("data", dataPointer, len);
std::string subtitle;
if (myMeta.live){ if (myMeta.live){
//if header needed //if header needed
@ -944,6 +966,7 @@ namespace Mist {
partListSent++; partListSent++;
} }
keyPart thisPart = *sortSet.begin(); keyPart thisPart = *sortSet.begin();
if ((unsigned long)thisPacket.getTrackId() != thisPart.trackID || thisPacket.getTime() != thisPart.time || len != thisPart.size){ if ((unsigned long)thisPacket.getTrackId() != thisPart.trackID || thisPacket.getTime() != thisPart.time || len != thisPart.size){
if (thisPacket.getTime() > sortSet.begin()->time || thisPacket.getTrackId() > sortSet.begin()->trackID){ if (thisPacket.getTime() > sortSet.begin()->time || thisPacket.getTrackId() > sortSet.begin()->trackID){
@ -964,6 +987,17 @@ namespace Mist {
return; return;
} }
//prepend subtitle text with 2 bytes datalength
if(myMeta.tracks[thisPacket.getTrackId()].codec == "subtitle"){
char pre[2];
Bit::htobs(pre,len);
subtitle.assign(pre,2);
subtitle.append(dataPointer, len);
dataPointer = (char*)subtitle.c_str();
len+=2;
}
if (currPos >= byteStart){ if (currPos >= byteStart){
myConn.SendNow(dataPointer, std::min(leftOver, (int64_t)len)); myConn.SendNow(dataPointer, std::min(leftOver, (int64_t)len));
leftOver -= len; leftOver -= len;
@ -1024,3 +1058,4 @@ namespace Mist {
} }
} }