forked from mia/Aegisub
Use read_file_mapping for YUV4MPEGVideoProvider
This commit is contained in:
parent
18e5144977
commit
23ff6dead1
2 changed files with 76 additions and 133 deletions
|
@ -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];
|
||||||
|
|
|
@ -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&);
|
||||||
|
|
Loading…
Reference in a new issue