Restyle
This commit is contained in:
parent
5b79f296d6
commit
fccf66fba2
280 changed files with 56975 additions and 71885 deletions
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -27,7 +27,6 @@ namespace Mist{
|
|||
pid_t inputProcess;
|
||||
uint32_t waitsSinceData;
|
||||
};
|
||||
}
|
||||
}// namespace Mist
|
||||
|
||||
typedef Mist::InputH264 mistIn;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 ×tamp, 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
|
||||
|
|
|
@ -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 ×tamp,
|
||||
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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
49
src/input/input_rtsp.cpp
Executable file → Normal 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
3
src/input/input_rtsp.h
Executable file → Normal 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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
61
src/input/input_ts.h
Executable file → Normal 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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue