Added SRT input, MP4 output subtitle support, DASH and HLS output subtitle support
This commit is contained in:
parent
b9e261e1ef
commit
e09cd5308e
6 changed files with 374 additions and 133 deletions
|
@ -382,6 +382,8 @@ makeInput(Folder folder)#LTS
|
|||
makeInput(Balancer balancer)#LTS
|
||||
makeInput(RTSP rtsp)#LTS
|
||||
|
||||
makeInput(SRT srt)#LTS
|
||||
|
||||
########################################
|
||||
# MistServer - Outputs #
|
||||
########################################
|
||||
|
|
145
src/input/input_srt.cpp
Normal file
145
src/input/input_srt.cpp
Normal 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
34
src/input/input_srt.h
Normal 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;
|
||||
|
|
@ -158,6 +158,7 @@ namespace Mist {
|
|||
MP4::SMHD smhdBox;
|
||||
minfBox.setContent(smhdBox, 2);
|
||||
}
|
||||
|
||||
mdiaBox.setContent(minfBox, 2);
|
||||
trakBox.setContent(mdiaBox, 1);
|
||||
moovBox.setContent(trakBox, 3);
|
||||
|
@ -183,6 +184,7 @@ namespace Mist {
|
|||
tfdtBox.setBaseMediaDecodeTime(Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime());
|
||||
trafBox.setContent(tfdtBox, 1);
|
||||
MP4::TRUN trunBox;
|
||||
|
||||
if (Trk.type == "video"){
|
||||
uint32_t headSize = 0;
|
||||
if (Trk.codec == "H264"){
|
||||
|
@ -371,6 +373,8 @@ namespace Mist {
|
|||
uint64_t vidInitTrack = 0;
|
||||
uint64_t lastAudTime = 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.
|
||||
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){
|
||||
|
@ -381,8 +385,12 @@ namespace Mist {
|
|||
lastAudTime = it->second.lastms;
|
||||
audInitTrack = it->first;
|
||||
}
|
||||
if(it->second.codec == "subtitle"){
|
||||
subInitTrack = it->first;
|
||||
}
|
||||
}
|
||||
std::stringstream r;
|
||||
|
||||
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\" ";
|
||||
if (myMeta.vod){
|
||||
|
@ -456,6 +464,20 @@ namespace Mist {
|
|||
}
|
||||
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 << "</MPD>" << std::endl;
|
||||
|
||||
|
@ -474,6 +496,8 @@ namespace Mist {
|
|||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
capa["codecs"][0u][2u].append("subtitle");
|
||||
|
||||
capa["methods"][0u]["handler"] = "http";
|
||||
capa["methods"][0u]["type"] = "dash/video/mp4";
|
||||
capa["methods"][0u]["priority"] = 8ll;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "output_hls.h"
|
||||
#include <mist/stream.h>
|
||||
#include <mist/langcodes.h> /*LTS*/
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Mist {
|
||||
|
|
|
@ -86,6 +86,12 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
|
||||
if (thisTrack.type == "meta"){
|
||||
tmpRes += 12 //NMHD Box
|
||||
+ 16//STSD
|
||||
+ 64; //tx3g Box
|
||||
}
|
||||
|
||||
if (!fragmented){
|
||||
//Unfortunately, for our STTS and CTTS boxes, we need to loop through all parts of the track
|
||||
uint64_t sttsCount = 1;
|
||||
|
@ -131,7 +137,6 @@ namespace Mist {
|
|||
return res;
|
||||
}
|
||||
|
||||
|
||||
///\todo This function does not indicate errors anywhere... maybe fix this...
|
||||
std::string OutProgressiveMP4::DTSCMeta2MP4Header(uint64_t & size, int fragmented){
|
||||
if (myMeta.live){
|
||||
|
@ -146,7 +151,6 @@ namespace Mist {
|
|||
//Keeps track of the total size of the mdat box
|
||||
uint64_t mdatSize = 0;
|
||||
|
||||
|
||||
//Start actually creating the header
|
||||
|
||||
//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
|
||||
unsigned int moovOffset = 0;
|
||||
|
||||
|
||||
//Construct with duration of -1, as this is the default for fragmented
|
||||
MP4::MVHD mvhdBox(-1);
|
||||
//Then override it only when we are not sending a fragmented file
|
||||
|
@ -224,6 +227,9 @@ namespace Mist {
|
|||
MP4::MINF minfBox;
|
||||
size_t minfOffset = 0;
|
||||
|
||||
MP4::STBL stblBox;
|
||||
unsigned int stblOffset = 0;
|
||||
|
||||
//Add a track-type specific box to the MINF box
|
||||
if (thisTrack.type == "video"){
|
||||
MP4::VMHD vmhdBox;
|
||||
|
@ -232,6 +238,10 @@ namespace Mist {
|
|||
}else if (thisTrack.type == "audio"){
|
||||
MP4::SMHD smhdBox;
|
||||
minfBox.setContent(smhdBox, minfOffset++);
|
||||
}else{
|
||||
//create nmhd box
|
||||
MP4::NMHD nmhdBox;
|
||||
minfBox.setContent(nmhdBox, minfOffset++);
|
||||
}
|
||||
|
||||
//Add the mandatory DREF (dataReference) box
|
||||
|
@ -240,10 +250,6 @@ namespace Mist {
|
|||
dinfBox.setContent(drefBox, 0);
|
||||
minfBox.setContent(dinfBox, minfOffset++);
|
||||
|
||||
|
||||
MP4::STBL stblBox;
|
||||
size_t stblOffset = 0;
|
||||
|
||||
//Add STSD box
|
||||
MP4::STSD stsdBox(0);
|
||||
if (thisTrack.type == "video"){
|
||||
|
@ -252,9 +258,16 @@ namespace Mist {
|
|||
}else if (thisTrack.type == "audio"){
|
||||
MP4::AudioSampleEntry sampleEntry(thisTrack);
|
||||
stsdBox.setEntry(sampleEntry, 0);
|
||||
}
|
||||
stblBox.setContent(stsdBox, stblOffset++);
|
||||
}else if (thisTrack.type == "meta"){
|
||||
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
|
||||
//note: STTS is empty when fragmented
|
||||
|
@ -267,7 +280,6 @@ namespace Mist {
|
|||
MP4::CTTS cttsBox;
|
||||
cttsBox.setVersion(0);
|
||||
|
||||
|
||||
MP4::CTTSEntry tmpEntry;
|
||||
tmpEntry.sampleCount = 0;
|
||||
tmpEntry.sampleOffset = thisTrack.parts[0].getOffset();
|
||||
|
@ -290,6 +302,10 @@ namespace Mist {
|
|||
//Update the counter
|
||||
sttsCounter.rbegin()->first++;
|
||||
|
||||
if(thisTrack.type == "meta"){
|
||||
partSize += 2;
|
||||
}
|
||||
|
||||
stszBox.setEntrySize(partSize, part);
|
||||
size += partSize;
|
||||
|
||||
|
@ -338,7 +354,6 @@ namespace Mist {
|
|||
}
|
||||
stblBox.setContent(stscBox, stblOffset++);
|
||||
|
||||
|
||||
//Create STCO Box (either stco or co64)
|
||||
//note: 64bit boxes will never be used in fragmented
|
||||
//note: Inserting empty values on purpose here, will be fixed later.
|
||||
|
@ -377,7 +392,6 @@ namespace Mist {
|
|||
//initial offset length ftyp, length moov + 8
|
||||
uint64_t dataOffset = ftypBox.boxedSize() + moovBox.boxedSize() + 8;
|
||||
|
||||
|
||||
std::map <size_t, MP4::STCO> checkStcoBoxes;
|
||||
std::map <size_t, MP4::CO64> checkCO64Boxes;
|
||||
|
||||
|
@ -423,6 +437,10 @@ namespace Mist {
|
|||
}
|
||||
dataSize += thisTrack.parts[temp.index].getSize();
|
||||
|
||||
if(thisTrack.type == "meta"){
|
||||
dataSize += 2;
|
||||
}
|
||||
|
||||
//add next keyPart to sortSet
|
||||
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();
|
||||
|
@ -473,6 +491,11 @@ namespace Mist {
|
|||
DTSC::Track & thisTrack = myMeta.tracks[temp.trackID];
|
||||
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
|
||||
seekPoint = temp.time;
|
||||
//substract the size of this fragment from byteStart
|
||||
|
@ -482,7 +505,6 @@ namespace Mist {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
byteStart -= partSize;
|
||||
|
||||
//otherwise, set currPos to where we are now and continue
|
||||
|
@ -758,6 +780,7 @@ namespace Mist {
|
|||
realTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*LTS-END*/
|
||||
|
||||
//Make sure we start receiving data after this function
|
||||
|
@ -920,8 +943,7 @@ namespace Mist {
|
|||
char * dataPointer = 0;
|
||||
unsigned int len = 0;
|
||||
thisPacket.getString("data", dataPointer, len);
|
||||
|
||||
|
||||
std::string subtitle;
|
||||
|
||||
if (myMeta.live){
|
||||
//if header needed
|
||||
|
@ -944,6 +966,7 @@ namespace Mist {
|
|||
partListSent++;
|
||||
}
|
||||
|
||||
|
||||
keyPart thisPart = *sortSet.begin();
|
||||
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){
|
||||
|
@ -964,6 +987,17 @@ namespace Mist {
|
|||
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){
|
||||
myConn.SendNow(dataPointer, std::min(leftOver, (int64_t)len));
|
||||
leftOver -= len;
|
||||
|
@ -1024,3 +1058,4 @@ namespace Mist {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue