source/render/VideoFile.h (131 lines of code) (raw):

/** * Copyright 2004-present Facebook. All Rights Reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #include <deque> #include <fstream> #include <iterator> #include <mutex> #include <boost/filesystem.hpp> #include <folly/Format.h> #include "source/gpu/GlUtil.h" #include "source/mesh_stream/StripedFile.h" #include "source/render/RigScene.h" namespace fb360_dep { // a video file is a striped file with a catalog describing the layout struct VideoFile { StripedFile stripedFile; folly::dynamic catalog; std::vector<std::string> frames; int current = 0; VideoFile(const VideoFile& videoFile) = delete; VideoFile& operator=(const VideoFile& videoFile) = delete; VideoFile(const std::string& catalogName, const std::vector<std::string>& diskNames) : stripedFile(diskNames), catalog(parseCatalog(catalogName)) { // find and sort all the frame names for (const auto& key : catalog["frames"].keys()) { frames.push_back(key.getString()); } CHECK(frames.size()) << "no frames in catalog " << catalogName; sort(frames.begin(), frames.end()); LOG(INFO) << folly::sformat("{} frames found", frames.size()); } int getFront() const { return static_cast<int>((current - pending.size() + frames.size()) % frames.size()); } void readBegin(const RigScene& scene, bool cull = false) { const folly::dynamic& frame = catalog["frames"][frames[current]]; pending.emplace_back(); std::vector<Loader>& loaders = pending.back(); // kick off a loader for every camera in scene.rig loaders.reserve(scene.rig.size()); for (int i = 0; i < int(scene.rig.size()); ++i) { const Camera& camera = scene.rig[i]; const folly::dynamic& layout = frame[camera.id]; if (cull && i < int(scene.culled.size()) && scene.culled[i]) { loaders.push_back({nullptr, 0, 0, layout, nullptr}); loaders.back().read = nullptr; } else { const uint64_t size = layout["size"].getInt(); // when reading, size must be page aligned const uint64_t sizeAligned = align(size, kPageSize); // allocate, map and align a buffer const uint64_t sizeAlloc = sizeAligned + kPageSize - 1; const GLuint buffer = createBuffer(kBufferType, (uint8_t*)nullptr, sizeAlloc); uint8_t* const p = static_cast<uint8_t*>(glMapBuffer(kBufferType, GL_WRITE_ONLY)); CHECK(p); uint8_t* const pAligned = align(p, kPageSize); glBindBuffer(kBufferType, 0); // start the read const uint64_t offset = layout["offset"].getInt(); StripedFile::PendingRead* const read = stripedFile.readBegin(pAligned, offset, sizeAligned); // stash the loader information for this camera const uint64_t offsetUnaligned = offset - (pAligned - p); loaders.push_back({read, buffer, offsetUnaligned, layout, p}); } } // increment frame counter current = (current + 1) % frames.size(); } // blocking function: wait for disk read void readWait(const RigScene& scene, int index = 0) { CHECK(index < int(pending.size())); const std::vector<Loader>& loaders = pending[index]; for (const Loader& loader : loaders) { if (loader.read != nullptr) { stripedFile.readEnd(loader.read); } } } // unmap gl buffer void readUnmap(const RigScene& scene, int index = 0) { CHECK(index < int(pending.size())); const std::vector<Loader>& loaders = pending[index]; for (const Loader& loader : loaders) { if (loader.read != nullptr) { glBindBuffer(kBufferType, loader.buffer); glUnmapBuffer(kBufferType); glBindBuffer(kBufferType, 0); } } } // create subframes from read data std::vector<RigScene::Subframe> readFrame(const RigScene& scene) { CHECK(!pending.empty()); const std::vector<Loader>& loaders = pending.front(); CHECK_EQ(loaders.size(), scene.rig.size()); std::vector<RigScene::Subframe> result; // create a subframe for every camera in scene.rig result.reserve(scene.rig.size()); for (int i = 0; i < int(scene.rig.size()); ++i) { const Loader& loader = loaders[i]; if (loader.read == nullptr) { result.emplace_back(); } else { // create the frame result.emplace_back( scene.createSubframe(scene.rig[i], loader.buffer, loader.offset, loader.layout)); } } pending.pop_front(); return result; } // blocking function std::vector<RigScene::Subframe> readEnd(const RigScene& scene) { readWait(scene); readUnmap(scene); return readFrame(scene); } private: static folly::dynamic parseCatalog(const std::string& fileName) { CHECK(boost::filesystem::exists(boost::filesystem::path(fileName))); std::ifstream file(fileName, std::ios::binary); folly::dynamic catalog = folly::parseJson(std::string( (std::istreambuf_iterator<char>(file)), // most vexing parse (std::istreambuf_iterator<char>()))); // Update legacy files without (both) metadata and frames entries if (catalog.find("metadata") == catalog.items().end()) { LOG(WARNING) << "No metadata found"; CHECK(catalog.find("frames") == catalog.items().end()) << "Malformed catalog file"; folly::dynamic oldCatalog = catalog; catalog = folly::dynamic::object; catalog["frames"] = oldCatalog; // Assume native endianness catalog["metadata"] = folly::dynamic::object; catalog["metadata"]["isLittleEndian"] = folly::kIsLittleEndian; } CHECK_EQ(folly::kIsLittleEndian, catalog["metadata"]["isLittleEndian"].getBool()) << "Endianness mismatch between video file and native platform"; return catalog; } GLenum kBufferType = GL_TEXTURE_BUFFER; // unimportant, pick unused type struct Loader { StripedFile::PendingRead* read; GLuint buffer; uint64_t offset; // offset of the unaligned buffer const folly::dynamic layout; // HACK FOR WINDOWS: should be reference uint8_t* p; // for debugging }; std::deque<std::vector<Loader>> pending; }; } // namespace fb360_dep