Skip to content

Commit aa31926

Browse files
authored
fix(ffmpeg): 10 bit video has wrong green channel (#4935)
Due to apparent limitations or bugs in swscale, there is a problem reading the 16 bit green channel that is not 4 byte aligned like red and blue. Reading each channel into its own plane solves the issue. This also addresses an existing comment about 32 bit float being read as 16 bit. This needed the same mechanism of reading separate planes, so the same code is reused to implement that. Resolves #4901 I added a 10 bit test file encoded with VP9 and Matroska, same as the existing 8 bit test file to maximize compatibility. I verified this now converts correctly to a HDR PNG file. Signed-off-by: Brecht Van Lommel <[email protected]>
1 parent 0f72f73 commit aa31926

File tree

5 files changed

+120
-25
lines changed

5 files changed

+120
-25
lines changed

src/ffmpeg.imageio/ffmpeginput.cpp

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,10 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec)
398398
case AV_PIX_FMT_GBRAP12LE:
399399
case AV_PIX_FMT_P016LE:
400400
case AV_PIX_FMT_P016BE:
401-
datatype = TypeUInt16;
402-
m_dst_pix_format = AV_PIX_FMT_RGB48;
401+
datatype = TypeUInt16;
402+
// Must use planar format because swscale does not handle
403+
// interleaved correctly for 16 bit.
404+
m_dst_pix_format = AV_PIX_FMT_GBRP16;
403405
break;
404406
// Grayscale 8 bit
405407
case AV_PIX_FMT_GRAY8:
@@ -456,29 +458,27 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec)
456458
case AV_PIX_FMT_YUVA444P16BE:
457459
case AV_PIX_FMT_YUVA444P16LE:
458460
case AV_PIX_FMT_GBRAP16:
459-
nchannels = 4;
460-
datatype = TypeUInt16;
461-
m_dst_pix_format = AV_PIX_FMT_RGBA64;
461+
nchannels = 4;
462+
datatype = TypeUInt16;
463+
// Must use planar format because swscale does not handle
464+
// interleaved correctly for 16 bit.
465+
m_dst_pix_format = AV_PIX_FMT_GBRAP16;
462466
break;
463467
// RGB float
464468
case AV_PIX_FMT_GBRPF32BE:
465469
case AV_PIX_FMT_GBRPF32LE:
466-
nchannels = 3;
467-
datatype = TypeFloat;
468-
m_dst_pix_format = AV_PIX_FMT_RGB48; // ? AV_PIX_FMT_GBRPF32
469-
// FIXME: They don't have a type for RGB float, only GBR float.
470-
// Yuck. Punt for now and save as uint16 RGB. If people care, we
471-
// can return and ask for GBR float and swap order.
470+
nchannels = 3;
471+
datatype = TypeFloat;
472+
// Must use planar format as there is no interleaved 32 bit float.
473+
m_dst_pix_format = AV_PIX_FMT_GBRPF32;
472474
break;
473475
// RGBA float
474476
case AV_PIX_FMT_GBRAPF32BE:
475477
case AV_PIX_FMT_GBRAPF32LE:
476-
nchannels = 4;
477-
datatype = TypeFloat;
478-
m_dst_pix_format = AV_PIX_FMT_RGBA64; // ? AV_PIX_FMT_GBRAPF32
479-
// FIXME: They don't have a type for RGBA float, only GBRA float.
480-
// Yuck. Punt for now and save as uint16 RGB. If people care, we
481-
// can return and ask for GBRA float and swap order.
478+
nchannels = 4;
479+
datatype = TypeFloat;
480+
// Must use planar format as there is no interleaved 32 bit float.
481+
m_dst_pix_format = AV_PIX_FMT_GBRAPF32;
482482
break;
483483

484484
// Everything else is regular 8 bit RGB
@@ -571,7 +571,31 @@ FFmpegInput::seek_subimage(int subimage, int miplevel)
571571
return true;
572572
}
573573

574+
template<typename T, int nchannels>
575+
static bool
576+
read_planar_scanline(void* data, int y, int width, AVFrame* rgb_frame)
577+
{
578+
static_assert(nchannels == 3 || nchannels == 4);
579+
580+
const T* in_planes[nchannels];
581+
const int swizzle[4] = { 1, 2, 0, 3 }; // GBR to RGB
582+
for (int channel = 0; channel < nchannels; ++channel) {
583+
if (rgb_frame->data[channel] == nullptr) {
584+
return false;
585+
}
586+
in_planes[swizzle[channel]] = reinterpret_cast<const T*>(
587+
rgb_frame->data[channel] + y * rgb_frame->linesize[channel]);
588+
}
574589

590+
T* out = static_cast<T*>(data);
591+
for (int x = 0; x < width; ++x) {
592+
for (int channel = 0; channel < nchannels; ++channel) {
593+
out[channel] = in_planes[channel][x];
594+
}
595+
out += nchannels;
596+
}
597+
return true;
598+
}
575599

