Skip to content

Commit

Permalink
Fix, simplify, and outsource ffms2 color space override logic
Browse files Browse the repository at this point in the history
Actually support all YCbCr Matrix values instead of only a 601 matrix.
Simplify the override logic and move most of it outside of the ffms2
provider so it can be shared with other providers.
Also stop erroring out on unknown video color spaces.

This is not yet perfect (the guessing logic could be improved, invalid
matrices should probably default to TV.601, and the added functions feel
a bit out of place in video_provider_manager.cpp and are partially
redundant with ycbcr_conv.cpp) but it's a lot better and more
maintainable than before.
  • Loading branch information
arch1t3cht committed Oct 25, 2024
1 parent 6d3d5a7 commit 9adeeb1
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 63 deletions.
39 changes: 39 additions & 0 deletions src/include/aegisub/video_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,45 @@

struct VideoFrame;

/// Color matrix constants matching the constants in ffmpeg
/// (specifically libavutil's AVColorSpace) and/or H.273.
typedef enum AGI_ColorSpaces {
AGI_CS_RGB = 0,
AGI_CS_BT709 = 1,
AGI_CS_UNSPECIFIED = 2,
AGI_CS_FCC = 4,
AGI_CS_BT470BG = 5,
AGI_CS_SMPTE170M = 6,
AGI_CS_SMPTE240M = 7,
AGI_CS_YCOCG = 8,
AGI_CS_BT2020_NCL = 9,
AGI_CS_BT2020_CL = 10,
AGI_CS_SMPTE2085 = 11,
AGI_CS_CHROMATICITY_DERIVED_NCL = 12,
AGI_CS_CHROMATICITY_DERIVED_CL = 13,
AGI_CS_ICTCP = 14
} AGI_ColorSpaces;

/// Color matrix constants matching the constants in ffmpeg
/// (specifically libavutil's AVColorRange) and/or H.273.
typedef enum AGI_ColorRanges {
AGI_CR_UNSPECIFIED = 0,
AGI_CR_MPEG = 1, // 219*2^(n-8), i.e. 16-235 with 8-bit samples
AGI_CR_JPEG = 2 // 2^n-1, or "fullrange"
} AGI_ColorRanges;

namespace ColorMatrix {

std::string colormatrix_description(int CS, int CR);

std::pair<int, int> parse_colormatrix(std::string matrix);

void guess_colorspace(int &CS, int &CR, int Width, int Height);

void override_colormatrix(int &CS, int &CR, std::string matrix, int Width, int Height);

}

