New Meta commit
This commit is contained in:
parent
fccf66fba2
commit
2b99f2f5ea
183 changed files with 13333 additions and 14421 deletions
1027
src/input/input.cpp
1027
src/input/input.cpp
File diff suppressed because it is too large
Load diff
|
@ -1,9 +1,11 @@
|
|||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <mist/bitfields.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/encryption.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/shared_memory.h>
|
||||
#include <mist/timing.h>
|
||||
|
@ -13,9 +15,9 @@
|
|||
|
||||
namespace Mist{
|
||||
struct booking{
|
||||
int first;
|
||||
int curKey;
|
||||
int curPart;
|
||||
uint32_t first;
|
||||
uint32_t curKey;
|
||||
uint32_t curPart;
|
||||
};
|
||||
|
||||
class Input : public InOutBase{
|
||||
|
@ -26,61 +28,63 @@ namespace Mist{
|
|||
virtual int boot(int argc, char *argv[]);
|
||||
virtual ~Input(){};
|
||||
|
||||
bool keepAlive();
|
||||
void reloadClientMeta();
|
||||
bool hasMeta() const;
|
||||
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 readHeader();
|
||||
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 getNext(size_t idx = INVALID_TRACK_ID){}
|
||||
virtual void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){}
|
||||
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 size_t streamByteCount(){
|
||||
return 0;
|
||||
}; // For live streams: to update the stats with correct values.
|
||||
virtual std::string streamMainLoop();
|
||||
virtual std::string realtimeMainLoop();
|
||||
bool isAlwaysOn();
|
||||
|
||||
virtual void userLeadIn();
|
||||
virtual void userOnActive(size_t id);
|
||||
virtual void userOnDisconnect(size_t id);
|
||||
virtual void userLeadOut();
|
||||
|
||||
virtual void parseHeader();
|
||||
bool bufferFrame(unsigned int track, unsigned int keyNum);
|
||||
bool bufferFrame(size_t track, uint32_t keyNum);
|
||||
|
||||
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;
|
||||
|
||||
bool isBuffer;
|
||||
uint64_t activityCounter;
|
||||
|
||||
JSON::Value capa;
|
||||
|
||||
std::map<int, std::set<int> > keyTimes;
|
||||
int64_t timeOffset;
|
||||
std::map<size_t, std::set<uint64_t> > keyTimes;
|
||||
|
||||
// Create server for user pages
|
||||
IPC::sharedServer userPage;
|
||||
Comms::Users users;
|
||||
size_t connectedUsers;
|
||||
|
||||
Encryption::AES aesCipher;
|
||||
|
||||
IPC::sharedPage streamStatus;
|
||||
|
||||
std::map<unsigned int, std::map<unsigned int, unsigned int> > pageCounter;
|
||||
std::map<size_t, std::map<uint32_t, size_t> > pageCounter;
|
||||
|
||||
static Input *singleton;
|
||||
|
||||
|
@ -93,6 +97,7 @@ namespace Mist{
|
|||
DTSC::Packet srtPack;
|
||||
|
||||
uint64_t simStartTime;
|
||||
};
|
||||
|
||||
void handleBuyDRM();
|
||||
};
|
||||
}// namespace Mist
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace Mist{
|
|||
if (ret != 0){
|
||||
char errstr[300];
|
||||
av_strerror(ret, errstr, 300);
|
||||
DEBUG_MSG(DLVL_FAIL, "Could not open file: %s", errstr);
|
||||
FAIL_MSG("Could not open file: %s", errstr);
|
||||
return false; // Couldn't open file
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ namespace Mist{
|
|||
if (ret < 0){
|
||||
char errstr[300];
|
||||
av_strerror(ret, errstr, 300);
|
||||
DEBUG_MSG(DLVL_FAIL, "Could not find stream info: %s", errstr);
|
||||
FAIL_MSG("Could not find stream info: %s", errstr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -160,12 +160,12 @@ namespace Mist{
|
|||
return true;
|
||||
}
|
||||
|
||||
void inputAV::getNext(bool smart){
|
||||
void inputAV::getNext(){
|
||||
AVPacket packet;
|
||||
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);
|
||||
HIGH_MSG("Track %u not selected", packet.stream_index + 1);
|
||||
continue;
|
||||
}
|
||||
AVStream *strm = pFormatCtx->streams[packet.stream_index];
|
||||
|
@ -187,7 +187,7 @@ namespace Mist{
|
|||
thisPacket.null();
|
||||
preRun();
|
||||
// failure :-(
|
||||
DEBUG_MSG(DLVL_FAIL, "getNext failed");
|
||||
FAIL_MSG("getNext failed");
|
||||
}
|
||||
|
||||
void inputAV::seek(int seekTime){
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Mist{
|
|||
bool checkArguments();
|
||||
bool preRun();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void getNext();
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace Mist{
|
|||
|
||||
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());
|
||||
WARN_MSG("Failed to reach %s on port %" PRIu16, url.host.c_str(), url.getPort());
|
||||
}else{
|
||||
HTTP::Parser http;
|
||||
http.url = "/" + url.path;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,5 @@
|
|||
#include <fstream>
|
||||
|
||||
#include "input.h"
|
||||
#include <fstream>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/shared_memory.h>
|
||||
|
||||
|
@ -12,11 +11,12 @@ namespace Mist{
|
|||
void onCrash();
|
||||
|
||||
private:
|
||||
void fillBufferDetails(JSON::Value &details);
|
||||
unsigned int bufferTime;
|
||||
unsigned int cutTime;
|
||||
unsigned int segmentSize; /*LTS*/
|
||||
unsigned int lastReTime; /*LTS*/
|
||||
void fillBufferDetails(JSON::Value &details) const;
|
||||
uint64_t bufferTime;
|
||||
uint64_t cutTime;
|
||||
size_t segmentSize; /*LTS*/
|
||||
uint64_t lastReTime; /*LTS*/
|
||||
uint64_t finalMillis;
|
||||
bool hasPush;
|
||||
bool resumeMode;
|
||||
IPC::semaphore *liveMeta;
|
||||
|
@ -28,29 +28,30 @@ namespace Mist{
|
|||
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 getNext(size_t idx = INVALID_TRACK_ID){};
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){};
|
||||
|
||||
void removeTrack(size_t tid);
|
||||
|
||||
bool removeKey(size_t 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;
|
||||
|
||||
uint64_t retrieveSetting(DTSC::Scan &streamCfg, const std::string &setting, const std::string &option = "");
|
||||
|
||||
void userLeadIn();
|
||||
void userOnActive(size_t id);
|
||||
void userOnDisconnect(size_t id);
|
||||
void userLeadOut();
|
||||
// This is used for an ugly fix to prevent metadata from disappearing in some cases.
|
||||
std::map<unsigned long, std::string> initData;
|
||||
std::map<size_t, 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
|
||||
|
||||
std::set<size_t> generatePids;
|
||||
std::map<size_t, std::set<size_t> > sourcePids;
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
|
@ -60,10 +60,19 @@ namespace Mist{
|
|||
capa["optional"]["segmentsize"]["type"] = "uint";
|
||||
capa["optional"]["segmentsize"]["default"] = 1900;
|
||||
/*LTS-END*/
|
||||
|
||||
F = NULL;
|
||||
lockCache = false;
|
||||
lockNeeded = false;
|
||||
}
|
||||
|
||||
bool inputDTSC::needsLock(){
|
||||
return config->getString("input").substr(0, 7) != "dtsc://" && config->getString("input") != "-";
|
||||
if (!lockCache){
|
||||
lockNeeded =
|
||||
config->getString("input").substr(0, 7) != "dtsc://" && config->getString("input") != "-";
|
||||
lockCache = true;
|
||||
}
|
||||
return lockNeeded;
|
||||
}
|
||||
|
||||
void parseDTSCURI(const std::string &src, std::string &host, uint16_t &port,
|
||||
|
@ -129,37 +138,40 @@ namespace Mist{
|
|||
void inputDTSC::parseStreamHeader(){
|
||||
while (srcConn.connected() && config->is_active){
|
||||
srcConn.spool();
|
||||
if (srcConn.Received().available(8)){
|
||||
if (srcConn.Received().copy(4) == "DTCM" || srcConn.Received().copy(4) == "DTSC"){
|
||||
// Command message
|
||||
std::string toRec = srcConn.Received().copy(8);
|
||||
unsigned long rSize = Bit::btohl(toRec.c_str() + 4);
|
||||
if (!srcConn.Received().available(8 + rSize)){
|
||||
nProxy.userClient.keepAlive();
|
||||
Util::sleep(100);
|
||||
continue; // abort - not enough data yet
|
||||
}
|
||||
// Ignore initial DTCM message, as this is a "hi" message from the server
|
||||
if (srcConn.Received().copy(4) == "DTCM"){
|
||||
srcConn.Received().remove(8 + rSize);
|
||||
}else{
|
||||
std::string dataPacket = srcConn.Received().remove(8 + rSize);
|
||||
DTSC::Packet metaPack(dataPacket.data(), dataPacket.size());
|
||||
myMeta.reinit(metaPack);
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
|
||||
it != myMeta.tracks.end(); it++){
|
||||
continueNegotiate(it->first, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
INFO_MSG("Received a wrong type of packet - '%s'", srcConn.Received().copy(4).c_str());
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
if (!srcConn.Received().available(8)){
|
||||
Util::sleep(100);
|
||||
nProxy.userClient.keepAlive();
|
||||
keepAlive();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (srcConn.Received().copy(4) != "DTCM" && srcConn.Received().copy(4) != "DTSC"){
|
||||
INFO_MSG("Received a wrong type of packet - '%s'", srcConn.Received().copy(4).c_str());
|
||||
break;
|
||||
}
|
||||
// Command message
|
||||
std::string toRec = srcConn.Received().copy(8);
|
||||
uint32_t rSize = Bit::btohl(toRec.c_str() + 4);
|
||||
if (!srcConn.Received().available(8 + rSize)){
|
||||
keepAlive();
|
||||
Util::sleep(100);
|
||||
continue; // abort - not enough data yet
|
||||
}
|
||||
// Ignore initial DTCM message, as this is a "hi" message from the server
|
||||
if (srcConn.Received().copy(4) == "DTCM"){
|
||||
srcConn.Received().remove(8 + rSize);
|
||||
continue;
|
||||
}
|
||||
std::string dataPacket = srcConn.Received().remove(8 + rSize);
|
||||
DTSC::Packet metaPack(dataPacket.data(), dataPacket.size());
|
||||
DTSC::Meta nM("", metaPack.getScan());
|
||||
meta.reInit(streamName, false);
|
||||
meta.merge(nM);
|
||||
std::set<size_t> validTracks = M.getMySourceTracks(getpid());
|
||||
userSelect.clear();
|
||||
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
|
||||
userSelect[*it].reload(streamName, *it, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,25 +206,26 @@ namespace Mist{
|
|||
void inputDTSC::closeStreamSource(){srcConn.close();}
|
||||
|
||||
bool inputDTSC::checkArguments(){
|
||||
if (!needsLock()){
|
||||
return true;
|
||||
}else{
|
||||
if (!config->getString("streamname").size()){
|
||||
if (config->getString("output") == "-"){
|
||||
std::cerr << "Output to stdout not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
if (config->getString("output") != "-"){
|
||||
std::cerr << "File output in player mode not supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!needsLock()){return true;}
|
||||
if (!config->getString("streamname").size()){
|
||||
if (config->getString("output") == "-"){
|
||||
std::cerr << "Output to stdout not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
if (config->getString("output") != "-"){
|
||||
std::cerr << "File output in player mode not supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// open File
|
||||
inFile = DTSC::File(config->getString("input"));
|
||||
if (!inFile){return false;}
|
||||
}
|
||||
|
||||
// open File
|
||||
F = fopen(config->getString("input").c_str(), "r+b");
|
||||
if (!F){
|
||||
HIGH_MSG("Could not open file %s", config->getString("input").c_str());
|
||||
return false;
|
||||
}
|
||||
fseek(F, 0, SEEK_SET);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -222,120 +235,215 @@ namespace Mist{
|
|||
}
|
||||
|
||||
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;
|
||||
if (!F){return false;}
|
||||
if (!readExistingHeader()){
|
||||
size_t moreHeader = 0;
|
||||
do{
|
||||
// read existing header from file here?
|
||||
char hdr[8];
|
||||
fseek(F, moreHeader, SEEK_SET);
|
||||
if (fread(hdr, 8, 1, F) != 1){
|
||||
FAIL_MSG("Could not read header @ bpos %zu", moreHeader);
|
||||
return false;
|
||||
}
|
||||
if (memcmp(hdr, DTSC::Magic_Header, 4)){
|
||||
FAIL_MSG("File does not have a DTSC header @ bpos %zu", moreHeader);
|
||||
return false;
|
||||
}
|
||||
size_t pktLen = Bit::btohl(hdr + 4);
|
||||
char *pkt = (char *)malloc(8 + pktLen * sizeof(char));
|
||||
fseek(F, moreHeader, SEEK_SET);
|
||||
if (fread(pkt, 8 + pktLen, 1, F) != 1){
|
||||
free(pkt);
|
||||
FAIL_MSG("Could not read packet @ bpos %zu", moreHeader);
|
||||
}
|
||||
DTSC::Scan S(pkt + 8, pktLen);
|
||||
if (S.hasMember("moreheader") && S.getMember("moreheader").asInt()){
|
||||
moreHeader = S.getMember("moreheader").asInt();
|
||||
}else{
|
||||
moreHeader = 0;
|
||||
meta.reInit(streamName, moreHeader);
|
||||
}
|
||||
|
||||
free(pkt);
|
||||
}while (moreHeader);
|
||||
}
|
||||
myMeta = DTSC::Meta(inFile.getMeta());
|
||||
DEBUG_MSG(DLVL_DEVEL, "Meta read in with %lu tracks", myMeta.tracks.size());
|
||||
return true;
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
void inputDTSC::getNext(bool smart){
|
||||
void inputDTSC::getNext(size_t idx){
|
||||
if (!needsLock()){
|
||||
thisPacket.reInit(srcConn);
|
||||
while (config->is_active){
|
||||
if (thisPacket.getVersion() == DTSC::DTCM){
|
||||
nProxy.userClient.keepAlive();
|
||||
std::string cmd;
|
||||
thisPacket.getString("cmd", cmd);
|
||||
if (cmd == "reset"){
|
||||
// Read next packet
|
||||
thisPacket.reInit(srcConn);
|
||||
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
|
||||
DTSC::Meta newMeta;
|
||||
newMeta.reinit(thisPacket);
|
||||
// Detect new tracks
|
||||
std::set<unsigned int> newTracks;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = newMeta.tracks.begin();
|
||||
it != newMeta.tracks.end(); it++){
|
||||
if (!myMeta.tracks.count(it->first)){newTracks.insert(it->first);}
|
||||
}
|
||||
|
||||
for (std::set<unsigned int>::iterator it = newTracks.begin(); it != newTracks.end(); it++){
|
||||
INFO_MSG("Reset: adding track %d", *it);
|
||||
myMeta.tracks[*it] = newMeta.tracks[*it];
|
||||
continueNegotiate(*it, true);
|
||||
}
|
||||
|
||||
// Detect removed tracks
|
||||
std::set<unsigned int> deletedTracks;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
|
||||
it != myMeta.tracks.end(); it++){
|
||||
if (!newMeta.tracks.count(it->first)){deletedTracks.insert(it->first);}
|
||||
}
|
||||
|
||||
for (std::set<unsigned int>::iterator it = deletedTracks.begin();
|
||||
it != deletedTracks.end(); it++){
|
||||
INFO_MSG("Reset: deleting track %d", *it);
|
||||
myMeta.tracks.erase(*it);
|
||||
}
|
||||
thisPacket.reInit(srcConn); // read the next packet before continuing
|
||||
}else{
|
||||
myMeta = DTSC::Meta();
|
||||
}
|
||||
}else{
|
||||
thisPacket.reInit(srcConn); // read the next packet before continuing
|
||||
}
|
||||
continue; // parse the next packet before returning
|
||||
}else if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
|
||||
DTSC::Meta newMeta;
|
||||
newMeta.reinit(thisPacket);
|
||||
std::set<unsigned int> newTracks;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = newMeta.tracks.begin();
|
||||
it != newMeta.tracks.end(); it++){
|
||||
if (!myMeta.tracks.count(it->first)){newTracks.insert(it->first);}
|
||||
}
|
||||
|
||||
for (std::set<unsigned int>::iterator it = newTracks.begin(); it != newTracks.end(); it++){
|
||||
INFO_MSG("New header: adding track %d (%s)", *it, newMeta.tracks[*it].type.c_str());
|
||||
myMeta.tracks[*it] = newMeta.tracks[*it];
|
||||
continueNegotiate(*it, true);
|
||||
}
|
||||
thisPacket.reInit(srcConn); // read the next packet before continuing
|
||||
continue; // parse the next packet before returning
|
||||
}
|
||||
// We now know we have either a data packet, or an error.
|
||||
if (!thisPacket.getTrackId()){
|
||||
if (thisPacket.getVersion() == DTSC::DTSC_V2){
|
||||
WARN_MSG("Received bad packet for stream %s: %llu@%llu", streamName.c_str(),
|
||||
thisPacket.getTrackId(), thisPacket.getTime());
|
||||
}else{
|
||||
// All types except data packets are handled above, so if it's not a V2 data packet, we assume corruption
|
||||
WARN_MSG("Invalid packet header for stream %s", streamName.c_str());
|
||||
}
|
||||
}
|
||||
return; // we have a packet
|
||||
getNextFromStream(idx);
|
||||
return;
|
||||
}
|
||||
if (!currentPositions.size()){
|
||||
WARN_MSG("No seek positions set - returning empty packet.");
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
seekPos thisPos = *currentPositions.begin();
|
||||
fseek(F, thisPos.bytePos, SEEK_SET);
|
||||
if (feof(F)){
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
clearerr(F);
|
||||
currentPositions.erase(currentPositions.begin());
|
||||
lastreadpos = ftell(F);
|
||||
if (fread(buffer, 4, 1, F) != 1){
|
||||
if (feof(F)){
|
||||
INFO_MSG("End of file reached while seeking @ %" PRIu64, lastreadpos);
|
||||
}else{
|
||||
ERROR_MSG("Could not seek to next @ %" PRIu64, lastreadpos);
|
||||
}
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
if (memcmp(buffer, DTSC::Magic_Header, 4) == 0){
|
||||
seekNext(thisPacket.getTime(), thisPacket.getTrackId(), true);
|
||||
getNext(idx);
|
||||
return;
|
||||
}
|
||||
uint8_t version = 0;
|
||||
if (memcmp(buffer, DTSC::Magic_Packet, 4) == 0){version = 1;}
|
||||
if (memcmp(buffer, DTSC::Magic_Packet2, 4) == 0){version = 2;}
|
||||
if (version == 0){
|
||||
ERROR_MSG("Invalid packet header @ %#" PRIx64 " - %.4s != %.4s @ %" PRIu64, lastreadpos,
|
||||
buffer, DTSC::Magic_Packet2, lastreadpos);
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
if (fread(buffer + 4, 4, 1, F) != 1){
|
||||
ERROR_MSG("Could not read packet size @ %" PRIu64, lastreadpos);
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
std::string pBuf;
|
||||
uint32_t packSize = Bit::btohl(buffer + 4);
|
||||
pBuf.resize(8 + packSize);
|
||||
memcpy((char *)pBuf.data(), buffer, 8);
|
||||
if (fread((void *)(pBuf.data() + 8), packSize, 1, F) != 1){
|
||||
ERROR_MSG("Could not read packet @ %" PRIu64, lastreadpos);
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
thisPacket.reInit(pBuf.data(), pBuf.size());
|
||||
seekNext(thisPos.seekTime, thisPos.trackID);
|
||||
fseek(F, thisPos.bytePos, SEEK_SET);
|
||||
}
|
||||
|
||||
void inputDTSC::getNextFromStream(size_t idx){
|
||||
thisPacket.reInit(srcConn);
|
||||
while (config->is_active){
|
||||
if (thisPacket.getVersion() == DTSC::DTCM){
|
||||
// userClient.keepAlive();
|
||||
std::string cmd;
|
||||
thisPacket.getString("cmd", cmd);
|
||||
if (cmd != "reset"){
|
||||
thisPacket.reInit(srcConn);
|
||||
continue;
|
||||
}
|
||||
// Read next packet
|
||||
thisPacket.reInit(srcConn);
|
||||
if (thisPacket.getVersion() != DTSC::DTSC_HEAD){
|
||||
meta.clear();
|
||||
continue;
|
||||
}
|
||||
DTSC::Meta nM("", thisPacket.getScan());
|
||||
meta.merge(nM, true, false);
|
||||
thisPacket.reInit(srcConn); // read the next packet before continuing
|
||||
continue; // parse the next packet before returning
|
||||
}
|
||||
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
|
||||
DTSC::Meta nM("", thisPacket.getScan());
|
||||
meta.merge(nM, false, false);
|
||||
thisPacket.reInit(srcConn); // read the next packet before continuing
|
||||
continue; // parse the next packet before returning
|
||||
}
|
||||
thisPacket = DTSC::Packet(thisPacket, M.trackIDToIndex(thisPacket.getTrackId(), getpid()));
|
||||
return; // we have a packet
|
||||
}
|
||||
}
|
||||
|
||||
void inputDTSC::seek(uint64_t seekTime, size_t idx){
|
||||
currentPositions.clear();
|
||||
if (idx != INVALID_TRACK_ID){
|
||||
seekNext(seekTime, idx, true);
|
||||
}else{
|
||||
if (smart){
|
||||
inFile.seekNext();
|
||||
}else{
|
||||
inFile.parseNext();
|
||||
std::set<size_t> tracks = M.getValidTracks();
|
||||
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
|
||||
seekNext(seekTime, *it, true);
|
||||
}
|
||||
thisPacket = inFile.getPacket();
|
||||
}
|
||||
}
|
||||
|
||||
void inputDTSC::seek(int seekTime){
|
||||
inFile.seek_time(seekTime);
|
||||
initialTime = 0;
|
||||
playUntil = 0;
|
||||
}
|
||||
|
||||
void inputDTSC::trackSelect(std::string trackSpec){
|
||||
selectedTracks.clear();
|
||||
long long unsigned int index;
|
||||
while (trackSpec != ""){
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
||||
if (index != std::string::npos){
|
||||
trackSpec.erase(0, index + 1);
|
||||
void inputDTSC::seekNext(uint64_t ms, size_t trackIdx, bool forceSeek){
|
||||
seekPos tmpPos;
|
||||
tmpPos.trackID = trackIdx;
|
||||
if (!forceSeek && thisPacket && ms >= thisPacket.getTime() && trackIdx >= thisPacket.getTrackId()){
|
||||
tmpPos.seekTime = thisPacket.getTime();
|
||||
tmpPos.bytePos = ftell(F);
|
||||
}else{
|
||||
tmpPos.seekTime = 0;
|
||||
tmpPos.bytePos = 0;
|
||||
}
|
||||
if (feof(F)){
|
||||
clearerr(F);
|
||||
fseek(F, 0, SEEK_SET);
|
||||
tmpPos.bytePos = 0;
|
||||
tmpPos.seekTime = 0;
|
||||
}
|
||||
DTSC::Keys keys(M.keys(trackIdx));
|
||||
uint32_t keyNum = keys.getNumForTime(ms);
|
||||
if (keys.getTime(keyNum) > tmpPos.seekTime){
|
||||
tmpPos.seekTime = keys.getTime(keyNum);
|
||||
tmpPos.bytePos = keys.getBpos(keyNum);
|
||||
}
|
||||
bool foundPacket = false;
|
||||
while (!foundPacket){
|
||||
lastreadpos = ftell(F);
|
||||
if (feof(F)){
|
||||
WARN_MSG("Reached EOF during seek to %" PRIu64 " in track %zu - aborting @ %" PRIu64, ms,
|
||||
trackIdx, lastreadpos);
|
||||
return;
|
||||
}
|
||||
// Seek to first packet after ms.
|
||||
fseek(F, tmpPos.bytePos, SEEK_SET);
|
||||
lastreadpos = ftell(F);
|
||||
// read the header
|
||||
char header[20];
|
||||
if (fread((void *)header, 20, 1, F) != 1){
|
||||
WARN_MSG("Could not read header from file. Much sadface.");
|
||||
return;
|
||||
}
|
||||
// check if packetID matches, if not, skip size + 8 bytes.
|
||||
uint32_t packSize = Bit::btohl(header + 4);
|
||||
uint32_t packID = Bit::btohl(header + 8);
|
||||
if (memcmp(header, DTSC::Magic_Packet2, 4) != 0 || packID != trackIdx){
|
||||
if (memcmp(header, "DT", 2) != 0){
|
||||
WARN_MSG("Invalid header during seek to %" PRIu64 " in track %zu @ %" PRIu64
|
||||
" - resetting bytePos from %" PRIu64 " to zero",
|
||||
ms, trackIdx, lastreadpos, tmpPos.bytePos);
|
||||
tmpPos.bytePos = 0;
|
||||
continue;
|
||||
}
|
||||
tmpPos.bytePos += 8 + packSize;
|
||||
continue;
|
||||
}
|
||||
// get timestamp of packet, if too large, break, if not, skip size bytes.
|
||||
uint64_t myTime = Bit::btohll(header + 12);
|
||||
tmpPos.seekTime = myTime;
|
||||
if (myTime >= ms){
|
||||
foundPacket = true;
|
||||
}else{
|
||||
trackSpec = "";
|
||||
tmpPos.bytePos += 8 + packSize;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
inFile.selectTracks(selectedTracks);
|
||||
// HIGH_MSG("Seek to %u:%d resulted in %lli", trackIdx, ms, tmpPos.seekTime);
|
||||
if (tmpPos.seekTime > 0xffffffffffffff00ll){tmpPos.seekTime = 0;}
|
||||
currentPositions.insert(tmpPos);
|
||||
return;
|
||||
}
|
||||
}// namespace Mist
|
||||
|
|
|
@ -1,7 +1,27 @@
|
|||
#include "input.h"
|
||||
|
||||
#include <set>
|
||||
#include <stdio.h> //for FILE
|
||||
|
||||
#include <mist/dtsc.h>
|
||||
|
||||
namespace Mist{
|
||||
///\brief A simple structure used for ordering byte seek positions.
|
||||
struct seekPos{
|
||||
///\brief Less-than comparison for seekPos structures.
|
||||
///\param rhs The seekPos to compare with.
|
||||
///\return Whether this object is smaller than rhs.
|
||||
bool operator<(const seekPos &rhs) const{
|
||||
if (seekTime < rhs.seekTime){return true;}
|
||||
if (seekTime == rhs.seekTime){return trackID < rhs.trackID;}
|
||||
return false;
|
||||
}
|
||||
uint64_t seekTime; ///< Stores the timestamp of the DTSC packet referenced by this structure.
|
||||
uint64_t bytePos; ///< Stores the byteposition of the DTSC packet referenced by this structure.
|
||||
uint32_t trackID; ///< Stores the track the DTSC packet referenced by this structure is
|
||||
///< associated with.
|
||||
};
|
||||
|
||||
class inputDTSC : public Input{
|
||||
public:
|
||||
inputDTSC(Util::Config *cfg);
|
||||
|
@ -15,13 +35,24 @@ namespace Mist{
|
|||
bool checkArguments();
|
||||
bool readHeader();
|
||||
bool needHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
void getNextFromStream(size_t idx = INVALID_TRACK_ID);
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
|
||||
|
||||
DTSC::File inFile;
|
||||
FILE *F;
|
||||
|
||||
Socket::Connection srcConn;
|
||||
|
||||
bool lockCache;
|
||||
bool lockNeeded;
|
||||
|
||||
std::set<seekPos> currentPositions;
|
||||
|
||||
uint64_t lastreadpos;
|
||||
|
||||
char buffer[8];
|
||||
|
||||
void seekNext(uint64_t ms, size_t trackIdx, bool forceSeek = false);
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ namespace Mist{
|
|||
lastClusterTime = 0;
|
||||
bufferedPacks = 0;
|
||||
wantBlocks = true;
|
||||
totalBytes = 0;
|
||||
}
|
||||
|
||||
std::string ASStoSRT(const char *ptr, uint32_t len){
|
||||
|
@ -112,13 +113,15 @@ namespace Mist{
|
|||
uint32_t needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal);
|
||||
while (ptr.size() < needed){
|
||||
if (!ptr.allocate(needed)){return false;}
|
||||
if (!fread(ptr + ptr.size(), needed - ptr.size(), 1, inFile)){
|
||||
int64_t toRead = needed - ptr.size();
|
||||
if (!fread(ptr + ptr.size(), toRead, 1, inFile)){
|
||||
// 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);
|
||||
FAIL_MSG("Could not read more data! (have %zu, need %" PRIu32 ")", ptr.size(), needed);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
totalBytes += toRead;
|
||||
ptr.size() = needed;
|
||||
needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal);
|
||||
if (ptr.size() >= needed){
|
||||
|
@ -141,26 +144,26 @@ namespace Mist{
|
|||
lastClusterBPos = bp;
|
||||
}
|
||||
}
|
||||
DONTEVEN_MSG("Found a cluster at position %llu", lastClusterBPos);
|
||||
DONTEVEN_MSG("Found a cluster at position %" PRIu64, lastClusterBPos);
|
||||
}
|
||||
if (E.getID() == EBML::EID_TIMECODE){
|
||||
lastClusterTime = E.getValUInt();
|
||||
DONTEVEN_MSG("Cluster time %llu ms", lastClusterTime);
|
||||
DONTEVEN_MSG("Cluster time %" PRIu64 " ms", lastClusterTime);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputEBML::readExistingHeader(){
|
||||
if (!Input::readExistingHeader()){return false;}
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
|
||||
it != myMeta.tracks.end(); ++it){
|
||||
if (it->second.codec == "PCMLE"){
|
||||
it->second.codec = "PCM";
|
||||
swapEndianness.insert(it->first);
|
||||
std::set<size_t> validTracks = M.getValidTracks();
|
||||
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
|
||||
if (M.getCodec(*it) == "PCMLE"){
|
||||
meta.setCodec(*it, "PCM");
|
||||
swapEndianness.insert(*it);
|
||||
}
|
||||
}
|
||||
if (myMeta.inputLocalVars.isMember("timescale")){
|
||||
timeScale = ((double)myMeta.inputLocalVars["timescale"].asInt()) / 1000000.0;
|
||||
if (M.inputLocalVars.isMember("timescale")){
|
||||
timeScale = ((double)M.inputLocalVars["timescale"].asInt()) / 1000000.0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -169,6 +172,7 @@ namespace Mist{
|
|||
if (!inFile){return false;}
|
||||
// Create header file from file
|
||||
uint64_t bench = Util::getMicros();
|
||||
if (!meta){meta.reInit(streamName);}
|
||||
|
||||
while (readElement()){
|
||||
EBML::Element E(ptr, readingMinimal);
|
||||
|
@ -178,7 +182,7 @@ namespace Mist{
|
|||
ERROR_MSG("Track without track number encountered, ignoring");
|
||||
continue;
|
||||
}
|
||||
uint64_t trackNo = tmpElem.getValUInt();
|
||||
uint64_t trackID = tmpElem.getValUInt();
|
||||
tmpElem = E.findChild(EBML::EID_CODECID);
|
||||
if (!tmpElem){
|
||||
ERROR_MSG("Track without codec id encountered, ignoring");
|
||||
|
@ -311,32 +315,33 @@ namespace Mist{
|
|||
}
|
||||
tmpElem = E.findChild(EBML::EID_LANGUAGE);
|
||||
if (tmpElem){lang = tmpElem.getValString();}
|
||||
DTSC::Track &Trk = myMeta.tracks[trackNo];
|
||||
Trk.trackID = trackNo;
|
||||
Trk.lang = lang;
|
||||
Trk.codec = trueCodec;
|
||||
Trk.type = trueType;
|
||||
Trk.init = init;
|
||||
if (Trk.type == "video"){
|
||||
size_t idx = M.trackIDToIndex(trackID, getpid());
|
||||
if (idx == INVALID_TRACK_ID){idx = meta.addTrack();}
|
||||
meta.setID(idx, trackID);
|
||||
meta.setLang(idx, lang);
|
||||
meta.setCodec(idx, trueCodec);
|
||||
meta.setType(idx, trueType);
|
||||
meta.setInit(idx, init);
|
||||
if (trueType == "video"){
|
||||
tmpElem = E.findChild(EBML::EID_PIXELWIDTH);
|
||||
Trk.width = tmpElem ? tmpElem.getValUInt() : 0;
|
||||
meta.setWidth(idx, tmpElem ? tmpElem.getValUInt() : 0);
|
||||
tmpElem = E.findChild(EBML::EID_PIXELHEIGHT);
|
||||
Trk.height = tmpElem ? tmpElem.getValUInt() : 0;
|
||||
Trk.fpks = 0;
|
||||
meta.setHeight(idx, tmpElem ? tmpElem.getValUInt() : 0);
|
||||
meta.setFpks(idx, 0);
|
||||
}
|
||||
if (Trk.type == "audio"){
|
||||
if (trueType == "audio"){
|
||||
tmpElem = E.findChild(EBML::EID_CHANNELS);
|
||||
Trk.channels = tmpElem ? tmpElem.getValUInt() : 1;
|
||||
meta.setChannels(idx, tmpElem ? tmpElem.getValUInt() : 1);
|
||||
tmpElem = E.findChild(EBML::EID_BITDEPTH);
|
||||
Trk.size = tmpElem ? tmpElem.getValUInt() : 0;
|
||||
meta.setSize(idx, tmpElem ? tmpElem.getValUInt() : 0);
|
||||
tmpElem = E.findChild(EBML::EID_SAMPLINGFREQUENCY);
|
||||
Trk.rate = tmpElem ? (int)tmpElem.getValFloat() : 8000;
|
||||
meta.setRate(idx, tmpElem ? (int)tmpElem.getValFloat() : 8000);
|
||||
}
|
||||
INFO_MSG("Detected track: %s", Trk.getIdentifier().c_str());
|
||||
INFO_MSG("Detected track: %s", M.getTrackIdentifier(idx).c_str());
|
||||
}
|
||||
if (E.getID() == EBML::EID_TIMECODESCALE){
|
||||
uint64_t timeScaleVal = E.getValUInt();
|
||||
myMeta.inputLocalVars["timescale"] = timeScaleVal;
|
||||
meta.inputLocalVars["timescale"] = timeScaleVal;
|
||||
timeScale = ((double)timeScaleVal) / 1000000.0;
|
||||
}
|
||||
// Live streams stop parsing the header as soon as the first Cluster is encountered
|
||||
|
@ -346,34 +351,35 @@ namespace Mist{
|
|||
uint64_t tNum = B.getTrackNum();
|
||||
uint64_t newTime = lastClusterTime + B.getTimecode();
|
||||
trackPredictor &TP = packBuf[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());
|
||||
size_t idx = meta.trackIDToIndex(tNum, getpid());
|
||||
bool isVideo = (M.getType(idx) == "video");
|
||||
bool isAudio = (M.getType(idx) == "audio");
|
||||
bool isASS = (M.getCodec(idx) == "subtitle" && M.getInit(idx).size());
|
||||
// If this is a new video keyframe, flush the corresponding trackPredictor
|
||||
if (isVideo && B.isKeyframe()){
|
||||
while (TP.hasPackets(true)){
|
||||
packetData &C = TP.getPacketData(true);
|
||||
myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
|
||||
meta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
|
||||
TP.remove();
|
||||
}
|
||||
TP.flush();
|
||||
}
|
||||
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"){
|
||||
if (M.getCodec(idx) == "AAC"){
|
||||
newTime += (1000000 / M.getRate(idx)) / timeScale; // assume ~1000 samples per frame
|
||||
}else if (M.getCodec(idx) == "MP3"){
|
||||
newTime += (1152000 / M.getRate(idx)) / timeScale; // 1152 samples per frame
|
||||
}else if (M.getCodec(idx) == "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 += (512000 / M.getRate(idx)) / timeScale;
|
||||
}else{
|
||||
newTime += 1 / timeScale;
|
||||
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
|
||||
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!",
|
||||
M.getCodec(idx).c_str());
|
||||
}
|
||||
}
|
||||
uint32_t frameSize = B.getFrameSize(frameNo);
|
||||
|
@ -388,7 +394,7 @@ namespace Mist{
|
|||
}
|
||||
while (TP.hasPackets()){
|
||||
packetData &C = TP.getPacketData(isVideo);
|
||||
myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
|
||||
meta.update(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.dsize, C.bpos, C.key);
|
||||
TP.remove();
|
||||
}
|
||||
}
|
||||
|
@ -398,23 +404,25 @@ namespace Mist{
|
|||
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");
|
||||
myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
|
||||
packetData &C =
|
||||
TP.getPacketData(M.getType(M.trackIDToIndex(it->first, getpid())) == "video");
|
||||
meta.update(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.dsize, C.bpos, C.key);
|
||||
TP.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bench = Util::getMicros(bench);
|
||||
INFO_MSG("Header generated in %llu ms", bench / 1000);
|
||||
INFO_MSG("Header generated in %" PRIu64 " ms", bench / 1000);
|
||||
clearPredictors();
|
||||
bufferedPacks = 0;
|
||||
myMeta.toFile(config->getString("input") + ".dtsh");
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
|
||||
it != myMeta.tracks.end(); ++it){
|
||||
if (it->second.codec == "PCMLE"){
|
||||
it->second.codec = "PCM";
|
||||
swapEndianness.insert(it->first);
|
||||
M.toFile(config->getString("input") + ".dtsh");
|
||||
|
||||
std::set<size_t> validTracks = M.getValidTracks();
|
||||
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
|
||||
if (M.getCodec(*it) == "PCMLE"){
|
||||
meta.setCodec(*it, "PCM");
|
||||
swapEndianness.insert(*it);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@ -422,7 +430,7 @@ namespace Mist{
|
|||
|
||||
void InputEBML::fillPacket(packetData &C){
|
||||
if (swapEndianness.count(C.track)){
|
||||
switch (myMeta.tracks[C.track].size){
|
||||
switch (M.getSize(M.trackIDToIndex(C.track, getpid()))){
|
||||
case 16:{
|
||||
char *ptr = C.ptr;
|
||||
uint32_t ptrSize = C.dsize;
|
||||
|
@ -455,16 +463,18 @@ namespace Mist{
|
|||
}break;
|
||||
}
|
||||
}
|
||||
thisPacket.genericFill(C.time, C.offset, C.track, C.ptr, C.dsize, C.bpos, C.key);
|
||||
thisPacket.genericFill(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.ptr, C.dsize,
|
||||
C.bpos, C.key);
|
||||
}
|
||||
|
||||
void InputEBML::getNext(bool smart){
|
||||
void InputEBML::getNext(size_t idx){
|
||||
// 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){
|
||||
trackPredictor &TP = it->second;
|
||||
if (TP.hasPackets()){
|
||||
packetData &C = TP.getPacketData(myMeta.tracks[it->first].type == "video");
|
||||
packetData &C =
|
||||
TP.getPacketData(M.getType(M.trackIDToIndex(it->first, getpid())) == "video");
|
||||
fillPacket(C);
|
||||
TP.remove();
|
||||
--bufferedPacks;
|
||||
|
@ -481,7 +491,7 @@ namespace Mist{
|
|||
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");
|
||||
packetData &C = TP.getPacketData(M.getType(M.trackIDToIndex(it->first, getpid())) == "video");
|
||||
fillPacket(C);
|
||||
TP.remove();
|
||||
--bufferedPacks;
|
||||
|
@ -494,7 +504,8 @@ namespace Mist{
|
|||
return;
|
||||
}
|
||||
B = EBML::Block(ptr);
|
||||
}while (!B || B.getType() != EBML::ELEM_BLOCK || !selectedTracks.count(B.getTrackNum()));
|
||||
}while (!B || B.getType() != EBML::ELEM_BLOCK ||
|
||||
(idx != INVALID_TRACK_ID && M.getID(idx) != B.getTrackNum()));
|
||||
}else{
|
||||
B = EBML::Block(ptr);
|
||||
}
|
||||
|
@ -502,10 +513,10 @@ namespace Mist{
|
|||
uint64_t tNum = B.getTrackNum();
|
||||
uint64_t newTime = lastClusterTime + B.getTimecode();
|
||||
trackPredictor &TP = packBuf[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());
|
||||
size_t trackIdx = M.trackIDToIndex(tNum, getpid());
|
||||
bool isVideo = (M.getType(trackIdx) == "video");
|
||||
bool isAudio = (M.getType(trackIdx) == "audio");
|
||||
bool isASS = (M.getCodec(trackIdx) == "subtitle" && M.getInit(trackIdx).size());
|
||||
|
||||
// If this is a new video keyframe, flush the corresponding trackPredictor
|
||||
if (isVideo && B.isKeyframe() && bufferedPacks){
|
||||
|
@ -523,18 +534,19 @@ 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"){
|
||||
if (M.getCodec(trackIdx) == "AAC"){
|
||||
newTime += (1000000 / M.getRate(trackIdx)) / timeScale; // assume ~1000 samples per frame
|
||||
}else if (M.getCodec(trackIdx) == "MP3"){
|
||||
newTime += (1152000 / M.getRate(trackIdx)) / timeScale; // 1152 samples per frame
|
||||
}else if (M.getCodec(trackIdx) == "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 += (512000 / M.getRate(trackIdx)) / timeScale;
|
||||
}else{
|
||||
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
|
||||
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!",
|
||||
M.getCodec(trackIdx).c_str());
|
||||
}
|
||||
}
|
||||
uint32_t frameSize = B.getFrameSize(frameNo);
|
||||
|
@ -560,22 +572,26 @@ namespace Mist{
|
|||
}else{
|
||||
// We didn't set thisPacket yet. Read another.
|
||||
// Recursing is fine, this can only happen a few times in a row.
|
||||
getNext(smart);
|
||||
getNext(idx);
|
||||
}
|
||||
}
|
||||
|
||||
void InputEBML::seek(int seekTime){
|
||||
void InputEBML::seek(uint64_t seekTime, size_t idx){
|
||||
wantBlocks = true;
|
||||
clearPredictors();
|
||||
bufferedPacks = 0;
|
||||
uint64_t mainTrack = getMainSelectedTrack();
|
||||
DTSC::Track Trk = myMeta.tracks[mainTrack];
|
||||
bool isVideo = (Trk.type == "video");
|
||||
uint64_t seekPos = Trk.keys[0].getBpos();
|
||||
|
||||
DTSC::Keys keys(M.keys(mainTrack));
|
||||
DTSC::Parts parts(M.parts(mainTrack));
|
||||
uint64_t seekPos = keys.getBpos(0);
|
||||
// Replay the parts of the previous keyframe, so the timestaps match up
|
||||
for (unsigned int i = 1; i < Trk.keys.size(); i++){
|
||||
if (Trk.keys[i].getTime() > seekTime){break;}
|
||||
seekPos = Trk.keys[i].getBpos();
|
||||
uint64_t partCount = 0;
|
||||
for (size_t i = 0; i < keys.getEndValid(); i++){
|
||||
if (keys.getTime(i) > seekTime){break;}
|
||||
partCount += keys.getParts(i);
|
||||
DONTEVEN_MSG("Seeking to %" PRIu64 ", found %" PRIu64 "...", seekTime, keys.getTime(i));
|
||||
seekPos = keys.getBpos(i);
|
||||
}
|
||||
Util::fseek(inFile, seekPos, SEEK_SET);
|
||||
}
|
||||
|
|
|
@ -11,14 +11,7 @@ namespace Mist{
|
|||
uint64_t time, offset, track, dsize, bpos;
|
||||
bool key;
|
||||
Util::ResizeablePointer ptr;
|
||||
packetData(){
|
||||
time = 0;
|
||||
offset = 0;
|
||||
track = 0;
|
||||
dsize = 0;
|
||||
bpos = 0;
|
||||
key = false;
|
||||
}
|
||||
packetData() : time(0), offset(0), track(0), dsize(0), 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){
|
||||
time = packTime;
|
||||
|
@ -132,10 +125,12 @@ namespace Mist{
|
|||
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);
|
||||
INSANE_MSG("Outputting%s %" PRIu64 "+%" PRIu64 " (#%" PRIu64 ", Max=%" PRIu64
|
||||
"), display at %" PRIu64,
|
||||
(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;}
|
||||
|
@ -155,13 +150,16 @@ namespace Mist{
|
|||
bool needsLock();
|
||||
|
||||
protected:
|
||||
virtual size_t streamByteCount(){
|
||||
return totalBytes;
|
||||
}; // For live streams: to update the stats with correct values.
|
||||
void fillPacket(packetData &C);
|
||||
bool checkArguments();
|
||||
bool preRun();
|
||||
bool readHeader();
|
||||
bool readElement();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
|
||||
void clearPredictors();
|
||||
FILE *inFile;
|
||||
Util::ResizeablePointer ptr;
|
||||
|
@ -177,6 +175,7 @@ namespace Mist{
|
|||
bool needHeader(){return needsLock() && !readExistingHeader();}
|
||||
double timeScale;
|
||||
bool wantBlocks;
|
||||
size_t totalBytes;
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ namespace Mist{
|
|||
capa["codecs"][0u][1u].append("MP3");
|
||||
}
|
||||
|
||||
inputFLV::~inputFLV(){}
|
||||
|
||||
bool inputFLV::checkArguments(){
|
||||
if (config->getString("input") == "-"){
|
||||
std::cerr << "Input from stdin not yet supported" << std::endl;
|
||||
|
@ -77,45 +79,49 @@ namespace Mist{
|
|||
|
||||
bool inputFLV::readHeader(){
|
||||
if (!inFile){return false;}
|
||||
meta.reInit(config->getString("streamname"));
|
||||
// Create header file from FLV data
|
||||
Util::fseek(inFile, 13, SEEK_SET);
|
||||
AMF::Object amf_storage;
|
||||
long long int lastBytePos = 13;
|
||||
uint64_t lastBytePos = 13;
|
||||
uint64_t bench = Util::getMicros();
|
||||
while (!feof(inFile) && !FLV::Parse_Error){
|
||||
if (tmpTag.FileLoader(inFile)){
|
||||
tmpTag.toMeta(myMeta, amf_storage);
|
||||
tmpTag.toMeta(meta, 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);
|
||||
size_t tNumber = meta.trackIDToIndex(tmpTag.getTrackID(), getpid());
|
||||
if (tNumber != INVALID_TRACK_ID){
|
||||
meta.update(tmpTag.tagTime(), tmpTag.offset(), tNumber, 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 %" PRIu64 " ms: @%" PRIu64 ", %s, %s", bench / 1000, lastBytePos,
|
||||
M.getVod() ? "VoD" : "NOVoD", M.getLive() ? "Live" : "NOLive");
|
||||
if (FLV::Parse_Error){
|
||||
tmpTag = FLV::Tag();
|
||||
FLV::Parse_Error = false;
|
||||
ERROR_MSG("Stopping at FLV parse error @%lld: %s", lastBytePos, FLV::Error_Str.c_str());
|
||||
ERROR_MSG("Stopping at FLV parse error @%" PRIu64 ": %s", lastBytePos, FLV::Error_Str.c_str());
|
||||
}
|
||||
myMeta.toFile(config->getString("input") + ".dtsh");
|
||||
M.toFile(config->getString("input") + ".dtsh");
|
||||
Util::fseek(inFile, 13, SEEK_SET);
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputFLV::getNext(bool smart){
|
||||
long long int lastBytePos = Util::ftell(inFile);
|
||||
if (selectedTracks.size() == 1){
|
||||
void inputFLV::getNext(size_t idx){
|
||||
uint64_t lastBytePos = Util::ftell(inFile);
|
||||
if (idx != INVALID_TRACK_ID){
|
||||
uint8_t targetTag = 0x08;
|
||||
if (selectedTracks.count(1)){targetTag = 0x09;}
|
||||
if (selectedTracks.count(3)){targetTag = 0x12;}
|
||||
if (M.getType(idx) == "video"){targetTag = 0x09;}
|
||||
if (M.getType(idx) == "meta"){targetTag = 0x12;}
|
||||
FLV::seekToTagType(inFile, targetTag);
|
||||
}
|
||||
while (!feof(inFile) && !FLV::Parse_Error){
|
||||
if (tmpTag.FileLoader(inFile)){
|
||||
if (!selectedTracks.count(tmpTag.getTrackID())){
|
||||
if (idx != INVALID_TRACK_ID && M.getID(idx) != tmpTag.getTrackID()){
|
||||
lastBytePos = Util::ftell(inFile);
|
||||
continue;
|
||||
}
|
||||
|
@ -129,22 +135,22 @@ namespace Mist{
|
|||
if (FLV::Parse_Error){
|
||||
FLV::Parse_Error = false;
|
||||
tmpTag = FLV::Tag();
|
||||
FAIL_MSG("FLV error @ %lld: %s", lastBytePos, FLV::Error_Str.c_str());
|
||||
FAIL_MSG("FLV error @ %" PRIu64 ": %s", lastBytePos, FLV::Error_Str.c_str());
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
if (!tmpTag.getDataLen() || (tmpTag.needsInitData() && tmpTag.isInitData())){
|
||||
return getNext();
|
||||
return getNext(idx);
|
||||
}
|
||||
thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getData(),
|
||||
tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe); // init packet from tmpTags data
|
||||
size_t tNumber = meta.trackIDToIndex(tmpTag.getTrackID(), getpid());
|
||||
thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tNumber, tmpTag.getData(),
|
||||
tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe);
|
||||
|
||||
DTSC::Track &trk = myMeta.tracks[tmpTag.getTrackID()];
|
||||
if (trk.codec == "PCM" && trk.size == 16){
|
||||
if (M.getCodec(idx) == "PCM" && M.getSize(idx) == 16){
|
||||
char *ptr = 0;
|
||||
size_t ptrSize = 0;
|
||||
thisPacket.getString("data", ptr, ptrSize);
|
||||
for (uint32_t i = 0; i < ptrSize; i += 2){
|
||||
for (size_t i = 0; i < ptrSize; i += 2){
|
||||
char tmpchar = ptr[i];
|
||||
ptr[i] = ptr[i + 1];
|
||||
ptr[i + 1] = tmpchar;
|
||||
|
@ -152,29 +158,12 @@ namespace Mist{
|
|||
}
|
||||
}
|
||||
|
||||
void inputFLV::seek(int seekTime){
|
||||
void inputFLV::seek(uint64_t seekTime, size_t idx){
|
||||
// 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;}
|
||||
seekPos = myMeta.tracks[trackSeek].keys[i].getBpos();
|
||||
}
|
||||
Util::fseek(inFile, seekPos, SEEK_SET);
|
||||
}
|
||||
|
||||
void inputFLV::trackSelect(std::string trackSpec){
|
||||
selectedTracks.clear();
|
||||
size_t index;
|
||||
while (trackSpec != ""){
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
||||
if (index != std::string::npos){
|
||||
trackSpec.erase(0, index + 1);
|
||||
}else{
|
||||
trackSpec = "";
|
||||
}
|
||||
}
|
||||
size_t seekTrack = (idx == INVALID_TRACK_ID ? M.mainTrack() : idx);
|
||||
DTSC::Keys keys(M.keys(seekTrack));
|
||||
uint32_t keyNum = keys.getNumForTime(seekTime);
|
||||
Util::fseek(inFile, keys.getBpos(keyNum), SEEK_SET);
|
||||
}
|
||||
}// namespace Mist
|
||||
|
|
|
@ -6,15 +6,15 @@ namespace Mist{
|
|||
class inputFLV : public Input{
|
||||
public:
|
||||
inputFLV(Util::Config *cfg);
|
||||
~inputFLV();
|
||||
|
||||
protected:
|
||||
// Private Functions
|
||||
bool checkArguments();
|
||||
bool preRun();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
|
||||
bool keepRunning();
|
||||
FLV::Tag tmpTag;
|
||||
uint64_t lastModTime;
|
||||
|
|
|
@ -11,6 +11,8 @@ namespace Mist{
|
|||
bool checkArguments(){return false;};
|
||||
bool readHeader(){return false;};
|
||||
bool needHeader(){return false;};
|
||||
void getNext(size_t idx = INVALID_TRACK_ID){}
|
||||
void seek(uint64_t time, size_t idx = INVALID_TRACK_ID){}
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
|
@ -17,10 +17,9 @@ namespace Mist{
|
|||
inputProcess = 0;
|
||||
}
|
||||
|
||||
bool InputH264::preRun(){
|
||||
bool InputH264::openStreamSource(){
|
||||
if (config->getString("input") != "-"){
|
||||
std::string input = config->getString("input");
|
||||
const char *argv[2];
|
||||
input = input.substr(10);
|
||||
|
||||
char *args[128];
|
||||
|
@ -50,15 +49,20 @@ namespace Mist{
|
|||
myConn.open(fileno(stdout), fileno(stdin));
|
||||
}
|
||||
myConn.Received().splitter.assign("\000\000\001", 3);
|
||||
myMeta.vod = false;
|
||||
myMeta.live = true;
|
||||
myMeta.tracks[1].type = "video";
|
||||
myMeta.tracks[1].codec = "H264";
|
||||
myMeta.tracks[1].trackID = 1;
|
||||
waitsSinceData = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void InputH264::parseStreamHeader(){
|
||||
tNumber = meta.addTrack();
|
||||
meta.setType(tNumber, "video");
|
||||
meta.setCodec(tNumber, "H264");
|
||||
meta.setID(tNumber, tNumber);
|
||||
waitsSinceData = 0;
|
||||
INFO_MSG("Waiting for init data...");
|
||||
while (myConn && !M.getInit(tNumber).size()){getNext();}
|
||||
INFO_MSG("Init data received!");
|
||||
}
|
||||
|
||||
bool InputH264::checkArguments(){
|
||||
std::string input = config->getString("input");
|
||||
if (input != "-" && input.substr(0, 10) != "h264-exec:"){
|
||||
|
@ -68,7 +72,7 @@ namespace Mist{
|
|||
return true;
|
||||
}
|
||||
|
||||
void InputH264::getNext(bool smart){
|
||||
void InputH264::getNext(size_t idx){
|
||||
do{
|
||||
if (!myConn.spool()){
|
||||
Util::sleep(25);
|
||||
|
@ -87,18 +91,18 @@ namespace Mist{
|
|||
while (nalSize && NAL.data()[nalSize - 1] == 0){--nalSize;}
|
||||
if (!nalSize){continue;}
|
||||
uint8_t nalType = NAL.data()[0] & 0x1F;
|
||||
INSANE_MSG("NAL unit, type %u, size %lu", nalType, nalSize);
|
||||
INSANE_MSG("NAL unit, type %u, size %" PRIu32, nalType, nalSize);
|
||||
if (nalType == 7 || nalType == 8){
|
||||
if (nalType == 7){spsInfo = NAL.substr(0, nalSize);}
|
||||
if (nalType == 8){ppsInfo = NAL.substr(0, nalSize);}
|
||||
if (!myMeta.tracks[1].init.size() && spsInfo.size() && ppsInfo.size()){
|
||||
if (!meta.getInit(tNumber).size() && spsInfo.size() && ppsInfo.size()){
|
||||
h264::sequenceParameterSet sps(spsInfo.data(), spsInfo.size());
|
||||
h264::SPSMeta spsChar = sps.getCharacteristics();
|
||||
myMeta.tracks[1].width = spsChar.width;
|
||||
myMeta.tracks[1].height = spsChar.height;
|
||||
myMeta.tracks[1].fpks = spsChar.fps * 1000;
|
||||
if (myMeta.tracks[1].fpks < 100 || myMeta.tracks[1].fpks > 1000000){
|
||||
myMeta.tracks[1].fpks = 0;
|
||||
meta.setWidth(tNumber, spsChar.width);
|
||||
meta.setHeight(tNumber, spsChar.height);
|
||||
meta.setFpks(tNumber, spsChar.fps * 1000);
|
||||
if (M.getFpks(tNumber) < 100 || M.getFpks(tNumber) > 1000000){
|
||||
meta.setFpks(tNumber, 0);
|
||||
}
|
||||
MP4::AVCC avccBox;
|
||||
avccBox.setVersion(1);
|
||||
|
@ -109,14 +113,14 @@ namespace Mist{
|
|||
avccBox.setSPS(spsInfo);
|
||||
avccBox.setPPSCount(1);
|
||||
avccBox.setPPS(ppsInfo);
|
||||
myMeta.tracks[1].init = std::string(avccBox.payload(), avccBox.payloadSize());
|
||||
meta.setInit(tNumber, avccBox.payload(), avccBox.payloadSize());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (myMeta.tracks[1].init.size()){
|
||||
if (M.getInit(tNumber).size()){
|
||||
uint64_t ts = Util::bootMS() - startTime;
|
||||
if (myMeta.tracks[1].fpks){ts = frameCount * (1000000 / myMeta.tracks[1].fpks);}
|
||||
thisPacket.genericFill(ts, 0, 1, 0, 0, 0, h264::isKeyframe(NAL.data(), nalSize));
|
||||
if (M.getFpks(tNumber)){ts = frameCount * (1000000 / M.getFpks(tNumber));}
|
||||
thisPacket.genericFill(ts, 0, tNumber, 0, 0, 0, h264::isKeyframe(NAL.data(), nalSize));
|
||||
thisPacket.appendNal(NAL.data(), nalSize);
|
||||
++frameCount;
|
||||
return;
|
||||
|
|
|
@ -8,24 +8,24 @@ namespace Mist{
|
|||
InputH264(Util::Config *cfg);
|
||||
|
||||
protected:
|
||||
virtual bool needHeader(){return false;}
|
||||
bool checkArguments();
|
||||
bool preRun();
|
||||
void getNext(bool smart = true);
|
||||
void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
Socket::Connection myConn;
|
||||
std::string ppsInfo;
|
||||
std::string spsInfo;
|
||||
uint64_t frameCount;
|
||||
// Empty defaults
|
||||
bool readHeader(){return true;}
|
||||
bool openStreamSource(){return true;}
|
||||
bool openStreamSource();
|
||||
void closeStreamSource(){}
|
||||
void parseStreamHeader(){}
|
||||
void seek(int seekTime){}
|
||||
void trackSelect(std::string trackSpec){}
|
||||
void parseStreamHeader();
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){}
|
||||
bool needsLock(){return false;}
|
||||
uint64_t startTime;
|
||||
pid_t inputProcess;
|
||||
uint32_t waitsSinceData;
|
||||
size_t tNumber;
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ namespace Mist{
|
|||
|
||||
/// Called by the global callbackFunc, to prevent timeouts
|
||||
bool inputHLS::callback(){
|
||||
if (nProxy.userClient.isAlive()){nProxy.userClient.keepAlive();}
|
||||
keepAlive();
|
||||
return config->is_active;
|
||||
}
|
||||
|
||||
|
@ -415,7 +415,7 @@ namespace Mist{
|
|||
|
||||
if (key == "TARGETDURATION"){
|
||||
waitTime = atoi(val.c_str()) / 2;
|
||||
if (waitTime < 5){waitTime = 5;}
|
||||
if (waitTime < 2){waitTime = 2;}
|
||||
}
|
||||
|
||||
if (key == "MEDIA-SEQUENCE"){fileNo = atoll(val.c_str());}
|
||||
|
@ -520,12 +520,13 @@ namespace Mist{
|
|||
memset(entry.keyAES, 0, 16);
|
||||
}
|
||||
|
||||
if (!isUrl()){
|
||||
std::ifstream fileSource;
|
||||
std::string test = root.link(entry.filename).getFilePath();
|
||||
fileSource.open(test.c_str(), std::ios::ate | std::ios::binary);
|
||||
if (!fileSource.good()){WARN_MSG("file: %s, error: %s", test.c_str(), strerror(errno));}
|
||||
totalBytes += fileSource.tellg();
|
||||
if (!isUrl()){
|
||||
std::ifstream fileSource;
|
||||
std::string test = root.link(entry.filename).getFilePath();
|
||||
fileSource.open(test.c_str(), std::ios::ate | std::ios::binary);
|
||||
if (!fileSource.good()){WARN_MSG("file: %s, error: %s", test.c_str(), strerror(errno));}
|
||||
entry.byteEnd = fileSource.tellg();
|
||||
totalBytes += entry.byteEnd;
|
||||
}
|
||||
|
||||
entry.timestamp = lastTimestamp + startTime;
|
||||
|
@ -588,20 +589,6 @@ namespace Mist{
|
|||
return true;
|
||||
}
|
||||
|
||||
void inputHLS::trackSelect(std::string trackSpec){
|
||||
selectedTracks.clear();
|
||||
size_t index;
|
||||
while (trackSpec != ""){
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
||||
if (index != std::string::npos){
|
||||
trackSpec.erase(0, index + 1);
|
||||
}else{
|
||||
trackSpec = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void inputHLS::parseStreamHeader(){
|
||||
if (!initPlaylist(config->getString("input"))){
|
||||
FAIL_MSG("Failed to load HLS playlist, aborting");
|
||||
|
@ -668,6 +655,8 @@ namespace Mist{
|
|||
}while (!segDowner.atEnd());
|
||||
if (preCounter < counter){break;}// We're done reading this playlist!
|
||||
}
|
||||
|
||||
in.close();
|
||||
}
|
||||
tsStream.clear();
|
||||
currentPlaylist = 0;
|
||||
|
@ -681,13 +670,12 @@ namespace Mist{
|
|||
bool hasHeader = false;
|
||||
|
||||
// See whether a separate header file exists.
|
||||
DTSC::File tmp(config->getString("input") + ".dtsh");
|
||||
if (tmp){
|
||||
myMeta = tmp.getMeta();
|
||||
if (myMeta){hasHeader = true;}
|
||||
}
|
||||
meta.reInit(config->getString("streamname"), config->getString("input") + ".dtsh");
|
||||
hasHeader = (bool)M;
|
||||
|
||||
if (!hasHeader){myMeta = DTSC::Meta();}
|
||||
if (M){return true;}
|
||||
|
||||
if (!hasHeader){meta.reInit(config->getString("streamname"), true);}
|
||||
|
||||
TS::Packet packet; // to analyse and extract data
|
||||
|
||||
|
@ -728,19 +716,22 @@ namespace Mist{
|
|||
counter++;
|
||||
}
|
||||
|
||||
if (!hasHeader && (!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){
|
||||
tsStream.initializeMetadata(myMeta, tmpTrackId, packetId);
|
||||
size_t idx = M.trackIDToIndex(packetId, getpid());
|
||||
INFO_MSG("PacketID: %" PRIu64 ", pid: %d, mapped to %zu", packetId, getpid(), idx);
|
||||
if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
|
||||
tsStream.initializeMetadata(meta, tmpTrackId, packetId);
|
||||
INFO_MSG("InitializingMeta for track %zu -> %zu", tmpTrackId, packetId);
|
||||
idx = M.trackIDToIndex(packetId, getpid());
|
||||
}
|
||||
|
||||
if (!hasHeader){
|
||||
headerPack.getString("data", data, dataLen);
|
||||
uint64_t pBPos = headerPack.getInt("bpos");
|
||||
|
||||
// keyframe data exists, so always add 19 bytes keyframedata.
|
||||
long long packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0;
|
||||
long long packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11;
|
||||
myMeta.update(headerPack.getTime(), packOffset, packetId, dataLen, entId,
|
||||
headerPack.hasMember("keyframe"), packSendSize);
|
||||
uint32_t packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0;
|
||||
size_t packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11;
|
||||
meta.update(headerPack.getTime(), packOffset, idx, dataLen, entId,
|
||||
headerPack.hasMember("keyframe"), packSendSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -766,19 +757,18 @@ namespace Mist{
|
|||
counter++;
|
||||
}
|
||||
|
||||
if (!hasHeader && (!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){
|
||||
tsStream.initializeMetadata(myMeta, tmpTrackId, packetId);
|
||||
if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
|
||||
tsStream.initializeMetadata(meta, tmpTrackId, packetId);
|
||||
idx = M.trackIDToIndex(packetId, getpid());
|
||||
}
|
||||
|
||||
if (!hasHeader){
|
||||
headerPack.getString("data", data, dataLen);
|
||||
uint64_t pBPos = headerPack.getInt("bpos");
|
||||
|
||||
// keyframe data exists, so always add 19 bytes keyframedata.
|
||||
long long packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0;
|
||||
long long packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11;
|
||||
myMeta.update(headerPack.getTime(), packOffset, packetId, dataLen, entId,
|
||||
headerPack.hasMember("keyframe"), packSendSize);
|
||||
meta.update(headerPack.getTime(), packOffset, idx, dataLen, entId,
|
||||
headerPack.hasMember("keyframe"), packSendSize);
|
||||
}
|
||||
tsStream.getEarliestPacket(headerPack);
|
||||
}
|
||||
|
@ -790,10 +780,8 @@ namespace Mist{
|
|||
if (streamIsLive){return true;}
|
||||
|
||||
INFO_MSG("write header file...");
|
||||
std::ofstream oFile((config->getString("input") + ".dtsh").c_str());
|
||||
|
||||
oFile << myMeta.toJSON().toNetPacked();
|
||||
oFile.close();
|
||||
M.toFile((config->getString("input") + ".dtsh").c_str());
|
||||
in.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -802,34 +790,30 @@ namespace Mist{
|
|||
|
||||
bool inputHLS::openStreamSource(){return true;}
|
||||
|
||||
void inputHLS::getNext(bool smart){
|
||||
void inputHLS::getNext(size_t idx){
|
||||
INSANE_MSG("Getting next");
|
||||
uint32_t tid = 0;
|
||||
bool finished = false;
|
||||
if (selectedTracks.size()){tid = *selectedTracks.begin();}
|
||||
if (userSelect.size()){tid = userSelect.begin()->first;}
|
||||
thisPacket.null();
|
||||
while (config->is_active && (needsLock() || nProxy.userClient.isAlive())){
|
||||
while (config->is_active && (needsLock() || keepAlive())){
|
||||
|
||||
// Check if we have a packet
|
||||
bool hasPacket = false;
|
||||
if (streamIsLive){
|
||||
hasPacket = tsStream.hasPacketOnEachTrack() || (segDowner.atEnd() && tsStream.hasPacket());
|
||||
}else{
|
||||
hasPacket = tsStream.hasPacket(getMappedTrackId(tid));
|
||||
hasPacket = tsStream.hasPacket(M.getID(idx) & 0xFFFF);
|
||||
}
|
||||
|
||||
// Yes? Excellent! Read and return it.
|
||||
if (hasPacket){
|
||||
// Read
|
||||
if (myMeta.live){
|
||||
if (M.getLive()){
|
||||
tsStream.getEarliestPacket(thisPacket);
|
||||
tid = getOriginalTrackId(currentPlaylist, thisPacket.getTrackId());
|
||||
if (!tid){
|
||||
INFO_MSG("Track %" PRIu64 " on PLS %u -> %" PRIu32, thisPacket.getTrackId(), currentPlaylist, tid);
|
||||
continue;
|
||||
}
|
||||
tid = M.trackIDToIndex((((uint64_t)currentPlaylist) << 16) + thisPacket.getTrackId(), getpid());
|
||||
}else{
|
||||
tsStream.getPacket(getMappedTrackId(tid), thisPacket);
|
||||
tsStream.getPacket(M.getID(idx) & 0xFFFF, thisPacket);
|
||||
}
|
||||
if (!thisPacket){
|
||||
FAIL_MSG("Could not getNext TS packet!");
|
||||
|
@ -940,25 +924,19 @@ namespace Mist{
|
|||
}
|
||||
|
||||
// Note: bpos is overloaded here for playlist entry!
|
||||
void inputHLS::seek(int seekTime){
|
||||
void inputHLS::seek(uint64_t seekTime, size_t idx){
|
||||
plsTimeOffset.clear();
|
||||
plsLastTime.clear();
|
||||
plsInterval.clear();
|
||||
tsStream.clear();
|
||||
int trackId = 0;
|
||||
uint64_t trackId = M.getID(idx);
|
||||
|
||||
unsigned long plistEntry = 0xFFFFFFFFull;
|
||||
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;}
|
||||
thisBPos = keyIt->getBpos();
|
||||
}
|
||||
if (thisBPos < plistEntry){
|
||||
plistEntry = thisBPos;
|
||||
trackId = *it;
|
||||
}
|
||||
unsigned long plistEntry = 0;
|
||||
|
||||
DTSC::Keys keys(M.keys(idx));
|
||||
for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); i++){
|
||||
if (keys.getTime(i) > seekTime){break;}
|
||||
plistEntry = keys.getBpos(i);
|
||||
}
|
||||
|
||||
if (plistEntry < 1){
|
||||
|
@ -995,7 +973,7 @@ namespace Mist{
|
|||
}
|
||||
}
|
||||
|
||||
int inputHLS::getEntryId(int playlistId, uint64_t bytePos){
|
||||
size_t inputHLS::getEntryId(uint32_t playlistId, uint64_t bytePos){
|
||||
if (bytePos == 0){return 0;}
|
||||
tthread::lock_guard<tthread::mutex> guard(entryMutex);
|
||||
for (int i = 0; i < listEntries[playlistId].size(); i++){
|
||||
|
@ -1248,9 +1226,9 @@ namespace Mist{
|
|||
/// return the playlist id from which we need to read the first upcoming segment
|
||||
/// by timestamp.
|
||||
/// this will keep the playlists in sync while reading segments.
|
||||
int inputHLS::firstSegment(){
|
||||
size_t inputHLS::firstSegment(){
|
||||
// Only one selected? Immediately return the right playlist.
|
||||
if (selectedTracks.size() == 1){return getMappedTrackPlaylist(*selectedTracks.begin());}
|
||||
if (userSelect.size() == 1){return ((M.getID(userSelect.begin()->first) >> 16) & 0xFFFF);}
|
||||
uint64_t firstTimeStamp = 0;
|
||||
int tmpId = -1;
|
||||
int segCount = 0;
|
||||
|
|
|
@ -27,8 +27,8 @@ namespace Mist{
|
|||
uint64_t bytePos;
|
||||
uint64_t mUTC; ///< UTC unix millis timestamp of first packet, if known
|
||||
float duration;
|
||||
unsigned int timestamp;
|
||||
unsigned int wait;
|
||||
uint64_t timestamp;
|
||||
uint64_t wait;
|
||||
char ivec[16];
|
||||
char keyAES[16];
|
||||
};
|
||||
|
@ -75,10 +75,10 @@ namespace Mist{
|
|||
int noChangeCount;
|
||||
uint64_t lastFileIndex;
|
||||
|
||||
int waitTime;
|
||||
uint64_t waitTime;
|
||||
PlaylistType playlistType;
|
||||
unsigned int lastTimestamp;
|
||||
unsigned int startTime;
|
||||
uint64_t lastTimestamp;
|
||||
uint64_t startTime;
|
||||
uint64_t nextUTC; ///< If non-zero, the UTC timestamp of the next segment on this playlist
|
||||
char keyAES[16];
|
||||
std::map<std::string, std::string> keys;
|
||||
|
@ -103,7 +103,7 @@ namespace Mist{
|
|||
int version;
|
||||
int targetDuration;
|
||||
bool endPlaylist;
|
||||
int currentPlaylist;
|
||||
uint64_t currentPlaylist;
|
||||
|
||||
bool allowRemap; ///< True if the next packet may remap the timestamps
|
||||
bool allowSoftRemap; ///< True if the next packet may soft-remap the timestamps
|
||||
|
@ -113,7 +113,7 @@ namespace Mist{
|
|||
std::map<int, uint64_t> plsLastTime;
|
||||
std::map<int, uint64_t> plsInterval;
|
||||
|
||||
int currentIndex;
|
||||
size_t currentIndex;
|
||||
std::string currentFile;
|
||||
|
||||
TS::Stream tsStream; ///< Used for parsing the incoming ts stream
|
||||
|
@ -128,9 +128,9 @@ namespace Mist{
|
|||
bool preSetup();
|
||||
bool readHeader();
|
||||
bool needHeader(){return true;}
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
|
||||
|
||||
FILE *inFile;
|
||||
FILE *tsFile;
|
||||
|
||||
|
@ -141,10 +141,7 @@ namespace Mist{
|
|||
|
||||
void parseStreamHeader();
|
||||
|
||||
uint32_t getMappedTrackId(uint64_t id);
|
||||
uint32_t getMappedTrackPlaylist(uint64_t id);
|
||||
uint64_t getOriginalTrackId(uint32_t playlistId, uint32_t id);
|
||||
int getEntryId(int playlistId, uint64_t bytePos);
|
||||
size_t getEntryId(uint32_t playlistId, uint64_t bytePos);
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
|
@ -40,297 +40,210 @@ namespace Mist{
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inputISMV::preRun(){
|
||||
// open File
|
||||
inFile = fopen(config->getString("input").c_str(), "r");
|
||||
if (!inFile){return false;}
|
||||
return true;
|
||||
return inFile; // True if not null
|
||||
}
|
||||
|
||||
bool inputISMV::readHeader(){
|
||||
if (!inFile){return false;}
|
||||
meta.reInit(streamName);
|
||||
// parse ismv header
|
||||
fseek(inFile, 0, SEEK_SET);
|
||||
std::string ftyp;
|
||||
readBox("ftyp", ftyp);
|
||||
if (ftyp == ""){return false;}
|
||||
std::string boxRes;
|
||||
readBox("moov", boxRes);
|
||||
if (boxRes == ""){return false;}
|
||||
MP4::MOOV hdrBox;
|
||||
hdrBox.read(boxRes);
|
||||
parseMoov(hdrBox);
|
||||
int tId;
|
||||
std::vector<MP4::trunSampleInformation> trunSamples;
|
||||
std::vector<std::string> initVecs;
|
||||
std::string mdat;
|
||||
unsigned int currOffset;
|
||||
JSON::Value lastPack;
|
||||
unsigned int lastBytePos = 0;
|
||||
std::map<int, unsigned int> currentDuration;
|
||||
unsigned int curBytePos = ftell(inFile);
|
||||
// Skip mandatory ftyp box
|
||||
MP4::skipBox(inFile);
|
||||
|
||||
MP4::MOOV moovBox;
|
||||
moovBox.read(inFile);
|
||||
parseMoov(moovBox);
|
||||
|
||||
std::map<size_t, uint64_t> duration;
|
||||
|
||||
uint64_t currOffset;
|
||||
uint64_t lastBytePos = 0;
|
||||
uint64_t curBytePos = ftell(inFile);
|
||||
// parse fragments form here
|
||||
while (parseFrag(tId, trunSamples, initVecs, mdat)){
|
||||
if (!currentDuration.count(tId)){currentDuration[tId] = 0;}
|
||||
|
||||
size_t tId;
|
||||
std::vector<MP4::trunSampleInformation> trunSamples;
|
||||
|
||||
while (readMoofSkipMdat(tId, trunSamples) && !feof(inFile)){
|
||||
if (!duration.count(tId)){duration[tId] = 0;}
|
||||
currOffset = 8;
|
||||
int i = 0;
|
||||
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];}
|
||||
lastPack["duration"] = trunSamples[i].sampleDuration;
|
||||
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;
|
||||
for (std::vector<MP4::trunSampleInformation>::iterator it = trunSamples.begin();
|
||||
it != trunSamples.end(); it++){
|
||||
bool first = (it == trunSamples.begin());
|
||||
|
||||
int64_t offsetConv = 0;
|
||||
if (M.getType(tId) == "video"){offsetConv = it->sampleOffset / 10000;}
|
||||
|
||||
if (first){
|
||||
lastBytePos = curBytePos;
|
||||
}else{
|
||||
if (i == 0){
|
||||
lastPack["keyframe"] = 1;
|
||||
lastPack["bpos"] = curBytePos;
|
||||
}
|
||||
++lastBytePos;
|
||||
}
|
||||
myMeta.update(lastPack);
|
||||
currentDuration[tId] += trunSamples[i].sampleDuration;
|
||||
currOffset += trunSamples[i].sampleSize;
|
||||
i++;
|
||||
|
||||
meta.update(duration[tId] / 10000, offsetConv, tId, it->sampleSize, lastBytePos, first);
|
||||
duration[tId] += it->sampleDuration;
|
||||
currOffset += it->sampleSize;
|
||||
}
|
||||
curBytePos = ftell(inFile);
|
||||
}
|
||||
myMeta.toFile(config->getString("input") + ".dtsh");
|
||||
M.toFile(config->getString("input") + ".dtsh");
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputISMV::getNext(bool smart){
|
||||
static JSON::Value thisPack;
|
||||
thisPack.null();
|
||||
if (!buffered.size()){
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
int tId = buffered.begin()->trackId;
|
||||
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));
|
||||
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;}
|
||||
thisPack["offset"] = (uint64_t)(buffered.begin()->offset / 10000);
|
||||
}else{
|
||||
if (buffered.begin()->isKeyFrame){thisPack["keyframe"] = 1;}
|
||||
}
|
||||
thisPack["bpos"] = (uint64_t)buffered.begin()->position;
|
||||
void inputISMV::getNext(size_t idx){
|
||||
thisPacket.null();
|
||||
|
||||
if (!buffered.size()){return;}
|
||||
|
||||
seekPos thisPos = *buffered.begin();
|
||||
buffered.erase(buffered.begin());
|
||||
if (buffered.size() < 2 * selectedTracks.size()){
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
parseFragHeader(*it, lastKeyNum[*it]);
|
||||
lastKeyNum[*it]++;
|
||||
|
||||
fseek(inFile, thisPos.position, SEEK_SET);
|
||||
dataPointer.allocate(thisPos.size);
|
||||
fread(dataPointer, thisPos.size, 1, inFile);
|
||||
|
||||
thisPacket.genericFill(thisPos.time / 10000, thisPos.offset / 10000, thisPos.trackId,
|
||||
dataPointer, thisPos.size, 0, thisPos.isKeyFrame);
|
||||
|
||||
if (buffered.size() < 2 * (idx == INVALID_TRACK_ID ? M.getValidTracks().size() : 1)){
|
||||
std::set<size_t> validTracks = M.getValidTracks();
|
||||
if (idx != INVALID_TRACK_ID){
|
||||
validTracks.clear();
|
||||
validTracks.insert(idx);
|
||||
}
|
||||
|
||||
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
|
||||
bufferFragmentData(*it, ++lastKeyNum[*it]);
|
||||
}
|
||||
}
|
||||
std::string tmpStr = thisPack.toNetPacked();
|
||||
thisPacket.reInit(tmpStr.data(), tmpStr.size());
|
||||
if (idx != INVALID_TRACK_ID && thisPacket.getTrackId() != M.getID(idx)){getNext(idx);}
|
||||
}
|
||||
|
||||
///\brief Overloads Input::atKeyFrame, for ISMV always sets the keyframe number
|
||||
bool inputISMV::atKeyFrame(){return thisPacket.getFlag("keyframe");}
|
||||
|
||||
void inputISMV::seek(int seekTime){
|
||||
void inputISMV::seek(uint64_t seekTime, size_t idx){
|
||||
buffered.clear();
|
||||
// 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?
|
||||
break;
|
||||
}
|
||||
}
|
||||
i--;
|
||||
DEBUG_MSG(DLVL_DEVEL, "ISMV seek frag %d:%d", *it, i);
|
||||
parseFragHeader(*it, i);
|
||||
lastKeyNum[*it] = i + 1;
|
||||
}
|
||||
}
|
||||
lastKeyNum.clear();
|
||||
|
||||
void inputISMV::trackSelect(std::string trackSpec){
|
||||
selectedTracks.clear();
|
||||
size_t index;
|
||||
while (trackSpec != ""){
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
||||
if (index != std::string::npos){
|
||||
trackSpec.erase(0, index + 1);
|
||||
}else{
|
||||
trackSpec = "";
|
||||
}
|
||||
// Select tracks
|
||||
std::set<size_t> validTracks = M.getValidTracks();
|
||||
if (idx != INVALID_TRACK_ID){
|
||||
validTracks.clear();
|
||||
validTracks.insert(idx);
|
||||
}
|
||||
|
||||
// For each selected track
|
||||
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
|
||||
DTSC::Keys keys(M.keys(*it));
|
||||
uint32_t i;
|
||||
for (i = keys.getFirstValid(); i < keys.getEndValid(); i++){
|
||||
if (keys.getTime(i) >= seekTime){break;}
|
||||
}
|
||||
INFO_MSG("ISMV seek frag %zu:%" PRIu32, *it, i);
|
||||
bufferFragmentData(*it, i);
|
||||
lastKeyNum[*it] = i;
|
||||
}
|
||||
seek(0);
|
||||
}
|
||||
|
||||
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")){
|
||||
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")){
|
||||
MP4::TKHD subContent = (MP4::TKHD &)content.getContent(j);
|
||||
trackId = subContent.getTrackID();
|
||||
myMeta.tracks[trackId].trackID = trackId;
|
||||
}
|
||||
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")){
|
||||
MP4::HDLR subsubContent = (MP4::HDLR &)subContent.getContent(k);
|
||||
if (subsubContent.getHandlerType() == "soun"){
|
||||
myMeta.tracks[trackId].type = "audio";
|
||||
}
|
||||
if (subsubContent.getHandlerType() == "vide"){
|
||||
myMeta.tracks[trackId].type = "video";
|
||||
}
|
||||
}
|
||||
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")){
|
||||
MP4::STBL stblBox = (MP4::STBL &)subsubContent.getContent(l);
|
||||
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")){
|
||||
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].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")){
|
||||
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].codec = "H264";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
|
||||
for (std::deque<MP4::TRAK>::iterator it = trak.begin(); it != trak.end(); it++){
|
||||
size_t tNumber = meta.addTrack();
|
||||
|
||||
meta.setID(tNumber, it->getChild<MP4::TKHD>().getTrackID());
|
||||
|
||||
MP4::MDIA mdia = it->getChild<MP4::MDIA>();
|
||||
|
||||
MP4::HDLR hdlr = mdia.getChild<MP4::HDLR>();
|
||||
if (hdlr.getHandlerType() == "soun"){meta.setType(tNumber, "audio");}
|
||||
if (hdlr.getHandlerType() == "vide"){meta.setType(tNumber, "video");}
|
||||
|
||||
MP4::STSD stsd = mdia.getChild<MP4::MINF>().getChild<MP4::STBL>().getChild<MP4::STSD>();
|
||||
for (size_t i = 0; i < stsd.getEntryCount(); ++i){
|
||||
if (stsd.getEntry(i).isType("mp4a") || stsd.getEntry(i).isType("enca")){
|
||||
MP4::MP4A mp4aBox = (MP4::MP4A &)stsd.getEntry(i);
|
||||
std::string tmpStr;
|
||||
tmpStr += (char)((mp4aBox.toAACInit() & 0xFF00) >> 8);
|
||||
tmpStr += (char)(mp4aBox.toAACInit() & 0x00FF);
|
||||
meta.setCodec(tNumber, "AAC");
|
||||
meta.setInit(tNumber, tmpStr);
|
||||
meta.setChannels(tNumber, mp4aBox.getChannelCount());
|
||||
meta.setSize(tNumber, mp4aBox.getSampleSize());
|
||||
meta.setRate(tNumber, mp4aBox.getSampleRate());
|
||||
}
|
||||
if (stsd.getEntry(i).isType("avc1") || stsd.getEntry(i).isType("encv")){
|
||||
MP4::AVC1 avc1Box = (MP4::AVC1 &)stsd.getEntry(i);
|
||||
meta.setCodec(tNumber, "H264");
|
||||
meta.setInit(tNumber, avc1Box.getCLAP().payload(), avc1Box.getCLAP().payloadSize());
|
||||
meta.setHeight(tNumber, avc1Box.getHeight());
|
||||
meta.setWidth(tNumber, avc1Box.getWidth());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool inputISMV::parseFrag(int &tId, std::vector<MP4::trunSampleInformation> &trunSamples,
|
||||
std::vector<std::string> &initVecs, std::string &mdat){
|
||||
tId = -1;
|
||||
bool inputISMV::readMoofSkipMdat(size_t &tId, std::vector<MP4::trunSampleInformation> &trunSamples){
|
||||
tId = INVALID_TRACK_ID;
|
||||
trunSamples.clear();
|
||||
initVecs.clear();
|
||||
mdat.clear();
|
||||
std::string boxRes;
|
||||
readBox("moof", boxRes);
|
||||
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")){
|
||||
MP4::TRAF trafBox = (MP4::TRAF &)moof.getContent(i);
|
||||
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++){
|
||||
trunSamples.push_back(trunBox.getSampleInformation(i));
|
||||
}
|
||||
}
|
||||
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"){
|
||||
MP4::UUID_SampleEncryption uuidBox = (MP4::UUID_SampleEncryption &)trafBox.getContent(j);
|
||||
for (unsigned int i = 0; i < uuidBox.getSampleCount(); i++){
|
||||
initVecs.push_back(uuidBox.getSample(i).InitializationVector);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
moof.read(inFile);
|
||||
|
||||
if (feof(inFile)){return false;}
|
||||
|
||||
MP4::TRAF trafBox = moof.getChild<MP4::TRAF>();
|
||||
for (size_t j = 0; j < trafBox.getContentCount(); j++){
|
||||
if (trafBox.getContent(j).isType("trun")){
|
||||
MP4::TRUN trunBox = (MP4::TRUN &)trafBox.getContent(j);
|
||||
for (size_t i = 0; i < trunBox.getSampleInformationCount(); i++){
|
||||
trunSamples.push_back(trunBox.getSampleInformation(i));
|
||||
}
|
||||
}
|
||||
if (trafBox.getContent(j).isType("tfhd")){
|
||||
tId = M.trackIDToIndex(((MP4::TFHD &)trafBox.getContent(j)).getTrackID(), getpid());
|
||||
}
|
||||
}
|
||||
readBox("mdat", mdat);
|
||||
if (mdat == ""){return false;}
|
||||
return true;
|
||||
|
||||
MP4::skipBox(inFile);
|
||||
return !feof(inFile);
|
||||
}
|
||||
|
||||
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;}
|
||||
MP4::MOOF moof;
|
||||
moof.read(boxRes);
|
||||
void inputISMV::bufferFragmentData(size_t trackId, uint32_t keyNum){
|
||||
INFO_MSG("Bpos seek for %zu/%" PRIu32, trackId, keyNum);
|
||||
if (trackId == INVALID_TRACK_ID){return;}
|
||||
DTSC::Keys keys(M.keys(trackId));
|
||||
INFO_MSG("Key %" PRIu32 " / %zu", keyNum, keys.getEndValid());
|
||||
if (keyNum >= keys.getEndValid()){return;}
|
||||
uint64_t currentPosition = keys.getBpos(keyNum);
|
||||
uint64_t currentTime = keys.getTime(keyNum) * 10000;
|
||||
INFO_MSG("Bpos seek to %" PRIu64, currentPosition);
|
||||
fseek(inFile, currentPosition, SEEK_SET);
|
||||
|
||||
MP4::MOOF moofBox;
|
||||
moofBox.read(inFile);
|
||||
|
||||
MP4::TRAF trafBox = moofBox.getChild<MP4::TRAF>();
|
||||
|
||||
MP4::TRUN trunBox;
|
||||
MP4::UUID_SampleEncryption uuidBox; /*LTS*/
|
||||
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")){
|
||||
trunBox = (MP4::TRUN &)trafBox.getContent(j);
|
||||
}
|
||||
if (trafBox.getContent(j).isType("tfhd")){
|
||||
if (trackId != ((MP4::TFHD &)trafBox.getContent(j)).getTrackID()){
|
||||
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"){
|
||||
uuidBox = (MP4::UUID_SampleEncryption &)trafBox.getContent(j);
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
MP4::UUID_SampleEncryption uuidBox;
|
||||
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 (M.getID(trackId) != ((MP4::TFHD &)trafBox.getContent(j)).getTrackID()){
|
||||
FAIL_MSG("Trackids do not match");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastPos = ftell(inFile) + 8;
|
||||
|
||||
currentPosition = ftell(inFile) + 8;
|
||||
for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++){
|
||||
seekPos myPos;
|
||||
myPos.position = lastPos;
|
||||
myPos.position = currentPosition;
|
||||
myPos.trackId = trackId;
|
||||
myPos.time = lastTime;
|
||||
myPos.time = currentTime;
|
||||
myPos.duration = trunBox.getSampleInformation(i).sampleDuration;
|
||||
myPos.size = trunBox.getSampleInformation(i).sampleSize;
|
||||
if (trunBox.getFlags() & MP4::trunsampleOffsets){
|
||||
|
@ -340,29 +253,9 @@ namespace Mist{
|
|||
myPos.offset = 0;
|
||||
}
|
||||
myPos.isKeyFrame = (i == 0);
|
||||
/*LTS-START*/
|
||||
if (i <= uuidBox.getSampleCount()){myPos.iVec = uuidBox.getSample(i).InitializationVector;}
|
||||
/*LTS-END*/
|
||||
lastTime += trunBox.getSampleInformation(i).sampleDuration;
|
||||
lastPos += trunBox.getSampleInformation(i).sampleSize;
|
||||
currentTime += trunBox.getSampleInformation(i).sampleDuration;
|
||||
currentPosition += trunBox.getSampleInformation(i).sampleSize;
|
||||
buffered.insert(myPos);
|
||||
}
|
||||
}
|
||||
|
||||
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)){
|
||||
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));
|
||||
fread(tmpBox, boxSize, 1, inFile);
|
||||
result = std::string(tmpBox, boxSize);
|
||||
free(tmpBox);
|
||||
}
|
||||
}// namespace Mist
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <mist/mp4.h>
|
||||
#include <mist/mp4_encryption.h>
|
||||
#include <mist/mp4_generic.h>
|
||||
#include <mist/util.h>
|
||||
#include <set>
|
||||
|
||||
namespace Mist{
|
||||
|
@ -11,12 +12,12 @@ namespace Mist{
|
|||
if (time < rhs.time){return true;}
|
||||
return (time == rhs.time && trackId < rhs.trackId);
|
||||
}
|
||||
long long int position;
|
||||
int trackId;
|
||||
long long int time;
|
||||
long long int duration;
|
||||
int size;
|
||||
long long int offset;
|
||||
uint64_t position;
|
||||
size_t trackId;
|
||||
uint64_t time;
|
||||
uint64_t duration;
|
||||
uint64_t size;
|
||||
int64_t offset;
|
||||
bool isKeyFrame;
|
||||
std::string iVec;
|
||||
};
|
||||
|
@ -30,20 +31,19 @@ namespace Mist{
|
|||
bool checkArguments();
|
||||
bool preRun();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
bool atKeyFrame();
|
||||
virtual void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
virtual void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
|
||||
|
||||
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);
|
||||
bool readMoofSkipMdat(size_t &tId, std::vector<MP4::trunSampleInformation> &trunSamples);
|
||||
|
||||
void bufferFragmentData(size_t trackId, uint32_t keyNum);
|
||||
std::set<seekPos> buffered;
|
||||
std::map<int, int> lastKeyNum;
|
||||
std::map<size_t, uint32_t> lastKeyNum;
|
||||
|
||||
Util::ResizeablePointer dataPointer;
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
|
@ -51,44 +51,45 @@ namespace Mist{
|
|||
|
||||
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";
|
||||
meta.reInit(config->getString("streamname"));
|
||||
size_t tNum = meta.addTrack();
|
||||
meta.setID(tNum, tNum);
|
||||
meta.setType(tNum, "audio");
|
||||
meta.setCodec(tNum, "MP3");
|
||||
// Create header file from MP3 data
|
||||
char header[10];
|
||||
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);
|
||||
((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
|
||||
size_t filePos = ftell(inFile);
|
||||
uint64_t filePos = ftell(inFile);
|
||||
fread(header, 4, 1, inFile);
|
||||
fseek(inFile, filePos, SEEK_SET);
|
||||
|
||||
Mpeg::MP2Info mp2Info = Mpeg::parseMP2Header(header);
|
||||
myMeta.tracks[1].rate = mp2Info.sampleRate;
|
||||
myMeta.tracks[1].channels = mp2Info.channels;
|
||||
meta.setRate(tNum, mp2Info.sampleRate);
|
||||
meta.setChannels(tNum, mp2Info.channels);
|
||||
|
||||
getNext();
|
||||
while (thisPacket){
|
||||
myMeta.update(thisPacket);
|
||||
meta.update(thisPacket);
|
||||
getNext();
|
||||
}
|
||||
|
||||
fseek(inFile, 0, SEEK_SET);
|
||||
timestamp = 0;
|
||||
myMeta.toFile(config->getString("input") + ".dtsh");
|
||||
M.toFile(config->getString("input") + ".dtsh");
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputMP3::getNext(bool smart){
|
||||
void inputMP3::getNext(size_t idx){
|
||||
thisPacket.null();
|
||||
static char packHeader[3000];
|
||||
size_t filePos = ftell(inFile);
|
||||
|
@ -107,7 +108,7 @@ namespace Mist{
|
|||
}
|
||||
}
|
||||
if (!offset){
|
||||
DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %lu", filePos);
|
||||
FAIL_MSG("Sync byte not found from offset %zu", filePos);
|
||||
return;
|
||||
}
|
||||
filePos += offset;
|
||||
|
@ -141,34 +142,16 @@ namespace Mist{
|
|||
fseek(inFile, filePos + dataSize, SEEK_SET);
|
||||
|
||||
// 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
|
||||
std::string tmpStr = thisPack.toNetPacked();
|
||||
thisPacket.reInit(tmpStr.data(), tmpStr.size());
|
||||
thisPacket.genericFill(timestamp, 0, idx, packHeader, dataSize, filePos, false);
|
||||
|
||||
// Update the internal timestamp
|
||||
timestamp += (sampleCount / (sampleRate / 1000));
|
||||
}
|
||||
|
||||
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;}
|
||||
seekPos = keys[i].getBpos();
|
||||
timestamp = keys[i].getTime();
|
||||
}
|
||||
timestamp = seekTime;
|
||||
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::seek(uint64_t seekTime, size_t idx){
|
||||
DTSC::Keys keys(M.keys(idx));
|
||||
uint32_t keyNum = keys.getNumForTime(seekTime);
|
||||
fseek(inFile, keys.getBpos(keyNum), SEEK_SET);
|
||||
timestamp = keys.getTime(keyNum);
|
||||
}
|
||||
}// namespace Mist
|
||||
|
|
|
@ -21,9 +21,9 @@ namespace Mist{
|
|||
bool checkArguments();
|
||||
bool preRun();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
|
||||
|
||||
double timestamp;
|
||||
|
||||
FILE *inFile;
|
||||
|
|
|
@ -32,6 +32,7 @@ namespace Mist{
|
|||
stcoBox.clear();
|
||||
co64Box.clear();
|
||||
stco64 = false;
|
||||
trackId = 0;
|
||||
}
|
||||
|
||||
uint64_t mp4TrackHeader::size(){return (stszBox.asBox() ? stszBox.getSampleCount() : 0);}
|
||||
|
@ -45,6 +46,7 @@ namespace Mist{
|
|||
MP4::MDIA mdiaBox = trakBox.getChild<MP4::MDIA>();
|
||||
|
||||
timeScale = mdiaBox.getChild<MP4::MDHD>().getTimeScale();
|
||||
trackId = trakBox.getChild<MP4::TKHD>().getTrackID();
|
||||
|
||||
MP4::STBL stblBox = mdiaBox.getChild<MP4::MINF>().getChild<MP4::STBL>();
|
||||
|
||||
|
@ -148,6 +150,14 @@ namespace Mist{
|
|||
size = stszBox.getEntrySize(index);
|
||||
}
|
||||
|
||||
mp4TrackHeader &inputMP4::headerData(size_t trackID){
|
||||
static mp4TrackHeader none;
|
||||
for (std::deque<mp4TrackHeader>::iterator it = trackHeaders.begin(); it != trackHeaders.end(); it++){
|
||||
if (it->trackId == trackID){return *it;}
|
||||
}
|
||||
return none;
|
||||
}
|
||||
|
||||
inputMP4::inputMP4(Util::Config *cfg) : Input(cfg){
|
||||
malSize = 4; // initialise data read buffer to 0;
|
||||
data = (char *)malloc(malSize);
|
||||
|
@ -200,9 +210,8 @@ namespace Mist{
|
|||
return false;
|
||||
}
|
||||
|
||||
uint32_t trackNo = 0;
|
||||
|
||||
// first we get the necessary header parts
|
||||
size_t tNumber = 0;
|
||||
while (!feof(inFile)){
|
||||
std::string boxType = MP4::readBoxType(inFile);
|
||||
if (boxType == "erro"){break;}
|
||||
|
@ -213,7 +222,8 @@ namespace Mist{
|
|||
|
||||
std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
|
||||
for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
|
||||
headerData[++trackNo].read(*trakIt);
|
||||
trackHeaders.push_back(mp4TrackHeader());
|
||||
trackHeaders.rbegin()->read(*trakIt);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -228,7 +238,9 @@ namespace Mist{
|
|||
if (readExistingHeader()){return true;}
|
||||
HIGH_MSG("Not read existing header");
|
||||
|
||||
trackNo = 0;
|
||||
meta.reInit(streamName);
|
||||
|
||||
tNumber = 0;
|
||||
// Create header file from MP4 data
|
||||
while (!feof(inFile)){
|
||||
std::string boxType = MP4::readBoxType(inFile);
|
||||
|
@ -241,96 +253,95 @@ 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;
|
||||
myMeta.tracks[trackNo].trackID = trackNo;
|
||||
|
||||
MP4::TKHD tkhdBox = trakIt->getChild<MP4::TKHD>();
|
||||
if (tkhdBox.getWidth() > 0){
|
||||
myMeta.tracks[trackNo].width = tkhdBox.getWidth();
|
||||
myMeta.tracks[trackNo].height = tkhdBox.getHeight();
|
||||
}
|
||||
|
||||
MP4::MDIA mdiaBox = trakIt->getChild<MP4::MDIA>();
|
||||
|
||||
MP4::MDHD mdhdBox = mdiaBox.getChild<MP4::MDHD>();
|
||||
uint64_t timescale = mdhdBox.getTimeScale();
|
||||
myMeta.tracks[trackNo].lang = mdhdBox.getLanguage();
|
||||
|
||||
std::string hdlrType = mdiaBox.getChild<MP4::HDLR>().getHandlerType();
|
||||
if (hdlrType != "vide" && hdlrType != "soun" && hdlrType != "sbtl"){
|
||||
headerData.erase(trackNo);
|
||||
myMeta.tracks.erase(trackNo);
|
||||
break;
|
||||
INFO_MSG("Unsupported handler: %s", hdlrType.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
tNumber = meta.addTrack();
|
||||
|
||||
MP4::TKHD tkhdBox = trakIt->getChild<MP4::TKHD>();
|
||||
if (tkhdBox.getWidth() > 0){
|
||||
meta.setWidth(tNumber, tkhdBox.getWidth());
|
||||
meta.setHeight(tNumber, tkhdBox.getHeight());
|
||||
}
|
||||
meta.setID(tNumber, tkhdBox.getTrackID());
|
||||
|
||||
MP4::MDHD mdhdBox = mdiaBox.getChild<MP4::MDHD>();
|
||||
uint64_t timescale = mdhdBox.getTimeScale();
|
||||
meta.setLang(tNumber, mdhdBox.getLanguage());
|
||||
|
||||
MP4::STBL stblBox = mdiaBox.getChild<MP4::MINF>().getChild<MP4::STBL>();
|
||||
|
||||
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", tNumber, sType.c_str());
|
||||
|
||||
if (sType == "avc1" || sType == "h264" || sType == "mp4v"){
|
||||
MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
|
||||
myMeta.tracks[trackNo].type = "video";
|
||||
myMeta.tracks[trackNo].codec = "H264";
|
||||
|
||||
myMeta.tracks[trackNo].width = vEntryBox.getWidth();
|
||||
myMeta.tracks[trackNo].height = vEntryBox.getHeight();
|
||||
|
||||
meta.setType(tNumber, "video");
|
||||
meta.setCodec(tNumber, "H264");
|
||||
if (!meta.getWidth(tNumber)){
|
||||
meta.setWidth(tNumber, vEntryBox.getWidth());
|
||||
meta.setHeight(tNumber, vEntryBox.getHeight());
|
||||
}
|
||||
MP4::Box initBox = vEntryBox.getCLAP();
|
||||
if (initBox.isType("avcC")){
|
||||
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
|
||||
meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
|
||||
}
|
||||
initBox = vEntryBox.getPASP();
|
||||
if (initBox.isType("avcC")){
|
||||
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
|
||||
meta.setInit(tNumber, 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...
|
||||
if (!myMeta.tracks[trackNo].width){
|
||||
/// this is a hacky way around invalid FLV data (since it gets ignored nearly
|
||||
/// everywhere, but we do need correct data...
|
||||
if (!meta.getWidth(tNumber)){
|
||||
h264::sequenceParameterSet sps;
|
||||
sps.fromDTSCInit(myMeta.tracks[trackNo].init);
|
||||
sps.fromDTSCInit(meta.getInit(tNumber));
|
||||
h264::SPSMeta spsChar = sps.getCharacteristics();
|
||||
myMeta.tracks[trackNo].width = spsChar.width;
|
||||
myMeta.tracks[trackNo].height = spsChar.height;
|
||||
meta.setWidth(tNumber, spsChar.width);
|
||||
meta.setHeight(tNumber, spsChar.height);
|
||||
}
|
||||
}
|
||||
if (sType == "hev1" || sType == "hvc1"){
|
||||
MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
|
||||
myMeta.tracks[trackNo].type = "video";
|
||||
myMeta.tracks[trackNo].codec = "HEVC";
|
||||
if (!myMeta.tracks[trackNo].width){
|
||||
myMeta.tracks[trackNo].width = vEntryBox.getWidth();
|
||||
myMeta.tracks[trackNo].height = vEntryBox.getHeight();
|
||||
meta.setType(tNumber, "video");
|
||||
meta.setCodec(tNumber, "HEVC");
|
||||
if (!meta.getWidth(tNumber)){
|
||||
meta.setWidth(tNumber, vEntryBox.getWidth());
|
||||
meta.setHeight(tNumber, vEntryBox.getHeight());
|
||||
}
|
||||
MP4::Box initBox = vEntryBox.getCLAP();
|
||||
if (initBox.isType("hvcC")){
|
||||
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
|
||||
meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
|
||||
}
|
||||
initBox = vEntryBox.getPASP();
|
||||
if (initBox.isType("hvcC")){
|
||||
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
|
||||
meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
|
||||
}
|
||||
}
|
||||
if (sType == "mp4a" || sType == "aac " || sType == "ac-3"){
|
||||
MP4::AudioSampleEntry &aEntryBox = (MP4::AudioSampleEntry &)sEntryBox;
|
||||
myMeta.tracks[trackNo].type = "audio";
|
||||
myMeta.tracks[trackNo].channels = aEntryBox.getChannelCount();
|
||||
myMeta.tracks[trackNo].rate = aEntryBox.getSampleRate();
|
||||
meta.setType(tNumber, "audio");
|
||||
meta.setChannels(tNumber, aEntryBox.getChannelCount());
|
||||
meta.setRate(tNumber, aEntryBox.getSampleRate());
|
||||
|
||||
if (sType == "ac-3"){
|
||||
myMeta.tracks[trackNo].codec = "AC3";
|
||||
meta.setCodec(tNumber, "AC3");
|
||||
}else{
|
||||
MP4::ESDS esdsBox = (MP4::ESDS &)(aEntryBox.getCodecBox());
|
||||
myMeta.tracks[trackNo].codec = esdsBox.getCodec();
|
||||
myMeta.tracks[trackNo].init = esdsBox.getInitData();
|
||||
meta.setCodec(tNumber, esdsBox.getCodec());
|
||||
meta.setInit(tNumber, esdsBox.getInitData());
|
||||
}
|
||||
myMeta.tracks[trackNo].size = 16; ///\todo this might be nice to calculate from mp4 file;
|
||||
meta.setSize(tNumber, 16); ///\todo this might be nice to calculate from mp4 file;
|
||||
}
|
||||
|
||||
if (sType == "tx3g"){// plain text subtitles
|
||||
myMeta.tracks[trackNo].type = "meta";
|
||||
myMeta.tracks[trackNo].codec = "subtitle";
|
||||
meta.setType(tNumber, "meta");
|
||||
meta.setCodec(tNumber, "subtitle");
|
||||
}
|
||||
|
||||
MP4::STSS stssBox = stblBox.getChild<MP4::STSS>();
|
||||
|
@ -374,7 +385,7 @@ namespace Mist{
|
|||
nextFirstChunk =
|
||||
(stscIndex + 1 < stscCount ? stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1 : stcoCount);
|
||||
}
|
||||
BsetPart.keyframe = (myMeta.tracks[trackNo].type == "video" && stssIndex < stssCount &&
|
||||
BsetPart.keyframe = (meta.getType(tNumber) == "video" && stssIndex < stssCount &&
|
||||
stszIndex + 1 == stssBox.getSampleNumber(stssIndex));
|
||||
if (BsetPart.keyframe){++stssIndex;}
|
||||
// in bpos set
|
||||
|
@ -417,12 +428,12 @@ namespace Mist{
|
|||
long long packSendSize = 0;
|
||||
packSendSize = 24 + (BsetPart.timeOffset ? 17 : 0) + (BsetPart.bpos ? 15 : 0) + 19 +
|
||||
stszBox.getEntrySize(stszIndex) + 11 - 2 + 19;
|
||||
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo,
|
||||
stszBox.getEntrySize(stszIndex) - 2, BsetPart.bpos, true, packSendSize);
|
||||
meta.update(BsetPart.time, BsetPart.timeOffset, tNumber,
|
||||
stszBox.getEntrySize(stszIndex) - 2, BsetPart.bpos, true, packSendSize);
|
||||
}
|
||||
}else{
|
||||
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo,
|
||||
stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe);
|
||||
meta.update(BsetPart.time, BsetPart.timeOffset, tNumber,
|
||||
stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -436,11 +447,11 @@ namespace Mist{
|
|||
clearerr(inFile);
|
||||
|
||||
// outputting dtsh file
|
||||
myMeta.toFile(config->getString("input") + ".dtsh");
|
||||
M.toFile(config->getString("input") + ".dtsh");
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputMP4::getNext(bool smart){// get next part from track in stream
|
||||
void inputMP4::getNext(size_t idx){// get next part from track in stream
|
||||
if (curPositions.empty()){
|
||||
thisPacket.null();
|
||||
return;
|
||||
|
@ -450,17 +461,17 @@ namespace Mist{
|
|||
curPositions.erase(curPositions.begin());
|
||||
|
||||
bool isKeyframe = false;
|
||||
if (nextKeyframe[curPart.trackID] < myMeta.tracks[curPart.trackID].keys.size()){
|
||||
DTSC::Keys keys(M.keys(curPart.trackID));
|
||||
uint32_t nextKeyNum = nextKeyframe[curPart.trackID];
|
||||
if (nextKeyNum < keys.getEndValid()){
|
||||
// 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 (meta.getType(curPart.trackID) == "video" && curPart.time == keys.getTime(nextKeyNum)){
|
||||
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 (keys.getTime(nextKeyNum) <= curPart.time){
|
||||
++nextKeyframe[curPart.trackID];
|
||||
++nextKeyNum;
|
||||
}
|
||||
}
|
||||
if (fseeko(inFile, curPart.bpos, SEEK_SET)){
|
||||
|
@ -478,85 +489,63 @@ namespace Mist{
|
|||
return;
|
||||
}
|
||||
|
||||
if (myMeta.tracks[curPart.trackID].codec == "subtitle"){
|
||||
if (M.getCodec(curPart.trackID) == "subtitle"){
|
||||
unsigned int txtLen = Bit::btohs(data);
|
||||
if (!txtLen && false){
|
||||
curPart.index++;
|
||||
return getNext(smart);
|
||||
// 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["time"] = curPart.time;
|
||||
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;
|
||||
|
||||
// thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data+2, txtLen, 0/*Note: no bpos*/, isKeyframe);
|
||||
return getNext(idx);
|
||||
}
|
||||
static JSON::Value thisPack;
|
||||
thisPack.null();
|
||||
thisPack["trackid"] = curPart.trackID;
|
||||
thisPack["bpos"] = curPart.bpos; //(long long)fileSource.tellg();
|
||||
thisPack["data"] = std::string(data + 2, txtLen);
|
||||
thisPack["time"] = curPart.time;
|
||||
if (curPart.duration){thisPack["duration"] = curPart.duration;}
|
||||
thisPack["keyframe"] = true;
|
||||
std::string tmpStr = thisPack.toNetPacked();
|
||||
thisPacket.reInit(tmpStr.data(), tmpStr.size());
|
||||
}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, isKeyframe);
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (curPart.index < headerData(M.getID(curPart.trackID)).size()){
|
||||
headerData(M.getID(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(uint64_t seekTime, size_t idx){// seek to a point
|
||||
nextKeyframe.clear();
|
||||
// for all tracks
|
||||
curPositions.clear();
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
nextKeyframe[*it] = 0;
|
||||
mp4PartTime addPart;
|
||||
addPart.bpos = 0;
|
||||
addPart.size = 0;
|
||||
addPart.time = 0;
|
||||
addPart.trackID = *it;
|
||||
// for all indexes in those tracks
|
||||
for (unsigned int i = 0; i < headerData[*it].size(); i++){
|
||||
// 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
|
||||
//
|
||||
if (myMeta.tracks[*it].keys[(nextKeyframe[*it])].getTime() < addPart.time){
|
||||
nextKeyframe[*it]++;
|
||||
}
|
||||
if (addPart.time >= seekTime){
|
||||
addPart.index = i;
|
||||
// use addPart thingy in time set and break
|
||||
curPositions.insert(addPart);
|
||||
break;
|
||||
}// end if time > seektime
|
||||
}// end for all indexes
|
||||
}// rof all tracks
|
||||
if (idx != INVALID_TRACK_ID){
|
||||
handleSeek(seekTime, idx);
|
||||
}else{
|
||||
std::set<size_t> tracks = M.getValidTracks();
|
||||
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
|
||||
handleSeek(seekTime, *it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void inputMP4::trackSelect(std::string trackSpec){
|
||||
selectedTracks.clear();
|
||||
long long int index;
|
||||
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);
|
||||
if (index != std::string::npos){
|
||||
trackSpec.erase(0, index + 1);
|
||||
}else{
|
||||
trackSpec = "";
|
||||
void inputMP4::handleSeek(uint64_t seekTime, size_t idx){
|
||||
nextKeyframe[idx] = 0;
|
||||
mp4PartTime addPart;
|
||||
addPart.trackID = idx;
|
||||
// for all stsz samples in those tracks
|
||||
mp4TrackHeader &thisHeader = headerData(M.getID(idx));
|
||||
size_t headerDataSize = thisHeader.size();
|
||||
DTSC::Keys keys(M.keys(idx));
|
||||
for (size_t i = 0; i < headerDataSize; i++){
|
||||
thisHeader.getPart(i, addPart.bpos, addPart.size, addPart.time, addPart.offset, addPart.duration);
|
||||
if (keys.getTime(nextKeyframe[idx]) < addPart.time){nextKeyframe[idx]++;}
|
||||
if (addPart.time >= seekTime){
|
||||
addPart.index = i;
|
||||
curPositions.insert(addPart);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
namespace Mist{
|
||||
class mp4PartTime{
|
||||
public:
|
||||
mp4PartTime() : time(0), offset(0), trackID(0), bpos(0), size(0), index(0), duration(0){}
|
||||
mp4PartTime() : time(0), duration(0), offset(0), trackID(0), bpos(0), size(0), index(0){}
|
||||
bool operator<(const mp4PartTime &rhs) const{
|
||||
if (time < rhs.time){return true;}
|
||||
if (time > rhs.time){return false;}
|
||||
|
@ -40,6 +40,7 @@ namespace Mist{
|
|||
class mp4TrackHeader{
|
||||
public:
|
||||
mp4TrackHeader();
|
||||
size_t trackId;
|
||||
void read(MP4::TRAK &trakBox);
|
||||
MP4::STCO stcoBox;
|
||||
MP4::CO64 co64Box;
|
||||
|
@ -80,21 +81,24 @@ namespace Mist{
|
|||
bool preRun();
|
||||
bool readHeader();
|
||||
bool needHeader(){return true;}
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
|
||||
void handleSeek(uint64_t seekTime, size_t idx);
|
||||
|
||||
FILE *inFile;
|
||||
|
||||
std::map<unsigned int, mp4TrackHeader> headerData;
|
||||
mp4TrackHeader &headerData(size_t trackID);
|
||||
|
||||
std::deque<mp4TrackHeader> trackHeaders;
|
||||
std::set<mp4PartTime> curPositions;
|
||||
|
||||
// remember last seeked keyframe;
|
||||
std::map<unsigned int, unsigned int> nextKeyframe;
|
||||
std::map<size_t, uint32_t> 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
|
||||
char *data; ///\todo rename this variable to a more sensible name, it is a temporary piece of
|
||||
/// memory to read from files
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
|
@ -21,9 +21,8 @@ namespace Mist{
|
|||
retval["time"] = time;
|
||||
retval["trackid"] = tid;
|
||||
std::string tmpString = "";
|
||||
for (unsigned int i = 0; i < parts.size(); i++){tmpString += parts[i];}
|
||||
for (size_t 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);
|
||||
retval["bpos"] = bytepos;
|
||||
if (myCodec == OGG::THEORA){
|
||||
if (!theora::isHeader(tmpString.data(), tmpString.size())){
|
||||
|
@ -34,12 +33,6 @@ namespace Mist{
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
unsigned long oggTrack::getBlockSize(unsigned int vModeIndex){//WTF!!?
|
||||
return blockSize[vModes[vModeIndex].blockFlag];
|
||||
|
||||
}
|
||||
*/
|
||||
inputOGG::inputOGG(Util::Config *cfg) : Input(cfg){
|
||||
capa["name"] = "OGG";
|
||||
capa["desc"] = "This input allows streaming of OGG files as Video on Demand.";
|
||||
|
@ -68,70 +61,74 @@ namespace Mist{
|
|||
///\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;
|
||||
unsigned int tid = bosPage.getBitstreamSerialNumber();
|
||||
size_t tid = bosPage.getBitstreamSerialNumber();
|
||||
size_t idx = M.trackIDToIndex(tid, getpid());
|
||||
if (idx == INVALID_TRACK_ID){idx = meta.addTrack();}
|
||||
if (memcmp(bosPage.getSegment(0) + 1, "theora", 6) == 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
|
||||
myMeta.tracks[tid].type = "video";
|
||||
myMeta.tracks[tid].codec = "theora";
|
||||
myMeta.tracks[tid].trackID = tid;
|
||||
myMeta.tracks[tid].fpks = (tmpHead.getFRN() * 1000) / tmpHead.getFRD();
|
||||
myMeta.tracks[tid].height = tmpHead.getPICH();
|
||||
myMeta.tracks[tid].width = tmpHead.getPICW();
|
||||
if (!myMeta.tracks[tid].init.size()){
|
||||
myMeta.tracks[tid].init = (char)((bosPage.getPayloadSize() >> 8) & 0xFF);
|
||||
myMeta.tracks[tid].init += (char)(bosPage.getPayloadSize() & 0xFF);
|
||||
myMeta.tracks[tid].init.append(bosPage.getSegment(0), bosPage.getSegmentLen(0));
|
||||
oggTracks[idx].codec = OGG::THEORA;
|
||||
oggTracks[idx].msPerFrame = (double)(tmpHead.getFRD() * 1000) / (double)tmpHead.getFRN(); // this should be: 1000/( tmpHead.getFRN()/ tmpHead.getFRD() )
|
||||
oggTracks[idx].KFGShift = tmpHead.getKFGShift(); // store KFGShift for granule calculations
|
||||
meta.setType(idx, "video");
|
||||
meta.setCodec(idx, "theora");
|
||||
meta.setID(idx, tid);
|
||||
meta.setFpks(idx, (double)(tmpHead.getFRN() * 1000) / tmpHead.getFRD());
|
||||
meta.setHeight(idx, tmpHead.getPICH());
|
||||
meta.setWidth(idx, tmpHead.getPICW());
|
||||
if (!M.getInit(idx).size()){
|
||||
std::string init = " ";
|
||||
Bit::htobs((char *)init.data(), bosPage.getPayloadSize());
|
||||
init.append(bosPage.getSegment(0), bosPage.getSegmentLen(0));
|
||||
meta.setInit(idx, init);
|
||||
}
|
||||
INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str());
|
||||
INFO_MSG("Track with id %zu is %s", tid, M.getCodec(tid).c_str());
|
||||
}
|
||||
if (memcmp(bosPage.getSegment(0) + 1, "vorbis", 6) == 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();
|
||||
oggTracks[idx].codec = OGG::VORBIS;
|
||||
oggTracks[idx].msPerFrame = (double)1000.0f / tmpHead.getAudioSampleRate();
|
||||
oggTracks[idx].channels = tmpHead.getAudioChannels();
|
||||
oggTracks[idx].blockSize[0] = 1 << tmpHead.getBlockSize0();
|
||||
oggTracks[idx].blockSize[1] = 1 << tmpHead.getBlockSize1();
|
||||
DEVEL_MSG("vorbis trackID: %zu msperFrame %f ", tid, oggTracks[idx].msPerFrame);
|
||||
// Abusing .contBuffer for temporarily storing the idHeader
|
||||
bosPage.getSegment(0, oggTracks[tid].contBuffer);
|
||||
bosPage.getSegment(0, oggTracks[idx].contBuffer);
|
||||
|
||||
myMeta.tracks[tid].type = "audio";
|
||||
myMeta.tracks[tid].codec = "vorbis";
|
||||
myMeta.tracks[tid].rate = tmpHead.getAudioSampleRate();
|
||||
myMeta.tracks[tid].trackID = tid;
|
||||
myMeta.tracks[tid].channels = tmpHead.getAudioChannels();
|
||||
INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str());
|
||||
meta.setType(idx, "audio");
|
||||
meta.setCodec(idx, "vorbis");
|
||||
meta.setRate(idx, tmpHead.getAudioSampleRate());
|
||||
meta.setID(idx, tid);
|
||||
meta.setChannels(idx, tmpHead.getAudioChannels());
|
||||
INFO_MSG("Track with id %zu is %s", tid, M.getCodec(idx).c_str());
|
||||
}
|
||||
if (memcmp(bosPage.getSegment(0), "OpusHead", 8) == 0){
|
||||
oggTracks[tid].codec = OGG::OPUS;
|
||||
myMeta.tracks[tid].type = "audio";
|
||||
myMeta.tracks[tid].codec = "opus";
|
||||
myMeta.tracks[tid].rate = 48000;
|
||||
myMeta.tracks[tid].trackID = tid;
|
||||
myMeta.tracks[tid].init.assign(bosPage.getSegment(0), bosPage.getSegmentLen(0));
|
||||
myMeta.tracks[tid].channels = myMeta.tracks[tid].init[9];
|
||||
INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str());
|
||||
oggTracks[idx].codec = OGG::OPUS;
|
||||
meta.setType(idx, "audio");
|
||||
meta.setCodec(idx, "opus");
|
||||
meta.setRate(idx, 48000);
|
||||
meta.setID(idx, tid);
|
||||
meta.setInit(idx, bosPage.getSegment(0), bosPage.getSegmentLen(0));
|
||||
meta.setChannels(idx, M.getInit(idx)[9]);
|
||||
INFO_MSG("Track with id %zu is %s", tid, M.getCodec(idx).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool inputOGG::readHeader(){
|
||||
meta.reInit(config->getString("streamname"), true);
|
||||
OGG::Page myPage;
|
||||
fseek(inFile, 0, SEEK_SET);
|
||||
while (myPage.read(inFile)){// assumes all headers are sent before any data
|
||||
unsigned int tid = myPage.getBitstreamSerialNumber();
|
||||
size_t tid = myPage.getBitstreamSerialNumber();
|
||||
size_t idx = M.trackIDToIndex(tid, getpid());
|
||||
if (myPage.getHeaderType() & OGG::BeginOfStream){
|
||||
parseBeginOfStream(myPage);
|
||||
INFO_MSG("Read BeginOfStream for track %d", tid);
|
||||
INFO_MSG("Read BeginOfStream for track %zu", tid);
|
||||
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<size_t, OGG::oggTrack>::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){
|
||||
if (!it->second.parsedHeaders){
|
||||
readAllHeaders = false;
|
||||
break;
|
||||
|
@ -139,143 +136,142 @@ namespace Mist{
|
|||
}
|
||||
if (readAllHeaders){break;}
|
||||
|
||||
// INFO_MSG("tid: %d",tid);
|
||||
|
||||
// Parsing headers
|
||||
if (myMeta.tracks[tid].codec == "theora"){
|
||||
for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){
|
||||
unsigned long len = myPage.getSegmentLen(i);
|
||||
if (M.getCodec(idx) == "theora"){
|
||||
for (size_t i = 0; i < myPage.getAllSegments().size(); i++){
|
||||
size_t len = myPage.getSegmentLen(i);
|
||||
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!");
|
||||
FAIL_MSG("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);
|
||||
std::string init = M.getInit(idx);
|
||||
init += (char)((len >> 8) & 0xFF);
|
||||
init += (char)(len & 0xFF);
|
||||
init.append(myPage.getSegment(i), len);
|
||||
meta.setInit(idx, init);
|
||||
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;
|
||||
std::string init = M.getInit(idx);
|
||||
init += (char)((len >> 8) & 0xFF);
|
||||
init += (char)(len & 0xFF);
|
||||
init.append(myPage.getSegment(i), len);
|
||||
meta.setInit(idx, init);
|
||||
oggTracks[idx].lastGran = 0;
|
||||
oggTracks[idx].parsedHeaders = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (myMeta.tracks[tid].codec == "vorbis"){
|
||||
for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){
|
||||
unsigned long len = myPage.getSegmentLen(i);
|
||||
if (M.getCodec(idx) == "vorbis"){
|
||||
for (size_t i = 0; i < myPage.getAllSegments().size(); i++){
|
||||
size_t len = myPage.getSegmentLen(i);
|
||||
vorbis::header tmpHead((char *)myPage.getSegment(i), len);
|
||||
if (!tmpHead.isHeader()){
|
||||
DEBUG_MSG(DLVL_FAIL, "Header read failed!");
|
||||
FAIL_MSG("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;
|
||||
std::string init = M.getInit(idx);
|
||||
init += (char)0x02;
|
||||
// ID header size
|
||||
for (unsigned int j = 0; j < (oggTracks[tid].contBuffer.size() / 255); j++){
|
||||
myMeta.tracks[tid].init += (char)0xFF;
|
||||
for (size_t j = 0; j < (oggTracks[idx].contBuffer.size() / 255); j++){
|
||||
init += (char)0xFF;
|
||||
}
|
||||
myMeta.tracks[tid].init += (char)(oggTracks[tid].contBuffer.size() % 255);
|
||||
init += (char)(oggTracks[idx].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);
|
||||
for (size_t j = 0; j < (len / 255); j++){init += (char)0xFF;}
|
||||
init += (char)(len % 255);
|
||||
init += oggTracks[idx].contBuffer;
|
||||
oggTracks[idx].contBuffer.clear();
|
||||
init.append(myPage.getSegment(i), len);
|
||||
meta.setInit(idx, init);
|
||||
break;
|
||||
}
|
||||
case 5:{
|
||||
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
|
||||
oggTracks[tid].vModes = tmpHead.readModeDeque(oggTracks[tid].channels);
|
||||
oggTracks[tid].parsedHeaders = true;
|
||||
std::string init = M.getInit(idx);
|
||||
init.append(myPage.getSegment(i), len);
|
||||
meta.setInit(idx, init);
|
||||
oggTracks[idx].vModes = tmpHead.readModeDeque(oggTracks[idx].channels);
|
||||
oggTracks[idx].parsedHeaders = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "opus"){oggTracks[tid].parsedHeaders = true;}
|
||||
if (M.getCodec(idx) == "opus"){oggTracks[idx].parsedHeaders = true;}
|
||||
}
|
||||
|
||||
for (std::map<long unsigned int, OGG::oggTrack>::iterator it = oggTracks.begin();
|
||||
it != oggTracks.end(); it++){
|
||||
for (std::map<size_t, 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);
|
||||
INFO_MSG("Finding first data for track %zu", it->first);
|
||||
position tmp = seekFirstData(it->first);
|
||||
if (tmp.trackID){
|
||||
currentPositions.insert(tmp);
|
||||
}else{
|
||||
INFO_MSG("missing track: %lu", it->first);
|
||||
}
|
||||
currentPositions.insert(tmp);
|
||||
}
|
||||
getNext();
|
||||
while (thisPacket){
|
||||
myMeta.update(thisPacket);
|
||||
meta.update(thisPacket);
|
||||
getNext();
|
||||
}
|
||||
|
||||
myMeta.toFile(config->getString("input") + ".dtsh");
|
||||
meta.toFile(config->getString("input") + ".dtsh");
|
||||
return true;
|
||||
}
|
||||
|
||||
position inputOGG::seekFirstData(long long unsigned int tid){
|
||||
position inputOGG::seekFirstData(size_t idx){
|
||||
fseek(inFile, 0, SEEK_SET);
|
||||
position res;
|
||||
res.time = 0;
|
||||
res.trackID = tid;
|
||||
res.trackID = idx;
|
||||
res.segmentNo = 0;
|
||||
bool readSuccesfull = true;
|
||||
bool quitloop = false;
|
||||
while (!quitloop){
|
||||
quitloop = true;
|
||||
res.bytepos = ftell(inFile);
|
||||
readSuccesfull = oggTracks[tid].myPage.read(inFile);
|
||||
readSuccesfull = oggTracks[idx].myPage.read(inFile);
|
||||
if (!readSuccesfull){
|
||||
quitloop = true; // break :(
|
||||
break;
|
||||
}
|
||||
if (oggTracks[tid].myPage.getBitstreamSerialNumber() != tid){
|
||||
if (oggTracks[idx].myPage.getBitstreamSerialNumber() != M.getID(idx)){
|
||||
quitloop = false;
|
||||
continue;
|
||||
}
|
||||
if (oggTracks[tid].myPage.getHeaderType() != OGG::Plain){
|
||||
if (oggTracks[idx].myPage.getHeaderType() != OGG::Plain){
|
||||
quitloop = false;
|
||||
continue;
|
||||
}
|
||||
if (oggTracks[tid].codec == OGG::OPUS){
|
||||
if (std::string(oggTracks[tid].myPage.getSegment(0), 2) == "Op"){quitloop = false;}
|
||||
if (oggTracks[idx].codec == OGG::OPUS){
|
||||
if (std::string(oggTracks[idx].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 (oggTracks[idx].codec == OGG::VORBIS){
|
||||
vorbis::header tmpHead((char *)oggTracks[idx].myPage.getSegment(0),
|
||||
oggTracks[idx].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 (oggTracks[idx].codec == OGG::THEORA){
|
||||
theora::header tmpHead((char *)oggTracks[idx].myPage.getSegment(0),
|
||||
oggTracks[idx].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());
|
||||
}
|
||||
INFO_MSG("seek first bytepos: %" PRIu64 " tid: %zu oggTracks[idx].myPage.getHeaderType(): %d ",
|
||||
res.bytepos, idx, oggTracks[idx].myPage.getHeaderType());
|
||||
if (!readSuccesfull){res.trackID = 0;}
|
||||
return res;
|
||||
}
|
||||
|
||||
void inputOGG::getNext(bool smart){
|
||||
void inputOGG::getNext(size_t idx){
|
||||
if (!currentPositions.size()){
|
||||
thisPacket.null();
|
||||
return;
|
||||
|
@ -287,7 +283,7 @@ namespace Mist{
|
|||
thisSegment.tid = curPos.trackID;
|
||||
thisSegment.time = curPos.time;
|
||||
thisSegment.bytepos = curPos.bytepos + curPos.segmentNo;
|
||||
unsigned int oldSegNo = curPos.segmentNo;
|
||||
size_t oldSegNo = curPos.segmentNo;
|
||||
fseek(inFile, curPos.bytepos, SEEK_SET);
|
||||
OGG::Page curPage;
|
||||
curPage.read(inFile);
|
||||
|
@ -296,7 +292,7 @@ namespace Mist{
|
|||
bool readFullPacket = false;
|
||||
if (curPos.segmentNo == curPage.getAllSegments().size() - 1){
|
||||
OGG::Page tmpPage;
|
||||
unsigned int bPos;
|
||||
uint64_t bPos;
|
||||
while (!readFullPacket){
|
||||
bPos = ftell(inFile); //<-- :(
|
||||
if (!tmpPage.read(inFile)){break;}
|
||||
|
@ -315,11 +311,11 @@ namespace Mist{
|
|||
}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) &&
|
||||
if ((oggTracks[curPos.trackID].codec == OGG::THEORA || oggTracks[curPos.trackID].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
|
||||
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){
|
||||
|
@ -328,37 +324,36 @@ namespace Mist{
|
|||
}
|
||||
readFullPacket = true;
|
||||
}
|
||||
std::string tmpStr = thisSegment.toJSON(oggTracks[thisSegment.tid].codec).toNetPacked();
|
||||
std::string tmpStr = thisSegment.toJSON(oggTracks[curPos.trackID].codec).toNetPacked();
|
||||
thisPacket.reInit(tmpStr.data(), tmpStr.size());
|
||||
|
||||
if (oggTracks[thisSegment.tid].codec == OGG::VORBIS){
|
||||
unsigned long blockSize = 0;
|
||||
if (oggTracks[curPos.trackID].codec == OGG::VORBIS){
|
||||
size_t blockSize = 0;
|
||||
Utils::bitstreamLSBF packet;
|
||||
packet.append((char *)curPage.getSegment(oldSegNo), curPage.getSegmentLen(oldSegNo));
|
||||
if (!packet.get(1)){
|
||||
// Read index first
|
||||
unsigned long vModeIndex = packet.get(vorbis::ilog(oggTracks[thisSegment.tid].vModes.size() - 1));
|
||||
size_t vModeIndex = packet.get(vorbis::ilog(oggTracks[curPos.trackID].vModes.size() - 1));
|
||||
blockSize =
|
||||
oggTracks[thisSegment.tid].blockSize[oggTracks[thisSegment.tid].vModes[vModeIndex].blockFlag]; // almost readable.
|
||||
oggTracks[curPos.trackID].blockSize[oggTracks[curPos.trackID].vModes[vModeIndex].blockFlag]; // almost
|
||||
// readable.
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_WARN, "Packet type != 0");
|
||||
WARN_MSG("Packet type != 0");
|
||||
}
|
||||
curPos.time += oggTracks[thisSegment.tid].msPerFrame * (blockSize / oggTracks[thisSegment.tid].channels);
|
||||
}else if (oggTracks[thisSegment.tid].codec == OGG::THEORA){
|
||||
curPos.time += oggTracks[curPos.trackID].msPerFrame * (blockSize / oggTracks[curPos.trackID].channels);
|
||||
}else if (oggTracks[curPos.trackID].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);
|
||||
uint64_t parseGranuleUpper = curPage.getGranulePosition() >> oggTracks[curPos.trackID].KFGShift;
|
||||
uint64_t parseGranuleLower(curPage.getGranulePosition() &
|
||||
((1 << oggTracks[curPos.trackID].KFGShift) - 1));
|
||||
thisSegment.time = oggTracks[curPos.trackID].msPerFrame * (parseGranuleUpper + parseGranuleLower - 1);
|
||||
curPos.time = thisSegment.time;
|
||||
std::string tmpStr = thisSegment.toJSON(oggTracks[thisSegment.tid].codec).toNetPacked();
|
||||
std::string tmpStr = thisSegment.toJSON(oggTracks[curPos.trackID].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){
|
||||
curPos.time += oggTracks[curPos.trackID].msPerFrame;
|
||||
}else if (oggTracks[curPos.trackID].codec == OGG::OPUS){
|
||||
if (thisSegment.parts.size()){
|
||||
curPos.time += Opus::Opus_getDuration(thisSegment.parts.front().data());
|
||||
}
|
||||
|
@ -366,21 +361,22 @@ namespace Mist{
|
|||
if (readFullPacket){currentPositions.insert(curPos);}
|
||||
}// getnext()
|
||||
|
||||
long long unsigned int inputOGG::calcGranuleTime(unsigned long tid, long long unsigned int granule){
|
||||
switch (oggTracks[tid].codec){
|
||||
uint64_t inputOGG::calcGranuleTime(size_t tid, uint64_t granule){
|
||||
size_t idx = M.trackIDToIndex(tid, getpid());
|
||||
switch (oggTracks[idx].codec){
|
||||
case OGG::VORBIS:
|
||||
return granule * oggTracks[tid].msPerFrame; //= samples * samples per second
|
||||
return granule * oggTracks[idx].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
|
||||
uint64_t parseGranuleUpper = granule >> oggTracks[idx].KFGShift;
|
||||
uint64_t parseGranuleLower = (granule & ((1 << oggTracks[idx].KFGShift) - 1));
|
||||
return (parseGranuleUpper + parseGranuleLower) * oggTracks[idx].msPerFrame; //= frames * msPerFrame
|
||||
break;
|
||||
}
|
||||
default: DEBUG_MSG(DLVL_WARN, "Unknown codec, can not calculate time from granule"); break;
|
||||
default: WARN_MSG("Unknown codec, can not calculate time from granule"); break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -398,27 +394,28 @@ namespace Mist{
|
|||
}
|
||||
#endif
|
||||
|
||||
void inputOGG::seek(int seekTime){
|
||||
void inputOGG::seek(uint64_t seekTime, size_t idx){
|
||||
currentPositions.clear();
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Seeking to %dms", seekTime);
|
||||
MEDIUM_MSG("Seeking to %" PRIu64 "ms", seekTime);
|
||||
|
||||
// for every track
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
|
||||
// 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++){
|
||||
if (ot->getTime() > seekTime){
|
||||
tmpPos.trackID = it->first;
|
||||
DTSC::Keys keys(M.keys(it->first));
|
||||
tmpPos.time = keys.getTime(keys.getFirstValid());
|
||||
tmpPos.bytepos = keys.getBpos(keys.getFirstValid());
|
||||
for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); ++i){
|
||||
if (keys.getTime(i) > seekTime){
|
||||
break;
|
||||
}else{
|
||||
tmpPos.time = ot->getTime();
|
||||
tmpPos.bytepos = ot->getBpos();
|
||||
tmpPos.time = keys.getTime(i);
|
||||
tmpPos.bytepos = keys.getBpos(i);
|
||||
}
|
||||
}
|
||||
INFO_MSG("Found %dms for track %lu at %llu bytepos %llu", seekTime, *it, tmpPos.time, tmpPos.bytepos);
|
||||
INFO_MSG("Found %" PRIu64 "ms for track %zu at %" PRIu64 " bytepos %" PRIu64, seekTime,
|
||||
it->first, tmpPos.time, tmpPos.bytepos);
|
||||
int backChrs = std::min((uint64_t)280, tmpPos.bytepos - 1);
|
||||
fseek(inFile, tmpPos.bytepos - backChrs, SEEK_SET);
|
||||
char buffer[300];
|
||||
|
@ -428,28 +425,15 @@ namespace Mist{
|
|||
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 @ %" PRIu64 ", track %zu", tmpPos.bytepos, it->first);
|
||||
continue;
|
||||
}
|
||||
tmpPos.segmentNo = backChrs - (loc - buffer);
|
||||
tmpPos.bytepos -= tmpPos.segmentNo;
|
||||
INFO_MSG("Track %lu, segment %llu found at bytepos %llu", *it, tmpPos.segmentNo, tmpPos.bytepos);
|
||||
INFO_MSG("Track %zu, segment %zu found at bytepos %" PRIu64, it->first, tmpPos.segmentNo,
|
||||
tmpPos.bytepos);
|
||||
|
||||
currentPositions.insert(tmpPos);
|
||||
}
|
||||
}
|
||||
|
||||
void inputOGG::trackSelect(std::string trackSpec){
|
||||
selectedTracks.clear();
|
||||
size_t index;
|
||||
while (trackSpec != ""){
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoll(trackSpec.substr(0, index).c_str()));
|
||||
if (index != std::string::npos){
|
||||
trackSpec.erase(0, index + 1);
|
||||
}else{
|
||||
trackSpec = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}// namespace Mist
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace Mist{
|
|||
|
||||
struct segPart{
|
||||
char *segData;
|
||||
unsigned int len;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
class segment{
|
||||
|
@ -25,43 +25,15 @@ namespace Mist{
|
|||
|
||||
struct position{
|
||||
bool operator<(const position &rhs) const{
|
||||
if (time < rhs.time){
|
||||
return true;
|
||||
}else{
|
||||
if (time == rhs.time){
|
||||
if (trackID < rhs.trackID){return true;}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (time < rhs.time){return true;}
|
||||
if (time > rhs.time){return false;}
|
||||
return trackID < rhs.trackID;
|
||||
}
|
||||
uint64_t trackID;
|
||||
uint64_t time;
|
||||
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 inputOGG : public Input{
|
||||
public:
|
||||
|
@ -72,18 +44,17 @@ namespace Mist{
|
|||
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);
|
||||
position seekFirstData(size_t tid);
|
||||
void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
|
||||
|
||||
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);
|
||||
std::map<size_t, OGG::oggTrack> oggTracks; // this remembers all metadata for every track
|
||||
std::set<segment> sortedSegments; // probably not needing this
|
||||
uint64_t calcGranuleTime(size_t tid, uint64_t granule);
|
||||
uint64_t calcSegmentDuration(size_t tid, std::string &segment);
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ void insertRTP(const uint64_t track, const RTP::Packet &p){
|
|||
///\param data The RTP Packet that needs to be sent
|
||||
///\param len The size of data
|
||||
///\param channel Not used here, but is kept for compatibility with sendTCP
|
||||
void sendUDP(void *socket, char *data, unsigned int len, unsigned int channel){
|
||||
void sendUDP(void *socket, const char *data, size_t len, uint8_t channel){
|
||||
((Socket::UDPConnection *)socket)->SendNow(data, len);
|
||||
if (mainConn){mainConn->addUp(len);}
|
||||
}
|
||||
|
@ -27,8 +27,10 @@ namespace Mist{
|
|||
|
||||
InputRTSP::InputRTSP(Util::Config *cfg) : Input(cfg){
|
||||
needAuth = false;
|
||||
setPacketOffset = false;
|
||||
packetOffset = 0;
|
||||
TCPmode = true;
|
||||
sdpState.myMeta = &myMeta;
|
||||
sdpState.myMeta = &meta;
|
||||
sdpState.incomingPacketCallback = incomingPacket;
|
||||
classPointer = this;
|
||||
standAlone = false;
|
||||
|
@ -153,28 +155,29 @@ namespace Mist{
|
|||
}
|
||||
if (sdpState.tracks.size()){
|
||||
bool atLeastOne = false;
|
||||
for (std::map<uint32_t, SDP::Track>::iterator it = sdpState.tracks.begin();
|
||||
for (std::map<size_t, SDP::Track>::iterator it = sdpState.tracks.begin();
|
||||
it != sdpState.tracks.end(); ++it){
|
||||
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);
|
||||
lastRequestedSetup = HTTP::URL(url.getUrl() + "/").link(it->second.control).getUrl();
|
||||
sendCommand("SETUP", lastRequestedSetup, "", &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());
|
||||
M.getTrackIdentifier(it->first).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", lastRequestedSetup, "", &extraHeaders);
|
||||
}
|
||||
if (tcpCon && transportSet){
|
||||
atLeastOne = true;
|
||||
continue;
|
||||
}
|
||||
FAIL_MSG("Could not setup track %s!", myMeta.tracks[it->first].getIdentifier().c_str());
|
||||
FAIL_MSG("Could not setup track %s!", M.getTrackIdentifier(it->first).c_str());
|
||||
tcpCon.close();
|
||||
return;
|
||||
}
|
||||
|
@ -183,11 +186,7 @@ namespace Mist{
|
|||
extraHeaders.clear();
|
||||
extraHeaders["Range"] = "npt=0.000-";
|
||||
sendCommand("PLAY", url.getUrl(), "", &extraHeaders);
|
||||
if (!TCPmode){
|
||||
connectedAt = Util::epoch() + 2208988800ll;
|
||||
}else{
|
||||
tcpCon.setBlocking(true);
|
||||
}
|
||||
if (TCPmode){tcpCon.setBlocking(true);}
|
||||
}
|
||||
|
||||
void InputRTSP::closeStreamSource(){
|
||||
|
@ -199,13 +198,9 @@ namespace Mist{
|
|||
IPC::sharedClient statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
|
||||
uint64_t startTime = Util::epoch();
|
||||
uint64_t lastPing = Util::bootSecs();
|
||||
uint64_t lastSecs = 0;
|
||||
while (config->is_active && nProxy.userClient.isAlive() && parsePacket()){
|
||||
while (keepAlive() && parsePacket()){
|
||||
handleUDP();
|
||||
// keep going
|
||||
nProxy.userClient.keepAlive();
|
||||
uint64_t currSecs = Util::bootSecs();
|
||||
if (currSecs - lastPing > 30){
|
||||
if (Util::bootSecs() - lastPing > 30){
|
||||
sendCommand("GET_PARAMETER", url.getUrl(), "");
|
||||
lastPing = Util::bootSecs();
|
||||
}
|
||||
|
@ -236,7 +231,7 @@ namespace Mist{
|
|||
statsPage.finish();
|
||||
if (!tcpCon){return "TCP connection closed";}
|
||||
if (!config->is_active){return "received deactivate signal";}
|
||||
if (!nProxy.userClient.isAlive()){return "buffer shutdown";}
|
||||
if (!keepAlive()){return "buffer shutdown";}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
|
@ -246,8 +241,7 @@ namespace Mist{
|
|||
do{
|
||||
// No new data? Sleep and retry, if connection still open
|
||||
if (!tcpCon.Received().size() || !tcpCon.Received().available(1)){
|
||||
if (!tcpCon.spool() && tcpCon && config->is_active && nProxy.userClient.isAlive()){
|
||||
nProxy.userClient.keepAlive();
|
||||
if (!tcpCon.spool() && tcpCon && keepAlive()){
|
||||
Util::sleep(waitTime);
|
||||
if (!mustHave){return tcpCon;}
|
||||
}
|
||||
|
@ -288,14 +282,15 @@ namespace Mist{
|
|||
seenSDP = true;
|
||||
sdpState.parseSDP(recH.body);
|
||||
recH.Clean();
|
||||
INFO_MSG("SDP contained %llu tracks", myMeta.tracks.size());
|
||||
INFO_MSG("SDP contained %zu tracks", M.getValidTracks().size());
|
||||
return true;
|
||||
}
|
||||
if (recH.hasHeader("Transport")){
|
||||
INFO_MSG("Received setup response");
|
||||
uint32_t trackNo = sdpState.parseSetup(recH, url.host, "");
|
||||
if (trackNo){
|
||||
INFO_MSG("Parsed transport for track: %lu", trackNo);
|
||||
recH.url = lastRequestedSetup;
|
||||
size_t trackNo = sdpState.parseSetup(recH, url.host, "");
|
||||
if (trackNo != INVALID_TRACK_ID){
|
||||
INFO_MSG("Parsed transport for track: %zu", trackNo);
|
||||
transportSet = true;
|
||||
}else{
|
||||
INFO_MSG("Could not parse transport string!");
|
||||
|
@ -319,17 +314,11 @@ namespace Mist{
|
|||
recH.Clean();
|
||||
return true;
|
||||
}
|
||||
if (!tcpCon.spool() && tcpCon && config->is_active && nProxy.userClient.isAlive()){
|
||||
nProxy.userClient.keepAlive();
|
||||
Util::sleep(waitTime);
|
||||
}
|
||||
if (!tcpCon.spool() && tcpCon && keepAlive()){Util::sleep(waitTime);}
|
||||
continue;
|
||||
}
|
||||
if (!tcpCon.Received().available(4)){
|
||||
if (!tcpCon.spool() && tcpCon && config->is_active && nProxy.userClient.isAlive()){
|
||||
nProxy.userClient.keepAlive();
|
||||
Util::sleep(waitTime);
|
||||
}
|
||||
if (!tcpCon.spool() && tcpCon && keepAlive()){Util::sleep(waitTime);}
|
||||
continue;
|
||||
}// a TCP RTP packet, but not complete yet
|
||||
|
||||
|
@ -345,19 +334,26 @@ namespace Mist{
|
|||
std::string tcpPacket = tcpCon.Received().remove(len + 4);
|
||||
RTP::Packet pkt(tcpPacket.data() + 4, len);
|
||||
uint8_t chan = tcpHead.data()[1];
|
||||
uint32_t trackNo = sdpState.getTrackNoForChannel(chan);
|
||||
EXTREME_MSG("Received %ub RTP packet #%u on channel %u, time %llu", len,
|
||||
(unsigned int)pkt.getSequence(), chan, pkt.getTimeStamp());
|
||||
if (!trackNo && (chan % 2) != 1){
|
||||
size_t trackNo = sdpState.getTrackNoForChannel(chan);
|
||||
EXTREME_MSG("Received %ub RTP packet #%u on channel %u, time %" PRIu32, len,
|
||||
pkt.getSequence(), chan, pkt.getTimeStamp());
|
||||
if ((trackNo == INVALID_TRACK_ID) && (chan % 2) != 1){
|
||||
WARN_MSG("Received packet for unknown track number on channel %u", chan);
|
||||
}
|
||||
if (trackNo){sdpState.tracks[trackNo].sorter.rtpSeq = pkt.getSequence();}
|
||||
if (trackNo != INVALID_TRACK_ID){
|
||||
sdpState.tracks[trackNo].sorter.rtpSeq = pkt.getSequence();
|
||||
}
|
||||
|
||||
sdpState.handleIncomingRTP(trackNo, pkt);
|
||||
if (trackNo != INVALID_TRACK_ID){
|
||||
if (!userSelect.count(trackNo)){
|
||||
userSelect[trackNo].reload(streamName, trackNo, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
|
||||
}
|
||||
sdpState.handleIncomingRTP(trackNo, pkt);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}while (tcpCon && config->is_active && nProxy.userClient.isAlive());
|
||||
}while (tcpCon && keepAlive());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -365,7 +361,7 @@ namespace Mist{
|
|||
bool InputRTSP::handleUDP(){
|
||||
if (TCPmode){return false;}
|
||||
bool r = false;
|
||||
for (std::map<uint32_t, SDP::Track>::iterator it = sdpState.tracks.begin();
|
||||
for (std::map<size_t, SDP::Track>::iterator it = sdpState.tracks.begin();
|
||||
it != sdpState.tracks.end(); ++it){
|
||||
Socket::UDPConnection &s = it->second.data;
|
||||
it->second.sorter.setCallback(it->first, insertRTP);
|
||||
|
@ -380,14 +376,29 @@ namespace Mist{
|
|||
if (!it->second.theirSSRC){it->second.theirSSRC = pack.getSSRC();}
|
||||
it->second.sorter.addPacket(pack);
|
||||
}
|
||||
if (Util::epoch() / 5 != it->second.rtcpSent){
|
||||
it->second.rtcpSent = Util::epoch() / 5;
|
||||
it->second.pack.sendRTCP_RR(connectedAt, it->second, it->first, myMeta, sendUDP);
|
||||
if (Util::bootSecs() != it->second.rtcpSent){
|
||||
it->second.rtcpSent = Util::bootSecs();
|
||||
it->second.pack.sendRTCP_RR(it->second, sendUDP);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void InputRTSP::incoming(const DTSC::Packet &pkt){nProxy.bufferLivePacket(pkt, myMeta);}
|
||||
void InputRTSP::incoming(const DTSC::Packet &pkt){
|
||||
if (!M.getBootMsOffset()){
|
||||
meta.setBootMsOffset(Util::bootMS() - pkt.getTime());
|
||||
packetOffset = 0;
|
||||
setPacketOffset = true;
|
||||
}else if (!setPacketOffset){
|
||||
packetOffset = (Util::bootMS() - pkt.getTime()) - M.getBootMsOffset();
|
||||
setPacketOffset = true;
|
||||
}
|
||||
static DTSC::Packet newPkt;
|
||||
char *pktData;
|
||||
size_t pktDataLen;
|
||||
pkt.getString("data", pktData, pktDataLen);
|
||||
bufferLivePacket(pkt.getTime() + packetOffset, pkt.getInt("offset"), pkt.getTrackId(), pktData,
|
||||
pktDataLen, 0, pkt.getFlag("keyframe"));
|
||||
}
|
||||
|
||||
}// namespace Mist
|
||||
|
|
|
@ -22,11 +22,9 @@ namespace Mist{
|
|||
bool checkArguments();
|
||||
bool needHeader(){return false;}
|
||||
bool readHeader(){return true;}
|
||||
void getNext(bool smart = true){}
|
||||
bool openStreamSource();
|
||||
void closeStreamSource();
|
||||
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);
|
||||
bool parsePacket(bool mustHave = false);
|
||||
|
@ -43,8 +41,10 @@ namespace Mist{
|
|||
bool TCPmode;
|
||||
bool needAuth;
|
||||
std::string session;
|
||||
long long connectedAt; ///< The timestamp the connection was made, as reference point for RTCP
|
||||
/// packets.
|
||||
bool setPacketOffset;
|
||||
int64_t packetOffset;
|
||||
|
||||
std::string lastRequestedSetup;
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
|
@ -47,25 +47,23 @@ namespace Mist{
|
|||
|
||||
bool InputSrt::readHeader(){
|
||||
if (!fileSource.good()){return false;}
|
||||
|
||||
myMeta.tracks[1].trackID = 1;
|
||||
myMeta.tracks[1].type = "meta";
|
||||
myMeta.tracks[1].codec = "subtitle";
|
||||
size_t idx = meta.addTrack();
|
||||
meta.setID(idx, 1);
|
||||
meta.setType(idx, "meta");
|
||||
meta.setCodec(idx, "subtitle");
|
||||
|
||||
getNext();
|
||||
while (thisPacket){
|
||||
myMeta.update(thisPacket);
|
||||
meta.update(thisPacket);
|
||||
getNext();
|
||||
}
|
||||
|
||||
// outputting dtsh file
|
||||
myMeta.toFile(config->getString("input") + ".dtsh");
|
||||
M.toFile(config->getString("input") + ".dtsh");
|
||||
return true;
|
||||
}
|
||||
|
||||
void InputSrt::getNext(bool smart){
|
||||
bool hasPacket = false;
|
||||
|
||||
void InputSrt::getNext(size_t idx){
|
||||
thisPacket.null();
|
||||
std::string line;
|
||||
|
||||
|
@ -93,7 +91,7 @@ namespace Mist{
|
|||
static JSON::Value thisPack;
|
||||
thisPack.null();
|
||||
thisPack["trackid"] = 1;
|
||||
thisPack["bpos"] = (uint64_t)fileSource.tellg();
|
||||
thisPack["bpos"] = fileSource.tellg();
|
||||
thisPack["data"] = data;
|
||||
thisPack["index"] = index;
|
||||
thisPack["time"] = timestamp;
|
||||
|
@ -133,12 +131,6 @@ namespace Mist{
|
|||
thisPacket.null();
|
||||
}
|
||||
|
||||
void InputSrt::seek(int seekTime){fileSource.seekg(0, fileSource.beg);}
|
||||
|
||||
void InputSrt::trackSelect(std::string trackSpec){
|
||||
// we only have one track..
|
||||
selectedTracks.clear();
|
||||
selectedTracks.insert(1);
|
||||
}
|
||||
void InputSrt::seek(uint64_t seekTime, size_t idx){fileSource.seekg(0, fileSource.beg);}
|
||||
|
||||
}// namespace Mist
|
||||
|
|
|
@ -16,9 +16,8 @@ namespace Mist{
|
|||
bool checkArguments();
|
||||
bool readHeader();
|
||||
bool preRun();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
|
||||
bool vtt;
|
||||
|
||||
FILE *inFile;
|
||||
|
|
|
@ -27,82 +27,112 @@ TS::Stream liveStream(true);
|
|||
Util::Config *cfgPointer = NULL;
|
||||
|
||||
#define THREAD_TIMEOUT 15
|
||||
std::map<unsigned long long, unsigned long long> threadTimer;
|
||||
std::map<size_t, uint64_t> threadTimer;
|
||||
|
||||
std::set<unsigned long> claimableThreads;
|
||||
std::set<size_t> claimableThreads;
|
||||
|
||||
void parseThread(void *ignored){
|
||||
void parseThread(void *mistIn){
|
||||
Mist::inputTS *input = reinterpret_cast<Mist::inputTS *>(mistIn);
|
||||
|
||||
int tid = -1;
|
||||
size_t tid = 0;
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
|
||||
if (claimableThreads.size()){
|
||||
tid = *claimableThreads.begin();
|
||||
claimableThreads.erase(claimableThreads.begin());
|
||||
}
|
||||
if (tid == -1){return;}
|
||||
}
|
||||
if (tid == 0){return;}
|
||||
|
||||
Mist::negotiationProxy myProxy;
|
||||
myProxy.streamName = globalStreamName;
|
||||
DTSC::Meta myMeta;
|
||||
Comms::Users userConn;
|
||||
DTSC::Meta meta;
|
||||
|
||||
if (liveStream.isDataTrack(tid)){
|
||||
bool dataTrack = liveStream.isDataTrack(tid);
|
||||
|
||||
if (dataTrack){
|
||||
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;
|
||||
}
|
||||
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, globalStreamName.c_str());
|
||||
myProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
myProxy.userClient.countAsViewer = false;
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
|
||||
if (!input->hasMeta()){input->reloadClientMeta();}
|
||||
}
|
||||
meta.reInit(globalStreamName, false);
|
||||
}
|
||||
|
||||
size_t idx = meta.trackIDToIndex(tid, getpid());
|
||||
|
||||
threadTimer[tid] = Util::bootSecs();
|
||||
while (Util::bootSecs() - threadTimer[tid] < THREAD_TIMEOUT && cfgPointer->is_active &&
|
||||
(!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive())){
|
||||
(!liveStream.isDataTrack(tid) || (userConn ? userConn.isAlive() : true))){
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
|
||||
threadTimer[tid] = Util::bootSecs();
|
||||
}
|
||||
if (liveStream.isDataTrack(tid)){myProxy.userClient.keepAlive();}
|
||||
if (liveStream.isDataTrack(tid)){userConn.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()))){
|
||||
liveStream.initializeMetadata(myMeta, tid);
|
||||
DTSC::Packet pack;
|
||||
liveStream.getPacket(tid, pack);
|
||||
if (!pack){
|
||||
Util::sleep(100);
|
||||
break;
|
||||
while (liveStream.hasPacket(tid) &&
|
||||
((Util::bootSecs() < startSecs + 2) && cfgPointer->is_active &&
|
||||
(!liveStream.isDataTrack(tid) || (userConn ? userConn.isAlive() : true)))){
|
||||
liveStream.parse(tid);
|
||||
if (liveStream.hasPacket(tid)){
|
||||
if (idx == INVALID_TRACK_ID){
|
||||
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
|
||||
liveStream.initializeMetadata(meta, tid);
|
||||
idx = meta.trackIDToIndex(tid, getpid());
|
||||
if (idx != INVALID_TRACK_ID){
|
||||
userConn.reload(globalStreamName, idx, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
|
||||
input->reloadClientMeta();
|
||||
}
|
||||
}
|
||||
if (idx == INVALID_TRACK_ID || !meta.trackValid(idx)){continue;}
|
||||
if (!meta.trackLoaded(idx)){meta.refresh();}
|
||||
DTSC::Packet pack;
|
||||
liveStream.getPacket(tid, pack);
|
||||
if (pack){
|
||||
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
|
||||
if (!input->hasMeta()){input->reloadClientMeta();}
|
||||
if (dataTrack){
|
||||
char *data;
|
||||
size_t dataLen;
|
||||
pack.getString("data", data, dataLen);
|
||||
input->bufferLivePacket(pack.getTime(), pack.getInt("offset"), idx, data, dataLen,
|
||||
pack.getInt("bpos"), pack.getFlag("keyframe"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (myMeta.tracks.count(tid)){
|
||||
myProxy.continueNegotiate(tid, myMeta, true);
|
||||
myProxy.bufferLivePacket(pack, myMeta);
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
|
||||
threadTimer[tid] = Util::bootSecs();
|
||||
}
|
||||
if (!liveStream.hasPacket(tid)){
|
||||
if (liveStream.isDataTrack(tid)){userConn.keepAlive();}
|
||||
Util::sleep(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string reason = "unknown reason";
|
||||
if (!(Util::bootSecs() - threadTimer[tid] < THREAD_TIMEOUT)){reason = "thread timeout";}
|
||||
if (!cfgPointer->is_active){reason = "input shutting down";}
|
||||
if (!(!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive())){
|
||||
if (!(!liveStream.isDataTrack(tid) || userConn.isAlive())){
|
||||
reason = "buffer disconnect";
|
||||
cfgPointer->is_active = false;
|
||||
}
|
||||
INFO_MSG("Shutting down thread for %d because %s", tid, reason.c_str());
|
||||
INFO_MSG("Shutting down thread for %zu because %s", tid, reason.c_str());
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
|
||||
threadTimer.erase(tid);
|
||||
}
|
||||
liveStream.eraseTrack(tid);
|
||||
myProxy.userClient.finish();
|
||||
if (dataTrack && userConn){userConn.setStatus(COMM_STATUS_DISCONNECT);}
|
||||
}
|
||||
|
||||
namespace Mist{
|
||||
|
@ -117,6 +147,7 @@ namespace Mist{
|
|||
"standard input (ts-exec:*), or multicast/unicast UDP sockets (tsudp://*).";
|
||||
capa["source_match"].append("/*.ts");
|
||||
capa["source_file"] = "$source";
|
||||
capa["source_match"].append("/*.m2ts");
|
||||
capa["source_match"].append("stream://*.ts");
|
||||
capa["source_match"].append("tsudp://*");
|
||||
capa["source_match"].append("ts-exec:*");
|
||||
|
@ -143,9 +174,9 @@ namespace Mist{
|
|||
capa["codecs"][0u][1u].append("MP2");
|
||||
inFile = NULL;
|
||||
inputProcess = 0;
|
||||
isFinished = false;
|
||||
|
||||
{
|
||||
int fin = 0, fout = 0, ferr = 0;
|
||||
pid_t srt_tx = -1;
|
||||
const char *args[] ={"srt-live-transmit", 0};
|
||||
srt_tx = Util::Procs::StartPiped(args, 0, 0, 0);
|
||||
|
@ -265,23 +296,6 @@ namespace Mist{
|
|||
return inFile;
|
||||
}
|
||||
|
||||
/// 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){
|
||||
selectedTracks.clear();
|
||||
long long int index;
|
||||
while (trackSpec != ""){
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
||||
if (index != std::string::npos){
|
||||
trackSpec.erase(0, index + 1);
|
||||
}else{
|
||||
trackSpec = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool inputTS::needHeader(){
|
||||
if (!standAlone){return false;}
|
||||
return Input::needHeader();
|
||||
|
@ -295,6 +309,7 @@ namespace Mist{
|
|||
///\todo Find errors, perhaps parts can be made more modular
|
||||
bool inputTS::readHeader(){
|
||||
if (!inFile){return false;}
|
||||
meta.reInit(streamName);
|
||||
TS::Packet packet; // to analyse and extract data
|
||||
DTSC::Packet headerPack;
|
||||
fseek(inFile, 0, SEEK_SET); // seek to beginning
|
||||
|
@ -306,28 +321,39 @@ namespace Mist{
|
|||
if (packet.getUnitStart()){
|
||||
while (tsStream.hasPacketOnEachTrack()){
|
||||
tsStream.getEarliestPacket(headerPack);
|
||||
if (!headerPack){break;}
|
||||
if (!myMeta.tracks.count(headerPack.getTrackId()) ||
|
||||
!myMeta.tracks[headerPack.getTrackId()].codec.size()){
|
||||
tsStream.initializeMetadata(myMeta, headerPack.getTrackId());
|
||||
size_t pid = headerPack.getTrackId();
|
||||
size_t idx = M.trackIDToIndex(pid, getpid());
|
||||
if (idx == INVALID_TRACK_ID || !M.getCodec(idx).size()){
|
||||
tsStream.initializeMetadata(meta, pid);
|
||||
idx = M.trackIDToIndex(pid, getpid());
|
||||
}
|
||||
myMeta.update(headerPack);
|
||||
char *data;
|
||||
size_t dataLen;
|
||||
headerPack.getString("data", data, dataLen);
|
||||
meta.update(headerPack.getTime(), headerPack.getInt("offset"), idx, dataLen,
|
||||
headerPack.getInt("bpos"), headerPack.getFlag("keyframe"), headerPack.getDataLen());
|
||||
}
|
||||
}
|
||||
}
|
||||
tsStream.finish();
|
||||
INFO_MSG("Reached %s at %llu bytes", feof(inFile) ? "EOF" : "error", lastBpos);
|
||||
INFO_MSG("Reached %s at %" PRIu64 " bytes", feof(inFile) ? "EOF" : "error", lastBpos);
|
||||
while (tsStream.hasPacket()){
|
||||
tsStream.getEarliestPacket(headerPack);
|
||||
if (!myMeta.tracks.count(headerPack.getTrackId()) ||
|
||||
!myMeta.tracks[headerPack.getTrackId()].codec.size()){
|
||||
tsStream.initializeMetadata(myMeta, headerPack.getTrackId());
|
||||
size_t pid = headerPack.getTrackId();
|
||||
size_t idx = M.trackIDToIndex(pid, getpid());
|
||||
if (idx == INVALID_TRACK_ID || !M.getCodec(idx).size()){
|
||||
tsStream.initializeMetadata(meta, pid);
|
||||
idx = M.trackIDToIndex(pid, getpid());
|
||||
}
|
||||
myMeta.update(headerPack);
|
||||
char *data;
|
||||
size_t dataLen;
|
||||
headerPack.getString("data", data, dataLen);
|
||||
meta.update(headerPack.getTime(), headerPack.getInt("offset"), idx, dataLen,
|
||||
headerPack.getInt("bpos"), headerPack.getFlag("keyframe"), headerPack.getDataLen());
|
||||
}
|
||||
|
||||
fseek(inFile, 0, SEEK_SET);
|
||||
myMeta.toFile(config->getString("input") + ".dtsh");
|
||||
meta.toFile(config->getString("input") + ".dtsh");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -335,40 +361,42 @@ namespace Mist{
|
|||
/// 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){
|
||||
INSANE_MSG("Getting next");
|
||||
void inputTS::getNext(size_t idx){
|
||||
size_t pid = (idx == INVALID_TRACK_ID ? 0 : M.getID(idx));
|
||||
INSANE_MSG("Getting next on track %zu", idx);
|
||||
thisPacket.null();
|
||||
bool hasPacket =
|
||||
(selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacket());
|
||||
bool hasPacket = (idx == INVALID_TRACK_ID ? tsStream.hasPacket() : tsStream.hasPacket(pid));
|
||||
while (!hasPacket && !feof(inFile) &&
|
||||
(inputProcess == 0 || Util::Procs::childRunning(inputProcess)) && config->is_active){
|
||||
tsBuf.FromFile(inFile);
|
||||
if (selectedTracks.count(tsBuf.getPID())){
|
||||
if (idx == INVALID_TRACK_ID || pid == tsBuf.getPID()){
|
||||
tsStream.parse(tsBuf, 0); // bPos == 0
|
||||
if (tsBuf.getUnitStart()){
|
||||
hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin())
|
||||
: tsStream.hasPacket());
|
||||
hasPacket = (idx == INVALID_TRACK_ID ? tsStream.hasPacket() : tsStream.hasPacket(pid));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (feof(inFile)){
|
||||
tsStream.finish();
|
||||
if (!isFinished){
|
||||
tsStream.finish();
|
||||
isFinished = true;
|
||||
}
|
||||
hasPacket = true;
|
||||
}
|
||||
if (!hasPacket){return;}
|
||||
if (selectedTracks.size() == 1){
|
||||
if (tsStream.hasPacket(*selectedTracks.begin())){
|
||||
tsStream.getPacket(*selectedTracks.begin(), thisPacket);
|
||||
}
|
||||
}else{
|
||||
if (idx == INVALID_TRACK_ID){
|
||||
if (tsStream.hasPacket()){tsStream.getEarliestPacket(thisPacket);}
|
||||
}else{
|
||||
if (tsStream.hasPacket(pid)){tsStream.getPacket(pid, thisPacket);}
|
||||
}
|
||||
|
||||
if (!thisPacket){
|
||||
INFO_MSG("Could not getNext TS packet!");
|
||||
return;
|
||||
}
|
||||
tsStream.initializeMetadata(myMeta);
|
||||
if (!myMeta.tracks.count(thisPacket.getTrackId())){getNext();}
|
||||
tsStream.initializeMetadata(meta);
|
||||
size_t thisIdx = M.trackIDToIndex(thisPacket.getTrackId(), getpid());
|
||||
if (thisIdx == INVALID_TRACK_ID){getNext(idx);}
|
||||
}
|
||||
|
||||
void inputTS::readPMT(){
|
||||
|
@ -388,24 +416,31 @@ namespace Mist{
|
|||
tsStream.partialClear();
|
||||
|
||||
// Restore original file position
|
||||
if (Util::fseek(inFile, bpos, SEEK_SET)){return;}
|
||||
if (Util::fseek(inFile, bpos, SEEK_SET)){
|
||||
clearerr(inFile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Seeks to a specific time
|
||||
void inputTS::seek(int seekTime){
|
||||
void inputTS::seek(uint64_t seekTime, size_t idx){
|
||||
tsStream.clear();
|
||||
readPMT();
|
||||
uint64_t seekPos = 0xFFFFFFFFFFFFFFFFull;
|
||||
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;}
|
||||
thisBPos = keyIt->getBpos();
|
||||
tsStream.setLastms(*it, keyIt->getTime());
|
||||
uint64_t seekPos = 0xFFFFFFFFull;
|
||||
if (idx != INVALID_TRACK_ID){
|
||||
DTSC::Keys keys(M.keys(idx));
|
||||
uint32_t keyNum = keys.getNumForTime(seekTime);
|
||||
seekPos = keys.getBpos(keyNum);
|
||||
}else{
|
||||
std::set<size_t> tracks = M.getValidTracks();
|
||||
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
|
||||
DTSC::Keys keys(M.keys(*it));
|
||||
uint32_t keyNum = keys.getNumForTime(seekTime);
|
||||
uint64_t thisBPos = keys.getBpos(keyNum);
|
||||
if (thisBPos < seekPos){seekPos = thisBPos;}
|
||||
}
|
||||
if (thisBPos < seekPos){seekPos = thisBPos;}
|
||||
}
|
||||
clearerr(inFile);
|
||||
Util::fseek(inFile, seekPos, SEEK_SET); // seek to the correct position
|
||||
}
|
||||
|
||||
|
@ -424,22 +459,23 @@ namespace Mist{
|
|||
}
|
||||
|
||||
void inputTS::parseStreamHeader(){
|
||||
// Placeholder to force normal code to continue despite no tracks available
|
||||
myMeta.tracks[0].type = "audio";
|
||||
// Placeholder empty track to force normal code to continue despite no tracks available
|
||||
tmpIdx = meta.addTrack(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
std::string inputTS::streamMainLoop(){
|
||||
myMeta.tracks.clear(); // wipe the placeholder track from above
|
||||
IPC::sharedClient statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
|
||||
meta.removeTrack(tmpIdx);
|
||||
INFO_MSG("Removed temptrack %zu", tmpIdx);
|
||||
Comms::Statistics statComm;
|
||||
uint64_t downCounter = 0;
|
||||
uint64_t startTime = Util::epoch();
|
||||
uint64_t startTime = Util::bootSecs();
|
||||
uint64_t noDataSince = Util::bootSecs();
|
||||
bool gettingData = false;
|
||||
bool hasStarted = false;
|
||||
cfgPointer = config;
|
||||
globalStreamName = streamName;
|
||||
unsigned long long threadCheckTimer = Util::bootSecs();
|
||||
while (config->is_active && nProxy.userClient.isAlive()){
|
||||
while (config->is_active){
|
||||
if (tcpCon){
|
||||
if (tcpCon.spool()){
|
||||
while (tcpCon.Received().available(188)){
|
||||
|
@ -471,7 +507,7 @@ namespace Mist{
|
|||
gettingData = true;
|
||||
INFO_MSG("Now receiving UDP data...");
|
||||
}
|
||||
int offset = 0;
|
||||
size_t 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){
|
||||
|
@ -489,7 +525,7 @@ namespace Mist{
|
|||
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);
|
||||
VERYHIGH_MSG("%" PRIu32 " bytes of non-sync-byte data received", numBytes);
|
||||
if (leftData.size()){
|
||||
leftData.append(udpCon.data + offset, numBytes);
|
||||
while (leftData.size() >= 188){
|
||||
|
@ -517,28 +553,23 @@ namespace Mist{
|
|||
// 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);
|
||||
}
|
||||
if (statsPage.getData()){
|
||||
if (!statsPage.isAlive()){
|
||||
statComm.reload();
|
||||
if (statComm){
|
||||
if (!statComm.isAlive()){
|
||||
config->is_active = false;
|
||||
statsPage.finish();
|
||||
return "received shutdown request from controller";
|
||||
}
|
||||
IPC::statExchange tmpEx(statsPage.getData());
|
||||
tmpEx.now(now);
|
||||
tmpEx.crc(getpid());
|
||||
tmpEx.streamName(streamName);
|
||||
tmpEx.connector("INPUT");
|
||||
tmpEx.up(0);
|
||||
tmpEx.down(downCounter + tcpCon.dataDown());
|
||||
tmpEx.time(now - startTime);
|
||||
tmpEx.lastSecond(0);
|
||||
statsPage.keepAlive();
|
||||
uint64_t now = Util::bootSecs();
|
||||
statComm.setNow(now);
|
||||
statComm.setCRC(getpid());
|
||||
statComm.setStream(streamName);
|
||||
statComm.setConnector("INPUT");
|
||||
statComm.setUp(0);
|
||||
statComm.setDown(downCounter + tcpCon.dataDown());
|
||||
statComm.setTime(now - startTime);
|
||||
statComm.setLastSecond(0);
|
||||
statComm.keepAlive();
|
||||
}
|
||||
nProxy.userClient.keepAlive();
|
||||
|
||||
std::set<size_t> activeTracks = liveStream.getActiveTracks();
|
||||
{
|
||||
|
@ -546,7 +577,6 @@ namespace Mist{
|
|||
if (hasStarted && !threadTimer.size()){
|
||||
if (!isAlwaysOn()){
|
||||
config->is_active = false;
|
||||
statsPage.finish();
|
||||
return "no active threads and we had input in the past";
|
||||
}else{
|
||||
hasStarted = false;
|
||||
|
@ -555,7 +585,8 @@ namespace Mist{
|
|||
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.",
|
||||
WARN_MSG("Thread for track %" PRIu64 " timed out %" PRIu64
|
||||
" seconds ago without a clean shutdown.",
|
||||
*it, Util::bootSecs() - threadTimer[*it]);
|
||||
threadTimer.erase(*it);
|
||||
}
|
||||
|
@ -566,7 +597,7 @@ namespace Mist{
|
|||
claimableThreads.insert(*it);
|
||||
|
||||
// Spawn thread here.
|
||||
tthread::thread thisThread(parseThread, 0);
|
||||
tthread::thread thisThread(parseThread, this);
|
||||
thisThread.detach();
|
||||
}
|
||||
}
|
||||
|
@ -576,14 +607,12 @@ namespace Mist{
|
|||
if (Util::bootSecs() - noDataSince > 20){
|
||||
if (!isAlwaysOn()){
|
||||
config->is_active = false;
|
||||
statsPage.finish();
|
||||
return "No packets received for 20 seconds - terminating";
|
||||
}else{
|
||||
noDataSince = Util::bootSecs();
|
||||
}
|
||||
}
|
||||
}
|
||||
statsPage.finish();
|
||||
return "received shutdown request";
|
||||
}
|
||||
|
||||
|
@ -612,9 +641,8 @@ namespace Mist{
|
|||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}// namespace Mist
|
||||
|
|
|
@ -20,9 +20,8 @@ namespace Mist{
|
|||
bool preRun();
|
||||
bool readHeader();
|
||||
bool needHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
|
||||
void readPMT();
|
||||
bool openStreamSource();
|
||||
void parseStreamHeader();
|
||||
|
@ -34,6 +33,8 @@ namespace Mist{
|
|||
Socket::Connection tcpCon;
|
||||
TS::Packet tsBuf;
|
||||
pid_t inputProcess;
|
||||
size_t tmpIdx;
|
||||
bool isFinished;
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue