#include "input_ogg.h" #include #include #include #include #include #include #include #include #include #include #include #include ///\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::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::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::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