From 44e59d9e9aac7082e1bab9f5b492e439b12d0fea Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 10 Oct 2018 22:42:37 +0200 Subject: [PATCH] Move Y'CbCr conversion into a common utility class. --- Makefile | 2 +- jpeg_frame_view.cpp | 56 ++---------------- jpeg_frame_view.h | 10 ++-- video_stream.cpp | 137 ++++++++++++++------------------------------ video_stream.h | 10 +--- ycbcr_converter.cpp | 96 +++++++++++++++++++++++++++++++ ycbcr_converter.h | 42 ++++++++++++++ 7 files changed, 194 insertions(+), 159 deletions(-) create mode 100644 ycbcr_converter.cpp create mode 100644 ycbcr_converter.h diff --git a/Makefile b/Makefile index 11634ba..0775211 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ OBJS += $(OBJS_WITH_MOC:.o=.moc.o) OBJS += flow.o gpu_timers.o OBJS += ffmpeg_raii.o main.o player.o httpd.o mux.o metacube2.o video_stream.o context.o chroma_subsampler.o -OBJS += vaapi_jpeg_decoder.o memcpy_interleaved.o db.o disk_space_estimator.o +OBJS += vaapi_jpeg_decoder.o memcpy_interleaved.o db.o disk_space_estimator.o ycbcr_converter.o OBJS += state.pb.o %.o: %.cpp diff --git a/jpeg_frame_view.cpp b/jpeg_frame_view.cpp index bc71d00..3d95a77 100644 --- a/jpeg_frame_view.cpp +++ b/jpeg_frame_view.cpp @@ -22,6 +22,7 @@ #include "post_to_main_thread.h" #include "vaapi_jpeg_decoder.h" #include "video_stream.h" +#include "ycbcr_converter.h" using namespace movit; using namespace std; @@ -317,34 +318,12 @@ void JPEGFrameView::initializeGL() jpeg_decoder_thread = std::thread(jpeg_decoder_thread_func); }); + ycbcr_converter.reset(new YCbCrConverter(YCbCrConverter::OUTPUT_TO_RGBA, resource_pool)); + ImageFormat inout_format; inout_format.color_space = COLORSPACE_sRGB; inout_format.gamma_curve = GAMMA_sRGB; - ycbcr_format.luma_coefficients = YCBCR_REC_709; - ycbcr_format.full_range = false; - ycbcr_format.num_levels = 256; - ycbcr_format.chroma_subsampling_x = 2; - ycbcr_format.chroma_subsampling_y = 1; - ycbcr_format.cb_x_position = 0.0f; // H.264 -- _not_ JPEG, even though our input is MJPEG-encoded - ycbcr_format.cb_y_position = 0.5f; // Irrelevant. - ycbcr_format.cr_x_position = 0.0f; - ycbcr_format.cr_y_position = 0.5f; - - // Planar Y'CbCr decoding chain. - planar_chain.reset(new EffectChain(1280, 720, resource_pool)); - ycbcr_planar_input = (movit::YCbCrInput *)planar_chain->add_input(new YCbCrInput(inout_format, ycbcr_format, 1280, 720, YCBCR_INPUT_PLANAR)); - planar_chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); - planar_chain->set_dither_bits(8); - planar_chain->finalize(); - - // Semiplanar Y'CbCr decoding chain (for images coming from VA-API). - semiplanar_chain.reset(new EffectChain(1280, 720, resource_pool)); - ycbcr_semiplanar_input = (movit::YCbCrInput *)semiplanar_chain->add_input(new YCbCrInput(inout_format, ycbcr_format, 1280, 720, YCBCR_INPUT_SPLIT_Y_AND_CBCR)); - semiplanar_chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); - semiplanar_chain->set_dither_bits(8); - semiplanar_chain->finalize(); - overlay_chain.reset(new EffectChain(overlay_base_width, overlay_base_height, resource_pool)); overlay_input = (movit::FlatInput *)overlay_chain->add_input(new FlatInput(inout_format, FORMAT_GRAYSCALE, GL_UNSIGNED_BYTE, overlay_base_width, overlay_base_height)); @@ -373,11 +352,7 @@ void JPEGFrameView::paintGL() } check_error(); - if (current_frame->is_semiplanar) { - semiplanar_chain->render_to_screen(); - } else { - planar_chain->render_to_screen(); - } + current_chain->render_to_screen(); if (overlay_image != nullptr) { if (overlay_input_needs_refresh) { @@ -394,28 +369,7 @@ void JPEGFrameView::setDecodedFrame(std::shared_ptr frame) { post_to_main_thread([this, frame] { current_frame = frame; - ycbcr_format.chroma_subsampling_x = frame->chroma_subsampling_x; - ycbcr_format.chroma_subsampling_y = frame->chroma_subsampling_y; - - if (frame->is_semiplanar) { - ycbcr_semiplanar_input->change_ycbcr_format(ycbcr_format); - ycbcr_semiplanar_input->set_width(frame->width); - ycbcr_semiplanar_input->set_height(frame->height); - ycbcr_semiplanar_input->set_pixel_data(0, frame->y.get()); - ycbcr_semiplanar_input->set_pixel_data(1, frame->cbcr.get()); - ycbcr_semiplanar_input->set_pitch(0, frame->pitch_y); - ycbcr_semiplanar_input->set_pitch(1, frame->pitch_chroma); - } else { - ycbcr_planar_input->change_ycbcr_format(ycbcr_format); - ycbcr_planar_input->set_width(frame->width); - ycbcr_planar_input->set_height(frame->height); - ycbcr_planar_input->set_pixel_data(0, frame->y.get()); - ycbcr_planar_input->set_pixel_data(1, frame->cb.get()); - ycbcr_planar_input->set_pixel_data(2, frame->cr.get()); - ycbcr_planar_input->set_pitch(0, frame->pitch_y); - ycbcr_planar_input->set_pitch(1, frame->pitch_chroma); - ycbcr_planar_input->set_pitch(2, frame->pitch_chroma); - } + current_chain = ycbcr_converter->prepare_chain_for_conversion(frame); update(); }); } diff --git a/jpeg_frame_view.h b/jpeg_frame_view.h index d0cd47c..d8babbb 100644 --- a/jpeg_frame_view.h +++ b/jpeg_frame_view.h @@ -14,6 +14,7 @@ #include #include "jpeg_frame.h" +#include "ycbcr_converter.h" struct JPEGID { unsigned stream_idx; @@ -59,13 +60,10 @@ private: // The stream index of the latest frame we displayed. unsigned current_stream_idx = 0; - std::unique_ptr planar_chain; - std::shared_ptr current_frame; // So that we hold on to the pixels. - movit::YCbCrInput *ycbcr_planar_input; - movit::YCbCrFormat ycbcr_format; + std::unique_ptr ycbcr_converter; + movit::EffectChain *current_chain = nullptr; // Owned by ycbcr_converter. - std::unique_ptr semiplanar_chain; - movit::YCbCrInput *ycbcr_semiplanar_input; + std::shared_ptr current_frame; // So that we hold on to the pixels. static constexpr int overlay_base_width = 16, overlay_base_height = 16; int overlay_width = overlay_base_width, overlay_height = overlay_base_height; diff --git a/video_stream.cpp b/video_stream.cpp index 2a75e20..c01ec5c 100644 --- a/video_stream.cpp +++ b/video_stream.cpp @@ -17,6 +17,7 @@ extern "C" { #include "mux.h" #include "player.h" #include "util.h" +#include "ycbcr_converter.h" #include @@ -132,9 +133,9 @@ vector encode_jpeg(const uint8_t *y_data, const uint8_t *cb_data, const JSAMPARRAY data[3] = { yptr, cbptr, crptr }; for (unsigned y = 0; y < height; y += 8) { for (unsigned yy = 0; yy < 8; ++yy) { - yptr[yy] = const_cast(&y_data[(height - y - yy - 1) * width]); - cbptr[yy] = const_cast(&cb_data[(height - y - yy - 1) * width/2]); - crptr[yy] = const_cast(&cr_data[(height - y - yy - 1) * width/2]); + yptr[yy] = const_cast(&y_data[(y + yy) * width]); + cbptr[yy] = const_cast(&cb_data[(y + yy) * width/2]); + crptr[yy] = const_cast(&cr_data[(y + yy) * width/2]); } jpeg_write_raw_data(&cinfo, data, /*num_lines=*/8); @@ -148,50 +149,11 @@ vector encode_jpeg(const uint8_t *y_data, const uint8_t *cb_data, const VideoStream::VideoStream() { - using namespace movit; - - ImageFormat inout_format; - inout_format.color_space = COLORSPACE_sRGB; - inout_format.gamma_curve = GAMMA_sRGB; - - ycbcr_format.luma_coefficients = YCBCR_REC_709; - ycbcr_format.full_range = true; // JPEG. - ycbcr_format.num_levels = 256; - ycbcr_format.chroma_subsampling_x = 2; - ycbcr_format.chroma_subsampling_y = 1; - ycbcr_format.cb_x_position = 0.0f; // H.264 -- _not_ JPEG, even though our input is MJPEG-encoded - ycbcr_format.cb_y_position = 0.5f; // Irrelevant. - ycbcr_format.cr_x_position = 0.0f; - ycbcr_format.cr_y_position = 0.5f; - - YCbCrFormat ycbcr_output_format = ycbcr_format; - ycbcr_output_format.chroma_subsampling_x = 1; - - // TODO: deduplicate code against JPEGFrameView? - ycbcr_planar_convert_chain.reset(new EffectChain(1280, 720)); - ycbcr_planar_input = (movit::YCbCrInput *)ycbcr_planar_convert_chain->add_input(new YCbCrInput(inout_format, ycbcr_format, 1280, 720, YCBCR_INPUT_PLANAR)); - - // One full Y'CbCr texture (for interpolation), one that's just Y (throwing away the - // Cb and Cr channels). The second copy is sort of redundant, but it's the easiest way - // of getting the gray data into a layered texture. - ycbcr_planar_convert_chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_output_format); - ycbcr_planar_convert_chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_output_format); - ycbcr_planar_convert_chain->set_dither_bits(8); - ycbcr_planar_convert_chain->finalize(); - - // Same, for semiplanar inputs. - ycbcr_semiplanar_convert_chain.reset(new EffectChain(1280, 720)); - ycbcr_semiplanar_input = (movit::YCbCrInput *)ycbcr_semiplanar_convert_chain->add_input(new YCbCrInput(inout_format, ycbcr_format, 1280, 720, YCBCR_INPUT_SPLIT_Y_AND_CBCR)); - - // One full Y'CbCr texture (for interpolation), one that's just Y (throwing away the - // Cb and Cr channels). The second copy is sort of redundant, but it's the easiest way - // of getting the gray data into a layered texture. - ycbcr_semiplanar_convert_chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_output_format); - ycbcr_semiplanar_convert_chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_output_format); - ycbcr_semiplanar_convert_chain->set_dither_bits(8); - ycbcr_semiplanar_convert_chain->finalize(); - - GLuint input_tex[num_interpolate_slots], gray_tex[num_interpolate_slots], cb_tex[num_interpolate_slots], cr_tex[num_interpolate_slots]; + ycbcr_converter.reset(new YCbCrConverter(YCbCrConverter::OUTPUT_TO_DUAL_YCBCR, /*resource_pool=*/nullptr)); + + GLuint input_tex[num_interpolate_slots], gray_tex[num_interpolate_slots]; + GLuint cb_tex[num_interpolate_slots], cr_tex[num_interpolate_slots]; + glCreateTextures(GL_TEXTURE_2D_ARRAY, 10, input_tex); glCreateTextures(GL_TEXTURE_2D_ARRAY, 10, gray_tex); glCreateTextures(GL_TEXTURE_2D, 10, cb_tex); @@ -320,7 +282,6 @@ void VideoStream::schedule_interpolated_frame(int64_t output_pts, unsigned strea check_error(); // Convert frame0 and frame1 to OpenGL textures. - // TODO: Deduplicate against JPEGFrameView::setDecodedFrame? for (size_t frame_no = 0; frame_no < 2; ++frame_no) { JPEGID jpeg_id; jpeg_id.stream_idx = stream_idx; @@ -328,30 +289,7 @@ void VideoStream::schedule_interpolated_frame(int64_t output_pts, unsigned strea jpeg_id.interpolated = false; bool did_decode; shared_ptr frame = decode_jpeg_with_cache(jpeg_id, DECODE_IF_NOT_IN_CACHE, &did_decode); - ycbcr_format.chroma_subsampling_x = frame->chroma_subsampling_x; - ycbcr_format.chroma_subsampling_y = frame->chroma_subsampling_y; - - if (frame->is_semiplanar) { - ycbcr_semiplanar_input->change_ycbcr_format(ycbcr_format); - ycbcr_semiplanar_input->set_width(frame->width); - ycbcr_semiplanar_input->set_height(frame->height); - ycbcr_semiplanar_input->set_pixel_data(0, frame->y.get()); - ycbcr_semiplanar_input->set_pixel_data(1, frame->cbcr.get()); - ycbcr_semiplanar_input->set_pitch(0, frame->pitch_y); - ycbcr_semiplanar_input->set_pitch(1, frame->pitch_chroma); - ycbcr_semiplanar_convert_chain->render_to_fbo(resources.input_fbos[frame_no], 1280, 720); - } else { - ycbcr_planar_input->change_ycbcr_format(ycbcr_format); - ycbcr_planar_input->set_width(frame->width); - ycbcr_planar_input->set_height(frame->height); - ycbcr_planar_input->set_pixel_data(0, frame->y.get()); - ycbcr_planar_input->set_pixel_data(1, frame->cb.get()); - ycbcr_planar_input->set_pixel_data(2, frame->cr.get()); - ycbcr_planar_input->set_pitch(0, frame->pitch_y); - ycbcr_planar_input->set_pitch(1, frame->pitch_chroma); - ycbcr_planar_input->set_pitch(2, frame->pitch_chroma); - ycbcr_planar_convert_chain->render_to_fbo(resources.input_fbos[frame_no], 1280, 720); - } + ycbcr_converter->prepare_chain_for_conversion(frame)->render_to_fbo(resources.input_fbos[frame_no], 1280, 720); } glGenerateTextureMipmap(resources.input_tex); @@ -395,6 +333,37 @@ void VideoStream::schedule_interpolated_frame(int64_t output_pts, unsigned strea queue_nonempty.notify_all(); } +namespace { + +shared_ptr frame_from_pbo(void *contents, size_t width, size_t height) +{ + size_t chroma_width = width / 2; + + const uint8_t *y = (const uint8_t *)contents; + const uint8_t *cb = (const uint8_t *)contents + width * height; + const uint8_t *cr = (const uint8_t *)contents + width * height + chroma_width * height; + + shared_ptr frame(new Frame); + frame->y.reset(new uint8_t[width * height]); + frame->cb.reset(new uint8_t[chroma_width * height]); + frame->cr.reset(new uint8_t[chroma_width * height]); + for (unsigned yy = 0; yy < height; ++yy) { + memcpy(frame->y.get() + width * yy, y + width * yy, width); + memcpy(frame->cb.get() + chroma_width * yy, cb + chroma_width * yy, chroma_width); + memcpy(frame->cr.get() + chroma_width * yy, cr + chroma_width * yy, chroma_width); + } + frame->is_semiplanar = false; + frame->width = width; + frame->height = height; + frame->chroma_subsampling_x = 2; + frame->chroma_subsampling_y = 1; + frame->pitch_y = width; + frame->pitch_chroma = chroma_width; + return frame; +} + +} // namespace + void VideoStream::encode_thread_func() { pthread_setname_np(pthread_self(), "VideoStream"); @@ -429,31 +398,13 @@ void VideoStream::encode_thread_func() } else if (qf.type == QueuedFrame::INTERPOLATED) { glClientWaitSync(qf.fence.get(), /*flags=*/0, GL_TIMEOUT_IGNORED); - const uint8_t *y = (const uint8_t *)qf.resources.pbo_contents; - const uint8_t *cb = (const uint8_t *)qf.resources.pbo_contents + 1280 * 720; - const uint8_t *cr = (const uint8_t *)qf.resources.pbo_contents + 1280 * 720 + 640 * 720; // Send a copy of the frame on to display. - shared_ptr frame(new Frame); - frame->y.reset(new uint8_t[1280 * 720]); - frame->cb.reset(new uint8_t[640 * 720]); - frame->cr.reset(new uint8_t[640 * 720]); - for (unsigned yy = 0; yy < 720; ++yy) { - memcpy(frame->y.get() + 1280 * yy, y + 1280 * (719 - yy), 1280); - memcpy(frame->cb.get() + 640 * yy, cb + 640 * (719 - yy), 640); - memcpy(frame->cr.get() + 640 * yy, cr + 640 * (719 - yy), 640); - } - frame->is_semiplanar = false; - frame->width = 1280; - frame->height = 720; - frame->chroma_subsampling_x = 2; - frame->chroma_subsampling_y = 1; - frame->pitch_y = 1280; - frame->pitch_chroma = 640; - JPEGFrameView::insert_interpolated_frame(qf.stream_idx, qf.output_pts, std::move(frame)); + shared_ptr frame = frame_from_pbo(qf.resources.pbo_contents, 1280, 720); + JPEGFrameView::insert_interpolated_frame(qf.stream_idx, qf.output_pts, frame); // Now JPEG encode it, and send it on to the stream. - vector jpeg = encode_jpeg(y, cb, cr, 1280, 720); + vector jpeg = encode_jpeg(frame->y.get(), frame->cb.get(), frame->cr.get(), 1280, 720); compute_flow->release_texture(qf.flow_tex); interpolate->release_texture(qf.output_tex); interpolate->release_texture(qf.cbcr_tex); diff --git a/video_stream.h b/video_stream.h index 5b8df26..f05a10a 100644 --- a/video_stream.h +++ b/video_stream.h @@ -25,6 +25,7 @@ class Interpolate; class Mux; class QSurface; class QSurfaceFormat; +class YCbCrConverter; class VideoStream { public: @@ -78,14 +79,7 @@ private: std::string stream_mux_header; bool seen_sync_markers = false; - // Effectively only converts from 4:2:2 to 4:4:4. - // TODO: Have a separate version with ResampleEffect, for scaling? - std::unique_ptr ycbcr_planar_convert_chain; - std::unique_ptr ycbcr_semiplanar_convert_chain; - - movit::YCbCrInput *ycbcr_planar_input; - movit::YCbCrInput *ycbcr_semiplanar_input; - movit::YCbCrFormat ycbcr_format; + std::unique_ptr ycbcr_converter; // Frame interpolation. std::unique_ptr compute_flow; diff --git a/ycbcr_converter.cpp b/ycbcr_converter.cpp new file mode 100644 index 0000000..d038fc4 --- /dev/null +++ b/ycbcr_converter.cpp @@ -0,0 +1,96 @@ +#include "ycbcr_converter.h" + +#include +#include + +#include "jpeg_frame.h" + +using namespace std; +using namespace movit; + +YCbCrConverter::YCbCrConverter(YCbCrConverter::OutputMode output_mode, ResourcePool *resource_pool) +{ + ImageFormat inout_format; + inout_format.color_space = COLORSPACE_sRGB; + inout_format.gamma_curve = GAMMA_sRGB; + + ycbcr_format.luma_coefficients = YCBCR_REC_709; + ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; + ycbcr_format.chroma_subsampling_x = 2; + ycbcr_format.chroma_subsampling_y = 1; + ycbcr_format.cb_x_position = 0.0f; // H.264 -- _not_ JPEG, even though our input is MJPEG-encoded + ycbcr_format.cb_y_position = 0.5f; // Irrelevant. + ycbcr_format.cr_x_position = 0.0f; + ycbcr_format.cr_y_position = 0.5f; + + YCbCrFormat ycbcr_output_format = ycbcr_format; + ycbcr_output_format.chroma_subsampling_x = 1; + + // Planar Y'CbCr decoding chain. + planar_chain.reset(new EffectChain(1280, 720, resource_pool)); + ycbcr_planar_input = (YCbCrInput *)planar_chain->add_input(new YCbCrInput(inout_format, ycbcr_format, 1280, 720, YCBCR_INPUT_PLANAR)); + if (output_mode == OUTPUT_TO_RGBA) { + planar_chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); + planar_chain->set_output_origin(OUTPUT_ORIGIN_BOTTOM_LEFT); + } else { + assert(output_mode == OUTPUT_TO_DUAL_YCBCR); + + // One full Y'CbCr texture (for interpolation), one that's just Y (throwing away the + // Cb and Cr channels). The second copy is sort of redundant, but it's the easiest way + // of getting the gray data into a layered texture. + planar_chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_output_format); + planar_chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_output_format); + planar_chain->set_output_origin(OUTPUT_ORIGIN_TOP_LEFT); + } + planar_chain->set_dither_bits(8); + planar_chain->finalize(); + + // Semiplanar Y'CbCr decoding chain (for images coming from VA-API). + semiplanar_chain.reset(new EffectChain(1280, 720, resource_pool)); + ycbcr_semiplanar_input = (YCbCrInput *)semiplanar_chain->add_input(new YCbCrInput(inout_format, ycbcr_format, 1280, 720, YCBCR_INPUT_SPLIT_Y_AND_CBCR)); + if (output_mode == OUTPUT_TO_RGBA) { + semiplanar_chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); + semiplanar_chain->set_output_origin(OUTPUT_ORIGIN_BOTTOM_LEFT); + } else { + // See above. + semiplanar_chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_output_format); + semiplanar_chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_output_format); + semiplanar_chain->set_output_origin(OUTPUT_ORIGIN_TOP_LEFT); + } + semiplanar_chain->set_dither_bits(8); + semiplanar_chain->finalize(); +} + +EffectChain *YCbCrConverter::prepare_chain_for_conversion(shared_ptr frame) +{ + if (frame->is_semiplanar) { + setup_input_for_frame(frame, ycbcr_format, ycbcr_semiplanar_input); + return semiplanar_chain.get(); + } else { + setup_input_for_frame(frame, ycbcr_format, ycbcr_planar_input); + return planar_chain.get(); + } +} + +void setup_input_for_frame(shared_ptr frame, const YCbCrFormat &ycbcr_format, YCbCrInput *input) +{ + YCbCrFormat format_copy = ycbcr_format; + format_copy.chroma_subsampling_x = frame->chroma_subsampling_x; + format_copy.chroma_subsampling_y = frame->chroma_subsampling_y; + input->change_ycbcr_format(format_copy); + + input->set_width(frame->width); + input->set_height(frame->height); + input->set_pixel_data(0, frame->y.get()); + input->set_pitch(0, frame->pitch_y); + if (frame->is_semiplanar) { + input->set_pixel_data(1, frame->cbcr.get()); + input->set_pitch(1, frame->pitch_chroma); + } else { + input->set_pixel_data(1, frame->cb.get()); + input->set_pixel_data(2, frame->cr.get()); + input->set_pitch(1, frame->pitch_chroma); + input->set_pitch(2, frame->pitch_chroma); + } +} diff --git a/ycbcr_converter.h b/ycbcr_converter.h new file mode 100644 index 0000000..928ebed --- /dev/null +++ b/ycbcr_converter.h @@ -0,0 +1,42 @@ +#ifndef _YCBCR_CONVERTER_H +#define _YCBCR_CONVERTER_H 1 + +#include + +#include + +namespace movit { + +class EffectChain; +class MixEffect; +class ResourcePool; +struct YCbCrFormat; + +} // namespace movit + +struct Frame; + +struct YCbCrConverter { +public: + enum OutputMode { + OUTPUT_TO_RGBA, // One texture (bottom-left origin): RGBA + OUTPUT_TO_DUAL_YCBCR // Two textures (top-left origin): Y'CbCr, Y'CbCr + }; + YCbCrConverter(OutputMode output_mode, movit::ResourcePool *resource_pool); + + // Returns the appropriate chain for rendering. + movit::EffectChain *prepare_chain_for_conversion(std::shared_ptr frame); + +private: + movit::YCbCrFormat ycbcr_format; + + // Effectively only converts from 4:2:2 to 4:4:4. + // TODO: Have a separate version with ResampleEffect, for scaling? + std::unique_ptr planar_chain, semiplanar_chain; + movit::YCbCrInput *ycbcr_planar_input, *ycbcr_semiplanar_input; +}; + +// TODO: make private +void setup_input_for_frame(std::shared_ptr frame, const movit::YCbCrFormat &ycbcr_format, movit::YCbCrInput *input); + +#endif // !defined(_YCBCR_CONVERTER_H) -- 2.39.2