blob: e9cb2a3531be0d8360127963c9774a1e5baf9804 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "lib/media/test/frame_sink.h"
#include <fuchsia/mediacodec/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/fit/defer.h>
#include <lib/media/codec_impl/fourcc.h>
#include <lib/media/test/frame_sink_view.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/zx/vmo.h>
#include <memory>
#include "src/lib/syslog/cpp/logger.h"
namespace {
// bear.mp4 says 29.97, and bear.h264 is same content.
// Other longer test files want 24.
constexpr double kDefaultFramesPerSecond = 24;
} // namespace
std::unique_ptr<FrameSink> FrameSink::Create(
sys::ComponentContext* component_context, async::Loop* main_loop, double frames_per_second,
fit::function<void(FrameSink*)> view_connected_callback) {
return std::unique_ptr<FrameSink>(new FrameSink(component_context, main_loop, frames_per_second,
std::move(view_connected_callback)));
}
FrameSink::~FrameSink() {
// Only after ~view_provider_component_ do we know there will be zero views_
// left.
view_provider_component_ = nullptr;
FX_DCHECK(views_.empty());
}
// Must be called on main_loop_'s thread.
void FrameSink::PutFrame(uint32_t image_id, const zx::vmo& vmo, uint64_t vmo_offset,
std::shared_ptr<const fuchsia::media::StreamOutputFormat> output_format,
fit::closure on_done) {
// This method fans out to the views_, and runs on_done async when all the
// views_ are done with the frame.
// The alive_views_ won't change during this method because we're on
// main_loop_'s thread.
frames_outstanding_++;
auto done_runner = fit::defer([this, image_id, on_done = std::move(on_done)] {
// To be clear, Scenic ImagePipe doesn't really "release an image_id",
// it releases an item in the present queue, but the way this example
// program uses the present queue, it's equivalent since there's only
// ever at most 1 usage of any given image_id in the present queue at
// any given time.
FX_VLOGS(3) << "Scenic released image_id: " << image_id;
on_done();
frames_outstanding_--;
CheckIfAllFramesReturned();
});
auto shared_done_runner = std::make_shared<decltype(done_runner)>(std::move(done_runner));
FX_DCHECK(output_format->has_format_details());
FX_DCHECK(output_format->format_details().has_domain());
FX_DCHECK(output_format->format_details().domain().is_video());
FX_DCHECK(output_format->format_details().domain().video().is_uncompressed());
const fuchsia::media::VideoUncompressedFormat& video_format =
output_format->format_details().domain().video().uncompressed();
zx_time_t present_time;
if (last_requested_present_time_ == ZX_TIME_INFINITE_PAST) {
// Tell Scenic to show the first frame around now-ish.
present_time = zx_clock_get_monotonic() + ZX_SEC(3);
} else {
auto delta = ZX_USEC(1000000 / frames_per_second_);
FX_CHECK(delta > 0);
present_time = last_requested_present_time_ + delta;
}
last_requested_present_time_ = present_time;
FX_VLOGS(3) << "putting frame - present_time: " << present_time << " image_id: " << image_id;
for (FrameSinkView* view : views_) {
view->PutFrame(image_id, present_time, vmo, vmo_offset, video_format, [shared_done_runner] {
// ~shared_done_runner will run on_done when shared_ptr<>
// refcount drops to 0
});
}
}
void FrameSink::PutEndOfStreamThenWaitForFramesReturnedAsync(fit::closure on_frames_returned) {
// We make a blank frame and send that in to be displayed 3 seconds after
// the last real frame, to give us a chance to see the last frame of a short
// .h264 file. The blank frame is necessary to get Scenic to release the last
// real frame, and this seems cleaner than any other option I can think of at
// the moment, like relying on any particular Scenic frame release behavior if
// RemoveImage() is called on a frame that's still (maybe) on-screen, etc.
constexpr double kDelayBeforeBlankFrameSeconds = 3.0;
// If this fourcc were to change, some of the other code to compute size,
// dimensions, and generate the frame data would need to change too.
// Currently we rely on a new VMO starting filled with 0s, which is what we
// want anyway for BGRA / BGRA_8.
constexpr uint32_t kBlankFrameFourcc = make_fourcc('B', 'G', 'R', 'A');
constexpr uint32_t kBlankFrameWidth = 1;
constexpr uint32_t kBlankFrameHeight = 1;
constexpr uint32_t kBlankFramePixelBytes = sizeof(uint32_t);
constexpr uint32_t kBlankFrameBytes =
kBlankFrameWidth * kBlankFrameHeight * kBlankFramePixelBytes;
constexpr uint32_t kBlankFrameVmoOffset = 0;
zx_time_t blank_frame_present_time =
last_requested_present_time_ + ZX_SEC(kDelayBeforeBlankFrameSeconds);
::zx::vmo blank_frame_vmo;
zx_status_t status = ::zx::vmo::create(kBlankFrameBytes, 0, &blank_frame_vmo);
FX_CHECK(status == ZX_OK) << "::zx::vmo::create() failed - status: " << status;
// We intentionally change the format, including pixel format, for the blank
// frame, because it's easier to generate a black frame in RGB, and because
// there's no harm in covering Scenic's ability to switch to a frame with
// completely different format.
fuchsia::media::VideoUncompressedFormat blank_frame_video_format{
.primary_width_pixels = kBlankFrameWidth,
.primary_height_pixels = kBlankFrameHeight,
.primary_display_width_pixels = kBlankFrameWidth,
.primary_display_height_pixels = kBlankFrameHeight,
.primary_line_stride_bytes = kBlankFramePixelBytes * kBlankFrameWidth,
.fourcc = kBlankFrameFourcc,
// None of the other fields matter for BGRA / BGRA_8.
};
for (FrameSinkView* view : views_) {
// This frame is not necessarily ever returned, which is fine.
view->PutFrame(kBlankFrameImageId, blank_frame_present_time, blank_frame_vmo,
kBlankFrameVmoOffset, blank_frame_video_format, nullptr);
}
FX_DCHECK(!on_frames_returned_);
on_frames_returned_ = std::move(on_frames_returned);
// It's unlikely that this would see all the frames returned, but possible,
// especially if Scenic died. Normally it'll be a similar check later on
// after a frame comes back that'll see all the frames returned.
CheckIfAllFramesReturned();
}
void FrameSink::AddFrameSinkView(FrameSinkView* view) {
views_.insert(view);
FX_DCHECK(view_connected_callback_);
view_connected_callback_(this);
}
void FrameSink::RemoveFrameSinkView(FrameSinkView* view) { views_.erase(view); }
FrameSink::FrameSink(sys::ComponentContext* component_context, async::Loop* main_loop,
double frames_per_second,
fit::function<void(FrameSink*)> view_connected_callback)
: component_context_(component_context),
main_loop_(main_loop),
// IEEE 754 floating point can represent 0.0 exactly.
frames_per_second_(frames_per_second != 0.0 ? frames_per_second : kDefaultFramesPerSecond),
view_connected_callback_(std::move(view_connected_callback)) {
view_provider_component_ = std::make_unique<scenic::ViewProviderComponent>(
[this](scenic::ViewContext view_context) {
return FrameSinkView::Create(std::move(view_context), this, main_loop_);
},
main_loop_, component_context_);
}
void FrameSink::CheckIfAllFramesReturned() {
if (on_frames_returned_ && !frames_outstanding_) {
// Always post, because calling back on same stack as setup of the async
// wait is a completely different thing that we can just avoid doing in the
// first place.
async::PostTask(
main_loop_->dispatcher(),
[on_frames_returned = std::move(on_frames_returned_)] { on_frames_returned(); });
FX_DCHECK(!on_frames_returned_);
}
}