576600
bool
577601
FFmpegInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
@@ -583,14 +607,39 @@ FFmpegInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
583607
if (!m_read_frame) {
584608
read_frame(m_subimage);
585609
}
586-
if (m_rgb_frame->data[0]) {
587-
memcpy(data, m_rgb_frame->data[0] + y * m_rgb_frame->linesize[0],
588-
m_stride);
589-
return true;
590-
} else {
591-
errorfmt("Error reading frame");
592-
return false;
610+
if (m_spec.format == TypeUInt8 || m_spec.nchannels == 1) {
611+
if (m_rgb_frame->data[0]) {
612+
memcpy(data, m_rgb_frame->data[0] + y * m_rgb_frame->linesize[0],
613+
m_stride);
614+
return true;
615+
}
616+
} else if (m_spec.format == TypeUInt16) {
617+
if (m_spec.nchannels == 3) {
618+
if (read_planar_scanline<uint16_t, 3>(data, y, m_spec.width,
619+
m_rgb_frame)) {
620+
return true;
621+
}
622+
} else if (m_spec.nchannels == 4) {
623+
if (read_planar_scanline<uint16_t, 4>(data, y, m_spec.width,
624+
m_rgb_frame)) {
625+
return true;
626+
}
627+
}
628+
} else if (m_spec.format == TypeFloat) {
629+
if (m_spec.nchannels == 3) {
630+
if (read_planar_scanline<float, 3>(data, y, m_spec.width,
631+
m_rgb_frame)) {
632+
return true;
633+
}
634+
} else if (m_spec.nchannels == 4) {
635+
if (read_planar_scanline<float, 4>(data, y, m_spec.width,
636+
m_rgb_frame)) {
637+
return true;
638+
}
639+
}
593640
}
641+
errorfmt("Error reading frame");
642+
return false;
594643
}
595644

596645

testsuite/ffmpeg/ref/out-ffmpeg6.1.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,26 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie
8585
oiio:BitsPerSample: 8
8686
oiio:Movie: 1
8787
oiio:subimages: 7
88+
Reading ref/vp9_rec2100_pq.mkv
89+
ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie
90+
2 subimages: 384x216 [u10,u10,u10]
91+
subimage 0: 384 x 216, 3 channel, uint10 FFmpeg movie
92+
SHA-1: 87BB7BBE57131F08F0336964256F2C190FE26A69
93+
channel list: R, G, B
94+
CICP: 9, 16, 9, 1
95+
ENCODER: "Lavf62.3.100"
96+
FramesPerSecond: 24/1 (24)
97+
ffmpeg:codec_name: "Google VP9"
98+
oiio:BitsPerSample: 10
99+
oiio:Movie: 1
100+
oiio:subimages: 2
101+
subimage 1: 384 x 216, 3 channel, uint10 FFmpeg movie
102+
SHA-1: 3820BE456CC3F4ADD3425E0C9397C83678A5BA8A
103+
channel list: R, G, B
104+
CICP: 9, 16, 9, 1
105+
ENCODER: "Lavf62.3.100"
106+
FramesPerSecond: 24/1 (24)
107+
ffmpeg:codec_name: "Google VP9"
108+
oiio:BitsPerSample: 10
109+
oiio:Movie: 1
110+
oiio:subimages: 2

testsuite/ffmpeg/ref/out-ffmpeg8.0.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,26 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie
8585
oiio:BitsPerSample: 8
8686
oiio:Movie: 1
8787
oiio:subimages: 7
88+
Reading ref/vp9_rec2100_pq.mkv
89+
ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie
90+
2 subimages: 384x216 [u10,u10,u10]
91+
subimage 0: 384 x 216, 3 channel, uint10 FFmpeg movie
92+
SHA-1: 87BB7BBE57131F08F0336964256F2C190FE26A69
93+
channel list: R, G, B
94+
CICP: 9, 16, 9, 1
95+
ENCODER: "Lavf62.3.100"
96+
FramesPerSecond: 24/1 (24)
97+
ffmpeg:codec_name: "Google VP9"
98+
oiio:BitsPerSample: 10
99+
oiio:Movie: 1
100+
oiio:subimages: 2
101+
subimage 1: 384 x 216, 3 channel, uint10 FFmpeg movie
102+
SHA-1: 3820BE456CC3F4ADD3425E0C9397C83678A5BA8A
103+
channel list: R, G, B
104+
CICP: 9, 16, 9, 1
105+
ENCODER: "Lavf62.3.100"
106+
FramesPerSecond: 24/1 (24)
107+
ffmpeg:codec_name: "Google VP9"
108+
oiio:BitsPerSample: 10
109+
oiio:Movie: 1
110+
oiio:subimages: 2
5.67 KB
Binary file not shown.

testsuite/ffmpeg/run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
# https://github.com/AcademySoftwareFoundation/OpenImageIO
66

77
imagedir = "ref/"
8-
files = [ "vp9_display_p3.mkv" ]
8+
files = [ "vp9_display_p3.mkv", "vp9_rec2100_pq.mkv" ]
99
for f in files:
1010
command = command + info_command (os.path.join(imagedir, f))

0 commit comments

Comments
 (0)