class VideoProvider {
public:
virtual ~VideoProvider() = default;
Expand Down
84 changes: 21 additions & 63 deletions src/video_provider_ffmpegsource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,6 @@
#include <libaegisub/make_unique.h>

namespace {
typedef enum AGI_ColorSpaces {
AGI_CS_RGB = 0,
AGI_CS_BT709 = 1,
AGI_CS_UNSPECIFIED = 2,
AGI_CS_FCC = 4,
AGI_CS_BT470BG = 5,
AGI_CS_SMPTE170M = 6,
AGI_CS_SMPTE240M = 7,
AGI_CS_YCOCG = 8,
AGI_CS_BT2020_NCL = 9,
AGI_CS_BT2020_CL = 10,
AGI_CS_SMPTE2085 = 11,
AGI_CS_CHROMATICITY_DERIVED_NCL = 12,
AGI_CS_CHROMATICITY_DERIVED_CL = 13,
AGI_CS_ICTCP = 14
} AGI_ColorSpaces;

/// @class FFmpegSourceVideoProvider
/// @brief Implements video loading through the FFMS library.
class FFmpegSourceVideoProvider final : public VideoProvider, FFmpegSourceProvider {
Expand All @@ -70,13 +53,12 @@ class FFmpegSourceVideoProvider final : public VideoProvider, FFmpegSourceProvid

int Width = -1; ///< width in pixels
int Height = -1; ///< height in pixels
int CS = -1; ///< Reported colorspace of first frame
int CR = -1; ///< Reported colorrange of first frame
int VideoCS = -1; ///< Reported colorspace of first frame (or guessed if unspecified)
int VideoCR = -1; ///< Reported colorrange of first frame (or guessed if unspecified)
double DAR; ///< display aspect ratio
std::vector<int> KeyFramesList; ///< list of keyframes
agi::vfr::Framerate Timecodes; ///< vfr object
std::string ColorSpace; ///< Colorspace name
std::string RealColorSpace; ///< Colorspace name

char FFMSErrMsg[1024]; ///< FFMS error message
FFMS_ErrorInfo ErrInfo; ///< FFMS error codes/messages
Expand All @@ -91,12 +73,14 @@ class FFmpegSourceVideoProvider final : public VideoProvider, FFmpegSourceProvid

void SetColorSpace(std::string const& matrix) override {
if (matrix == ColorSpace) return;
if (matrix == RealColorSpace)
FFMS_SetInputFormatV(VideoSource, CS, CR, FFMS_GetPixFmt(""), nullptr);
else if (matrix == "TV.601")
FFMS_SetInputFormatV(VideoSource, AGI_CS_BT470BG, CR, FFMS_GetPixFmt(""), nullptr);
else
return;

int CS = VideoCS;
int CR = VideoCR;
ColorMatrix::override_colormatrix(CS, CR, matrix, Width, Height);

if (FFMS_SetInputFormatV(VideoSource, CS, CR, FFMS_GetPixFmt(""), &ErrInfo))
throw VideoOpenError(std::string("Failed to set input format: ") + ErrInfo.Buffer);

ColorSpace = matrix;
}

Expand All @@ -114,34 +98,19 @@ class FFmpegSourceVideoProvider final : public VideoProvider, FFmpegSourceProvid

agi::vfr::Framerate GetFPS() const override { return Timecodes; }
std::string GetColorSpace() const override { return ColorSpace; }
std::string GetRealColorSpace() const override { return RealColorSpace; }
std::string GetRealColorSpace() const override {
std::string result = ColorMatrix::colormatrix_description(VideoCS, VideoCR);
if (result == "") {
return "None";
}
return result;
}
std::vector<int> GetKeyFrames() const override { return KeyFramesList; };
std::string GetDecoderName() const override { return "FFmpegSource"; }
bool WantsCaching() const override { return true; }
bool HasAudio() const override { return has_audio; }
};

std::string colormatrix_description(int cs, int cr) {
// Assuming TV for unspecified
std::string str = cr == FFMS_CR_JPEG ? "PC" : "TV";

switch (cs) {
case AGI_CS_RGB:
return "None";
case AGI_CS_BT709:
return str + ".709";
case AGI_CS_FCC:
return str + ".FCC";
case AGI_CS_BT470BG:
case AGI_CS_SMPTE170M:
return str + ".601";
case AGI_CS_SMPTE240M:
return str + ".240M";
default:
throw VideoOpenError("Unknown video color space");
}
}

FFmpegSourceVideoProvider::FFmpegSourceVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br) try
: FFmpegSourceProvider(br)
, VideoSource(nullptr, FFMS_DestroyVideoSource)
Expand Down Expand Up @@ -260,22 +229,11 @@ void FFmpegSourceVideoProvider::LoadVideo(agi::fs::path const& filename, std::st
else
DAR = double(Width) / Height;

int VideoCS = CS = TempFrame->ColorSpace;
CR = TempFrame->ColorRange;

if (CS == AGI_CS_UNSPECIFIED)
CS = Width > 1024 || Height >= 600 ? AGI_CS_BT709 : AGI_CS_BT470BG;
RealColorSpace = ColorSpace = colormatrix_description(CS, CR);
VideoCS = TempFrame->ColorSpace;
VideoCR = TempFrame->ColorRange;
ColorMatrix::guess_colorspace(VideoCS, VideoCR, Width, Height);

if (CS != AGI_CS_RGB && CS != AGI_CS_BT470BG && ColorSpace != colormatrix && colormatrix == "TV.601") {
CS = AGI_CS_BT470BG;
ColorSpace = colormatrix_description(AGI_CS_BT470BG, CR);
}

if (CS != VideoCS) {
if (FFMS_SetInputFormatV(VideoSource, CS, CR, FFMS_GetPixFmt(""), &ErrInfo))
throw VideoOpenError(std::string("Failed to set input format: ") + ErrInfo.Buffer);
}
SetColorSpace(colormatrix);

const int TargetFormat[] = { FFMS_GetPixFmt("bgra"), -1 };
if (FFMS_SetOutputFormatV2(VideoSource, TargetFormat, Width, Height, FFMS_RESIZER_BICUBIC, &ErrInfo))
Expand Down
69 changes: 69 additions & 0 deletions src/video_provider_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <libaegisub/fs.h>
#include <libaegisub/log.h>
#include <libaegisub/split.h>

#include <boost/range/iterator_range.hpp>

Expand All @@ -34,6 +35,74 @@ std::unique_ptr<VideoProvider> CreateVapourSynthVideoProvider(agi::fs::path cons

std::unique_ptr<VideoProvider> CreateCacheVideoProvider(std::unique_ptr<VideoProvider>);

namespace ColorMatrix {

std::string colormatrix_description(int cs, int cr) {
// Assuming TV for unspecified
std::string str = cr == AGI_CR_JPEG ? "PC" : "TV";

switch (cs) {
case AGI_CS_RGB:
return "None";
case AGI_CS_BT709:
return str + ".709";
case AGI_CS_FCC:
return str + ".FCC";
case AGI_CS_BT470BG:
case AGI_CS_SMPTE170M:
return str + ".601";
case AGI_CS_SMPTE240M:
return str + ".240M";
default:
return "";
}
}

std::pair<int, int> parse_colormatrix(std::string matrix) {
int cs = AGI_CS_UNSPECIFIED;
int cr = AGI_CR_UNSPECIFIED;

std::vector<std::string> parts;
agi::Split(parts, matrix, '.');
if (parts.size() == 2) {
if (parts[0] == "TV") {
cr = AGI_CR_MPEG;
} else if (parts[0] == "PC") {
cr = AGI_CR_JPEG;
}

if (parts[1] == "709") {
cs = AGI_CS_BT709;
} else if (parts[1] == "601") {
cs = AGI_CS_BT470BG;
} else if (parts[1] == "FCC") {
cs = AGI_CS_FCC;
} else if (parts[1] == "240M") {
cs = AGI_CS_SMPTE240M;
}
}

return std::make_pair(cs, cr);
}

void guess_colorspace(int &CS, int &CR, int Width, int Height) {
if (CS == AGI_CS_UNSPECIFIED)
CS = Width > 1024 || Height >= 600 ? AGI_CS_BT709 : AGI_CS_BT470BG;
if (CR != AGI_CR_MPEG)
CR = AGI_CR_MPEG;
}

void override_colormatrix(int &CS, int &CR, std::string matrix, int Width, int Height) {
guess_colorspace(CS, CR, Width, Height);
auto [oCS, oCR] = parse_colormatrix(matrix);
if (oCS != AGI_CS_UNSPECIFIED && oCR != AGI_CR_UNSPECIFIED) {
CS = oCS;
CR = oCR;
}
}

}

namespace {
struct factory {
const char *name;
Expand Down

0 comments on commit 9adeeb1

Please sign in to comment.