2021-06-05 16:42:25 -04:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2021, Hunter Salyer <thefalsehonesty@gmail.com>
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
*/
|
|
|
|
|
2022-09-25 17:23:16 -05:00
|
|
|
#include <LibCore/ArgsParser.h>
|
|
|
|
#include <LibCore/ElapsedTimer.h>
|
2021-06-05 16:42:25 -04:00
|
|
|
#include <LibGUI/Application.h>
|
|
|
|
#include <LibGUI/BoxLayout.h>
|
|
|
|
#include <LibGUI/ImageWidget.h>
|
|
|
|
#include <LibGUI/Window.h>
|
|
|
|
#include <LibGfx/Bitmap.h>
|
2021-12-31 22:29:07 +00:00
|
|
|
#include <LibMain/Main.h>
|
2021-06-05 16:42:25 -04:00
|
|
|
#include <LibVideo/MatroskaReader.h>
|
|
|
|
#include <LibVideo/VP9/Decoder.h>
|
|
|
|
|
2021-12-31 22:29:07 +00:00
|
|
|
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
2021-06-05 16:42:25 -04:00
|
|
|
{
|
2022-09-25 17:23:16 -05:00
|
|
|
bool benchmark = false;
|
|
|
|
StringView filename = "/home/anon/Videos/test-webm.webm"sv;
|
|
|
|
|
|
|
|
Core::ArgsParser args_parser;
|
|
|
|
args_parser.add_option(benchmark, "Benchmark the video decoder.", "benchmark", 'b');
|
|
|
|
args_parser.add_positional_argument(filename, "The video file to display.", "filename", Core::ArgsParser::Required::No);
|
|
|
|
args_parser.parse(arguments);
|
|
|
|
|
2021-12-31 22:29:07 +00:00
|
|
|
auto app = TRY(GUI::Application::try_create(arguments));
|
|
|
|
auto window = TRY(GUI::Window::try_create());
|
2021-06-05 16:42:25 -04:00
|
|
|
|
2022-09-25 17:23:16 -05:00
|
|
|
auto document = Video::MatroskaReader::parse_matroska_from_file(filename);
|
|
|
|
// FIXME: MatroskaReader should use ErrorOr
|
|
|
|
if (!document) {
|
|
|
|
outln("{} could not be read", filename);
|
|
|
|
return 1;
|
|
|
|
}
|
2021-06-20 10:37:33 -04:00
|
|
|
auto const& optional_track = document->track_for_track_type(Video::TrackEntry::TrackType::Video);
|
2021-06-05 16:42:25 -04:00
|
|
|
if (!optional_track.has_value())
|
|
|
|
return 1;
|
2021-06-20 10:37:33 -04:00
|
|
|
auto const& track = optional_track.value();
|
|
|
|
auto const video_track = track.video_track().value();
|
2021-06-05 16:42:25 -04:00
|
|
|
|
2022-09-16 04:07:52 -05:00
|
|
|
auto image = TRY(Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, Gfx::IntSize(video_track.pixel_width, video_track.pixel_height)));
|
|
|
|
|
2022-01-07 15:01:38 +01:00
|
|
|
auto main_widget = TRY(window->try_set_main_widget<GUI::Widget>());
|
|
|
|
main_widget->set_fill_with_background_color(true);
|
|
|
|
main_widget->set_layout<GUI::VerticalBoxLayout>();
|
2022-09-16 04:07:52 -05:00
|
|
|
auto image_widget = TRY(main_widget->try_add<GUI::ImageWidget>());
|
2021-06-05 16:42:25 -04:00
|
|
|
|
|
|
|
Video::VP9::Decoder vp9_decoder;
|
2022-09-16 04:07:52 -05:00
|
|
|
size_t cluster_index = 0;
|
|
|
|
size_t block_index = 0;
|
|
|
|
size_t frame_index = 0;
|
|
|
|
auto frame_number = 0u;
|
|
|
|
|
|
|
|
auto get_next_sample = [&]() -> Optional<ByteBuffer> {
|
|
|
|
for (; cluster_index < document->clusters().size(); cluster_index++) {
|
|
|
|
for (; block_index < document->clusters()[cluster_index].blocks().size(); block_index++) {
|
|
|
|
auto const& candidate_block = document->clusters()[cluster_index].blocks()[block_index];
|
|
|
|
if (candidate_block.track_number() != track.track_number())
|
|
|
|
continue;
|
|
|
|
if (frame_index < candidate_block.frames().size())
|
|
|
|
return candidate_block.frame(frame_index);
|
|
|
|
frame_index = 0;
|
2022-10-08 19:13:02 -05:00
|
|
|
}
|
2022-09-16 04:07:52 -05:00
|
|
|
block_index = 0;
|
2021-06-05 16:42:25 -04:00
|
|
|
}
|
2022-09-16 04:07:52 -05:00
|
|
|
return {};
|
|
|
|
};
|
|
|
|
|
|
|
|
auto display_next_frame = [&]() {
|
|
|
|
auto optional_sample = get_next_sample();
|
|
|
|
|
|
|
|
if (!optional_sample.has_value())
|
|
|
|
return;
|
|
|
|
|
2022-09-22 21:49:10 -05:00
|
|
|
auto result = vp9_decoder.decode(optional_sample.release_value());
|
2022-09-16 04:07:52 -05:00
|
|
|
|
|
|
|
if (result.is_error()) {
|
|
|
|
outln("Error decoding frame {}: {}", frame_number, result.error().string_literal());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: This method of output is temporary and should be replaced with an image struct
|
|
|
|
// containing the planes and their sizes. Ideally, this struct would be interpreted
|
|
|
|
// by some color conversion library and then passed to something (GL?) for output.
|
|
|
|
auto const& output_y = vp9_decoder.get_output_buffer_for_plane(0);
|
|
|
|
auto const& output_u = vp9_decoder.get_output_buffer_for_plane(1);
|
|
|
|
auto const& output_v = vp9_decoder.get_output_buffer_for_plane(2);
|
|
|
|
auto y_size = vp9_decoder.get_y_plane_size();
|
|
|
|
auto uv_subsampling_y = vp9_decoder.get_uv_subsampling_y();
|
|
|
|
auto uv_subsampling_x = vp9_decoder.get_uv_subsampling_x();
|
|
|
|
Gfx::IntSize uv_size { y_size.width() >> uv_subsampling_x, y_size.height() >> uv_subsampling_y };
|
|
|
|
|
|
|
|
for (auto y_row = 0u; y_row < video_track.pixel_height; y_row++) {
|
|
|
|
auto uv_row = y_row >> uv_subsampling_y;
|
|
|
|
|
|
|
|
for (auto y_column = 0u; y_column < video_track.pixel_width; y_column++) {
|
|
|
|
auto uv_column = y_column >> uv_subsampling_x;
|
|
|
|
|
|
|
|
auto y = output_y[y_row * y_size.width() + y_column];
|
|
|
|
auto cb = output_u[uv_row * uv_size.width() + uv_column];
|
|
|
|
auto cr = output_v[uv_row * uv_size.width() + uv_column];
|
|
|
|
// Convert from Rec.709 YCbCr to RGB.
|
|
|
|
auto r_float = floorf(clamp(y + (cr - 128) * 219.0f / 224.0f * 1.5748f, 0, 255));
|
|
|
|
auto g_float = floorf(clamp(y + (cb - 128) * 219.0f / 224.0f * -0.0722f * 1.8556f / 0.7152f + (cr - 128) * 219.0f / 224.0f * -0.2126f * 1.5748f / 0.7152f, 0, 255));
|
|
|
|
auto b_float = floorf(clamp(y + (cb - 128) * 219.0f / 224.0f * 1.8556f, 0, 255));
|
|
|
|
auto r = static_cast<u8>(r_float);
|
|
|
|
auto g = static_cast<u8>(g_float);
|
|
|
|
auto b = static_cast<u8>(b_float);
|
|
|
|
|
|
|
|
image->set_pixel(y_column, y_row, Gfx::Color(r, g, b));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
image_widget->set_bitmap(image);
|
|
|
|
image_widget->update();
|
|
|
|
|
|
|
|
frame_index++;
|
|
|
|
frame_number++;
|
|
|
|
};
|
|
|
|
|
2022-09-25 17:23:16 -05:00
|
|
|
image_widget->set_fixed_size(video_track.pixel_width, video_track.pixel_height);
|
|
|
|
image_widget->on_click = [&]() { display_next_frame(); };
|
|
|
|
|
|
|
|
if (benchmark) {
|
|
|
|
auto timer = Core::ElapsedTimer::start_new();
|
|
|
|
for (auto i = 0; i < 100; i++)
|
|
|
|
display_next_frame();
|
|
|
|
auto elapsed_time = timer.elapsed_time();
|
|
|
|
outln("Decoding 100 frames took {} ms", elapsed_time.to_milliseconds());
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-09-16 04:07:52 -05:00
|
|
|
display_next_frame();
|
2021-06-05 16:42:25 -04:00
|
|
|
|
|
|
|
window->show();
|
|
|
|
return app->exec();
|
|
|
|
}
|