474 lines
18 KiB
C++
474 lines
18 KiB
C++
#include <iostream>
|
|
#include <fstream>
|
|
#include <cstring>
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <mist/stream.h>
|
|
#include <mist/ogg.h>
|
|
#include <mist/defines.h>
|
|
#include <mist/bitstream.h>
|
|
#include <mist/opus.h>
|
|
#include "input_ogg.h"
|
|
|
|
///\todo Whar be Opus support?
|
|
|
|
namespace Mist {
|
|
|
|
JSON::Value segment::toJSON(OGG::oggCodec myCodec){
|
|
JSON::Value retval;
|
|
retval["time"] = (long long int)time;
|
|
retval["trackid"] = (long long int)tid;
|
|
std::string tmpString = "";
|
|
for (unsigned int i = 0; i < parts.size(); i++){
|
|
tmpString += parts[i];
|
|
}
|
|
retval["data"] = tmpString;
|
|
// INFO_MSG("Setting bpos for packet on track %llu, time %llu, to %llu", tid, time, bytepos);
|
|
retval["bpos"] = (long long int)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"] = 1LL;
|
|
}
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
unsigned long oggTrack::getBlockSize(unsigned int vModeIndex){ //WTF!!?
|
|
return blockSize[vModes[vModeIndex].blockFlag];
|
|
|
|
}
|
|
*/
|
|
inputOGG::inputOGG(Util::Config * cfg) : Input(cfg){
|
|
capa["name"] = "OGG";
|
|
capa["desc"] = "Enables OGG input";
|
|
capa["source_match"] = "/*.ogg";
|
|
capa["codecs"][0u][0u].append("theora");
|
|
capa["codecs"][0u][1u].append("vorbis");
|
|
capa["codecs"][0u][1u].append("opus");
|
|
}
|
|
|
|
bool inputOGG::setup(){
|
|
if (config->getString("input") == "-"){
|
|
std::cerr << "Input from stream not yet supported" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
//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;
|
|
unsigned int tid = bosPage.getBitstreamSerialNumber();
|
|
if (memcmp(bosPage.getSegment(0) + 1, "theora", 6) == 0){
|
|
theora::header tmpHead((char*)bosPage.getSegment(0), bosPage.getSegmentLen(0));
|
|
oggTracks[tid].codec = OGG::THEORA;
|
|
oggTracks[tid].msPerFrame = (double)(tmpHead.getFRD() * 1000) / (double)tmpHead.getFRN(); //this should be: 1000/( tmpHead.getFRN()/ tmpHead.getFRD() )
|
|
oggTracks[tid].KFGShift = tmpHead.getKFGShift(); //store KFGShift for granule calculations
|
|
myMeta.tracks[tid].type = "video";
|
|
myMeta.tracks[tid].codec = "theora";
|
|
myMeta.tracks[tid].trackID = tid;
|
|
myMeta.tracks[tid].fpks = (tmpHead.getFRN() * 1000) / tmpHead.getFRD();
|
|
myMeta.tracks[tid].height = tmpHead.getPICH();
|
|
myMeta.tracks[tid].width = tmpHead.getPICW();
|
|
if (!myMeta.tracks[tid].init.size()){
|
|
myMeta.tracks[tid].init = (char)((bosPage.getPayloadSize() >> 8) & 0xFF);
|
|
myMeta.tracks[tid].init += (char)(bosPage.getPayloadSize() & 0xFF);
|
|
myMeta.tracks[tid].init.append(bosPage.getSegment(0), bosPage.getSegmentLen(0));
|
|
}
|
|
INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str());
|
|
}
|
|
if (memcmp(bosPage.getSegment(0) + 1, "vorbis", 6) == 0){
|
|
vorbis::header tmpHead((char*)bosPage.getSegment(0), bosPage.getSegmentLen(0));
|
|
|
|
oggTracks[tid].codec = OGG::VORBIS;
|
|
oggTracks[tid].msPerFrame = (double)1000.0f / tmpHead.getAudioSampleRate();
|
|
DEBUG_MSG(DLVL_DEVEL, "vorbis trackID: %d msperFrame %f ", tid, oggTracks[tid].msPerFrame);
|
|
oggTracks[tid].channels = tmpHead.getAudioChannels();
|
|
oggTracks[tid].blockSize[0] = 1 << tmpHead.getBlockSize0();
|
|
oggTracks[tid].blockSize[1] = 1 << tmpHead.getBlockSize1();
|
|
//Abusing .contBuffer for temporarily storing the idHeader
|
|
bosPage.getSegment(0, oggTracks[tid].contBuffer);
|
|
|
|
myMeta.tracks[tid].type = "audio";
|
|
myMeta.tracks[tid].codec = "vorbis";
|
|
myMeta.tracks[tid].rate = tmpHead.getAudioSampleRate();
|
|
myMeta.tracks[tid].trackID = tid;
|
|
myMeta.tracks[tid].channels = tmpHead.getAudioChannels();
|
|
INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str());
|
|
}
|
|
if (memcmp(bosPage.getSegment(0), "OpusHead", 8) == 0){
|
|
oggTracks[tid].codec = OGG::OPUS;
|
|
myMeta.tracks[tid].type = "audio";
|
|
myMeta.tracks[tid].codec = "opus";
|
|
myMeta.tracks[tid].rate = 48000;
|
|
myMeta.tracks[tid].trackID = tid;
|
|
myMeta.tracks[tid].init.assign(bosPage.getSegment(0), bosPage.getSegmentLen(0));
|
|
myMeta.tracks[tid].channels = myMeta.tracks[tid].init[9];
|
|
INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str());
|
|
}
|
|
}
|
|
|
|
bool inputOGG::readHeader(){
|
|
OGG::Page myPage;
|
|
fseek(inFile, 0, SEEK_SET);
|
|
while (myPage.read(inFile)){ //assumes all headers are sent before any data
|
|
unsigned int tid = myPage.getBitstreamSerialNumber();
|
|
if (myPage.getHeaderType() & OGG::BeginOfStream){
|
|
parseBeginOfStream(myPage);
|
|
INFO_MSG("Read BeginOfStream for track %d", tid);
|
|
continue; //Continue reading next pages
|
|
}
|
|
|
|
bool readAllHeaders = true;
|
|
for (std::map<long unsigned int, OGG::oggTrack>::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){
|
|
if (!it->second.parsedHeaders){
|
|
readAllHeaders = false;
|
|
break;
|
|
}
|
|
}
|
|
if (readAllHeaders){
|
|
break;
|
|
}
|
|
|
|
// INFO_MSG("tid: %d",tid);
|
|
|
|
//Parsing headers
|
|
if (myMeta.tracks[tid].codec == "theora"){
|
|
for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){
|
|
unsigned long len = myPage.getSegmentLen(i);
|
|
theora::header tmpHead((char*)myPage.getSegment(i),len);
|
|
if (!tmpHead.isHeader()){ //not copying the header anymore, should this check isHeader?
|
|
DEBUG_MSG(DLVL_FAIL, "Theora Header read failed!");
|
|
return false;
|
|
}
|
|
switch (tmpHead.getHeaderType()){
|
|
//Case 0 is being handled by parseBeginOfStream
|
|
case 1: {
|
|
myMeta.tracks[tid].init += (char)((len >> 8) & 0xFF);
|
|
myMeta.tracks[tid].init += (char)(len & 0xFF);
|
|
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
|
|
break;
|
|
}
|
|
case 2: {
|
|
myMeta.tracks[tid].init += (char)((len >> 8) & 0xFF);
|
|
myMeta.tracks[tid].init += (char)(len & 0xFF);
|
|
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
|
|
oggTracks[tid].lastGran = 0;
|
|
oggTracks[tid].parsedHeaders = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (myMeta.tracks[tid].codec == "vorbis"){
|
|
for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){
|
|
unsigned long len = myPage.getSegmentLen(i);
|
|
vorbis::header tmpHead((char*)myPage.getSegment(i), len);
|
|
if (!tmpHead.isHeader()){
|
|
DEBUG_MSG(DLVL_FAIL, "Header read failed!");
|
|
return false;
|
|
}
|
|
switch (tmpHead.getHeaderType()){
|
|
//Case 1 is being handled by parseBeginOfStream
|
|
case 3: {
|
|
//we have the first header stored in contBuffer
|
|
myMeta.tracks[tid].init += (char)0x02;
|
|
//ID header size
|
|
for (unsigned int j = 0; j < (oggTracks[tid].contBuffer.size() / 255); j++){
|
|
myMeta.tracks[tid].init += (char)0xFF;
|
|
}
|
|
myMeta.tracks[tid].init += (char)(oggTracks[tid].contBuffer.size() % 255);
|
|
//Comment header size
|
|
for (unsigned int j = 0; j < (len / 255); j++){
|
|
myMeta.tracks[tid].init += (char)0xFF;
|
|
}
|
|
myMeta.tracks[tid].init += (char)(len % 255);
|
|
myMeta.tracks[tid].init += oggTracks[tid].contBuffer;
|
|
oggTracks[tid].contBuffer.clear();
|
|
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
|
|
break;
|
|
}
|
|
case 5: {
|
|
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
|
|
oggTracks[tid].vModes = tmpHead.readModeDeque(oggTracks[tid].channels);
|
|
oggTracks[tid].parsedHeaders = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (myMeta.tracks[tid].codec == "opus"){
|
|
oggTracks[tid].parsedHeaders = true;
|
|
}
|
|
}
|
|
|
|
for (std::map<long unsigned int, OGG::oggTrack>::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){
|
|
fseek(inFile, 0, SEEK_SET);
|
|
INFO_MSG("Finding first data for track %lu", it->first);
|
|
position tmp = seekFirstData(it->first);
|
|
if (tmp.trackID){
|
|
currentPositions.insert(tmp);
|
|
} else {
|
|
INFO_MSG("missing track: %lu", it->first);
|
|
}
|
|
}
|
|
getNext();
|
|
while (thisPacket){
|
|
myMeta.update(thisPacket);
|
|
getNext();
|
|
}
|
|
|
|
myMeta.toFile(config->getString("input") + ".dtsh");
|
|
return true;
|
|
}
|
|
|
|
position inputOGG::seekFirstData(long long unsigned int tid){
|
|
fseek(inFile, 0, SEEK_SET);
|
|
position res;
|
|
res.time = 0;
|
|
res.trackID = tid;
|
|
res.segmentNo = 0;
|
|
bool readSuccesfull = true;
|
|
bool quitloop = false;
|
|
while (!quitloop){
|
|
quitloop = true;
|
|
res.bytepos = ftell(inFile);
|
|
readSuccesfull = oggTracks[tid].myPage.read(inFile);
|
|
if (!readSuccesfull){
|
|
quitloop = true; //break :(
|
|
break;
|
|
}
|
|
if (oggTracks[tid].myPage.getBitstreamSerialNumber() != tid){
|
|
quitloop = false;
|
|
continue;
|
|
}
|
|
if (oggTracks[tid].myPage.getHeaderType() != OGG::Plain){
|
|
quitloop = false;
|
|
continue;
|
|
}
|
|
if (oggTracks[tid].codec == OGG::OPUS){
|
|
if (std::string(oggTracks[tid].myPage.getSegment(0), 2) == "Op"){
|
|
quitloop = false;
|
|
}
|
|
}
|
|
if (oggTracks[tid].codec == OGG::VORBIS){
|
|
vorbis::header tmpHead((char*)oggTracks[tid].myPage.getSegment(0), oggTracks[tid].myPage.getSegmentLen(0));
|
|
if (tmpHead.isHeader()){
|
|
quitloop = false;
|
|
}
|
|
}
|
|
if (oggTracks[tid].codec == OGG::THEORA){
|
|
theora::header tmpHead((char*)oggTracks[tid].myPage.getSegment(0), oggTracks[tid].myPage.getSegmentLen(0));
|
|
if (tmpHead.isHeader()){
|
|
quitloop = false;
|
|
}
|
|
}
|
|
}// while ( oggTracks[tid].myPage.getHeaderType() != OGG::Plain && readSuccesfull && oggTracks[tid].myPage.getBitstreamSerialNumber() != tid);
|
|
INFO_MSG("seek first bytepos: %llu tid: %llu oggTracks[tid].myPage.getHeaderType(): %d ", res.bytepos, tid, oggTracks[tid].myPage.getHeaderType());
|
|
if (!readSuccesfull){
|
|
res.trackID = 0;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void inputOGG::getNext(bool smart){
|
|
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;
|
|
unsigned int 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;
|
|
unsigned int 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[thisSegment.tid].codec == OGG::THEORA && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull) && curPos.segmentNo == curPage.getAllSegments().size() - 1){ //if the next segment is the last one on the page, the (theora) granule should be used to sync the time for the current segment
|
|
if ((oggTracks[thisSegment.tid].codec == OGG::THEORA ||oggTracks[thisSegment.tid].codec == OGG::VORBIS)&& 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[thisSegment.tid].codec).toNetPacked();
|
|
thisPacket.reInit(tmpStr.data(), tmpStr.size());
|
|
|
|
if (oggTracks[thisSegment.tid].codec == OGG::VORBIS){
|
|
unsigned long blockSize = 0;
|
|
Utils::bitstreamLSBF packet;
|
|
packet.append((char*)curPage.getSegment(oldSegNo), curPage.getSegmentLen(oldSegNo));
|
|
if (!packet.get(1)){
|
|
//Read index first
|
|
unsigned long vModeIndex = packet.get(vorbis::ilog(oggTracks[thisSegment.tid].vModes.size() - 1));
|
|
blockSize= oggTracks[thisSegment.tid].blockSize[oggTracks[thisSegment.tid].vModes[vModeIndex].blockFlag]; //almost readable.
|
|
} else {
|
|
DEBUG_MSG(DLVL_WARN, "Packet type != 0");
|
|
}
|
|
curPos.time += oggTracks[thisSegment.tid].msPerFrame * (blockSize / oggTracks[thisSegment.tid].channels);
|
|
} else if (oggTracks[thisSegment.tid].codec == OGG::THEORA){
|
|
if (lastCompleteSegment == true && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull)){ //this segment should be used to sync time using granule
|
|
long long unsigned int parseGranuleUpper = curPage.getGranulePosition() >> oggTracks[thisSegment.tid].KFGShift ;
|
|
long long unsigned int parseGranuleLower(curPage.getGranulePosition() & ((1 << oggTracks[thisSegment.tid].KFGShift) - 1));
|
|
thisSegment.time = oggTracks[thisSegment.tid].msPerFrame * (parseGranuleUpper + parseGranuleLower - 1);
|
|
curPos.time = thisSegment.time;
|
|
std::string tmpStr = thisSegment.toJSON(oggTracks[thisSegment.tid].codec).toNetPacked();
|
|
thisPacket.reInit(tmpStr.data(), tmpStr.size());
|
|
// INFO_MSG("thisTime: %d", thisPacket.getTime());
|
|
}
|
|
curPos.time += oggTracks[thisSegment.tid].msPerFrame;
|
|
} else if (oggTracks[thisSegment.tid].codec == OGG::OPUS){
|
|
if (thisSegment.parts.size()){
|
|
curPos.time += Opus::Opus_getDuration(thisSegment.parts.front().data());
|
|
}
|
|
}
|
|
if (readFullPacket){
|
|
currentPositions.insert(curPos);
|
|
}
|
|
}//getnext()
|
|
|
|
long long unsigned int inputOGG::calcGranuleTime(unsigned long tid, long long unsigned int granule){
|
|
switch (oggTracks[tid].codec){
|
|
case OGG::VORBIS:
|
|
return granule * oggTracks[tid].msPerFrame ; //= samples * samples per second
|
|
break;
|
|
case OGG::OPUS:
|
|
return granule / 48; //always 48kHz
|
|
break;
|
|
case OGG::THEORA:{
|
|
long long unsigned int parseGranuleUpper = granule >> oggTracks[tid].KFGShift ;
|
|
long long unsigned int parseGranuleLower = (granule & ((1 << oggTracks[tid].KFGShift) - 1));
|
|
return (parseGranuleUpper + parseGranuleLower) * oggTracks[tid].msPerFrame ; //= frames * msPerFrame
|
|
break;
|
|
}
|
|
default:
|
|
DEBUG_MSG(DLVL_WARN, "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(int seekTime){
|
|
currentPositions.clear();
|
|
DEBUG_MSG(DLVL_MEDIUM, "Seeking to %dms", seekTime);
|
|
|
|
//for every track
|
|
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
|
//find first keyframe before keyframe with ms > seektime
|
|
position tmpPos;
|
|
tmpPos.trackID = *it;
|
|
tmpPos.time = myMeta.tracks[*it].keys.begin()->getTime();
|
|
tmpPos.bytepos = myMeta.tracks[*it].keys.begin()->getBpos();
|
|
for (std::deque<DTSC::Key>::iterator ot = myMeta.tracks[*it].keys.begin(); ot != myMeta.tracks[*it].keys.end(); ot++){
|
|
if (ot->getTime() > seekTime){
|
|
break;
|
|
} else {
|
|
tmpPos.time = ot->getTime();
|
|
tmpPos.bytepos = ot->getBpos();
|
|
}
|
|
}
|
|
INFO_MSG("Found %dms for track %lu at %llu bytepos %llu", seekTime, *it, tmpPos.time, tmpPos.bytepos);
|
|
int backChrs=std::min(280ull, 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 @ %llu, track %lu", tmpPos.bytepos, *it);
|
|
continue;
|
|
}
|
|
tmpPos.segmentNo = backChrs - (loc - buffer);
|
|
tmpPos.bytepos -= tmpPos.segmentNo;
|
|
INFO_MSG("Track %lu, segment %llu found at bytepos %llu", *it, tmpPos.segmentNo, tmpPos.bytepos);
|
|
|
|
currentPositions.insert(tmpPos);
|
|
}
|
|
}
|
|
|
|
void inputOGG::trackSelect(std::string trackSpec){
|
|
selectedTracks.clear();
|
|
size_t index;
|
|
while (trackSpec != ""){
|
|
index = trackSpec.find(' ');
|
|
selectedTracks.insert(atoll(trackSpec.substr(0, index).c_str()));
|
|
if (index != std::string::npos){
|
|
trackSpec.erase(0, index + 1);
|
|
} else {
|
|
trackSpec = "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|