LTS Commits
This commit is contained in:
parent
f24d97b510
commit
4bdbd82f66
72 changed files with 8245 additions and 105 deletions
281
src/input/input_av.cpp
Normal file
281
src/input/input_av.cpp
Normal file
|
@ -0,0 +1,281 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include "input_av.h"
|
||||
|
||||
namespace Mist {
|
||||
inputAV::inputAV(Util::Config * cfg) : Input(cfg) {
|
||||
pFormatCtx = 0;
|
||||
capa["name"] = "AV";
|
||||
capa["decs"] = "Enables generic avformat/avcodec based input";
|
||||
capa["source_match"] = "/*";
|
||||
capa["priority"] = 1ll;
|
||||
capa["codecs"][0u][0u].null();
|
||||
capa["codecs"][0u][1u].null();
|
||||
capa["codecs"][0u][2u].null();
|
||||
av_register_all();
|
||||
AVCodec * cInfo = 0;
|
||||
while ((cInfo = av_codec_next(cInfo)) != 0){
|
||||
if (cInfo->type == AVMEDIA_TYPE_VIDEO){
|
||||
capa["codecs"][0u][0u].append(cInfo->name);
|
||||
}
|
||||
if (cInfo->type == AVMEDIA_TYPE_AUDIO){
|
||||
capa["codecs"][0u][1u].append(cInfo->name);
|
||||
}
|
||||
if (cInfo->type == AVMEDIA_TYPE_SUBTITLE){
|
||||
capa["codecs"][0u][3u].append(cInfo->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputAV::~inputAV(){
|
||||
if (pFormatCtx){
|
||||
avformat_close_input(&pFormatCtx);
|
||||
}
|
||||
}
|
||||
|
||||
bool inputAV::setup() {
|
||||
if (config->getString("input") == "-") {
|
||||
std::cerr << "Input from stdin not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!config->getString("streamname").size()){
|
||||
if (config->getString("output") == "-") {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//make sure all av inputs are registered properly, just in case
|
||||
//setup() already does this, but under windows it doesn't remember that it has.
|
||||
//Very sad, that. We may need to get windows some medication for it.
|
||||
av_register_all();
|
||||
|
||||
//close any already open files
|
||||
if (pFormatCtx){
|
||||
avformat_close_input(&pFormatCtx);
|
||||
pFormatCtx = 0;
|
||||
}
|
||||
|
||||
//Open video file
|
||||
int ret = avformat_open_input(&pFormatCtx, config->getString("input").c_str(), NULL, NULL);
|
||||
if(ret != 0){
|
||||
char errstr[300];
|
||||
av_strerror(ret, errstr, 300);
|
||||
DEBUG_MSG(DLVL_FAIL, "Could not open file: %s", errstr);
|
||||
return false; // Couldn't open file
|
||||
}
|
||||
|
||||
//Retrieve stream information
|
||||
ret = avformat_find_stream_info(pFormatCtx, NULL);
|
||||
if(ret < 0){
|
||||
char errstr[300];
|
||||
av_strerror(ret, errstr, 300);
|
||||
DEBUG_MSG(DLVL_FAIL, "Could not find stream info: %s", errstr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inputAV::readHeader() {
|
||||
//See whether a separate header file exists.
|
||||
DTSC::File tmp(config->getString("input") + ".dtsh");
|
||||
if (tmp){
|
||||
myMeta = tmp.getMeta();
|
||||
return true;
|
||||
}
|
||||
|
||||
myMeta.tracks.clear();
|
||||
myMeta.live = false;
|
||||
myMeta.vod = true;
|
||||
for(unsigned int i=0; i < pFormatCtx->nb_streams; ){
|
||||
AVStream * strm = pFormatCtx->streams[i++];
|
||||
myMeta.tracks[i].trackID = i;
|
||||
switch (strm->codec->codec_id){
|
||||
case AV_CODEC_ID_HEVC:
|
||||
myMeta.tracks[i].codec = "HEVC";
|
||||
break;
|
||||
case AV_CODEC_ID_H264:
|
||||
myMeta.tracks[i].codec = "H264";
|
||||
break;
|
||||
case AV_CODEC_ID_THEORA:
|
||||
myMeta.tracks[i].codec = "theora";
|
||||
break;
|
||||
case AV_CODEC_ID_VORBIS:
|
||||
myMeta.tracks[i].codec = "vorbis";
|
||||
break;
|
||||
case AV_CODEC_ID_OPUS:
|
||||
myMeta.tracks[i].codec = "opus";
|
||||
break;
|
||||
case AV_CODEC_ID_AAC:
|
||||
myMeta.tracks[i].codec = "AAC";
|
||||
break;
|
||||
case AV_CODEC_ID_MP3:
|
||||
myMeta.tracks[i].codec = "MP3";
|
||||
break;
|
||||
case AV_CODEC_ID_AC3:
|
||||
case AV_CODEC_ID_EAC3:
|
||||
myMeta.tracks[i].codec = "AC3";
|
||||
break;
|
||||
default:
|
||||
const AVCodecDescriptor *desc = av_codec_get_codec_descriptor(strm->codec);
|
||||
if (desc && desc->name){
|
||||
myMeta.tracks[i].codec = desc->name;
|
||||
}else{
|
||||
myMeta.tracks[i].codec = "?";
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (strm->codec->extradata_size){
|
||||
myMeta.tracks[i].init = std::string((char*)strm->codec->extradata, strm->codec->extradata_size);
|
||||
}
|
||||
if(strm->codec->codec_type == AVMEDIA_TYPE_VIDEO){
|
||||
myMeta.tracks[i].type = "video";
|
||||
if (strm->avg_frame_rate.den && strm->avg_frame_rate.num){
|
||||
myMeta.tracks[i].fpks = (strm->avg_frame_rate.num * 1000) / strm->avg_frame_rate.den;
|
||||
}else{
|
||||
myMeta.tracks[i].fpks = 0;
|
||||
}
|
||||
myMeta.tracks[i].width = strm->codec->width;
|
||||
myMeta.tracks[i].height = strm->codec->height;
|
||||
}
|
||||
if(strm->codec->codec_type == AVMEDIA_TYPE_AUDIO){
|
||||
myMeta.tracks[i].type = "audio";
|
||||
myMeta.tracks[i].rate = strm->codec->sample_rate;
|
||||
switch (strm->codec->sample_fmt){
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
case AV_SAMPLE_FMT_U8P:
|
||||
myMeta.tracks[i].size = 8;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
case AV_SAMPLE_FMT_S16P:
|
||||
myMeta.tracks[i].size = 16;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
case AV_SAMPLE_FMT_S32P:
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
case AV_SAMPLE_FMT_FLTP:
|
||||
myMeta.tracks[i].size = 32;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_DBL:
|
||||
case AV_SAMPLE_FMT_DBLP:
|
||||
myMeta.tracks[i].size = 64;
|
||||
break;
|
||||
default:
|
||||
myMeta.tracks[i].size = 0;
|
||||
break;
|
||||
}
|
||||
myMeta.tracks[i].channels = strm->codec->channels;
|
||||
}
|
||||
}
|
||||
|
||||
AVPacket packet;
|
||||
while(av_read_frame(pFormatCtx, &packet)>=0){
|
||||
AVStream * strm = pFormatCtx->streams[packet.stream_index];
|
||||
JSON::Value pkt;
|
||||
pkt["trackid"] = (long long)packet.stream_index + 1;
|
||||
pkt["data"] = std::string((char*)packet.data, packet.size);
|
||||
pkt["time"] = (long long)(packet.dts * 1000 * strm->time_base.num / strm->time_base.den);
|
||||
if (pkt["time"].asInt() < 0){
|
||||
pkt["time"] = 0ll;
|
||||
}
|
||||
if (packet.flags & AV_PKT_FLAG_KEY && myMeta.tracks[(long long)packet.stream_index + 1].type != "audio"){
|
||||
pkt["keyframe"] = 1ll;
|
||||
pkt["bpos"] = (long long)packet.pos;
|
||||
}
|
||||
if (packet.pts != AV_NOPTS_VALUE && packet.pts != packet.dts){
|
||||
pkt["offset"] = (long long)((packet.pts - packet.dts) * 1000 * strm->time_base.num / strm->time_base.den);
|
||||
}
|
||||
myMeta.update(pkt);
|
||||
av_free_packet(&packet);
|
||||
}
|
||||
myMeta.live = false;
|
||||
myMeta.vod = true;
|
||||
|
||||
//store dtsc-style header file for faster processing, later
|
||||
std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str());
|
||||
oFile << myMeta.toJSON().toNetPacked();
|
||||
oFile.close();
|
||||
|
||||
seek(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputAV::getNext(bool smart) {
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
AVStream * strm = pFormatCtx->streams[packet.stream_index];
|
||||
JSON::Value pkt;
|
||||
pkt["trackid"] = (long long)packet.stream_index + 1;
|
||||
pkt["data"] = std::string((char*)packet.data, packet.size);
|
||||
pkt["time"] = (long long)(packet.dts * 1000 * strm->time_base.num / strm->time_base.den);
|
||||
if (pkt["time"].asInt() < 0){
|
||||
pkt["time"] = 0ll;
|
||||
}
|
||||
if (packet.flags & AV_PKT_FLAG_KEY && myMeta.tracks[(long long)packet.stream_index + 1].type != "audio"){
|
||||
pkt["keyframe"] = 1ll;
|
||||
pkt["bpos"] = (long long)packet.pos;
|
||||
}
|
||||
if (packet.pts != AV_NOPTS_VALUE && packet.pts != packet.dts){
|
||||
pkt["offset"] = (long long)((packet.pts - packet.dts) * 1000 * strm->time_base.num / strm->time_base.den);
|
||||
}
|
||||
pkt.netPrepare();
|
||||
lastPack.reInit(pkt.toNetPacked().data(), pkt.toNetPacked().size());
|
||||
av_free_packet(&packet);
|
||||
return;//success!
|
||||
}
|
||||
lastPack.null();
|
||||
setup();
|
||||
//failure :-(
|
||||
DEBUG_MSG(DLVL_FAIL, "getNext failed");
|
||||
}
|
||||
|
||||
void inputAV::seek(int seekTime) {
|
||||
int stream_index = av_find_default_stream_index(pFormatCtx);
|
||||
//Convert ts to frame
|
||||
unsigned long long reseekTime = av_rescale(seekTime, pFormatCtx->streams[stream_index]->time_base.den, pFormatCtx->streams[stream_index]->time_base.num);
|
||||
reseekTime /= 1000;
|
||||
unsigned long long seekStreamDuration = pFormatCtx->streams[stream_index]->duration;
|
||||
int flags = AVSEEK_FLAG_BACKWARD;
|
||||
if (reseekTime > 0 && reseekTime < seekStreamDuration){
|
||||
flags |= AVSEEK_FLAG_ANY; // H.264 I frames don't always register as "key frames" in FFmpeg
|
||||
}
|
||||
int ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, flags);
|
||||
if (ret < 0){
|
||||
ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, AVSEEK_FLAG_ANY);
|
||||
}
|
||||
}
|
||||
|
||||
void inputAV::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);
|
||||
}
|
||||
}
|
||||
|
32
src/input/input_av.h
Normal file
32
src/input/input_av.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef INT64_MIN
|
||||
#define INT64_MIN (-(9223372036854775807 ## LL)-1)
|
||||
#endif
|
||||
|
||||
#ifndef INT64_MAX
|
||||
#define INT64_MAX ((9223372036854775807 ## LL))
|
||||
#endif
|
||||
|
||||
#include "input.h"
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
namespace Mist {
|
||||
class inputAV : public Input {
|
||||
public:
|
||||
inputAV(Util::Config * cfg);
|
||||
~inputAV();
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
private:
|
||||
AVFormatContext *pFormatCtx;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputAV mistIn;
|
|
@ -31,6 +31,44 @@ namespace Mist {
|
|||
capa["optional"]["DVR"]["option"] = "--buffer";
|
||||
capa["optional"]["DVR"]["type"] = "uint";
|
||||
capa["optional"]["DVR"]["default"] = 50000LL;
|
||||
/*LTS-start*/
|
||||
option.null();
|
||||
option["arg"] = "string";
|
||||
option["long"] = "record";
|
||||
option["short"] = "r";
|
||||
option["help"] = "Record the stream to a file";
|
||||
option["value"].append("");
|
||||
config->addOption("record", option);
|
||||
capa["optional"]["record"]["name"] = "Record to file";
|
||||
capa["optional"]["record"]["help"] = "Filename to record the stream to.";
|
||||
capa["optional"]["record"]["option"] = "--record";
|
||||
capa["optional"]["record"]["type"] = "str";
|
||||
capa["optional"]["record"]["default"] = "";
|
||||
option.null();
|
||||
option["arg"] = "integer";
|
||||
option["long"] = "cut";
|
||||
option["short"] = "c";
|
||||
option["help"] = "Any timestamps before this will be cut from the live buffer";
|
||||
option["value"].append(0LL);
|
||||
config->addOption("cut", option);
|
||||
capa["optional"]["cut"]["name"] = "Cut time (ms)";
|
||||
capa["optional"]["cut"]["help"] = "Any timestamps before this will be cut from the live buffer.";
|
||||
capa["optional"]["cut"]["option"] = "--cut";
|
||||
capa["optional"]["cut"]["type"] = "uint";
|
||||
capa["optional"]["cut"]["default"] = 0LL;
|
||||
option.null();
|
||||
option["arg"] = "integer";
|
||||
option["long"] = "segment-size";
|
||||
option["short"] = "S";
|
||||
option["help"] = "Target time duration in milliseconds for segments";
|
||||
option["value"].append(5000LL);
|
||||
config->addOption("segmentsize", option);
|
||||
capa["optional"]["segmentsize"]["name"] = "Segment size (ms)";
|
||||
capa["optional"]["segmentsize"]["help"] = "Target time duration in milliseconds for segments.";
|
||||
capa["optional"]["segmentsize"]["option"] = "--segment-size";
|
||||
capa["optional"]["segmentsize"]["type"] = "uint";
|
||||
capa["optional"]["segmentsize"]["default"] = 5000LL;
|
||||
/*LTS-end*/
|
||||
capa["source_match"] = "push://*";
|
||||
capa["priority"] = 9ll;
|
||||
capa["desc"] = "Provides buffered live input";
|
||||
|
@ -41,6 +79,7 @@ namespace Mist {
|
|||
singleton = this;
|
||||
bufferTime = 0;
|
||||
cutTime = 0;
|
||||
segmentSize = 0;
|
||||
}
|
||||
|
||||
inputBuffer::~inputBuffer() {
|
||||
|
@ -48,7 +87,29 @@ namespace Mist {
|
|||
if (myMeta.tracks.size()) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Cleaning up, removing last keyframes");
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
while (removeKey(it->first)) {}
|
||||
std::map<unsigned long, DTSCPageData> & locations = bufferLocations[it->first];
|
||||
|
||||
//First detect all entries on metaPage
|
||||
for (int i = 0; i < 8192; i += 8) {
|
||||
int * tmpOffset = (int *)(metaPages[it->first].mapped + i);
|
||||
if (tmpOffset[0] == 0 && tmpOffset[1] == 0) {
|
||||
continue;
|
||||
}
|
||||
unsigned long keyNum = ntohl(tmpOffset[0]);
|
||||
|
||||
//Add an entry into bufferLocations[tNum] for the pages we haven't handled yet.
|
||||
if (!locations.count(keyNum)) {
|
||||
locations[keyNum].curOffset = 0;
|
||||
}
|
||||
locations[keyNum].pageNum = keyNum;
|
||||
locations[keyNum].keyNum = ntohl(tmpOffset[1]);
|
||||
}
|
||||
for (std::map<unsigned long, DTSCPageData>::iterator it2 = locations.begin(); it2 != locations.end(); it2++){
|
||||
char thisPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), it->first, it2->first);
|
||||
IPC::sharedPage erasePage(thisPageName, 20971520);
|
||||
erasePage.master = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,8 +153,59 @@ namespace Mist {
|
|||
DEBUG_MSG(DLVL_HIGH, "Erasing key %d:%lu", 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++) {
|
||||
/*LTS-START*/
|
||||
if (recFile.is_open()) {
|
||||
if (!recMeta.tracks.count(tid)) {
|
||||
recMeta.tracks[tid] = myMeta.tracks[tid];
|
||||
recMeta.tracks[tid].reset();
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
myMeta.tracks[tid].parts.pop_front();
|
||||
}
|
||||
///\todo Fix recording
|
||||
/*LTS-START
|
||||
///\todo Maybe optimize this by keeping track of the byte positions
|
||||
if (recFile.good()){
|
||||
long long unsigned int firstms = myMeta.tracks[tid].keys[0].getTime();
|
||||
long long unsigned int lastms = myMeta.tracks[tid].lastms;
|
||||
if (myMeta.tracks[tid].keys.size() > 1){
|
||||
lastms = myMeta.tracks[tid].keys[1].getTime();
|
||||
}
|
||||
DEBUG_MSG(DLVL_DEVEL, "Recording track %d from %llums to %llums", tid, firstms, lastms);
|
||||
long long unsigned int bpos = 0;
|
||||
DTSC::Packet recPack;
|
||||
int pageLen = dataPages[tid][bufferLocations[tid].begin()->first].len;
|
||||
char * pageMapped = dataPages[tid][bufferLocations[tid].begin()->first].mapped;
|
||||
while( bpos < (unsigned long long)pageLen) {
|
||||
int tmpSize = ((int)pageMapped[bpos + 4] << 24) | ((int)pageMapped[bpos + 5] << 16) | ((int)pageMapped[bpos + 6] << 8) | (int)pageMapped[bpos + 7];
|
||||
tmpSize += 8;
|
||||
recPack.reInit(pageMapped + bpos, tmpSize, true);
|
||||
if (tmpSize != recPack.getDataLen()){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Something went wrong while trying to record a packet @ %llu, %d != %d", bpos, tmpSize, recPack.getDataLen());
|
||||
break;
|
||||
}
|
||||
if (recPack.getTime() >= lastms){/// \todo getTime never reaches >= lastms, so probably the recording bug has something to do with this
|
||||
DEBUG_MSG(DLVL_HIGH, "Stopping record, %llu >= %llu", recPack.getTime(), lastms);
|
||||
break;
|
||||
}
|
||||
if (recPack.getTime() >= firstms){
|
||||
//Actually record to file here
|
||||
JSON::Value recJSON = recPack.toJSON();
|
||||
recJSON["bpos"] = recBpos;
|
||||
recFile << recJSON.toNetPacked();
|
||||
recFile.flush();
|
||||
recBpos = recFile.tellp();
|
||||
recMeta.update(recJSON);
|
||||
}
|
||||
bpos += recPack.getDataLen();
|
||||
}
|
||||
recFile.flush();
|
||||
std::ofstream tmp(std::string(recName + ".dtsh").c_str());
|
||||
tmp << recMeta.toJSON().toNetPacked();
|
||||
tmp.close();
|
||||
}
|
||||
LTS-END*/
|
||||
//remove the key itself
|
||||
myMeta.tracks[tid].keys.pop_front();
|
||||
myMeta.tracks[tid].keySizes.pop_front();
|
||||
|
@ -242,6 +354,12 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void inputBuffer::userCallback(char * data, size_t len, unsigned int id) {
|
||||
/*LTS-START*/
|
||||
//Reload the configuration to make sure we stay up to date with changes through the api
|
||||
if (Util::epoch() - lastReTime > 4) {
|
||||
setup();
|
||||
}
|
||||
/*LTS-END*/
|
||||
//Static variable keeping track of the next temporary mapping to use for a track.
|
||||
static int nextTempId = 1001;
|
||||
//Get the counter of this user
|
||||
|
@ -331,6 +449,18 @@ namespace Mist {
|
|||
|
||||
std::string trackIdentifier = trackMeta.tracks.find(value)->second.getIdentifier();
|
||||
DEBUG_MSG(DLVL_HIGH, "Attempting colision detection for track %s", trackIdentifier.c_str());
|
||||
/*LTS-START*/
|
||||
//Get the identifier for the track, and attempt colission detection.
|
||||
int collidesWith = -1;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
//If the identifier of an existing track and the current track match, assume the are the same track and reject the negotiated one.
|
||||
///\todo Maybe switch to a new form of detecting collisions, especially with regards to multiple audio languages and camera angles.
|
||||
if (it->second.getIdentifier() == trackIdentifier) {
|
||||
collidesWith = it->first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
|
||||
//Remove the "negotiate" status in either case
|
||||
negotiatingTracks.erase(value);
|
||||
|
@ -338,15 +468,49 @@ namespace Mist {
|
|||
metaPages[value].master = true;
|
||||
metaPages.erase(value);
|
||||
|
||||
int finalMap = (trackMeta.tracks.find(value)->second.type == "video" ? 1 : 2);
|
||||
//Check if the track collides, and whether the track it collides with is active.
|
||||
if (collidesWith != -1 && activeTracks.count(collidesWith)) {/*LTS*/
|
||||
//Print a warning message and set the state of the track to rejected.
|
||||
WARN_MSG("Collision of temporary track %lu with existing track %d detected. Handling as a new valid track.", value, collidesWith);
|
||||
collidesWith = -1;
|
||||
}
|
||||
/*LTS-START*/
|
||||
unsigned long finalMap = collidesWith;
|
||||
if (finalMap == -1) {
|
||||
//No collision has been detected, assign a new final number
|
||||
finalMap = (myMeta.tracks.size() ? myMeta.tracks.rbegin()->first : 0) + 1;
|
||||
DEBUG_MSG(DLVL_DEVEL, "No colision detected for temporary track %lu from user %u, assigning final track number %lu", value, id, finalMap);
|
||||
}
|
||||
/*LTS-END*/
|
||||
//Resume either if we have more than 1 keyframe on the replacement track (assume it was already pushing before the track "dissapeared")
|
||||
//or if the firstms of the replacement track is later than the lastms on the existing track
|
||||
if (!myMeta.tracks.count(finalMap) || trackMeta.tracks.find(value)->second.keys.size() > 1 || trackMeta.tracks.find(value)->second.firstms >= myMeta.tracks[finalMap].lastms) {
|
||||
if (myMeta.tracks.count(finalMap) && myMeta.tracks[finalMap].lastms > 0) {
|
||||
INFO_MSG("Resume of track %d detected, coming from temporary track %lu of user %u", finalMap, value, id);
|
||||
INFO_MSG("Resume of track %lu detected, coming from temporary track %lu of user %u", finalMap, value, id);
|
||||
} else {
|
||||
INFO_MSG("New track detected, assigned track id %d, coming from temporary track %lu of user %u", finalMap, value, id);
|
||||
INFO_MSG("New track detected, assigned track id %lu, coming from temporary track %lu of user %u", finalMap, value, id);
|
||||
}
|
||||
} else {
|
||||
//Otherwise replace existing track
|
||||
INFO_MSG("Replacement of track %lu detected, coming from temporary track %lu of user %u", finalMap, value, id);
|
||||
myMeta.tracks.erase(finalMap);
|
||||
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
||||
metaPages[finalMap].master = true;
|
||||
metaPages.erase(finalMap);
|
||||
bufferLocations.erase(finalMap);
|
||||
}
|
||||
|
||||
//Register the new track as an active track.
|
||||
activeTracks.insert(finalMap);
|
||||
//Register the time of registration as initial value for the lastUpdated field.
|
||||
lastUpdated[finalMap] = Util::bootSecs();
|
||||
//Register the user thats is pushing this element
|
||||
pushLocation[finalMap] = thisData;
|
||||
//Initialize the metadata for this track if it was not in place yet.
|
||||
if (!myMeta.tracks.count(finalMap)) {
|
||||
DEBUG_MSG(DLVL_HIGH, "Inserting metadata for track number %lu", finalMap);
|
||||
myMeta.tracks[finalMap] = trackMeta.tracks.begin()->second;
|
||||
myMeta.tracks[finalMap].trackID = finalMap;
|
||||
}
|
||||
|
||||
//Register the new track as an active track.
|
||||
|
@ -456,7 +620,8 @@ namespace Mist {
|
|||
lastUpdated[tNum] = Util::bootSecs();
|
||||
while (tmpPack) {
|
||||
//Update the metadata with this packet
|
||||
myMeta.update(tmpPack);
|
||||
///\todo Why is there an LTS tag here?
|
||||
myMeta.update(tmpPack, segmentSize);/*LTS*/
|
||||
//Set the first time when appropriate
|
||||
if (pageData.firstTime == 0) {
|
||||
pageData.firstTime = tmpPack.getTime();
|
||||
|
@ -469,6 +634,7 @@ namespace Mist {
|
|||
}
|
||||
|
||||
bool inputBuffer::setup() {
|
||||
lastReTime = Util::epoch(); /*LTS*/
|
||||
std::string strName = config->getString("streamname");
|
||||
Util::sanitizeName(strName);
|
||||
strName = strName.substr(0, (strName.find_first_of("+ ")));
|
||||
|
@ -496,6 +662,74 @@ namespace Mist {
|
|||
bufferTime = tmpNum;
|
||||
}
|
||||
|
||||
/*LTS-START*/
|
||||
//if stream is configured and setting is present, use it, always
|
||||
if (streamCfg && streamCfg.getMember("cut")) {
|
||||
tmpNum = streamCfg.getMember("cut").asInt();
|
||||
} else {
|
||||
if (streamCfg) {
|
||||
//otherwise, if stream is configured use the default
|
||||
tmpNum = config->getOption("cut", true)[0u].asInt();
|
||||
} else {
|
||||
//if not, use the commandline argument
|
||||
tmpNum = config->getOption("cut").asInt();
|
||||
}
|
||||
}
|
||||
//if the new value is different, print a message and apply it
|
||||
if (cutTime != tmpNum) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Setting cutTime from %u to new value of %lli", cutTime, tmpNum);
|
||||
cutTime = tmpNum;
|
||||
}
|
||||
|
||||
|
||||
//if stream is configured and setting is present, use it, always
|
||||
if (streamCfg && streamCfg.getMember("segmentsize")) {
|
||||
tmpNum = streamCfg.getMember("segmentsize").asInt();
|
||||
} else {
|
||||
if (streamCfg) {
|
||||
//otherwise, if stream is configured use the default
|
||||
tmpNum = config->getOption("segmentsize", true)[0u].asInt();
|
||||
} else {
|
||||
//if not, use the commandline argument
|
||||
tmpNum = config->getOption("segmentsize").asInt();
|
||||
}
|
||||
}
|
||||
//if the new value is different, print a message and apply it
|
||||
if (segmentSize != tmpNum) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Setting segmentSize from %u to new value of %lli", segmentSize, tmpNum);
|
||||
segmentSize = tmpNum;
|
||||
}
|
||||
|
||||
//if stream is configured and setting is present, use it, always
|
||||
std::string rec;
|
||||
if (streamCfg && streamCfg.getMember("record")) {
|
||||
rec = streamCfg.getMember("record").asInt();
|
||||
} else {
|
||||
if (streamCfg) {
|
||||
//otherwise, if stream is configured use the default
|
||||
rec = config->getOption("record", true)[0u].asString();
|
||||
} else {
|
||||
//if not, use the commandline argument
|
||||
rec = config->getOption("record").asString();
|
||||
}
|
||||
}
|
||||
//if the new value is different, print a message and apply it
|
||||
if (recName != rec) {
|
||||
//close currently recording file, for we should open a new one
|
||||
DEBUG_MSG(DLVL_DEVEL, "Stopping recording of %s to %s", config->getString("streamname").c_str(), recName.c_str());
|
||||
recFile.close();
|
||||
recMeta.tracks.clear();
|
||||
recName = rec;
|
||||
}
|
||||
if (recName != "" && !recFile.is_open()) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Starting recording of %s to %s", config->getString("streamname").c_str(), recName.c_str());
|
||||
recFile.open(recName.c_str());
|
||||
if (recFile.fail()) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Error occured during record opening: %s", strerror(errno));
|
||||
}
|
||||
recBpos = 0;
|
||||
}
|
||||
/*LTS-END*/
|
||||
configLock.post();
|
||||
configLock.close();
|
||||
return true;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include <fstream>
|
||||
|
||||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/shared_memory.h>
|
||||
|
@ -10,6 +12,8 @@ namespace Mist {
|
|||
private:
|
||||
unsigned int bufferTime;
|
||||
unsigned int cutTime;
|
||||
unsigned int segmentSize; /*LTS*/
|
||||
unsigned int lastReTime; /*LTS*/
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
|
@ -31,6 +35,11 @@ namespace Mist {
|
|||
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations;
|
||||
std::map<unsigned long, char *> pushLocation;
|
||||
inputBuffer * singleton;
|
||||
|
||||
std::string recName;/*LTS*/
|
||||
DTSC::Meta recMeta;/*LTS*/
|
||||
std::ofstream recFile;/*LTS*/
|
||||
long long int recBpos;/*LTS*/
|
||||
};
|
||||
}
|
||||
|
||||
|
|
19
src/input/input_folder.cpp
Normal file
19
src/input/input_folder.cpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include "input_folder.h"
|
||||
|
||||
namespace Mist {
|
||||
inputFolder::inputFolder(Util::Config * cfg) : Input(cfg) {
|
||||
capa["name"] = "Folder";
|
||||
capa["decs"] = "Folder input, re-starts itself as the appropiate input.";
|
||||
capa["source_match"] = "/*/";
|
||||
capa["priority"] = 9ll;
|
||||
}
|
||||
}
|
14
src/input/input_folder.h
Normal file
14
src/input/input_folder.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
|
||||
namespace Mist {
|
||||
class inputFolder : public Input {
|
||||
public:
|
||||
inputFolder(Util::Config * cfg);
|
||||
protected:
|
||||
bool setup(){return false;};
|
||||
bool readHeader(){return false;};
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputFolder mistIn;
|
407
src/input/input_ismv.cpp
Normal file
407
src/input/input_ismv.cpp
Normal file
|
@ -0,0 +1,407 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include "input_ismv.h"
|
||||
|
||||
namespace Mist {
|
||||
inputISMV::inputISMV(Util::Config * cfg) : Input(cfg) {
|
||||
capa["name"] = "ISMV";
|
||||
capa["decs"] = "Enables ISMV Input";
|
||||
capa["source_match"] = "/*.ismv";
|
||||
capa["priority"] = 9ll;
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
|
||||
inFile = 0;
|
||||
}
|
||||
|
||||
bool inputISMV::setup() {
|
||||
if (config->getString("input") == "-") {
|
||||
std::cerr << "Input from stdin not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!config->getString("streamname").size()){
|
||||
if (config->getString("output") == "-") {
|
||||
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 = fopen(config->getString("input").c_str(), "r");
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inputISMV::readHeader() {
|
||||
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;
|
||||
}
|
||||
//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);
|
||||
//parse fragments form here
|
||||
while (parseFrag(tId, trunSamples, initVecs, mdat)) {
|
||||
if (!currentDuration.count(tId)) {
|
||||
currentDuration[tId] = 0;
|
||||
}
|
||||
currOffset = 8;
|
||||
int i = 0;
|
||||
while (currOffset < mdat.size()) {
|
||||
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) {
|
||||
lastPack["interframe"] = 1LL;
|
||||
lastBytePos ++;
|
||||
} else {
|
||||
lastPack["keyframe"] = 1LL;
|
||||
lastBytePos = curBytePos;
|
||||
}
|
||||
lastPack["bpos"] = lastBytePos;
|
||||
lastPack["nalu"] = 1LL;
|
||||
unsigned int offsetConv = trunSamples[i].sampleOffset / 10000;
|
||||
lastPack["offset"] = *(int*)&offsetConv;
|
||||
} else {
|
||||
if (i == 0) {
|
||||
lastPack["keyframe"] = 1LL;
|
||||
lastPack["bpos"] = curBytePos;
|
||||
}
|
||||
}
|
||||
myMeta.update(lastPack);
|
||||
currentDuration[tId] += trunSamples[i].sampleDuration;
|
||||
currOffset += trunSamples[i].sampleSize;
|
||||
i ++;
|
||||
}
|
||||
curBytePos = ftell(inFile);
|
||||
}
|
||||
std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str());
|
||||
oFile << myMeta.toJSON().toNetPacked();
|
||||
oFile.close();
|
||||
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"] = (long long int)(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"] = 1LL;
|
||||
} else {
|
||||
thisPack["interframe"] = 1LL;
|
||||
}
|
||||
thisPack["nalu"] = 1LL;
|
||||
thisPack["offset"] = buffered.begin()->offset / 10000;
|
||||
} else {
|
||||
if (buffered.begin()->isKeyFrame) {
|
||||
thisPack["keyframe"] = 1LL;
|
||||
}
|
||||
}
|
||||
thisPack["bpos"] = buffered.begin()->position;
|
||||
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]++;
|
||||
}
|
||||
}
|
||||
std::string tmpStr = thisPack.toNetPacked();
|
||||
thisPacket.reInit(tmpStr.data(), tmpStr.size());
|
||||
}
|
||||
|
||||
///\brief Overloads Input::atKeyFrame, for ISMV always sets the keyframe number
|
||||
bool inputISMV::atKeyFrame(){
|
||||
return thisPacket.getFlag("keyframe");
|
||||
}
|
||||
|
||||
void inputISMV::seek(int seekTime) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 = "";
|
||||
}
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool inputISMV::parseFrag(int & tId, std::vector<MP4::trunSampleInformation> & trunSamples, std::vector<std::string> & initVecs, std::string & mdat) {
|
||||
tId = -1;
|
||||
trunSamples.clear();
|
||||
initVecs.clear();
|
||||
mdat.clear();
|
||||
std::string boxRes;
|
||||
readBox("moof", boxRes);
|
||||
if (boxRes == ""){
|
||||
return false;
|
||||
}
|
||||
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*/
|
||||
}
|
||||
}
|
||||
}
|
||||
readBox("mdat", mdat);
|
||||
if (mdat ==""){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputISMV::parseFragHeader(const unsigned int & trackId, const unsigned int & keyNum) {
|
||||
if (!myMeta.tracks.count(trackId) || (myMeta.tracks[trackId].keys.size() <= keyNum)) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
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*/
|
||||
}
|
||||
}
|
||||
}
|
||||
lastPos = ftell(inFile) + 8;
|
||||
for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++){
|
||||
seekPos myPos;
|
||||
myPos.position = lastPos;
|
||||
myPos.trackId = trackId;
|
||||
myPos.time = lastTime;
|
||||
myPos.duration = trunBox.getSampleInformation(i).sampleDuration;
|
||||
myPos.size = trunBox.getSampleInformation(i).sampleSize;
|
||||
if( trunBox.getFlags() & MP4::trunsampleOffsets){
|
||||
unsigned int offsetConv = trunBox.getSampleInformation(i).sampleOffset;
|
||||
myPos.offset = *(int*)&offsetConv;
|
||||
}else{
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
51
src/input/input_ismv.h
Normal file
51
src/input/input_ismv.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/mp4.h>
|
||||
#include <mist/mp4_generic.h>
|
||||
#include <mist/mp4_encryption.h>
|
||||
#include <set>
|
||||
|
||||
namespace Mist {
|
||||
struct seekPos {
|
||||
bool operator < (const seekPos & rhs) const {
|
||||
if (time < rhs.time){
|
||||
return true;
|
||||
}
|
||||
return (time == rhs.time && trackId < rhs.trackId);
|
||||
}
|
||||
long long int position;
|
||||
int trackId;
|
||||
long long int time;
|
||||
long long int duration;
|
||||
int size;
|
||||
long long int offset;
|
||||
bool isKeyFrame;
|
||||
std::string iVec;
|
||||
};
|
||||
|
||||
|
||||
class inputISMV : public Input {
|
||||
public:
|
||||
inputISMV(Util::Config * cfg);
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
bool atKeyFrame();
|
||||
|
||||
FILE * inFile;
|
||||
|
||||
void parseMoov(MP4::MOOV & moovBox);
|
||||
bool parseFrag(int & tId, std::vector<MP4::trunSampleInformation> & trunSamples, std::vector<std::string> & initVecs, std::string & mdat);
|
||||
void parseFragHeader(const unsigned int & trackId, const unsigned int & keyNum);
|
||||
void readBox(const char * type, std::string & result);
|
||||
std::set<seekPos> buffered;
|
||||
std::map<int, int> lastKeyNum;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputISMV mistIn;
|
||||
|
|
@ -122,7 +122,7 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
if (!offset){
|
||||
DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %llu", filePos);
|
||||
DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %lu", filePos);
|
||||
return;
|
||||
}
|
||||
filePos += offset;
|
||||
|
|
632
src/input/input_mp4.cpp
Normal file
632
src/input/input_mp4.cpp
Normal file
|
@ -0,0 +1,632 @@
|
|||
#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_mp4.h"
|
||||
|
||||
namespace Mist {
|
||||
|
||||
mp4TrackHeader::mp4TrackHeader(){
|
||||
initialised = false;
|
||||
stscStart = 0;
|
||||
sampleIndex = 0;
|
||||
deltaIndex = 0;
|
||||
deltaPos = 0;
|
||||
deltaTotal = 0;
|
||||
offsetIndex = 0;
|
||||
offsetPos = 0;
|
||||
sttsBox.clear();
|
||||
cttsBox.clear();
|
||||
stszBox.clear();
|
||||
stcoBox.clear();
|
||||
}
|
||||
|
||||
long unsigned int mp4TrackHeader::size(){
|
||||
if (!stszBox.asBox()){
|
||||
return 0;
|
||||
}else{
|
||||
return stszBox.getSampleCount();
|
||||
}
|
||||
}
|
||||
|
||||
void mp4TrackHeader::read(MP4::TRAK & trakBox){
|
||||
initialised = false;
|
||||
std::string tmp;//temporary string for copying box data
|
||||
MP4::Box trakLoopPeek;
|
||||
timeScale = 1;
|
||||
//for all in trak
|
||||
for (uint32_t j = 0; j < trakBox.getContentCount(); j++){
|
||||
trakLoopPeek = MP4::Box(trakBox.getContent(j).asBox(),false);
|
||||
std::string trakBoxType = trakLoopPeek.getType();
|
||||
if (trakBoxType == "mdia"){//fi tkhd & if mdia
|
||||
MP4::Box mdiaLoopPeek;
|
||||
//for all in mdia
|
||||
for (uint32_t k = 0; k < ((MP4::MDIA&)trakLoopPeek).getContentCount(); k++){
|
||||
mdiaLoopPeek = MP4::Box(((MP4::MDIA&)trakLoopPeek).getContent(k).asBox(),false);
|
||||
std::string mdiaBoxType = mdiaLoopPeek.getType();
|
||||
if (mdiaBoxType == "mdhd"){
|
||||
timeScale = ((MP4::MDHD&)mdiaLoopPeek).getTimeScale();
|
||||
}else if (mdiaBoxType == "minf"){//fi hdlr
|
||||
//for all in minf
|
||||
//get all boxes: stco stsz,stss
|
||||
MP4::Box minfLoopPeek;
|
||||
for (uint32_t l = 0; l < ((MP4::MINF&)mdiaLoopPeek).getContentCount(); l++){
|
||||
minfLoopPeek = MP4::Box(((MP4::MINF&)mdiaLoopPeek).getContent(l).asBox(),false);
|
||||
std::string minfBoxType = minfLoopPeek.getType();
|
||||
///\todo more stuff here
|
||||
if (minfBoxType == "stbl"){
|
||||
MP4::Box stblLoopPeek;
|
||||
for (uint32_t m = 0; m < ((MP4::STBL&)minfLoopPeek).getContentCount(); m++){
|
||||
stblLoopPeek = MP4::Box(((MP4::STBL&)minfLoopPeek).getContent(m).asBox(),false);
|
||||
std::string stblBoxType = stblLoopPeek.getType();
|
||||
if (stblBoxType == "stts"){
|
||||
tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize());
|
||||
sttsBox.clear();
|
||||
sttsBox.read(tmp);
|
||||
}else if (stblBoxType == "ctts"){
|
||||
tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize());
|
||||
cttsBox.clear();
|
||||
cttsBox.read(tmp);
|
||||
}else if (stblBoxType == "stsz"){
|
||||
tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize());
|
||||
stszBox.clear();
|
||||
stszBox.read(tmp);
|
||||
}else if (stblBoxType == "stco" || stblBoxType == "co64"){
|
||||
tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize());
|
||||
stcoBox.clear();
|
||||
stcoBox.read(tmp);
|
||||
}else if (stblBoxType == "stsc"){
|
||||
tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize());
|
||||
stscBox.clear();
|
||||
stscBox.read(tmp);
|
||||
}
|
||||
}//rof stbl
|
||||
}//fi stbl
|
||||
}//rof minf
|
||||
}//fi minf
|
||||
}//rof mdia
|
||||
}//fi mdia
|
||||
}//rof trak
|
||||
}
|
||||
|
||||
void mp4TrackHeader::getPart(long unsigned int index, long long unsigned int & offset,unsigned int& size, long long unsigned int & timestamp, long long unsigned int & timeOffset){
|
||||
|
||||
|
||||
if (index < sampleIndex){
|
||||
sampleIndex = 0;
|
||||
stscStart = 0;
|
||||
}
|
||||
|
||||
while (stscStart < stscBox.getEntryCount()){
|
||||
//check where the next index starts
|
||||
unsigned long long nextSampleIndex;
|
||||
if (stscStart + 1 < stscBox.getEntryCount()){
|
||||
nextSampleIndex = sampleIndex + (stscBox.getSTSCEntry(stscStart+1).firstChunk - stscBox.getSTSCEntry(stscStart).firstChunk) * stscBox.getSTSCEntry(stscStart).samplesPerChunk;
|
||||
}else{
|
||||
nextSampleIndex = stszBox.getSampleCount();
|
||||
}
|
||||
//if the next one is too far, we're in the right spot
|
||||
if (nextSampleIndex > index){
|
||||
break;
|
||||
}
|
||||
sampleIndex = nextSampleIndex;
|
||||
++stscStart;
|
||||
}
|
||||
|
||||
if (sampleIndex > index){
|
||||
FAIL_MSG("Could not complete seek - not in file (%llu > %lu)", sampleIndex, index);
|
||||
}
|
||||
|
||||
long long unsigned stcoPlace = (stscBox.getSTSCEntry(stscStart).firstChunk - 1 ) + ((index - sampleIndex) / stscBox.getSTSCEntry(stscStart).samplesPerChunk);
|
||||
long long unsigned stszStart = sampleIndex + (stcoPlace - (stscBox.getSTSCEntry(stscStart).firstChunk - 1)) * stscBox.getSTSCEntry(stscStart).samplesPerChunk;
|
||||
|
||||
//set the offset to the correct value
|
||||
if (stcoBox.getType() == "co64"){
|
||||
offset = ((MP4::CO64*)&stcoBox)->getChunkOffset(stcoPlace);
|
||||
}else{
|
||||
offset = stcoBox.getChunkOffset(stcoPlace);
|
||||
}
|
||||
|
||||
for (int j = stszStart; j < index; j++){
|
||||
offset += stszBox.getEntrySize(j);
|
||||
}
|
||||
|
||||
if (index < deltaPos){
|
||||
deltaIndex = 0;
|
||||
deltaPos = 0;
|
||||
deltaTotal = 0;
|
||||
}
|
||||
|
||||
MP4::STTSEntry tmpSTTS;
|
||||
while (deltaIndex < sttsBox.getEntryCount()){
|
||||
tmpSTTS = sttsBox.getSTTSEntry(deltaIndex);
|
||||
if ((index - deltaPos) < tmpSTTS.sampleCount){
|
||||
break;
|
||||
}else{
|
||||
deltaTotal += tmpSTTS.sampleCount * tmpSTTS.sampleDelta;
|
||||
deltaPos += tmpSTTS.sampleCount;
|
||||
}
|
||||
++deltaIndex;
|
||||
}
|
||||
timestamp = ((deltaTotal + ((index-deltaPos) * tmpSTTS.sampleDelta))*1000) / timeScale;
|
||||
initialised = true;
|
||||
|
||||
if (index < offsetPos){
|
||||
offsetIndex = 0;
|
||||
offsetPos = 0;
|
||||
}
|
||||
MP4::CTTSEntry tmpCTTS;
|
||||
while (offsetIndex < cttsBox.getEntryCount()){
|
||||
tmpCTTS = cttsBox.getCTTSEntry(offsetIndex);
|
||||
if ((index - offsetPos) < tmpCTTS.sampleCount){
|
||||
timeOffset = (tmpCTTS.sampleOffset*1000)/timeScale;
|
||||
break;
|
||||
}
|
||||
offsetPos += tmpCTTS.sampleCount;
|
||||
++offsetIndex;
|
||||
}
|
||||
|
||||
//next lines are common for next-getting and seeking
|
||||
size = stszBox.getEntrySize(index);
|
||||
|
||||
}
|
||||
|
||||
inputMP4::inputMP4(Util::Config * cfg) : Input(cfg) {
|
||||
malSize = 4;//initialise data read buffer to 0;
|
||||
data = (char*)malloc(malSize);
|
||||
capa["name"] = "MP4";
|
||||
capa["decs"] = "Enables MP4 Input";
|
||||
capa["source_match"] = "/*.mp4";
|
||||
capa["priority"] = 9ll;
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
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("AC3");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
}
|
||||
|
||||
inputMP4::~inputMP4(){
|
||||
free(data);
|
||||
}
|
||||
|
||||
bool inputMP4::setup() {
|
||||
if (config->getString("input") == "-") {
|
||||
std::cerr << "Input from stdin not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!config->getString("streamname").size()){
|
||||
if (config->getString("output") == "-") {
|
||||
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 = fopen(config->getString("input").c_str(), "r");
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool inputMP4::readHeader() {
|
||||
if (!inFile) {
|
||||
INFO_MSG("inFile failed!");
|
||||
return false;
|
||||
}
|
||||
//make trackmap here from inFile
|
||||
long long unsigned int trackNo = 0;
|
||||
|
||||
std::string tmp;//temp string for reading boxes.
|
||||
|
||||
|
||||
//first we get the necessary header parts
|
||||
while(!feof(inFile)){
|
||||
std::string boxType = MP4::readBoxType(inFile);
|
||||
if (boxType=="moov"){
|
||||
MP4::MOOV moovBox;
|
||||
moovBox.read(inFile);
|
||||
//for all box in moov
|
||||
|
||||
MP4::Box moovLoopPeek;
|
||||
for (uint32_t i = 0; i < moovBox.getContentCount(); i++){
|
||||
tmp = std::string(moovBox.getContent(i).asBox() ,moovBox.getContent(i).boxedSize());
|
||||
moovLoopPeek.read(tmp);
|
||||
//if trak
|
||||
if (moovLoopPeek.getType() == "trak"){
|
||||
//create new track entry here
|
||||
trackNo ++;
|
||||
|
||||
headerData[trackNo].read((MP4::TRAK&)moovLoopPeek);
|
||||
}
|
||||
}
|
||||
}else if (boxType == "erro"){
|
||||
break;
|
||||
}else{
|
||||
if (!MP4::skipBox(inFile)){//moving on to next box
|
||||
DEBUG_MSG(DLVL_FAIL,"Error in skipping box, exiting");
|
||||
return false;
|
||||
}
|
||||
}//fi moov
|
||||
}//when at the end of the file
|
||||
//seek file to 0;
|
||||
fseeko(inFile,0,SEEK_SET);
|
||||
|
||||
//See whether a separate header file exists.
|
||||
DTSC::File tmpdtsh(config->getString("input") + ".dtsh");
|
||||
if (tmpdtsh){
|
||||
myMeta = tmpdtsh.getMeta();
|
||||
return true;
|
||||
}
|
||||
trackNo = 0;
|
||||
std::set<mp4PartBpos> BPosSet;
|
||||
//Create header file from MP4 data
|
||||
while(!feof(inFile)){
|
||||
std::string boxType = MP4::readBoxType(inFile);
|
||||
if (boxType=="moov"){
|
||||
MP4::MOOV moovBox;
|
||||
moovBox.read(inFile);
|
||||
//for all box in moov
|
||||
MP4::Box moovLoopPeek;
|
||||
for (uint32_t i = 0; i < moovBox.getContentCount(); i++){
|
||||
tmp = std::string(moovBox.getContent(i).asBox(), moovBox.getContent(i).boxedSize());
|
||||
moovLoopPeek.read(tmp);
|
||||
//if trak
|
||||
if (moovLoopPeek.getType() == "trak"){
|
||||
//create new track entry here
|
||||
long long unsigned int trackNo = myMeta.tracks.size()+1;
|
||||
myMeta.tracks[trackNo].trackID = trackNo;
|
||||
MP4::Box trakLoopPeek;
|
||||
unsigned long int timeScale = 1;
|
||||
//for all in trak
|
||||
for (uint32_t j = 0; j < ((MP4::TRAK&)moovLoopPeek).getContentCount(); j++){
|
||||
tmp = std::string(((MP4::MOOV&)moovLoopPeek).getContent(j).asBox(),((MP4::MOOV&)moovLoopPeek).getContent(j).boxedSize());
|
||||
trakLoopPeek.read(tmp);
|
||||
std::string trakBoxType = trakLoopPeek.getType();
|
||||
//note: per track: trackID codec, type (vid/aud), init
|
||||
//if tkhd
|
||||
if (trakBoxType == "tkhd"){
|
||||
MP4::TKHD tkhdBox(0,0,0,0);///\todo: this can be done with casting
|
||||
tmp = std::string(trakLoopPeek.asBox(), trakLoopPeek.boxedSize());
|
||||
tkhdBox.read(tmp);
|
||||
//remember stuff for decoding stuff later
|
||||
if (tkhdBox.getWidth() > 0){
|
||||
myMeta.tracks[trackNo].width = tkhdBox.getWidth();
|
||||
myMeta.tracks[trackNo].height = tkhdBox.getHeight();
|
||||
}
|
||||
}else if (trakBoxType == "mdia"){//fi tkhd & if mdia
|
||||
MP4::Box mdiaLoopPeek;
|
||||
//for all in mdia
|
||||
for (uint32_t k = 0; k < ((MP4::MDIA&)trakLoopPeek).getContentCount(); k++){
|
||||
tmp = std::string(((MP4::MDIA&)trakLoopPeek).getContent(k).asBox(),((MP4::MDIA&)trakLoopPeek).getContent(k).boxedSize());
|
||||
mdiaLoopPeek.read(tmp);
|
||||
std::string mdiaBoxType = mdiaLoopPeek.getType();
|
||||
if (mdiaBoxType == "mdhd"){
|
||||
timeScale = ((MP4::MDHD&)mdiaLoopPeek).getTimeScale();
|
||||
}else if (mdiaBoxType == "hdlr"){//fi mdhd
|
||||
std::string handlerType = ((MP4::HDLR&)mdiaLoopPeek).getHandlerType();
|
||||
if (handlerType != "vide" && handlerType !="soun"){
|
||||
myMeta.tracks.erase(trackNo);
|
||||
//skip meta boxes for now
|
||||
break;
|
||||
}
|
||||
}else if (mdiaBoxType == "minf"){//fi hdlr
|
||||
//for all in minf
|
||||
//get all boxes: stco stsz,stss
|
||||
MP4::Box minfLoopPeek;
|
||||
for (uint32_t l = 0; l < ((MP4::MINF&)mdiaLoopPeek).getContentCount(); l++){
|
||||
tmp = std::string(((MP4::MINF&)mdiaLoopPeek).getContent(l).asBox(),((MP4::MINF&)mdiaLoopPeek).getContent(l).boxedSize());
|
||||
minfLoopPeek.read(tmp);
|
||||
std::string minfBoxType = minfLoopPeek.getType();
|
||||
///\todo more stuff here
|
||||
if (minfBoxType == "stbl"){
|
||||
MP4::Box stblLoopPeek;
|
||||
MP4::STSS stssBox;
|
||||
MP4::STTS sttsBox;
|
||||
MP4::STSZ stszBox;
|
||||
MP4::STCO stcoBox;
|
||||
MP4::STSC stscBox;
|
||||
for (uint32_t m = 0; m < ((MP4::STBL&)minfLoopPeek).getContentCount(); m++){
|
||||
tmp = std::string(((MP4::STBL&)minfLoopPeek).getContent(m).asBox(),((MP4::STBL&)minfLoopPeek).getContent(m).boxedSize());
|
||||
std::string stboxRead = tmp;
|
||||
stblLoopPeek.read(tmp);
|
||||
std::string stblBoxType = stblLoopPeek.getType();
|
||||
if (stblBoxType == "stss"){
|
||||
stssBox.read(stboxRead);
|
||||
}else if (stblBoxType == "stts"){
|
||||
sttsBox.read(stboxRead);
|
||||
}else if (stblBoxType == "stsz"){
|
||||
stszBox.read(stboxRead);
|
||||
}else if (stblBoxType == "stco" || stblBoxType == "co64"){
|
||||
stcoBox.read(stboxRead);
|
||||
}else if (stblBoxType == "stsc"){
|
||||
stscBox.read(stboxRead);
|
||||
}else if (stblBoxType == "stsd"){
|
||||
//check for codec in here
|
||||
MP4::Box & tmpBox = ((MP4::STSD&)stblLoopPeek).getEntry(0);
|
||||
std::string tmpType = tmpBox.getType();
|
||||
INFO_MSG("Found track of type %s", tmpType.c_str());
|
||||
if (tmpType == "avc1" || tmpType == "h264" || tmpType == "mp4v"){
|
||||
myMeta.tracks[trackNo].type = "video";
|
||||
myMeta.tracks[trackNo].codec = "H264";
|
||||
myMeta.tracks[trackNo].width = ((MP4::VisualSampleEntry&)tmpBox).getWidth();
|
||||
myMeta.tracks[trackNo].height = ((MP4::VisualSampleEntry&)tmpBox).getHeight();
|
||||
MP4::Box tmpBox2 = tmpBox;
|
||||
MP4::Box tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getCLAP();
|
||||
if (tmpContent.getType() == "avcC"){
|
||||
myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize());
|
||||
}
|
||||
tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getPASP();
|
||||
if (tmpContent.getType() == "avcC"){
|
||||
myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize());
|
||||
}
|
||||
}else if (tmpType == "hev1"){
|
||||
myMeta.tracks[trackNo].type = "video";
|
||||
myMeta.tracks[trackNo].codec = "HEVC";
|
||||
myMeta.tracks[trackNo].width = ((MP4::VisualSampleEntry&)tmpBox).getWidth();
|
||||
myMeta.tracks[trackNo].height = ((MP4::VisualSampleEntry&)tmpBox).getHeight();
|
||||
MP4::Box tmpBox2 = ((MP4::VisualSampleEntry&)tmpBox).getCLAP();
|
||||
myMeta.tracks[trackNo].init = std::string(tmpBox2.payload(),tmpBox2.payloadSize());
|
||||
}else if (tmpType == "mp4a" || tmpType == "aac " || tmpType == "ac-3"){
|
||||
myMeta.tracks[trackNo].type = "audio";
|
||||
myMeta.tracks[trackNo].channels = ((MP4::AudioSampleEntry&)tmpBox).getChannelCount();
|
||||
myMeta.tracks[trackNo].rate = (long long int)(((MP4::AudioSampleEntry&)tmpBox).getSampleRate());
|
||||
if (tmpType == "ac-3"){
|
||||
myMeta.tracks[trackNo].codec = "AC3";
|
||||
}else{
|
||||
MP4::Box esds = ((MP4::AudioSampleEntry&)tmpBox).getCodecBox();
|
||||
if (((MP4::ESDS&)esds).isAAC()){
|
||||
myMeta.tracks[trackNo].codec = "AAC";
|
||||
myMeta.tracks[trackNo].init = ((MP4::ESDS&)esds).getInitData();
|
||||
}else{
|
||||
myMeta.tracks[trackNo].codec = "MP3";
|
||||
}
|
||||
}
|
||||
myMeta.tracks[trackNo].size = 16;///\todo this might be nice to calculate from mp4 file;
|
||||
//get Visual sample entry -> esds -> startcodes
|
||||
}else{
|
||||
myMeta.tracks.erase(trackNo);
|
||||
}
|
||||
}
|
||||
}//rof stbl
|
||||
uint64_t totaldur = 0;///\todo note: set this to begin time
|
||||
mp4PartBpos BsetPart;
|
||||
long long unsigned int entryNo = 0;
|
||||
long long unsigned int sampleNo = 0;
|
||||
MP4::STTSEntry tempSTTS;
|
||||
tempSTTS = sttsBox.getSTTSEntry(entryNo);
|
||||
long long unsigned int curSTSS = 0;
|
||||
bool vidTrack = (myMeta.tracks[trackNo].type == "video");
|
||||
//change to for over all samples
|
||||
unsigned int stcoIndex = 0;
|
||||
unsigned int stscIndex = 0;
|
||||
unsigned int fromSTCOinSTSC = 0;
|
||||
long long unsigned int tempOffset;
|
||||
bool stcoIs64 = (stcoBox.getType() == "co64");
|
||||
if (stcoIs64){
|
||||
tempOffset = ((MP4::CO64*)&stcoBox)->getChunkOffset(0);
|
||||
}else{
|
||||
tempOffset = stcoBox.getChunkOffset(0);
|
||||
}
|
||||
long long unsigned int nextFirstChunk;
|
||||
if (stscBox.getEntryCount() > 1){
|
||||
nextFirstChunk = stscBox.getSTSCEntry(1).firstChunk - 1;
|
||||
}else{
|
||||
if (stcoIs64){
|
||||
nextFirstChunk = ((MP4::CO64*)&stcoBox)->getEntryCount();
|
||||
}else{
|
||||
nextFirstChunk = stcoBox.getEntryCount();
|
||||
}
|
||||
}
|
||||
for(long long unsigned int sampleIndex = 0; sampleIndex < stszBox.getSampleCount(); sampleIndex ++){
|
||||
if (stcoIndex >= nextFirstChunk){//note
|
||||
stscIndex ++;
|
||||
if (stscIndex + 1 < stscBox.getEntryCount()){
|
||||
nextFirstChunk = stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1;
|
||||
}else{
|
||||
if (stcoIs64){
|
||||
nextFirstChunk = ((MP4::CO64*)&stcoBox)->getEntryCount();
|
||||
}else{
|
||||
nextFirstChunk = stcoBox.getEntryCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vidTrack && curSTSS < stssBox.getEntryCount() && sampleIndex + 1 == stssBox.getSampleNumber(curSTSS)){
|
||||
BsetPart.keyframe = true;
|
||||
curSTSS ++;
|
||||
}else{
|
||||
BsetPart.keyframe = false;
|
||||
}
|
||||
//in bpos set
|
||||
BsetPart.stcoNr=stcoIndex;
|
||||
//bpos = chunkoffset[samplenr] in stco
|
||||
BsetPart.bpos = tempOffset;
|
||||
fromSTCOinSTSC ++;
|
||||
if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){//as long as we are still in this chunk
|
||||
tempOffset += stszBox.getEntrySize(sampleIndex);
|
||||
}else{
|
||||
stcoIndex ++;
|
||||
fromSTCOinSTSC = 0;
|
||||
if (stcoIs64){
|
||||
tempOffset = ((MP4::CO64*)&stcoBox)->getChunkOffset(stcoIndex);
|
||||
}else{
|
||||
tempOffset = stcoBox.getChunkOffset(stcoIndex);
|
||||
}
|
||||
}
|
||||
//time = totaldur + stts[entry][sample]
|
||||
BsetPart.time = (totaldur*1000)/timeScale;
|
||||
totaldur += tempSTTS.sampleDelta;
|
||||
sampleNo++;
|
||||
if (sampleNo >= tempSTTS.sampleCount){
|
||||
entryNo++;
|
||||
sampleNo = 0;
|
||||
if (entryNo < sttsBox.getEntryCount()){
|
||||
tempSTTS = sttsBox.getSTTSEntry(entryNo);
|
||||
}
|
||||
}
|
||||
//set size, that's easy
|
||||
BsetPart.size = stszBox.getEntrySize(sampleIndex);
|
||||
//trackid
|
||||
BsetPart.trackID=trackNo;
|
||||
BPosSet.insert(BsetPart);
|
||||
}//while over stsc
|
||||
if (vidTrack){
|
||||
//something wrong with the time formula, but the answer is right for some reason
|
||||
/// \todo Fix this. This makes no sense whatsoever.
|
||||
if (stcoIs64){
|
||||
myMeta.tracks[trackNo].fpks = (((double)(((MP4::CO64*)&stcoBox)->getEntryCount()*1000))/((totaldur*1000)/timeScale))*1000;
|
||||
}else{
|
||||
myMeta.tracks[trackNo].fpks = (((double)(stcoBox.getEntryCount()*1000))/((totaldur*1000)/timeScale))*1000;
|
||||
}
|
||||
}
|
||||
}//fi stbl
|
||||
}//rof minf
|
||||
}//fi minf
|
||||
}//rof mdia
|
||||
}//fi mdia
|
||||
}//rof trak
|
||||
}//endif trak
|
||||
}//rof moov
|
||||
}else if (boxType == "erro"){
|
||||
break;
|
||||
}else{
|
||||
if (!MP4::skipBox(inFile)){//moving on to next box
|
||||
DEBUG_MSG(DLVL_FAIL,"Error in Skipping box, exiting");
|
||||
return false;
|
||||
}
|
||||
}// if moov
|
||||
}// end while file read
|
||||
//for all in bpos set, find its data
|
||||
clearerr(inFile);
|
||||
|
||||
for (std::set<mp4PartBpos>::iterator it = BPosSet.begin(); it != BPosSet.end(); it++){
|
||||
if (!fseeko(inFile,it->bpos,SEEK_SET)){
|
||||
if (it->size > malSize){
|
||||
data = (char*)realloc(data, it->size);
|
||||
malSize = it->size;
|
||||
}
|
||||
int tmp = fread(data, it->size, 1, inFile);
|
||||
if (tmp == 1){
|
||||
//add data
|
||||
myMeta.update(it->time, 1, it->trackID, it->size, it->bpos, it->keyframe);
|
||||
}else{
|
||||
INFO_MSG("fread did not return 1, bpos: %llu size: %llu keyframe: %d error: %s", it->bpos, it->size, it->keyframe, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
INFO_MSG("fseek failed!");
|
||||
return false;
|
||||
}
|
||||
}//rof bpos set
|
||||
//outputting dtsh file
|
||||
std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str());
|
||||
oFile << myMeta.toJSON().toNetPacked();
|
||||
oFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputMP4::getNext(bool smart) {//get next part from track in stream
|
||||
if (curPositions.empty()){
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
//pop uit set
|
||||
mp4PartTime curPart = *curPositions.begin();
|
||||
curPositions.erase(curPositions.begin());
|
||||
|
||||
bool isKeyframe = false;
|
||||
if(nextKeyframe[curPart.trackID] < myMeta.tracks[curPart.trackID].keys.size()){
|
||||
//checking if this is a keyframe
|
||||
if (myMeta.tracks[curPart.trackID].type == "video" && (long long int) curPart.time == myMeta.tracks[curPart.trackID].keys[(nextKeyframe[curPart.trackID])].getTime()){
|
||||
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 (fseeko(inFile,curPart.bpos,SEEK_SET)){
|
||||
DEBUG_MSG(DLVL_FAIL,"seek unsuccessful; bpos: %llu error: %s",curPart.bpos, strerror(errno));
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
if (curPart.size > malSize){
|
||||
data = (char*)realloc(data, curPart.size);
|
||||
malSize = curPart.size;
|
||||
}
|
||||
if (fread(data, curPart.size, 1, inFile)!=1){
|
||||
DEBUG_MSG(DLVL_FAIL,"read unsuccessful at %ld", ftell(inFile));
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size, 0/*Note: no bpos*/, isKeyframe);
|
||||
|
||||
//get the next part for this track
|
||||
curPart.index ++;
|
||||
if (curPart.index < headerData[curPart.trackID].size()){
|
||||
headerData[curPart.trackID].getPart(curPart.index, curPart.bpos, curPart.size, curPart.time, curPart.offset);
|
||||
curPositions.insert(curPart);
|
||||
}
|
||||
}
|
||||
|
||||
void inputMP4::seek(int seekTime) {//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);
|
||||
//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
|
||||
}
|
||||
|
||||
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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
102
src/input/input_mp4.h
Normal file
102
src/input/input_mp4.h
Normal file
|
@ -0,0 +1,102 @@
|
|||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/mp4.h>
|
||||
#include <mist/mp4_generic.h>
|
||||
namespace Mist {
|
||||
class mp4PartTime{
|
||||
public:
|
||||
mp4PartTime() : time(0), offset(0), trackID(0), bpos(0), size(0), index(0) {}
|
||||
bool operator < (const mp4PartTime & rhs) const {
|
||||
if (time < rhs.time){
|
||||
return true;
|
||||
}else{
|
||||
if (time == rhs.time){
|
||||
if (trackID < rhs.trackID){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
long long unsigned int time;
|
||||
long long unsigned int offset;
|
||||
unsigned int trackID;
|
||||
long long unsigned int bpos;
|
||||
unsigned int size;
|
||||
long unsigned int index;
|
||||
};
|
||||
|
||||
struct mp4PartBpos{
|
||||
bool operator < (const mp4PartBpos & rhs) const {
|
||||
if (time < rhs.time){
|
||||
return true;
|
||||
}else{
|
||||
if (time == rhs.time){
|
||||
if (trackID < rhs.trackID){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
long long unsigned int time;
|
||||
long long unsigned int trackID;
|
||||
long long unsigned int bpos;
|
||||
long long unsigned int size;
|
||||
long long unsigned int stcoNr;
|
||||
bool keyframe;
|
||||
};
|
||||
|
||||
class mp4TrackHeader{
|
||||
public:
|
||||
mp4TrackHeader();
|
||||
void read(MP4::TRAK & trakBox);
|
||||
MP4::STCO stcoBox;
|
||||
MP4::STSZ stszBox;
|
||||
MP4::STTS sttsBox;
|
||||
MP4::CTTS cttsBox;
|
||||
MP4::STSC stscBox;
|
||||
long unsigned int timeScale;
|
||||
void getPart(long unsigned int index, long long unsigned int & offset,unsigned int& size, long long unsigned int & timestamp, long long unsigned int & timeOffset);
|
||||
long unsigned int size();
|
||||
private:
|
||||
bool initialised;
|
||||
//next variables are needed for the stsc/stco loop
|
||||
long long unsigned int stscStart;
|
||||
long long unsigned int sampleIndex;
|
||||
//next variables are needed for the stts loop
|
||||
long long unsigned deltaIndex;///< Index in STTS box
|
||||
long long unsigned deltaPos;///< Sample counter for STTS box
|
||||
long long unsigned deltaTotal;///< Total timestamp for STTS box
|
||||
//for CTTS box loop
|
||||
long long unsigned offsetIndex;///< Index in CTTS box
|
||||
long long unsigned offsetPos;///< Sample counter for CTTS box
|
||||
};
|
||||
|
||||
class inputMP4 : public Input {
|
||||
public:
|
||||
inputMP4(Util::Config * cfg);
|
||||
~inputMP4();
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
|
||||
FILE * inFile;
|
||||
|
||||
std::map<unsigned int, mp4TrackHeader> headerData;
|
||||
std::set<mp4PartTime> curPositions;
|
||||
|
||||
//remember last seeked keyframe;
|
||||
std::map <unsigned int, unsigned int> nextKeyframe;
|
||||
|
||||
//these next two variables keep a buffer for reading from filepointer inFile;
|
||||
unsigned long long int malSize;
|
||||
char* data;///\todo rename this variable to a more sensible name, it is a temporary piece of memory to read from files
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputMP4 mistIn;
|
516
src/input/input_ts.cpp
Executable file
516
src/input/input_ts.cpp
Executable file
|
@ -0,0 +1,516 @@
|
|||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/flv_tag.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/ts_packet.h>
|
||||
#include <mist/mp4_generic.h>
|
||||
#include "input_ts.h"
|
||||
|
||||
namespace Mist {
|
||||
|
||||
|
||||
/// Constructor of TS Input
|
||||
/// \arg cfg Util::Config that contains all current configurations.
|
||||
inputTS::inputTS(Util::Config * cfg) : Input(cfg) {
|
||||
capa["name"] = "TS";
|
||||
capa["decs"] = "Enables TS Input";
|
||||
capa["source_match"] = "/*.ts";
|
||||
capa["priority"] = 9ll;
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
}
|
||||
|
||||
///Setup of TS Input
|
||||
bool inputTS::setup() {
|
||||
if (config->getString("input") == "-") {
|
||||
inFile = stdin;
|
||||
}else{
|
||||
inFile = fopen(config->getString("input").c_str(), "r");
|
||||
}
|
||||
|
||||
if (config->getString("output") != "-") {
|
||||
std::cerr << "Output to non-stdout not yet supported" << std::endl;
|
||||
}
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
///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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void inputTS::parsePESHeader(int tid, pesBuffer & buf){
|
||||
if (buf.data.size() < 9){
|
||||
return;
|
||||
}
|
||||
if (buf.data.size() < 9 + buf.data[8]){
|
||||
return;
|
||||
}
|
||||
if( (((int)buf.data[0] << 16) | ((int)buf.data[1] << 8) | buf.data[2]) != 0x000001){
|
||||
DEBUG_MSG(DLVL_WARN, "Parsing PES for track %d failed due to incorrect header (%0.6X), throwing away", tid, (((int)buf.data[0] << 16) | ((int)buf.data[1] << 8) | buf.data[2]) );
|
||||
buf.data = "";
|
||||
return;
|
||||
}
|
||||
buf.len = (((int)buf.data[4] << 8) | buf.data[5]) - (3 + buf.data[8]);
|
||||
if ((buf.data[7] >> 6) & 0x02){//Check for PTS presence
|
||||
buf.time = ((buf.data[9] >> 1) & 0x07);
|
||||
buf.time <<= 15;
|
||||
buf.time |= ((int)buf.data[10] << 7) | ((buf.data[11] >> 1) & 0x7F);
|
||||
buf.time <<= 15;
|
||||
buf.time |= ((int)buf.data[12] << 7) | ((buf.data[13] >> 1) & 0x7F);
|
||||
buf.time /= 90;
|
||||
if (((buf.data[7] & 0xC0) >> 6) & 0x01){//Check for DTS presence (yes, only if PTS present)
|
||||
buf.offset = buf.time;
|
||||
buf.time = ((buf.data[14] >> 1) & 0x07);
|
||||
buf.time <<= 15;
|
||||
buf.time |= ((int)buf.data[15] << 7) | ((buf.data[16] >> 1) & 0x7F);
|
||||
buf.time <<= 15;
|
||||
buf.time |= ((int)buf.data[17] << 7) | ((buf.data[18] >> 1) & 0x7F);
|
||||
buf.time /= 90;
|
||||
buf.offset -= buf.time;
|
||||
}
|
||||
}
|
||||
if (!firstTimes.count(tid)){
|
||||
firstTimes[tid] = buf.time;
|
||||
}
|
||||
buf.time -= firstTimes[tid];
|
||||
buf.data.erase(0, 9 + buf.data[8]);
|
||||
}
|
||||
|
||||
void inputTS::parsePESPayload(int tid, pesBuffer & buf){
|
||||
if (myMeta.tracks[tid].codec == "H264"){
|
||||
parseH264PES(tid, buf);
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "AAC"){
|
||||
parseAACPES(tid, buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void inputTS::parseAACPES(int tid, pesBuffer & buf){
|
||||
if (!buf.data.size()){
|
||||
buf.len = 0;
|
||||
return;
|
||||
}
|
||||
if (myMeta.tracks[tid].init == ""){
|
||||
char audioInit[2];//5 bits object type, 4 bits frequency index, 4 bits channel index
|
||||
char AACProfile = ((buf.data[2] >> 6) & 0x03) + 1;
|
||||
char frequencyIndex = ((buf.data[2] >> 2) & 0x0F);
|
||||
char channelConfig = ((buf.data[2] & 0x01) << 2) | ((buf.data[3] >> 6) & 0x03);
|
||||
switch(frequencyIndex){
|
||||
case 0: myMeta.tracks[tid].rate = 96000; break;
|
||||
case 1: myMeta.tracks[tid].rate = 88200; break;
|
||||
case 2: myMeta.tracks[tid].rate = 64000; break;
|
||||
case 3: myMeta.tracks[tid].rate = 48000; break;
|
||||
case 4: myMeta.tracks[tid].rate = 44100; break;
|
||||
case 5: myMeta.tracks[tid].rate = 32000; break;
|
||||
case 6: myMeta.tracks[tid].rate = 24000; break;
|
||||
case 7: myMeta.tracks[tid].rate = 22050; break;
|
||||
case 8: myMeta.tracks[tid].rate = 16000; break;
|
||||
case 9: myMeta.tracks[tid].rate = 12000; break;
|
||||
case 10: myMeta.tracks[tid].rate = 11025; break;
|
||||
case 11: myMeta.tracks[tid].rate = 8000; break;
|
||||
case 12: myMeta.tracks[tid].rate = 7350; break;
|
||||
default: myMeta.tracks[tid].rate = 0; break;
|
||||
}
|
||||
myMeta.tracks[tid].channels = channelConfig;
|
||||
if (channelConfig == 7){
|
||||
myMeta.tracks[tid].channels = 8;
|
||||
}
|
||||
audioInit[0] = ((AACProfile & 0x1F) << 3) | ((frequencyIndex & 0x0E) >> 1);
|
||||
audioInit[1] = ((frequencyIndex & 0x01) << 7) | ((channelConfig & 0x0F) << 3);
|
||||
myMeta.tracks[tid].init = std::string(audioInit, 2);
|
||||
//\todo This value is right now hardcoded, maybe fix this when we know how
|
||||
myMeta.tracks[tid].size = 16;
|
||||
}
|
||||
buf.len = (((buf.data[3] & 0x03) << 11) | (buf.data[4] << 3) | ((buf.data[5] >> 5) & 0x07)) - (buf.data[1] & 0x01 ? 7 :9);
|
||||
buf.curSampleCount += 1024 * ((buf.data[6] & 0x3) + 1);//Number of frames * samples per frame(1024);
|
||||
buf.data.erase(0, (buf.data[1] & 0x01 ? 7 : 9));//Substract header
|
||||
}
|
||||
|
||||
void inputTS::parseH264PES(int tid, pesBuffer & buf){
|
||||
static char annexB[] = {0x00,0x00,0x01};
|
||||
static char nalLen[4];
|
||||
|
||||
int nalLength = 0;
|
||||
std::string newData;
|
||||
int pos = 0;
|
||||
int nxtPos = buf.data.find(annexB, pos, 3);
|
||||
//Rewrite buf.data from annexB to size-prefixed h.264
|
||||
while (nxtPos != std::string::npos){
|
||||
//Detect next packet (if any) and deduce current packet length
|
||||
pos = nxtPos + 3;
|
||||
nxtPos = buf.data.find(annexB, pos, 3);
|
||||
if (nxtPos == std::string::npos){
|
||||
nalLength = buf.data.size() - pos;
|
||||
}else{
|
||||
nalLength = nxtPos - pos;
|
||||
if (buf.data[nxtPos - 1] == 0x00){//4-byte annexB header
|
||||
nalLength--;
|
||||
}
|
||||
}
|
||||
//Do nal type specific stuff
|
||||
switch (buf.data[pos] & 0x1F){
|
||||
case 0x05: buf.isKey = true; break;
|
||||
case 0x07: buf.sps = buf.data.substr(pos, nalLength); break;
|
||||
case 0x08: buf.pps = buf.data.substr(pos, nalLength); break;
|
||||
default: break;
|
||||
}
|
||||
if ((buf.data[pos] & 0x1F) != 0x07 && (buf.data[pos] & 0x1F) != 0x08 && (buf.data[pos] & 0x1F) != 0x09){
|
||||
//Append length + payload
|
||||
nalLen[0] = (nalLength >> 24) & 0xFF;
|
||||
nalLen[1] = (nalLength >> 16) & 0xFF;
|
||||
nalLen[2] = (nalLength >> 8) & 0xFF;
|
||||
nalLen[3] = nalLength & 0xFF;
|
||||
newData.append(nalLen, 4);
|
||||
newData += buf.data.substr(pos, nalLength);
|
||||
}
|
||||
}
|
||||
buf.data = newData;
|
||||
buf.len = newData.size();
|
||||
//If this packet had both a Sequence Parameter Set (sps) and a Picture Parameter Set (pps), calculate the metadata for the stream
|
||||
if (buf.sps != "" && buf.pps != ""){
|
||||
MP4::AVCC avccBox;
|
||||
avccBox.setVersion(1);
|
||||
avccBox.setProfile(buf.sps[1]);
|
||||
avccBox.setCompatibleProfiles(buf.sps[2]);
|
||||
avccBox.setLevel(buf.sps[3]);
|
||||
avccBox.setSPSNumber(1);
|
||||
avccBox.setSPS(buf.sps);
|
||||
avccBox.setPPSNumber(1);
|
||||
avccBox.setPPS(buf.pps);
|
||||
myMeta.tracks[tid].init = std::string(avccBox.payload(), avccBox.payloadSize());
|
||||
h264::SPS tmpNal(buf.sps, true);
|
||||
h264::SPSMeta tmpMeta = tmpNal.getCharacteristics();
|
||||
myMeta.tracks[tid].width = tmpMeta.width;
|
||||
myMeta.tracks[tid].height = tmpMeta.height;
|
||||
myMeta.tracks[tid].fpks = tmpMeta.fps * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
///Reads headers from a TS stream, and saves them into metadata
|
||||
///It works by going through the entire TS stream, and every time
|
||||
///It encounters a new PES start, it writes the currently found PES data
|
||||
///for a specific track to metadata. After the entire stream has been read,
|
||||
///it writes the remaining metadata.
|
||||
///\todo Find errors, perhaps parts can be made more modular
|
||||
bool inputTS::readHeader(){
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
DTSC::File tmp(config->getString("input") + ".dtsh");
|
||||
if (tmp){
|
||||
myMeta = tmp.getMeta();
|
||||
return true;
|
||||
}
|
||||
|
||||
TS::Packet packet;//to analyse and extract data
|
||||
fseek(inFile, 0, SEEK_SET);//seek to beginning
|
||||
JSON::Value thisPacket;
|
||||
|
||||
std::set<int> PATIds;
|
||||
std::map<int, int> pidToType;
|
||||
std::map<int, pesBuffer> lastBuffer;
|
||||
|
||||
//h264::SPSmMta spsdata;//to analyse sps data, and extract resolution etc...
|
||||
long long int lastBpos = 0;
|
||||
while (packet.FromFile(inFile)){
|
||||
//Handle special packets (PAT/PMT)
|
||||
if(packet.getPID() == 0x00){
|
||||
PATIds.clear();
|
||||
for (int i = 0; i < ((TS::ProgramAssociationTable&)packet).getProgramCount(); i++){
|
||||
PATIds.insert(((TS::ProgramAssociationTable&)packet).getProgramPID(i));
|
||||
}
|
||||
}
|
||||
if(PATIds.count(packet.getPID())){
|
||||
TS::ProgramMappingEntry entry = ((TS::ProgramMappingTable&)packet).getEntry(0);
|
||||
while(entry){
|
||||
unsigned int pid = entry.getElementaryPid();
|
||||
pidToType[pid] = entry.getStreamType();
|
||||
//Check if the track exists in metadata
|
||||
if (!myMeta.tracks.count(pid)){
|
||||
switch (entry.getStreamType()){
|
||||
case 0x1B:
|
||||
myMeta.tracks[pid].codec = "H264";
|
||||
myMeta.tracks[pid].type = "video";
|
||||
myMeta.tracks[pid].trackID = pid;
|
||||
break;
|
||||
case 0x0F:
|
||||
myMeta.tracks[pid].codec = "AAC";
|
||||
myMeta.tracks[pid].type = "audio";
|
||||
myMeta.tracks[pid].trackID = pid;
|
||||
break;
|
||||
case 0x81:
|
||||
myMeta.tracks[pid].codec = "AC3";
|
||||
myMeta.tracks[pid].type = "audio";
|
||||
myMeta.tracks[pid].trackID = pid;
|
||||
break;
|
||||
default:
|
||||
DEBUG_MSG(DLVL_WARN, "Ignoring unsupported track type %0.2X, on pid %d", entry.getStreamType(), pid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
entry.advance();
|
||||
}
|
||||
}
|
||||
if(pidToType.count(packet.getPID())){
|
||||
//analyzing audio/video
|
||||
//we have audio/video payload
|
||||
//get trackID of this packet
|
||||
int tid = packet.getPID();
|
||||
if (packet.getUnitStart() && lastBuffer.count(tid) && lastBuffer[tid].len){
|
||||
parsePESPayload(tid, lastBuffer[tid]);
|
||||
thisPacket.null();
|
||||
thisPacket["data"] = lastBuffer[tid].data;
|
||||
thisPacket["trackid"] = tid;//last trackid
|
||||
thisPacket["bpos"] = lastBuffer[tid].bpos;
|
||||
thisPacket["time"] = lastBuffer[tid].time ;
|
||||
if (lastBuffer[tid].offset){
|
||||
thisPacket["offset"] = lastBuffer[tid].offset;
|
||||
}
|
||||
if (lastBuffer[tid].isKey){
|
||||
thisPacket["keyframe"] = 1LL;
|
||||
}
|
||||
myMeta.update(thisPacket);//metadata was read in
|
||||
lastBuffer.erase(tid);
|
||||
}
|
||||
if (!lastBuffer.count(tid)){
|
||||
lastBuffer[tid] = pesBuffer();
|
||||
lastBuffer[tid].bpos = lastBpos;
|
||||
}
|
||||
lastBuffer[tid].data.append(packet.getPayload(), packet.getPayloadLength());
|
||||
if (!lastBuffer[tid].len){
|
||||
parsePESHeader(tid, lastBuffer[tid]);
|
||||
}
|
||||
if (lastBuffer[tid].data.size() == lastBuffer[tid].len) {
|
||||
parsePESPayload(tid, lastBuffer[tid]);
|
||||
if (myMeta.tracks[tid].codec == "AAC"){
|
||||
while(lastBuffer[tid].data.size()){
|
||||
thisPacket.null();
|
||||
thisPacket["data"] = lastBuffer[tid].data.substr(0, lastBuffer[tid].len);
|
||||
thisPacket["trackid"] = tid;//last trackid
|
||||
thisPacket["bpos"] = lastBuffer[tid].bpos;
|
||||
thisPacket["time"] = lastBuffer[tid].time + (long long int)((double)((lastBuffer[tid].curSampleCount - 1024) * 1000)/ myMeta.tracks[tid].rate) ;
|
||||
myMeta.update(thisPacket);//metadata was read in
|
||||
lastBuffer[tid].data.erase(0, lastBuffer[tid].len);
|
||||
parsePESPayload(tid, lastBuffer[tid]);
|
||||
}
|
||||
}else{
|
||||
thisPacket.null();
|
||||
thisPacket["data"] = lastBuffer[tid].data;
|
||||
thisPacket["trackid"] = tid;//last trackid
|
||||
thisPacket["bpos"] = lastBuffer[tid].bpos;
|
||||
thisPacket["time"] = lastBuffer[tid].time ;
|
||||
if (myMeta.tracks[tid].type == "video"){
|
||||
if (lastBuffer[tid].offset){
|
||||
thisPacket["offset"] = lastBuffer[tid].offset;
|
||||
}
|
||||
if (lastBuffer[tid].isKey){
|
||||
thisPacket["keyframe"] = 1LL;
|
||||
}
|
||||
}
|
||||
myMeta.update(thisPacket);//metadata was read in
|
||||
}
|
||||
lastBuffer.erase(tid);
|
||||
}
|
||||
}
|
||||
lastBpos = ftell(inFile);
|
||||
}
|
||||
|
||||
std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str());
|
||||
oFile << myMeta.toJSON().toNetPacked();
|
||||
oFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
///Reads a full PES packet, starting at the current byteposition
|
||||
///Assumes that you want a full PES for the first PID encountered
|
||||
///\todo Update to search for a specific PID
|
||||
pesBuffer inputTS::readFullPES(int tid){
|
||||
pesBuffer pesBuf;
|
||||
pesBuf.tid = tid;
|
||||
if (feof(inFile)){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Trying to read a PES past the end of the file, returning");
|
||||
return pesBuf;
|
||||
}
|
||||
unsigned int lastPos = ftell(inFile);
|
||||
TS::Packet tsBuf;
|
||||
tsBuf.FromFile(inFile);
|
||||
//Find first PES start on the selected track
|
||||
while (tsBuf.getPID() != tid || !tsBuf.getUnitStart()){
|
||||
lastPos = ftell(inFile);
|
||||
tsBuf.FromFile(inFile);
|
||||
if (feof(inFile)){
|
||||
return pesBuf;
|
||||
}
|
||||
}
|
||||
pesBuf.bpos = lastPos;
|
||||
pesBuf.data.append(tsBuf.getPayload(), tsBuf.getPayloadLength());
|
||||
parsePESHeader(tid, pesBuf);
|
||||
bool unbound = false;
|
||||
while (pesBuf.data.size() != pesBuf.len){
|
||||
//ReadNextPage
|
||||
tsBuf.FromFile(inFile);
|
||||
if (tsBuf.getPID() == tid && tsBuf.getUnitStart()){
|
||||
unbound = true;
|
||||
break;
|
||||
}
|
||||
if (feof(inFile)){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Reached EOF at an unexpected point... what happened?");
|
||||
return pesBuf;
|
||||
}
|
||||
if (tsBuf.getPID() == tid){
|
||||
pesBuf.data.append(tsBuf.getPayload(), tsBuf.getPayloadLength());
|
||||
pesBuf.lastPos = ftell(inFile);
|
||||
}
|
||||
if (pesBuf.len == 0){
|
||||
parsePESHeader(tid, pesBuf);
|
||||
}
|
||||
}
|
||||
pesBuf.lastPos = ftell(inFile);
|
||||
if (unbound){
|
||||
pesBuf.lastPos -= 188;
|
||||
}
|
||||
parsePESPayload(tid, pesBuf);
|
||||
return pesBuf;
|
||||
}
|
||||
|
||||
///Gets the next packet that is to be sent
|
||||
///At the moment, the logic of sending the last packet that was finished has been implemented,
|
||||
///but the seeking and finding data is not yet ready.
|
||||
///\todo Finish the implementation
|
||||
void inputTS::getNext(bool smart){
|
||||
static JSON::Value thisPack;
|
||||
if ( !playbackBuf.size()){
|
||||
DEBUG_MSG(DLVL_WARN, "No seek positions set - returning empty packet.");
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
|
||||
//Store current buffer
|
||||
pesBuffer thisBuf = *playbackBuf.begin();
|
||||
playbackBuf.erase(playbackBuf.begin());
|
||||
|
||||
//Seek follow up
|
||||
fseek(inFile, thisBuf.lastPos, SEEK_SET);
|
||||
pesBuffer nxtBuf;
|
||||
if (myMeta.tracks[thisBuf.tid].codec != "AAC" || playbackBuf.size() < 2){
|
||||
nxtBuf = readFullPES(thisBuf.tid);
|
||||
}
|
||||
if (nxtBuf.len){
|
||||
if (myMeta.tracks[nxtBuf.tid].codec == "AAC"){//only in case of aac we have more packets, for now
|
||||
while (nxtBuf.len){
|
||||
pesBuffer pesBuf;
|
||||
pesBuf.tid = nxtBuf.tid;
|
||||
pesBuf.time = nxtBuf.time + ((double)((nxtBuf.curSampleCount - 1024) * 1000)/ myMeta.tracks[nxtBuf.tid].rate) ;
|
||||
pesBuf.offset = nxtBuf.offset;
|
||||
pesBuf.len = nxtBuf.len;
|
||||
pesBuf.lastPos = nxtBuf.lastPos;
|
||||
pesBuf.isKey = false;
|
||||
pesBuf.data = nxtBuf.data.substr(0, nxtBuf.len);
|
||||
playbackBuf.insert(pesBuf);
|
||||
|
||||
nxtBuf.data.erase(0, nxtBuf.len);
|
||||
parsePESPayload(thisBuf.tid, nxtBuf);
|
||||
}
|
||||
}else{
|
||||
nxtBuf.data = nxtBuf.data.substr(0, nxtBuf.len);
|
||||
playbackBuf.insert(nxtBuf);
|
||||
}
|
||||
}
|
||||
|
||||
thisPack.null();
|
||||
thisPack["data"] = thisBuf.data;
|
||||
thisPack["trackid"] = thisBuf.tid;
|
||||
thisPack["bpos"] = thisBuf.bpos;
|
||||
thisPack["time"] = thisBuf.time;
|
||||
if (thisBuf.offset){
|
||||
thisPack["offset"] = thisBuf.offset;
|
||||
}
|
||||
if (thisBuf.isKey){
|
||||
thisPack["keyframe"] = 1LL;
|
||||
}
|
||||
std::string tmpStr = thisPack.toNetPacked();
|
||||
thisPacket.reInit(tmpStr.data(), tmpStr.size());
|
||||
}
|
||||
|
||||
///Seeks to a specific time
|
||||
void inputTS::seek(int seekTime){
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
if (feof(inFile)){
|
||||
clearerr(inFile);
|
||||
fseek(inFile, 0, SEEK_SET);
|
||||
}
|
||||
pesBuffer tmpBuf;
|
||||
tmpBuf.tid = *it;
|
||||
for (unsigned int i = 0; i < myMeta.tracks[*it].keys.size(); i++){
|
||||
if (myMeta.tracks[*it].keys[i].getTime() > seekTime){
|
||||
break;
|
||||
}
|
||||
if (myMeta.tracks[*it].keys[i].getTime() > tmpBuf.time){
|
||||
tmpBuf.time = myMeta.tracks[*it].keys[i].getTime();
|
||||
tmpBuf.bpos = myMeta.tracks[*it].keys[i].getBpos();
|
||||
}
|
||||
}
|
||||
|
||||
bool foundPacket = false;
|
||||
unsigned long long lastPos;
|
||||
pesBuffer nxtBuf;
|
||||
while ( !foundPacket){
|
||||
lastPos = ftell(inFile);
|
||||
if (feof(inFile)){
|
||||
DEBUG_MSG(DLVL_WARN, "Reached EOF during seek to %u in track %d - aborting @ %lld", seekTime, *it, lastPos);
|
||||
return;
|
||||
}
|
||||
fseek(inFile, tmpBuf.bpos, SEEK_SET);
|
||||
nxtBuf = readFullPES(*it);
|
||||
if (nxtBuf.time >= seekTime){
|
||||
foundPacket = true;
|
||||
}else{
|
||||
tmpBuf.bpos = nxtBuf.lastPos;
|
||||
}
|
||||
}
|
||||
if (myMeta.tracks[nxtBuf.tid].codec == "AAC"){//only in case of aac we have more packets, for now
|
||||
while (nxtBuf.len){
|
||||
pesBuffer pesBuf;
|
||||
pesBuf.tid = nxtBuf.tid;
|
||||
pesBuf.time = nxtBuf.time + ((double)((nxtBuf.curSampleCount - 1024) * 1000)/ myMeta.tracks[nxtBuf.tid].rate);
|
||||
pesBuf.offset = nxtBuf.offset;
|
||||
pesBuf.len = nxtBuf.len;
|
||||
pesBuf.lastPos = nxtBuf.lastPos;
|
||||
pesBuf.isKey = false;
|
||||
pesBuf.data = nxtBuf.data.substr(0, nxtBuf.len);
|
||||
playbackBuf.insert(pesBuf);
|
||||
|
||||
nxtBuf.data.erase(0, nxtBuf.len);
|
||||
parsePESPayload(nxtBuf.tid, nxtBuf);
|
||||
}
|
||||
}else{
|
||||
playbackBuf.insert(nxtBuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
81
src/input/input_ts.h
Executable file
81
src/input/input_ts.h
Executable file
|
@ -0,0 +1,81 @@
|
|||
#include "input.h"
|
||||
#include <mist/nal.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/ts_packet.h>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
|
||||
namespace Mist {
|
||||
class pesBuffer {
|
||||
public:
|
||||
pesBuffer() : lastPos(0), len(0), time(0), offset(0), bpos(0), curSampleCount(0), isKey(false) {}
|
||||
///\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 pesBuffer & rhs) const {
|
||||
if (time < rhs.time) {
|
||||
return true;
|
||||
} else {
|
||||
if (time == rhs.time){
|
||||
if (tid < rhs.tid){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int tid;//When used for buffering, not for header generation
|
||||
long long int lastPos;//set by readFullPES, stores the byteposition directly after the last read ts packet
|
||||
long long int len;
|
||||
std::string data;
|
||||
long long int time;
|
||||
long long int offset;
|
||||
long long int bpos;
|
||||
long long int curSampleCount;
|
||||
bool isKey;
|
||||
std::string sps;
|
||||
std::string pps;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
/// This struct stores all metadata of a track, and sends them once a whole PES has been analyzed and sent
|
||||
struct trackInfo{
|
||||
//saves all data that needs to be sent.
|
||||
//as packets can be interleaved, the data needs to be temporarily stored
|
||||
long long int lastPos;//last byte position of trackSelect
|
||||
long long int pesTime;//the pes time of the current pes packet
|
||||
bool keyframe;//whether the current pes packet of the track has a keyframe or not
|
||||
std::string curPayload;//payload to be sent to user
|
||||
unsigned int packetCount;//number of TS packets read between between and end (to set bpos correctly)
|
||||
};
|
||||
|
||||
*/
|
||||
|
||||
/// This class contains all functions needed to implement TS Input
|
||||
class inputTS : public Input {
|
||||
public:
|
||||
inputTS(Util::Config * cfg);
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
void parsePESHeader(int tid, pesBuffer & buf);
|
||||
void parsePESPayload(int tid, pesBuffer & buf);
|
||||
void parseH264PES(int tid, pesBuffer & buf);
|
||||
void parseAACPES(int tid, pesBuffer & buf);
|
||||
pesBuffer readFullPES(int tid);
|
||||
FILE * inFile;///<The input file with ts data
|
||||
h264::NAL nal;///<Used to analyze raw h264 data
|
||||
long long int lastPos;///<last position played in file
|
||||
std::set<pesBuffer> playbackBuf;///Used for buffering playback items
|
||||
std::map<int, int> firstTimes;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputTS mistIn;
|
||||
|
53
src/input/mist_in_folder.cpp
Normal file
53
src/input/mist_in_folder.cpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#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>
|
||||
#include <mist/stream.h>
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
Util::Config conf(argv[0], PACKAGE_VERSION);
|
||||
mistIn conv(&conf);
|
||||
if (conf.parseArgs(argc, argv)) {
|
||||
if (conf.getBool("json")) {
|
||||
conv.run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string strm = conf.getString("streamname");
|
||||
if (strm.find_first_of("+ ") == std::string::npos){
|
||||
FAIL_MSG("Folder input requires a + or space in the stream name.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string folder = conf.getString("input");
|
||||
if (folder[folder.size() - 1] != '/'){
|
||||
FAIL_MSG("Input path must end in a forward slash.");
|
||||
return 1;
|
||||
}
|
||||
std::string folder_noslash = folder.substr(0, folder.size() - 1);
|
||||
struct stat fileCheck;
|
||||
if (stat(folder_noslash.c_str(), &fileCheck) != 0 || !S_ISDIR(fileCheck.st_mode)){
|
||||
FAIL_MSG("Folder input requires a folder as input.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string path = folder + strm.substr(strm.find_first_of("+ ")+1);
|
||||
if (stat(path.c_str(), &fileCheck) != 0 || S_ISDIR(fileCheck.st_mode)){
|
||||
FAIL_MSG("File not found: %s", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
Util::startInput(strm, path, false);
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue