Use read_file_mapping for YUV4MPEGVideoProvider

This commit is contained in:
Thomas Goyne 2014-03-21 10:26:06 -07:00
parent 18e5144977
commit 23ff6dead1
2 changed files with 76 additions and 133 deletions

View File

@ -40,6 +40,7 @@
#include "utils.h" #include "utils.h"
#include "video_frame.h" #include "video_frame.h"
#include <libaegisub/file_mapping.h>
#include <libaegisub/fs.h> #include <libaegisub/fs.h>
#include <libaegisub/log.h> #include <libaegisub/log.h>
#include <libaegisub/util.h> #include <libaegisub/util.h>
@ -47,127 +48,93 @@
#include <boost/algorithm/string/case_conv.hpp> #include <boost/algorithm/string/case_conv.hpp>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
// 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 /// @brief Constructor
/// @param filename The filename to open /// @param filename The filename to open
YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(agi::fs::path const& filename, std::string const&) { YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(agi::fs::path const& filename, std::string const&)
fps_rat.num = -1; : file(agi::util::make_unique<agi::read_file_mapping>(filename))
fps_rat.den = 1; {
CheckFileFormat();
try { uint64_t pos = 0;
#ifdef WIN32 ParseFileHeader(ReadHeader(pos));
sf = _wfopen(filename.c_str(), L"rb");
#else
sf = fopen(filename.c_str(), "rb");
#endif
if (!sf) throw agi::fs::FileNotFound(filename); if (w <= 0 || h <= 0)
throw VideoOpenError("Invalid resolution");
CheckFileFormat(); if (fps_rat.num <= 0 || fps_rat.den <= 0) {
fps_rat.num = 25;
ParseFileHeader(ReadHeader(0)); fps_rat.den = 1;
LOG_D("provider/video/yuv4mpeg") << "framerate info unavailable, assuming 25fps";
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);
} }
catch (...) { if (pixfmt == Y4M_PIXFMT_NONE)
if (sf) fclose(sf); pixfmt = Y4M_PIXFMT_420JPEG;
throw; 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() { YUV4MPEGVideoProvider::~YUV4MPEGVideoProvider() { }
fclose(sf);
}
/// @brief Checks if the file is an YUV4MPEG file or not /// @brief Checks if the file is an YUV4MPEG file or not
/// Note that it reports the error by throwing an exception, /// Note that it reports the error by throwing an exception,
/// not by returning a false value. /// not by returning a false value.
void YUV4MPEGVideoProvider::CheckFileFormat() { void YUV4MPEGVideoProvider::CheckFileFormat() {
char buf[10]; if (file->size() < 10)
if (fread(buf, 10, 1, sf) != 1) throw VideoNotSupported("CheckFileFormat: File is not a YUV4MPEG file (too small)");
throw VideoNotSupported("CheckFileFormat: Failed reading header"); if (strncmp("YUV4MPEG2 ", file->read(0, 10), 10))
if (strncmp("YUV4MPEG2 ", buf, 10))
throw VideoNotSupported("CheckFileFormat: File is not a YUV4MPEG file (bad magic)"); 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 /// @brief Read a frame or file header at a given file position
/// @param startpos The byte offset at where to start reading /// @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 /// @return A list of parameters
std::vector<std::string> YUV4MPEGVideoProvider::ReadHeader(int64_t startpos) { std::vector<std::string> YUV4MPEGVideoProvider::ReadHeader(uint64_t &pos) {
std::vector<std::string> tags; std::vector<std::string> tags;
std::string curtag; if (pos >= file->size())
int bytesread = 0; return tags;
int buf;
if (fseeko(sf, startpos, SEEK_SET)) auto len = std::min<uint64_t>(YUV4MPEG_HEADER_MAXLEN, file->size() - pos);
throw VideoOpenError("YUV4MPEG video provider: ReadHeader: failed seeking to position " + std::to_string(startpos)); auto buff = file->read(pos, len);
// read header until terminating newline (0x0A) is found // read header until terminating newline (0x0A) is found
while ((buf = fgetc(sf)) != 0x0A) { const char *curtag = buff;
if (ferror(sf)) auto end = buff + len;
throw VideoOpenError("ReadHeader: Failed to read from file"); for (; buff < end && *buff != 0x0A; ++buff, ++pos) {
if (feof(sf)) if (*buff == 0)
throw VideoOpenError("ReadHeader: Reached eof while reading header");
// some basic low-effort sanity checking
if (buf == 0x00)
throw VideoOpenError("ReadHeader: Malformed header (unexpected NUL)"); 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 (*buff == 0x20) {
if (buf == 0x20 && !curtag.empty()) { if (curtag != buff)
tags.push_back(curtag); tags.emplace_back(curtag, buff);
curtag.clear(); 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 // if only one tag with no trailing space was found (possible in the
// FRAME header case), make sure we get it // FRAME header case), make sure we get it
if (!curtag.empty()) if (curtag != buff)
tags.push_back(curtag); tags.emplace_back(curtag, buff);
pos += 1; // Move past newline
return tags; 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, /// 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 /// and creates a seek table that lists the byte positions of all frames so seeking
/// can easily be done. /// can easily be done.
int YUV4MPEGVideoProvider::IndexFile() { int YUV4MPEGVideoProvider::IndexFile(uint64_t pos) {
int framecount = 0; int framecount = 0;
// the ParseFileHeader() call in LoadVideo() will already have read // the ParseFileHeader() call in LoadVideo() will already have read
// the file header for us and set the seek position correctly // the file header for us and set the seek position correctly
while (true) { 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 // continue reading headers until no more are found
std::vector<std::string> tags = ReadHeader(curpos); std::vector<std::string> tags = ReadHeader(pos);
curpos = ftello(sf);
if (curpos < 0)
throw VideoOpenError("IndexFile: ftello failed");
if (tags.empty()) if (tags.empty())
break; // no more headers break; // no more headers
@ -314,10 +273,8 @@ int YUV4MPEGVideoProvider::IndexFile() {
if (flags == Y4M_FFLAG_NONE) { if (flags == Y4M_FFLAG_NONE) {
framecount++; framecount++;
seek_table.push_back(curpos); seek_table.push_back(pos);
// seek to next frame header start position pos += frame_sz;
if (fseeko(sf, frame_sz, SEEK_CUR))
throw VideoOpenError("IndexFile: failed seeking to position " + std::to_string(curpos + frame_sz));
} }
else { else {
/// @todo implement rff flags etc /// @todo implement rff flags etc
@ -350,25 +307,9 @@ std::shared_ptr<VideoFrame> YUV4MPEGVideoProvider::GetFrame(int n) {
throw "YUV4MPEG video provider: GetFrame: Unsupported source colorspace"; throw "YUV4MPEG video provider: GetFrame: Unsupported source colorspace";
} }
std::vector<uint8_t> planes[3]; auto src_y = reinterpret_cast<const unsigned char *>(file->read(seek_table[n], luma_sz + chroma_sz * 2));
planes[0].resize(luma_sz); auto src_u = src_y + luma_sz;
planes[1].resize(chroma_sz); auto src_v = src_u + 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];
std::vector<unsigned char> data; std::vector<unsigned char> data;
data.resize(w * h * 4); data.resize(w * h * 4);
unsigned char *dst = &data[0]; unsigned char *dst = &data[0];

View File

@ -34,9 +34,11 @@
#include "include/aegisub/video_provider.h" #include "include/aegisub/video_provider.h"
#include <cstdio> #include <memory>
#include <vector> #include <vector>
namespace agi { class read_file_mapping; }
/// the maximum allowed header length, in bytes /// the maximum allowed header length, in bytes
#define YUV4MPEG_HEADER_MAXLEN 128 #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) Y4M_FFLAG_C_UNKNOWN = 0x0800 /// unknown (only allowed for non-4:2:0 sampling)
}; };
FILE *sf = nullptr; /// source file std::unique_ptr<agi::read_file_mapping> file;
bool inited = false; /// initialization state bool inited = false; /// initialization state
int w = 0, h = 0; /// frame width/height 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_PixelFormat pixfmt = Y4M_PIXFMT_NONE; /// colorspace/pixel format
Y4M_InterlacingMode imode = Y4M_ILACE_NOTSET; /// interlacing mode (for the entire stream) Y4M_InterlacingMode imode = Y4M_ILACE_NOTSET; /// interlacing mode (for the entire stream)
struct { struct {
int num; /// numerator int num = -1; /// numerator
int den; /// denominator int den = 1; /// denominator
} fps_rat; /// framerate } fps_rat; /// framerate
agi::vfr::Framerate fps; agi::vfr::Framerate fps;
/// a list of byte positions detailing where in the file /// a list of byte positions detailing where in the file
/// each frame header can be found /// each frame header can be found
std::vector<int64_t> seek_table; std::vector<uint64_t> seek_table;
void CheckFileFormat(); void CheckFileFormat();
void ParseFileHeader(const std::vector<std::string>& tags); void ParseFileHeader(const std::vector<std::string>& tags);
Y4M_FrameFlags ParseFrameHeader(const std::vector<std::string>& tags); Y4M_FrameFlags ParseFrameHeader(const std::vector<std::string>& tags);
std::vector<std::string> ReadHeader(int64_t startpos); std::vector<std::string> ReadHeader(uint64_t &startpos);
int IndexFile(); int IndexFile(uint64_t pos);
public: public:
YUV4MPEGVideoProvider(agi::fs::path const& filename, std::string const&); YUV4MPEGVideoProvider(agi::fs::path const& filename, std::string const&);