2009-07-19 06:13:46 +02:00
|
|
|
// Copyright (c) 2009, Karl Blomster
|
|
|
|
// All rights reserved.
|
|
|
|
//
|
|
|
|
// Redistribution and use in source and binary forms, with or without
|
|
|
|
// modification, are permitted provided that the following conditions are met:
|
|
|
|
//
|
|
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
|
|
// this list of conditions and the following disclaimer.
|
|
|
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
|
|
// and/or other materials provided with the distribution.
|
|
|
|
// * Neither the name of the Aegisub Group nor the names of its contributors
|
|
|
|
// may be used to endorse or promote products derived from this software
|
|
|
|
// without specific prior written permission.
|
|
|
|
//
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
//
|
2009-07-29 07:43:02 +02:00
|
|
|
// Aegisub Project http://www.aegisub.org/
|
|
|
|
|
|
|
|
/// @file video_provider_yuv4mpeg.cpp
|
|
|
|
/// @brief Video provider reading YUV4MPEG files directly without depending on external libraries
|
|
|
|
/// @ingroup video_input
|
|
|
|
///
|
2009-07-19 06:13:46 +02:00
|
|
|
|
2009-10-09 18:34:38 +02:00
|
|
|
#include "config.h"
|
|
|
|
|
2012-01-18 21:08:42 +01:00
|
|
|
#include "video_provider_yuv4mpeg.h"
|
|
|
|
|
2010-08-02 08:32:01 +02:00
|
|
|
#include "compat.h"
|
2010-12-31 22:03:11 +01:00
|
|
|
#include "utils.h"
|
2012-01-18 21:08:42 +01:00
|
|
|
#include "video_frame.h"
|
2009-07-19 06:13:46 +02:00
|
|
|
|
2013-01-04 16:01:50 +01:00
|
|
|
#include <libaegisub/fs.h>
|
|
|
|
#include <libaegisub/log.h>
|
|
|
|
#include <libaegisub/util.h>
|
|
|
|
|
|
|
|
#include <boost/algorithm/string/case_conv.hpp>
|
|
|
|
#include <boost/filesystem/path.hpp>
|
|
|
|
|
2009-07-19 06:13:46 +02:00
|
|
|
// 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
|
|
|
|
|
2009-08-07 21:59:37 +02:00
|
|
|
/// @brief Constructor
|
|
|
|
/// @param filename The filename to open
|
2013-12-12 01:29:48 +01:00
|
|
|
YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(agi::fs::path const& filename, std::string const&) {
|
2010-08-02 08:32:01 +02:00
|
|
|
fps_rat.num = -1;
|
2009-07-19 06:13:46 +02:00
|
|
|
fps_rat.den = 1;
|
|
|
|
|
|
|
|
try {
|
|
|
|
#ifdef WIN32
|
2013-01-04 16:01:50 +01:00
|
|
|
sf = _wfopen(filename.c_str(), L"rb");
|
2009-07-19 06:13:46 +02:00
|
|
|
#else
|
2013-01-04 16:01:50 +01:00
|
|
|
sf = fopen(filename.c_str(), "rb");
|
2009-07-19 06:13:46 +02:00
|
|
|
#endif
|
|
|
|
|
2013-01-04 16:01:50 +01:00
|
|
|
if (!sf) throw agi::fs::FileNotFound(filename);
|
2009-07-19 06:13:46 +02:00
|
|
|
|
2010-08-02 08:32:01 +02:00
|
|
|
CheckFileFormat();
|
2009-07-19 06:13:46 +02:00
|
|
|
|
2012-10-15 06:37:14 +02:00
|
|
|
ParseFileHeader(ReadHeader(0));
|
2010-08-02 08:32:01 +02:00
|
|
|
|
|
|
|
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) {
|
2009-07-19 06:13:46 +02:00
|
|
|
case Y4M_PIXFMT_420JPEG:
|
|
|
|
case Y4M_PIXFMT_420MPEG2:
|
|
|
|
case Y4M_PIXFMT_420PALDV:
|
2009-09-19 23:34:40 +02:00
|
|
|
chroma_sz = (w * h) >> 2; break;
|
2009-07-19 06:13:46 +02:00
|
|
|
case Y4M_PIXFMT_422:
|
2009-09-19 23:34:40 +02:00
|
|
|
chroma_sz = (w * h) >> 1; break;
|
2010-08-02 08:32:01 +02:00
|
|
|
/// @todo add support for more pixel formats
|
2009-07-19 06:13:46 +02:00
|
|
|
default:
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("Unsupported pixel format");
|
|
|
|
}
|
|
|
|
frame_sz = luma_sz + chroma_sz*2;
|
2009-07-19 06:13:46 +02:00
|
|
|
|
2010-08-02 08:32:01 +02:00
|
|
|
num_frames = IndexFile();
|
|
|
|
if (num_frames <= 0 || seek_table.empty())
|
|
|
|
throw VideoOpenError("Unable to determine file length");
|
2009-07-19 06:13:46 +02:00
|
|
|
|
2010-08-02 08:32:01 +02:00
|
|
|
fseeko(sf, 0, SEEK_SET);
|
|
|
|
}
|
|
|
|
catch (...) {
|
|
|
|
if (sf) fclose(sf);
|
|
|
|
throw;
|
|
|
|
}
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
|
|
|
|
2010-08-02 08:32:01 +02:00
|
|
|
YUV4MPEGVideoProvider::~YUV4MPEGVideoProvider() {
|
2013-01-04 16:01:50 +01:00
|
|
|
fclose(sf);
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
|
|
|
|
2009-08-07 21:59:37 +02:00
|
|
|
/// @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.
|
2009-07-19 06:13:46 +02:00
|
|
|
void YUV4MPEGVideoProvider::CheckFileFormat() {
|
|
|
|
char buf[10];
|
|
|
|
if (fread(buf, 10, 1, sf) != 1)
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoNotSupported("CheckFileFormat: Failed reading header");
|
2009-07-19 06:13:46 +02:00
|
|
|
if (strncmp("YUV4MPEG2 ", buf, 10))
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoNotSupported("CheckFileFormat: File is not a YUV4MPEG file (bad magic)");
|
2009-07-19 06:13:46 +02:00
|
|
|
|
|
|
|
fseeko(sf, 0, SEEK_SET);
|
|
|
|
}
|
|
|
|
|
2009-08-07 21:59:37 +02:00
|
|
|
/// @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
|
2013-01-04 16:01:50 +01:00
|
|
|
std::vector<std::string> YUV4MPEGVideoProvider::ReadHeader(int64_t startpos) {
|
|
|
|
std::vector<std::string> tags;
|
|
|
|
std::string curtag;
|
2010-08-02 08:32:01 +02:00
|
|
|
int bytesread = 0;
|
2009-07-19 06:13:46 +02:00
|
|
|
int buf;
|
|
|
|
|
|
|
|
if (fseeko(sf, startpos, SEEK_SET))
|
2013-01-04 16:01:50 +01:00
|
|
|
throw VideoOpenError("YUV4MPEG video provider: ReadHeader: failed seeking to position " + std::to_string(startpos));
|
2009-07-19 06:13:46 +02:00
|
|
|
|
|
|
|
// read header until terminating newline (0x0A) is found
|
|
|
|
while ((buf = fgetc(sf)) != 0x0A) {
|
|
|
|
if (ferror(sf))
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ReadHeader: Failed to read from file");
|
2012-10-15 06:37:14 +02:00
|
|
|
if (feof(sf))
|
|
|
|
throw VideoOpenError("ReadHeader: Reached eof while reading header");
|
2009-07-19 06:13:46 +02:00
|
|
|
|
|
|
|
// some basic low-effort sanity checking
|
|
|
|
if (buf == 0x00)
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ReadHeader: Malformed header (unexpected NUL)");
|
2009-07-19 06:13:46 +02:00
|
|
|
if (++bytesread >= YUV4MPEG_HEADER_MAXLEN)
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ReadHeader: Malformed header (no terminating newline found)");
|
2009-07-19 06:13:46 +02:00
|
|
|
|
|
|
|
// found a new tag
|
2013-01-04 16:01:50 +01:00
|
|
|
if (buf == 0x20 && !curtag.empty()) {
|
2009-07-19 06:13:46 +02:00
|
|
|
tags.push_back(curtag);
|
2013-01-04 16:01:50 +01:00
|
|
|
curtag.clear();
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
|
|
|
else
|
2013-01-04 16:01:50 +01:00
|
|
|
curtag.push_back(buf);
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
|
|
|
// if only one tag with no trailing space was found (possible in the
|
|
|
|
// FRAME header case), make sure we get it
|
2013-01-04 16:01:50 +01:00
|
|
|
if (!curtag.empty())
|
2009-07-19 06:13:46 +02:00
|
|
|
tags.push_back(curtag);
|
|
|
|
|
|
|
|
return tags;
|
|
|
|
}
|
|
|
|
|
2009-08-07 21:59:37 +02:00
|
|
|
/// @brief Parses a list of parameters and sets reader state accordingly
|
|
|
|
/// @param tags The list of parameters to parse
|
2013-01-04 16:01:50 +01:00
|
|
|
void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector<std::string>& tags) {
|
2009-07-19 06:13:46 +02:00
|
|
|
if (tags.size() <= 1)
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: contentless header");
|
2013-01-04 16:01:50 +01:00
|
|
|
if (tags.front() != "YUV4MPEG2")
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: malformed header (bad magic)");
|
2009-07-19 06:13:46 +02:00
|
|
|
|
|
|
|
// temporary stuff
|
|
|
|
int t_w = -1;
|
|
|
|
int t_h = -1;
|
|
|
|
int t_fps_num = -1;
|
|
|
|
int t_fps_den = -1;
|
|
|
|
Y4M_InterlacingMode t_imode = Y4M_ILACE_NOTSET;
|
2009-07-20 00:51:19 +02:00
|
|
|
Y4M_PixelFormat t_pixfmt = Y4M_PIXFMT_NONE;
|
2009-07-19 06:13:46 +02:00
|
|
|
|
|
|
|
for (unsigned i = 1; i < tags.size(); i++) {
|
2013-01-04 16:01:50 +01:00
|
|
|
char type = tags[i][0];
|
|
|
|
std::string tag = tags[i].substr(1);
|
2009-07-19 06:13:46 +02:00
|
|
|
|
2013-01-04 16:01:50 +01:00
|
|
|
if (type == 'W') {
|
|
|
|
if (!agi::util::try_parse(tag, &t_w))
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: invalid width");
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
2013-01-04 16:01:50 +01:00
|
|
|
else if (type == 'H') {
|
|
|
|
if (!agi::util::try_parse(tag, &t_h))
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: invalid height");
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
2013-01-04 16:01:50 +01:00
|
|
|
else if (type == 'F') {
|
|
|
|
size_t pos = tag.find(':');
|
|
|
|
if (pos == tag.npos)
|
|
|
|
throw VideoOpenError("ParseFileHeader: invalid framerate");
|
|
|
|
|
|
|
|
if (!agi::util::try_parse(tag.substr(0, pos), &t_fps_num) ||
|
|
|
|
!agi::util::try_parse(tag.substr(pos + 1), &t_fps_den))
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: invalid framerate");
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
2013-01-04 16:01:50 +01:00
|
|
|
else if (type == 'C') {
|
2009-07-19 06:13:46 +02:00
|
|
|
// technically this should probably be case sensitive,
|
|
|
|
// but being liberal in what you accept doesn't hurt
|
2013-01-04 16:01:50 +01:00
|
|
|
boost::to_lower(tag);
|
2010-12-31 22:03:11 +01:00
|
|
|
if (tag == "420") t_pixfmt = Y4M_PIXFMT_420JPEG; // is this really correct?
|
|
|
|
else if (tag == "420jpeg") t_pixfmt = Y4M_PIXFMT_420JPEG;
|
|
|
|
else if (tag == "420mpeg2") t_pixfmt = Y4M_PIXFMT_420MPEG2;
|
|
|
|
else if (tag == "420paldv") t_pixfmt = Y4M_PIXFMT_420PALDV;
|
|
|
|
else if (tag == "411") t_pixfmt = Y4M_PIXFMT_411;
|
|
|
|
else if (tag == "422") t_pixfmt = Y4M_PIXFMT_422;
|
|
|
|
else if (tag == "444") t_pixfmt = Y4M_PIXFMT_444;
|
|
|
|
else if (tag == "444alpha") t_pixfmt = Y4M_PIXFMT_444ALPHA;
|
|
|
|
else if (tag == "mono") t_pixfmt = Y4M_PIXFMT_MONO;
|
2009-07-19 06:13:46 +02:00
|
|
|
else
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: invalid or unknown colorspace");
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
2013-01-04 16:01:50 +01:00
|
|
|
else if (type == 'I') {
|
|
|
|
boost::to_lower(tag);
|
2010-12-31 22:03:11 +01:00
|
|
|
if (tag == "p") t_imode = Y4M_ILACE_PROGRESSIVE;
|
|
|
|
else if (tag == "t") t_imode = Y4M_ILACE_TFF;
|
|
|
|
else if (tag == "b") t_imode = Y4M_ILACE_BFF;
|
|
|
|
else if (tag == "m") t_imode = Y4M_ILACE_MIXED;
|
|
|
|
else if (tag == "?") t_imode = Y4M_ILACE_UNKNOWN;
|
2009-07-19 06:13:46 +02:00
|
|
|
else
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: invalid or unknown interlacing mode");
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
|
|
|
else
|
2013-01-04 16:01:50 +01:00
|
|
|
LOG_D("provider/video/yuv4mpeg") << "Unparsed tag: " << tags[i];
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// The point of all this is to allow multiple YUV4MPEG2 headers in a single file
|
|
|
|
// (can happen if you concat several files) as long as they have identical
|
|
|
|
// header flags. The spec doesn't explicitly say you have to allow this,
|
|
|
|
// but the "reference implementation" (mjpegtools) does, so I'm doing it too.
|
|
|
|
if (inited) {
|
|
|
|
if (t_w > 0 && t_w != w)
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: illegal width change");
|
2009-07-19 06:13:46 +02:00
|
|
|
if (t_h > 0 && t_h != h)
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: illegal height change");
|
2009-07-19 06:13:46 +02:00
|
|
|
if ((t_fps_num > 0 && t_fps_den > 0) && (t_fps_num != fps_rat.num || t_fps_den != fps_rat.den))
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: illegal framerate change");
|
2009-07-19 06:13:46 +02:00
|
|
|
if (t_pixfmt != Y4M_PIXFMT_NONE && t_pixfmt != pixfmt)
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: illegal colorspace change");
|
2009-07-19 06:13:46 +02:00
|
|
|
if (t_imode != Y4M_ILACE_NOTSET && t_imode != imode)
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFileHeader: illegal interlacing mode change");
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
w = t_w;
|
|
|
|
h = t_h;
|
|
|
|
fps_rat.num = t_fps_num;
|
|
|
|
fps_rat.den = t_fps_den;
|
2009-08-07 21:59:37 +02:00
|
|
|
pixfmt = t_pixfmt != Y4M_PIXFMT_NONE ? t_pixfmt : Y4M_PIXFMT_420JPEG;
|
|
|
|
imode = t_imode != Y4M_ILACE_NOTSET ? t_imode : Y4M_ILACE_UNKNOWN;
|
2010-07-08 06:29:04 +02:00
|
|
|
fps = double(fps_rat.num) / fps_rat.den;
|
2009-07-19 06:13:46 +02:00
|
|
|
inited = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-07 21:59:37 +02:00
|
|
|
/// @brief Parses a frame header
|
|
|
|
/// @param tags The list of parameters to parse
|
|
|
|
/// @return The flags set, as a binary mask
|
|
|
|
/// This function is currently unimplemented (it will always return Y4M_FFLAG_NONE).
|
2013-01-04 16:01:50 +01:00
|
|
|
YUV4MPEGVideoProvider::Y4M_FrameFlags YUV4MPEGVideoProvider::ParseFrameHeader(const std::vector<std::string>& tags) {
|
|
|
|
if (tags.front() != "FRAME")
|
2010-08-02 08:32:01 +02:00
|
|
|
throw VideoOpenError("ParseFrameHeader: malformed frame header (bad magic)");
|
2009-07-19 06:13:46 +02:00
|
|
|
|
2009-08-07 21:59:37 +02:00
|
|
|
/// @todo implement parsing of frame flags
|
2009-07-19 06:13:46 +02:00
|
|
|
|
|
|
|
return Y4M_FFLAG_NONE;
|
|
|
|
}
|
|
|
|
|
2009-08-07 21:59:37 +02:00
|
|
|
/// @brief Indexes the file
|
|
|
|
/// @return The number of frames found in the file
|
|
|
|
/// 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.
|
2009-07-19 06:13:46 +02:00
|
|
|
int YUV4MPEGVideoProvider::IndexFile() {
|
|
|
|
int framecount = 0;
|
|
|
|
|
|
|
|
// the ParseFileHeader() call in LoadVideo() will already have read
|
2012-03-25 06:05:06 +02:00
|
|
|
// the file header for us and set the seek position correctly
|
2009-07-19 06:13:46 +02:00
|
|
|
while (true) {
|
2012-09-14 16:55:01 +02:00
|
|
|
int64_t curpos = ftello(sf); // update position
|
2012-10-15 06:37:14 +02:00
|
|
|
if (curpos < 0)
|
|
|
|
throw VideoOpenError("IndexFile: ftello failed");
|
|
|
|
|
2009-07-19 06:13:46 +02:00
|
|
|
// continue reading headers until no more are found
|
2013-01-04 16:01:50 +01:00
|
|
|
std::vector<std::string> tags = ReadHeader(curpos);
|
2009-07-19 06:13:46 +02:00
|
|
|
curpos = ftello(sf);
|
2012-10-15 06:37:14 +02:00
|
|
|
if (curpos < 0)
|
|
|
|
throw VideoOpenError("IndexFile: ftello failed");
|
2009-07-19 06:13:46 +02:00
|
|
|
|
|
|
|
if (tags.empty())
|
|
|
|
break; // no more headers
|
|
|
|
|
|
|
|
Y4M_FrameFlags flags = Y4M_FFLAG_NOTSET;
|
2013-01-04 16:01:50 +01:00
|
|
|
if (tags.front() == "YUV4MPEG2") {
|
2009-07-19 06:13:46 +02:00
|
|
|
ParseFileHeader(tags);
|
|
|
|
continue;
|
|
|
|
}
|
2013-01-04 16:01:50 +01:00
|
|
|
else if (tags.front() == "FRAME")
|
2009-07-19 06:13:46 +02:00
|
|
|
flags = ParseFrameHeader(tags);
|
|
|
|
|
|
|
|
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))
|
2013-01-04 16:01:50 +01:00
|
|
|
throw VideoOpenError("IndexFile: failed seeking to position " + std::to_string(curpos + frame_sz));
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
|
|
|
else {
|
2009-08-07 21:59:37 +02:00
|
|
|
/// @todo implement rff flags etc
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return framecount;
|
|
|
|
}
|
|
|
|
|
2010-12-31 22:03:11 +01:00
|
|
|
// http://bob.allegronetwork.com/prog/tricks.html#clamp
|
|
|
|
static FORCEINLINE int clamp(int x) {
|
|
|
|
x &= (~x) >> 31;
|
|
|
|
x -= 255;
|
|
|
|
x &= x >> 31;
|
|
|
|
x += 255;
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
2013-07-01 05:15:43 +02:00
|
|
|
std::shared_ptr<VideoFrame> YUV4MPEGVideoProvider::GetFrame(int n) {
|
|
|
|
n = mid(0, n, num_frames - 1);
|
2010-12-31 22:03:11 +01:00
|
|
|
|
|
|
|
int uv_width = w / 2;
|
2009-07-19 06:13:46 +02:00
|
|
|
switch (pixfmt) {
|
|
|
|
case Y4M_PIXFMT_420JPEG:
|
|
|
|
case Y4M_PIXFMT_420MPEG2:
|
|
|
|
case Y4M_PIXFMT_420PALDV:
|
2010-12-31 22:03:11 +01:00
|
|
|
break;
|
2009-08-07 21:59:37 +02:00
|
|
|
/// @todo add support for more pixel formats
|
2009-07-19 06:13:46 +02:00
|
|
|
default:
|
2010-12-31 22:03:11 +01:00
|
|
|
throw "YUV4MPEG video provider: GetFrame: Unsupported source colorspace";
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
2010-12-31 22:03:11 +01:00
|
|
|
|
|
|
|
std::vector<uint8_t> planes[3];
|
|
|
|
planes[0].resize(luma_sz);
|
|
|
|
planes[1].resize(chroma_sz);
|
|
|
|
planes[2].resize(chroma_sz);
|
2009-07-19 06:13:46 +02:00
|
|
|
|
|
|
|
fseeko(sf, seek_table[n], SEEK_SET);
|
|
|
|
size_t ret;
|
2010-12-31 22:03:11 +01:00
|
|
|
ret = fread(&planes[0][0], luma_sz, 1, sf);
|
2009-07-19 06:13:46 +02:00
|
|
|
if (ret != 1 || feof(sf) || ferror(sf))
|
2010-12-31 22:03:11 +01:00
|
|
|
throw "YUV4MPEG video provider: GetFrame: failed to read luma plane";
|
2009-07-19 06:13:46 +02:00
|
|
|
for (int i = 1; i <= 2; i++) {
|
2010-12-31 22:03:11 +01:00
|
|
|
ret = fread(&planes[i][0], chroma_sz, 1, sf);
|
2009-07-19 06:13:46 +02:00
|
|
|
if (ret != 1 || feof(sf) || ferror(sf))
|
2010-12-31 22:03:11 +01:00
|
|
|
throw "YUV4MPEG video provider: GetFrame: failed to read chroma planes";
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|
|
|
|
|
2010-12-31 22:03:11 +01:00
|
|
|
const unsigned char *src_y = &planes[0][0];
|
|
|
|
const unsigned char *src_u = &planes[1][0];
|
|
|
|
const unsigned char *src_v = &planes[2][0];
|
2013-07-01 05:15:43 +02:00
|
|
|
std::vector<unsigned char> data;
|
|
|
|
data.resize(w * h * 4);
|
|
|
|
unsigned char *dst = &data[0];
|
2010-12-31 22:03:11 +01:00
|
|
|
|
|
|
|
for (int py = 0; py < h; ++py) {
|
|
|
|
for (int px = 0; px < w / 2; ++px) {
|
|
|
|
const int u = *src_u++ - 128;
|
|
|
|
const int v = *src_v++ - 128;
|
|
|
|
for (unsigned int i = 0; i < 2; ++i) {
|
|
|
|
const int y = (*src_y++ - 16) * 298;
|
|
|
|
|
|
|
|
*dst++ = clamp((y + 516 * u + 128) >> 8); // Blue
|
|
|
|
*dst++ = clamp((y - 100 * u - 208 * v + 128) >> 8); // Green
|
|
|
|
*dst++ = clamp((y + 409 * v + 128) >> 8); // Red
|
|
|
|
*dst++ = 0; // Alpha
|
|
|
|
}
|
|
|
|
}
|
2009-07-19 06:13:46 +02:00
|
|
|
|
2010-12-31 22:03:11 +01:00
|
|
|
// Roll back u/v on even lines
|
|
|
|
if (!(py & 1)) {
|
|
|
|
src_u -= uv_width;
|
|
|
|
src_v -= uv_width;
|
|
|
|
}
|
|
|
|
}
|
2009-07-19 06:13:46 +02:00
|
|
|
|
2013-07-01 05:15:43 +02:00
|
|
|
return std::make_shared<VideoFrame>(data.data(), w, h, w * 4, false);
|
2009-07-19 06:13:46 +02:00
|
|
|
}
|