mistserver/src/input/input_ismv.cpp
Eli Mallon e324c2ee58 refactor: capitalize Input classes, rename srt to subrip in source as well
Co-authored-by: Thulinma <jaron@vietors.com>
2024-05-16 16:07:49 +02:00

273 lines
9.1 KiB
C++

#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/stream.h>
#include <string>
#include "input_ismv.h"
namespace Mist{
InputISMV::InputISMV(Util::Config *cfg) : Input(cfg){
capa["name"] = "ISMV";
capa["desc"] = "This input allows you to stream ISMV Video on Demand files.";
capa["source_match"] = "/*.ismv";
capa["priority"] = 9;
capa["codecs"]["video"].append("H264");
capa["codecs"]["audio"].append("AAC");
inFile = 0;
}
bool InputISMV::checkArguments(){
if (config->getString("input") == "-"){
Util::logExitReason(ER_FORMAT_SPECIFIC, "Input from stdin not yet supported");
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-"){
Util::logExitReason(ER_FORMAT_SPECIFIC, "Output to stdout not yet supported");
return false;
}
}else{
if (config->getString("output") != "-"){
Util::logExitReason(ER_FORMAT_SPECIFIC, "File output in player mode not supported");
return false;
}
}
return true;
}
bool InputISMV::preRun(){
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile){
Util::logExitReason(ER_READ_START_FAILURE, "Opening input '%s' failed", config->getString("input").c_str());
return false;
}
return true;
}
bool InputISMV::readHeader(){
if (!inFile){
Util::logExitReason(ER_READ_START_FAILURE, "Reading header for '%s' failed: Could not open input stream", config->getString("input").c_str());
return false;
}
meta.reInit(streamName);
// parse ismv header
fseek(inFile, 0, SEEK_SET);
// Skip mandatory ftyp box
MP4::skipBox(inFile);
MP4::MOOV moovBox;
moovBox.read(inFile);
parseMoov(moovBox);
std::map<size_t, uint64_t> duration;
uint64_t lastBytePos = 0;
uint64_t curBytePos = ftell(inFile);
// parse fragments form here
size_t tId;
std::vector<MP4::trunSampleInformation> trunSamples;
while (readMoofSkipMdat(tId, trunSamples) && !feof(inFile)){
if (!duration.count(tId)){duration[tId] = 0;}
for (std::vector<MP4::trunSampleInformation>::iterator it = trunSamples.begin();
it != trunSamples.end(); it++){
bool first = (it == trunSamples.begin());
int64_t offsetConv = 0;
if (M.getType(tId) == "video"){offsetConv = it->sampleOffset / 10000;}
if (first){
lastBytePos = curBytePos;
}else{
++lastBytePos;
}
meta.update(duration[tId] / 10000, offsetConv, tId, it->sampleSize, lastBytePos, first);
duration[tId] += it->sampleDuration;
}
curBytePos = ftell(inFile);
}
return true;
}
void InputISMV::getNext(size_t idx){
thisPacket.null();
if (!buffered.size()){return;}
seekPos thisPos = *buffered.begin();
buffered.erase(buffered.begin());
fseek(inFile, thisPos.position, SEEK_SET);
dataPointer.allocate(thisPos.size);
fread(dataPointer, thisPos.size, 1, inFile);
thisPacket.genericFill(thisPos.time / 10000, thisPos.offset / 10000, thisPos.trackId,
dataPointer, thisPos.size, 0, thisPos.isKeyFrame);
thisTime = thisPos.time/1000;
thisIdx = thisPos.trackId;
if (buffered.size() < 2 * (idx == INVALID_TRACK_ID ? M.getValidTracks().size() : 1)){
std::set<size_t> validTracks = M.getValidTracks();
if (idx != INVALID_TRACK_ID){
validTracks.clear();
validTracks.insert(idx);
}
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
bufferFragmentData(*it, ++lastKeyNum[*it]);
}
}
if (idx != INVALID_TRACK_ID && thisPacket.getTrackId() != M.getID(idx)){getNext(idx);}
}
void InputISMV::seek(uint64_t seekTime, size_t idx){
buffered.clear();
lastKeyNum.clear();
// Select tracks
std::set<size_t> validTracks = M.getValidTracks();
if (idx != INVALID_TRACK_ID){
validTracks.clear();
validTracks.insert(idx);
}
// For each selected track
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
DTSC::Keys keys(M.keys(*it));
uint32_t i;
for (i = keys.getFirstValid(); i < keys.getEndValid(); i++){
if (keys.getTime(i) >= seekTime){break;}
}
INFO_MSG("ISMV seek frag %zu:%" PRIu32, *it, i);
bufferFragmentData(*it, i);
lastKeyNum[*it] = i;
}
}
void InputISMV::parseMoov(MP4::MOOV &moovBox){
std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
for (std::deque<MP4::TRAK>::iterator it = trak.begin(); it != trak.end(); it++){
size_t tNumber = meta.addTrack();
meta.setID(tNumber, it->getChild<MP4::TKHD>().getTrackID());
MP4::MDIA mdia = it->getChild<MP4::MDIA>();
MP4::HDLR hdlr = mdia.getChild<MP4::HDLR>();
if (hdlr.getHandlerType() == "soun"){meta.setType(tNumber, "audio");}
if (hdlr.getHandlerType() == "vide"){meta.setType(tNumber, "video");}
MP4::STSD stsd = mdia.getChild<MP4::MINF>().getChild<MP4::STBL>().getChild<MP4::STSD>();
for (size_t i = 0; i < stsd.getEntryCount(); ++i){
if (stsd.getEntry(i).isType("mp4a") || stsd.getEntry(i).isType("enca")){
MP4::MP4A mp4aBox = (MP4::MP4A &)stsd.getEntry(i);
std::string tmpStr;
tmpStr += (char)((mp4aBox.toAACInit() & 0xFF00) >> 8);
tmpStr += (char)(mp4aBox.toAACInit() & 0x00FF);
meta.setCodec(tNumber, "AAC");
meta.setInit(tNumber, tmpStr);
meta.setChannels(tNumber, mp4aBox.getChannelCount());
meta.setSize(tNumber, mp4aBox.getSampleSize());
meta.setRate(tNumber, mp4aBox.getSampleRate());
}
if (stsd.getEntry(i).isType("avc1") || stsd.getEntry(i).isType("encv")){
MP4::AVC1 avc1Box = (MP4::AVC1 &)stsd.getEntry(i);
meta.setCodec(tNumber, "H264");
meta.setInit(tNumber, avc1Box.getCLAP().payload(), avc1Box.getCLAP().payloadSize());
meta.setHeight(tNumber, avc1Box.getHeight());
meta.setWidth(tNumber, avc1Box.getWidth());
}
}
}
}
bool InputISMV::readMoofSkipMdat(size_t &tId, std::vector<MP4::trunSampleInformation> &trunSamples){
tId = INVALID_TRACK_ID;
trunSamples.clear();
MP4::MOOF moof;
moof.read(inFile);
if (feof(inFile)){
Util::logExitReason(ER_CLEAN_EOF, "Reached EOF");
return false;
}
MP4::TRAF trafBox = moof.getChild<MP4::TRAF>();
for (size_t j = 0; j < trafBox.getContentCount(); j++){
if (trafBox.getContent(j).isType("trun")){
MP4::TRUN trunBox = (MP4::TRUN &)trafBox.getContent(j);
for (size_t i = 0; i < trunBox.getSampleInformationCount(); i++){
trunSamples.push_back(trunBox.getSampleInformation(i));
}
}
if (trafBox.getContent(j).isType("tfhd")){
tId = M.trackIDToIndex(((MP4::TFHD &)trafBox.getContent(j)).getTrackID(), getpid());
}
}
MP4::skipBox(inFile);
if (feof(inFile)){
Util::logExitReason(ER_CLEAN_EOF, "Reached EOF");
return false;
}
return true;
}
void InputISMV::bufferFragmentData(size_t trackId, uint32_t keyNum){
INFO_MSG("Bpos seek for %zu/%" PRIu32, trackId, keyNum);
if (trackId == INVALID_TRACK_ID){return;}
DTSC::Keys keys(M.keys(trackId));
INFO_MSG("Key %" PRIu32 " / %zu", keyNum, keys.getEndValid());
if (keyNum >= keys.getEndValid()){return;}
uint64_t currentPosition = keys.getBpos(keyNum);
uint64_t currentTime = keys.getTime(keyNum) * 10000;
INFO_MSG("Bpos seek to %" PRIu64, currentPosition);
fseek(inFile, currentPosition, SEEK_SET);
MP4::MOOF moofBox;
moofBox.read(inFile);
MP4::TRAF trafBox = moofBox.getChild<MP4::TRAF>();
MP4::TRUN trunBox;
MP4::UUID_SampleEncryption uuidBox;
for (unsigned int j = 0; j < trafBox.getContentCount(); j++){
if (trafBox.getContent(j).isType("trun")){trunBox = (MP4::TRUN &)trafBox.getContent(j);}
if (trafBox.getContent(j).isType("tfhd")){
if (M.getID(trackId) != ((MP4::TFHD &)trafBox.getContent(j)).getTrackID()){
Util::logExitReason(ER_FORMAT_SPECIFIC, "Trackids do not match");
return;
}
}
}
currentPosition = ftell(inFile) + 8;
for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++){
seekPos myPos;
myPos.position = currentPosition;
myPos.trackId = trackId;
myPos.time = currentTime;
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);
currentTime += trunBox.getSampleInformation(i).sampleDuration;
currentPosition += trunBox.getSampleInformation(i).sampleSize;
buffered.insert(myPos);
}
}
}// namespace Mist