From 23ff6dead12eaf61b909936a73c0b739a15af51c Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 21 Mar 2014 10:26:06 -0700 Subject: [PATCH] Use read_file_mapping for YUV4MPEGVideoProvider --- src/video_provider_yuv4mpeg.cpp | 191 +++++++++++--------------------- src/video_provider_yuv4mpeg.h | 18 +-- 2 files changed, 76 insertions(+), 133 deletions(-) diff --git a/src/video_provider_yuv4mpeg.cpp b/src/video_provider_yuv4mpeg.cpp index 8ffbd8c29..32c97e339 100644 --- a/src/video_provider_yuv4mpeg.cpp +++ b/src/video_provider_yuv4mpeg.cpp @@ -40,6 +40,7 @@ #include "utils.h" #include "video_frame.h" +#include #include #include #include @@ -47,127 +48,93 @@ #include #include -// All of this cstdio bogus is because of one reason and one reason only: -// MICROSOFT'S IMPLEMENTATION OF STD::FSTREAM DOES NOT SUPPORT FILES LARGER THAN 2 GB. -// (yes, really) -// With cstdio it's at least possible to work around the problem... -#ifdef _MSC_VER -#define fseeko _fseeki64 -#define ftello _ftelli64 -#endif - /// @brief Constructor /// @param filename The filename to open -YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(agi::fs::path const& filename, std::string const&) { - fps_rat.num = -1; - fps_rat.den = 1; +YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(agi::fs::path const& filename, std::string const&) +: file(agi::util::make_unique(filename)) +{ + CheckFileFormat(); - try { -#ifdef WIN32 - sf = _wfopen(filename.c_str(), L"rb"); -#else - sf = fopen(filename.c_str(), "rb"); -#endif + uint64_t pos = 0; + ParseFileHeader(ReadHeader(pos)); - if (!sf) throw agi::fs::FileNotFound(filename); - - CheckFileFormat(); - - ParseFileHeader(ReadHeader(0)); - - if (w <= 0 || h <= 0) - throw VideoOpenError("Invalid resolution"); - if (fps_rat.num <= 0 || fps_rat.den <= 0) { - fps_rat.num = 25; - fps_rat.den = 1; - LOG_D("provider/video/yuv4mpeg") << "framerate info unavailable, assuming 25fps"; - } - if (pixfmt == Y4M_PIXFMT_NONE) - pixfmt = Y4M_PIXFMT_420JPEG; - if (imode == Y4M_ILACE_NOTSET) - imode = Y4M_ILACE_UNKNOWN; - - luma_sz = w * h; - switch (pixfmt) { - case Y4M_PIXFMT_420JPEG: - case Y4M_PIXFMT_420MPEG2: - case Y4M_PIXFMT_420PALDV: - chroma_sz = (w * h) >> 2; break; - case Y4M_PIXFMT_422: - chroma_sz = (w * h) >> 1; break; - /// @todo add support for more pixel formats - default: - throw VideoOpenError("Unsupported pixel format"); - } - frame_sz = luma_sz + chroma_sz*2; - - num_frames = IndexFile(); - if (num_frames <= 0 || seek_table.empty()) - throw VideoOpenError("Unable to determine file length"); - - fseeko(sf, 0, SEEK_SET); + if (w <= 0 || h <= 0) + throw VideoOpenError("Invalid resolution"); + if (fps_rat.num <= 0 || fps_rat.den <= 0) { + fps_rat.num = 25; + fps_rat.den = 1; + LOG_D("provider/video/yuv4mpeg") << "framerate info unavailable, assuming 25fps"; } - catch (...) { - if (sf) fclose(sf); - throw; + if (pixfmt == Y4M_PIXFMT_NONE) + pixfmt = Y4M_PIXFMT_420JPEG; + if (imode == Y4M_ILACE_NOTSET) + imode = Y4M_ILACE_UNKNOWN; + + luma_sz = w * h; + switch (pixfmt) { + case Y4M_PIXFMT_420JPEG: + case Y4M_PIXFMT_420MPEG2: + case Y4M_PIXFMT_420PALDV: + chroma_sz = (w * h) >> 2; break; + case Y4M_PIXFMT_422: + chroma_sz = (w * h) >> 1; break; + /// @todo add support for more pixel formats + default: + throw VideoOpenError("Unsupported pixel format"); } + frame_sz = luma_sz + chroma_sz*2; + + num_frames = IndexFile(pos); + if (num_frames <= 0 || seek_table.empty()) + throw VideoOpenError("Unable to determine file length"); } -YUV4MPEGVideoProvider::~YUV4MPEGVideoProvider() { - fclose(sf); -} +YUV4MPEGVideoProvider::~YUV4MPEGVideoProvider() { } /// @brief Checks if the file is an YUV4MPEG file or not /// Note that it reports the error by throwing an exception, /// not by returning a false value. void YUV4MPEGVideoProvider::CheckFileFormat() { - char buf[10]; - if (fread(buf, 10, 1, sf) != 1) - throw VideoNotSupported("CheckFileFormat: Failed reading header"); - if (strncmp("YUV4MPEG2 ", buf, 10)) + if (file->size() < 10) + throw VideoNotSupported("CheckFileFormat: File is not a YUV4MPEG file (too small)"); + if (strncmp("YUV4MPEG2 ", file->read(0, 10), 10)) throw VideoNotSupported("CheckFileFormat: File is not a YUV4MPEG file (bad magic)"); - - fseeko(sf, 0, SEEK_SET); } /// @brief Read a frame or file header at a given file position /// @param startpos The byte offset at where to start reading -/// @param reset_pos If true, the function will reset the file position to what it was before the function call before returning /// @return A list of parameters -std::vector YUV4MPEGVideoProvider::ReadHeader(int64_t startpos) { +std::vector YUV4MPEGVideoProvider::ReadHeader(uint64_t &pos) { std::vector tags; - std::string curtag; - int bytesread = 0; - int buf; + if (pos >= file->size()) + return tags; - if (fseeko(sf, startpos, SEEK_SET)) - throw VideoOpenError("YUV4MPEG video provider: ReadHeader: failed seeking to position " + std::to_string(startpos)); + auto len = std::min(YUV4MPEG_HEADER_MAXLEN, file->size() - pos); + auto buff = file->read(pos, len); // read header until terminating newline (0x0A) is found - while ((buf = fgetc(sf)) != 0x0A) { - if (ferror(sf)) - throw VideoOpenError("ReadHeader: Failed to read from file"); - if (feof(sf)) - throw VideoOpenError("ReadHeader: Reached eof while reading header"); - - // some basic low-effort sanity checking - if (buf == 0x00) + const char *curtag = buff; + auto end = buff + len; + for (; buff < end && *buff != 0x0A; ++buff, ++pos) { + if (*buff == 0) throw VideoOpenError("ReadHeader: Malformed header (unexpected NUL)"); - if (++bytesread >= YUV4MPEG_HEADER_MAXLEN) - throw VideoOpenError("ReadHeader: Malformed header (no terminating newline found)"); - // found a new tag - if (buf == 0x20 && !curtag.empty()) { - tags.push_back(curtag); - curtag.clear(); + if (*buff == 0x20) { + if (curtag != buff) + tags.emplace_back(curtag, buff); + curtag = buff + 1; } - else - curtag.push_back(buf); } + + if (buff == end) + throw VideoOpenError("ReadHeader: Malformed header (no terminating newline found)"); + // if only one tag with no trailing space was found (possible in the // FRAME header case), make sure we get it - if (!curtag.empty()) - tags.push_back(curtag); + if (curtag != buff) + tags.emplace_back(curtag, buff); + + pos += 1; // Move past newline return tags; } @@ -285,22 +252,14 @@ YUV4MPEGVideoProvider::Y4M_FrameFlags YUV4MPEGVideoProvider::ParseFrameHeader(co /// This function goes through the file, finds and parses all file and frame headers, /// and creates a seek table that lists the byte positions of all frames so seeking /// can easily be done. -int YUV4MPEGVideoProvider::IndexFile() { +int YUV4MPEGVideoProvider::IndexFile(uint64_t pos) { int framecount = 0; // the ParseFileHeader() call in LoadVideo() will already have read // the file header for us and set the seek position correctly while (true) { - int64_t curpos = ftello(sf); // update position - if (curpos < 0) - throw VideoOpenError("IndexFile: ftello failed"); - // continue reading headers until no more are found - std::vector tags = ReadHeader(curpos); - curpos = ftello(sf); - if (curpos < 0) - throw VideoOpenError("IndexFile: ftello failed"); - + std::vector tags = ReadHeader(pos); if (tags.empty()) break; // no more headers @@ -314,10 +273,8 @@ int YUV4MPEGVideoProvider::IndexFile() { if (flags == Y4M_FFLAG_NONE) { framecount++; - seek_table.push_back(curpos); - // seek to next frame header start position - if (fseeko(sf, frame_sz, SEEK_CUR)) - throw VideoOpenError("IndexFile: failed seeking to position " + std::to_string(curpos + frame_sz)); + seek_table.push_back(pos); + pos += frame_sz; } else { /// @todo implement rff flags etc @@ -350,25 +307,9 @@ std::shared_ptr YUV4MPEGVideoProvider::GetFrame(int n) { throw "YUV4MPEG video provider: GetFrame: Unsupported source colorspace"; } - std::vector planes[3]; - planes[0].resize(luma_sz); - planes[1].resize(chroma_sz); - planes[2].resize(chroma_sz); - - fseeko(sf, seek_table[n], SEEK_SET); - size_t ret; - ret = fread(&planes[0][0], luma_sz, 1, sf); - if (ret != 1 || feof(sf) || ferror(sf)) - throw "YUV4MPEG video provider: GetFrame: failed to read luma plane"; - for (int i = 1; i <= 2; i++) { - ret = fread(&planes[i][0], chroma_sz, 1, sf); - if (ret != 1 || feof(sf) || ferror(sf)) - throw "YUV4MPEG video provider: GetFrame: failed to read chroma planes"; - } - - const unsigned char *src_y = &planes[0][0]; - const unsigned char *src_u = &planes[1][0]; - const unsigned char *src_v = &planes[2][0]; + auto src_y = reinterpret_cast(file->read(seek_table[n], luma_sz + chroma_sz * 2)); + auto src_u = src_y + luma_sz; + auto src_v = src_u + chroma_sz; std::vector data; data.resize(w * h * 4); unsigned char *dst = &data[0]; diff --git a/src/video_provider_yuv4mpeg.h b/src/video_provider_yuv4mpeg.h index 3c8c9e250..ac57502cd 100644 --- a/src/video_provider_yuv4mpeg.h +++ b/src/video_provider_yuv4mpeg.h @@ -34,9 +34,11 @@ #include "include/aegisub/video_provider.h" -#include +#include #include +namespace agi { class read_file_mapping; } + /// the maximum allowed header length, in bytes #define YUV4MPEG_HEADER_MAXLEN 128 @@ -100,7 +102,7 @@ class YUV4MPEGVideoProvider final : public VideoProvider { Y4M_FFLAG_C_UNKNOWN = 0x0800 /// unknown (only allowed for non-4:2:0 sampling) }; - FILE *sf = nullptr; /// source file + std::unique_ptr file; bool inited = false; /// initialization state int w = 0, h = 0; /// frame width/height @@ -112,21 +114,21 @@ class YUV4MPEGVideoProvider final : public VideoProvider { Y4M_PixelFormat pixfmt = Y4M_PIXFMT_NONE; /// colorspace/pixel format Y4M_InterlacingMode imode = Y4M_ILACE_NOTSET; /// interlacing mode (for the entire stream) struct { - int num; /// numerator - int den; /// denominator - } fps_rat; /// framerate + int num = -1; /// numerator + int den = 1; /// denominator + } fps_rat; /// framerate agi::vfr::Framerate fps; /// a list of byte positions detailing where in the file /// each frame header can be found - std::vector seek_table; + std::vector seek_table; void CheckFileFormat(); void ParseFileHeader(const std::vector& tags); Y4M_FrameFlags ParseFrameHeader(const std::vector& tags); - std::vector ReadHeader(int64_t startpos); - int IndexFile(); + std::vector ReadHeader(uint64_t &startpos); + int IndexFile(uint64_t pos); public: YUV4MPEGVideoProvider(agi::fs::path const& filename, std::string const&);