Shared memory rewrite
This commit is contained in:
parent
afcddbfca6
commit
cd2fe225c5
81 changed files with 7775 additions and 5411 deletions
338
src/input/input.cpp
Normal file
338
src/input/input.cpp
Normal file
|
@ -0,0 +1,338 @@
|
|||
#include <semaphore.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <mist/defines.h>
|
||||
#include "input.h"
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
|
||||
namespace Mist {
|
||||
Input * Input::singleton = NULL;
|
||||
|
||||
void Input::userCallback(char * data, size_t len, unsigned int id){
|
||||
long tid = ((long)(data[0]) << 24) | ((long)(data[1]) << 16) | ((long)(data[2]) << 8) | ((long)(data[3]));
|
||||
long keyNum = ((long)(data[4]) << 8) | ((long)(data[5]));
|
||||
bufferFrame(tid, keyNum + 1);//Try buffer next frame
|
||||
}
|
||||
|
||||
void Input::doNothing(char * data, size_t len, unsigned int id){
|
||||
DEBUG_MSG(DLVL_DONTEVEN, "Doing 'nothing'");
|
||||
for (int i = 0; i < 5; i++){
|
||||
int tmp = ((long)(data[i*6]) << 24) | ((long)(data[i*6 + 1]) << 16) | ((long)(data[i*6 + 2]) << 8) | data[i*6 + 3];
|
||||
if (tmp){
|
||||
singleton->userCallback(data + (i*6), 6, id);//call the userCallback for this input
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Input::Input(Util::Config * cfg) {
|
||||
config = cfg;
|
||||
JSON::Value option;
|
||||
option["long"] = "json";
|
||||
option["short"] = "j";
|
||||
option["help"] = "Output MistIn info in JSON format, then exit.";
|
||||
option["value"].append(0ll);
|
||||
config->addOption("json", option);
|
||||
option.null();
|
||||
option["arg_num"] = 1ll;
|
||||
option["arg"] = "string";
|
||||
option["help"] = "Name of the input file or - for stdin";
|
||||
option["value"].append("-");
|
||||
config->addOption("input", option);
|
||||
option.null();
|
||||
option["arg_num"] = 2ll;
|
||||
option["arg"] = "string";
|
||||
option["help"] = "Name of the output file or - for stdout";
|
||||
option["value"].append("-");
|
||||
config->addOption("output", option);
|
||||
option.null();
|
||||
option["arg"] = "string";
|
||||
option["short"] = "s";
|
||||
option["long"] = "stream";
|
||||
option["help"] = "The name of the stream that this connector will transmit.";
|
||||
config->addOption("streamname", option);
|
||||
option.null();
|
||||
option["short"] = "p";
|
||||
option["long"] = "player";
|
||||
option["help"] = "Makes this connector into a player";
|
||||
config->addOption("player", option);
|
||||
|
||||
packTime = 0;
|
||||
lastActive = Util::epoch();
|
||||
playing = 0;
|
||||
playUntil = 0;
|
||||
|
||||
singleton = this;
|
||||
isBuffer = false;
|
||||
}
|
||||
|
||||
int Input::run() {
|
||||
if (config->getBool("json")) {
|
||||
std::cerr << capa.toString() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
if (!setup()) {
|
||||
std::cerr << config->getString("cmd") << " setup failed." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
if (!readHeader()) {
|
||||
std::cerr << "Reading header for " << config->getString("input") << " failed." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
parseHeader();
|
||||
|
||||
if (!config->getBool("player")){
|
||||
//check filename for no -
|
||||
if (config->getString("output") != "-"){
|
||||
//output to dtsc
|
||||
DTSC::Meta newMeta = myMeta;
|
||||
newMeta.reset();
|
||||
JSON::Value tempVal;
|
||||
std::ofstream file(config->getString("output").c_str());
|
||||
long long int bpos = 0;
|
||||
seek(0);
|
||||
getNext();
|
||||
while (lastPack){
|
||||
tempVal = lastPack.toJSON();
|
||||
tempVal["bpos"] = bpos;
|
||||
newMeta.update(tempVal);
|
||||
file << std::string(lastPack.getData(), lastPack.getDataLen());
|
||||
bpos += lastPack.getDataLen();
|
||||
getNext();
|
||||
}
|
||||
//close file
|
||||
file.close();
|
||||
//create header
|
||||
file.open((config->getString("output")+".dtsh").c_str());
|
||||
file << newMeta.toJSON().toNetPacked();
|
||||
file.close();
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_FAIL,"No filename specified, exiting");
|
||||
}
|
||||
}else{
|
||||
//after this player functionality
|
||||
|
||||
metaPage.init(config->getString("streamname"), (isBuffer ? 8388608 : myMeta.getSendLen()), true);
|
||||
myMeta.writeTo(metaPage.mapped);
|
||||
userPage.init(config->getString("streamname") + "_users", 30, true);
|
||||
|
||||
|
||||
if (!isBuffer){
|
||||
for (std::map<int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
bufferFrame(it->first, 0);
|
||||
}
|
||||
}
|
||||
|
||||
sem_t * waiting = sem_open(std::string("/wait_" + config->getString("streamname")).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 0);
|
||||
if (waiting == SEM_FAILED){
|
||||
DEBUG_MSG(DLVL_FAIL, "Failed to open semaphore - cancelling");
|
||||
return -1;
|
||||
}
|
||||
sem_post(waiting);
|
||||
sem_close(waiting);
|
||||
|
||||
DEBUG_MSG(DLVL_HIGH,"Pre-While");
|
||||
|
||||
long long int activityCounter = Util::getMS();
|
||||
while ((Util::getMS() - activityCounter) < 10000){//1minute timeout
|
||||
DEBUG_MSG(DLVL_HIGH, "Timer running");
|
||||
Util::sleep(1000);
|
||||
removeUnused();
|
||||
userPage.parseEach(doNothing);
|
||||
if (userPage.amount){
|
||||
activityCounter = Util::getMS();
|
||||
DEBUG_MSG(DLVL_HIGH, "Connected users: %d", userPage.amount);
|
||||
}
|
||||
}
|
||||
DEBUG_MSG(DLVL_DEVEL,"Closing clean");
|
||||
//end player functionality
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Input::removeUnused(){
|
||||
for (std::map<unsigned int, std::map<unsigned int, unsigned int> >::iterator it = pageCounter.begin(); it != pageCounter.end(); it++){
|
||||
for (std::map<unsigned int, unsigned int>::iterator it2 = it->second.begin(); it2 != it->second.end(); it2++){
|
||||
it2->second--;
|
||||
}
|
||||
bool change = true;
|
||||
while (change){
|
||||
change = false;
|
||||
for (std::map<unsigned int, unsigned int>::iterator it2 = it->second.begin(); it2 != it->second.end(); it2++){
|
||||
if (!it2->second){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Erasing page %u:%u", it->first, it2->first);
|
||||
pagesByTrack[it->first].erase(it2->first);
|
||||
pageCounter[it->first].erase(it2->first);
|
||||
change = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Input::parseHeader(){
|
||||
DEBUG_MSG(DLVL_DEVEL,"Parsing the header");
|
||||
//Select all tracks for parsing header
|
||||
selectedTracks.clear();
|
||||
std::stringstream trackSpec;
|
||||
for (std::map<int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Track %d encountered", it->first);
|
||||
//selectedTracks.insert(it->first);
|
||||
if (trackSpec.str() != ""){
|
||||
trackSpec << " ";
|
||||
}
|
||||
trackSpec << it->first;
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Trackspec now %s", trackSpec.str().c_str());
|
||||
for (std::deque<DTSC::Key>::iterator it2 = it->second.keys.begin(); it2 != it->second.keys.end(); it2++){
|
||||
keyTimes[it->first].insert(it2->getTime());
|
||||
}
|
||||
}
|
||||
trackSelect(trackSpec.str());
|
||||
|
||||
std::map<int, DTSCPageData> curData;
|
||||
std::map<int, booking> bookKeeping;
|
||||
|
||||
seek(0);
|
||||
getNext();
|
||||
|
||||
while(lastPack){//loop through all
|
||||
int tid = lastPack.getTrackId();
|
||||
if (!bookKeeping.count(tid)){
|
||||
bookKeeping[tid].first = 0;
|
||||
bookKeeping[tid].curPart = 0;
|
||||
bookKeeping[tid].curKey = 0;
|
||||
|
||||
curData[tid].lastKeyTime = 0xFFFFFFFF;
|
||||
curData[tid].keyNum = 1;
|
||||
curData[tid].partNum = 0;
|
||||
curData[tid].dataSize = 0;
|
||||
curData[tid].curOffset = 0;
|
||||
curData[tid].firstTime = myMeta.tracks[tid].keys[0].getTime();
|
||||
|
||||
char tmpId[20];
|
||||
sprintf(tmpId, "%d", tid);
|
||||
indexPages[tid].init(config->getString("streamname") + tmpId, 8192, true);//Pages of 8kb in size, room for 512 parts.
|
||||
}
|
||||
if (myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getParts() == curData[tid].partNum){
|
||||
if (curData[tid].dataSize > 8388608) {
|
||||
pagesByTrack[tid][bookKeeping[tid].first] = curData[tid];
|
||||
bookKeeping[tid].first += curData[tid].keyNum;
|
||||
curData[tid].keyNum = 0;
|
||||
curData[tid].dataSize = 0;
|
||||
curData[tid].firstTime = myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getTime();
|
||||
}
|
||||
bookKeeping[tid].curKey++;
|
||||
curData[tid].keyNum++;
|
||||
curData[tid].partNum = 0;
|
||||
}
|
||||
curData[tid].dataSize += lastPack.getDataLen();
|
||||
curData[tid].partNum ++;
|
||||
bookKeeping[tid].curPart ++;
|
||||
getNext(false);
|
||||
}
|
||||
for (std::map<int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
if (curData.count(it->first) && !pagesByTrack[it->first].count(bookKeeping[it->first].first)){
|
||||
pagesByTrack[it->first][bookKeeping[it->first].first] = curData[it->first];
|
||||
}
|
||||
if (!pagesByTrack.count(it->first)){
|
||||
DEBUG_MSG(DLVL_WARN, "No pages for track %d found", it->first);
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_HIGH, "Track %d (%s) split into %lu pages", it->first, myMeta.tracks[it->first].codec.c_str(), pagesByTrack[it->first].size());
|
||||
for (std::map<int, DTSCPageData>::iterator it2 = pagesByTrack[it->first].begin(); it2 != pagesByTrack[it->first].end(); it2++){
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Input::bufferFrame(int track, int keyNum){
|
||||
DEBUG_MSG(DLVL_DONTEVEN, "Attempting to buffer %d:%d", track, keyNum);
|
||||
if (!pagesByTrack.count(track)){
|
||||
return false;
|
||||
}
|
||||
std::map<int, DTSCPageData> ::iterator it = pagesByTrack[track].upper_bound(keyNum);
|
||||
if (it == pagesByTrack[track].begin()){
|
||||
return false;
|
||||
}
|
||||
it --;
|
||||
int pageNum = it->first;
|
||||
pageCounter[track][pageNum] = 15;///Keep page 15seconds in memory after last use
|
||||
|
||||
if (!dataPages[track].count(pageNum)){
|
||||
char pageId[100];
|
||||
int pageIdLen = sprintf(pageId, "%s%d_%d", config->getString("streamname").c_str(), track, pageNum);
|
||||
std::string tmpString(pageId, pageIdLen);
|
||||
dataPages[track][pageNum].init(tmpString, it->second.dataSize, true);
|
||||
DEBUG_MSG(DLVL_HIGH, "Buffering page %d through %d / %lu", pageNum, pageNum + it->second.keyNum, myMeta.tracks[track].keys.size());
|
||||
|
||||
std::stringstream trackSpec;
|
||||
trackSpec << track;
|
||||
trackSelect(trackSpec.str());
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
seek(myMeta.tracks[track].keys[pageNum].getTime());
|
||||
long long unsigned int stopTime = myMeta.tracks[track].lastms + 1;
|
||||
if ((int)myMeta.tracks[track].keys.size() > pageNum + it->second.keyNum){
|
||||
stopTime = myMeta.tracks[track].keys[pageNum + it->second.keyNum].getTime();
|
||||
}
|
||||
DEBUG_MSG(DLVL_HIGH, "Playing from %ld to %llu", myMeta.tracks[track].keys[pageNum].getTime(), stopTime);
|
||||
getNext();
|
||||
while (lastPack && lastPack.getTime() < stopTime){
|
||||
if (it->second.curOffset + lastPack.getDataLen() > pagesByTrack[track][pageNum].dataSize){
|
||||
DEBUG_MSG(DLVL_WARN, "Trying to write %u bytes past the end of page %u/%u", lastPack.getDataLen(), track, pageNum);
|
||||
return true;
|
||||
}else{
|
||||
memcpy(dataPages[track][pageNum].mapped + it->second.curOffset, lastPack.getData(), lastPack.getDataLen());
|
||||
it->second.curOffset += lastPack.getDataLen();
|
||||
}
|
||||
getNext();
|
||||
}
|
||||
for (int i = 0; i < indexPages[track].len / 8; i++){
|
||||
if (((long long int*)indexPages[track].mapped)[i] == 0){
|
||||
((long long int*)indexPages[track].mapped)[i] = (((long long int)htonl(pageNum)) << 32) | htonl(it->second.keyNum);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Input::atKeyFrame(){
|
||||
static std::map<int, int> lastSeen;
|
||||
//not in keyTimes? We're not at a keyframe.
|
||||
unsigned int c = keyTimes[lastPack.getTrackId()].count(lastPack.getTime());
|
||||
if (!c){
|
||||
return false;
|
||||
}
|
||||
//skip double times
|
||||
if (lastSeen.count(lastPack.getTrackId()) && lastSeen[lastPack.getTrackId()] == lastPack.getTime()){
|
||||
return false;
|
||||
}
|
||||
//set last seen, and return true
|
||||
lastSeen[lastPack.getTrackId()] = lastPack.getTime();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Input::play(int until) {
|
||||
playing = -1;
|
||||
playUntil = until;
|
||||
initialTime = 0;
|
||||
benchMark = Util::getMS();
|
||||
}
|
||||
|
||||
void Input::playOnce() {
|
||||
if (playing <= 0) {
|
||||
playing = 1;
|
||||
}
|
||||
++playing;
|
||||
benchMark = Util::getMS();
|
||||
}
|
||||
|
||||
void Input::quitPlay() {
|
||||
playing = 0;
|
||||
}
|
||||
}
|
||||
|
84
src/input/input.h
Normal file
84
src/input/input.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
#include <set>
|
||||
#include <map>
|
||||
#include <cstdlib>
|
||||
#include <mist/config.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/shared_memory.h>
|
||||
|
||||
namespace Mist {
|
||||
struct DTSCPageData {
|
||||
DTSCPageData() : keyNum(0), partNum(0), dataSize(0), curOffset(0), firstTime(0){}
|
||||
int keyNum;///<The number of keyframes in this page.
|
||||
int partNum;///<The number of parts in this page.
|
||||
unsigned long long int dataSize;///<The full size this page should be.
|
||||
unsigned long long int curOffset;///<The current write offset in the page.
|
||||
unsigned long long int firstTime;///<The first timestamp of the page.
|
||||
unsigned long lastKeyTime;///<The last key time encountered on this track.
|
||||
};
|
||||
|
||||
struct booking {
|
||||
int first;
|
||||
int curKey;
|
||||
int curPart;
|
||||
};
|
||||
|
||||
class Input {
|
||||
public:
|
||||
Input(Util::Config * cfg);
|
||||
int run();
|
||||
virtual ~Input() {};
|
||||
protected:
|
||||
static void doNothing(char * data, size_t len, unsigned int id);
|
||||
virtual bool setup() = 0;
|
||||
virtual bool readHeader() = 0;
|
||||
virtual bool atKeyFrame();
|
||||
virtual void getNext(bool smart = true) {};
|
||||
virtual void seek(int seekTime){};
|
||||
void play(int until = 0);
|
||||
void playOnce();
|
||||
void quitPlay();
|
||||
virtual void removeUnused();
|
||||
virtual void trackSelect(std::string trackSpec){};
|
||||
virtual void userCallback(char * data, size_t len, unsigned int id);
|
||||
|
||||
void parseHeader();
|
||||
bool bufferFrame(int track, int 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;
|
||||
unsigned int benchMark;
|
||||
std::set<int> selectedTracks;
|
||||
|
||||
bool isBuffer;
|
||||
|
||||
Util::Config * config;
|
||||
JSON::Value capa;
|
||||
Socket::Connection StatsSocket;
|
||||
DTSC::Meta myMeta;
|
||||
DTSC::Packet lastPack;
|
||||
|
||||
std::map<int,std::set<int> > keyTimes;
|
||||
IPC::sharedPage metaPage;
|
||||
//Create server for user pages
|
||||
IPC::sharedServer userPage;
|
||||
|
||||
|
||||
//TrackIndex pages
|
||||
std::map<int, IPC::sharedPage> indexPages;
|
||||
std::map<int, std::map<int, IPC::sharedPage> > dataPages;
|
||||
|
||||
//Page Overview
|
||||
std::map<int, std::map<int, DTSCPageData> > pagesByTrack;
|
||||
|
||||
std::map<unsigned int, std::map<unsigned int, unsigned int> > pageCounter;
|
||||
|
||||
static Input * singleton;
|
||||
};
|
||||
|
||||
}
|
||||
|
274
src/input/input_buffer.cpp
Normal file
274
src/input/input_buffer.cpp
Normal file
|
@ -0,0 +1,274 @@
|
|||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include "input_buffer.h"
|
||||
|
||||
namespace Mist {
|
||||
inputBuffer::inputBuffer(Util::Config * cfg) : Input(cfg) {
|
||||
JSON::Value option;
|
||||
option["arg"] = "integer";
|
||||
option["long"] = "buffer";
|
||||
option["short"] = "b";
|
||||
option["help"] = "Buffertime for this stream.";
|
||||
option["value"].append(30000LL);
|
||||
config->addOption("bufferTime", option);
|
||||
|
||||
capa["desc"] = "Enables buffered live input";
|
||||
capa["codecs"][0u][0u].append("*");
|
||||
capa["codecs"][0u][1u].append("*");
|
||||
capa["codecs"][0u][2u].append("*");
|
||||
capa["codecs"][0u][3u].append("*");
|
||||
capa["codecs"][0u][4u].append("*");
|
||||
capa["codecs"][0u][5u].append("*");
|
||||
capa["codecs"][0u][6u].append("*");
|
||||
capa["codecs"][0u][7u].append("*");
|
||||
capa["codecs"][0u][8u].append("*");
|
||||
capa["codecs"][0u][9u].append("*");
|
||||
DEBUG_MSG(DLVL_DEVEL, "Started MistInBuffer");
|
||||
isBuffer = true;
|
||||
singleton = this;
|
||||
bufferTime = 0;
|
||||
cutTime = 0;
|
||||
|
||||
}
|
||||
|
||||
void inputBuffer::updateMeta(){
|
||||
long long unsigned int firstms = 0xFFFFFFFFFFFFFFFF;
|
||||
long long unsigned int lastms = 0;
|
||||
for (std::map<int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.firstms < firstms){
|
||||
firstms = it->second.firstms;
|
||||
}
|
||||
if (it->second.firstms > lastms){
|
||||
lastms = it->second.lastms;
|
||||
}
|
||||
}
|
||||
myMeta.bufferWindow = lastms - firstms;
|
||||
myMeta.writeTo(metaPage.mapped);
|
||||
}
|
||||
|
||||
bool inputBuffer::removeKey(unsigned int tid){
|
||||
if (myMeta.tracks[tid].keys.size() < 2 || myMeta.tracks[tid].fragments.size() < 2){
|
||||
return false;
|
||||
}
|
||||
DEBUG_MSG(DLVL_HIGH, "Erasing key %d:%d", tid, myMeta.tracks[tid].keys[0].getNumber());
|
||||
//remove all parts of this key
|
||||
for (int i = 0; i < myMeta.tracks[tid].keys[0].getParts(); i++){
|
||||
myMeta.tracks[tid].parts.pop_front();
|
||||
}
|
||||
//remove the key itself
|
||||
myMeta.tracks[tid].keys.pop_front();
|
||||
//re-calculate firstms
|
||||
myMeta.tracks[tid].firstms = myMeta.tracks[tid].keys[0].getTime();
|
||||
//delete the fragment if it's no longer fully buffered
|
||||
if (myMeta.tracks[tid].fragments[0].getNumber() < myMeta.tracks[tid].keys[0].getNumber()){
|
||||
myMeta.tracks[tid].fragments.pop_front();
|
||||
myMeta.tracks[tid].missedFrags ++;
|
||||
}
|
||||
//if there is more than one page buffered for this track...
|
||||
if (inputLoc[tid].size() > 1){
|
||||
//Check if the first key starts on the second page or higher
|
||||
if (myMeta.tracks[tid].keys[0].getNumber() >= (++(inputLoc[tid].begin()))->first){
|
||||
//Find page in indexpage and null it
|
||||
for (int i = 0; i < 8192; i += 8){
|
||||
int thisKeyNum = ((((long long int *)(indexPages[tid].mapped + i))[0]) >> 32) & 0xFFFFFFFF;
|
||||
if (thisKeyNum == htonl(pagesByTrack[tid].begin()->first) && ((((long long int *)(indexPages[tid].mapped + i))[0]) != 0)){
|
||||
(((long long int *)(indexPages[tid].mapped + i))[0]) = 0;
|
||||
}
|
||||
}
|
||||
DEBUG_MSG(DLVL_DEVEL, "Erasing track %d, keys %lu-%lu from buffer", tid, inputLoc[tid].begin()->first, inputLoc[tid].begin()->first + inputLoc[tid].begin()->second.keyNum - 1);
|
||||
inputLoc[tid].erase(inputLoc[tid].begin());
|
||||
dataPages[tid].erase(dataPages[tid].begin());
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_HIGH, "%d still on first page (%lu - %lu)", myMeta.tracks[tid].keys[0].getNumber(), inputLoc[tid].begin()->first, inputLoc[tid].begin()->first + inputLoc[tid].begin()->second.keyNum - 1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputBuffer::removeUnused(){
|
||||
//find the earliest video keyframe stored
|
||||
unsigned int firstVideo = 1;
|
||||
for(std::map<int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.type == "video"){
|
||||
if (it->second.firstms < firstVideo || firstVideo == 1){
|
||||
firstVideo = it->second.firstms;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(std::map<int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
//non-video tracks need to have a second keyframe that is <= firstVideo
|
||||
if (it->second.type != "video"){
|
||||
if (it->second.keys.size() < 2 || it->second.keys[1].getTime() > firstVideo){
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//Buffer cutting
|
||||
while(it->second.keys.size() > 1 && it->second.keys[0].getTime() < cutTime){
|
||||
if (!removeKey(it->first)){break;}
|
||||
}
|
||||
//Buffer size management
|
||||
while(it->second.keys.size() > 1 && (it->second.lastms - it->second.keys[1].getTime()) > bufferTime){
|
||||
if (!removeKey(it->first)){break;}
|
||||
}
|
||||
}
|
||||
updateMeta();
|
||||
}
|
||||
|
||||
void inputBuffer::userCallback(char * data, size_t len, unsigned int id) {
|
||||
unsigned long tmp = ((long)(data[0]) << 24) | ((long)(data[1]) << 16) | ((long)(data[2]) << 8) | ((long)(data[3]));
|
||||
if (tmp & 0x80000000) {
|
||||
//Track is set to "New track request", assign new track id and create shared memory page
|
||||
unsigned long tNum = (givenTracks.size() ? (*givenTracks.rbegin()) : 0) + 1;
|
||||
///\todo Neatify this
|
||||
data[0] = (tNum >> 24) & 0xFF;
|
||||
data[1] = (tNum >> 16) & 0xFF;
|
||||
data[2] = (tNum >> 8) & 0xFF;
|
||||
data[3] = (tNum) & 0xFF;
|
||||
givenTracks.insert(tNum);
|
||||
char tmpChr[100];
|
||||
long tmpLen = sprintf(tmpChr, "liveStream_%s%lu", config->getString("streamname").c_str(), tNum);
|
||||
metaPages[tNum].init(std::string(tmpChr, tmpLen), 8388608, true);
|
||||
} else {
|
||||
unsigned long tNum = ((long)(data[0]) << 24) | ((long)(data[1]) << 16) | ((long)(data[2]) << 8) | ((long)(data[3]));
|
||||
if (!myMeta.tracks.count(tNum)) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Tracknum not in meta: %lu, from user %u", tNum, id);
|
||||
if (metaPages[tNum].mapped) {
|
||||
if (metaPages[tNum].mapped[0] == 'D' && metaPages[tNum].mapped[1] == 'T') {
|
||||
unsigned int len = ntohl(((int *)metaPages[tNum].mapped)[1]);
|
||||
unsigned int i = 0;
|
||||
JSON::Value tmpMeta;
|
||||
JSON::fromDTMI((const unsigned char *)metaPages[tNum].mapped + 8, len, i, tmpMeta);
|
||||
DTSC::Meta tmpTrack(tmpMeta);
|
||||
int oldTNum = tmpTrack.tracks.begin()->first;
|
||||
bool collision = false;
|
||||
for (std::map<int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
if (it->first == tNum) {
|
||||
continue;
|
||||
}
|
||||
if (it->second.getIdentifier() == tmpTrack.tracks[oldTNum].getIdentifier()) {
|
||||
collision = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (collision) {
|
||||
/// \todo Erasing page for now, should do more here
|
||||
DEBUG_MSG(DLVL_DEVEL, "Collision detected! Erasing page for now, should do more here");
|
||||
metaPages.erase(tNum);
|
||||
data[0] = 0xFF;
|
||||
data[1] = 0xFF;
|
||||
data[2] = 0xFF;
|
||||
data[3] = 0xFF;
|
||||
} else {
|
||||
if (!myMeta.tracks.count(tNum)) {
|
||||
myMeta.tracks[tNum] = tmpTrack.tracks[oldTNum];
|
||||
data[4] = 0x00;
|
||||
data[5] = 0x00;
|
||||
updateMeta();
|
||||
char firstPage[100];
|
||||
sprintf(firstPage, "%s%lu", config->getString("streamname").c_str(), tNum);
|
||||
indexPages[tNum].init(firstPage, 8192, true);
|
||||
((long long int *)indexPages[tNum].mapped)[0] = htonl(1000);
|
||||
///\todo Fix for non-first-key-pushing
|
||||
sprintf(firstPage, "%s%lu_0", config->getString("streamname").c_str(), tNum);
|
||||
///\todo Make size dynamic / other solution. 25mb is too much.
|
||||
dataPages[tNum][0].init(firstPage, 26214400, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//First check if the previous page has been finished:
|
||||
if (!inputLoc[tNum].count(dataPages[tNum].rbegin()->first) || !inputLoc[tNum][dataPages[tNum].rbegin()->first].curOffset){
|
||||
if (dataPages[tNum].size() > 1){
|
||||
int prevPage = (++dataPages[tNum].rbegin())->first;
|
||||
//update previous page.
|
||||
updateMetaFromPage(tNum, prevPage);
|
||||
}
|
||||
}
|
||||
//update current page
|
||||
int curPage = dataPages[tNum].rbegin()->first;
|
||||
updateMetaFromPage(tNum, curPage);
|
||||
if (inputLoc[tNum][curPage].curOffset > 8388608) {
|
||||
//create new page is > 8MB
|
||||
int nxtPage = curPage + inputLoc[tNum][curPage].keyNum;
|
||||
char nextPageName[100];
|
||||
sprintf(nextPageName, "%s%lu_%d", config->getString("streamname").c_str(), tNum, nxtPage);
|
||||
dataPages[tNum][nxtPage].init(nextPageName, 20971520, true);
|
||||
bool createdNew = false;
|
||||
for (int i = 0; i < 8192; i += 8){
|
||||
int thisKeyNum = ((((long long int *)(indexPages[tNum].mapped + i))[0]) >> 32) & 0xFFFFFFFF;
|
||||
if (thisKeyNum == htonl(curPage)){
|
||||
if((ntohl((((long long int*)(indexPages[tNum].mapped + i))[0]) & 0xFFFFFFFF) == 1000)){
|
||||
((long long int *)(indexPages[tNum].mapped + i))[0] &= 0xFFFFFFFF00000000;
|
||||
((long long int *)(indexPages[tNum].mapped + i))[0] |= htonl(inputLoc[tNum][curPage].keyNum);
|
||||
}
|
||||
}
|
||||
if (!createdNew && (((long long int*)(indexPages[tNum].mapped + i))[0]) == 0){
|
||||
createdNew = true;
|
||||
((long long int *)(indexPages[tNum].mapped + i))[0] = (((long long int)htonl(nxtPage)) << 32) | htonl(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void inputBuffer::updateMetaFromPage(int tNum, int pageNum){
|
||||
DTSC::Packet tmpPack;
|
||||
tmpPack.reInit(dataPages[tNum][pageNum].mapped + inputLoc[tNum][pageNum].curOffset, 0);
|
||||
while (tmpPack) {
|
||||
myMeta.update(tmpPack);
|
||||
if (inputLoc[tNum][pageNum].firstTime == 0){
|
||||
inputLoc[tNum][pageNum].firstTime = tmpPack.getTime();
|
||||
}
|
||||
//Overloaded use of .firstTime to indicate last Keytime on non-video streams;
|
||||
if (myMeta.tracks[tNum].type == "video"){
|
||||
inputLoc[tNum][pageNum].keyNum += tmpPack.getFlag("keyframe");
|
||||
}else{
|
||||
if ((tmpPack.getTime() > 5000) && ((tmpPack.getTime() - 5000) > inputLoc[tNum][pageNum].firstTime)){
|
||||
inputLoc[tNum][pageNum].keyNum ++;
|
||||
}
|
||||
}
|
||||
inputLoc[tNum][pageNum].curOffset += tmpPack.getDataLen();
|
||||
tmpPack.reInit(dataPages[tNum][pageNum].mapped + inputLoc[tNum][pageNum].curOffset, 0);
|
||||
}
|
||||
updateMeta();
|
||||
}
|
||||
|
||||
bool inputBuffer::setup() {
|
||||
if (!bufferTime){
|
||||
bufferTime = config->getInteger("bufferTime");
|
||||
}
|
||||
JSON::Value servConf = JSON::fromFile(Util::getTmpFolder() + "streamlist");
|
||||
if (servConf.isMember("streams") && servConf["streams"].isMember(config->getString("streamname"))){
|
||||
JSON::Value & streamConfig = servConf["streams"][config->getString("streamname")];
|
||||
if (streamConfig.isMember("DVR") && streamConfig["DVR"].asInt()){
|
||||
if (bufferTime != streamConfig["DVR"].asInt()){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Setting bufferTime from %u to new value of %lli", bufferTime, streamConfig["DVR"].asInt());
|
||||
bufferTime = streamConfig["DVR"].asInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inputBuffer::readHeader() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputBuffer::getNext(bool smart) {}
|
||||
|
||||
void inputBuffer::seek(int seekTime) {}
|
||||
|
||||
void inputBuffer::trackSelect(std::string trackSpec) {}
|
||||
}
|
||||
|
||||
|
||||
|
33
src/input/input_buffer.h
Normal file
33
src/input/input_buffer.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/shared_memory.h>
|
||||
|
||||
namespace Mist {
|
||||
class inputBuffer : public Input {
|
||||
public:
|
||||
inputBuffer(Util::Config * cfg);
|
||||
private:
|
||||
unsigned int bufferTime;
|
||||
unsigned int cutTime;
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
void updateMeta();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void updateMetaFromPage(int tNum, int pageNum);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
bool removeKey(unsigned int tid);
|
||||
void removeUnused();
|
||||
void userCallback(char * data, size_t len, unsigned int id);
|
||||
std::set<unsigned long> givenTracks;
|
||||
std::map<unsigned long, IPC::sharedPage> metaPages;
|
||||
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > inputLoc;
|
||||
inputBuffer * singleton;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputBuffer mistIn;
|
||||
|
||||
|
90
src/input/input_dtsc.cpp
Normal file
90
src/input/input_dtsc.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include "input_dtsc.h"
|
||||
|
||||
namespace Mist {
|
||||
inputDTSC::inputDTSC(Util::Config * cfg) : Input(cfg) {
|
||||
capa["decs"] = "Enables DTSC Input";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][0u].append("H263");
|
||||
capa["codecs"][0u][0u].append("VP6");
|
||||
capa["codecs"][0u][0u].append("theora");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
capa["codecs"][0u][1u].append("vorbis");
|
||||
}
|
||||
|
||||
bool inputDTSC::setup() {
|
||||
if (config->getString("input") == "-") {
|
||||
std::cerr << "Input from stream not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (config->getString("output") != "-") {
|
||||
std::cerr << "Output to non-stdout not yet supported" << std::endl;
|
||||
}
|
||||
|
||||
//open File
|
||||
inFile = DTSC::File(config->getString("input"));
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inputDTSC::readHeader() {
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
DTSC::File tmp(config->getString("input") + ".dtsh");
|
||||
if (tmp) {
|
||||
myMeta = tmp.getMeta();
|
||||
DEBUG_MSG(DLVL_DEVEL,"Meta read in with %lu tracks", myMeta.tracks.size());
|
||||
return true;
|
||||
}
|
||||
if (inFile.getMeta().moreheader < 0 || inFile.getMeta().tracks.size() == 0) {
|
||||
DEBUG_MSG(DLVL_FAIL,"Missing external header file");
|
||||
return false;
|
||||
}
|
||||
myMeta = DTSC::Meta(inFile.getMeta());
|
||||
DEBUG_MSG(DLVL_DEVEL,"Meta read in with %lu tracks", myMeta.tracks.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputDTSC::getNext(bool smart) {
|
||||
if (smart){
|
||||
inFile.seekNext();
|
||||
}else{
|
||||
inFile.parseNext();
|
||||
}
|
||||
lastPack = 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);
|
||||
} else {
|
||||
trackSpec = "";
|
||||
}
|
||||
}
|
||||
inFile.selectTracks(selectedTracks);
|
||||
}
|
||||
}
|
||||
|
22
src/input/input_dtsc.h
Normal file
22
src/input/input_dtsc.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
|
||||
namespace Mist {
|
||||
class inputDTSC : public Input {
|
||||
public:
|
||||
inputDTSC(Util::Config * cfg);
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
|
||||
DTSC::File inFile;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputDTSC mistIn;
|
||||
|
||||
|
130
src/input/input_flv.cpp
Normal file
130
src/input/input_flv.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/flv_tag.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include "input_flv.h"
|
||||
|
||||
namespace Mist {
|
||||
inputFLV::inputFLV(Util::Config * cfg) : Input(cfg) {
|
||||
capa["decs"] = "Enables FLV Input";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][0u].append("H263");
|
||||
capa["codecs"][0u][0u].append("VP6");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
}
|
||||
|
||||
bool inputFLV::setup() {
|
||||
if (config->getString("input") == "-") {
|
||||
std::cerr << "Input from stream not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (config->getString("output") != "-") {
|
||||
std::cerr << "Output to non-stdout not yet supported" << std::endl;
|
||||
}
|
||||
|
||||
//open File
|
||||
inFile = fopen(config->getString("input").c_str(), "r");
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inputFLV::readHeader() {
|
||||
JSON::Value lastPack;
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
//See whether a separate header file exists.
|
||||
DTSC::File tmp(config->getString("input") + ".dtsh");
|
||||
if (tmp){
|
||||
myMeta = tmp.getMeta();
|
||||
return true;
|
||||
}
|
||||
//Create header file from FLV data
|
||||
fseek(inFile, 13, SEEK_SET);
|
||||
FLV::Tag tmpTag;
|
||||
long long int lastBytePos = 13;
|
||||
while (!feof(inFile) && !FLV::Parse_Error){
|
||||
if (tmpTag.FileLoader(inFile)){
|
||||
lastPack.null();
|
||||
lastPack = tmpTag.toJSON(myMeta);
|
||||
lastPack["bpos"] = lastBytePos;
|
||||
myMeta.update(lastPack);
|
||||
lastBytePos = ftell(inFile);
|
||||
}
|
||||
}
|
||||
if (FLV::Parse_Error){
|
||||
std::cerr << FLV::Error_Str << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str());
|
||||
oFile << myMeta.toJSON().toNetPacked();
|
||||
oFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputFLV::getNext(bool smart) {
|
||||
static JSON::Value thisPack;
|
||||
thisPack.null();
|
||||
long long int lastBytePos = ftell(inFile);
|
||||
FLV::Tag tmpTag;
|
||||
while (!feof(inFile) && !FLV::Parse_Error){
|
||||
if (tmpTag.FileLoader(inFile)){
|
||||
thisPack = tmpTag.toJSON(myMeta);
|
||||
thisPack["bpos"] = lastBytePos;
|
||||
if ( !selectedTracks.count(thisPack["trackid"].asInt())){
|
||||
getNext();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (FLV::Parse_Error){
|
||||
std::cerr << FLV::Error_Str << std::endl;
|
||||
thisPack.null();
|
||||
lastPack.null();
|
||||
return;
|
||||
}
|
||||
std::string tmpStr = thisPack.toNetPacked();
|
||||
lastPack.reInit(tmpStr.data(), tmpStr.size());
|
||||
}
|
||||
|
||||
void inputFLV::seek(int seekTime) {
|
||||
//We will seek to the corresponding keyframe of the video track if selected, otherwise audio keyframe.
|
||||
//Flv files are never multi-track, so track 1 is video, track 2 is audio.
|
||||
int trackSeek = (selectedTracks.count(1) ? 1 : 2);
|
||||
size_t seekPos = myMeta.tracks[trackSeek].keys[0].getBpos();
|
||||
for (int i = 0; i < myMeta.tracks[trackSeek].keys.size(); i++){
|
||||
if (myMeta.tracks[trackSeek].keys[i].getTime() > seekTime){
|
||||
DEBUG_MSG(DLVL_WARN, "Seeking to keyframe %d on track %d, timestamp %ld, bytepos %lu", i, trackSeek, myMeta.tracks[trackSeek].keys[i].getTime(), seekPos);
|
||||
break;
|
||||
}
|
||||
seekPos = myMeta.tracks[trackSeek].keys[i].getBpos();
|
||||
}
|
||||
fseek(inFile, seekPos, SEEK_SET);
|
||||
}
|
||||
|
||||
void inputFLV::trackSelect(std::string trackSpec) {
|
||||
selectedTracks.clear();
|
||||
long long int index;
|
||||
while (trackSpec != "") {
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
||||
DEBUG_MSG(DLVL_WARN, "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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
src/input/input_flv.h
Normal file
21
src/input/input_flv.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
|
||||
namespace Mist {
|
||||
class inputFLV : public Input {
|
||||
public:
|
||||
inputFLV(Util::Config * cfg);
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
|
||||
FILE * inFile;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputFLV mistIn;
|
||||
|
274
src/input/input_ogg.cpp
Normal file
274
src/input/input_ogg.cpp
Normal file
|
@ -0,0 +1,274 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/ogg.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/bitstream.h>
|
||||
|
||||
#include "input_ogg.h"
|
||||
|
||||
namespace Mist {
|
||||
inputOGG::inputOGG(Util::Config * cfg) : Input(cfg) {
|
||||
capa["decs"] = "Enables OGG Input";
|
||||
capa["codecs"][0u][0u].append("theora");
|
||||
capa["codecs"][0u][1u].append("vorbis");
|
||||
}
|
||||
|
||||
bool inputOGG::setup() {
|
||||
if (config->getString("input") == "-") {
|
||||
std::cerr << "Input from stream not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (config->getString("output") != "-") {
|
||||
std::cerr << "Output to non-stdout not yet supported" << std::endl;
|
||||
}
|
||||
|
||||
//open File
|
||||
inFile = fopen(config->getString("input").c_str(), "r");
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputOGG::parseBeginOfStream(OGG::Page & bosPage) {
|
||||
long long int tid = snum2tid.size() + 1;
|
||||
snum2tid[bosPage.getBitstreamSerialNumber()] = tid;
|
||||
if (!memcmp(bosPage.getFullPayload() + 1, "theora", 6)) {
|
||||
oggTracks[tid].codec = THEORA;
|
||||
theora::header tmpHead(bosPage.getFullPayload(), bosPage.getPayloadSize());
|
||||
oggTracks[tid].msPerFrame = (double)(tmpHead.getFRD() * 1000) / tmpHead.getFRN();
|
||||
}
|
||||
if (!memcmp(bosPage.getFullPayload() + 1, "vorbis", 6)) {
|
||||
oggTracks[tid].codec = VORBIS;
|
||||
vorbis::header tmpHead(bosPage.getFullPayload(), bosPage.getPayloadSize());
|
||||
oggTracks[tid].msPerFrame = (double)1000 / ntohl(tmpHead.getAudioSampleRate());
|
||||
}
|
||||
}
|
||||
|
||||
bool inputOGG::readHeader() {
|
||||
JSON::Value lastPack;
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
//See whether a separate header file exists.
|
||||
DTSC::File tmp(config->getString("input") + ".dtsh");
|
||||
if (tmp) {
|
||||
myMeta = tmp.getMeta();
|
||||
return true;
|
||||
}
|
||||
//Create header file from OGG data
|
||||
fseek(inFile, 0, SEEK_SET);
|
||||
OGG::Page tmpPage;
|
||||
long long int lastBytePos = 0;
|
||||
while (tmpPage.read(inFile)) {
|
||||
DEBUG_MSG(DLVL_WARN,"Read a page");
|
||||
if (tmpPage.getHeaderType() & OGG::BeginOfStream){
|
||||
parseBeginOfStream(tmpPage);
|
||||
DEBUG_MSG(DLVL_WARN,"Read BOS page for stream %lu, now track %lld", tmpPage.getBitstreamSerialNumber(), snum2tid[tmpPage.getBitstreamSerialNumber()]);
|
||||
}
|
||||
int offset = 0;
|
||||
long long int tid = snum2tid[tmpPage.getBitstreamSerialNumber()];
|
||||
for (std::deque<unsigned int>::iterator it = tmpPage.getSegmentTableDeque().begin(); it != tmpPage.getSegmentTableDeque().end(); it++) {
|
||||
if (oggTracks[tid].parsedHeaders) {
|
||||
DEBUG_MSG(DLVL_WARN,"Parsing a page segment on track %lld", tid);
|
||||
if ((it == (tmpPage.getSegmentTableDeque().end() - 1)) && (int)(tmpPage.getPageSegments()) == 255 && (int)(tmpPage.getSegmentTable()[254]) == 255) {
|
||||
oggTracks[tid].contBuffer.append(tmpPage.getFullPayload() + offset, (*it));
|
||||
} else {
|
||||
lastPack["trackid"] = tid;
|
||||
lastPack["time"] = (long long)oggTracks[tid].lastTime;
|
||||
if (oggTracks[tid].contBuffer.size()) {
|
||||
lastPack["data"] = oggTracks[tid].contBuffer + std::string(tmpPage.getFullPayload() + offset, (*it));
|
||||
oggTracks[tid].contBuffer.clear();
|
||||
} else {
|
||||
lastPack["data"] = std::string(tmpPage.getFullPayload() + offset, (*it));
|
||||
}
|
||||
if (oggTracks[tid].codec == VORBIS) {
|
||||
unsigned int blockSize = 0;
|
||||
Utils::bitstreamLSBF packet;
|
||||
packet.append(lastPack["data"].asString());
|
||||
if (!packet.get(1)) {
|
||||
blockSize = oggTracks[tid].blockSize[oggTracks[tid].vModes[packet.get(vorbis::ilog(oggTracks[tid].vModes.size() - 1))].blockFlag];
|
||||
} else {
|
||||
DEBUG_MSG(DLVL_WARN, "Packet type != 0");
|
||||
}
|
||||
oggTracks[tid].lastTime += oggTracks[tid].msPerFrame * (blockSize / oggTracks[tid].channels);
|
||||
}
|
||||
if (oggTracks[tid].codec == THEORA) {
|
||||
oggTracks[tid].lastTime += oggTracks[tid].msPerFrame;
|
||||
if (it == (tmpPage.getSegmentTableDeque().end() - 1)) {
|
||||
if (oggTracks[tid].idHeader.parseGranuleUpper(oggTracks[tid].lastGran) != oggTracks[tid].idHeader.parseGranuleUpper(tmpPage.getGranulePosition())) {
|
||||
lastPack["keyframe"] = 1ll;
|
||||
oggTracks[tid].lastGran = tmpPage.getGranulePosition();
|
||||
} else {
|
||||
lastPack["interframe"] = 1ll;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastPack["bpos"] = 0ll;
|
||||
DEBUG_MSG(DLVL_WARN,"Parsed a packet of track %lld, new timestamp %f", tid, oggTracks[tid].lastTime);
|
||||
myMeta.update(lastPack);
|
||||
}
|
||||
} else {
|
||||
//Parsing headers
|
||||
switch (oggTracks[tid].codec) {
|
||||
case THEORA: {
|
||||
theora::header tmpHead(tmpPage.getFullPayload() + offset, (*it));
|
||||
DEBUG_MSG(DLVL_WARN,"Theora header, type %d", tmpHead.getHeaderType());
|
||||
switch (tmpHead.getHeaderType()) {
|
||||
case 0: {
|
||||
oggTracks[tid].idHeader = tmpHead;
|
||||
myMeta.tracks[tid].height = tmpHead.getPICH();
|
||||
myMeta.tracks[tid].width = tmpHead.getPICW();
|
||||
myMeta.tracks[tid].idHeader = std::string(tmpPage.getFullPayload() + offset, (*it));
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
myMeta.tracks[tid].commentHeader = std::string(tmpPage.getFullPayload() + offset, (*it));
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
myMeta.tracks[tid].codec = "theora";
|
||||
myMeta.tracks[tid].trackID = tid;
|
||||
myMeta.tracks[tid].type = "video";
|
||||
myMeta.tracks[tid].init = std::string(tmpPage.getFullPayload() + offset, (*it));
|
||||
oggTracks[tid].parsedHeaders = true;
|
||||
oggTracks[tid].lastGran = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VORBIS: {
|
||||
vorbis::header tmpHead(tmpPage.getFullPayload() + offset, (*it));
|
||||
DEBUG_MSG(DLVL_WARN,"Vorbis header, type %d", tmpHead.getHeaderType());
|
||||
switch (tmpHead.getHeaderType()) {
|
||||
case 1: {
|
||||
myMeta.tracks[tid].channels = tmpHead.getAudioChannels();
|
||||
myMeta.tracks[tid].idHeader = std::string(tmpPage.getFullPayload() + offset, (*it));
|
||||
oggTracks[tid].channels = tmpHead.getAudioChannels();
|
||||
oggTracks[tid].blockSize[0] = 1 << tmpHead.getBlockSize0();
|
||||
oggTracks[tid].blockSize[1] = 1 << tmpHead.getBlockSize1();
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
myMeta.tracks[tid].commentHeader = std::string(tmpPage.getFullPayload() + offset, (*it));
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
myMeta.tracks[tid].codec = "vorbis";
|
||||
myMeta.tracks[tid].trackID = tid;
|
||||
myMeta.tracks[tid].type = "audio";
|
||||
DEBUG_MSG(DLVL_WARN,"Set default values");
|
||||
myMeta.tracks[tid].init = std::string(tmpPage.getFullPayload() + offset, (*it));
|
||||
DEBUG_MSG(DLVL_WARN,"Set init values");
|
||||
oggTracks[tid].vModes = tmpHead.readModeDeque(oggTracks[tid].channels);
|
||||
DEBUG_MSG(DLVL_WARN,"Set vmodevalues");
|
||||
oggTracks[tid].parsedHeaders = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
offset += (*it);
|
||||
}
|
||||
}
|
||||
lastBytePos = ftell(inFile);
|
||||
DEBUG_MSG(DLVL_WARN,"End of Loop, @ filepos %lld", lastBytePos);
|
||||
}
|
||||
DEBUG_MSG(DLVL_WARN,"Exited while loop");
|
||||
std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str());
|
||||
oFile << myMeta.toJSON().toNetPacked();
|
||||
oFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inputOGG::seekNextPage(int tid){
|
||||
fseek(inFile, oggTracks[tid].lastPageOffset, SEEK_SET);
|
||||
bool res = true;
|
||||
do {
|
||||
res = oggTracks[tid].myPage.read(inFile);
|
||||
} while(res && snum2tid[oggTracks[tid].myPage.getBitstreamSerialNumber()] != tid);
|
||||
oggTracks[tid].lastPageOffset = ftell(inFile);
|
||||
oggTracks[tid].nxtSegment = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
void inputOGG::getNext(bool smart) {
|
||||
if (!sortedSegments.size()){
|
||||
for (std::set<int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
seekNextPage((*it));
|
||||
}
|
||||
}
|
||||
if (sortedSegments.size()){
|
||||
int tid = (*(sortedSegments.begin())).tid;
|
||||
bool addedPacket = false;
|
||||
while (!addedPacket){
|
||||
segPart tmpPart;
|
||||
if (oggTracks[tid].myPage.getSegment(oggTracks[tid].nxtSegment, tmpPart.segData, tmpPart.len)){
|
||||
if (oggTracks[tid].nxtSegment == 0 && oggTracks[tid].myPage.getHeaderType() && OGG::Continued){
|
||||
segment tmpSeg = *(sortedSegments.begin());
|
||||
tmpSeg.parts.push_back(tmpPart);
|
||||
sortedSegments.erase(sortedSegments.begin());
|
||||
sortedSegments.insert(tmpSeg);
|
||||
}else{
|
||||
segment tmpSeg;
|
||||
tmpSeg.parts.push_back(tmpPart);
|
||||
tmpSeg.tid = tid;
|
||||
tmpSeg.time = oggTracks[tid].lastTime;
|
||||
if (oggTracks[tid].codec == VORBIS) {
|
||||
std::string data;
|
||||
data.append(tmpPart.segData, tmpPart.len);
|
||||
unsigned int blockSize = 0;
|
||||
Utils::bitstreamLSBF packet;
|
||||
packet.append(data);
|
||||
if (!packet.get(1)) {
|
||||
blockSize = oggTracks[tid].blockSize[oggTracks[tid].vModes[packet.get(vorbis::ilog(oggTracks[tid].vModes.size() - 1))].blockFlag];
|
||||
}
|
||||
oggTracks[tid].lastTime += oggTracks[tid].msPerFrame * (blockSize / oggTracks[tid].channels);
|
||||
}
|
||||
if (oggTracks[tid].codec == THEORA) {
|
||||
oggTracks[tid].lastTime += oggTracks[tid].msPerFrame;
|
||||
}
|
||||
sortedSegments.insert(tmpSeg);
|
||||
addedPacket = true;
|
||||
}
|
||||
oggTracks[tid].nxtSegment ++;
|
||||
}else{
|
||||
if (!seekNextPage(tid)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string data;
|
||||
}
|
||||
}
|
||||
|
||||
void inputOGG::seek(int seekTime) {
|
||||
DEBUG_MSG(DLVL_WARN,"Seeking is not yet supported for ogg files");
|
||||
//Do nothing, seeking is not yet implemented for ogg
|
||||
}
|
||||
|
||||
void inputOGG::trackSelect(std::string trackSpec) {
|
||||
selectedTracks.clear();
|
||||
long long int index;
|
||||
while (trackSpec != "") {
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
||||
DEBUG_MSG(DLVL_WARN, "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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
65
src/input/input_ogg.h
Normal file
65
src/input/input_ogg.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/ogg.h>
|
||||
|
||||
namespace Mist {
|
||||
enum codecType {THEORA, VORBIS};
|
||||
|
||||
struct segPart{
|
||||
char * segData;
|
||||
unsigned int len;
|
||||
};
|
||||
|
||||
struct segment{
|
||||
bool operator < (const segment & rhs) const {
|
||||
return time < rhs.time || (time == rhs.time && tid < rhs.tid);
|
||||
}
|
||||
std::vector<segPart> parts;
|
||||
unsigned int time;
|
||||
unsigned int tid;
|
||||
};
|
||||
|
||||
class oggTrack{
|
||||
public:
|
||||
oggTrack() : lastTime(0), parsedHeaders(false), lastPageOffset(0), nxtSegment(0) { }
|
||||
codecType codec;
|
||||
std::string contBuffer;//buffer for continuing pages
|
||||
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;
|
||||
long long unsigned int blockSize[2];
|
||||
};
|
||||
|
||||
class inputOGG : public Input {
|
||||
public:
|
||||
inputOGG(Util::Config * cfg);
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
bool seekNextPage(int tid);
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
|
||||
void parseBeginOfStream(OGG::Page & bosPage);
|
||||
|
||||
FILE * inFile;
|
||||
std::map<long long int, long long int> snum2tid;
|
||||
std::map<long long int, oggTrack> oggTracks;
|
||||
std::set<segment> sortedSegments;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputOGG mistIn;
|
||||
|
62
src/input/mist_in.cpp
Normal file
62
src/input/mist_in.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <semaphore.h>
|
||||
|
||||
#include INPUTTYPE
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
Util::Config conf(argv[0], PACKAGE_VERSION);
|
||||
mistIn conv(&conf);
|
||||
if (conf.parseArgs(argc, argv)) {
|
||||
sem_t * playerLock = sem_open(std::string("/lock_" + conf.getString("streamname")).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
if (sem_trywait(playerLock) == -1){
|
||||
DEBUG_MSG(DLVL_DEVEL, "A player for stream %s is already running", conf.getString("streamname").c_str());
|
||||
return 1;
|
||||
}
|
||||
conf.activate();
|
||||
while (conf.is_active){
|
||||
int pid = fork();
|
||||
if (pid == 0){
|
||||
sem_close(playerLock);
|
||||
return conv.run();
|
||||
}
|
||||
if (pid == -1){
|
||||
DEBUG_MSG(DLVL_FAIL, "Unable to spawn player process");
|
||||
sem_post(playerLock);
|
||||
return 2;
|
||||
}
|
||||
//wait for the process to exit
|
||||
int status;
|
||||
while (waitpid(pid, &status, 0) != pid && errno == EINTR) continue;
|
||||
//clean up the semaphore by waiting for it, if it's non-zero
|
||||
sem_t * waiting = sem_open(std::string("/wait_" + conf.getString("streamname")).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 0);
|
||||
if (waiting == SEM_FAILED){
|
||||
DEBUG_MSG(DLVL_FAIL, "Failed to open semaphore - cancelling");
|
||||
return -1;
|
||||
}
|
||||
int sem_val = 0;
|
||||
sem_getvalue(waiting, &sem_val);
|
||||
while (sem_val){
|
||||
while (sem_wait(waiting) == -1 && errno == EINTR) continue;
|
||||
sem_getvalue(waiting, &sem_val);
|
||||
}
|
||||
sem_close(waiting);
|
||||
//if the exit was clean, don't restart it
|
||||
if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Finished player succesfully");
|
||||
break;
|
||||
}
|
||||
}
|
||||
sem_post(playerLock);
|
||||
sem_close(playerLock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue