mistserver/src/input/input_ogg.cpp
2021-10-19 22:29:40 +02:00

439 lines
17 KiB
C++

#include "input_ogg.h"
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/bitstream.h>
#include <mist/defines.h>
#include <mist/ogg.h>
#include <mist/opus.h>
#include <mist/stream.h>
#include <string>
///\todo Whar be Opus support?
namespace Mist{
JSON::Value segment::toJSON(OGG::oggCodec myCodec){
JSON::Value retval;
retval["time"] = time;
retval["trackid"] = tid;
std::string tmpString = "";
for (size_t i = 0; i < parts.size(); i++){tmpString += parts[i];}
retval["data"] = tmpString;
retval["bpos"] = bytepos;
if (myCodec == OGG::THEORA){
if (!theora::isHeader(tmpString.data(), tmpString.size())){
theora::header tmpHeader((char *)tmpString.data(), tmpString.size());
if (tmpHeader.getFTYPE() == 0){retval["keyframe"] = 1;}
}
}
return retval;
}
inputOGG::inputOGG(Util::Config *cfg) : Input(cfg){
capa["name"] = "OGG";
capa["desc"] = "This input allows streaming of OGG files as Video on Demand.";
capa["source_match"] = "/*.ogg";
capa["source_file"] = "$source";
capa["codecs"][0u][0u].append("theora");
capa["codecs"][0u][1u].append("vorbis");
capa["codecs"][0u][1u].append("opus");
}
bool inputOGG::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stream not yet supported" << std::endl;
return false;
}
return true;
}
bool inputOGG::preRun(){
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile){return false;}
return true;
}
///\todo check if all trackID (tid) instances are replaced with bitstream serial numbers
void inputOGG::parseBeginOfStream(OGG::Page &bosPage){
// long long int tid = snum2tid.size() + 1;
size_t tid = bosPage.getBitstreamSerialNumber();
size_t idx = M.trackIDToIndex(tid, getpid());
if (idx == INVALID_TRACK_ID){idx = meta.addTrack();}
if (memcmp(bosPage.getSegment(0) + 1, "theora", 6) == 0){
theora::header tmpHead((char *)bosPage.getSegment(0), bosPage.getSegmentLen(0));
oggTracks[idx].codec = OGG::THEORA;
oggTracks[idx].msPerFrame = (double)(tmpHead.getFRD() * 1000) / (double)tmpHead.getFRN(); // this should be: 1000/( tmpHead.getFRN()/ tmpHead.getFRD() )
oggTracks[idx].KFGShift = tmpHead.getKFGShift(); // store KFGShift for granule calculations
meta.setType(idx, "video");
meta.setCodec(idx, "theora");
meta.setID(idx, tid);
meta.setFpks(idx, (double)(tmpHead.getFRN() * 1000) / tmpHead.getFRD());
meta.setHeight(idx, tmpHead.getPICH());
meta.setWidth(idx, tmpHead.getPICW());
if (!M.getInit(idx).size()){
std::string init = " ";
Bit::htobs((char *)init.data(), bosPage.getPayloadSize());
init.append(bosPage.getSegment(0), bosPage.getSegmentLen(0));
meta.setInit(idx, init);
}
INFO_MSG("Track with id %zu is %s", tid, M.getCodec(tid).c_str());
}
if (memcmp(bosPage.getSegment(0) + 1, "vorbis", 6) == 0){
vorbis::header tmpHead((char *)bosPage.getSegment(0), bosPage.getSegmentLen(0));
oggTracks[idx].codec = OGG::VORBIS;
oggTracks[idx].msPerFrame = (double)1000.0f / tmpHead.getAudioSampleRate();
oggTracks[idx].channels = tmpHead.getAudioChannels();
oggTracks[idx].blockSize[0] = 1 << tmpHead.getBlockSize0();
oggTracks[idx].blockSize[1] = 1 << tmpHead.getBlockSize1();
DEVEL_MSG("vorbis trackID: %zu msperFrame %f ", tid, oggTracks[idx].msPerFrame);
// Abusing .contBuffer for temporarily storing the idHeader
bosPage.getSegment(0, oggTracks[idx].contBuffer);
meta.setType(idx, "audio");
meta.setCodec(idx, "vorbis");
meta.setRate(idx, tmpHead.getAudioSampleRate());
meta.setID(idx, tid);
meta.setChannels(idx, tmpHead.getAudioChannels());
INFO_MSG("Track with id %zu is %s", tid, M.getCodec(idx).c_str());
}
if (memcmp(bosPage.getSegment(0), "OpusHead", 8) == 0){
oggTracks[idx].codec = OGG::OPUS;
meta.setType(idx, "audio");
meta.setCodec(idx, "opus");
meta.setRate(idx, 48000);
meta.setID(idx, tid);
meta.setInit(idx, bosPage.getSegment(0), bosPage.getSegmentLen(0));
meta.setChannels(idx, M.getInit(idx)[9]);
INFO_MSG("Track with id %zu is %s", tid, M.getCodec(idx).c_str());
}
}
bool inputOGG::readHeader(){
meta.reInit(config->getString("streamname"), true);
OGG::Page myPage;
fseek(inFile, 0, SEEK_SET);
while (myPage.read(inFile)){// assumes all headers are sent before any data
size_t tid = myPage.getBitstreamSerialNumber();
size_t idx = M.trackIDToIndex(tid, getpid());
if (myPage.getHeaderType() & OGG::BeginOfStream){
parseBeginOfStream(myPage);
INFO_MSG("Read BeginOfStream for track %zu", tid);
continue; // Continue reading next pages
}
bool readAllHeaders = true;
for (std::map<size_t, OGG::oggTrack>::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){
if (!it->second.parsedHeaders){
readAllHeaders = false;
break;
}
}
if (readAllHeaders){break;}
// Parsing headers
if (M.getCodec(idx) == "theora"){
for (size_t i = 0; i < myPage.getAllSegments().size(); i++){
size_t len = myPage.getSegmentLen(i);
theora::header tmpHead((char *)myPage.getSegment(i), len);
if (!tmpHead.isHeader()){// not copying the header anymore, should this check isHeader?
FAIL_MSG("Theora Header read failed!");
return false;
}
switch (tmpHead.getHeaderType()){
// Case 0 is being handled by parseBeginOfStream
case 1:{
std::string init = M.getInit(idx);
init += (char)((len >> 8) & 0xFF);
init += (char)(len & 0xFF);
init.append(myPage.getSegment(i), len);
meta.setInit(idx, init);
break;
}
case 2:{
std::string init = M.getInit(idx);
init += (char)((len >> 8) & 0xFF);
init += (char)(len & 0xFF);
init.append(myPage.getSegment(i), len);
meta.setInit(idx, init);
oggTracks[idx].lastGran = 0;
oggTracks[idx].parsedHeaders = true;
break;
}
}
}
}
if (M.getCodec(idx) == "vorbis"){
for (size_t i = 0; i < myPage.getAllSegments().size(); i++){
size_t len = myPage.getSegmentLen(i);
vorbis::header tmpHead((char *)myPage.getSegment(i), len);
if (!tmpHead.isHeader()){
FAIL_MSG("Header read failed!");
return false;
}
switch (tmpHead.getHeaderType()){
// Case 1 is being handled by parseBeginOfStream
case 3:{
// we have the first header stored in contBuffer
std::string init = M.getInit(idx);
init += (char)0x02;
// ID header size
for (size_t j = 0; j < (oggTracks[idx].contBuffer.size() / 255); j++){
init += (char)0xFF;
}
init += (char)(oggTracks[idx].contBuffer.size() % 255);
// Comment header size
for (size_t j = 0; j < (len / 255); j++){init += (char)0xFF;}
init += (char)(len % 255);
init += oggTracks[idx].contBuffer;
oggTracks[idx].contBuffer.clear();
init.append(myPage.getSegment(i), len);
meta.setInit(idx, init);
break;
}
case 5:{
std::string init = M.getInit(idx);
init.append(myPage.getSegment(i), len);
meta.setInit(idx, init);
oggTracks[idx].vModes = tmpHead.readModeDeque(oggTracks[idx].channels);
oggTracks[idx].parsedHeaders = true;
break;
}
}
}
}
if (M.getCodec(idx) == "opus"){oggTracks[idx].parsedHeaders = true;}
}
for (std::map<size_t, OGG::oggTrack>::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){
fseek(inFile, 0, SEEK_SET);
INFO_MSG("Finding first data for track %zu", it->first);
position tmp = seekFirstData(it->first);
currentPositions.insert(tmp);
}
getNext();
while (thisPacket){
meta.update(thisPacket);
getNext();
}
meta.toFile(config->getString("input") + ".dtsh");
return true;
}
position inputOGG::seekFirstData(size_t idx){
fseek(inFile, 0, SEEK_SET);
position res;
res.time = 0;
res.trackID = idx;
res.segmentNo = 0;
bool readSuccesfull = true;
bool quitloop = false;
while (!quitloop){
quitloop = true;
res.bytepos = ftell(inFile);
readSuccesfull = oggTracks[idx].myPage.read(inFile);
if (!readSuccesfull){
quitloop = true; // break :(
break;
}
if (oggTracks[idx].myPage.getBitstreamSerialNumber() != M.getID(idx)){
quitloop = false;
continue;
}
if (oggTracks[idx].myPage.getHeaderType() != OGG::Plain){
quitloop = false;
continue;
}
if (oggTracks[idx].codec == OGG::OPUS){
if (std::string(oggTracks[idx].myPage.getSegment(0), 2) == "Op"){quitloop = false;}
}
if (oggTracks[idx].codec == OGG::VORBIS){
vorbis::header tmpHead((char *)oggTracks[idx].myPage.getSegment(0),
oggTracks[idx].myPage.getSegmentLen(0));
if (tmpHead.isHeader()){quitloop = false;}
}
if (oggTracks[idx].codec == OGG::THEORA){
theora::header tmpHead((char *)oggTracks[idx].myPage.getSegment(0),
oggTracks[idx].myPage.getSegmentLen(0));
if (tmpHead.isHeader()){quitloop = false;}
}
}
INFO_MSG("seek first bytepos: %" PRIu64 " tid: %zu oggTracks[idx].myPage.getHeaderType(): %d ",
res.bytepos, idx, oggTracks[idx].myPage.getHeaderType());
if (!readSuccesfull){res.trackID = 0;}
return res;
}
void inputOGG::getNext(size_t idx){
if (!currentPositions.size()){
thisPacket.null();
return;
}
bool lastCompleteSegment = false;
position curPos = *currentPositions.begin();
currentPositions.erase(currentPositions.begin());
segment thisSegment;
thisSegment.tid = curPos.trackID;
thisSegment.time = curPos.time;
thisSegment.bytepos = curPos.bytepos + curPos.segmentNo;
size_t oldSegNo = curPos.segmentNo;
fseek(inFile, curPos.bytepos, SEEK_SET);
OGG::Page curPage;
curPage.read(inFile);
thisSegment.parts.push_back(
std::string(curPage.getSegment(curPos.segmentNo), curPage.getSegmentLen(curPos.segmentNo)));
bool readFullPacket = false;
if (curPos.segmentNo == curPage.getAllSegments().size() - 1){
OGG::Page tmpPage;
uint64_t bPos;
while (!readFullPacket){
bPos = ftell(inFile); //<-- :(
if (!tmpPage.read(inFile)){break;}
if (tmpPage.getBitstreamSerialNumber() != thisSegment.tid){continue;}
curPos.bytepos = bPos;
curPos.segmentNo = 0;
if (tmpPage.getHeaderType() == OGG::Continued){
thisSegment.parts.push_back(std::string(tmpPage.getSegment(0), tmpPage.getSegmentLen(0)));
curPos.segmentNo = 1;
if (tmpPage.getAllSegments().size() == 1){continue;}
}else{
lastCompleteSegment = true; // if this segment ends on the page, use granule to sync video time
}
readFullPacket = true;
}
}else{
curPos.segmentNo++;
if ((oggTracks[curPos.trackID].codec == OGG::THEORA || oggTracks[curPos.trackID].codec == OGG::VORBIS) &&
curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull) &&
curPos.segmentNo == curPage.getAllSegments().size() -
1){// if the next segment is the last one on the page, the (theora) granule
// should be used to sync the time for the current segment
OGG::Page tmpPage;
while (tmpPage.read(inFile) && tmpPage.getBitstreamSerialNumber() != thisSegment.tid){}
if ((tmpPage.getBitstreamSerialNumber() == thisSegment.tid) && tmpPage.getHeaderType() == OGG::Continued){
lastCompleteSegment = true; // this segment should be used to sync time using granule
}
}
readFullPacket = true;
}
std::string tmpStr = thisSegment.toJSON(oggTracks[curPos.trackID].codec).toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
if (oggTracks[curPos.trackID].codec == OGG::VORBIS){
size_t blockSize = 0;
Utils::bitstreamLSBF packet;
packet.append((char *)curPage.getSegment(oldSegNo), curPage.getSegmentLen(oldSegNo));
if (!packet.get(1)){
// Read index first
size_t vModeIndex = packet.get(vorbis::ilog(oggTracks[curPos.trackID].vModes.size() - 1));
blockSize =
oggTracks[curPos.trackID].blockSize[oggTracks[curPos.trackID].vModes[vModeIndex].blockFlag]; // almost
// readable.
}else{
WARN_MSG("Packet type != 0");
}
curPos.time += oggTracks[curPos.trackID].msPerFrame * (blockSize / oggTracks[curPos.trackID].channels);
}else if (oggTracks[curPos.trackID].codec == OGG::THEORA){
if (lastCompleteSegment == true && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull)){// this segment should be used to sync time using granule
uint64_t parseGranuleUpper = curPage.getGranulePosition() >> oggTracks[curPos.trackID].KFGShift;
uint64_t parseGranuleLower(curPage.getGranulePosition() &
((1 << oggTracks[curPos.trackID].KFGShift) - 1));
thisSegment.time = oggTracks[curPos.trackID].msPerFrame * (parseGranuleUpper + parseGranuleLower - 1);
curPos.time = thisSegment.time;
std::string tmpStr = thisSegment.toJSON(oggTracks[curPos.trackID].codec).toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
// INFO_MSG("thisTime: %d", thisPacket.getTime());
}
curPos.time += oggTracks[curPos.trackID].msPerFrame;
}else if (oggTracks[curPos.trackID].codec == OGG::OPUS){
if (thisSegment.parts.size()){
curPos.time += Opus::Opus_getDuration(thisSegment.parts.front().data());
}
}
if (readFullPacket){currentPositions.insert(curPos);}
}// getnext()
uint64_t inputOGG::calcGranuleTime(size_t tid, uint64_t granule){
size_t idx = M.trackIDToIndex(tid, getpid());
switch (oggTracks[idx].codec){
case OGG::VORBIS:
return granule * oggTracks[idx].msPerFrame; //= samples * samples per second
break;
case OGG::OPUS:
return granule / 48; // always 48kHz
break;
case OGG::THEORA:{
uint64_t parseGranuleUpper = granule >> oggTracks[idx].KFGShift;
uint64_t parseGranuleLower = (granule & ((1 << oggTracks[idx].KFGShift) - 1));
return (parseGranuleUpper + parseGranuleLower) * oggTracks[idx].msPerFrame; //= frames * msPerFrame
break;
}
default: WARN_MSG("Unknown codec, can not calculate time from granule"); break;
}
return 0;
}
#ifndef _GNU_SOURCE
void *memrchr(const void *s, int c, size_t n){
const unsigned char *cp;
if (n != 0){
cp = (unsigned char *)s + n;
do{
if (*(--cp) == (unsigned char)c) return ((void *)cp);
}while (--n != 0);
}
return ((void *)0);
}
#endif
void inputOGG::seek(uint64_t seekTime, size_t idx){
currentPositions.clear();
MEDIUM_MSG("Seeking to %" PRIu64 "ms", seekTime);
// for every track
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
// find first keyframe before keyframe with ms > seektime
position tmpPos;
tmpPos.trackID = it->first;
DTSC::Keys keys(M.keys(it->first));
tmpPos.time = keys.getTime(keys.getFirstValid());
tmpPos.bytepos = keys.getBpos(keys.getFirstValid());
for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); ++i){
if (keys.getTime(i) > seekTime){
break;
}else{
tmpPos.time = keys.getTime(i);
tmpPos.bytepos = keys.getBpos(i);
}
}
INFO_MSG("Found %" PRIu64 "ms for track %zu at %" PRIu64 " bytepos %" PRIu64, seekTime,
it->first, tmpPos.time, tmpPos.bytepos);
int backChrs = std::min((uint64_t)280, tmpPos.bytepos - 1);
fseek(inFile, tmpPos.bytepos - backChrs, SEEK_SET);
char buffer[300];
fread(buffer, 300, 1, inFile);
char *loc = buffer + backChrs + 2; // start at tmpPos.bytepos+2
while (loc && !(loc[0] == 'O' && loc[1] == 'g' && loc[2] == 'g' && loc[3] == 'S')){
loc = (char *)memrchr(buffer, 'O', (loc - buffer) - 1); // seek reverse
}
if (!loc){
INFO_MSG("Unable to find a page boundary starting @ %" PRIu64 ", track %zu", tmpPos.bytepos, it->first);
continue;
}
tmpPos.segmentNo = backChrs - (loc - buffer);
tmpPos.bytepos -= tmpPos.segmentNo;
INFO_MSG("Track %zu, segment %zu found at bytepos %" PRIu64, it->first, tmpPos.segmentNo,
tmpPos.bytepos);
currentPositions.insert(tmpPos);
}
}
}// namespace Mist