This commit is contained in:
DDVTech 2021-09-10 23:44:31 +02:00 committed by Thulinma
parent 5b79f296d6
commit fccf66fba2
280 changed files with 56975 additions and 71885 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,98 +1,98 @@
#include <set>
#include <map>
#include <cstdlib>
#include <mist/config.h>
#include <mist/json.h>
#include <mist/timing.h>
#include <mist/dtsc.h>
#include <mist/defines.h>
#include <mist/shared_memory.h>
#include <fstream>
#include <map>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/dtsc.h>
#include <mist/json.h>
#include <mist/shared_memory.h>
#include <mist/timing.h>
#include <set>
#include "../io.h"
namespace Mist {
struct booking {
namespace Mist{
struct booking{
int first;
int curKey;
int curPart;
};
class Input : public InOutBase {
public:
Input(Util::Config * cfg);
virtual int run();
virtual void onCrash(){}
virtual int boot(int argc, char * argv[]);
virtual ~Input() {};
class Input : public InOutBase{
public:
Input(Util::Config *cfg);
virtual int run();
virtual void onCrash(){}
virtual int boot(int argc, char *argv[]);
virtual ~Input(){};
static Util::Config * config;
virtual bool needsLock(){return !config->getBool("realtime");}
protected:
static void callbackWrapper(char * data, size_t len, unsigned int id);
virtual bool checkArguments() = 0;
virtual bool readHeader() = 0;
virtual bool needHeader(){return !readExistingHeader();}
virtual bool preRun(){return true;}
virtual bool isSingular(){return !config->getBool("realtime");}
virtual bool readExistingHeader();
virtual bool atKeyFrame();
virtual void getNext(bool smart = true) {}
virtual void seek(int seekTime){};
virtual void finish();
virtual bool keepRunning();
virtual bool openStreamSource() { return readHeader(); }
virtual void closeStreamSource() {}
virtual void parseStreamHeader() {}
void play(int until = 0);
void playOnce();
void quitPlay();
void checkHeaderTimes(std::string streamFile);
virtual void removeUnused();
virtual void trackSelect(std::string trackSpec);
virtual void userCallback(char * data, size_t len, unsigned int id);
virtual void convert();
virtual void serve();
virtual void stream();
virtual std::string streamMainLoop();
virtual std::string realtimeMainLoop();
bool isAlwaysOn();
static Util::Config *config;
virtual bool needsLock(){return !config->getBool("realtime");}
virtual void parseHeader();
bool bufferFrame(unsigned int track, unsigned int keyNum);
protected:
static void callbackWrapper(char *data, size_t len, unsigned int id);
virtual bool checkArguments() = 0;
virtual bool readHeader() = 0;
virtual bool needHeader(){return !readExistingHeader();}
virtual bool preRun(){return true;}
virtual bool isSingular(){return !config->getBool("realtime");}
virtual bool readExistingHeader();
virtual bool atKeyFrame();
virtual void getNext(bool smart = true){}
virtual void seek(int seekTime){};
virtual void finish();
virtual bool keepRunning();
virtual bool openStreamSource(){return readHeader();}
virtual void closeStreamSource(){}
virtual void parseStreamHeader(){}
void play(int until = 0);
void playOnce();
void quitPlay();
void checkHeaderTimes(std::string streamFile);
virtual void removeUnused();
virtual void trackSelect(std::string trackSpec);
virtual void userCallback(char *data, size_t len, unsigned int id);
virtual void convert();
virtual void serve();
virtual void stream();
virtual std::string streamMainLoop();
virtual std::string realtimeMainLoop();
bool isAlwaysOn();
unsigned int packTime;///Media-timestamp of the last packet.
int lastActive;///Timestamp of the last time we received or sent something.
int initialTime;
int playing;
unsigned int playUntil;
virtual void parseHeader();
bool bufferFrame(unsigned int track, unsigned int keyNum);
bool isBuffer;
uint64_t activityCounter;
unsigned int packTime; /// Media-timestamp of the last packet.
int lastActive; /// Timestamp of the last time we received or sent something.
int initialTime;
int playing;
unsigned int playUntil;
JSON::Value capa;
std::map<int,std::set<int> > keyTimes;
int64_t timeOffset;
bool isBuffer;
uint64_t activityCounter;
//Create server for user pages
IPC::sharedServer userPage;
IPC::sharedPage streamStatus;
JSON::Value capa;
std::map<unsigned int, std::map<unsigned int, unsigned int> > pageCounter;
std::map<int, std::set<int> > keyTimes;
int64_t timeOffset;
static Input * singleton;
// Create server for user pages
IPC::sharedServer userPage;
IPC::sharedPage streamStatus;
bool hasSrt;
std::ifstream srtSource;
unsigned int srtTrack;
std::map<unsigned int, std::map<unsigned int, unsigned int> > pageCounter;
void readSrtHeader();
void getNextSrt(bool smart = true);
DTSC::Packet srtPack;
static Input *singleton;
uint64_t simStartTime;
bool hasSrt;
std::ifstream srtSource;
unsigned int srtTrack;
void readSrtHeader();
void getNextSrt(bool smart = true);
DTSC::Packet srtPack;
uint64_t simStartTime;
};
}
}// namespace Mist

View file

@ -1,20 +1,22 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/stream.h>
#include <string>
#include "input_av.h"
namespace Mist {
inputAV::inputAV(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputAV::inputAV(Util::Config *cfg) : Input(cfg){
pFormatCtx = 0;
capa["name"] = "AV";
capa["desc"] = "This input uses libavformat to read any type of file. Unfortunately this input cannot be redistributed, but it is a great tool for testing the other file-based inputs against.";
capa["desc"] =
"This input uses libavformat to read any type of file. Unfortunately this input cannot be "
"redistributed, but it is a great tool for testing the other file-based inputs against.";
capa["source_match"] = "/*";
capa["source_file"] = "$source";
capa["priority"] = 1ll;
@ -22,38 +24,30 @@ namespace Mist {
capa["codecs"][0u][1u].null();
capa["codecs"][0u][2u].null();
av_register_all();
AVCodec * cInfo = 0;
AVCodec *cInfo = 0;
while ((cInfo = av_codec_next(cInfo)) != 0){
if (cInfo->type == AVMEDIA_TYPE_VIDEO){
capa["codecs"][0u][0u].append(cInfo->name);
}
if (cInfo->type == AVMEDIA_TYPE_AUDIO){
capa["codecs"][0u][1u].append(cInfo->name);
}
if (cInfo->type == AVMEDIA_TYPE_SUBTITLE){
capa["codecs"][0u][3u].append(cInfo->name);
}
if (cInfo->type == AVMEDIA_TYPE_VIDEO){capa["codecs"][0u][0u].append(cInfo->name);}
if (cInfo->type == AVMEDIA_TYPE_AUDIO){capa["codecs"][0u][1u].append(cInfo->name);}
if (cInfo->type == AVMEDIA_TYPE_SUBTITLE){capa["codecs"][0u][3u].append(cInfo->name);}
}
}
inputAV::~inputAV(){
if (pFormatCtx){
avformat_close_input(&pFormatCtx);
}
if (pFormatCtx){avformat_close_input(&pFormatCtx);}
}
bool inputAV::checkArguments() {
if (config->getString("input") == "-") {
bool inputAV::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not yet supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
@ -62,29 +56,29 @@ namespace Mist {
}
bool inputAV::preRun(){
//make sure all av inputs are registered properly, just in case
//the constructor already does this, but under windows it doesn't remember that it has.
//Very sad, that. We may need to get windows some medication for it.
// make sure all av inputs are registered properly, just in case
// the constructor already does this, but under windows it doesn't remember that it has.
// Very sad, that. We may need to get windows some medication for it.
av_register_all();
//close any already open files
// close any already open files
if (pFormatCtx){
avformat_close_input(&pFormatCtx);
pFormatCtx = 0;
}
//Open video file
// Open video file
int ret = avformat_open_input(&pFormatCtx, config->getString("input").c_str(), NULL, NULL);
if(ret != 0){
if (ret != 0){
char errstr[300];
av_strerror(ret, errstr, 300);
DEBUG_MSG(DLVL_FAIL, "Could not open file: %s", errstr);
return false; // Couldn't open file
}
//Retrieve stream information
// Retrieve stream information
ret = avformat_find_stream_info(pFormatCtx, NULL);
if(ret < 0){
if (ret < 0){
char errstr[300];
av_strerror(ret, errstr, 300);
DEBUG_MSG(DLVL_FAIL, "Could not find stream info: %s", errstr);
@ -93,63 +87,39 @@ namespace Mist {
return true;
}
bool inputAV::readHeader() {
bool inputAV::readHeader(){
myMeta.tracks.clear();
for(unsigned int i=0; i < pFormatCtx->nb_streams; ){
AVStream * strm = pFormatCtx->streams[i++];
for (unsigned int i = 0; i < pFormatCtx->nb_streams;){
AVStream *strm = pFormatCtx->streams[i++];
myMeta.tracks[i].trackID = i;
switch (strm->codecpar->codec_id){
case AV_CODEC_ID_HEVC:
myMeta.tracks[i].codec = "HEVC";
break;
case AV_CODEC_ID_MPEG1VIDEO:
case AV_CODEC_ID_MPEG2VIDEO:
myMeta.tracks[i].codec = "MPEG2";
break;
case AV_CODEC_ID_MP2:
myMeta.tracks[i].codec = "MP2";
break;
case AV_CODEC_ID_H264:
myMeta.tracks[i].codec = "H264";
break;
case AV_CODEC_ID_THEORA:
myMeta.tracks[i].codec = "theora";
break;
case AV_CODEC_ID_VORBIS:
myMeta.tracks[i].codec = "vorbis";
break;
case AV_CODEC_ID_OPUS:
myMeta.tracks[i].codec = "opus";
break;
case AV_CODEC_ID_VP8:
myMeta.tracks[i].codec = "VP8";
break;
case AV_CODEC_ID_VP9:
myMeta.tracks[i].codec = "VP9";
break;
case AV_CODEC_ID_AAC:
myMeta.tracks[i].codec = "AAC";
break;
case AV_CODEC_ID_MP3:
myMeta.tracks[i].codec = "MP3";
break;
case AV_CODEC_ID_AC3:
case AV_CODEC_ID_EAC3:
myMeta.tracks[i].codec = "AC3";
break;
default:
const AVCodecDescriptor *desc = avcodec_descriptor_get(strm->codecpar->codec_id);
if (desc && desc->name){
myMeta.tracks[i].codec = desc->name;
}else{
myMeta.tracks[i].codec = "?";
}
break;
case AV_CODEC_ID_HEVC: myMeta.tracks[i].codec = "HEVC"; break;
case AV_CODEC_ID_MPEG1VIDEO:
case AV_CODEC_ID_MPEG2VIDEO: myMeta.tracks[i].codec = "MPEG2"; break;
case AV_CODEC_ID_MP2: myMeta.tracks[i].codec = "MP2"; break;
case AV_CODEC_ID_H264: myMeta.tracks[i].codec = "H264"; break;
case AV_CODEC_ID_THEORA: myMeta.tracks[i].codec = "theora"; break;
case AV_CODEC_ID_VORBIS: myMeta.tracks[i].codec = "vorbis"; break;
case AV_CODEC_ID_OPUS: myMeta.tracks[i].codec = "opus"; break;
case AV_CODEC_ID_VP8: myMeta.tracks[i].codec = "VP8"; break;
case AV_CODEC_ID_VP9: myMeta.tracks[i].codec = "VP9"; break;
case AV_CODEC_ID_AAC: myMeta.tracks[i].codec = "AAC"; break;
case AV_CODEC_ID_MP3: myMeta.tracks[i].codec = "MP3"; break;
case AV_CODEC_ID_AC3:
case AV_CODEC_ID_EAC3: myMeta.tracks[i].codec = "AC3"; break;
default:
const AVCodecDescriptor *desc = avcodec_descriptor_get(strm->codecpar->codec_id);
if (desc && desc->name){
myMeta.tracks[i].codec = desc->name;
}else{
myMeta.tracks[i].codec = "?";
}
break;
}
if (strm->codecpar->extradata_size){
myMeta.tracks[i].init = std::string((char*)strm->codecpar->extradata, strm->codecpar->extradata_size);
myMeta.tracks[i].init = std::string((char *)strm->codecpar->extradata, strm->codecpar->extradata_size);
}
if(strm->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
if (strm->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
myMeta.tracks[i].type = "video";
if (strm->avg_frame_rate.den && strm->avg_frame_rate.num){
myMeta.tracks[i].fpks = (strm->avg_frame_rate.num * 1000) / strm->avg_frame_rate.den;
@ -159,23 +129,21 @@ namespace Mist {
myMeta.tracks[i].width = strm->codecpar->width;
myMeta.tracks[i].height = strm->codecpar->height;
}
if(strm->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
if (strm->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
myMeta.tracks[i].type = "audio";
myMeta.tracks[i].rate = strm->codecpar->sample_rate;
myMeta.tracks[i].size = strm->codecpar->frame_size;
myMeta.tracks[i].channels = strm->codecpar->channels;
}
}
AVPacket packet;
while(av_read_frame(pFormatCtx, &packet)>=0){
AVStream * strm = pFormatCtx->streams[packet.stream_index];
while (av_read_frame(pFormatCtx, &packet) >= 0){
AVStream *strm = pFormatCtx->streams[packet.stream_index];
long long packTime = (packet.dts * 1000 * strm->time_base.num / strm->time_base.den);
long long packOffset = 0;
bool isKey = false;
if (packTime < 0){
packTime = 0;
}
if (packTime < 0){packTime = 0;}
if (packet.flags & AV_PKT_FLAG_KEY && myMeta.tracks[(long long)packet.stream_index + 1].type != "audio"){
isKey = true;
}
@ -185,48 +153,49 @@ namespace Mist {
myMeta.update(packTime, packOffset, packet.stream_index + 1, packet.size, packet.pos, isKey);
av_packet_unref(&packet);
}
myMeta.toFile(config->getString("input") + ".dtsh");
seek(0);
return true;
}
void inputAV::getNext(bool smart) {
void inputAV::getNext(bool smart){
AVPacket packet;
while (av_read_frame(pFormatCtx, &packet)>=0){
//filter tracks we don't care about
while (av_read_frame(pFormatCtx, &packet) >= 0){
// filter tracks we don't care about
if (!selectedTracks.count(packet.stream_index + 1)){
DEBUG_MSG(DLVL_HIGH, "Track %u not selected", packet.stream_index + 1);
continue;
}
AVStream * strm = pFormatCtx->streams[packet.stream_index];
AVStream *strm = pFormatCtx->streams[packet.stream_index];
long long packTime = (packet.dts * 1000 * strm->time_base.num / strm->time_base.den);
long long packOffset = 0;
bool isKey = false;
if (packTime < 0){
packTime = 0;
}
if (packTime < 0){packTime = 0;}
if (packet.flags & AV_PKT_FLAG_KEY && myMeta.tracks[(long long)packet.stream_index + 1].type != "audio"){
isKey = true;
}
if (packet.pts != AV_NOPTS_VALUE && packet.pts != packet.dts){
packOffset = ((packet.pts - packet.dts) * 1000 * strm->time_base.num / strm->time_base.den);
}
thisPacket.genericFill(packTime, packOffset, packet.stream_index + 1, (const char*)packet.data, packet.size, 0, isKey);
thisPacket.genericFill(packTime, packOffset, packet.stream_index + 1,
(const char *)packet.data, packet.size, 0, isKey);
av_packet_unref(&packet);
return;//success!
return; // success!
}
thisPacket.null();
preRun();
//failure :-(
// failure :-(
DEBUG_MSG(DLVL_FAIL, "getNext failed");
}
void inputAV::seek(int seekTime) {
void inputAV::seek(int seekTime){
int stream_index = av_find_default_stream_index(pFormatCtx);
//Convert ts to frame
unsigned long long reseekTime = av_rescale(seekTime, pFormatCtx->streams[stream_index]->time_base.den, pFormatCtx->streams[stream_index]->time_base.num);
// Convert ts to frame
unsigned long long reseekTime =
av_rescale(seekTime, pFormatCtx->streams[stream_index]->time_base.den,
pFormatCtx->streams[stream_index]->time_base.num);
reseekTime /= 1000;
unsigned long long seekStreamDuration = pFormatCtx->streams[stream_index]->duration;
int flags = AVSEEK_FLAG_BACKWARD;
@ -234,24 +203,21 @@ namespace Mist {
flags |= AVSEEK_FLAG_ANY; // H.264 I frames don't always register as "key frames" in FFmpeg
}
int ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, flags);
if (ret < 0){
ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, AVSEEK_FLAG_ANY);
}
if (ret < 0){ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, AVSEEK_FLAG_ANY);}
}
void inputAV::trackSelect(std::string trackSpec) {
void inputAV::trackSelect(std::string trackSpec){
selectedTracks.clear();
long long unsigned int index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
//inFile.selectTracks(selectedTracks);
// inFile.selectTracks(selectedTracks);
}
}
}// namespace Mist

View file

@ -1,33 +1,35 @@
#ifndef INT64_MIN
#define INT64_MIN (-(9223372036854775807 ## LL)-1)
#define INT64_MIN (-(9223372036854775807##LL) - 1)
#endif
#ifndef INT64_MAX
#define INT64_MAX ((9223372036854775807 ## LL))
#define INT64_MAX ((9223372036854775807##LL))
#endif
#include "input.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
extern "C"{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
namespace Mist {
class inputAV : public Input {
public:
inputAV(Util::Config * cfg);
~inputAV();
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
private:
AVFormatContext *pFormatCtx;
namespace Mist{
class inputAV : public Input{
public:
inputAV(Util::Config *cfg);
~inputAV();
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
private:
AVFormatContext *pFormatCtx;
};
}
}// namespace Mist
typedef Mist::inputAV mistIn;

View file

@ -1,25 +1,29 @@
#include <mist/defines.h>
#include <mist/stream.h>
#include <mist/http_parser.h>
#include <mist/encode.h>
#include <mist/url.h>
#include "input_balancer.h"
#include <mist/defines.h>
#include <mist/encode.h>
#include <mist/http_parser.h>
#include <mist/stream.h>
#include <mist/url.h>
namespace Mist {
inputBalancer::inputBalancer(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputBalancer::inputBalancer(Util::Config *cfg) : Input(cfg){
capa["name"] = "Balancer";
capa["desc"] = "The load balancer input restarts itself as the input a load balancer tells it it should be. The syntax is in the form 'balance:http://HOST:PORT[?fallback=FALLBACK]', where HOST and PORT are the host and port of the load balancer and the FALLBACK is the full source URL that should be used if the load balancer cannot be reached.";
capa["desc"] =
"The load balancer input restarts itself as the input a load balancer tells it it should "
"be. The syntax is in the form 'balance:http://HOST:PORT[?fallback=FALLBACK]', where HOST "
"and PORT are the host and port of the load balancer and the FALLBACK is the full source "
"URL that should be used if the load balancer cannot be reached.";
capa["source_match"] = "balance:*";
capa["priority"] = 9;
capa["morphic"] = 1;
}
int inputBalancer::boot(int argc, char * argv[]){
int inputBalancer::boot(int argc, char *argv[]){
if (!config->parseArgs(argc, argv)){return 1;}
if (config->getBool("json")){return Input::boot(argc, argv);}
streamName = config->getString("streamname");
std::string blncr = config->getString("input");
if (blncr.substr(0, 8) != "balance:"){
FAIL_MSG("Input must start with \"balance:\"");
@ -32,16 +36,15 @@ namespace Mist {
return 1;
}
std::string source; //empty by default
std::string source; // empty by default
//Parse fallback from URL arguments, if possible.
// Parse fallback from URL arguments, if possible.
if (url.args.size()){
std::map<std::string, std::string> args;
HTTP::parseVars(url.args, args);
if (args.count("fallback")){source = args.at("fallback");}
}
Socket::Connection balConn(url.host, url.getPort(), true);
if (!balConn){
WARN_MSG("Failed to reach %s on port %lu", url.host.c_str(), url.getPort());
@ -49,9 +52,7 @@ namespace Mist {
HTTP::Parser http;
http.url = "/" + url.path;
http.SetVar("source", streamName);
if (source.size()){
http.SetVar("fallback", source);
}
if (source.size()){http.SetVar("fallback", source);}
http.method = "GET";
http.SetHeader("Host", url.host);
http.SetHeader("X-MistServer", PACKAGE_VERSION);
@ -66,29 +67,28 @@ namespace Mist {
if (Socket::isLocalhost(newUrl.host)){
WARN_MSG("Load balancer returned a local address - ignoring");
startTime = 0;
break;//break out of while loop, ignore return value - it's local.
break; // break out of while loop, ignore return value - it's local.
}
source = http.body;
startTime = 0;//note success
break;//break out of while loop
startTime = 0; // note success
break; // break out of while loop
}
}
}
if (startTime){
FAIL_MSG("Timeout while trying to contact load balancer at %s!", blncr.c_str()+8);
FAIL_MSG("Timeout while trying to contact load balancer at %s!", blncr.c_str() + 8);
}
balConn.close();
}
if (!source.size()){
FAIL_MSG("Could not determine source to use for %s", streamName.c_str());
return 1;
}
//Attempt to boot the source we got
// Attempt to boot the source we got
Util::startInput(streamName, source, false, getenv("MISTPROVIDER"));
return 1;
}
}
}// namespace Mist

View file

@ -1,17 +1,17 @@
#include "input.h"
#include <mist/dtsc.h>
namespace Mist {
class inputBalancer : public Input {
public:
inputBalancer(Util::Config * cfg);
int boot(int argc, char * argv[]);
protected:
bool checkArguments(){return false;};
bool readHeader(){return false;};
bool needHeader(){return false;};
namespace Mist{
class inputBalancer : public Input{
public:
inputBalancer(Util::Config *cfg);
int boot(int argc, char *argv[]);
protected:
bool checkArguments(){return false;};
bool readHeader(){return false;};
bool needHeader(){return false;};
};
}
}// namespace Mist
typedef Mist::inputBalancer mistIn;

File diff suppressed because it is too large Load diff

View file

@ -4,54 +4,54 @@
#include <mist/dtsc.h>
#include <mist/shared_memory.h>
namespace Mist {
class inputBuffer : public Input {
public:
inputBuffer(Util::Config * cfg);
~inputBuffer();
void onCrash();
private:
void fillBufferDetails(JSON::Value & details);
unsigned int bufferTime;
unsigned int cutTime;
unsigned int segmentSize; /*LTS*/
unsigned int lastReTime; /*LTS*/
bool hasPush;
bool resumeMode;
IPC::semaphore * liveMeta;
protected:
//Private Functions
bool preRun();
bool checkArguments(){return true;}
void updateMeta();
bool readHeader(){return false;}
bool needHeader(){return false;}
void getNext(bool smart = true){}
void updateTrackMeta(unsigned long tNum);
void updateMetaFromPage(unsigned long tNum, unsigned long pageNum);
void seek(int seekTime){}
void trackSelect(std::string trackSpec){}
bool removeKey(unsigned int tid);
void removeUnused();
void eraseTrackDataPages(unsigned long tid);
void finish();
void userCallback(char * data, size_t len, unsigned int id);
std::set<unsigned long> negotiatingTracks;
std::set<unsigned long> activeTracks;
std::map<unsigned long, unsigned long long> lastUpdated;
std::map<unsigned long, unsigned long long> negotiationTimeout;
///Maps trackid to a pagenum->pageData map
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations;
std::map<unsigned long, char *> pushLocation;
inputBuffer * singleton;
//This is used for an ugly fix to prevent metadata from disappearing in some cases.
std::map<unsigned long, std::string> initData;
uint64_t findTrack(const std::string &trackVal);
void checkProcesses(const JSON::Value & procs); //LTS
std::map<std::string, pid_t> runningProcs;//LTS
namespace Mist{
class inputBuffer : public Input{
public:
inputBuffer(Util::Config *cfg);
~inputBuffer();
void onCrash();
private:
void fillBufferDetails(JSON::Value &details);
unsigned int bufferTime;
unsigned int cutTime;
unsigned int segmentSize; /*LTS*/
unsigned int lastReTime; /*LTS*/
bool hasPush;
bool resumeMode;
IPC::semaphore *liveMeta;
protected:
// Private Functions
bool preRun();
bool checkArguments(){return true;}
void updateMeta();
bool readHeader(){return false;}
bool needHeader(){return false;}
void getNext(bool smart = true){}
void updateTrackMeta(unsigned long tNum);
void updateMetaFromPage(unsigned long tNum, unsigned long pageNum);
void seek(int seekTime){}
void trackSelect(std::string trackSpec){}
bool removeKey(unsigned int tid);
void removeUnused();
void eraseTrackDataPages(unsigned long tid);
void finish();
void userCallback(char *data, size_t len, unsigned int id);
std::set<unsigned long> negotiatingTracks;
std::set<unsigned long> activeTracks;
std::map<unsigned long, unsigned long long> lastUpdated;
std::map<unsigned long, unsigned long long> negotiationTimeout;
/// Maps trackid to a pagenum->pageData map
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations;
std::map<unsigned long, char *> pushLocation;
inputBuffer *singleton;
// This is used for an ugly fix to prevent metadata from disappearing in some cases.
std::map<unsigned long, std::string> initData;
uint64_t findTrack(const std::string &trackVal);
void checkProcesses(const JSON::Value &procs); // LTS
std::map<std::string, pid_t> runningProcs; // LTS
};
}
}// namespace Mist
typedef Mist::inputBuffer mistIn;

View file

@ -1,25 +1,27 @@
#include <iostream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <mist/defines.h>
#include <mist/stream.h>
#include <string>
#include <mist/util.h>
#include <mist/bitfields.h>
#include <mist/util.h>
#include "input_dtsc.h"
namespace Mist {
inputDTSC::inputDTSC(Util::Config * cfg) : Input(cfg) {
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["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["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");
@ -29,7 +31,6 @@ namespace Mist {
capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("vorbis");
JSON::Value option;
option["arg"] = "integer";
option["long"] = "buffer";
@ -38,7 +39,10 @@ namespace Mist {
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"]["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;
@ -56,93 +60,94 @@ namespace Mist {
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) {
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)) {
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)) {
// 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)) {
// 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)) {
// 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)) {
// 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)) {
// 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)) {
// 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)) {
// Default port and streamname, no password
if (Util::stringScan(src, "%s", matches)){
host = matches[0];
return;
}
}
void inputDTSC::parseStreamHeader() {
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") {
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)) {
if (!srcConn.Received().available(8 + rSize)){
nProxy.userClient.keepAlive();
Util::sleep(100);
continue; //abort - not enough data yet
continue; // abort - not enough data yet
}
//Ignore initial DTCM message, as this is a "hi" message from the server
// 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);
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++){
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
continueNegotiate(it->first, true);
}
break;
@ -158,65 +163,55 @@ namespace Mist {
}
}
bool inputDTSC::openStreamSource() {
bool inputDTSC::openStreamSource(){
std::string source = config->getString("input");
if (source == "-"){
srcConn.open(fileno(stdout),fileno(stdin));
srcConn.open(fileno(stdout), fileno(stdin));
return true;
}
if (source.find("dtsc://") == 0) {
source.erase(0, 7);
}
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;
}
if (streamName == ""){streamName = givenStream;}
srcConn.open(host, port, true);
if (!srcConn.connected()){
return false;
}
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};
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();
}
void inputDTSC::closeStreamSource(){srcConn.close();}
bool inputDTSC::checkArguments() {
if (!needsLock()) {
bool inputDTSC::checkArguments(){
if (!needsLock()){
return true;
} else {
if (!config->getString("streamname").size()) {
if (config->getString("output") == "-") {
}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") != "-") {
}else{
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
}
//open File
// open File
inFile = DTSC::File(config->getString("input"));
if (!inFile) {
return false;
}
if (!inFile){return false;}
}
return true;
}
@ -226,11 +221,9 @@ namespace Mist {
return Input::needHeader();
}
bool inputDTSC::readHeader() {
if (!inFile) {
return false;
}
if (inFile.getMeta().moreheader < 0 || inFile.getMeta().tracks.size() == 0) {
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;
}
@ -239,7 +232,7 @@ namespace Mist {
return true;
}
void inputDTSC::getNext(bool smart) {
void inputDTSC::getNext(bool smart){
if (!needsLock()){
thisPacket.reInit(srcConn);
while (config->is_active){
@ -248,17 +241,16 @@ namespace Mist {
std::string cmd;
thisPacket.getString("cmd", cmd);
if (cmd == "reset"){
//Read next packet
// Read next packet
thisPacket.reInit(srcConn);
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
DTSC::Meta newMeta;
newMeta.reinit(thisPacket);
//Detect new tracks
// 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::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++){
@ -267,34 +259,33 @@ namespace Mist {
continueNegotiate(*it, true);
}
//Detect removed tracks
// 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::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++){
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
thisPacket.reInit(srcConn); // read the next packet before continuing
}else{
myMeta = DTSC::Meta();
}
}else{
thisPacket.reInit(srcConn);//read the next packet before continuing
thisPacket.reInit(srcConn); // read the next packet before continuing
}
continue;//parse the next packet before returning
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::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++){
@ -302,49 +293,49 @@ namespace Mist {
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
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.
// 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());
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
// 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
return; // we have a packet
}
}else{
if (smart) {
if (smart){
inFile.seekNext();
} else {
}else{
inFile.parseNext();
}
thisPacket = inFile.getPacket();
}
}
void inputDTSC::seek(int seekTime) {
void inputDTSC::seek(int seekTime){
inFile.seek_time(seekTime);
initialTime = 0;
playUntil = 0;
}
void inputDTSC::trackSelect(std::string trackSpec) {
void inputDTSC::trackSelect(std::string trackSpec){
selectedTracks.clear();
long long unsigned int index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
inFile.selectTracks(selectedTracks);
}
}
}// namespace Mist

View file

@ -1,29 +1,28 @@
#include "input.h"
#include <mist/dtsc.h>
namespace Mist {
class inputDTSC : public Input {
public:
inputDTSC(Util::Config * cfg);
bool needsLock();
protected:
//Private Functions
bool openStreamSource();
void closeStreamSource();
void parseStreamHeader();
bool checkArguments();
bool readHeader();
bool needHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
namespace Mist{
class inputDTSC : public Input{
public:
inputDTSC(Util::Config *cfg);
bool needsLock();
DTSC::File inFile;
protected:
// Private Functions
bool openStreamSource();
void closeStreamSource();
void parseStreamHeader();
bool checkArguments();
bool readHeader();
bool needHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
Socket::Connection srcConn;
DTSC::File inFile;
Socket::Connection srcConn;
};
}
}// namespace Mist
typedef Mist::inputDTSC mistIn;

View file

@ -1,20 +1,20 @@
#include <iostream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/encode.h>
#include <mist/defines.h>
#include <mist/encryption.h>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <mist/bitfields.h>
#include <mist/defines.h>
#include <mist/encode.h>
#include <mist/encryption.h>
#include <mist/stream.h>
#include <string>
#include "input_dtsccrypt.h"
#include <ctime>
namespace Mist {
inputDTSC::inputDTSC(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputDTSC::inputDTSC(Util::Config *cfg) : Input(cfg){
capa["name"] = "DTSC";
capa["desc"] = "Enables DTSC Input";
capa["priority"] = 9;
@ -26,7 +26,7 @@ namespace Mist {
capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("vorbis");
JSON::Value option;
option["long"] = "key";
option["short"] = "k";
@ -34,7 +34,7 @@ namespace Mist {
option["help"] = "The key to en/decrypt the current file with";
config->addOption("key", option);
option.null();
option["long"] = "keyseed";
option["short"] = "s";
option["arg"] = "string";
@ -52,7 +52,7 @@ namespace Mist {
srand(time(NULL));
}
bool inputDTSC::checkArguments() {
bool inputDTSC::checkArguments(){
key = Encodings::Base64::decode(config->getString("key"));
if (key == ""){
if (config->getString("keyseed") == "" || config->getString("keyid") == ""){
@ -65,59 +65,55 @@ namespace Mist {
key = Encryption::PR_GenerateContentKey(tmpSeed, guid);
}
if (config->getString("input") == "-") {
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not yet supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
}
//open File
// open File
inFile = DTSC::File(config->getString("input"));
if (!inFile) {
return false;
}
if (!inFile){return false;}
return true;
}
bool inputDTSC::readHeader() {
if (!inFile) {
return false;
}
bool inputDTSC::readHeader(){
if (!inFile){return false;}
DTSC::File tmp(config->getString("input") + ".dtsh");
if (tmp) {
if (tmp){
myMeta = tmp.getMeta();
DEBUG_MSG(DLVL_HIGH,"Meta read in with %lu tracks", myMeta.tracks.size());
DEBUG_MSG(DLVL_HIGH, "Meta read in with %lu tracks", myMeta.tracks.size());
return true;
}
if (inFile.getMeta().moreheader < 0 || inFile.getMeta().tracks.size() == 0) {
DEBUG_MSG(DLVL_FAIL,"Missing external header file");
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());
DEBUG_MSG(DLVL_DEVEL, "Meta read in with %lu tracks", myMeta.tracks.size());
return true;
}
void inputDTSC::getNext(bool smart) {
void inputDTSC::getNext(bool smart){
if (smart){
inFile.seekNext();
}else{
inFile.parseNext();
}
thisPacket = inFile.getPacket();
//Do encryption/decryption here
// Do encryption/decryption here
int tid = thisPacket.getTrackId();
char * ivec;
char *ivec;
size_t ivecLen;
thisPacket.getString("ivec", ivec, ivecLen);
char iVec[16];
@ -125,34 +121,32 @@ namespace Mist {
memcpy(iVec, ivec, 8);
}else{
if (iVecs.find(tid) == iVecs.end()){
iVecs[tid] = ((long long unsigned int)rand() << 32) + rand();
iVecs[tid] = ((long long unsigned int)rand() << 32) + rand();
}
Bit::htobll(iVec, iVecs[tid]);
iVecs[tid] ++;
iVecs[tid]++;
}
Encryption::encryptPlayReady(thisPacket, myMeta.tracks[tid].codec, iVec, key.data());
}
void inputDTSC::seek(int seekTime) {
void inputDTSC::seek(int seekTime){
inFile.seek_time(seekTime);
initialTime = 0;
playUntil = 0;
}
void inputDTSC::trackSelect(std::string trackSpec) {
void inputDTSC::trackSelect(std::string trackSpec){
selectedTracks.clear();
long long unsigned int index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
inFile.selectTracks(selectedTracks);
}
}
}// namespace Mist

View file

@ -1,25 +1,24 @@
#include "input.h"
#include <mist/dtsc.h>
namespace Mist {
class inputDTSC : public Input {
public:
inputDTSC(Util::Config * cfg);
protected:
//Private Functions
bool checkArguments();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
namespace Mist{
class inputDTSC : public Input{
public:
inputDTSC(Util::Config *cfg);
DTSC::File inFile;
protected:
// Private Functions
bool checkArguments();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
std::map<int,unsigned long long int> iVecs;
std::string key;
DTSC::File inFile;
std::map<int, unsigned long long int> iVecs;
std::string key;
};
}
}// namespace Mist
typedef Mist::inputDTSC mistIn;

View file

@ -1,14 +1,15 @@
#include "input_ebml.h"
#include <mist/bitfields.h>
#include <mist/defines.h>
#include <mist/ebml.h>
#include <mist/bitfields.h>
namespace Mist{
InputEBML::InputEBML(Util::Config *cfg) : Input(cfg){
timeScale = 1.0;
capa["name"] = "EBML";
capa["desc"] = "Allows loading MKV, MKA, MK3D, MKS and WebM files for Video on Demand, or accepts live streams in those formats over standard input.";
capa["desc"] = "Allows loading MKV, MKA, MK3D, MKS and WebM files for Video on Demand, or "
"accepts live streams in those formats over standard input.";
capa["source_match"].append("/*.mkv");
capa["source_match"].append("/*.mka");
capa["source_match"].append("/*.mk3d");
@ -42,21 +43,27 @@ namespace Mist{
wantBlocks = true;
}
std::string ASStoSRT(const char * ptr, uint32_t len){
std::string ASStoSRT(const char *ptr, uint32_t len){
uint16_t commas = 0;
uint16_t brackets = 0;
std::string tmpStr;
tmpStr.reserve(len);
for (uint32_t i = 0; i < len; ++i){
//Skip everything until the 8th comma
// Skip everything until the 8th comma
if (commas < 8){
if (ptr[i] == ','){commas++;}
continue;
}
if (ptr[i] == '{'){brackets++; continue;}
if (ptr[i] == '}'){brackets--; continue;}
if (ptr[i] == '{'){
brackets++;
continue;
}
if (ptr[i] == '}'){
brackets--;
continue;
}
if (!brackets){
if (ptr[i] == '\\' && i < len-1 && (ptr[i+1] == 'N' || ptr[i+1] == 'n')){
if (ptr[i] == '\\' && i < len - 1 && (ptr[i + 1] == 'N' || ptr[i + 1] == 'n')){
tmpStr += '\n';
++i;
continue;
@ -67,7 +74,6 @@ namespace Mist{
return tmpStr;
}
bool InputEBML::checkArguments(){
if (!config->getString("streamname").size()){
if (config->getString("output") == "-"){
@ -83,8 +89,8 @@ namespace Mist{
return true;
}
bool InputEBML::needsLock() {
//Standard input requires no lock, otherwise default behaviour.
bool InputEBML::needsLock(){
// Standard input requires no lock, otherwise default behaviour.
if (config->getString("input") == "-"){return false;}
return Input::needsLock();
}
@ -107,7 +113,7 @@ namespace Mist{
while (ptr.size() < needed){
if (!ptr.allocate(needed)){return false;}
if (!fread(ptr + ptr.size(), needed - ptr.size(), 1, inFile)){
//We assume if there is no current data buffered, that we are at EOF and don't print a warning
// We assume if there is no current data buffered, that we are at EOF and don't print a warning
if (ptr.size()){
FAIL_MSG("Could not read more data! (have %lu, need %lu)", ptr.size(), needed);
}
@ -129,7 +135,7 @@ namespace Mist{
lastClusterBPos = 0;
}else{
int64_t bp = Util::ftell(inFile);
if(bp == -1 && errno == ESPIPE){
if (bp == -1 && errno == ESPIPE){
lastClusterBPos = 0;
}else{
lastClusterBPos = bp;
@ -154,7 +160,7 @@ namespace Mist{
}
}
if (myMeta.inputLocalVars.isMember("timescale")){
timeScale = ((double)myMeta.inputLocalVars["timescale"].asInt()) / 1000000.0;
timeScale = ((double)myMeta.inputLocalVars["timescale"].asInt()) / 1000000.0;
}
return true;
}
@ -279,25 +285,23 @@ namespace Mist{
std::string WAVEFORMATEX = tmpElem.getValStringUntrimmed();
unsigned int formatTag = Bit::btohs_le(WAVEFORMATEX.data());
switch (formatTag){
case 3:
trueCodec = "FLOAT";
trueType = "audio";
break;
case 6:
trueCodec = "ALAW";
trueType = "audio";
break;
case 7:
trueCodec = "ULAW";
trueType = "audio";
break;
case 85:
trueCodec = "MP3";
trueType = "audio";
break;
default:
ERROR_MSG("Unimplemented A_MS/ACM formatTag: %u", formatTag);
break;
case 3:
trueCodec = "FLOAT";
trueType = "audio";
break;
case 6:
trueCodec = "ALAW";
trueType = "audio";
break;
case 7:
trueCodec = "ULAW";
trueType = "audio";
break;
case 85:
trueCodec = "MP3";
trueType = "audio";
break;
default: ERROR_MSG("Unimplemented A_MS/ACM formatTag: %u", formatTag); break;
}
}
}
@ -335,7 +339,7 @@ namespace Mist{
myMeta.inputLocalVars["timescale"] = timeScaleVal;
timeScale = ((double)timeScaleVal) / 1000000.0;
}
//Live streams stop parsing the header as soon as the first Cluster is encountered
// Live streams stop parsing the header as soon as the first Cluster is encountered
if (E.getID() == EBML::EID_CLUSTER && !needsLock()){return true;}
if (E.getType() == EBML::ELEM_BLOCK){
EBML::Block B(ptr);
@ -346,7 +350,7 @@ namespace Mist{
bool isVideo = (Trk.type == "video");
bool isAudio = (Trk.type == "audio");
bool isASS = (Trk.codec == "subtitle" && Trk.init.size());
//If this is a new video keyframe, flush the corresponding trackPredictor
// If this is a new video keyframe, flush the corresponding trackPredictor
if (isVideo && B.isKeyframe()){
while (TP.hasPackets(true)){
packetData &C = TP.getPacketData(true);
@ -358,29 +362,28 @@ namespace Mist{
for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
if (frameNo){
if (Trk.codec == "AAC"){
newTime += (1000000 / Trk.rate)/timeScale;//assume ~1000 samples per frame
} else if (Trk.codec == "MP3"){
newTime += (1152000 / Trk.rate)/timeScale;//1152 samples per frame
} else if (Trk.codec == "DTS"){
//Assume 512 samples per frame (DVD default)
//actual amount can be calculated from data, but data
//is not available during header generation...
//See: http://www.stnsoft.com/DVD/dtshdr.html
newTime += (512000 / Trk.rate)/timeScale;
newTime += (1000000 / Trk.rate) / timeScale; // assume ~1000 samples per frame
}else if (Trk.codec == "MP3"){
newTime += (1152000 / Trk.rate) / timeScale; // 1152 samples per frame
}else if (Trk.codec == "DTS"){
// Assume 512 samples per frame (DVD default)
// actual amount can be calculated from data, but data
// is not available during header generation...
// See: http://www.stnsoft.com/DVD/dtshdr.html
newTime += (512000 / Trk.rate) / timeScale;
}else{
newTime += 1/timeScale;
newTime += 1 / timeScale;
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
}
}
uint32_t frameSize = B.getFrameSize(frameNo);
if (isASS){
char * ptr = (char *)B.getFrameData(frameNo);
char *ptr = (char *)B.getFrameData(frameNo);
std::string assStr = ASStoSRT(ptr, frameSize);
frameSize = assStr.size();
}
if (frameSize){
TP.add(newTime*timeScale, 0, tNum, frameSize, lastClusterBPos,
B.isKeyframe() && !isAudio, isVideo);
TP.add(newTime * timeScale, 0, tNum, frameSize, lastClusterBPos, B.isKeyframe() && !isAudio, isVideo);
}
}
while (TP.hasPackets()){
@ -392,8 +395,7 @@ namespace Mist{
}
if (packBuf.size()){
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end();
++it){
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){
trackPredictor &TP = it->second;
while (TP.hasPackets(true)){
packetData &C = TP.getPacketData(myMeta.tracks[it->first].type == "video");
@ -459,8 +461,7 @@ namespace Mist{
void InputEBML::getNext(bool smart){
// Make sure we empty our buffer first
if (bufferedPacks && packBuf.size()){
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin();
it != packBuf.end(); ++it){
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){
trackPredictor &TP = it->second;
if (TP.hasPackets()){
packetData &C = TP.getPacketData(myMeta.tracks[it->first].type == "video");
@ -477,8 +478,7 @@ namespace Mist{
if (!readElement()){
// Make sure we empty our buffer first
if (bufferedPacks && packBuf.size()){
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin();
it != packBuf.end(); ++it){
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){
trackPredictor &TP = it->second;
if (TP.hasPackets(true)){
packetData &C = TP.getPacketData(myMeta.tracks[it->first].type == "video");
@ -502,12 +502,12 @@ namespace Mist{
uint64_t tNum = B.getTrackNum();
uint64_t newTime = lastClusterTime + B.getTimecode();
trackPredictor &TP = packBuf[tNum];
DTSC::Track & Trk = myMeta.tracks[tNum];
DTSC::Track &Trk = myMeta.tracks[tNum];
bool isVideo = (Trk.type == "video");
bool isAudio = (Trk.type == "audio");
bool isASS = (Trk.codec == "subtitle" && Trk.init.size());
//If this is a new video keyframe, flush the corresponding trackPredictor
// If this is a new video keyframe, flush the corresponding trackPredictor
if (isVideo && B.isKeyframe() && bufferedPacks){
if (TP.hasPackets(true)){
wantBlocks = false;
@ -518,39 +518,36 @@ namespace Mist{
return;
}
}
if (isVideo && B.isKeyframe()){
TP.flush();
}
if (isVideo && B.isKeyframe()){TP.flush();}
wantBlocks = true;
for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
if (frameNo){
if (Trk.codec == "AAC"){
newTime += (1000000 / Trk.rate)/timeScale;//assume ~1000 samples per frame
} else if (Trk.codec == "MP3"){
newTime += (1152000 / Trk.rate)/timeScale;//1152 samples per frame
} else if (Trk.codec == "DTS"){
//Assume 512 samples per frame (DVD default)
//actual amount can be calculated from data, but data
//is not available during header generation...
//See: http://www.stnsoft.com/DVD/dtshdr.html
newTime += (512000 / Trk.rate)/timeScale;
newTime += (1000000 / Trk.rate) / timeScale; // assume ~1000 samples per frame
}else if (Trk.codec == "MP3"){
newTime += (1152000 / Trk.rate) / timeScale; // 1152 samples per frame
}else if (Trk.codec == "DTS"){
// Assume 512 samples per frame (DVD default)
// actual amount can be calculated from data, but data
// is not available during header generation...
// See: http://www.stnsoft.com/DVD/dtshdr.html
newTime += (512000 / Trk.rate) / timeScale;
}else{
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
}
}
uint32_t frameSize = B.getFrameSize(frameNo);
if (frameSize){
char * ptr = (char *)B.getFrameData(frameNo);
char *ptr = (char *)B.getFrameData(frameNo);
if (isASS){
std::string assStr = ASStoSRT(ptr, frameSize);
frameSize = assStr.size();
memcpy(ptr, assStr.data(), frameSize);
}
if (frameSize){
TP.add(newTime*timeScale, 0, tNum, frameSize, lastClusterBPos,
B.isKeyframe() && !isAudio, isVideo, (void *)ptr);
TP.add(newTime * timeScale, 0, tNum, frameSize, lastClusterBPos,
B.isKeyframe() && !isAudio, isVideo, (void *)ptr);
++bufferedPacks;
}
}
@ -583,7 +580,7 @@ namespace Mist{
Util::fseek(inFile, seekPos, SEEK_SET);
}
///Flushes all trackPredictors without deleting permanent data from them.
/// Flushes all trackPredictors without deleting permanent data from them.
void InputEBML::clearPredictors(){
if (!packBuf.size()){return;}
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){
@ -592,4 +589,3 @@ namespace Mist{
}
}// namespace Mist

View file

@ -4,11 +4,10 @@
namespace Mist{
#define PKT_COUNT 64
class packetData{
public:
public:
uint64_t time, offset, track, dsize, bpos;
bool key;
Util::ResizeablePointer ptr;
@ -20,137 +19,143 @@ namespace Mist{
bpos = 0;
key = false;
}
void set(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, void * dataPtr = 0){
void set(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize,
uint64_t packBytePos, bool isKeyframe, void *dataPtr = 0){
time = packTime;
offset = packOffset;
track = packTrack;
dsize = packDataSize;
bpos = packBytePos;
key = isKeyframe;
if (dataPtr){
ptr.assign(dataPtr, packDataSize);
}
if (dataPtr){ptr.assign(dataPtr, packDataSize);}
}
packetData(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, void * dataPtr = 0){
packetData(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize,
uint64_t packBytePos, bool isKeyframe, void *dataPtr = 0){
set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
}
};
class trackPredictor{
public:
packetData pkts[PKT_COUNT];
uint64_t frameOffset;///The static average offset between transmit time and display time
bool frameOffsetKnown;///Whether the average frame offset is known
uint16_t smallestFrame;///low-ball estimate of time per frame
uint64_t lastTime;///last send transmit timestamp
uint64_t ctr;///ingested frame count
uint64_t rem;///removed frame count
uint64_t maxOffset;///maximum offset for this track
uint64_t lowestTime;///First timestamp to enter the buffer
trackPredictor(){
smallestFrame = 0xFFFF;
frameOffsetKnown = false;
frameOffset = 0;
maxOffset = 0;
flush();
public:
packetData pkts[PKT_COUNT];
uint64_t frameOffset; /// The static average offset between transmit time and display time
bool frameOffsetKnown; /// Whether the average frame offset is known
uint16_t smallestFrame; /// low-ball estimate of time per frame
uint64_t lastTime; /// last send transmit timestamp
uint64_t ctr; /// ingested frame count
uint64_t rem; /// removed frame count
uint64_t maxOffset; /// maximum offset for this track
uint64_t lowestTime; /// First timestamp to enter the buffer
trackPredictor(){
smallestFrame = 0xFFFF;
frameOffsetKnown = false;
frameOffset = 0;
maxOffset = 0;
flush();
}
bool hasPackets(bool finished = false){
if (finished || frameOffsetKnown){
return (ctr - rem > 0);
}else{
return (ctr - rem > 12);
}
bool hasPackets(bool finished = false){
if (finished || frameOffsetKnown){
return (ctr - rem > 0);
}else{
return (ctr - rem > 12);
}
}
/// Clears all internal values, for reuse as-new.
void flush(){
lastTime = 0;
ctr = 0;
rem = 0;
lowestTime = 0;
}
packetData & getPacketData(bool mustCalcOffsets){
//grab the next packet to output
packetData & p = pkts[rem % PKT_COUNT];
if (!mustCalcOffsets){
frameOffsetKnown = true;
return p;
}
if (rem && !p.key){
uint64_t dispTime = p.time;
if (p.time + frameOffset < lastTime + smallestFrame){
uint32_t shift = (uint32_t)((((lastTime+smallestFrame)-(p.time+frameOffset)) + (smallestFrame-1)) / smallestFrame) * smallestFrame;
if (shift < smallestFrame){shift = smallestFrame;}
VERYHIGH_MSG("Offset negative, shifting original time forward by %" PRIu32, shift);
p.time += shift;
}
p.offset = p.time - (lastTime + smallestFrame) + frameOffset;
if (p.offset > maxOffset){
uint64_t diff = p.offset - maxOffset;
VERYHIGH_MSG("Shifting forward %" PRIu64 "ms (maxOffset reached: %" PRIu64 " > %" PRIu64 ")", diff, p.offset, maxOffset);
p.offset -= diff;
lastTime += diff;
}
p.time = (lastTime + smallestFrame);
//If we calculate an offset less than a frame away,
//we assume it's just time stamp drift due to lack of precision.
p.offset = ((uint32_t)((p.offset + (smallestFrame/2)) / smallestFrame)) * smallestFrame;
//Shift the time forward if needed, but never backward
if (p.offset + p.time < dispTime){
VERYHIGH_MSG("Shifting forward %" PRIu64 "ms (time drift)", dispTime - (p.offset + p.time));
p.time += dispTime - (p.offset + p.time);
}
}else{
if (!frameOffsetKnown){
//Check the first few timestamps against each other, find the smallest distance.
for (uint64_t i = 1; i < ctr; ++i){
uint64_t t1 = pkts[i%PKT_COUNT].time;
for (uint64_t j = 0; j < ctr; ++j){
if (i == j){continue;}
uint64_t t2 = pkts[j%PKT_COUNT].time;
uint64_t tDiff = (t1<t2)?(t2-t1):(t1-t2);
if (tDiff < smallestFrame){smallestFrame = tDiff;}
}
}
//Cool, now we're pretty sure we know the frame rate. Let's calculate some offsets.
for (uint64_t i = 1; i < ctr; ++i){
uint64_t timeDiff = pkts[i%PKT_COUNT].time - lowestTime;
uint64_t timeExpt = smallestFrame*i;
if (timeDiff > timeExpt && maxOffset < timeDiff-timeExpt){
maxOffset = timeDiff-timeExpt;
}
if (timeDiff < timeExpt && frameOffset < timeExpt-timeDiff){
frameOffset = timeExpt - timeDiff;
}
}
maxOffset += frameOffset;
//Print for debugging purposes, and consider them gospel from here on forward. Yay!
HIGH_MSG("smallestFrame=%" PRIu16 ", frameOffset=%" PRIu64 ", maxOffset=%" PRIu64, smallestFrame, frameOffset, maxOffset);
frameOffsetKnown = true;
}
p.offset = ((uint32_t)((frameOffset + (smallestFrame/2)) / smallestFrame)) * smallestFrame;
}
lastTime = p.time;
INSANE_MSG("Outputting%s %llu+%llu (#%llu, Max=%llu), display at %llu", (p.key?"KEY":""), p.time, p.offset, rem, maxOffset, p.time+p.offset);
}
/// Clears all internal values, for reuse as-new.
void flush(){
lastTime = 0;
ctr = 0;
rem = 0;
lowestTime = 0;
}
packetData &getPacketData(bool mustCalcOffsets){
// grab the next packet to output
packetData &p = pkts[rem % PKT_COUNT];
if (!mustCalcOffsets){
frameOffsetKnown = true;
return p;
}
void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, bool isVideo, void * dataPtr = 0){
if (!ctr){lowestTime = packTime;}
if (packTime > lowestTime && packTime - lowestTime < smallestFrame){smallestFrame = packTime - lowestTime;}
pkts[ctr % PKT_COUNT].set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
++ctr;
if (ctr == PKT_COUNT-1){frameOffsetKnown = true;}
if (rem && !p.key){
uint64_t dispTime = p.time;
if (p.time + frameOffset < lastTime + smallestFrame){
uint32_t shift =
(uint32_t)((((lastTime + smallestFrame) - (p.time + frameOffset)) + (smallestFrame - 1)) / smallestFrame) *
smallestFrame;
if (shift < smallestFrame){shift = smallestFrame;}
VERYHIGH_MSG("Offset negative, shifting original time forward by %" PRIu32, shift);
p.time += shift;
}
p.offset = p.time - (lastTime + smallestFrame) + frameOffset;
if (p.offset > maxOffset){
uint64_t diff = p.offset - maxOffset;
VERYHIGH_MSG("Shifting forward %" PRIu64 "ms (maxOffset reached: %" PRIu64 " > %" PRIu64 ")",
diff, p.offset, maxOffset);
p.offset -= diff;
lastTime += diff;
}
p.time = (lastTime + smallestFrame);
// If we calculate an offset less than a frame away,
// we assume it's just time stamp drift due to lack of precision.
p.offset = ((uint32_t)((p.offset + (smallestFrame / 2)) / smallestFrame)) * smallestFrame;
// Shift the time forward if needed, but never backward
if (p.offset + p.time < dispTime){
VERYHIGH_MSG("Shifting forward %" PRIu64 "ms (time drift)", dispTime - (p.offset + p.time));
p.time += dispTime - (p.offset + p.time);
}
}else{
if (!frameOffsetKnown){
// Check the first few timestamps against each other, find the smallest distance.
for (uint64_t i = 1; i < ctr; ++i){
uint64_t t1 = pkts[i % PKT_COUNT].time;
for (uint64_t j = 0; j < ctr; ++j){
if (i == j){continue;}
uint64_t t2 = pkts[j % PKT_COUNT].time;
uint64_t tDiff = (t1 < t2) ? (t2 - t1) : (t1 - t2);
if (tDiff < smallestFrame){smallestFrame = tDiff;}
}
}
// Cool, now we're pretty sure we know the frame rate. Let's calculate some offsets.
for (uint64_t i = 1; i < ctr; ++i){
uint64_t timeDiff = pkts[i % PKT_COUNT].time - lowestTime;
uint64_t timeExpt = smallestFrame * i;
if (timeDiff > timeExpt && maxOffset < timeDiff - timeExpt){
maxOffset = timeDiff - timeExpt;
}
if (timeDiff < timeExpt && frameOffset < timeExpt - timeDiff){
frameOffset = timeExpt - timeDiff;
}
}
maxOffset += frameOffset;
// Print for debugging purposes, and consider them gospel from here on forward. Yay!
HIGH_MSG("smallestFrame=%" PRIu16 ", frameOffset=%" PRIu64 ", maxOffset=%" PRIu64,
smallestFrame, frameOffset, maxOffset);
frameOffsetKnown = true;
}
p.offset = ((uint32_t)((frameOffset + (smallestFrame / 2)) / smallestFrame)) * smallestFrame;
}
void remove(){
++rem;
lastTime = p.time;
INSANE_MSG("Outputting%s %llu+%llu (#%llu, Max=%llu), display at %llu", (p.key ? "KEY" : ""),
p.time, p.offset, rem, maxOffset, p.time + p.offset);
return p;
}
void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize,
uint64_t packBytePos, bool isKeyframe, bool isVideo, void *dataPtr = 0){
if (!ctr){lowestTime = packTime;}
if (packTime > lowestTime && packTime - lowestTime < smallestFrame){
smallestFrame = packTime - lowestTime;
}
pkts[ctr % PKT_COUNT].set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
++ctr;
if (ctr == PKT_COUNT - 1){frameOffsetKnown = true;}
}
void remove(){++rem;}
};
class InputEBML : public Input{
public:
InputEBML(Util::Config *cfg);
bool needsLock();
protected:
void fillPacket(packetData & C);
void fillPacket(packetData &C);
bool checkArguments();
bool preRun();
bool readHeader();
@ -167,15 +172,12 @@ namespace Mist{
std::map<uint64_t, trackPredictor> packBuf;
std::set<uint64_t> swapEndianness;
bool readExistingHeader();
void parseStreamHeader(){
readHeader();
}
void parseStreamHeader(){readHeader();}
bool openStreamSource(){return true;}
bool needHeader(){return needsLock() && !readExistingHeader();}
double timeScale;
bool wantBlocks;
};
}
}// namespace Mist
typedef Mist::InputEBML mistIn;

View file

@ -1,21 +1,21 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <sys/types.h>//for stat
#include <sys/stat.h>//for stat
#include <unistd.h>//for stat
#include <mist/util.h>
#include <mist/stream.h>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/stream.h>
#include <mist/util.h>
#include <string>
#include <sys/stat.h> //for stat
#include <sys/types.h> //for stat
#include <unistd.h> //for stat
#include "input_flv.h"
namespace Mist {
inputFLV::inputFLV(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputFLV::inputFLV(Util::Config *cfg) : Input(cfg){
capa["name"] = "FLV";
capa["desc"] = "Allows loading FLV files for Video on Demand.";
capa["source_match"] = "/*.flv";
@ -28,31 +28,29 @@ namespace Mist {
capa["codecs"][0u][1u].append("MP3");
}
bool inputFLV::checkArguments() {
if (config->getString("input") == "-") {
bool inputFLV::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not yet supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
}
return true;
}
bool inputFLV::preRun() {
//open File
bool inputFLV::preRun(){
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile) {
return false;
}
if (!inFile){return false;}
struct stat statData;
lastModTime = 0;
if (stat(config->getString("input").c_str(), &statData) != -1){
@ -77,9 +75,9 @@ namespace Mist {
return Input::keepRunning();
}
bool inputFLV::readHeader() {
bool inputFLV::readHeader(){
if (!inFile){return false;}
//Create header file from FLV data
// Create header file from FLV data
Util::fseek(inFile, 13, SEEK_SET);
AMF::Object amf_storage;
long long int lastBytePos = 13;
@ -89,12 +87,14 @@ namespace Mist {
tmpTag.toMeta(myMeta, amf_storage);
if (!tmpTag.getDataLen()){continue;}
if (tmpTag.needsInitData() && tmpTag.isInitData()){continue;}
myMeta.update(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe);
myMeta.update(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getDataLen(),
lastBytePos, tmpTag.isKeyframe);
lastBytePos = Util::ftell(inFile);
}
}
bench = Util::getMicros(bench);
INFO_MSG("Header generated in %llu ms: @%lld, %s, %s", bench/1000, lastBytePos, myMeta.vod?"VoD":"NOVoD", myMeta.live?"Live":"NOLive");
INFO_MSG("Header generated in %llu ms: @%lld, %s, %s", bench / 1000, lastBytePos,
myMeta.vod ? "VoD" : "NOVoD", myMeta.live ? "Live" : "NOLive");
if (FLV::Parse_Error){
tmpTag = FLV::Tag();
FLV::Parse_Error = false;
@ -104,8 +104,8 @@ namespace Mist {
Util::fseek(inFile, 13, SEEK_SET);
return true;
}
void inputFLV::getNext(bool smart) {
void inputFLV::getNext(bool smart){
long long int lastBytePos = Util::ftell(inFile);
if (selectedTracks.size() == 1){
uint8_t targetTag = 0x08;
@ -115,7 +115,7 @@ namespace Mist {
}
while (!feof(inFile) && !FLV::Parse_Error){
if (tmpTag.FileLoader(inFile)){
if ( !selectedTracks.count(tmpTag.getTrackID())){
if (!selectedTracks.count(tmpTag.getTrackID())){
lastBytePos = Util::ftell(inFile);
continue;
}
@ -136,47 +136,45 @@ namespace Mist {
if (!tmpTag.getDataLen() || (tmpTag.needsInitData() && tmpTag.isInitData())){
return getNext();
}
thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getData(), tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe); //init packet from tmpTags data
thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getData(),
tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe); // init packet from tmpTags data
DTSC::Track & trk = myMeta.tracks[tmpTag.getTrackID()];
DTSC::Track &trk = myMeta.tracks[tmpTag.getTrackID()];
if (trk.codec == "PCM" && trk.size == 16){
char * ptr = 0;
char *ptr = 0;
size_t ptrSize = 0;
thisPacket.getString("data", ptr, ptrSize);
for (uint32_t i = 0; i < ptrSize; i+=2){
for (uint32_t i = 0; i < ptrSize; i += 2){
char tmpchar = ptr[i];
ptr[i] = ptr[i+1];
ptr[i+1] = tmpchar;
ptr[i] = ptr[i + 1];
ptr[i + 1] = tmpchar;
}
}
}
void inputFLV::seek(int seekTime) {
//We will seek to the corresponding keyframe of the video track if selected, otherwise audio keyframe.
//Flv files are never multi-track, so track 1 is video, track 2 is audio.
void inputFLV::seek(int seekTime){
// We will seek to the corresponding keyframe of the video track if selected, otherwise audio
// keyframe. Flv files are never multi-track, so track 1 is video, track 2 is audio.
int trackSeek = (selectedTracks.count(1) ? 1 : 2);
uint64_t seekPos = myMeta.tracks[trackSeek].keys[0].getBpos();
for (unsigned int i = 0; i < myMeta.tracks[trackSeek].keys.size(); i++){
if (myMeta.tracks[trackSeek].keys[i].getTime() > seekTime){
break;
}
if (myMeta.tracks[trackSeek].keys[i].getTime() > seekTime){break;}
seekPos = myMeta.tracks[trackSeek].keys[i].getBpos();
}
Util::fseek(inFile, seekPos, SEEK_SET);
}
void inputFLV::trackSelect(std::string trackSpec) {
void inputFLV::trackSelect(std::string trackSpec){
selectedTracks.clear();
size_t index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
}
}
}// namespace Mist

View file

@ -2,24 +2,24 @@
#include <mist/dtsc.h>
#include <mist/flv_tag.h>
namespace Mist {
class inputFLV : public Input {
public:
inputFLV(Util::Config * cfg);
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool keepRunning();
FLV::Tag tmpTag;
uint64_t lastModTime;
FILE * inFile;
namespace Mist{
class inputFLV : public Input{
public:
inputFLV(Util::Config *cfg);
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool keepRunning();
FLV::Tag tmpTag;
uint64_t lastModTime;
FILE *inFile;
};
}
}// namespace Mist
typedef Mist::inputFLV mistIn;

View file

@ -1,31 +1,36 @@
#include <mist/stream.h>
#include <mist/defines.h>
#include <sys/types.h>
#include <mist/stream.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "input_folder.h"
namespace Mist {
inputFolder::inputFolder(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputFolder::inputFolder(Util::Config *cfg) : Input(cfg){
capa["name"] = "Folder";
capa["desc"] = "The folder input will make available all supported files in the given folder as streams under this stream name, in the format STREAMNAME+FILENAME. For example, if your stream is called 'files' and you have a file called 'movie.flv', you could access this file streamed as 'files+movie.flv'. This input does not support subdirectories. To support more complex libraries, look into the documentation for the STREAM_SOURCE trigger.";
capa["desc"] =
"The folder input will make available all supported files in the given folder as streams "
"under this stream name, in the format STREAMNAME+FILENAME. For example, if your stream is "
"called 'files' and you have a file called 'movie.flv', you could access this file "
"streamed as 'files+movie.flv'. This input does not support subdirectories. To support "
"more complex libraries, look into the documentation for the STREAM_SOURCE trigger.";
capa["source_match"] = "/*/";
capa["source_file"] = "$source/$wildcard";
capa["priority"] = 9;
capa["morphic"] = 1;
}
int inputFolder::boot(int argc, char * argv[]){
int inputFolder::boot(int argc, char *argv[]){
if (!config->parseArgs(argc, argv)){return 1;}
if (config->getBool("json")){return Input::boot(argc,argv);}
if (config->getBool("json")){return Input::boot(argc, argv);}
streamName = config->getString("streamname");
if (streamName.find_first_of("+ ") == std::string::npos){
FAIL_MSG("Folder input requires a + or space in the stream name.");
return 1;
}
std::string folder = config->getString("input");
if (folder[folder.size() - 1] != '/'){
FAIL_MSG("Input path must end in a forward slash.");
@ -38,16 +43,15 @@ namespace Mist {
FAIL_MSG("Folder input requires a folder as input.");
return 1;
}
std::string path = folder + streamName.substr(streamName.find_first_of("+ ")+1);
std::string path = folder + streamName.substr(streamName.find_first_of("+ ") + 1);
if (stat(path.c_str(), &fileCheck) != 0 || S_ISDIR(fileCheck.st_mode)){
FAIL_MSG("File not found: %s", path.c_str());
return 1;
}
Util::startInput(streamName, path, false);
return 1;
}
}
}// namespace Mist

View file

@ -1,16 +1,17 @@
#include "input.h"
#include <mist/dtsc.h>
namespace Mist {
class inputFolder : public Input {
public:
inputFolder(Util::Config * cfg);
int boot(int argc, char * argv[]);
protected:
bool checkArguments(){return false;};
bool readHeader(){return false;};
bool needHeader(){return false;};
namespace Mist{
class inputFolder : public Input{
public:
inputFolder(Util::Config *cfg);
int boot(int argc, char *argv[]);
protected:
bool checkArguments(){return false;};
bool readHeader(){return false;};
bool needHeader(){return false;};
};
}
}// namespace Mist
typedef Mist::inputFolder mistIn;

View file

@ -5,9 +5,10 @@
namespace Mist{
InputH264::InputH264(Util::Config *cfg) : Input(cfg){
capa["name"] = "H264";
capa["desc"] = "This input allows you to take raw H264 Annex B data over a standard input pipe, and turn it into a live stream.";
capa["desc"] = "This input allows you to take raw H264 Annex B data over a standard input "
"pipe, and turn it into a live stream.";
capa["source_match"] = "h264-exec:*";
//May be set to always-on mode
// May be set to always-on mode
capa["always_match"].append("h264-exec:*");
capa["priority"] = 0;
capa["codecs"][0u][0u].append("H264");
@ -25,7 +26,7 @@ namespace Mist{
char *args[128];
uint8_t argCnt = 0;
char *startCh = 0;
for (char *i = (char*)input.c_str(); i <= input.data() + input.size(); ++i){
for (char *i = (char *)input.c_str(); i <= input.data() + input.size(); ++i){
if (!*i){
if (startCh){args[argCnt++] = startCh;}
break;
@ -123,5 +124,4 @@ namespace Mist{
}while (myConn && (inputProcess == 0 || Util::Procs::childRunning(inputProcess)));
if (inputProcess){myConn.close();}
}
}
}// namespace Mist

View file

@ -27,7 +27,6 @@ namespace Mist{
pid_t inputProcess;
uint32_t waitsSinceData;
};
}
}// namespace Mist
typedef Mist::InputH264 mistIn;

View file

@ -1,17 +1,17 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/stream.h>
#include <string>
#include "input_ismv.h"
namespace Mist {
inputISMV::inputISMV(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputISMV::inputISMV(Util::Config *cfg) : Input(cfg){
capa["name"] = "ISMV";
capa["desc"] = "This input allows you to stream ISMV Video on Demand files.";
capa["source_match"] = "/*.ismv";
@ -22,49 +22,41 @@ namespace Mist {
inFile = 0;
}
bool inputISMV::checkArguments() {
if (config->getString("input") == "-") {
bool inputISMV::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not yet supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
}
return true;
}
bool inputISMV::preRun() {
//open File
bool inputISMV::preRun(){
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile) {
return false;
}
if (!inFile){return false;}
return true;
}
bool inputISMV::readHeader() {
if (!inFile) {
return false;
}
//parse ismv header
bool inputISMV::readHeader(){
if (!inFile){return false;}
// parse ismv header
fseek(inFile, 0, SEEK_SET);
std::string ftyp;
readBox("ftyp", ftyp);
if (ftyp == ""){
return false;
}
if (ftyp == ""){return false;}
std::string boxRes;
readBox("moov", boxRes);
if (boxRes == ""){
return false;
}
if (boxRes == ""){return false;}
MP4::MOOV hdrBox;
hdrBox.read(boxRes);
parseMoov(hdrBox);
@ -77,34 +69,30 @@ namespace Mist {
unsigned int lastBytePos = 0;
std::map<int, unsigned int> currentDuration;
unsigned int curBytePos = ftell(inFile);
//parse fragments form here
while (parseFrag(tId, trunSamples, initVecs, mdat)) {
if (!currentDuration.count(tId)) {
currentDuration[tId] = 0;
}
// parse fragments form here
while (parseFrag(tId, trunSamples, initVecs, mdat)){
if (!currentDuration.count(tId)){currentDuration[tId] = 0;}
currOffset = 8;
int i = 0;
while (currOffset < mdat.size()) {
while (currOffset < mdat.size()){
lastPack.null();
lastPack["time"] = currentDuration[tId] / 10000;
lastPack["trackid"] = tId;
lastPack["data"] = mdat.substr(currOffset, trunSamples[i].sampleSize);
if (initVecs.size() == trunSamples.size()) {
lastPack["ivec"] = initVecs[i];
}
if (initVecs.size() == trunSamples.size()){lastPack["ivec"] = initVecs[i];}
lastPack["duration"] = trunSamples[i].sampleDuration;
if (myMeta.tracks[tId].type == "video") {
if (i) {
lastBytePos ++;
} else {
if (myMeta.tracks[tId].type == "video"){
if (i){
lastBytePos++;
}else{
lastPack["keyframe"] = 1;
lastBytePos = curBytePos;
}
lastPack["bpos"] = lastBytePos;
unsigned int offsetConv = trunSamples[i].sampleOffset / 10000;
lastPack["offset"] = (int)offsetConv;
} else {
if (i == 0) {
}else{
if (i == 0){
lastPack["keyframe"] = 1;
lastPack["bpos"] = curBytePos;
}
@ -112,7 +100,7 @@ namespace Mist {
myMeta.update(lastPack);
currentDuration[tId] += trunSamples[i].sampleDuration;
currOffset += trunSamples[i].sampleSize;
i ++;
i++;
}
curBytePos = ftell(inFile);
}
@ -120,7 +108,7 @@ namespace Mist {
return true;
}
void inputISMV::getNext(bool smart) {
void inputISMV::getNext(bool smart){
static JSON::Value thisPack;
thisPack.null();
if (!buffered.size()){
@ -131,22 +119,16 @@ namespace Mist {
thisPack["time"] = (uint64_t)(buffered.begin()->time / 10000);
thisPack["trackid"] = tId;
fseek(inFile, buffered.begin()->position, SEEK_SET);
char * tmpData = (char*)malloc(buffered.begin()->size * sizeof(char));
char *tmpData = (char *)malloc(buffered.begin()->size * sizeof(char));
fread(tmpData, buffered.begin()->size, 1, inFile);
thisPack["data"] = std::string(tmpData, buffered.begin()->size);
free(tmpData);
if (buffered.begin()->iVec != "") {
thisPack["ivec"] = buffered.begin()->iVec;
}
if (myMeta.tracks[tId].type == "video") {
if (buffered.begin()->isKeyFrame) {
thisPack["keyframe"] = 1;
}
if (buffered.begin()->iVec != ""){thisPack["ivec"] = buffered.begin()->iVec;}
if (myMeta.tracks[tId].type == "video"){
if (buffered.begin()->isKeyFrame){thisPack["keyframe"] = 1;}
thisPack["offset"] = (uint64_t)(buffered.begin()->offset / 10000);
} else {
if (buffered.begin()->isKeyFrame) {
thisPack["keyframe"] = 1;
}
}else{
if (buffered.begin()->isKeyFrame){thisPack["keyframe"] = 1;}
}
thisPack["bpos"] = (uint64_t)buffered.begin()->position;
buffered.erase(buffered.begin());
@ -159,95 +141,96 @@ namespace Mist {
std::string tmpStr = thisPack.toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
}
///\brief Overloads Input::atKeyFrame, for ISMV always sets the keyframe number
bool inputISMV::atKeyFrame(){
return thisPacket.getFlag("keyframe");
}
void inputISMV::seek(int seekTime) {
///\brief Overloads Input::atKeyFrame, for ISMV always sets the keyframe number
bool inputISMV::atKeyFrame(){return thisPacket.getFlag("keyframe");}
void inputISMV::seek(int seekTime){
buffered.clear();
//Seek to corresponding keyframes on EACH track
// Seek to corresponding keyframes on EACH track
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
unsigned int i;
for (i = 0; i < myMeta.tracks[*it].keys.size(); i++){
if (myMeta.tracks[*it].keys[i].getTime() > seekTime && i > 0){//Ehh, whut?
if (myMeta.tracks[*it].keys[i].getTime() > seekTime && i > 0){// Ehh, whut?
break;
}
}
i --;
i--;
DEBUG_MSG(DLVL_DEVEL, "ISMV seek frag %d:%d", *it, i);
parseFragHeader(*it, i);
lastKeyNum[*it] = i + 1;
}
}
void inputISMV::trackSelect(std::string trackSpec) {
void inputISMV::trackSelect(std::string trackSpec){
selectedTracks.clear();
size_t index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
seek(0);
}
void inputISMV::parseMoov(MP4::MOOV & moovBox) {
for (unsigned int i = 0; i < moovBox.getContentCount(); i++) {
if (moovBox.getContent(i).isType("mvhd")) {
void inputISMV::parseMoov(MP4::MOOV &moovBox){
for (unsigned int i = 0; i < moovBox.getContentCount(); i++){
if (moovBox.getContent(i).isType("mvhd")){
MP4::MVHD content = (MP4::MVHD &)moovBox.getContent(i);
}
if (moovBox.getContent(i).isType("trak")) {
if (moovBox.getContent(i).isType("trak")){
MP4::TRAK content = (MP4::TRAK &)moovBox.getContent(i);
int trackId;
for (unsigned int j = 0; j < content.getContentCount(); j++) {
if (content.getContent(j).isType("tkhd")) {
for (unsigned int j = 0; j < content.getContentCount(); j++){
if (content.getContent(j).isType("tkhd")){
MP4::TKHD subContent = (MP4::TKHD &)content.getContent(j);
trackId = subContent.getTrackID();
myMeta.tracks[trackId].trackID = trackId;
}
if (content.getContent(j).isType("mdia")) {
if (content.getContent(j).isType("mdia")){
MP4::MDIA subContent = (MP4::MDIA &)content.getContent(j);
for (unsigned int k = 0; k < subContent.getContentCount(); k++) {
if (subContent.getContent(k).isType("hdlr")) {
for (unsigned int k = 0; k < subContent.getContentCount(); k++){
if (subContent.getContent(k).isType("hdlr")){
MP4::HDLR subsubContent = (MP4::HDLR &)subContent.getContent(k);
if (subsubContent.getHandlerType() == "soun") {
if (subsubContent.getHandlerType() == "soun"){
myMeta.tracks[trackId].type = "audio";
}
if (subsubContent.getHandlerType() == "vide") {
if (subsubContent.getHandlerType() == "vide"){
myMeta.tracks[trackId].type = "video";
}
}
if (subContent.getContent(k).isType("minf")) {
if (subContent.getContent(k).isType("minf")){
MP4::MINF subsubContent = (MP4::MINF &)subContent.getContent(k);
for (unsigned int l = 0; l < subsubContent.getContentCount(); l++) {
if (subsubContent.getContent(l).isType("stbl")) {
for (unsigned int l = 0; l < subsubContent.getContentCount(); l++){
if (subsubContent.getContent(l).isType("stbl")){
MP4::STBL stblBox = (MP4::STBL &)subsubContent.getContent(l);
for (unsigned int m = 0; m < stblBox.getContentCount(); m++) {
if (stblBox.getContent(m).isType("stsd")) {
for (unsigned int m = 0; m < stblBox.getContentCount(); m++){
if (stblBox.getContent(m).isType("stsd")){
MP4::STSD stsdBox = (MP4::STSD &)stblBox.getContent(m);
for (unsigned int n = 0; n < stsdBox.getEntryCount(); n++) {
if (stsdBox.getEntry(n).isType("mp4a") || stsdBox.getEntry(n).isType("enca")) {
for (unsigned int n = 0; n < stsdBox.getEntryCount(); n++){
if (stsdBox.getEntry(n).isType("mp4a") ||
stsdBox.getEntry(n).isType("enca")){
MP4::MP4A mp4aBox = (MP4::MP4A &)stsdBox.getEntry(n);
myMeta.tracks[trackId].codec = "AAC";
std::string tmpStr;
tmpStr += (char)((mp4aBox.toAACInit() & 0xFF00) >> 8);
tmpStr += (char)(mp4aBox.toAACInit() & 0x00FF);
myMeta.tracks[trackId].init = tmpStr;
myMeta.tracks[trackId].init = tmpStr;
myMeta.tracks[trackId].channels = mp4aBox.getChannelCount();
myMeta.tracks[trackId].size = mp4aBox.getSampleSize();
myMeta.tracks[trackId].rate = mp4aBox.getSampleRate();
}
if (stsdBox.getEntry(n).isType("avc1") || stsdBox.getEntry(n).isType("encv")) {
if (stsdBox.getEntry(n).isType("avc1") ||
stsdBox.getEntry(n).isType("encv")){
MP4::AVC1 avc1Box = (MP4::AVC1 &)stsdBox.getEntry(n);
myMeta.tracks[trackId].height = avc1Box.getHeight();
myMeta.tracks[trackId].width = avc1Box.getWidth();
myMeta.tracks[trackId].init = std::string(avc1Box.getCLAP().payload(), avc1Box.getCLAP().payloadSize());
myMeta.tracks[trackId].init =
std::string(avc1Box.getCLAP().payload(), avc1Box.getCLAP().payloadSize());
myMeta.tracks[trackId].codec = "H264";
}
}
@ -263,36 +246,36 @@ namespace Mist {
}
}
bool inputISMV::parseFrag(int & tId, std::vector<MP4::trunSampleInformation> & trunSamples, std::vector<std::string> & initVecs, std::string & mdat) {
bool inputISMV::parseFrag(int &tId, std::vector<MP4::trunSampleInformation> &trunSamples,
std::vector<std::string> &initVecs, std::string &mdat){
tId = -1;
trunSamples.clear();
initVecs.clear();
mdat.clear();
std::string boxRes;
readBox("moof", boxRes);
if (boxRes == ""){
return false;
}
if (boxRes == ""){return false;}
MP4::MOOF moof;
moof.read(boxRes);
for (unsigned int i = 0; i < moof.getContentCount(); i++) {
if (moof.getContent(i).isType("traf")) {
for (unsigned int i = 0; i < moof.getContentCount(); i++){
if (moof.getContent(i).isType("traf")){
MP4::TRAF trafBox = (MP4::TRAF &)moof.getContent(i);
for (unsigned int j = 0; j < trafBox.getContentCount(); j++) {
if (trafBox.getContent(j).isType("trun")) {
for (unsigned int j = 0; j < trafBox.getContentCount(); j++){
if (trafBox.getContent(j).isType("trun")){
MP4::TRUN trunBox = (MP4::TRUN &)trafBox.getContent(j);
for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++) {
for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++){
trunSamples.push_back(trunBox.getSampleInformation(i));
}
}
if (trafBox.getContent(j).isType("tfhd")) {
if (trafBox.getContent(j).isType("tfhd")){
tId = ((MP4::TFHD &)trafBox.getContent(j)).getTrackID();
}
/*LTS-START*/
if (trafBox.getContent(j).isType("uuid")) {
if (((MP4::UUID &)trafBox.getContent(j)).getUUID() == "a2394f52-5a9b-4f14-a244-6c427c648df4") {
if (trafBox.getContent(j).isType("uuid")){
if (((MP4::UUID &)trafBox.getContent(j)).getUUID() ==
"a2394f52-5a9b-4f14-a244-6c427c648df4"){
MP4::UUID_SampleEncryption uuidBox = (MP4::UUID_SampleEncryption &)trafBox.getContent(j);
for (unsigned int i = 0; i < uuidBox.getSampleCount(); i++) {
for (unsigned int i = 0; i < uuidBox.getSampleCount(); i++){
initVecs.push_back(uuidBox.getSample(i).InitializationVector);
}
}
@ -302,44 +285,39 @@ namespace Mist {
}
}
readBox("mdat", mdat);
if (mdat ==""){
return false;
}
if (mdat == ""){return false;}
return true;
}
void inputISMV::parseFragHeader(const unsigned int & trackId, const unsigned int & keyNum) {
if (!myMeta.tracks.count(trackId) || (myMeta.tracks[trackId].keys.size() <= keyNum)) {
return;
}
void inputISMV::parseFragHeader(const unsigned int &trackId, const unsigned int &keyNum){
if (!myMeta.tracks.count(trackId) || (myMeta.tracks[trackId].keys.size() <= keyNum)){return;}
long long int lastPos = myMeta.tracks[trackId].keys[keyNum].getBpos();
long long int lastTime = myMeta.tracks[trackId].keys[keyNum].getTime() * 10000;
fseek(inFile, lastPos, SEEK_SET);
std::string boxRes;
readBox("moof", boxRes);
if (boxRes == ""){
return;
}
if (boxRes == ""){return;}
MP4::MOOF moof;
moof.read(boxRes);
MP4::TRUN trunBox;
MP4::UUID_SampleEncryption uuidBox; /*LTS*/
for (unsigned int i = 0; i < moof.getContentCount(); i++) {
if (moof.getContent(i).isType("traf")) {
for (unsigned int i = 0; i < moof.getContentCount(); i++){
if (moof.getContent(i).isType("traf")){
MP4::TRAF trafBox = (MP4::TRAF &)moof.getContent(i);
for (unsigned int j = 0; j < trafBox.getContentCount(); j++) {
if (trafBox.getContent(j).isType("trun")) {
for (unsigned int j = 0; j < trafBox.getContentCount(); j++){
if (trafBox.getContent(j).isType("trun")){
trunBox = (MP4::TRUN &)trafBox.getContent(j);
}
if (trafBox.getContent(j).isType("tfhd")) {
if (trafBox.getContent(j).isType("tfhd")){
if (trackId != ((MP4::TFHD &)trafBox.getContent(j)).getTrackID()){
DEBUG_MSG(DLVL_FAIL,"Trackids do not match");
DEBUG_MSG(DLVL_FAIL, "Trackids do not match");
return;
}
}
/*LTS-START*/
if (trafBox.getContent(j).isType("uuid")) {
if (((MP4::UUID &)trafBox.getContent(j)).getUUID() == "a2394f52-5a9b-4f14-a244-6c427c648df4") {
if (trafBox.getContent(j).isType("uuid")){
if (((MP4::UUID &)trafBox.getContent(j)).getUUID() ==
"a2394f52-5a9b-4f14-a244-6c427c648df4"){
uuidBox = (MP4::UUID_SampleEncryption &)trafBox.getContent(j);
}
}
@ -355,17 +333,15 @@ namespace Mist {
myPos.time = lastTime;
myPos.duration = trunBox.getSampleInformation(i).sampleDuration;
myPos.size = trunBox.getSampleInformation(i).sampleSize;
if( trunBox.getFlags() & MP4::trunsampleOffsets){
if (trunBox.getFlags() & MP4::trunsampleOffsets){
unsigned int offsetConv = trunBox.getSampleInformation(i).sampleOffset;
myPos.offset = *(int*)&offsetConv;
myPos.offset = *(int *)&offsetConv;
}else{
myPos.offset = 0;
}
myPos.isKeyFrame = (i == 0);
/*LTS-START*/
if (i <= uuidBox.getSampleCount()){
myPos.iVec = uuidBox.getSample(i).InitializationVector;
}
if (i <= uuidBox.getSampleCount()){myPos.iVec = uuidBox.getSample(i).InitializationVector;}
/*LTS-END*/
lastTime += trunBox.getSampleInformation(i).sampleDuration;
lastPos += trunBox.getSampleInformation(i).sampleSize;
@ -373,25 +349,20 @@ namespace Mist {
}
}
void inputISMV::readBox(const char * type, std::string & result) {
void inputISMV::readBox(const char *type, std::string &result){
int pos = ftell(inFile);
char mp4Head[8];
fread(mp4Head, 8, 1, inFile);
fseek(inFile, pos, SEEK_SET);
if (memcmp(mp4Head + 4, type, 4)) {
if (memcmp(mp4Head + 4, type, 4)){
DEBUG_MSG(DLVL_FAIL, "No %.4s box found at position %d", type, pos);
result = "";
return;
}
unsigned int boxSize = (mp4Head[0] << 24) + (mp4Head[1] << 16) + (mp4Head[2] << 8) + mp4Head[3];
char * tmpBox = (char *)malloc(boxSize * sizeof(char));
char *tmpBox = (char *)malloc(boxSize * sizeof(char));
fread(tmpBox, boxSize, 1, inFile);
result = std::string(tmpBox, boxSize);
free(tmpBox);
}
}
}// namespace Mist

View file

@ -1,16 +1,14 @@
#include "input.h"
#include <mist/dtsc.h>
#include <mist/mp4.h>
#include <mist/mp4_generic.h>
#include <mist/mp4_encryption.h>
#include <mist/mp4_generic.h>
#include <set>
namespace Mist {
struct seekPos {
bool operator < (const seekPos & rhs) const {
if (time < rhs.time){
return true;
}
namespace Mist{
struct seekPos{
bool operator<(const seekPos &rhs) const{
if (time < rhs.time){return true;}
return (time == rhs.time && trackId < rhs.trackId);
}
long long int position;
@ -23,30 +21,30 @@ namespace Mist {
std::string iVec;
};
class inputISMV : public Input{
public:
inputISMV(Util::Config *cfg);
class inputISMV : public Input {
public:
inputISMV(Util::Config * cfg);
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool atKeyFrame();
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool atKeyFrame();
FILE * inFile;
FILE *inFile;
void parseMoov(MP4::MOOV & moovBox);
bool parseFrag(int & tId, std::vector<MP4::trunSampleInformation> & trunSamples, std::vector<std::string> & initVecs, std::string & mdat);
void parseFragHeader(const unsigned int & trackId, const unsigned int & keyNum);
void readBox(const char * type, std::string & result);
std::set<seekPos> buffered;
std::map<int, int> lastKeyNum;
void parseMoov(MP4::MOOV &moovBox);
bool parseFrag(int &tId, std::vector<MP4::trunSampleInformation> &trunSamples,
std::vector<std::string> &initVecs, std::string &mdat);
void parseFragHeader(const unsigned int &trackId, const unsigned int &keyNum);
void readBox(const char *type, std::string &result);
std::set<seekPos> buffered;
std::map<int, int> lastKeyNum;
};
}
}// namespace Mist
typedef Mist::inputISMV mistIn;

View file

@ -1,19 +1,19 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/flv_tag.h>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/flv_tag.h>
#include <mist/mpeg.h>
#include <mist/stream.h>
#include <string>
#include "input_mp3.h"
namespace Mist {
inputMP3::inputMP3(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputMP3::inputMP3(Util::Config *cfg) : Input(cfg){
capa["name"] = "MP3";
capa["desc"] = "This input allows you to stream MP3 Video on Demand files.";
capa["source_match"] = "/*.mp3";
@ -23,51 +23,51 @@ namespace Mist {
timestamp = 0;
}
bool inputMP3::checkArguments() {
if (config->getString("input") == "-") {
bool inputMP3::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not yet supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
}
return true;
}
bool inputMP3::preRun() {
//open File
bool inputMP3::preRun(){
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile) {
return false;
}
if (!inFile){return false;}
return true;
}
bool inputMP3::readHeader() {
bool inputMP3::readHeader(){
if (!inFile){return false;}
myMeta = DTSC::Meta();
myMeta.tracks[1].trackID = 1;
myMeta.tracks[1].type = "audio";
myMeta.tracks[1].codec = "MP3";
//Create header file from MP3 data
// Create header file from MP3 data
char header[10];
fread(header, 10, 1, inFile);//Read a 10 byte header
fread(header, 10, 1, inFile); // Read a 10 byte header
if (header[0] == 'I' || header[1] == 'D' || header[2] == '3'){
size_t id3size = (((int)header[6] & 0x7F) << 21) | (((int)header[7] & 0x7F) << 14) | (((int)header[8] & 0x7F) << 7) | (header[9] & 0x7F) + 10 + ((header[5] & 0x10) ? 10 : 0);
size_t id3size = (((int)header[6] & 0x7F) << 21) | (((int)header[7] & 0x7F) << 14) |
(((int)header[8] & 0x7F) << 7) |
(header[9] & 0x7F) + 10 + ((header[5] & 0x10) ? 10 : 0);
INFO_MSG("id3 size: %lu bytes", id3size);
fseek(inFile, id3size, SEEK_SET);
}else{
fseek(inFile, 0, SEEK_SET);
}
//Read the first mp3 header for bitrate and such
// Read the first mp3 header for bitrate and such
size_t filePos = ftell(inFile);
fread(header, 4, 1, inFile);
fseek(inFile, filePos, SEEK_SET);
@ -87,25 +87,21 @@ namespace Mist {
myMeta.toFile(config->getString("input") + ".dtsh");
return true;
}
void inputMP3::getNext(bool smart) {
void inputMP3::getNext(bool smart){
thisPacket.null();
static char packHeader[3000];
size_t filePos = ftell(inFile);
size_t read = fread(packHeader, 1, 3000, inFile);
if (!read) {
return;
}
if (!read){return;}
if (packHeader[0] != 0xFF || (packHeader[1] & 0xE0) != 0xE0){
//Find the first occurence of sync byte
char* i = (char*)memchr(packHeader, (char)0xFF, read);
if (!i) {
return;
}
// Find the first occurence of sync byte
char *i = (char *)memchr(packHeader, (char)0xFF, read);
if (!i){return;}
size_t offset = i - packHeader;
while (offset && (i[1] & 0xE0) != 0xE0){
i = (char*)memchr(i + 1, (char)0xFF, read - (offset + 1));
if (!i) {
i = (char *)memchr(i + 1, (char)0xFF, read - (offset + 1));
if (!i){
offset = 0;
break;
}
@ -118,60 +114,52 @@ namespace Mist {
fseek(inFile, filePos, SEEK_SET);
read = fread(packHeader, 1, 3000, inFile);
}
//We now have a sync byte for sure
// We now have a sync byte for sure
//mpeg version is on the bits 0x18 of packHeader[1], but only 0x08 is important --> 0 is version 2, 1 is version 1
//leads to 2 - value == version, -1 to get the right index for the array
// mpeg version is on the bits 0x18 of packHeader[1], but only 0x08 is important --> 0 is version 2, 1 is version 1
// leads to 2 - value == version, -1 to get the right index for the array
int mpegVersion = 1 - ((packHeader[1] >> 3) & 0x01);
//mpeg layer is on the bits 0x06 of packHeader[1] --> 1 is layer 3, 2 is layer 2, 3 is layer 1
//leads to 4 - value == layer, -1 to get the right index for the array
// mpeg layer is on the bits 0x06 of packHeader[1] --> 1 is layer 3, 2 is layer 2, 3 is layer 1
// leads to 4 - value == layer, -1 to get the right index for the array
int mpegLayer = 3 - ((packHeader[1] >> 1) & 0x03);
int sampleCount = sampleCounts[mpegVersion][mpegLayer];
//samplerate is encoded in bits 0x0C of packHeader[2];
int sampleCount = sampleCounts[mpegVersion][mpegLayer];
// samplerate is encoded in bits 0x0C of packHeader[2];
int sampleRate = sampleRates[mpegVersion][((packHeader[2] >> 2) & 0x03)] * 1000;
int bitRate = bitRates[mpegVersion][mpegLayer][((packHeader[2] >> 4) & 0x0F)] * 1000;
size_t dataSize = 0;
if (mpegLayer == 0){ //layer 1
//Layer 1: dataSize = (12 * BitRate / SampleRate + Padding) * 4
if (mpegLayer == 0){// layer 1
// Layer 1: dataSize = (12 * BitRate / SampleRate + Padding) * 4
dataSize = (12 * ((double)bitRate / sampleRate) + ((packHeader[2] >> 1) & 0x01)) * 4;
}else{//Layer 2 or 3
//Layer 2, 3: dataSize = 144 * BitRate / SampleRate + Padding
}else{// Layer 2 or 3
// Layer 2, 3: dataSize = 144 * BitRate / SampleRate + Padding
dataSize = 144 * ((double)bitRate / sampleRate) + ((packHeader[2] >> 1) & 0x01);
}
if (!dataSize){
return;
}
if (!dataSize){return;}
fseek(inFile, filePos + dataSize, SEEK_SET);
//Create a json value with the right data
// Create a json value with the right data
static JSON::Value thisPack;
thisPack.null();
thisPack["trackid"] = 1;
thisPack["bpos"] = (uint64_t)filePos;
thisPack["data"] = std::string(packHeader, dataSize);
thisPack["time"] = timestamp;
//Write the json value to lastpack
// Write the json value to lastpack
std::string tmpStr = thisPack.toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
//Update the internal timestamp
// Update the internal timestamp
timestamp += (sampleCount / (sampleRate / 1000));
}
void inputMP3::seek(int seekTime) {
std::deque<DTSC::Key> & keys = myMeta.tracks[1].keys;
void inputMP3::seek(int seekTime){
std::deque<DTSC::Key> &keys = myMeta.tracks[1].keys;
size_t seekPos = keys[0].getBpos();
for (unsigned int i = 0; i < keys.size(); i++){
if (keys[i].getTime() > seekTime){
break;
}
if (keys[i].getTime() > seekTime){break;}
seekPos = keys[i].getBpos();
timestamp = keys[i].getTime();
}
@ -179,9 +167,8 @@ namespace Mist {
fseek(inFile, seekPos, SEEK_SET);
}
void inputMP3::trackSelect(std::string trackSpec) {
//Ignore, do nothing
//MP3 Always has only 1 track, so we can't select much else..
void inputMP3::trackSelect(std::string trackSpec){
// Ignore, do nothing
// MP3 Always has only 1 track, so we can't select much else..
}
}
}// namespace Mist

View file

@ -1,32 +1,33 @@
#include "input.h"
#include <mist/dtsc.h>
#include <deque>
#include <mist/dtsc.h>
namespace Mist {
const static double sampleRates[2][3] = {{44.1, 48.0, 32.0}, {22.05, 24.0, 16.0}};
const static int sampleCounts[2][3] = {{374, 1152, 1152}, {384, 1152, 576}};
const static int bitRates[2][3][16] = {{{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1},
{0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1},
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1}},
{{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1}}};
class inputMP3 : public Input {
public:
inputMP3(Util::Config * cfg);
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
double timestamp;
namespace Mist{
const static double sampleRates[2][3] ={{44.1, 48.0, 32.0},{22.05, 24.0, 16.0}};
const static int sampleCounts[2][3] ={{374, 1152, 1152},{384, 1152, 576}};
const static int bitRates[2][3][16] ={
{{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1},
{0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1},
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1}},
{{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1}}};
class inputMP3 : public Input{
public:
inputMP3(Util::Config *cfg);
FILE * inFile;
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
double timestamp;
FILE *inFile;
};
}
}// namespace Mist
typedef Mist::inputMP3 mistIn;

View file

@ -1,16 +1,16 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <inttypes.h>
#include <mist/stream.h>
#include <mist/flv_tag.h>
#include <mist/defines.h>
#include <mist/h264.h>
#include <iostream>
#include <mist/bitfields.h>
#include <mist/defines.h>
#include <mist/flv_tag.h>
#include <mist/h264.h>
#include <mist/stream.h>
#include <string>
#include "input_mp4.h"
@ -34,13 +34,11 @@ namespace Mist{
stco64 = false;
}
uint64_t mp4TrackHeader::size(){
return (stszBox.asBox() ? stszBox.getSampleCount() : 0);
}
uint64_t mp4TrackHeader::size(){return (stszBox.asBox() ? stszBox.getSampleCount() : 0);}
void mp4TrackHeader::read(MP4::TRAK & trakBox){
void mp4TrackHeader::read(MP4::TRAK &trakBox){
initialised = false;
std::string tmp;//temporary string for copying box data
std::string tmp; // temporary string for copying box data
MP4::Box trakLoopPeek;
timeScale = 1;
@ -60,26 +58,26 @@ namespace Mist{
hasCTTS = cttsBox.isType("ctts");
}
void mp4TrackHeader::getPart(uint64_t index, uint64_t & offset, uint32_t & size, uint64_t & timestamp, int32_t & timeOffset, uint64_t & duration){
void mp4TrackHeader::getPart(uint64_t index, uint64_t &offset, uint32_t &size,
uint64_t &timestamp, int32_t &timeOffset, uint64_t &duration){
if (index < sampleIndex){
sampleIndex = 0;
stscStart = 0;
}
uint64_t stscCount = stscBox.getEntryCount();
MP4::STSCEntry stscEntry;
while (stscStart < stscCount){
stscEntry = stscBox.getSTSCEntry(stscStart);
//check where the next index starts
// check where the next index starts
uint64_t nextSampleIndex;
if (stscStart + 1 < stscCount){
nextSampleIndex = sampleIndex + (stscBox.getSTSCEntry(stscStart+1).firstChunk - stscEntry.firstChunk) * stscEntry.samplesPerChunk;
nextSampleIndex = sampleIndex + (stscBox.getSTSCEntry(stscStart + 1).firstChunk - stscEntry.firstChunk) *
stscEntry.samplesPerChunk;
}else{
nextSampleIndex = stszBox.getSampleCount();
}
if (nextSampleIndex > index){
break;
}
if (nextSampleIndex > index){break;}
sampleIndex = nextSampleIndex;
++stscStart;
}
@ -88,13 +86,11 @@ namespace Mist{
FAIL_MSG("Could not complete seek - not in file (%" PRIu64 " > %" PRIu64 ")", sampleIndex, index);
}
uint64_t stcoPlace = (stscEntry.firstChunk - 1 ) + ((index - sampleIndex) / stscEntry.samplesPerChunk);
uint64_t stcoPlace = (stscEntry.firstChunk - 1) + ((index - sampleIndex) / stscEntry.samplesPerChunk);
uint64_t stszStart = sampleIndex + (stcoPlace - (stscEntry.firstChunk - 1)) * stscEntry.samplesPerChunk;
offset = (stco64 ? co64Box.getChunkOffset(stcoPlace) : stcoBox.getChunkOffset(stcoPlace));
for (int j = stszStart; j < index; j++){
offset += stszBox.getEntrySize(j);
}
for (int j = stszStart; j < index; j++){offset += stszBox.getEntrySize(j);}
if (index < deltaPos){
deltaIndex = 0;
@ -106,24 +102,22 @@ namespace Mist{
uint64_t sttsCount = sttsBox.getEntryCount();
while (deltaIndex < sttsCount){
tmpSTTS = sttsBox.getSTTSEntry(deltaIndex);
if ((index - deltaPos) < tmpSTTS.sampleCount){
break;
}
if ((index - deltaPos) < tmpSTTS.sampleCount){break;}
deltaTotal += tmpSTTS.sampleCount * tmpSTTS.sampleDelta;
deltaPos += tmpSTTS.sampleCount;
++deltaIndex;
}
timestamp = ((deltaTotal + ((index-deltaPos) * tmpSTTS.sampleDelta))*1000) / timeScale;
timestamp = ((deltaTotal + ((index - deltaPos) * tmpSTTS.sampleDelta)) * 1000) / timeScale;
duration = 0;
{
uint64_t tmpIndex = deltaIndex;
uint64_t tmpPos = deltaPos;
uint64_t tmpTotal = deltaTotal;
while (tmpIndex < sttsCount){
tmpSTTS = sttsBox.getSTTSEntry(tmpIndex);
if ((index+1 - tmpPos) < tmpSTTS.sampleCount){
duration = (((tmpTotal + ((index+1-tmpPos) * tmpSTTS.sampleDelta))*1000) / timeScale) - timestamp;
if ((index + 1 - tmpPos) < tmpSTTS.sampleCount){
duration = (((tmpTotal + ((index + 1 - tmpPos) * tmpSTTS.sampleDelta)) * 1000) / timeScale) - timestamp;
break;
}
tmpTotal += tmpSTTS.sampleCount * tmpSTTS.sampleDelta;
@ -144,7 +138,7 @@ namespace Mist{
while (offsetIndex < cttsCount){
tmpCTTS = cttsBox.getCTTSEntry(offsetIndex);
if ((index - offsetPos) < tmpCTTS.sampleCount){
timeOffset = (tmpCTTS.sampleOffset*1000)/timeScale;
timeOffset = (tmpCTTS.sampleOffset * 1000) / timeScale;
break;
}
offsetPos += tmpCTTS.sampleCount;
@ -153,10 +147,10 @@ namespace Mist{
}
size = stszBox.getEntrySize(index);
}
inputMP4::inputMP4(Util::Config * cfg) : Input(cfg){
malSize = 4;//initialise data read buffer to 0;
data = (char*)malloc(malSize);
inputMP4::inputMP4(Util::Config *cfg) : Input(cfg){
malSize = 4; // initialise data read buffer to 0;
data = (char *)malloc(malSize);
capa["name"] = "MP4";
capa["desc"] = "This input allows streaming of MP4 files as Video on Demand.";
capa["source_match"] = "/*.mp4";
@ -170,11 +164,9 @@ namespace Mist{
capa["codecs"][0u][1u].append("AC3");
capa["codecs"][0u][1u].append("MP3");
}
inputMP4::~inputMP4(){
free(data);
}
inputMP4::~inputMP4(){free(data);}
bool inputMP4::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
@ -194,15 +186,12 @@ namespace Mist{
}
return true;
}
bool inputMP4::preRun(){
//open File
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile){
return false;
}
if (!inFile){return false;}
return true;
}
bool inputMP4::readHeader(){
@ -210,19 +199,17 @@ namespace Mist{
INFO_MSG("inFile failed!");
return false;
}
uint32_t trackNo = 0;
//first we get the necessary header parts
while(!feof(inFile)){
// first we get the necessary header parts
while (!feof(inFile)){
std::string boxType = MP4::readBoxType(inFile);
if (boxType == "erro"){
break;
}
if (boxType=="moov"){
if (boxType == "erro"){break;}
if (boxType == "moov"){
MP4::MOOV moovBox;
moovBox.read(inFile);
//for all box in moov
// for all box in moov
std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
@ -230,25 +217,23 @@ namespace Mist{
}
continue;
}
if (!MP4::skipBox(inFile)){//moving on to next box
if (!MP4::skipBox(inFile)){// moving on to next box
FAIL_MSG("Error in skipping box, exiting");
return false;
}
}
fseeko(inFile,0,SEEK_SET);
//See whether a separate header file exists.
fseeko(inFile, 0, SEEK_SET);
// See whether a separate header file exists.
if (readExistingHeader()){return true;}
HIGH_MSG("Not read existing header");
trackNo = 0;
//Create header file from MP4 data
while(!feof(inFile)){
// Create header file from MP4 data
while (!feof(inFile)){
std::string boxType = MP4::readBoxType(inFile);
if (boxType=="erro"){
break;
}
if (boxType=="moov"){
if (boxType == "erro"){break;}
if (boxType == "moov"){
MP4::MOOV moovBox;
moovBox.read(inFile);
@ -256,7 +241,7 @@ namespace Mist{
HIGH_MSG("Obtained %zu trak Boxes", trak.size());
for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
uint64_t trackNo = myMeta.tracks.size()+1;
uint64_t trackNo = myMeta.tracks.size() + 1;
myMeta.tracks[trackNo].trackID = trackNo;
MP4::TKHD tkhdBox = trakIt->getChild<MP4::TKHD>();
@ -283,10 +268,10 @@ namespace Mist{
MP4::STSD stsdBox = stblBox.getChild<MP4::STSD>();
MP4::Box sEntryBox = stsdBox.getEntry(0);
std::string sType = sEntryBox.getType();
HIGH_MSG("Found track %zu of type %s", trackNo, sType.c_str());
HIGH_MSG("Found track %zu of type %s", trackNo, sType.c_str());
if (sType == "avc1" || sType == "h264" || sType == "mp4v"){
MP4::VisualSampleEntry & vEntryBox = (MP4::VisualSampleEntry&)sEntryBox;
MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
myMeta.tracks[trackNo].type = "video";
myMeta.tracks[trackNo].codec = "H264";
@ -301,7 +286,7 @@ namespace Mist{
if (initBox.isType("avcC")){
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
}
///this is a hacky way around invalid FLV data (since it gets ignored nearly everywhere, but we do need correct data...
/// this is a hacky way around invalid FLV data (since it gets ignored nearly everywhere, but we do need correct data...
if (!myMeta.tracks[trackNo].width){
h264::sequenceParameterSet sps;
sps.fromDTSCInit(myMeta.tracks[trackNo].init);
@ -311,7 +296,7 @@ namespace Mist{
}
}
if (sType == "hev1" || sType == "hvc1"){
MP4::VisualSampleEntry & vEntryBox = (MP4::VisualSampleEntry&)sEntryBox;
MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
myMeta.tracks[trackNo].type = "video";
myMeta.tracks[trackNo].codec = "HEVC";
if (!myMeta.tracks[trackNo].width){
@ -328,7 +313,7 @@ namespace Mist{
}
}
if (sType == "mp4a" || sType == "aac " || sType == "ac-3"){
MP4::AudioSampleEntry & aEntryBox = (MP4::AudioSampleEntry&)sEntryBox;
MP4::AudioSampleEntry &aEntryBox = (MP4::AudioSampleEntry &)sEntryBox;
myMeta.tracks[trackNo].type = "audio";
myMeta.tracks[trackNo].channels = aEntryBox.getChannelCount();
myMeta.tracks[trackNo].rate = aEntryBox.getSampleRate();
@ -336,14 +321,14 @@ namespace Mist{
if (sType == "ac-3"){
myMeta.tracks[trackNo].codec = "AC3";
}else{
MP4::ESDS esdsBox = (MP4::ESDS&)(aEntryBox.getCodecBox());
MP4::ESDS esdsBox = (MP4::ESDS &)(aEntryBox.getCodecBox());
myMeta.tracks[trackNo].codec = esdsBox.getCodec();
myMeta.tracks[trackNo].init = esdsBox.getInitData();
}
myMeta.tracks[trackNo].size = 16;///\todo this might be nice to calculate from mp4 file;
myMeta.tracks[trackNo].size = 16; ///\todo this might be nice to calculate from mp4 file;
}
if (sType == "tx3g"){//plain text subtitles
if (sType == "tx3g"){// plain text subtitles
myMeta.tracks[trackNo].type = "meta";
myMeta.tracks[trackNo].codec = "subtitle";
}
@ -354,12 +339,12 @@ namespace Mist{
MP4::STCO stcoBox = stblBox.getChild<MP4::STCO>();
MP4::CO64 co64Box = stblBox.getChild<MP4::CO64>();
MP4::STSC stscBox = stblBox.getChild<MP4::STSC>();
MP4::CTTS cttsBox = stblBox.getChild<MP4::CTTS>();//optional ctts box
MP4::CTTS cttsBox = stblBox.getChild<MP4::CTTS>(); // optional ctts box
bool stco64 = co64Box.isType("co64");
bool hasCTTS = cttsBox.isType("ctts");
uint64_t totaldur = 0;///\todo note: set this to begin time
uint64_t totaldur = 0; ///\todo note: set this to begin time
mp4PartBpos BsetPart;
uint64_t entryNo = 0;
@ -368,8 +353,8 @@ namespace Mist{
uint64_t stssIndex = 0;
uint64_t stcoIndex = 0;
uint64_t stscIndex = 0;
uint64_t cttsIndex = 0;//current ctts Index we are reading
uint64_t cttsEntryRead = 0;//current part of ctts we are reading
uint64_t cttsIndex = 0; // current ctts Index we are reading
uint64_t cttsEntryRead = 0; // current part of ctts we are reading
uint64_t stssCount = stssBox.getEntryCount();
uint64_t stscCount = stscBox.getEntryCount();
@ -386,35 +371,33 @@ namespace Mist{
for (uint64_t stszIndex = 0; stszIndex < stszCount; ++stszIndex){
if (stcoIndex >= nextFirstChunk){
++stscIndex;
nextFirstChunk = (stscIndex + 1 < stscCount ? stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1 : stcoCount);
nextFirstChunk =
(stscIndex + 1 < stscCount ? stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1 : stcoCount);
}
BsetPart.keyframe = (myMeta.tracks[trackNo].type == "video" && stssIndex < stssCount && stszIndex + 1 == stssBox.getSampleNumber(stssIndex));
if (BsetPart.keyframe){
++stssIndex;
}
//in bpos set
BsetPart.keyframe = (myMeta.tracks[trackNo].type == "video" && stssIndex < stssCount &&
stszIndex + 1 == stssBox.getSampleNumber(stssIndex));
if (BsetPart.keyframe){++stssIndex;}
// in bpos set
BsetPart.stcoNr = stcoIndex;
//bpos = chunkoffset[samplenr] in stco
// bpos = chunkoffset[samplenr] in stco
BsetPart.bpos = tmpOffset;
++fromSTCOinSTSC;
if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){//as long as we are still in this chunk
if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){// as long as we are still in this chunk
tmpOffset += stszBox.getEntrySize(stszIndex);
}else{
++stcoIndex;
fromSTCOinSTSC = 0;
tmpOffset = (stco64 ? co64Box.getChunkOffset(stcoIndex) : stcoBox.getChunkOffset(stcoIndex));
}
BsetPart.time = (totaldur*1000)/timescale;
BsetPart.time = (totaldur * 1000) / timescale;
totaldur += sttsEntry.sampleDelta;
sampleNo++;
if (sampleNo >= sttsEntry.sampleCount){
++entryNo;
sampleNo = 0;
if (entryNo < sttsBox.getEntryCount()){
sttsEntry = sttsBox.getSTTSEntry(entryNo);
}
if (entryNo < sttsBox.getEntryCount()){sttsEntry = sttsBox.getSTTSEntry(entryNo);}
}
if (hasCTTS){
MP4::CTTSEntry cttsEntry = cttsBox.getCTTSEntry(cttsIndex);
cttsEntryRead++;
@ -422,71 +405,74 @@ namespace Mist{
++cttsIndex;
cttsEntryRead = 0;
}
BsetPart.timeOffset = (cttsEntry.sampleOffset * 1000)/timescale;
BsetPart.timeOffset = (cttsEntry.sampleOffset * 1000) / timescale;
}else{
BsetPart.timeOffset = 0;
}
if(sType == "tx3g"){
if(stszBox.getEntrySize(stszIndex) <=2 && false){
if (sType == "tx3g"){
if (stszBox.getEntrySize(stszIndex) <= 2 && false){
FAIL_MSG("size <=2");
}else{
long long packSendSize = 0;
packSendSize = 24 + (BsetPart.timeOffset ? 17 : 0) + (BsetPart.bpos ? 15 : 0) + 19 +
stszBox.getEntrySize(stszIndex) + 11-2 + 19;
stszBox.getEntrySize(stszIndex) + 11 - 2 + 19;
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo,
stszBox.getEntrySize(stszIndex) -2 , BsetPart.bpos, true,
packSendSize);
stszBox.getEntrySize(stszIndex) - 2, BsetPart.bpos, true, packSendSize);
}
}else{
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo, stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe);
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo,
stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe);
}
}
}
continue;
}
if (!MP4::skipBox(inFile)){//moving on to next box
if (!MP4::skipBox(inFile)){// moving on to next box
FAIL_MSG("Error in Skipping box, exiting");
return false;
}
}
clearerr(inFile);
//outputting dtsh file
// outputting dtsh file
myMeta.toFile(config->getString("input") + ".dtsh");
return true;
}
void inputMP4::getNext(bool smart){//get next part from track in stream
void inputMP4::getNext(bool smart){// get next part from track in stream
if (curPositions.empty()){
thisPacket.null();
return;
}
//pop uit set
// pop uit set
mp4PartTime curPart = *curPositions.begin();
curPositions.erase(curPositions.begin());
bool isKeyframe = false;
if(nextKeyframe[curPart.trackID] < myMeta.tracks[curPart.trackID].keys.size()){
//checking if this is a keyframe
if (myMeta.tracks[curPart.trackID].type == "video" && (long long int) curPart.time == myMeta.tracks[curPart.trackID].keys[(nextKeyframe[curPart.trackID])].getTime()){
if (nextKeyframe[curPart.trackID] < myMeta.tracks[curPart.trackID].keys.size()){
// checking if this is a keyframe
if (myMeta.tracks[curPart.trackID].type == "video" &&
(long long int)curPart.time ==
myMeta.tracks[curPart.trackID].keys[(nextKeyframe[curPart.trackID])].getTime()){
isKeyframe = true;
}
//if a keyframe has passed, we find the next keyframe
if (myMeta.tracks[curPart.trackID].keys[(nextKeyframe[curPart.trackID])].getTime() <= (long long int)curPart.time){
nextKeyframe[curPart.trackID] ++;
// if a keyframe has passed, we find the next keyframe
if (myMeta.tracks[curPart.trackID].keys[(nextKeyframe[curPart.trackID])].getTime() <=
(long long int)curPart.time){
nextKeyframe[curPart.trackID]++;
}
}
if (fseeko(inFile,curPart.bpos,SEEK_SET)){
FAIL_MSG("seek unsuccessful @bpos %" PRIu64 ": %s",curPart.bpos, strerror(errno));
if (fseeko(inFile, curPart.bpos, SEEK_SET)){
FAIL_MSG("seek unsuccessful @bpos %" PRIu64 ": %s", curPart.bpos, strerror(errno));
thisPacket.null();
return;
}
if (curPart.size > malSize){
data = (char*)realloc(data, curPart.size);
data = (char *)realloc(data, curPart.size);
malSize = curPart.size;
}
if (fread(data, curPart.size, 1, inFile)!=1){
if (fread(data, curPart.size, 1, inFile) != 1){
FAIL_MSG("read unsuccessful at %" PRIu64, ftell(inFile));
thisPacket.null();
return;
@ -494,44 +480,44 @@ namespace Mist{
if (myMeta.tracks[curPart.trackID].codec == "subtitle"){
unsigned int txtLen = Bit::btohs(data);
if (!txtLen && false ){
curPart.index ++;
if (!txtLen && false){
curPart.index++;
return getNext(smart);
//thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, " ", 1, 0/*Note: no bpos*/, isKeyframe);
// thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, " ", 1, 0/*Note: no bpos*/, isKeyframe);
}else{
static JSON::Value thisPack;
thisPack.null();
thisPack["trackid"] = (uint64_t)curPart.trackID;
thisPack["bpos"] = curPart.bpos; //(long long)fileSource.tellg();
thisPack["data"] = std::string(data+2,txtLen);
thisPack["data"] = std::string(data + 2, txtLen);
thisPack["time"] = curPart.time;
if (curPart.duration){
thisPack["duration"] = curPart.duration;
}
thisPack["keyframe"] = true;
if (curPart.duration){thisPack["duration"] = curPart.duration;}
thisPack["keyframe"] = true;
// Write the json value to lastpack
std::string tmpStr = thisPack.toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
//return;
// return;
//thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data+2, txtLen, 0/*Note: no bpos*/, isKeyframe);
// thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data+2, txtLen, 0/*Note: no bpos*/, isKeyframe);
}
}else{
thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size, 0/*Note: no bpos*/, isKeyframe);
thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size,
0 /*Note: no bpos*/, isKeyframe);
}
//get the next part for this track
curPart.index ++;
// get the next part for this track
curPart.index++;
if (curPart.index < headerData[curPart.trackID].size()){
headerData[curPart.trackID].getPart(curPart.index, curPart.bpos, curPart.size, curPart.time, curPart.offset, curPart.duration);
headerData[curPart.trackID].getPart(curPart.index, curPart.bpos, curPart.size, curPart.time,
curPart.offset, curPart.duration);
curPositions.insert(curPart);
}
}
void inputMP4::seek(int seekTime){//seek to a point
void inputMP4::seek(int seekTime){// seek to a point
nextKeyframe.clear();
//for all tracks
// for all tracks
curPositions.clear();
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
nextKeyframe[*it] = 0;
@ -540,23 +526,23 @@ namespace Mist{
addPart.size = 0;
addPart.time = 0;
addPart.trackID = *it;
//for all indexes in those tracks
// for all indexes in those tracks
for (unsigned int i = 0; i < headerData[*it].size(); i++){
//if time > seekTime
// if time > seekTime
headerData[*it].getPart(i, addPart.bpos, addPart.size, addPart.time, addPart.offset, addPart.duration);
//check for keyframe time in myMeta and update nextKeyframe
// check for keyframe time in myMeta and update nextKeyframe
//
if (myMeta.tracks[*it].keys[(nextKeyframe[*it])].getTime() < addPart.time){
nextKeyframe[*it] ++;
nextKeyframe[*it]++;
}
if (addPart.time >= seekTime){
addPart.index = i;
//use addPart thingy in time set and break
// use addPart thingy in time set and break
curPositions.insert(addPart);
break;
}//end if time > seektime
}//end for all indexes
}//rof all tracks
}// end if time > seektime
}// end for all indexes
}// rof all tracks
}
void inputMP4::trackSelect(std::string trackSpec){
@ -565,7 +551,8 @@ namespace Mist{
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
VERYHIGH_MSG("Added track %d, index = %lld, (index == npos) = %d", atoi(trackSpec.substr(0, index).c_str()), index, index == std::string::npos);
VERYHIGH_MSG("Added track %d, index = %lld, (index == npos) = %d",
atoi(trackSpec.substr(0, index).c_str()), index, index == std::string::npos);
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
}else{
@ -573,5 +560,4 @@ namespace Mist{
}
}
}
}
}// namespace Mist

View file

@ -2,42 +2,30 @@
#include <mist/dtsc.h>
#include <mist/mp4.h>
#include <mist/mp4_generic.h>
namespace Mist {
namespace Mist{
class mp4PartTime{
public:
mp4PartTime() : time(0), offset(0), trackID(0), bpos(0), size(0), index(0), duration(0) {}
bool operator < (const mp4PartTime & rhs) const {
if (time < rhs.time){
return true;
}
if (time > rhs.time){
return false;
}
if (trackID < rhs.trackID){
return true;
}
return (trackID == rhs.trackID && bpos < rhs.bpos);
}
uint64_t time;
uint64_t duration;
int32_t offset;
size_t trackID;
uint64_t bpos;
uint32_t size;
uint64_t index;
public:
mp4PartTime() : time(0), offset(0), trackID(0), bpos(0), size(0), index(0), duration(0){}
bool operator<(const mp4PartTime &rhs) const{
if (time < rhs.time){return true;}
if (time > rhs.time){return false;}
if (trackID < rhs.trackID){return true;}
return (trackID == rhs.trackID && bpos < rhs.bpos);
}
uint64_t time;
uint64_t duration;
int32_t offset;
size_t trackID;
uint64_t bpos;
uint32_t size;
uint64_t index;
};
struct mp4PartBpos{
bool operator < (const mp4PartBpos & rhs) const {
if (time < rhs.time){
return true;
}
if (time > rhs.time){
return false;
}
if (trackID < rhs.trackID){
return true;
}
bool operator<(const mp4PartBpos &rhs) const{
if (time < rhs.time){return true;}
if (time > rhs.time){return false;}
if (trackID < rhs.trackID){return true;}
return (trackID == rhs.trackID && bpos < rhs.bpos);
}
uint64_t time;
@ -50,61 +38,64 @@ namespace Mist {
};
class mp4TrackHeader{
public:
mp4TrackHeader();
void read(MP4::TRAK & trakBox);
MP4::STCO stcoBox;
MP4::CO64 co64Box;
MP4::STSZ stszBox;
MP4::STTS sttsBox;
bool hasCTTS;
MP4::CTTS cttsBox;
MP4::STSC stscBox;
uint64_t timeScale;
void getPart(uint64_t index, uint64_t & offset, uint32_t & size, uint64_t & timestamp, int32_t & timeOffset, uint64_t & duration);
uint64_t size();
private:
bool initialised;
//next variables are needed for the stsc/stco loop
uint64_t stscStart;
uint64_t sampleIndex;
//next variables are needed for the stts loop
uint64_t deltaIndex;///< Index in STTS box
uint64_t deltaPos;///< Sample counter for STTS box
uint64_t deltaTotal;///< Total timestamp for STTS box
//for CTTS box loop
uint64_t offsetIndex;///< Index in CTTS box
uint64_t offsetPos;///< Sample counter for CTTS box
public:
mp4TrackHeader();
void read(MP4::TRAK &trakBox);
MP4::STCO stcoBox;
MP4::CO64 co64Box;
MP4::STSZ stszBox;
MP4::STTS sttsBox;
bool hasCTTS;
MP4::CTTS cttsBox;
MP4::STSC stscBox;
uint64_t timeScale;
void getPart(uint64_t index, uint64_t &offset, uint32_t &size, uint64_t &timestamp,
int32_t &timeOffset, uint64_t &duration);
uint64_t size();
bool stco64;
private:
bool initialised;
// next variables are needed for the stsc/stco loop
uint64_t stscStart;
uint64_t sampleIndex;
// next variables are needed for the stts loop
uint64_t deltaIndex; ///< Index in STTS box
uint64_t deltaPos; ///< Sample counter for STTS box
uint64_t deltaTotal; ///< Total timestamp for STTS box
// for CTTS box loop
uint64_t offsetIndex; ///< Index in CTTS box
uint64_t offsetPos; ///< Sample counter for CTTS box
bool stco64;
};
class inputMP4 : public Input {
public:
inputMP4(Util::Config * cfg);
~inputMP4();
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
bool needHeader(){return true;}
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
class inputMP4 : public Input{
public:
inputMP4(Util::Config *cfg);
~inputMP4();
FILE * inFile;
std::map<unsigned int, mp4TrackHeader> headerData;
std::set<mp4PartTime> curPositions;
//remember last seeked keyframe;
std::map <unsigned int, unsigned int> nextKeyframe;
//these next two variables keep a buffer for reading from filepointer inFile;
uint64_t malSize;
char* data;///\todo rename this variable to a more sensible name, it is a temporary piece of memory to read from files
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
bool needHeader(){return true;}
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
FILE *inFile;
std::map<unsigned int, mp4TrackHeader> headerData;
std::set<mp4PartTime> curPositions;
// remember last seeked keyframe;
std::map<unsigned int, unsigned int> nextKeyframe;
// these next two variables keep a buffer for reading from filepointer inFile;
uint64_t malSize;
char *data; ///\todo rename this variable to a more sensible name, it is a temporary piece of memory to read from files
};
}
}// namespace Mist
typedef Mist::inputMP4 mistIn;

View file

@ -1,50 +1,46 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/ogg.h>
#include <mist/defines.h>
#include <mist/bitstream.h>
#include <mist/opus.h>
#include "input_ogg.h"
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/bitstream.h>
#include <mist/defines.h>
#include <mist/ogg.h>
#include <mist/opus.h>
#include <mist/stream.h>
#include <string>
///\todo Whar be Opus support?
namespace Mist {
namespace Mist{
JSON::Value segment::toJSON(OGG::oggCodec myCodec){
JSON::Value retval;
retval["time"] = time;
retval["trackid"] = tid;
std::string tmpString = "";
for (unsigned int i = 0; i < parts.size(); i++){
tmpString += parts[i];
}
for (unsigned int i = 0; i < parts.size(); i++){tmpString += parts[i];}
retval["data"] = tmpString;
// INFO_MSG("Setting bpos for packet on track %llu, time %llu, to %llu", tid, time, bytepos);
// INFO_MSG("Setting bpos for packet on track %llu, time %llu, to %llu", tid, time, bytepos);
retval["bpos"] = bytepos;
if (myCodec == OGG::THEORA){
if (!theora::isHeader(tmpString.data(), tmpString.size())){
theora::header tmpHeader((char*)tmpString.data(), tmpString.size());
if (tmpHeader.getFTYPE() == 0){
retval["keyframe"] = 1;
}
theora::header tmpHeader((char *)tmpString.data(), tmpString.size());
if (tmpHeader.getFTYPE() == 0){retval["keyframe"] = 1;}
}
}
return retval;
}
/*
unsigned long oggTrack::getBlockSize(unsigned int vModeIndex){ //WTF!!?
return blockSize[vModes[vModeIndex].blockFlag];
/*
unsigned long oggTrack::getBlockSize(unsigned int vModeIndex){//WTF!!?
return blockSize[vModes[vModeIndex].blockFlag];
}
*/
inputOGG::inputOGG(Util::Config * cfg) : Input(cfg){
}
*/
inputOGG::inputOGG(Util::Config *cfg) : Input(cfg){
capa["name"] = "OGG";
capa["desc"] = "This input allows streaming of OGG files as Video on Demand.";
capa["source_match"] = "/*.ogg";
@ -63,23 +59,21 @@ namespace Mist {
}
bool inputOGG::preRun(){
//open File
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile){
return false;
}
if (!inFile){return false;}
return true;
}
///\todo check if all trackID (tid) instances are replaced with bitstream serial numbers
void inputOGG::parseBeginOfStream(OGG::Page & bosPage){
//long long int tid = snum2tid.size() + 1;
void inputOGG::parseBeginOfStream(OGG::Page &bosPage){
// long long int tid = snum2tid.size() + 1;
unsigned int tid = bosPage.getBitstreamSerialNumber();
if (memcmp(bosPage.getSegment(0) + 1, "theora", 6) == 0){
theora::header tmpHead((char*)bosPage.getSegment(0), bosPage.getSegmentLen(0));
theora::header tmpHead((char *)bosPage.getSegment(0), bosPage.getSegmentLen(0));
oggTracks[tid].codec = OGG::THEORA;
oggTracks[tid].msPerFrame = (double)(tmpHead.getFRD() * 1000) / (double)tmpHead.getFRN(); //this should be: 1000/( tmpHead.getFRN()/ tmpHead.getFRD() )
oggTracks[tid].KFGShift = tmpHead.getKFGShift(); //store KFGShift for granule calculations
oggTracks[tid].msPerFrame = (double)(tmpHead.getFRD() * 1000) / (double)tmpHead.getFRN(); // this should be: 1000/( tmpHead.getFRN()/ tmpHead.getFRD() )
oggTracks[tid].KFGShift = tmpHead.getKFGShift(); // store KFGShift for granule calculations
myMeta.tracks[tid].type = "video";
myMeta.tracks[tid].codec = "theora";
myMeta.tracks[tid].trackID = tid;
@ -94,15 +88,15 @@ namespace Mist {
INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str());
}
if (memcmp(bosPage.getSegment(0) + 1, "vorbis", 6) == 0){
vorbis::header tmpHead((char*)bosPage.getSegment(0), bosPage.getSegmentLen(0));
vorbis::header tmpHead((char *)bosPage.getSegment(0), bosPage.getSegmentLen(0));
oggTracks[tid].codec = OGG::VORBIS;
oggTracks[tid].msPerFrame = (double)1000.0f / tmpHead.getAudioSampleRate();
DEBUG_MSG(DLVL_DEVEL, "vorbis trackID: %d msperFrame %f ", tid, oggTracks[tid].msPerFrame);
oggTracks[tid].channels = tmpHead.getAudioChannels();
oggTracks[tid].blockSize[0] = 1 << tmpHead.getBlockSize0();
oggTracks[tid].blockSize[1] = 1 << tmpHead.getBlockSize1();
//Abusing .contBuffer for temporarily storing the idHeader
oggTracks[tid].blockSize[0] = 1 << tmpHead.getBlockSize0();
oggTracks[tid].blockSize[1] = 1 << tmpHead.getBlockSize1();
// Abusing .contBuffer for temporarily storing the idHeader
bosPage.getSegment(0, oggTracks[tid].contBuffer);
myMeta.tracks[tid].type = "audio";
@ -127,52 +121,51 @@ namespace Mist {
bool inputOGG::readHeader(){
OGG::Page myPage;
fseek(inFile, 0, SEEK_SET);
while (myPage.read(inFile)){ //assumes all headers are sent before any data
while (myPage.read(inFile)){// assumes all headers are sent before any data
unsigned int tid = myPage.getBitstreamSerialNumber();
if (myPage.getHeaderType() & OGG::BeginOfStream){
parseBeginOfStream(myPage);
INFO_MSG("Read BeginOfStream for track %d", tid);
continue; //Continue reading next pages
continue; // Continue reading next pages
}
bool readAllHeaders = true;
for (std::map<long unsigned int, OGG::oggTrack>::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){
for (std::map<long unsigned int, OGG::oggTrack>::iterator it = oggTracks.begin();
it != oggTracks.end(); it++){
if (!it->second.parsedHeaders){
readAllHeaders = false;
break;
}
}
if (readAllHeaders){
break;
}
if (readAllHeaders){break;}
// INFO_MSG("tid: %d",tid);
//Parsing headers
// Parsing headers
if (myMeta.tracks[tid].codec == "theora"){
for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){
unsigned long len = myPage.getSegmentLen(i);
theora::header tmpHead((char*)myPage.getSegment(i),len);
if (!tmpHead.isHeader()){ //not copying the header anymore, should this check isHeader?
theora::header tmpHead((char *)myPage.getSegment(i), len);
if (!tmpHead.isHeader()){// not copying the header anymore, should this check isHeader?
DEBUG_MSG(DLVL_FAIL, "Theora Header read failed!");
return false;
}
switch (tmpHead.getHeaderType()){
//Case 0 is being handled by parseBeginOfStream
case 1: {
myMeta.tracks[tid].init += (char)((len >> 8) & 0xFF);
myMeta.tracks[tid].init += (char)(len & 0xFF);
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
break;
}
case 2: {
myMeta.tracks[tid].init += (char)((len >> 8) & 0xFF);
myMeta.tracks[tid].init += (char)(len & 0xFF);
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
oggTracks[tid].lastGran = 0;
oggTracks[tid].parsedHeaders = true;
break;
}
// Case 0 is being handled by parseBeginOfStream
case 1:{
myMeta.tracks[tid].init += (char)((len >> 8) & 0xFF);
myMeta.tracks[tid].init += (char)(len & 0xFF);
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
break;
}
case 2:{
myMeta.tracks[tid].init += (char)((len >> 8) & 0xFF);
myMeta.tracks[tid].init += (char)(len & 0xFF);
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
oggTracks[tid].lastGran = 0;
oggTracks[tid].parsedHeaders = true;
break;
}
}
}
}
@ -180,52 +173,51 @@ namespace Mist {
if (myMeta.tracks[tid].codec == "vorbis"){
for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){
unsigned long len = myPage.getSegmentLen(i);
vorbis::header tmpHead((char*)myPage.getSegment(i), len);
vorbis::header tmpHead((char *)myPage.getSegment(i), len);
if (!tmpHead.isHeader()){
DEBUG_MSG(DLVL_FAIL, "Header read failed!");
return false;
}
switch (tmpHead.getHeaderType()){
//Case 1 is being handled by parseBeginOfStream
case 3: {
//we have the first header stored in contBuffer
myMeta.tracks[tid].init += (char)0x02;
//ID header size
for (unsigned int j = 0; j < (oggTracks[tid].contBuffer.size() / 255); j++){
myMeta.tracks[tid].init += (char)0xFF;
}
myMeta.tracks[tid].init += (char)(oggTracks[tid].contBuffer.size() % 255);
//Comment header size
for (unsigned int j = 0; j < (len / 255); j++){
myMeta.tracks[tid].init += (char)0xFF;
}
myMeta.tracks[tid].init += (char)(len % 255);
myMeta.tracks[tid].init += oggTracks[tid].contBuffer;
oggTracks[tid].contBuffer.clear();
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
break;
}
case 5: {
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
oggTracks[tid].vModes = tmpHead.readModeDeque(oggTracks[tid].channels);
oggTracks[tid].parsedHeaders = true;
break;
}
// Case 1 is being handled by parseBeginOfStream
case 3:{
// we have the first header stored in contBuffer
myMeta.tracks[tid].init += (char)0x02;
// ID header size
for (unsigned int j = 0; j < (oggTracks[tid].contBuffer.size() / 255); j++){
myMeta.tracks[tid].init += (char)0xFF;
}
myMeta.tracks[tid].init += (char)(oggTracks[tid].contBuffer.size() % 255);
// Comment header size
for (unsigned int j = 0; j < (len / 255); j++){
myMeta.tracks[tid].init += (char)0xFF;
}
myMeta.tracks[tid].init += (char)(len % 255);
myMeta.tracks[tid].init += oggTracks[tid].contBuffer;
oggTracks[tid].contBuffer.clear();
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
break;
}
case 5:{
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
oggTracks[tid].vModes = tmpHead.readModeDeque(oggTracks[tid].channels);
oggTracks[tid].parsedHeaders = true;
break;
}
}
}
}
if (myMeta.tracks[tid].codec == "opus"){
oggTracks[tid].parsedHeaders = true;
}
if (myMeta.tracks[tid].codec == "opus"){oggTracks[tid].parsedHeaders = true;}
}
for (std::map<long unsigned int, OGG::oggTrack>::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){
for (std::map<long unsigned int, OGG::oggTrack>::iterator it = oggTracks.begin();
it != oggTracks.end(); it++){
fseek(inFile, 0, SEEK_SET);
INFO_MSG("Finding first data for track %lu", it->first);
position tmp = seekFirstData(it->first);
if (tmp.trackID){
currentPositions.insert(tmp);
} else {
}else{
INFO_MSG("missing track: %lu", it->first);
}
}
@ -252,7 +244,7 @@ namespace Mist {
res.bytepos = ftell(inFile);
readSuccesfull = oggTracks[tid].myPage.read(inFile);
if (!readSuccesfull){
quitloop = true; //break :(
quitloop = true; // break :(
break;
}
if (oggTracks[tid].myPage.getBitstreamSerialNumber() != tid){
@ -264,27 +256,22 @@ namespace Mist {
continue;
}
if (oggTracks[tid].codec == OGG::OPUS){
if (std::string(oggTracks[tid].myPage.getSegment(0), 2) == "Op"){
quitloop = false;
}
if (std::string(oggTracks[tid].myPage.getSegment(0), 2) == "Op"){quitloop = false;}
}
if (oggTracks[tid].codec == OGG::VORBIS){
vorbis::header tmpHead((char*)oggTracks[tid].myPage.getSegment(0), oggTracks[tid].myPage.getSegmentLen(0));
if (tmpHead.isHeader()){
quitloop = false;
}
vorbis::header tmpHead((char *)oggTracks[tid].myPage.getSegment(0),
oggTracks[tid].myPage.getSegmentLen(0));
if (tmpHead.isHeader()){quitloop = false;}
}
if (oggTracks[tid].codec == OGG::THEORA){
theora::header tmpHead((char*)oggTracks[tid].myPage.getSegment(0), oggTracks[tid].myPage.getSegmentLen(0));
if (tmpHead.isHeader()){
quitloop = false;
}
theora::header tmpHead((char *)oggTracks[tid].myPage.getSegment(0),
oggTracks[tid].myPage.getSegmentLen(0));
if (tmpHead.isHeader()){quitloop = false;}
}
}// while ( oggTracks[tid].myPage.getHeaderType() != OGG::Plain && readSuccesfull && oggTracks[tid].myPage.getBitstreamSerialNumber() != tid);
INFO_MSG("seek first bytepos: %llu tid: %llu oggTracks[tid].myPage.getHeaderType(): %d ", res.bytepos, tid, oggTracks[tid].myPage.getHeaderType());
if (!readSuccesfull){
res.trackID = 0;
}
INFO_MSG("seek first bytepos: %llu tid: %llu oggTracks[tid].myPage.getHeaderType(): %d ",
res.bytepos, tid, oggTracks[tid].myPage.getHeaderType());
if (!readSuccesfull){res.trackID = 0;}
return res;
}
@ -304,41 +291,39 @@ namespace Mist {
fseek(inFile, curPos.bytepos, SEEK_SET);
OGG::Page curPage;
curPage.read(inFile);
thisSegment.parts.push_back(std::string(curPage.getSegment(curPos.segmentNo), curPage.getSegmentLen(curPos.segmentNo)));
thisSegment.parts.push_back(
std::string(curPage.getSegment(curPos.segmentNo), curPage.getSegmentLen(curPos.segmentNo)));
bool readFullPacket = false;
if (curPos.segmentNo == curPage.getAllSegments().size() - 1){
OGG::Page tmpPage;
unsigned int bPos;
while (!readFullPacket){
bPos = ftell(inFile);//<-- :(
if (!tmpPage.read(inFile)){
break;
}
if (tmpPage.getBitstreamSerialNumber() != thisSegment.tid){
continue;
}
bPos = ftell(inFile); //<-- :(
if (!tmpPage.read(inFile)){break;}
if (tmpPage.getBitstreamSerialNumber() != thisSegment.tid){continue;}
curPos.bytepos = bPos;
curPos.segmentNo = 0;
if (tmpPage.getHeaderType() == OGG::Continued){
thisSegment.parts.push_back(std::string(tmpPage.getSegment(0), tmpPage.getSegmentLen(0)));
curPos.segmentNo = 1;
if (tmpPage.getAllSegments().size() == 1){
continue;
}
} else {
lastCompleteSegment = true; //if this segment ends on the page, use granule to sync video time
if (tmpPage.getAllSegments().size() == 1){continue;}
}else{
lastCompleteSegment = true; // if this segment ends on the page, use granule to sync video time
}
readFullPacket = true;
}
} else {
}else{
curPos.segmentNo++;
// if (oggTracks[thisSegment.tid].codec == OGG::THEORA && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull) && curPos.segmentNo == curPage.getAllSegments().size() - 1){ //if the next segment is the last one on the page, the (theora) granule should be used to sync the time for the current segment
if ((oggTracks[thisSegment.tid].codec == OGG::THEORA ||oggTracks[thisSegment.tid].codec == OGG::VORBIS)&& curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull) && curPos.segmentNo == curPage.getAllSegments().size() - 1){ //if the next segment is the last one on the page, the (theora) granule should be used to sync the time for the current segment
// if (oggTracks[thisSegment.tid].codec == OGG::THEORA && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull)
// && curPos.segmentNo == curPage.getAllSegments().size() - 1){//if the next segment is the last one on the page, the (theora) granule should be used to sync the time for the current segment
if ((oggTracks[thisSegment.tid].codec == OGG::THEORA || oggTracks[thisSegment.tid].codec == OGG::VORBIS) &&
curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull) &&
curPos.segmentNo == curPage.getAllSegments().size() - 1){// if the next segment is the last one on the page, the (theora) granule should be used to sync the time for the current segment
OGG::Page tmpPage;
while (tmpPage.read(inFile) && tmpPage.getBitstreamSerialNumber() != thisSegment.tid){}
if ((tmpPage.getBitstreamSerialNumber() == thisSegment.tid) && tmpPage.getHeaderType() == OGG::Continued){
lastCompleteSegment = true; //this segment should be used to sync time using granule
lastCompleteSegment = true; // this segment should be used to sync time using granule
}
}
readFullPacket = true;
@ -349,101 +334,101 @@ namespace Mist {
if (oggTracks[thisSegment.tid].codec == OGG::VORBIS){
unsigned long blockSize = 0;
Utils::bitstreamLSBF packet;
packet.append((char*)curPage.getSegment(oldSegNo), curPage.getSegmentLen(oldSegNo));
packet.append((char *)curPage.getSegment(oldSegNo), curPage.getSegmentLen(oldSegNo));
if (!packet.get(1)){
//Read index first
// Read index first
unsigned long vModeIndex = packet.get(vorbis::ilog(oggTracks[thisSegment.tid].vModes.size() - 1));
blockSize= oggTracks[thisSegment.tid].blockSize[oggTracks[thisSegment.tid].vModes[vModeIndex].blockFlag]; //almost readable.
} else {
blockSize =
oggTracks[thisSegment.tid].blockSize[oggTracks[thisSegment.tid].vModes[vModeIndex].blockFlag]; // almost readable.
}else{
DEBUG_MSG(DLVL_WARN, "Packet type != 0");
}
curPos.time += oggTracks[thisSegment.tid].msPerFrame * (blockSize / oggTracks[thisSegment.tid].channels);
} else if (oggTracks[thisSegment.tid].codec == OGG::THEORA){
if (lastCompleteSegment == true && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull)){ //this segment should be used to sync time using granule
long long unsigned int parseGranuleUpper = curPage.getGranulePosition() >> oggTracks[thisSegment.tid].KFGShift ;
long long unsigned int parseGranuleLower(curPage.getGranulePosition() & ((1 << oggTracks[thisSegment.tid].KFGShift) - 1));
thisSegment.time = oggTracks[thisSegment.tid].msPerFrame * (parseGranuleUpper + parseGranuleLower - 1);
curPos.time += oggTracks[thisSegment.tid].msPerFrame * (blockSize / oggTracks[thisSegment.tid].channels);
}else if (oggTracks[thisSegment.tid].codec == OGG::THEORA){
if (lastCompleteSegment == true && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull)){// this segment should be used to sync time using granule
long long unsigned int parseGranuleUpper =
curPage.getGranulePosition() >> oggTracks[thisSegment.tid].KFGShift;
long long unsigned int parseGranuleLower(curPage.getGranulePosition() &
((1 << oggTracks[thisSegment.tid].KFGShift) - 1));
thisSegment.time =
oggTracks[thisSegment.tid].msPerFrame * (parseGranuleUpper + parseGranuleLower - 1);
curPos.time = thisSegment.time;
std::string tmpStr = thisSegment.toJSON(oggTracks[thisSegment.tid].codec).toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
// INFO_MSG("thisTime: %d", thisPacket.getTime());
}
curPos.time += oggTracks[thisSegment.tid].msPerFrame;
} else if (oggTracks[thisSegment.tid].codec == OGG::OPUS){
}else if (oggTracks[thisSegment.tid].codec == OGG::OPUS){
if (thisSegment.parts.size()){
curPos.time += Opus::Opus_getDuration(thisSegment.parts.front().data());
}
}
if (readFullPacket){
currentPositions.insert(curPos);
}
}//getnext()
if (readFullPacket){currentPositions.insert(curPos);}
}// getnext()
long long unsigned int inputOGG::calcGranuleTime(unsigned long tid, long long unsigned int granule){
switch (oggTracks[tid].codec){
case OGG::VORBIS:
return granule * oggTracks[tid].msPerFrame ; //= samples * samples per second
break;
case OGG::OPUS:
return granule / 48; //always 48kHz
break;
case OGG::THEORA:{
long long unsigned int parseGranuleUpper = granule >> oggTracks[tid].KFGShift ;
long long unsigned int parseGranuleLower = (granule & ((1 << oggTracks[tid].KFGShift) - 1));
return (parseGranuleUpper + parseGranuleLower) * oggTracks[tid].msPerFrame ; //= frames * msPerFrame
break;
}
default:
DEBUG_MSG(DLVL_WARN, "Unknown codec, can not calculate time from granule");
break;
case OGG::VORBIS:
return granule * oggTracks[tid].msPerFrame; //= samples * samples per second
break;
case OGG::OPUS:
return granule / 48; // always 48kHz
break;
case OGG::THEORA:{
long long unsigned int parseGranuleUpper = granule >> oggTracks[tid].KFGShift;
long long unsigned int parseGranuleLower = (granule & ((1 << oggTracks[tid].KFGShift) - 1));
return (parseGranuleUpper + parseGranuleLower) * oggTracks[tid].msPerFrame; //= frames * msPerFrame
break;
}
default: DEBUG_MSG(DLVL_WARN, "Unknown codec, can not calculate time from granule"); break;
}
return 0;
}
#ifndef _GNU_SOURCE
void * memrchr(const void *s, int c, size_t n){
const unsigned char *cp;
if (n != 0) {
cp = (unsigned char *)s + n;
do {
if (*(--cp) == (unsigned char)c)
return((void *)cp);
} while (--n != 0);
}
return((void *)0);
}
#endif
#ifndef _GNU_SOURCE
void *memrchr(const void *s, int c, size_t n){
const unsigned char *cp;
if (n != 0){
cp = (unsigned char *)s + n;
do{
if (*(--cp) == (unsigned char)c) return ((void *)cp);
}while (--n != 0);
}
return ((void *)0);
}
#endif
void inputOGG::seek(int seekTime){
currentPositions.clear();
DEBUG_MSG(DLVL_MEDIUM, "Seeking to %dms", seekTime);
//for every track
// for every track
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
//find first keyframe before keyframe with ms > seektime
// find first keyframe before keyframe with ms > seektime
position tmpPos;
tmpPos.trackID = *it;
tmpPos.time = myMeta.tracks[*it].keys.begin()->getTime();
tmpPos.bytepos = myMeta.tracks[*it].keys.begin()->getBpos();
for (std::deque<DTSC::Key>::iterator ot = myMeta.tracks[*it].keys.begin(); ot != myMeta.tracks[*it].keys.end(); ot++){
for (std::deque<DTSC::Key>::iterator ot = myMeta.tracks[*it].keys.begin();
ot != myMeta.tracks[*it].keys.end(); ot++){
if (ot->getTime() > seekTime){
break;
} else {
}else{
tmpPos.time = ot->getTime();
tmpPos.bytepos = ot->getBpos();
}
}
INFO_MSG("Found %dms for track %lu at %llu bytepos %llu", seekTime, *it, tmpPos.time, tmpPos.bytepos);
int backChrs=std::min((uint64_t)280, tmpPos.bytepos - 1);
int backChrs = std::min((uint64_t)280, tmpPos.bytepos - 1);
fseek(inFile, tmpPos.bytepos - backChrs, SEEK_SET);
char buffer[300];
fread(buffer, 300, 1, inFile);
char * loc = buffer + backChrs + 2; //start at tmpPos.bytepos+2
char *loc = buffer + backChrs + 2; // start at tmpPos.bytepos+2
while (loc && !(loc[0] == 'O' && loc[1] == 'g' && loc[2] == 'g' && loc[3] == 'S')){
loc = (char *)memrchr(buffer, 'O', (loc-buffer) -1 );//seek reverse
loc = (char *)memrchr(buffer, 'O', (loc - buffer) - 1); // seek reverse
}
if (!loc){
INFO_MSG("Unable to find a page boundary starting @ %llu, track %lu", tmpPos.bytepos, *it);
INFO_MSG("Unable to find a page boundary starting @ %llu, track %lu", tmpPos.bytepos, *it);
continue;
}
tmpPos.segmentNo = backChrs - (loc - buffer);
@ -462,17 +447,9 @@ namespace Mist {
selectedTracks.insert(atoll(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
}
}
}// namespace Mist

View file

@ -2,36 +2,34 @@
#include <mist/dtsc.h>
#include <mist/ogg.h>
namespace Mist {
namespace Mist{
struct segPart {
char * segData;
struct segPart{
char *segData;
unsigned int len;
};
class segment {
public:
segment() : time(0), tid(0), bytepos(0), keyframe(0){}
bool operator < (const segment & rhs) const {
return time < rhs.time || (time == rhs.time && tid < rhs.tid);
}
std::vector<std::string> parts;
uint64_t time;
uint64_t tid;
uint64_t bytepos;
bool keyframe;
JSON::Value toJSON(OGG::oggCodec myCodec);
class segment{
public:
segment() : time(0), tid(0), bytepos(0), keyframe(0){}
bool operator<(const segment &rhs) const{
return time < rhs.time || (time == rhs.time && tid < rhs.tid);
}
std::vector<std::string> parts;
uint64_t time;
uint64_t tid;
uint64_t bytepos;
bool keyframe;
JSON::Value toJSON(OGG::oggCodec myCodec);
};
struct position {
bool operator < (const position & rhs) const {
struct position{
bool operator<(const position &rhs) const{
if (time < rhs.time){
return true;
} else {
}else{
if (time == rhs.time){
if (trackID < rhs.trackID){
return true;
}
if (trackID < rhs.trackID){return true;}
}
}
return false;
@ -41,54 +39,52 @@ namespace Mist {
uint64_t bytepos;
uint64_t segmentNo;
};
/*
class oggTrack {
public:
oggTrack() : lastTime(0), parsedHeaders(false), lastPageOffset(0), nxtSegment(0){ }
codecType codec;
std::string contBuffer;//buffer for continuing pages
segment myBuffer;
double lastTime;
long long unsigned int lastGran;
bool parsedHeaders;
double msPerFrame;
long long unsigned int lastPageOffset;
OGG::Page myPage;
unsigned int nxtSegment;
//Codec specific elements
//theora
theora::header idHeader;
//vorbis
std::deque<vorbis::mode> vModes;
char channels;
unsigned long blockSize[2];
unsigned long getBlockSize(unsigned int vModeIndex);
};*/
/*
class oggTrack{
public:
oggTrack() : lastTime(0), parsedHeaders(false), lastPageOffset(0), nxtSegment(0){}
codecType codec;
std::string contBuffer;//buffer for continuing pages
segment myBuffer;
double lastTime;
long long unsigned int lastGran;
bool parsedHeaders;
double msPerFrame;
long long unsigned int lastPageOffset;
OGG::Page myPage;
unsigned int nxtSegment;
//Codec specific elements
//theora
theora::header idHeader;
//vorbis
std::deque<vorbis::mode> vModes;
char channels;
unsigned long blockSize[2];
unsigned long getBlockSize(unsigned int vModeIndex);
};*/
class inputOGG : public Input {
public:
inputOGG(Util::Config * cfg);
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
position seekFirstData(long long unsigned int tid);
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
class inputOGG : public Input{
public:
inputOGG(Util::Config *cfg);
void parseBeginOfStream(OGG::Page & bosPage);
std::set<position> currentPositions;
FILE * inFile;
std::map<long unsigned int, OGG::oggTrack> oggTracks;//this remembers all metadata for every track
std::set<segment> sortedSegments;//probably not needing this
long long unsigned int calcGranuleTime(unsigned long tid, long long unsigned int granule);
long long unsigned int calcSegmentDuration(unsigned long tid , std::string & segment);
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
position seekFirstData(long long unsigned int tid);
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void parseBeginOfStream(OGG::Page &bosPage);
std::set<position> currentPositions;
FILE *inFile;
std::map<long unsigned int, OGG::oggTrack> oggTracks; // this remembers all metadata for every track
std::set<segment> sortedSegments; // probably not needing this
long long unsigned int calcGranuleTime(unsigned long tid, long long unsigned int granule);
long long unsigned int calcSegmentDuration(unsigned long tid, std::string &segment);
};
}
}// namespace Mist
typedef Mist::inputOGG mistIn;

View file

@ -1,14 +1,14 @@
#include "input_playlist.h"
#include <algorithm>
#include <mist/stream.h>
#include <mist/procs.h>
#include <mist/stream.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
namespace Mist {
inputPlaylist::inputPlaylist(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputPlaylist::inputPlaylist(Util::Config *cfg) : Input(cfg){
capa["name"] = "Playlist";
capa["desc"] = "Enables Playlist Input";
capa["source_match"] = "*.pls";
@ -17,21 +17,21 @@ namespace Mist {
capa["priority"] = 9;
capa["hardcoded"]["resume"] = 1;
playlistIndex = 0xFFFFFFFEull;//Not FFFFFFFF on purpose!
playlistIndex = 0xFFFFFFFEull; // Not FFFFFFFF on purpose!
}
bool inputPlaylist::checkArguments(){
if (config->getString("input") == "-") {
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output not supported" << std::endl;
return false;
}
@ -43,48 +43,47 @@ namespace Mist {
bool seenValidEntry = true;
uint64_t startTime = Util::bootMS();
while (config->is_active && nProxy.userClient.isAlive()){
struct tm * wTime;
struct tm *wTime;
time_t nowTime = time(0);
wTime = localtime(&nowTime);
wallTime = wTime->tm_hour*60+wTime->tm_min;
wallTime = wTime->tm_hour * 60 + wTime->tm_min;
nProxy.userClient.keepAlive();
reloadPlaylist();
if (!playlist.size()){
return "No entries in playlist";
}
if (!playlist.size()){return "No entries in playlist";}
++playlistIndex;
if (playlistIndex >= playlist.size()){
if (!seenValidEntry){
HIGH_MSG("Parsed entire playlist without seeing a valid entry, waiting for any entry to become available");
HIGH_MSG("Parsed entire playlist without seeing a valid entry, waiting for any entry to "
"become available");
Util::sleep(1000);
}
playlistIndex = 0;
seenValidEntry = false;
}
if (minIndex != std::string::npos && playlistIndex < minIndex){
INFO_MSG("Clipping playlist index from %zu to %zu to stay within playback timing schedule", playlistIndex, minIndex);
INFO_MSG("Clipping playlist index from %zu to %zu to stay within playback timing schedule",
playlistIndex, minIndex);
playlistIndex = minIndex;
}
if (maxIndex != std::string::npos && playlistIndex > maxIndex){
INFO_MSG("Clipping playlist index from %zu to %zu to stay within playback timing schedule", playlistIndex, maxIndex);
INFO_MSG("Clipping playlist index from %zu to %zu to stay within playback timing schedule",
playlistIndex, maxIndex);
playlistIndex = maxIndex;
}
currentSource = playlist.at(playlistIndex);
std::map<std::string, std::string> overrides;
overrides["realtime"] = "1";
overrides["alwaysStart"] = "";//Just making this value "available" is enough
overrides["alwaysStart"] = ""; // Just making this value "available" is enough
overrides["simulated-starttime"] = JSON::Value(startTime).asString();
std::string srcPath = config->getString("input");
if ((currentSource.size() && currentSource[0] == '/') || srcPath.rfind('/') == std::string::npos){
srcPath = currentSource;
} else {
}else{
srcPath = srcPath.substr(0, srcPath.rfind("/") + 1) + currentSource;
}
char * workingDir = getcwd(NULL, 0);
if (srcPath[0] != '/'){
srcPath = std::string(workingDir) + "/" + srcPath;
}
char *workingDir = getcwd(NULL, 0);
if (srcPath[0] != '/'){srcPath = std::string(workingDir) + "/" + srcPath;}
free(workingDir);
Util::streamVariables(srcPath, streamName, "");
@ -98,7 +97,7 @@ namespace Mist {
continue;
}
pid_t spawn_pid = 0;
//manually override stream url to start the correct input
// manually override stream url to start the correct input
if (!Util::startInput(streamName, srcPath, true, true, overrides, &spawn_pid)){
FAIL_MSG("Could not start input for source %s", srcPath.c_str());
continue;
@ -109,31 +108,29 @@ namespace Mist {
if (reloadOn != 0xFFFF){
time_t nowTime = time(0);
wTime = localtime(&nowTime);
wallTime = wTime->tm_hour*60+wTime->tm_min;
if (wallTime >= reloadOn){
reloadPlaylist();
}
if ((minIndex != std::string::npos && playlistIndex < minIndex) || (maxIndex != std::string::npos && playlistIndex > maxIndex)){
INFO_MSG("Killing current playback to stay within min/max playlist entry for current time of day");
wallTime = wTime->tm_hour * 60 + wTime->tm_min;
if (wallTime >= reloadOn){reloadPlaylist();}
if ((minIndex != std::string::npos && playlistIndex < minIndex) ||
(maxIndex != std::string::npos && playlistIndex > maxIndex)){
INFO_MSG("Killing current playback to stay within min/max playlist entry for current "
"time of day");
Util::Procs::Stop(spawn_pid);
}
}
nProxy.userClient.keepAlive();
}
if (!config->is_active && Util::Procs::isRunning(spawn_pid)){
Util::Procs::Stop(spawn_pid);
}
if (!config->is_active && Util::Procs::isRunning(spawn_pid)){Util::Procs::Stop(spawn_pid);}
}
if (!config->is_active){return "received deactivate signal";}
if (!nProxy.userClient.isAlive()){return "buffer shutdown";}
return "Unknown";
}
void inputPlaylist::reloadPlaylist(){
minIndex = std::string::npos;
maxIndex = std::string::npos;
std::string playlistFile;
char * origSource = getenv("MIST_ORIGINAL_SOURCE");
char *origSource = getenv("MIST_ORIGINAL_SOURCE");
if (origSource){
playlistFile = origSource;
}else{
@ -157,9 +154,9 @@ namespace Mist {
playlist.push_back(line);
playlist_startTime.push_back(plsStartTime);
if (plsStartTime != 0xFFFF){
//If the newest entry has a time under the current time, we know we should never play earlier than this
// If the newest entry has a time under the current time, we know we should never play earlier than this
if (plsStartTime <= wallTime){minIndex = playlist.size() - 1;}
//If the newest entry has a time above the current time, we know we should never play it
// If the newest entry has a time above the current time, we know we should never play it
if (plsStartTime > wallTime && maxIndex == std::string::npos){
maxIndex = playlist.size() - 2;
reloadOn = plsStartTime;
@ -170,12 +167,13 @@ namespace Mist {
}else{
if (line.size() > 13 && line.at(0) == '#' && line.substr(0, 13) == "#X-STARTTIME:"){
int hour, min;
if (sscanf(line.c_str()+13, "%d:%d", &hour, &min) == 2){plsStartTime = hour*60+min;}
if (sscanf(line.c_str() + 13, "%d:%d", &hour, &min) == 2){
plsStartTime = hour * 60 + min;
}
}
}
}
inFile.close();
}
}
}// namespace Mist

View file

@ -1,30 +1,31 @@
#include "input.h"
#include <mist/dtsc.h>
#include <deque>
#include <mist/dtsc.h>
namespace Mist {
class inputPlaylist : public Input {
public:
inputPlaylist(Util::Config * cfg);
bool needsLock(){return false;}
protected:
bool checkArguments();
bool readHeader() { return true; }
virtual void parseStreamHeader() {myMeta.tracks[1].codec = "PLACEHOLDER";}
std::string streamMainLoop();
virtual bool needHeader(){return false;}
private:
void reloadPlaylist();
std::deque<std::string> playlist;
std::deque<uint16_t> playlist_startTime;
std::string currentSource;
size_t playlistIndex;
size_t minIndex, maxIndex;
bool seenValidEntry;
uint32_t wallTime;
uint32_t reloadOn;
namespace Mist{
class inputPlaylist : public Input{
public:
inputPlaylist(Util::Config *cfg);
bool needsLock(){return false;}
protected:
bool checkArguments();
bool readHeader(){return true;}
virtual void parseStreamHeader(){myMeta.tracks[1].codec = "PLACEHOLDER";}
std::string streamMainLoop();
virtual bool needHeader(){return false;}
private:
void reloadPlaylist();
std::deque<std::string> playlist;
std::deque<uint16_t> playlist_startTime;
std::string currentSource;
size_t playlistIndex;
size_t minIndex, maxIndex;
bool seenValidEntry;
uint32_t wallTime;
uint32_t reloadOn;
};
}
}// namespace Mist
typedef Mist::inputPlaylist mistIn;

49
src/input/input_rtsp.cpp Executable file → Normal file
View file

@ -3,8 +3,12 @@
Mist::InputRTSP *classPointer = 0;
Socket::Connection *mainConn = 0;
void incomingPacket(const DTSC::Packet &pkt){classPointer->incoming(pkt);}
void insertRTP(const uint64_t track, const RTP::Packet &p){classPointer->incomingRTP(track, p);}
void incomingPacket(const DTSC::Packet &pkt){
classPointer->incoming(pkt);
}
void insertRTP(const uint64_t track, const RTP::Packet &p){
classPointer->incomingRTP(track, p);
}
/// Function used to send RTP packets over UDP
///\param socket A UDP Connection pointer, sent as a void*, to keep portability.
@ -17,7 +21,9 @@ void sendUDP(void *socket, char *data, unsigned int len, unsigned int channel){
}
namespace Mist{
void InputRTSP::incomingRTP(const uint64_t track, const RTP::Packet &p){sdpState.handleIncomingRTP(track, p);}
void InputRTSP::incomingRTP(const uint64_t track, const RTP::Packet &p){
sdpState.handleIncomingRTP(track, p);
}
InputRTSP::InputRTSP(Util::Config *cfg) : Input(cfg){
needAuth = false;
@ -79,10 +85,8 @@ namespace Mist{
capa["optional"]["transport"]["default"] = "TCP";
}
void InputRTSP::sendCommand(const std::string &cmd, const std::string &cUrl,
const std::string &body,
const std::map<std::string, std::string> *extraHeaders,
bool reAuth){
void InputRTSP::sendCommand(const std::string &cmd, const std::string &cUrl, const std::string &body,
const std::map<std::string, std::string> *extraHeaders, bool reAuth){
++cSeq;
sndH.Clean();
sndH.protocol = "RTSP/1.0";
@ -107,9 +111,7 @@ namespace Mist{
if (reAuth && needAuth && authRequest.size() && (username.size() || password.size()) && tcpCon){
INFO_MSG("Authenticating %s...", cmd.c_str());
sendCommand(cmd, cUrl, body, extraHeaders, false);
if (needAuth){
FAIL_MSG("Authentication failed! Are the provided credentials correct?");
}
if (needAuth){FAIL_MSG("Authentication failed! Are the provided credentials correct?");}
}
}
@ -156,16 +158,17 @@ namespace Mist{
transportSet = false;
extraHeaders.clear();
extraHeaders["Transport"] = it->second.generateTransport(it->first, url.host, TCPmode);
sendCommand("SETUP", HTTP::URL(url.getUrl()+"/").link(it->second.control).getUrl(), "", &extraHeaders);
sendCommand("SETUP", HTTP::URL(url.getUrl() + "/").link(it->second.control).getUrl(), "", &extraHeaders);
if (tcpCon && transportSet){
atLeastOne = true;
continue;
}
if (!atLeastOne && tcpCon){
INFO_MSG("Failed to set up transport for track %s, switching transports...", myMeta.tracks[it->first].getIdentifier().c_str());
INFO_MSG("Failed to set up transport for track %s, switching transports...",
myMeta.tracks[it->first].getIdentifier().c_str());
TCPmode = !TCPmode;
extraHeaders["Transport"] = it->second.generateTransport(it->first, url.host, TCPmode);
sendCommand("SETUP", HTTP::URL(url.getUrl()+"/").link(it->second.control).getUrl(), "", &extraHeaders);
sendCommand("SETUP", HTTP::URL(url.getUrl() + "/").link(it->second.control).getUrl(), "", &extraHeaders);
}
if (tcpCon && transportSet){
atLeastOne = true;
@ -265,8 +268,10 @@ namespace Mist{
if (recH.hasHeader("Content-Location")){
url = HTTP::URL(recH.GetHeader("Content-Location"));
}
if (recH.hasHeader("Content-Base") && recH.GetHeader("Content-Base") != "" && recH.GetHeader("Content-Base") != url.getUrl()){
INFO_MSG("Changing base URL from %s to %s", url.getUrl().c_str(), recH.GetHeader("Content-Base").c_str());
if (recH.hasHeader("Content-Base") && recH.GetHeader("Content-Base") != "" &&
recH.GetHeader("Content-Base") != url.getUrl()){
INFO_MSG("Changing base URL from %s to %s", url.getUrl().c_str(),
recH.GetHeader("Content-Base").c_str());
url = HTTP::URL(recH.GetHeader("Content-Base"));
}
if (recH.hasHeader("Session")){
@ -276,8 +281,9 @@ namespace Mist{
}
}
if ((recH.hasHeader("Content-Type") &&
recH.GetHeader("Content-Type") == "application/sdp") || (recH.hasHeader("Content-type") &&
recH.GetHeader("Content-type") == "application/sdp")){
recH.GetHeader("Content-Type") == "application/sdp") ||
(recH.hasHeader("Content-type") &&
recH.GetHeader("Content-type") == "application/sdp")){
INFO_MSG("Received SDP");
seenSDP = true;
sdpState.parseSDP(recH.body);
@ -308,8 +314,8 @@ namespace Mist{
return true;
}
//DO NOT Print anything possibly interesting to cerr
//std::cerr << recH.BuildRequest() << std::endl;
// DO NOT Print anything possibly interesting to cerr
// std::cerr << recH.BuildRequest() << std::endl;
recH.Clean();
return true;
}
@ -371,9 +377,7 @@ namespace Mist{
//}
tcpCon.addDown(s.data_len);
RTP::Packet pack(s.data, s.data_len);
if (!it->second.theirSSRC){
it->second.theirSSRC = pack.getSSRC();
}
if (!it->second.theirSSRC){it->second.theirSSRC = pack.getSSRC();}
it->second.sorter.addPacket(pack);
}
if (Util::epoch() / 5 != it->second.rtcpSent){
@ -387,4 +391,3 @@ namespace Mist{
void InputRTSP::incoming(const DTSC::Packet &pkt){nProxy.bufferLivePacket(pkt, myMeta);}
}// namespace Mist

3
src/input/input_rtsp.h Executable file → Normal file
View file

@ -28,7 +28,7 @@ namespace Mist{
void parseStreamHeader();
void seek(int seekTime){}
void sendCommand(const std::string &cmd, const std::string &cUrl, const std::string &body,
const std::map<std::string, std::string> *extraHeaders = 0, bool reAuth=true);
const std::map<std::string, std::string> *extraHeaders = 0, bool reAuth = true);
bool parsePacket(bool mustHave = false);
bool handleUDP();
std::string streamMainLoop();
@ -49,4 +49,3 @@ namespace Mist{
}// namespace Mist
typedef Mist::InputRTSP mistIn;

View file

@ -5,7 +5,8 @@ namespace Mist{
InputSrt::InputSrt(Util::Config *cfg) : Input(cfg){
vtt = false;
capa["name"] = "SRT";
capa["decs"] = "This input allows streaming of SRT and WebVTT subtitle files as Video on Demand.";
capa["decs"] =
"This input allows streaming of SRT and WebVTT subtitle files as Video on Demand.";
capa["source_match"].append("/*.srt");
capa["source_match"].append("/*.vtt");
capa["priority"] = 9;
@ -120,10 +121,8 @@ namespace Mist{
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;
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");}
@ -143,4 +142,3 @@ namespace Mist{
}
}// namespace Mist

View file

@ -4,31 +4,26 @@
#include <mist/dtsc.h>
#include <string>
namespace Mist{
class InputSrt : public Input{
public:
InputSrt(Util::Config *cfg);
public:
InputSrt(Util::Config *cfg);
protected:
std::ifstream fileSource;
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;
FILE * inFile;
bool checkArguments();
bool readHeader();
bool preRun();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool vtt;
FILE *inFile;
};
}
}// namespace Mist
typedef Mist::InputSrt mistIn;

View file

@ -1,48 +1,46 @@
#include <mist/util.h>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/flv_tag.h>
#include <mist/defines.h>
#include <mist/ts_packet.h>
#include <mist/timing.h>
#include <mist/mp4_generic.h>
#include <mist/http_parser.h>
#include <mist/downloader.h>
#include "input_ts.h"
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <mist/defines.h>
#include <mist/downloader.h>
#include <mist/flv_tag.h>
#include <mist/http_parser.h>
#include <mist/mp4_generic.h>
#include <mist/stream.h>
#include <mist/timing.h>
#include <mist/ts_packet.h>
#include <mist/util.h>
#include <string>
#include <mist/tinythread.h>
#include <mist/procs.h>
#include <mist/tinythread.h>
#include <sys/stat.h>
tthread::mutex threadClaimMutex;
std::string globalStreamName;
TS::Stream liveStream(true);
Util::Config * cfgPointer = NULL;
Util::Config *cfgPointer = NULL;
#define THREAD_TIMEOUT 15
std::map<unsigned long long, unsigned long long> threadTimer;
std::set<unsigned long> claimableThreads;
void parseThread(void * ignored) {
void parseThread(void *ignored){
int tid = -1;
{
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
if (claimableThreads.size()) {
if (claimableThreads.size()){
tid = *claimableThreads.begin();
claimableThreads.erase(claimableThreads.begin());
}
if (tid == -1) {
return;
}
if (tid == -1){return;}
}
Mist::negotiationProxy myProxy;
@ -50,7 +48,8 @@ void parseThread(void * ignored) {
DTSC::Meta myMeta;
if (liveStream.isDataTrack(tid)){
if (!Util::streamAlive(globalStreamName) && !Util::startInput(globalStreamName, "push://INTERNAL_ONLY:"+cfgPointer->getString("input"), true, true)) {
if (!Util::streamAlive(globalStreamName) &&
!Util::startInput(globalStreamName, "push://INTERNAL_ONLY:" + cfgPointer->getString("input"), true, true)){
FAIL_MSG("Could not start buffer for %s", globalStreamName.c_str());
return;
}
@ -62,21 +61,21 @@ void parseThread(void * ignored) {
}
threadTimer[tid] = Util::bootSecs();
while (Util::bootSecs() - threadTimer[tid] < THREAD_TIMEOUT && cfgPointer->is_active && (!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive())) {
while (Util::bootSecs() - threadTimer[tid] < THREAD_TIMEOUT && cfgPointer->is_active &&
(!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive())){
{
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
threadTimer[tid] = Util::bootSecs();
}
if (liveStream.isDataTrack(tid)){
myProxy.userClient.keepAlive();
}
if (liveStream.isDataTrack(tid)){myProxy.userClient.keepAlive();}
liveStream.parse(tid);
if (!liveStream.hasPacket(tid)){
Util::sleep(100);
continue;
}
uint64_t startSecs = Util::bootSecs();
while (liveStream.hasPacket(tid) && ((Util::bootSecs() < startSecs + 2) && cfgPointer->is_active && (!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive()))){
while (liveStream.hasPacket(tid) && ((Util::bootSecs() < startSecs + 2) && cfgPointer->is_active &&
(!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive()))){
liveStream.initializeMetadata(myMeta, tid);
DTSC::Packet pack;
liveStream.getPacket(tid, pack);
@ -106,13 +105,16 @@ void parseThread(void * ignored) {
myProxy.userClient.finish();
}
namespace Mist {
namespace Mist{
/// Constructor of TS Input
/// \arg cfg Util::Config that contains all current configurations.
inputTS::inputTS(Util::Config * cfg) : Input(cfg) {
inputTS::inputTS(Util::Config *cfg) : Input(cfg){
capa["name"] = "TS";
capa["desc"] = "This input allows you to stream MPEG2-TS data from static files (/*.ts), streamed files or named pipes (stream://*.ts), streamed over HTTP (http(s)://*.ts, http(s)-ts://*), standard input (ts-exec:*), or multicast/unicast UDP sockets (tsudp://*).";
capa["desc"] =
"This input allows you to stream MPEG2-TS data from static files (/*.ts), streamed files "
"or named pipes (stream://*.ts), streamed over HTTP (http(s)://*.ts, http(s)-ts://*), "
"standard input (ts-exec:*), or multicast/unicast UDP sockets (tsudp://*).";
capa["source_match"].append("/*.ts");
capa["source_file"] = "$source";
capa["source_match"].append("stream://*.ts");
@ -122,7 +124,7 @@ namespace Mist {
capa["source_match"].append("http-ts://*");
capa["source_match"].append("https://*.ts");
capa["source_match"].append("https-ts://*");
//These can/may be set to always-on mode
// These can/may be set to always-on mode
capa["always_match"].append("stream://*.ts");
capa["always_match"].append("tsudp://*");
capa["always_match"].append("ts-exec:*");
@ -145,14 +147,16 @@ namespace Mist {
{
int fin = 0, fout = 0, ferr = 0;
pid_t srt_tx = -1;
const char *args[] = {"srt-live-transmit", 0};
const char *args[] ={"srt-live-transmit", 0};
srt_tx = Util::Procs::StartPiped(args, 0, 0, 0);
if (srt_tx > 1){
capa["source_match"].append("srt://*");
capa["always_match"].append("srt://*");
capa["desc"] = capa["desc"].asStringRef() + " SRT support (srt://*) is installed and available.";
capa["desc"] =
capa["desc"].asStringRef() + " SRT support (srt://*) is installed and available.";
}else{
capa["desc"] = capa["desc"].asStringRef() + " To enable SRT support, please install the srt-live-transmit binary.";
capa["desc"] = capa["desc"].asStringRef() +
" To enable SRT support, please install the srt-live-transmit binary.";
}
}
@ -164,19 +168,18 @@ namespace Mist {
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"]["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;
}
inputTS::~inputTS() {
if (inFile) {
fclose(inFile);
}
if (tcpCon){
tcpCon.close();
}
inputTS::~inputTS(){
if (inFile){fclose(inFile);}
if (tcpCon){tcpCon.close();}
if (!standAlone){
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
threadTimer.clear();
@ -184,46 +187,46 @@ namespace Mist {
}
}
bool inputTS::checkArguments(){
if (config->getString("input").substr(0, 6) == "srt://"){
std::string source = config->getString("input");
HTTP::URL srtUrl(source);
config->getOption("input", true).append("ts-exec:srt-live-transmit "+srtUrl.getUrl()+" file://con");
config->getOption("input", true).append("ts-exec:srt-live-transmit " + srtUrl.getUrl() + " file://con");
INFO_MSG("Rewriting SRT source '%s' to '%s'", source.c_str(), config->getString("input").c_str());
}
return true;
}
///Live Setup of TS Input
bool inputTS::preRun() {
//streamed standard input
if (config->getString("input") == "-") {
/// Live Setup of TS Input
bool inputTS::preRun(){
// streamed standard input
if (config->getString("input") == "-"){
standAlone = false;
tcpCon.open(fileno(stdout), fileno(stdin));
return true;
}
if (config->getString("input").substr(0, 7) == "http://" || config->getString("input").substr(0, 10) == "http-ts://" || config->getString("input").substr(0, 8) == "https://" || config->getString("input").substr(0, 11) == "https-ts://"){
if (config->getString("input").substr(0, 7) == "http://" ||
config->getString("input").substr(0, 10) == "http-ts://" ||
config->getString("input").substr(0, 8) == "https://" ||
config->getString("input").substr(0, 11) == "https-ts://"){
standAlone = false;
HTTP::URL url(config->getString("input"));
if (url.protocol == "http-ts"){url.protocol = "http";}
if (url.protocol == "https-ts"){url.protocol = "https";}
HTTP::Downloader DL;
DL.getHTTP().headerOnly = true;
if (!DL.get(url)){
return false;
}
if (!DL.get(url)){return false;}
tcpCon = DL.getSocket();
DL.getSocket().drop();//Prevent shutdown of connection, keeping copy of socket open
DL.getSocket().drop(); // Prevent shutdown of connection, keeping copy of socket open
return true;
}
if (config->getString("input").substr(0, 8) == "ts-exec:") {
if (config->getString("input").substr(0, 8) == "ts-exec:"){
standAlone = false;
std::string input = config->getString("input").substr(8);
char *args[128];
uint8_t argCnt = 0;
char *startCh = 0;
for (char *i = (char*)input.c_str(); i <= input.data() + input.size(); ++i){
for (char *i = (char *)input.c_str(); i <= input.data() + input.size(); ++i){
if (!*i){
if (startCh){args[argCnt++] = startCh;}
break;
@ -245,70 +248,67 @@ namespace Mist {
tcpCon.open(-1, fout);
return true;
}
//streamed file
if (config->getString("input").substr(0,9) == "stream://"){
inFile = fopen(config->getString("input").c_str()+9, "r");
// streamed file
if (config->getString("input").substr(0, 9) == "stream://"){
inFile = fopen(config->getString("input").c_str() + 9, "r");
tcpCon.open(-1, fileno(inFile));
standAlone = false;
return inFile;
}
//UDP input (tsudp://[host:]port[/iface[,iface[,...]]])
// UDP input (tsudp://[host:]port[/iface[,iface[,...]]])
if (config->getString("input").substr(0, 8) == "tsudp://"){
standAlone = false;
return true;
}
//plain VoD file
// plain VoD file
inFile = fopen(config->getString("input").c_str(), "r");
return inFile;
}
///Track selector of TS Input
/// Track selector of TS Input
///\arg trackSpec specifies which tracks are to be selected
///\todo test whether selecting a subset of tracks work
void inputTS::trackSelect(std::string trackSpec) {
void inputTS::trackSelect(std::string trackSpec){
selectedTracks.clear();
long long int index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
}
bool inputTS::needHeader(){
if (!standAlone){return false;}
return Input::needHeader();
}
///Reads headers from a TS stream, and saves them into metadata
///It works by going through the entire TS stream, and every time
///It encounters a new PES start, it writes the currently found PES data
///for a specific track to metadata. After the entire stream has been read,
///it writes the remaining metadata.
/// Reads headers from a TS stream, and saves them into metadata
/// It works by going through the entire TS stream, and every time
/// It encounters a new PES start, it writes the currently found PES data
/// for a specific track to metadata. After the entire stream has been read,
/// it writes the remaining metadata.
///\todo Find errors, perhaps parts can be made more modular
bool inputTS::readHeader() {
bool inputTS::readHeader(){
if (!inFile){return false;}
TS::Packet packet;//to analyse and extract data
TS::Packet packet; // to analyse and extract data
DTSC::Packet headerPack;
fseek(inFile, 0, SEEK_SET);//seek to beginning
fseek(inFile, 0, SEEK_SET); // seek to beginning
uint64_t lastBpos = 0;
while (packet.FromFile(inFile) && !feof(inFile)) {
while (packet.FromFile(inFile) && !feof(inFile)){
tsStream.parse(packet, lastBpos);
lastBpos = Util::ftell(inFile);
if (packet.getUnitStart()){
while (tsStream.hasPacketOnEachTrack()) {
while (tsStream.hasPacketOnEachTrack()){
tsStream.getEarliestPacket(headerPack);
if (!headerPack){
break;
}
if (!myMeta.tracks.count(headerPack.getTrackId()) || !myMeta.tracks[headerPack.getTrackId()].codec.size()) {
if (!headerPack){break;}
if (!myMeta.tracks.count(headerPack.getTrackId()) ||
!myMeta.tracks[headerPack.getTrackId()].codec.size()){
tsStream.initializeMetadata(myMeta, headerPack.getTrackId());
}
myMeta.update(headerPack);
@ -316,34 +316,38 @@ namespace Mist {
}
}
tsStream.finish();
INFO_MSG("Reached %s at %llu bytes", feof(inFile)?"EOF":"error", lastBpos);
while (tsStream.hasPacket()) {
INFO_MSG("Reached %s at %llu bytes", feof(inFile) ? "EOF" : "error", lastBpos);
while (tsStream.hasPacket()){
tsStream.getEarliestPacket(headerPack);
if (!myMeta.tracks.count(headerPack.getTrackId()) || !myMeta.tracks[headerPack.getTrackId()].codec.size()) {
if (!myMeta.tracks.count(headerPack.getTrackId()) ||
!myMeta.tracks[headerPack.getTrackId()].codec.size()){
tsStream.initializeMetadata(myMeta, headerPack.getTrackId());
}
myMeta.update(headerPack);
}
fseek(inFile, 0, SEEK_SET);
myMeta.toFile(config->getString("input") + ".dtsh");
return true;
}
///Gets the next packet that is to be sent
///At the moment, the logic of sending the last packet that was finished has been implemented,
///but the seeking and finding data is not yet ready.
/// Gets the next packet that is to be sent
/// At the moment, the logic of sending the last packet that was finished has been implemented,
/// but the seeking and finding data is not yet ready.
///\todo Finish the implementation
void inputTS::getNext(bool smart) {
void inputTS::getNext(bool smart){
INSANE_MSG("Getting next");
thisPacket.null();
bool hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacket());
while (!hasPacket && !feof(inFile) && (inputProcess == 0 || Util::Procs::childRunning(inputProcess)) && config->is_active) {
bool hasPacket =
(selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacket());
while (!hasPacket && !feof(inFile) &&
(inputProcess == 0 || Util::Procs::childRunning(inputProcess)) && config->is_active){
tsBuf.FromFile(inFile);
if (selectedTracks.count(tsBuf.getPID())) {
tsStream.parse(tsBuf, 0);//bPos == 0
if (selectedTracks.count(tsBuf.getPID())){
tsStream.parse(tsBuf, 0); // bPos == 0
if (tsBuf.getUnitStart()){
hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacket());
hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin())
: tsStream.hasPacket());
}
}
}
@ -351,73 +355,62 @@ namespace Mist {
tsStream.finish();
hasPacket = true;
}
if (!hasPacket) {
return;
}
if (selectedTracks.size() == 1) {
if (!hasPacket){return;}
if (selectedTracks.size() == 1){
if (tsStream.hasPacket(*selectedTracks.begin())){
tsStream.getPacket(*selectedTracks.begin(), thisPacket);
}
} else {
if (tsStream.hasPacket()){
tsStream.getEarliestPacket(thisPacket);
}
}else{
if (tsStream.hasPacket()){tsStream.getEarliestPacket(thisPacket);}
}
if (!thisPacket){
INFO_MSG("Could not getNext TS packet!");
return;
}
tsStream.initializeMetadata(myMeta);
if (!myMeta.tracks.count(thisPacket.getTrackId())) {
getNext();
}
if (!myMeta.tracks.count(thisPacket.getTrackId())){getNext();}
}
void inputTS::readPMT() {
//save current file position
void inputTS::readPMT(){
// save current file position
uint64_t bpos = Util::ftell(inFile);
if (fseek(inFile, 0, SEEK_SET)) {
if (fseek(inFile, 0, SEEK_SET)){
FAIL_MSG("Seek to 0 failed");
return;
}
TS::Packet tsBuffer;
while (!tsStream.hasPacketOnEachTrack() && tsBuffer.FromFile(inFile)) {
while (!tsStream.hasPacketOnEachTrack() && tsBuffer.FromFile(inFile)){
tsStream.parse(tsBuffer, 0);
}
//Clear leaves the PMT in place
// Clear leaves the PMT in place
tsStream.partialClear();
//Restore original file position
if (Util::fseek(inFile, bpos, SEEK_SET)) {
return;
}
// Restore original file position
if (Util::fseek(inFile, bpos, SEEK_SET)){return;}
}
///Seeks to a specific time
void inputTS::seek(int seekTime) {
/// Seeks to a specific time
void inputTS::seek(int seekTime){
tsStream.clear();
readPMT();
uint64_t seekPos = 0xFFFFFFFFFFFFFFFFull;
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) {
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
unsigned long thisBPos = 0;
for (std::deque<DTSC::Key>::iterator keyIt = myMeta.tracks[*it].keys.begin(); keyIt != myMeta.tracks[*it].keys.end(); keyIt++) {
if (keyIt->getTime() > seekTime) {
break;
}
for (std::deque<DTSC::Key>::iterator keyIt = myMeta.tracks[*it].keys.begin();
keyIt != myMeta.tracks[*it].keys.end(); keyIt++){
if (keyIt->getTime() > seekTime){break;}
thisBPos = keyIt->getBpos();
tsStream.setLastms(*it, keyIt->getTime());
}
if (thisBPos < seekPos) {
seekPos = thisBPos;
}
if (thisBPos < seekPos){seekPos = thisBPos;}
}
Util::fseek(inFile, seekPos, SEEK_SET);//seek to the correct position
Util::fseek(inFile, seekPos, SEEK_SET); // seek to the correct position
}
bool inputTS::openStreamSource(){
const std::string & inpt = config->getString("input");
const std::string &inpt = config->getString("input");
if (inpt.substr(0, 8) == "tsudp://"){
HTTP::URL input_url(inpt);
udpCon.setBlocking(false);
@ -431,12 +424,12 @@ namespace Mist {
}
void inputTS::parseStreamHeader(){
//Placeholder to force normal code to continue despite no tracks available
// Placeholder to force normal code to continue despite no tracks available
myMeta.tracks[0].type = "audio";
}
std::string inputTS::streamMainLoop() {
myMeta.tracks.clear();//wipe the placeholder track from above
std::string inputTS::streamMainLoop(){
myMeta.tracks.clear(); // wipe the placeholder track from above
IPC::sharedClient statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
uint64_t downCounter = 0;
uint64_t startTime = Util::epoch();
@ -446,8 +439,8 @@ namespace Mist {
cfgPointer = config;
globalStreamName = streamName;
unsigned long long threadCheckTimer = Util::bootSecs();
while (config->is_active && nProxy.userClient.isAlive()) {
if (tcpCon) {
while (config->is_active && nProxy.userClient.isAlive()){
if (tcpCon){
if (tcpCon.spool()){
while (tcpCon.Received().available(188)){
while (tcpCon.Received().get()[0] != 0x47 && tcpCon.Received().available(188)){
@ -457,9 +450,7 @@ namespace Mist {
std::string newData = tcpCon.Received().remove(188);
tsBuf.FromPointer(newData.data());
liveStream.add(tsBuf);
if (!liveStream.isDataTrack(tsBuf.getPID())){
liveStream.parse(tsBuf.getPID());
}
if (!liveStream.isDataTrack(tsBuf.getPID())){liveStream.parse(tsBuf.getPID());}
}
}
noDataSince = Util::bootSecs();
@ -470,10 +461,10 @@ namespace Mist {
config->is_active = false;
return "end of streamed input";
}
} else {
}else{
std::string leftData;
bool received = false;
while (udpCon.Receive()) {
while (udpCon.Receive()){
downCounter += udpCon.data_len;
received = true;
if (!gettingData){
@ -481,34 +472,31 @@ namespace Mist {
INFO_MSG("Now receiving UDP data...");
}
int offset = 0;
//Try to read full TS Packets
//Watch out! We push here to a global, in order for threads to be able to access it.
while (offset < udpCon.data_len) {
if (udpCon.data[offset] == 0x47){//check for sync byte
// Try to read full TS Packets
// Watch out! We push here to a global, in order for threads to be able to access it.
while (offset < udpCon.data_len){
if (udpCon.data[offset] == 0x47){// check for sync byte
if (offset + 188 <= udpCon.data_len){
tsBuf.FromPointer(udpCon.data + offset);
liveStream.add(tsBuf);
if (!liveStream.isDataTrack(tsBuf.getPID())){
liveStream.parse(tsBuf.getPID());
}
if (!liveStream.isDataTrack(tsBuf.getPID())){liveStream.parse(tsBuf.getPID());}
leftData.clear();
}else{
leftData.append(udpCon.data + offset, udpCon.data_len - offset);
}
offset += 188;
}else{
uint32_t maxBytes = std::min((uint32_t)(188 - leftData.size()), (uint32_t)(udpCon.data_len - offset));
uint32_t maxBytes =
std::min((uint32_t)(188 - leftData.size()), (uint32_t)(udpCon.data_len - offset));
uint32_t numBytes = maxBytes;
VERYHIGH_MSG("%lu bytes of non-sync-byte data received", numBytes);
if (leftData.size()){
leftData.append(udpCon.data + offset, numBytes);
while (leftData.size() >= 188){
VERYHIGH_MSG("Assembled scrap packet");
tsBuf.FromPointer((char*)leftData.data());
tsBuf.FromPointer((char *)leftData.data());
liveStream.add(tsBuf);
if (!liveStream.isDataTrack(tsBuf.getPID())){
liveStream.parse(tsBuf.getPID());
}
if (!liveStream.isDataTrack(tsBuf.getPID())){liveStream.parse(tsBuf.getPID());}
leftData.erase(0, 188);
}
}
@ -526,9 +514,9 @@ namespace Mist {
gettingData = false;
INFO_MSG("No longer receiving data.");
}
//Check for and spawn threads here.
if (Util::bootSecs() - threadCheckTimer > 1) {
//Connect to stats for INPUT detection
// Check for and spawn threads here.
if (Util::bootSecs() - threadCheckTimer > 1){
// Connect to stats for INPUT detection
uint64_t now = Util::epoch();
if (!statsPage.getData()){
statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
@ -564,21 +552,20 @@ namespace Mist {
hasStarted = false;
}
}
for (std::set<size_t>::iterator it = activeTracks.begin(); it != activeTracks.end(); it++) {
for (std::set<size_t>::iterator it = activeTracks.begin(); it != activeTracks.end(); it++){
if (!liveStream.isDataTrack(*it)){continue;}
if (threadTimer.count(*it) && ((Util::bootSecs() - threadTimer[*it]) > (2 * THREAD_TIMEOUT))) {
WARN_MSG("Thread for track %d timed out %d seconds ago without a clean shutdown.", *it, Util::bootSecs() - threadTimer[*it]);
if (threadTimer.count(*it) && ((Util::bootSecs() - threadTimer[*it]) > (2 * THREAD_TIMEOUT))){
WARN_MSG("Thread for track %d timed out %d seconds ago without a clean shutdown.",
*it, Util::bootSecs() - threadTimer[*it]);
threadTimer.erase(*it);
}
if (!hasStarted){
hasStarted = true;
}
if (!threadTimer.count(*it)) {
if (!hasStarted){hasStarted = true;}
if (!threadTimer.count(*it)){
//Add to list of unclaimed threads
// Add to list of unclaimed threads
claimableThreads.insert(*it);
//Spawn thread here.
// Spawn thread here.
tthread::thread thisThread(parseThread, 0);
thisThread.detach();
}
@ -600,34 +587,34 @@ namespace Mist {
return "received shutdown request";
}
void inputTS::finish() {
void inputTS::finish(){
if (standAlone){
Input::finish();
return;
}
int threadCount = 0;
do {
do{
{
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
threadCount = threadTimer.size();
}
if (threadCount){
Util::sleep(100);
}
} while (threadCount);
if (threadCount){Util::sleep(100);}
}while (threadCount);
}
bool inputTS::needsLock() {
//we already know no lock will be needed
bool inputTS::needsLock(){
// we already know no lock will be needed
if (!standAlone){return false;}
//otherwise, check input param
const std::string & inpt = config->getString("input");
if (inpt.size() && inpt != "-" && inpt.substr(0,9) != "stream://" && inpt.substr(0,8) != "tsudp://" && inpt.substr(0, 8) != "ts-exec:" && inpt.substr(0, 6) != "srt://" && inpt.substr(0, 7) != "http://" && inpt.substr(0, 10) != "http-ts://" && inpt.substr(0, 8) != "https://" && inpt.substr(0, 11) != "https-ts://"){
// otherwise, check input param
const std::string &inpt = config->getString("input");
if (inpt.size() && inpt != "-" && inpt.substr(0, 9) != "stream://" && inpt.substr(0, 8) != "tsudp://" &&
inpt.substr(0, 8) != "ts-exec:" && inpt.substr(0, 6) != "srt://" &&
inpt.substr(0, 7) != "http://" && inpt.substr(0, 10) != "http-ts://" &&
inpt.substr(0, 8) != "https://" && inpt.substr(0, 11) != "https-ts://"){
return Input::needsLock();
}else{
return false;
}
}
}
}// namespace Mist

61
src/input/input_ts.h Executable file → Normal file
View file

@ -1,41 +1,40 @@
#include "input.h"
#include <mist/nal.h>
#include <mist/dtsc.h>
#include <mist/nal.h>
#include <mist/ts_packet.h>
#include <mist/ts_stream.h>
#include <string>
#include <set>
#include <string>
namespace Mist {
namespace Mist{
/// This class contains all functions needed to implement TS Input
class inputTS : public Input {
public:
inputTS(Util::Config * cfg);
~inputTS();
bool needsLock();
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
bool needHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void readPMT();
bool openStreamSource();
void parseStreamHeader();
std::string streamMainLoop();
void finish();
FILE * inFile;///<The input file with ts data
TS::Stream tsStream;///<Used for parsing the incoming ts stream
Socket::UDPConnection udpCon;
Socket::Connection tcpCon;
TS::Packet tsBuf;
pid_t inputProcess;
class inputTS : public Input{
public:
inputTS(Util::Config *cfg);
~inputTS();
bool needsLock();
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
bool needHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void readPMT();
bool openStreamSource();
void parseStreamHeader();
std::string streamMainLoop();
void finish();
FILE *inFile; ///< The input file with ts data
TS::Stream tsStream; ///< Used for parsing the incoming ts stream
Socket::UDPConnection udpCon;
Socket::Connection tcpCon;
TS::Packet tsBuf;
pid_t inputProcess;
};
}
}// namespace Mist
typedef Mist::inputTS mistIn;

View file

@ -1,10 +1,9 @@
#include INPUTTYPE
#include INPUTTYPE
#include <mist/util.h>
int main(int argc, char * argv[]) {
int main(int argc, char *argv[]){
Util::redirectLogsIfNeeded();
Util::Config conf(argv[0]);
mistIn conv(&conf);
return conv.boot(argc, argv);
}