diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-06 12:48:11 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:33:43 +0000 |
commit | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (patch) | |
tree | fa14ba0ca8d2683ba2efdabd246dc9b18a1229c6 /chromium/media/fuchsia | |
parent | 79b4f909db1049fca459c07cca55af56a9b54fe3 (diff) | |
download | qtwebengine-chromium-7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3.tar.gz |
BASELINE: Update Chromium to 84.0.4147.141
Change-Id: Ib85eb4cfa1cbe2b2b81e5022c8cad5c493969535
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/media/fuchsia')
17 files changed, 1734 insertions, 16 deletions
diff --git a/chromium/media/fuchsia/audio/BUILD.gn b/chromium/media/fuchsia/audio/BUILD.gn index 039276f85ff..23c6d3db808 100644 --- a/chromium/media/fuchsia/audio/BUILD.gn +++ b/chromium/media/fuchsia/audio/BUILD.gn @@ -21,6 +21,20 @@ source_set("audio") { ] } +source_set("test_support") { + testonly = true + public_deps = [ + "//base", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.media", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.media.audio", + "//third_party/fuchsia-sdk/sdk/pkg/fidl_cpp", + ] + sources = [ + "fake_audio_consumer.cc", + "fake_audio_consumer.h", + ] +} + source_set("unittests") { testonly = true diff --git a/chromium/media/fuchsia/audio/fake_audio_consumer.cc b/chromium/media/fuchsia/audio/fake_audio_consumer.cc new file mode 100644 index 00000000000..f8368980332 --- /dev/null +++ b/chromium/media/fuchsia/audio/fake_audio_consumer.cc @@ -0,0 +1,268 @@ +// Copyright 2020 The Chromium 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 "media/fuchsia/audio/fake_audio_consumer.h" + +#include <lib/vfs/cpp/pseudo_dir.h> +#include <lib/vfs/cpp/service.h> + +#include "base/fuchsia/fuchsia_logging.h" + +namespace media { + +namespace { + +// Lead time range returned from WatchStatus(); +constexpr base::TimeDelta kMinLeadTime = base::TimeDelta::FromMilliseconds(100); +constexpr base::TimeDelta kMaxLeadTime = base::TimeDelta::FromMilliseconds(500); + +} // namespace + +// Buffering delay. +constexpr base::TimeDelta kBufferDelay = base::TimeDelta::FromMilliseconds(30); + +FakeAudioConsumer::FakeAudioConsumer( + uint64_t session_id, + fidl::InterfaceRequest<fuchsia::media::AudioConsumer> request) + : session_id_(session_id), + audio_consumer_binding_(this), + stream_sink_binding_(this), + volume_control_binding_(this) { + audio_consumer_binding_.Bind(std::move(request)); +} + +FakeAudioConsumer::~FakeAudioConsumer() = default; + +base::TimeDelta FakeAudioConsumer::GetMediaPosition() { + base::TimeDelta result = media_pos_; + if (state_ == State::kPlaying) { + result += (base::TimeTicks::Now() - reference_time_) * media_delta_ / + reference_delta_; + } + return result; +} + +void FakeAudioConsumer::CreateStreamSink( + std::vector<zx::vmo> buffers, + fuchsia::media::AudioStreamType stream_type, + std::unique_ptr<fuchsia::media::Compression> compression, + fidl::InterfaceRequest<fuchsia::media::StreamSink> stream_sink_request) { + num_buffers_ = buffers.size(); + CHECK_GT(num_buffers_, 0U); + stream_sink_binding_.Bind(std::move(stream_sink_request)); +} + +void FakeAudioConsumer::Start(fuchsia::media::AudioConsumerStartFlags flags, + int64_t reference_time, + int64_t media_time) { + CHECK(state_ == State::kStopped); + + if (reference_time != fuchsia::media::NO_TIMESTAMP) { + reference_time_ = base::TimeTicks::FromZxTime(reference_time); + } else { + reference_time_ = base::TimeTicks::Now() + kBufferDelay; + } + + if (media_time != fuchsia::media::NO_TIMESTAMP) { + media_pos_ = base::TimeDelta::FromZxDuration(media_time); + } else { + if (media_pos_.is_min()) { + media_pos_ = base::TimeDelta(); + } + } + + state_ = State::kPlaying; + + OnStatusUpdate(); + ScheduleNextStreamPosUpdate(); +} + +void FakeAudioConsumer::Stop() { + CHECK(state_ != State::kPlaying); + + state_ = State::kStopped; + OnStatusUpdate(); +} + +void FakeAudioConsumer::WatchStatus(WatchStatusCallback callback) { + status_callback_ = std::move(callback); + if (have_status_update_) { + CallStatusCallback(); + } +} + +void FakeAudioConsumer::SetRate(float rate) { + // Playback rate must not be negative. + CHECK_GE(rate, 0.0); + + // Update reference position. + auto now = base::TimeTicks::Now(); + media_pos_ = + media_pos_ + (now - reference_time_) * media_delta_ / reference_delta_; + reference_time_ = now; + + // Approximate the rate as n/1000; + reference_delta_ = 1000; + media_delta_ = static_cast<int>(rate * 1000.0); + + OnStatusUpdate(); + + if (update_timer_.IsRunning()) + update_timer_.Reset(); + ScheduleNextStreamPosUpdate(); +} + +void FakeAudioConsumer::BindVolumeControl( + fidl::InterfaceRequest<fuchsia::media::audio::VolumeControl> + volume_control_request) { + volume_control_binding_.Bind(std::move(volume_control_request)); +} + +void FakeAudioConsumer::SendPacket(fuchsia::media::StreamPacket stream_packet, + SendPacketCallback callback) { + CHECK_LT(stream_packet.payload_buffer_id, num_buffers_); + + Packet packet; + if (stream_packet.pts == fuchsia::media::NO_TIMESTAMP) { + if (media_pos_.is_min()) { + packet.pts = base::TimeDelta(); + } else { + packet.pts = media_pos_; + } + } else { + packet.pts = base::TimeDelta::FromZxDuration(stream_packet.pts); + } + pending_packets_.push_back(std::move(packet)); + + callback(); + + ScheduleNextStreamPosUpdate(); +} + +void FakeAudioConsumer::SendPacketNoReply(fuchsia::media::StreamPacket packet) { + NOTREACHED(); +} + +void FakeAudioConsumer::EndOfStream() { + Packet packet; + packet.is_eos = true; + pending_packets_.push_back(std::move(packet)); +} + +void FakeAudioConsumer::DiscardAllPackets(DiscardAllPacketsCallback callback) { + DiscardAllPacketsNoReply(); + std::move(callback)(); +} + +void FakeAudioConsumer::DiscardAllPacketsNoReply() { + pending_packets_.clear(); +} + +void FakeAudioConsumer::SetVolume(float volume) { + volume_ = volume; +} + +void FakeAudioConsumer::SetMute(bool mute) { + is_muted_ = mute; +} + +void FakeAudioConsumer::NotImplemented_(const std::string& name) { + LOG(FATAL) << "Reached non-implemented " << name; +} + +void FakeAudioConsumer::ScheduleNextStreamPosUpdate() { + if (pending_packets_.empty() || update_timer_.IsRunning() || + media_delta_ == 0 || state_ != State::kPlaying) { + return; + } + base::TimeDelta delay; + if (!pending_packets_.front().is_eos) { + auto next_packet_time = + reference_time_ + (pending_packets_.front().pts - media_pos_) * + reference_delta_ / media_delta_; + delay = (next_packet_time - base::TimeTicks::Now()); + } + update_timer_.Start(FROM_HERE, delay, + base::BindOnce(&FakeAudioConsumer::UpdateStreamPos, + base::Unretained(this))); +} + +void FakeAudioConsumer::UpdateStreamPos() { + if (state_ != State::kPlaying) + return; + + auto now = base::TimeTicks::Now(); + auto new_media_pos = + media_pos_ + (now - reference_time_) * media_delta_ / reference_delta_; + + // Drop all packets with PTS before the current position. + while (!pending_packets_.empty()) { + if (!pending_packets_.front().is_eos && + pending_packets_.front().pts > new_media_pos) { + break; + } + + Packet packet = pending_packets_.front(); + pending_packets_.pop_front(); + + if (packet.is_eos) { + // No data should be submitted after EOS. + CHECK(pending_packets_.empty()); + audio_consumer_binding_.events().OnEndOfStream(); + state_ = State::kEndOfStream; + media_pos_ = new_media_pos; + reference_time_ = now; + } + } + + ScheduleNextStreamPosUpdate(); +} + +void FakeAudioConsumer::OnStatusUpdate() { + have_status_update_ = true; + if (status_callback_) { + CallStatusCallback(); + } +} + +void FakeAudioConsumer::CallStatusCallback() { + DCHECK(status_callback_); + DCHECK(have_status_update_); + + fuchsia::media::AudioConsumerStatus status; + if (state_ == State::kPlaying) { + fuchsia::media::TimelineFunction timeline; + timeline.reference_time = reference_time_.ToZxTime(); + timeline.subject_time = media_pos_.ToZxDuration(); + timeline.reference_delta = reference_delta_; + timeline.subject_delta = media_delta_; + + status.set_presentation_timeline(std::move(timeline)); + } + + status.set_min_lead_time(kMinLeadTime.ToZxDuration()); + status.set_max_lead_time(kMaxLeadTime.ToZxDuration()); + + have_status_update_ = false; + std::move(status_callback_)(std::move(status)); + status_callback_ = {}; +} + +FakeAudioConsumerService::FakeAudioConsumerService(vfs::PseudoDir* pseudo_dir) + : binding_(pseudo_dir, this) {} + +FakeAudioConsumerService::~FakeAudioConsumerService() {} + +void FakeAudioConsumerService::CreateAudioConsumer( + uint64_t session_id, + fidl::InterfaceRequest<fuchsia::media::AudioConsumer> request) { + audio_consumers_.push_back( + std::make_unique<FakeAudioConsumer>(session_id, std::move(request))); +} + +void FakeAudioConsumerService::NotImplemented_(const std::string& name) { + LOG(FATAL) << "Reached non-implemented " << name; +} + +} // namespace media
\ No newline at end of file diff --git a/chromium/media/fuchsia/audio/fake_audio_consumer.h b/chromium/media/fuchsia/audio/fake_audio_consumer.h new file mode 100644 index 00000000000..56615269cbd --- /dev/null +++ b/chromium/media/fuchsia/audio/fake_audio_consumer.h @@ -0,0 +1,164 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_AUDIO_FAKE_AUDIO_CONSUMER_H_ +#define MEDIA_FUCHSIA_AUDIO_FAKE_AUDIO_CONSUMER_H_ + +#include <fuchsia/media/audio/cpp/fidl.h> +#include <fuchsia/media/audio/cpp/fidl_test_base.h> +#include <fuchsia/media/cpp/fidl.h> +#include <fuchsia/media/cpp/fidl_test_base.h> +#include <lib/fidl/cpp/binding.h> + +#include <vector> + +#include "base/fuchsia/scoped_service_binding.h" +#include "base/time/time.h" +#include "base/timer/timer.h" + +namespace vfs { +class PseudoDir; +} // namespace vfs + +namespace media { + +// Fake implementation of fuchsia::media::AudioConsumer interface. Used for +// tests. +class FakeAudioConsumer + : public fuchsia::media::testing::AudioConsumer_TestBase, + public fuchsia::media::testing::StreamSink_TestBase, + public fuchsia::media::audio::testing::VolumeControl_TestBase { + public: + FakeAudioConsumer( + uint64_t session_id, + fidl::InterfaceRequest<fuchsia::media::AudioConsumer> request); + ~FakeAudioConsumer() final; + + FakeAudioConsumer(const FakeAudioConsumer&) = delete; + FakeAudioConsumer& operator=(const FakeAudioConsumer&) = delete; + + uint64_t session_id() { return session_id_; } + float volume() const { return volume_; } + bool is_muted() const { return is_muted_; } + + base::TimeDelta GetMediaPosition(); + + private: + enum class State { + kStopped, + kPlaying, + kEndOfStream, + }; + + struct Packet { + base::TimeDelta pts; + bool is_eos = false; + }; + + // fuchsia::media::AudioConsumer interface; + void CreateStreamSink( + std::vector<zx::vmo> buffers, + fuchsia::media::AudioStreamType stream_type, + std::unique_ptr<fuchsia::media::Compression> compression, + fidl::InterfaceRequest<fuchsia::media::StreamSink> stream_sink_request) + final; + void Start(fuchsia::media::AudioConsumerStartFlags flags, + int64_t reference_time, + int64_t media_time) final; + void Stop() final; + void WatchStatus(WatchStatusCallback callback) final; + void SetRate(float rate) final; + void BindVolumeControl( + fidl::InterfaceRequest<fuchsia::media::audio::VolumeControl> + volume_control_request) final; + + // fuchsia::media::StreamSink interface. + void SendPacket(fuchsia::media::StreamPacket packet, + SendPacketCallback callback) final; + void SendPacketNoReply(fuchsia::media::StreamPacket packet) final; + void EndOfStream() final; + void DiscardAllPackets(DiscardAllPacketsCallback callback) final; + void DiscardAllPacketsNoReply() final; + + // fuchsia::media::audio::VolumeControl interface. + void SetVolume(float volume) final; + void SetMute(bool mute) final; + + // Not-implemented handler for _TestBase parents. + void NotImplemented_(const std::string& name) final; + + void ScheduleNextStreamPosUpdate(); + + // Updates stream position and drops old packets from the stream. + void UpdateStreamPos(); + + void OnStatusUpdate(); + void CallStatusCallback(); + + const uint64_t session_id_; + + fidl::Binding<fuchsia::media::AudioConsumer> audio_consumer_binding_; + fidl::Binding<fuchsia::media::StreamSink> stream_sink_binding_; + fidl::Binding<fuchsia::media::audio::VolumeControl> volume_control_binding_; + + size_t num_buffers_ = 0; + + State state_ = State::kStopped; + + bool have_status_update_ = true; + WatchStatusCallback status_callback_; + + base::TimeTicks reference_time_; + + // Numerator and denumerator for current playback rate. + uint32_t media_delta_ = 1; + uint32_t reference_delta_ = 1; + + // Last known media position. Min value indicates that the stream position + // hasn't been set. If stream is playing then value corresponds to + // |reference_time_|. + base::TimeDelta media_pos_ = base::TimeDelta::Min(); + + std::list<Packet> pending_packets_; + + // Timer to call UpdateStreamPos() for the next packet. + base::OneShotTimer update_timer_; + + float volume_ = 1.0; + bool is_muted_ = false; +}; + +class FakeAudioConsumerService + : public fuchsia::media::testing::SessionAudioConsumerFactory_TestBase { + public: + explicit FakeAudioConsumerService(vfs::PseudoDir* pseudo_dir); + ~FakeAudioConsumerService() final; + + FakeAudioConsumerService(const FakeAudioConsumerService&) = delete; + FakeAudioConsumerService& operator=(const FakeAudioConsumerService&) = delete; + + size_t num_instances() { return audio_consumers_.size(); } + FakeAudioConsumer* instance(size_t index) { + return audio_consumers_[index].get(); + } + + private: + // fuchsia::media::SessionAudioConsumerFactory implementation. + void CreateAudioConsumer(uint64_t session_id, + fidl::InterfaceRequest<fuchsia::media::AudioConsumer> + audio_consumer_request) final; + + // Not-implemented handler for SessionAudioConsumerFactory_TestBase. + void NotImplemented_(const std::string& name) final; + + base::fuchsia::ScopedServiceBinding< + fuchsia::media::SessionAudioConsumerFactory> + binding_; + + std::vector<std::unique_ptr<FakeAudioConsumer>> audio_consumers_; +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_AUDIO_FAKE_AUDIO_CONSUMER_H_ diff --git a/chromium/media/fuchsia/audio/fuchsia_audio_renderer.cc b/chromium/media/fuchsia/audio/fuchsia_audio_renderer.cc index 9c1c262e547..b4f6476516c 100644 --- a/chromium/media/fuchsia/audio/fuchsia_audio_renderer.cc +++ b/chromium/media/fuchsia/audio/fuchsia_audio_renderer.cc @@ -254,7 +254,6 @@ void FuchsiaAudioRenderer::StartTicking() { SetPlaybackState(PlaybackState::kStarting); } - audio_consumer_->Start(flags, fuchsia::media::NO_TIMESTAMP, media_pos.ToZxDuration()); } @@ -528,6 +527,13 @@ void FuchsiaAudioRenderer::OnDemuxerStreamReadDone( OnStreamSendDone(buffer_index); }); + // AudioConsumer doesn't report exact time when the data is decoded, but it's + // safe to report it as decoded right away since the packet is expected to be + // decoded soon after AudioConsumer receives it. + PipelineStatistics stats; + stats.audio_bytes_decoded = buffer->data_size(); + client_->OnStatisticsUpdate(stats); + last_packet_timestamp_ = buffer->timestamp(); ScheduleReadDemuxerStream(); diff --git a/chromium/media/fuchsia/camera/BUILD.gn b/chromium/media/fuchsia/camera/BUILD.gn new file mode 100644 index 00000000000..c8ec86f4a93 --- /dev/null +++ b/chromium/media/fuchsia/camera/BUILD.gn @@ -0,0 +1,19 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("test_support") { + testonly = true + public_deps = [ + "//base", + "//testing/gtest", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.camera3", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.sysmem", + "//third_party/fuchsia-sdk/sdk/pkg/fidl_cpp", + "//ui/gfx/geometry", + ] + sources = [ + "fake_fuchsia_camera.cc", + "fake_fuchsia_camera.h", + ] +} diff --git a/chromium/media/fuchsia/camera/fake_fuchsia_camera.cc b/chromium/media/fuchsia/camera/fake_fuchsia_camera.cc new file mode 100644 index 00000000000..921a1b6cfb1 --- /dev/null +++ b/chromium/media/fuchsia/camera/fake_fuchsia_camera.cc @@ -0,0 +1,517 @@ +// Copyright 2020 The Chromium 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 "media/fuchsia/camera/fake_fuchsia_camera.h" + +#include <fuchsia/sysmem/cpp/fidl.h> +#include <lib/sys/cpp/component_context.h> + +#include "base/fuchsia/default_context.h" +#include "base/memory/platform_shared_memory_region.h" +#include "base/memory/writable_shared_memory_region.h" +#include "base/message_loop/message_loop_current.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +namespace { + +constexpr uint64_t kDefaultFakeDeviceId = 42; + +constexpr uint8_t kYPlaneSalt = 1; +constexpr uint8_t kUPlaneSalt = 2; +constexpr uint8_t kVPlaneSalt = 3; + +uint8_t GetTestFrameValue(gfx::Size size, int x, int y, uint8_t salt) { + return static_cast<uint8_t>(y + x * size.height() + salt); +} + +// Fills one plane of a test frame. |data| points at the location of the pixel +// (0, 0). |orientation| specifies frame orientation transformation that will be +// applied on the receiving end, so this function applies _reverse_ of the +// |orientation| transformation. +void FillPlane(uint8_t* data, + gfx::Size size, + int x_step, + int y_step, + fuchsia::camera3::Orientation orientation, + uint8_t salt) { + // First flip X axis for flipped orientation. + if (orientation == fuchsia::camera3::Orientation::UP_FLIPPED || + orientation == fuchsia::camera3::Orientation::DOWN_FLIPPED || + orientation == fuchsia::camera3::Orientation::RIGHT_FLIPPED || + orientation == fuchsia::camera3::Orientation::LEFT_FLIPPED) { + // Move the origin to the top right corner and flip the X axis. + data += (size.width() - 1) * x_step; + x_step = -x_step; + } + + switch (orientation) { + case fuchsia::camera3::Orientation::UP: + case fuchsia::camera3::Orientation::UP_FLIPPED: + break; + + case fuchsia::camera3::Orientation::DOWN: + case fuchsia::camera3::Orientation::DOWN_FLIPPED: + // Move |data| to point to the bottom right corner and reverse direction + // of both axes. + data += (size.width() - 1) * x_step + (size.height() - 1) * y_step; + x_step = -x_step; + y_step = -y_step; + break; + + case fuchsia::camera3::Orientation::LEFT: + case fuchsia::camera3::Orientation::LEFT_FLIPPED: + // Rotate 90 degrees clockwise by moving |data| to point to the right top + // corner, swapping the axes and reversing direction of the Y axis. + data += (size.width() - 1) * x_step; + size = gfx::Size(size.height(), size.width()); + std::swap(x_step, y_step); + y_step = -y_step; + break; + + case fuchsia::camera3::Orientation::RIGHT: + case fuchsia::camera3::Orientation::RIGHT_FLIPPED: + // Rotate 90 degrees counter-clockwise by moving |data| to point to the + // bottom left corner, swapping the axes and reversing direction of the X + // axis. + data += (size.height() - 1) * y_step; + size = gfx::Size(size.height(), size.width()); + std::swap(x_step, y_step); + x_step = -x_step; + break; + } + + for (int y = 0; y < size.height(); ++y) { + for (int x = 0; x < size.width(); ++x) { + data[x * x_step + y * y_step] = GetTestFrameValue(size, x, y, salt); + } + } +} + +void ValidatePlane(const uint8_t* data, + gfx::Size size, + size_t x_step, + size_t y_step, + uint8_t salt) { + for (int y = 0; y < size.height(); ++y) { + for (int x = 0; x < size.width(); ++x) { + SCOPED_TRACE(testing::Message() << "x=" << x << " y=" << y); + EXPECT_EQ(data[x * x_step + y * y_step], + GetTestFrameValue(size, x, y, salt)); + } + } +} + +} // namespace + +// static +const gfx::Size FakeCameraStream::kMaxFrameSize = gfx::Size(100, 60); +// static +const gfx::Size FakeCameraStream::kDefaultFrameSize = gfx::Size(60, 40); + +// static +void FakeCameraStream::ValidateFrameData(const uint8_t* data, + gfx::Size size, + uint8_t salt) { + const uint8_t* y_plane = data; + { + SCOPED_TRACE("Y plane"); + ValidatePlane(y_plane, size, 1, size.width(), salt + kYPlaneSalt); + } + + gfx::Size uv_size(size.width() / 2, size.height() / 2); + const uint8_t* u_plane = y_plane + size.width() * size.height(); + { + SCOPED_TRACE("U plane"); + ValidatePlane(u_plane, uv_size, 1, uv_size.width(), salt + kUPlaneSalt); + } + + const uint8_t* v_plane = u_plane + uv_size.width() * uv_size.height(); + { + SCOPED_TRACE("V plane"); + ValidatePlane(v_plane, uv_size, 1, uv_size.width(), salt + kVPlaneSalt); + } +} + +struct FakeCameraStream::Buffer { + explicit Buffer(base::WritableSharedMemoryMapping mapping) + : mapping(std::move(mapping)), + release_fence_watch_controller(FROM_HERE) {} + + base::WritableSharedMemoryMapping mapping; + + // Frame is used by the client when the |release_fence| is not null. + zx::eventpair release_fence; + + base::MessagePumpForIO::ZxHandleWatchController + release_fence_watch_controller; +}; + +FakeCameraStream::FakeCameraStream() : binding_(this) {} +FakeCameraStream::~FakeCameraStream() = default; + +void FakeCameraStream::Bind( + fidl::InterfaceRequest<fuchsia::camera3::Stream> request) { + binding_.Bind(std::move(request)); +} + +void FakeCameraStream::SetFakeResolution(gfx::Size resolution) { + resolution_ = resolution; + resolution_update_ = + fuchsia::math::Size{resolution_.width(), resolution_.height()}; + SendResolution(); +} + +void FakeCameraStream::SetFakeOrientation( + fuchsia::camera3::Orientation orientation) { + orientation_ = orientation; + orientation_update_ = orientation; + SendOrientation(); +} + +bool FakeCameraStream::WaitBuffersAllocated() { + EXPECT_FALSE(wait_buffers_allocated_run_loop_); + + if (!buffers_.empty()) + return true; + + wait_buffers_allocated_run_loop_.emplace(); + wait_buffers_allocated_run_loop_->Run(); + wait_buffers_allocated_run_loop_.reset(); + + return !buffers_.empty(); +} + +bool FakeCameraStream::WaitFreeBuffer() { + EXPECT_FALSE(wait_free_buffer_run_loop_); + + if (num_used_buffers_ < buffers_.size()) + return true; + + wait_free_buffer_run_loop_.emplace(); + wait_free_buffer_run_loop_->Run(); + wait_free_buffer_run_loop_.reset(); + + return num_used_buffers_ < buffers_.size(); +} + +void FakeCameraStream::ProduceFrame(base::TimeTicks timestamp, uint8_t salt) { + ASSERT_LT(num_used_buffers_, buffers_.size()); + ASSERT_FALSE(next_frame_); + + size_t index = buffers_.size(); + for (size_t i = 0; i < buffers_.size(); ++i) { + if (!buffers_[i]->release_fence) { + index = i; + break; + } + } + EXPECT_LT(index, buffers_.size()); + + auto* buffer = buffers_[index].get(); + + gfx::Size coded_size((resolution_.width() + 1) & ~1, + (resolution_.height() + 1) & ~1); + + // Fill Y plane. + uint8_t* y_plane = reinterpret_cast<uint8_t*>(buffer->mapping.memory()); + size_t stride = kMaxFrameSize.width(); + FillPlane(y_plane, coded_size, /*x_step=*/1, /*y_step=*/stride, orientation_, + salt + kYPlaneSalt); + + // Fill UV plane. + gfx::Size uv_size(coded_size.width() / 2, coded_size.height() / 2); + uint8_t* uv_plane = y_plane + kMaxFrameSize.width() * kMaxFrameSize.height(); + FillPlane(uv_plane, uv_size, /*x_step=*/2, /*y_step=*/stride, orientation_, + salt + kUPlaneSalt); + FillPlane(uv_plane + 1, uv_size, /*x_step=*/2, /*y_step=*/stride, + orientation_, salt + kVPlaneSalt); + + // Create FrameInfo. + fuchsia::camera3::FrameInfo frame; + frame.frame_counter = frame_counter_++; + frame.buffer_index = 0; + frame.timestamp = timestamp.ToZxTime(); + EXPECT_EQ( + zx::eventpair::create(0u, &frame.release_fence, &buffer->release_fence), + ZX_OK); + + // Watch release fence to get notified when the frame is released. + base::MessageLoopCurrentForIO::Get()->WatchZxHandle( + buffer->release_fence.get(), /*persistent=*/false, + ZX_EVENTPAIR_PEER_CLOSED, &buffer->release_fence_watch_controller, this); + + num_used_buffers_++; + next_frame_ = std::move(frame); + SendNextFrame(); +} + +void FakeCameraStream::WatchResolution(WatchResolutionCallback callback) { + EXPECT_FALSE(watch_resolution_callback_); + watch_resolution_callback_ = std::move(callback); + SendResolution(); +} + +void FakeCameraStream::WatchOrientation(WatchOrientationCallback callback) { + EXPECT_FALSE(watch_orientation_callback_); + watch_orientation_callback_ = std::move(callback); + SendOrientation(); +} + +void FakeCameraStream::SetBufferCollection( + fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> + token_handle) { + EXPECT_TRUE(token_handle); + + // Drop old buffers. + buffers_.clear(); + if (buffer_collection_) { + buffer_collection_->Close(); + buffer_collection_.Unbind(); + } + + // Use a SyncPtr to be able to wait for Sync() synchronously. + fuchsia::sysmem::BufferCollectionTokenSyncPtr token; + token.Bind(std::move(token_handle)); + + // Duplicate the token to access from the stream. + fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> local_token; + zx_status_t status = + token->Duplicate(/*rights_attenuation_mask=*/0, local_token.NewRequest()); + EXPECT_EQ(status, ZX_OK); + + status = token->Sync(); + EXPECT_EQ(status, ZX_OK); + + // Return the token back to the client. + new_buffer_collection_token_ = token.Unbind(); + SendBufferCollection(); + + // Initialize the new collection using |local_token|. + auto allocator = base::fuchsia::ComponentContextForCurrentProcess() + ->svc() + ->Connect<fuchsia::sysmem::Allocator>(); + + allocator->BindSharedCollection(std::move(local_token), + buffer_collection_.NewRequest()); + EXPECT_EQ(status, ZX_OK); + + buffer_collection_.set_error_handler( + fit::bind_member(this, &FakeCameraStream::OnBufferCollectionError)); + + fuchsia::sysmem::BufferCollectionConstraints constraints; + constraints.usage.cpu = + fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageWrite; + + // The client is expected to request buffers it may need. We don't need to + // reserve any for the server side. + constraints.min_buffer_count_for_camping = 0; + + // Initialize image format. + constraints.image_format_constraints_count = 1; + constraints.image_format_constraints[0].pixel_format.type = + fuchsia::sysmem::PixelFormatType::NV12; + constraints.image_format_constraints[0].color_spaces_count = 1; + constraints.image_format_constraints[0].color_space[0].type = + fuchsia::sysmem::ColorSpaceType::REC601_NTSC; + constraints.image_format_constraints[0].required_max_coded_width = + kMaxFrameSize.width(); + constraints.image_format_constraints[0].required_max_coded_height = + kMaxFrameSize.height(); + + buffer_collection_->SetConstraints(/*has_constraints=*/true, + std::move(constraints)); + buffer_collection_->WaitForBuffersAllocated( + fit::bind_member(this, &FakeCameraStream::OnBufferCollectionAllocated)); +} + +void FakeCameraStream::WatchBufferCollection( + WatchBufferCollectionCallback callback) { + EXPECT_FALSE(watch_buffer_collection_callback_); + watch_buffer_collection_callback_ = std::move(callback); + SendBufferCollection(); +} + +void FakeCameraStream::GetNextFrame(GetNextFrameCallback callback) { + EXPECT_FALSE(get_next_frame_callback_); + get_next_frame_callback_ = std::move(callback); + SendNextFrame(); +} + +void FakeCameraStream::NotImplemented_(const std::string& name) { + ADD_FAILURE() << "NotImplemented_: " << name; +} + +void FakeCameraStream::OnBufferCollectionError(zx_status_t status) { + ADD_FAILURE() << "BufferCollection failed."; + if (wait_buffers_allocated_run_loop_) + wait_buffers_allocated_run_loop_->Quit(); + if (wait_free_buffer_run_loop_) + wait_free_buffer_run_loop_->Quit(); +} + +void FakeCameraStream::OnBufferCollectionAllocated( + zx_status_t status, + fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info) { + if (status != ZX_OK) { + OnBufferCollectionError(status); + return; + } + + EXPECT_TRUE(buffers_.empty()); + EXPECT_TRUE(buffer_collection_info.settings.has_image_format_constraints); + EXPECT_EQ(buffer_collection_info.settings.image_format_constraints + .pixel_format.type, + fuchsia::sysmem::PixelFormatType::NV12); + + size_t buffer_size = + buffer_collection_info.settings.buffer_settings.size_bytes; + for (size_t i = 0; i < buffer_collection_info.buffer_count; ++i) { + auto& buffer = buffer_collection_info.buffers[i]; + EXPECT_EQ(buffer.vmo_usable_start, 0U); + auto region = base::WritableSharedMemoryRegion::Deserialize( + base::subtle::PlatformSharedMemoryRegion::Take( + std::move(buffer.vmo), + base::subtle::PlatformSharedMemoryRegion::Mode::kWritable, + buffer_size, base::UnguessableToken::Create())); + auto mapping = region.Map(); + EXPECT_TRUE(mapping.IsValid()); + buffers_.push_back(std::make_unique<Buffer>(std::move(mapping))); + } + + if (wait_buffers_allocated_run_loop_) + wait_buffers_allocated_run_loop_->Quit(); +} + +void FakeCameraStream::SendResolution() { + if (!watch_resolution_callback_ || !resolution_update_) + return; + watch_resolution_callback_(resolution_update_.value()); + watch_resolution_callback_ = {}; + resolution_update_.reset(); +} + +void FakeCameraStream::SendOrientation() { + if (!watch_orientation_callback_ || !orientation_update_) + return; + watch_orientation_callback_(orientation_update_.value()); + watch_orientation_callback_ = {}; + orientation_update_.reset(); +} + +void FakeCameraStream::SendBufferCollection() { + if (!watch_buffer_collection_callback_ || !new_buffer_collection_token_) + return; + watch_buffer_collection_callback_( + std::move(new_buffer_collection_token_.value())); + watch_buffer_collection_callback_ = {}; + new_buffer_collection_token_.reset(); +} + +void FakeCameraStream::SendNextFrame() { + if (!get_next_frame_callback_ || !next_frame_) + return; + get_next_frame_callback_(std::move(next_frame_.value())); + get_next_frame_callback_ = {}; + next_frame_.reset(); +} + +void FakeCameraStream::OnZxHandleSignalled(zx_handle_t handle, + zx_signals_t signals) { + EXPECT_EQ(signals, ZX_EVENTPAIR_PEER_CLOSED); + + // Find the buffer that corresponds to the |handle|. + size_t index = buffers_.size(); + for (size_t i = 0; i < buffers_.size(); ++i) { + if (buffers_[i]->release_fence.get() == handle) { + index = i; + break; + } + } + ASSERT_LT(index, buffers_.size()); + buffers_[index]->release_fence = {}; + buffers_[index]->release_fence_watch_controller.StopWatchingZxHandle(); + num_used_buffers_--; + + if (wait_free_buffer_run_loop_) + wait_free_buffer_run_loop_->Quit(); +} +FakeCameraDevice::FakeCameraDevice(FakeCameraStream* stream) + : binding_(this), stream_(stream) {} + +FakeCameraDevice::~FakeCameraDevice() = default; + +void FakeCameraDevice::Bind( + fidl::InterfaceRequest<fuchsia::camera3::Device> request) { + binding_.Bind(std::move(request)); +} + +void FakeCameraDevice::GetIdentifier(GetIdentifierCallback callback) { + callback("Fake Camera"); +} + +void FakeCameraDevice::GetConfigurations(GetConfigurationsCallback callback) { + std::vector<fuchsia::camera3::Configuration> configurations(1); + configurations[0].streams.resize(1); + configurations[0].streams[0].frame_rate.numerator = 30; + configurations[0].streams[0].frame_rate.denominator = 1; + configurations[0].streams[0].image_format.pixel_format.type = + fuchsia::sysmem::PixelFormatType::NV12; + configurations[0].streams[0].image_format.coded_width = 640; + configurations[0].streams[0].image_format.coded_height = 480; + configurations[0].streams[0].image_format.bytes_per_row = 640; + callback(std::move(configurations)); +} + +void FakeCameraDevice::ConnectToStream( + uint32_t index, + fidl::InterfaceRequest<fuchsia::camera3::Stream> request) { + EXPECT_EQ(index, 0U); + stream_->Bind(std::move(request)); +} + +void FakeCameraDevice::NotImplemented_(const std::string& name) { + ADD_FAILURE() << "NotImplemented_: " << name; +} + +FakeCameraDeviceWatcher::FakeCameraDeviceWatcher( + sys::OutgoingDirectory* outgoing_directory) { + outgoing_directory->AddPublicService<fuchsia::camera3::DeviceWatcher>( + [this](fidl::InterfaceRequest<fuchsia::camera3::DeviceWatcher> request) { + bindings_.AddBinding(std::make_unique<Client>(&device_), + std::move(request)); + }); +} + +FakeCameraDeviceWatcher::~FakeCameraDeviceWatcher() = default; + +FakeCameraDeviceWatcher::Client::Client(FakeCameraDevice* device) + : device_(device) {} +FakeCameraDeviceWatcher::Client::~Client() {} + +void FakeCameraDeviceWatcher::Client::WatchDevices( + WatchDevicesCallback callback) { + if (devices_sent_) + return; + + std::vector<fuchsia::camera3::WatchDevicesEvent> events(1); + events[0].set_added(kDefaultFakeDeviceId); + callback(std::move(events)); + + devices_sent_ = true; +} + +void FakeCameraDeviceWatcher::Client::ConnectToDevice( + uint64_t id, + fidl::InterfaceRequest<fuchsia::camera3::Device> request) { + if (id == kDefaultFakeDeviceId) + device_->Bind(std::move(request)); +} + +void FakeCameraDeviceWatcher::Client::NotImplemented_(const std::string& name) { + ADD_FAILURE() << "NotImplemented_: " << name; +} + +} // namespace media
\ No newline at end of file diff --git a/chromium/media/fuchsia/camera/fake_fuchsia_camera.h b/chromium/media/fuchsia/camera/fake_fuchsia_camera.h new file mode 100644 index 00000000000..016c68d0fc1 --- /dev/null +++ b/chromium/media/fuchsia/camera/fake_fuchsia_camera.h @@ -0,0 +1,194 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_CAMERA_FAKE_FUCHSIA_CAMERA_H_ +#define MEDIA_FUCHSIA_CAMERA_FAKE_FUCHSIA_CAMERA_H_ + +#include <fuchsia/camera3/cpp/fidl.h> +#include <fuchsia/camera3/cpp/fidl_test_base.h> +#include <lib/fidl/cpp/binding.h> +#include <lib/fidl/cpp/binding_set.h> +#include <lib/sys/cpp/outgoing_directory.h> + +#include <vector> + +#include "base/message_loop/message_pump_for_io.h" +#include "base/optional.h" +#include "base/run_loop.h" +#include "ui/gfx/geometry/size.h" + +namespace media { + +class FakeCameraStream : public fuchsia::camera3::testing::Stream_TestBase, + public base::MessagePumpForIO::ZxHandleWatcher { + public: + static const gfx::Size kMaxFrameSize; + static const gfx::Size kDefaultFrameSize; + + // Verifies that the I420 image stored at |data| matches the frame produced + // by ProduceFrame(). + static void ValidateFrameData(const uint8_t* data, + gfx::Size size, + uint8_t salt); + + FakeCameraStream(); + ~FakeCameraStream() final; + + FakeCameraStream(const FakeCameraStream&) = delete; + FakeCameraStream& operator=(const FakeCameraStream&) = delete; + + void Bind(fidl::InterfaceRequest<fuchsia::camera3::Stream> request); + + void SetFakeResolution(gfx::Size resolution); + void SetFakeOrientation(fuchsia::camera3::Orientation orientation); + + // Waits for the buffer collection to be allocated. Returns true if the buffer + // collection was allocated successfully. + bool WaitBuffersAllocated(); + + // Waits until there is at least one free buffer that can be used for the next + // frame. + bool WaitFreeBuffer(); + + void ProduceFrame(base::TimeTicks timestamp, uint8_t salt); + + private: + struct Buffer; + + // fuchsia::camera3::Stream implementation. + void WatchResolution(WatchResolutionCallback callback) final; + void WatchOrientation(WatchOrientationCallback callback) final; + void SetBufferCollection( + fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> + token_handle) final; + void WatchBufferCollection(WatchBufferCollectionCallback callback) final; + void GetNextFrame(GetNextFrameCallback callback) final; + + // fuchsia::camera3::testing::Stream_TestBase override. + void NotImplemented_(const std::string& name) override; + + void OnBufferCollectionError(zx_status_t status); + + void OnBufferCollectionAllocated( + zx_status_t status, + fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info); + + // Calls callback for the pending WatchResolution() if the call is pending and + // resolution has been updated. + void SendResolution(); + + // Calls callback for the pending WatchOrientation() if the call is pending + // and orientation has been updated. + void SendOrientation(); + + // Calls callback for the pending WatchBufferCollection() if we have a new + // token and the call is pending. + void SendBufferCollection(); + + // Calls callback for the pending GetNextFrame() if we have a new frame and + // the call is pending. + void SendNextFrame(); + + // ZxHandleWatcher interface. Used to wait for frame release_fences to get + // notified when the client releases a buffer. + void OnZxHandleSignalled(zx_handle_t handle, zx_signals_t signals) final; + + fidl::Binding<fuchsia::camera3::Stream> binding_; + + gfx::Size resolution_ = kDefaultFrameSize; + fuchsia::camera3::Orientation orientation_ = + fuchsia::camera3::Orientation::UP; + + base::Optional<fuchsia::math::Size> resolution_update_ = fuchsia::math::Size{ + kDefaultFrameSize.width(), kDefaultFrameSize.height()}; + WatchResolutionCallback watch_resolution_callback_; + + base::Optional<fuchsia::camera3::Orientation> orientation_update_ = + fuchsia::camera3::Orientation::UP; + WatchOrientationCallback watch_orientation_callback_; + + base::Optional<fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>> + new_buffer_collection_token_; + WatchBufferCollectionCallback watch_buffer_collection_callback_; + + base::Optional<fuchsia::camera3::FrameInfo> next_frame_; + GetNextFrameCallback get_next_frame_callback_; + + fuchsia::sysmem::BufferCollectionPtr buffer_collection_; + + base::Optional<base::RunLoop> wait_buffers_allocated_run_loop_; + base::Optional<base::RunLoop> wait_free_buffer_run_loop_; + + std::vector<std::unique_ptr<Buffer>> buffers_; + size_t num_used_buffers_ = 0; + + size_t frame_counter_ = 0; +}; + +class FakeCameraDevice : public fuchsia::camera3::testing::Device_TestBase { + public: + explicit FakeCameraDevice(FakeCameraStream* stream); + ~FakeCameraDevice() final; + + FakeCameraDevice(const FakeCameraDevice&) = delete; + FakeCameraDevice& operator=(const FakeCameraDevice&) = delete; + + void Bind(fidl::InterfaceRequest<fuchsia::camera3::Device> request); + + private: + // fuchsia::camera3::Device implementation. + void GetIdentifier(GetIdentifierCallback callback) final; + void GetConfigurations(GetConfigurationsCallback callback) final; + void ConnectToStream( + uint32_t index, + fidl::InterfaceRequest<fuchsia::camera3::Stream> request) final; + + // fuchsia::camera3::testing::Device_TestBase override. + void NotImplemented_(const std::string& name) override; + + fidl::Binding<fuchsia::camera3::Device> binding_; + FakeCameraStream* const stream_; +}; + +class FakeCameraDeviceWatcher { + public: + explicit FakeCameraDeviceWatcher(sys::OutgoingDirectory* outgoing_directory); + ~FakeCameraDeviceWatcher(); + + FakeCameraDeviceWatcher(const FakeCameraDeviceWatcher&) = delete; + FakeCameraDeviceWatcher& operator=(const FakeCameraDeviceWatcher&) = delete; + + private: + class Client : public fuchsia::camera3::testing::DeviceWatcher_TestBase { + public: + explicit Client(FakeCameraDevice* device); + ~Client() final; + + Client(const Client&) = delete; + Client& operator=(const Client&) = delete; + + // fuchsia::camera3::testing::DeviceWatcher_TestBase override. + void NotImplemented_(const std::string& name) final; + + // fuchsia::camera3::DeviceWatcher implementation. + void WatchDevices(WatchDevicesCallback callback) final; + void ConnectToDevice( + uint64_t id, + fidl::InterfaceRequest<fuchsia::camera3::Device> request) final; + + private: + bool devices_sent_ = false; + FakeCameraDevice* const device_; + }; + + fidl::BindingSet<fuchsia::camera3::DeviceWatcher, std::unique_ptr<Client>> + bindings_; + + FakeCameraStream stream_; + FakeCameraDevice device_{&stream_}; +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_CAMERA_FAKE_FUCHSIA_CAMERA_H_ diff --git a/chromium/media/fuchsia/cdm/fuchsia_decryptor.cc b/chromium/media/fuchsia/cdm/fuchsia_decryptor.cc index b36fcfb878c..e10b2a1a863 100644 --- a/chromium/media/fuchsia/cdm/fuchsia_decryptor.cc +++ b/chromium/media/fuchsia/cdm/fuchsia_decryptor.cc @@ -4,9 +4,10 @@ #include "media/fuchsia/cdm/fuchsia_decryptor.h" +#include "base/check.h" #include "base/fuchsia/fuchsia_logging.h" #include "base/location.h" -#include "base/logging.h" +#include "base/notreached.h" #include "base/threading/thread_task_runner_handle.h" #include "media/base/decoder_buffer.h" #include "media/base/video_frame.h" diff --git a/chromium/media/fuchsia/common/sysmem_buffer_pool.cc b/chromium/media/fuchsia/common/sysmem_buffer_pool.cc index 6c30ad8a0df..94c1a1f43d9 100644 --- a/chromium/media/fuchsia/common/sysmem_buffer_pool.cc +++ b/chromium/media/fuchsia/common/sysmem_buffer_pool.cc @@ -140,13 +140,18 @@ BufferAllocator::BufferAllocator() { BufferAllocator::~BufferAllocator() = default; +fuchsia::sysmem::BufferCollectionTokenPtr BufferAllocator::CreateNewToken() { + fuchsia::sysmem::BufferCollectionTokenPtr collection_token; + allocator_->AllocateSharedCollection(collection_token.NewRequest()); + return collection_token; +} + std::unique_ptr<SysmemBufferPool::Creator> BufferAllocator::MakeBufferPoolCreator(size_t num_of_tokens) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); // Create a new sysmem buffer collection token for the allocated buffers. - fuchsia::sysmem::BufferCollectionTokenPtr collection_token; - allocator_->AllocateSharedCollection(collection_token.NewRequest()); + fuchsia::sysmem::BufferCollectionTokenPtr collection_token = CreateNewToken(); // Create collection token for sharing with other components. std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens; @@ -166,4 +171,15 @@ BufferAllocator::MakeBufferPoolCreator(size_t num_of_tokens) { std::move(buffer_collection), std::move(shared_tokens)); } +std::unique_ptr<SysmemBufferPool::Creator> +BufferAllocator::MakeBufferPoolCreatorFromToken( + fuchsia::sysmem::BufferCollectionTokenPtr token) { + fuchsia::sysmem::BufferCollectionPtr buffer_collection; + allocator_->BindSharedCollection(std::move(token), + buffer_collection.NewRequest()); + return std::make_unique<SysmemBufferPool::Creator>( + std::move(buffer_collection), + std::vector<fuchsia::sysmem::BufferCollectionTokenPtr>{}); +} + } // namespace media diff --git a/chromium/media/fuchsia/common/sysmem_buffer_pool.h b/chromium/media/fuchsia/common/sysmem_buffer_pool.h index c4c6ceabb54..ec657ed370d 100644 --- a/chromium/media/fuchsia/common/sysmem_buffer_pool.h +++ b/chromium/media/fuchsia/common/sysmem_buffer_pool.h @@ -98,9 +98,14 @@ class BufferAllocator { BufferAllocator(); ~BufferAllocator(); + fuchsia::sysmem::BufferCollectionTokenPtr CreateNewToken(); + std::unique_ptr<SysmemBufferPool::Creator> MakeBufferPoolCreator( size_t num_shared_token); + std::unique_ptr<SysmemBufferPool::Creator> MakeBufferPoolCreatorFromToken( + fuchsia::sysmem::BufferCollectionTokenPtr token); + // TODO(sergeyu): Update FuchsiaVideoDecoder to use SysmemBufferPool and // remove this function. fuchsia::sysmem::Allocator* raw() { return allocator_.get(); } diff --git a/chromium/media/fuchsia/common/sysmem_buffer_reader.cc b/chromium/media/fuchsia/common/sysmem_buffer_reader.cc index 8667a877398..406096a2617 100644 --- a/chromium/media/fuchsia/common/sysmem_buffer_reader.cc +++ b/chromium/media/fuchsia/common/sysmem_buffer_reader.cc @@ -17,27 +17,74 @@ SysmemBufferReader::~SysmemBufferReader() = default; bool SysmemBufferReader::Read(size_t index, size_t offset, base::span<uint8_t> data) { - DCHECK_LT(index, collection_info_.buffer_count); - const fuchsia::sysmem::BufferMemorySettings& settings = - collection_info_.settings.buffer_settings; - fuchsia::sysmem::VmoBuffer& buffer = collection_info_.buffers[index]; - DCHECK_LE(buffer.vmo_usable_start + offset + data.size(), - settings.size_bytes); + DCHECK_LT(index, num_buffers()); + DCHECK_LE(offset + data.size(), + collection_info_.settings.buffer_settings.size_bytes); + const fuchsia::sysmem::VmoBuffer& buffer = collection_info_.buffers[index]; size_t vmo_offset = buffer.vmo_usable_start + offset; - // Invalidate cache. - if (settings.coherency_domain == fuchsia::sysmem::CoherencyDomain::RAM) { - zx_status_t status = buffer.vmo.op_range( - ZX_VMO_OP_CACHE_CLEAN_INVALIDATE, vmo_offset, data.size(), nullptr, 0); - ZX_LOG_IF(ERROR, status != ZX_OK, status) << "Fail to invalidate cache"; - } + InvalidateCacheIfNecessary(buffer.vmo, vmo_offset, data.size()); zx_status_t status = buffer.vmo.read(data.data(), vmo_offset, data.size()); + ZX_LOG_IF(ERROR, status != ZX_OK, status) << "Fail to read"; return status == ZX_OK; } +base::span<const uint8_t> SysmemBufferReader::GetMappingForBuffer( + size_t index) { + if (mappings_.empty()) + mappings_.resize(num_buffers()); + + DCHECK_LT(index, mappings_.size()); + + const fuchsia::sysmem::BufferMemorySettings& settings = + collection_info_.settings.buffer_settings; + fuchsia::sysmem::VmoBuffer& buffer = collection_info_.buffers[index]; + + auto& mapping = mappings_[index]; + size_t buffer_start = buffer.vmo_usable_start; + + if (!mapping.IsValid()) { + size_t mapping_size = buffer_start + settings.size_bytes; + auto region = base::ReadOnlySharedMemoryRegion::Deserialize( + base::subtle::PlatformSharedMemoryRegion::Take( + std::move(buffer.vmo), + base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly, + mapping_size, base::UnguessableToken::Create())); + + mapping = region.Map(); + + // Return the VMO handle back to buffer_. + buffer.vmo = base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization( + std::move(region)) + .PassPlatformHandle(); + } + + if (!mapping.IsValid()) { + DLOG(WARNING) << "Failed to map VMO returned by sysmem"; + return {}; + } + + InvalidateCacheIfNecessary(buffer.vmo, buffer_start, settings.size_bytes); + + return base::make_span( + reinterpret_cast<const uint8_t*>(mapping.memory()) + buffer_start, + settings.size_bytes); +} + +void SysmemBufferReader::InvalidateCacheIfNecessary(const zx::vmo& vmo, + size_t offset, + size_t size) { + if (collection_info_.settings.buffer_settings.coherency_domain == + fuchsia::sysmem::CoherencyDomain::RAM) { + zx_status_t status = vmo.op_range(ZX_VMO_OP_CACHE_CLEAN_INVALIDATE, offset, + size, nullptr, 0); + ZX_LOG_IF(ERROR, status != ZX_OK, status) << "Fail to invalidate cache"; + } +} + // static std::unique_ptr<SysmemBufferReader> SysmemBufferReader::Create( fuchsia::sysmem::BufferCollectionInfo_2 info) { diff --git a/chromium/media/fuchsia/common/sysmem_buffer_reader.h b/chromium/media/fuchsia/common/sysmem_buffer_reader.h index 0c314162e68..bc9727facd0 100644 --- a/chromium/media/fuchsia/common/sysmem_buffer_reader.h +++ b/chromium/media/fuchsia/common/sysmem_buffer_reader.h @@ -11,6 +11,7 @@ #include <memory> #include "base/containers/span.h" +#include "base/memory/read_only_shared_memory_region.h" namespace media { @@ -26,11 +27,35 @@ class SysmemBufferReader { explicit SysmemBufferReader(fuchsia::sysmem::BufferCollectionInfo_2 info); ~SysmemBufferReader(); + size_t num_buffers() const { return collection_info_.buffer_count; } + + const fuchsia::sysmem::SingleBufferSettings& buffer_settings() { + return collection_info_.settings; + } + // Read the buffer content at |index| into |data|, starting from |offset|. bool Read(size_t index, size_t offset, base::span<uint8_t> data); + // Returns a span for the memory-mapping of the buffer with the specified + // |index|, invalidating the CPU cache for the specified buffer in the sysmem + // collection if necessary. Buffers are mapped lazily and remain mapped for + // the lifetime of SysmemBufferReader. Should be called every time before + // accessing the mapping to ensure that the CPU cache is invalidated for + // buffers with RAM coherency. + base::span<const uint8_t> GetMappingForBuffer(size_t index); + private: + // Invalidates CPU cache for the specified range of the specified vmo in + // case the collection was allocated with RAM coherency. No-op for collections + // with CPU coherency. Called from Read() and GetMapping() to ensure clients + // get up-to-date buffer content in case the buffer was updated by other + // participants directly in RAM (bypassing CPU cache). + void InvalidateCacheIfNecessary(const zx::vmo& buffer, + size_t offset, + size_t size); + fuchsia::sysmem::BufferCollectionInfo_2 collection_info_; + std::vector<base::ReadOnlySharedMemoryMapping> mappings_; DISALLOW_COPY_AND_ASSIGN(SysmemBufferReader); }; diff --git a/chromium/media/fuchsia/metrics/BUILD.gn b/chromium/media/fuchsia/metrics/BUILD.gn new file mode 100644 index 00000000000..ee6952ba233 --- /dev/null +++ b/chromium/media/fuchsia/metrics/BUILD.gn @@ -0,0 +1,28 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("metrics") { + sources = [ + "fuchsia_playback_events_recorder.cc", + "fuchsia_playback_events_recorder.h", + ] + deps = [ + "//media/mojo/mojom", + "//mojo/public/cpp/bindings", + ] +} + +source_set("unittests") { + testonly = true + + deps = [ + ":metrics", + "//base", + "//base/test:test_support", + "//media", + "//testing/gtest", + ] + + sources = [ "fuchsia_playback_events_recorder_test.cc" ] +} diff --git a/chromium/media/fuchsia/metrics/DEPS b/chromium/media/fuchsia/metrics/DEPS new file mode 100644 index 00000000000..ef8ad28d9d4 --- /dev/null +++ b/chromium/media/fuchsia/metrics/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+mojo/public", +] diff --git a/chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder.cc b/chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder.cc new file mode 100644 index 00000000000..d883b08b38a --- /dev/null +++ b/chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder.cc @@ -0,0 +1,139 @@ +// Copyright 2020 The Chromium 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 "media/fuchsia/metrics/fuchsia_playback_events_recorder.h" + +#include "base/metrics/user_metrics.h" +#include "base/strings/stringprintf.h" +#include "mojo/public/cpp/bindings/self_owned_receiver.h" + +namespace media { + +namespace { + +void RecordEventWithValueAt(const char* name, + int64_t value, + base::TimeTicks time) { + base::RecordComputedActionAt( + base::StringPrintf("WebEngine.Media.%s:%ld", name, value), time); +} + +void RecordEventWithValue(const char* name, int64_t value) { + RecordEventWithValueAt(name, value, base::TimeTicks::Now()); +} + +constexpr base::TimeDelta kBitrateReportPeriod = + base::TimeDelta::FromSeconds(5); + +} // namespace + +FuchsiaPlaybackEventsRecorder::BitrateEstimator::BitrateEstimator() {} +FuchsiaPlaybackEventsRecorder::BitrateEstimator::~BitrateEstimator() {} + +void FuchsiaPlaybackEventsRecorder::BitrateEstimator::Update( + const PipelineStatistics& stats) { + base::TimeTicks now = base::TimeTicks::Now(); + + // The code below trusts that |stats| are valid even though they came from an + // untrusted process. That's accepable because the stats are used only to + // record metrics. + if (last_stats_) { + time_elapsed_ += now - last_stats_time_; + audio_bytes_ += + stats.audio_bytes_decoded - last_stats_->audio_bytes_decoded; + video_bytes_ += + stats.video_bytes_decoded - last_stats_->video_bytes_decoded; + if (time_elapsed_ >= kBitrateReportPeriod) { + size_t audio_bitrate_kbps = + 8 * audio_bytes_ / time_elapsed_.InMilliseconds(); + RecordEventWithValueAt("AudioBitrate", audio_bitrate_kbps, now); + + size_t video_bitrate_kbps = + 8 * video_bytes_ / time_elapsed_.InMilliseconds(); + RecordEventWithValueAt("VideoBitrate", video_bitrate_kbps, now); + + time_elapsed_ = base::TimeDelta(); + audio_bytes_ = 0; + video_bytes_ = 0; + } + } + + last_stats_ = stats; + last_stats_time_ = now; +} + +void FuchsiaPlaybackEventsRecorder::BitrateEstimator::OnPause() { + last_stats_ = {}; +} + +// static +void FuchsiaPlaybackEventsRecorder::Create( + mojo::PendingReceiver<mojom::PlaybackEventsRecorder> receiver) { + mojo::MakeSelfOwnedReceiver(std::make_unique<FuchsiaPlaybackEventsRecorder>(), + std::move(receiver)); +} + +FuchsiaPlaybackEventsRecorder::FuchsiaPlaybackEventsRecorder() = default; +FuchsiaPlaybackEventsRecorder::~FuchsiaPlaybackEventsRecorder() = default; + +void FuchsiaPlaybackEventsRecorder::OnPlaying() { + base::RecordComputedAction("WebEngine.Media.Playing"); +} + +void FuchsiaPlaybackEventsRecorder::OnPaused() { + base::RecordComputedAction("WebEngine.Media.Pause"); + bitrate_estimator_.OnPause(); +} + +void FuchsiaPlaybackEventsRecorder::OnSeeking() { + buffering_state_ = BufferingState::kInitialBuffering; +} + +void FuchsiaPlaybackEventsRecorder::OnEnded() { + base::RecordComputedAction("WebEngine.Media.Ended"); +} + +void FuchsiaPlaybackEventsRecorder::OnBuffering() { + DCHECK(buffering_state_ == BufferingState::kBuffered); + + buffering_start_time_ = base::TimeTicks::Now(); + buffering_state_ = BufferingState::kBuffering; + + bitrate_estimator_.OnPause(); +} + +void FuchsiaPlaybackEventsRecorder::OnBufferingComplete() { + auto now = base::TimeTicks::Now(); + + if (buffering_state_ == BufferingState::kBuffering) { + base::TimeDelta time_between_buffering = + buffering_start_time_ - last_buffering_end_time_; + RecordEventWithValueAt("PlayTimeBeforeAutoPause", + time_between_buffering.InMilliseconds(), now); + + base::TimeDelta buffering_user_time = now - buffering_start_time_; + RecordEventWithValueAt("AutoPauseTime", + buffering_user_time.InMilliseconds(), now); + } + + buffering_state_ = BufferingState::kBuffered; + last_buffering_end_time_ = now; +} + +void FuchsiaPlaybackEventsRecorder::OnError(PipelineStatus status) { + RecordEventWithValue("Error", status); +} + +void FuchsiaPlaybackEventsRecorder::OnNaturalSizeChanged( + const gfx::Size& size) { + base::RecordComputedAction(base::StringPrintf( + "WebEngine.Media.VideoResolution:%dx%d", size.width(), size.height())); +} + +void FuchsiaPlaybackEventsRecorder::OnPipelineStatistics( + const PipelineStatistics& stats) { + bitrate_estimator_.Update(stats); +} + +} // namespace media diff --git a/chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder.h b/chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder.h new file mode 100644 index 00000000000..2686bafec9e --- /dev/null +++ b/chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder.h @@ -0,0 +1,70 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_METRICS_FUCHSIA_PLAYBACK_EVENTS_RECORDER_H_ +#define MEDIA_FUCHSIA_METRICS_FUCHSIA_PLAYBACK_EVENTS_RECORDER_H_ + +#include "media/mojo/mojom/playback_events_recorder.mojom.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "mojo/public/cpp/bindings/remote.h" + +namespace media { + +class FuchsiaPlaybackEventsRecorder : public mojom::PlaybackEventsRecorder { + public: + static void Create( + mojo::PendingReceiver<mojom::PlaybackEventsRecorder> receiver); + + FuchsiaPlaybackEventsRecorder(); + ~FuchsiaPlaybackEventsRecorder() final; + + FuchsiaPlaybackEventsRecorder(const FuchsiaPlaybackEventsRecorder&) = delete; + FuchsiaPlaybackEventsRecorder& operator=( + const FuchsiaPlaybackEventsRecorder&) = delete; + + // mojom::PlaybackEventsRecorder implementation. + void OnPlaying() final; + void OnPaused() final; + void OnSeeking() final; + void OnEnded() final; + void OnBuffering() final; + void OnBufferingComplete() final; + void OnError(PipelineStatus status) final; + void OnNaturalSizeChanged(const gfx::Size& size) final; + void OnPipelineStatistics(const PipelineStatistics& stats) final; + + private: + class BitrateEstimator { + public: + BitrateEstimator(); + ~BitrateEstimator(); + + void Update(const PipelineStatistics& stats); + void OnPause(); + + private: + base::TimeDelta time_elapsed_; + size_t audio_bytes_ = 0; + size_t video_bytes_ = 0; + + base::Optional<PipelineStatistics> last_stats_; + base::TimeTicks last_stats_time_; + }; + + enum class BufferingState { + kInitialBuffering, + kBuffering, + kBuffered, + }; + + BufferingState buffering_state_ = BufferingState::kInitialBuffering; + base::TimeTicks buffering_start_time_; + base::TimeTicks last_buffering_end_time_; + + BitrateEstimator bitrate_estimator_; +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_METRICS_FUCHSIA_PLAYBACK_EVENTS_RECORDER_H_
\ No newline at end of file diff --git a/chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder_test.cc b/chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder_test.cc new file mode 100644 index 00000000000..a18704575a2 --- /dev/null +++ b/chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder_test.cc @@ -0,0 +1,202 @@ +// Copyright 2020 The Chromium 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 "media/fuchsia/metrics/fuchsia_playback_events_recorder.h" + +#include "base/metrics/user_metrics.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/test/task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +constexpr base::TimeDelta kSecond = base::TimeDelta::FromSeconds(1); + +class FuchsiaPlaybackEventsRecorderTest : public testing::Test { + public: + FuchsiaPlaybackEventsRecorderTest() + : task_environment_(base::test::TaskEnvironment::MainThreadType::IO, + base::test::TaskEnvironment::TimeSource::MOCK_TIME) { + time_base_ = base::TimeTicks::Now(); + + base::SetRecordActionTaskRunner( + task_environment_.GetMainThreadTaskRunner()); + action_callback_ = base::BindRepeating( + &FuchsiaPlaybackEventsRecorderTest::OnAction, base::Unretained(this)); + base::AddActionCallback(action_callback_); + } + + ~FuchsiaPlaybackEventsRecorderTest() override { + base::RemoveActionCallback(action_callback_); + } + + protected: + struct Event { + base::TimeTicks time; + std::string name; + + bool operator==(const Event& other) const { + return time == other.time && name == other.name; + } + }; + + void OnAction(const std::string& name, base::TimeTicks time) { + recorded_events_.push_back({time, name}); + } + + void ExpectEvents(const std::vector<Event>& expected) { + EXPECT_EQ(recorded_events_.size(), expected.size()); + size_t end = std::min(recorded_events_.size(), expected.size()); + for (size_t i = 0; i < end; ++i) { + SCOPED_TRACE(i); + EXPECT_EQ(recorded_events_[i].time, expected[i].time); + EXPECT_EQ(recorded_events_[i].name, expected[i].name); + } + } + + base::test::TaskEnvironment task_environment_; + + base::SimpleTestTickClock test_clock_; + base::TimeTicks time_base_; + + base::ActionCallback action_callback_; + FuchsiaPlaybackEventsRecorder recorder_; + std::vector<Event> recorded_events_; +}; + +TEST_F(FuchsiaPlaybackEventsRecorderTest, PlayPause) { + recorder_.OnNaturalSizeChanged(gfx::Size(640, 480)); + recorder_.OnPlaying(); + task_environment_.AdvanceClock(2 * kSecond); + recorder_.OnPaused(); + + ExpectEvents({ + {time_base_, "WebEngine.Media.VideoResolution:640x480"}, + {time_base_, "WebEngine.Media.Playing"}, + {time_base_ + 2 * kSecond, "WebEngine.Media.Pause"}, + }); +} + +TEST_F(FuchsiaPlaybackEventsRecorderTest, Error) { + recorder_.OnPlaying(); + task_environment_.AdvanceClock(2 * kSecond); + recorder_.OnError(PIPELINE_ERROR_DECODE); + + ExpectEvents({ + {time_base_, "WebEngine.Media.Playing"}, + {time_base_ + 2 * kSecond, "WebEngine.Media.Error:3"}, + }); +} + +TEST_F(FuchsiaPlaybackEventsRecorderTest, Buffering) { + recorder_.OnPlaying(); + recorder_.OnBufferingComplete(); + task_environment_.AdvanceClock(2 * kSecond); + recorder_.OnBuffering(); + task_environment_.AdvanceClock(3 * kSecond); + recorder_.OnBufferingComplete(); + + ExpectEvents({ + {time_base_, "WebEngine.Media.Playing"}, + {time_base_ + 5 * kSecond, + "WebEngine.Media.PlayTimeBeforeAutoPause:2000"}, + {time_base_ + 5 * kSecond, "WebEngine.Media.AutoPauseTime:3000"}, + }); +} + +TEST_F(FuchsiaPlaybackEventsRecorderTest, Bitrate) { + recorder_.OnPlaying(); + recorder_.OnBufferingComplete(); + + PipelineStatistics stats; + recorder_.OnPipelineStatistics(stats); + + for (int i = 0; i < 5; ++i) { + stats.audio_bytes_decoded += 5000; + stats.video_bytes_decoded += 10000; + + task_environment_.AdvanceClock(kSecond); + recorder_.OnPipelineStatistics(stats); + } + + ExpectEvents({ + {time_base_, "WebEngine.Media.Playing"}, + {time_base_ + 5 * kSecond, "WebEngine.Media.AudioBitrate:40"}, + {time_base_ + 5 * kSecond, "WebEngine.Media.VideoBitrate:80"}, + }); +} + +TEST_F(FuchsiaPlaybackEventsRecorderTest, BitrateAfterPause) { + recorder_.OnPlaying(); + recorder_.OnBufferingComplete(); + + PipelineStatistics stats; + recorder_.OnPipelineStatistics(stats); + + for (int i = 0; i < 3; ++i) { + stats.audio_bytes_decoded += 5000; + stats.video_bytes_decoded += 10000; + + task_environment_.AdvanceClock(kSecond); + recorder_.OnPipelineStatistics(stats); + } + + recorder_.OnPaused(); + task_environment_.AdvanceClock(10 * kSecond); + recorder_.OnPlaying(); + + for (int i = 0; i < 3; ++i) { + stats.audio_bytes_decoded += 5000; + stats.video_bytes_decoded += 10000; + + task_environment_.AdvanceClock(kSecond); + recorder_.OnPipelineStatistics(stats); + } + + ExpectEvents({ + {time_base_, "WebEngine.Media.Playing"}, + {time_base_ + 3 * kSecond, "WebEngine.Media.Pause"}, + {time_base_ + 13 * kSecond, "WebEngine.Media.Playing"}, + {time_base_ + 16 * kSecond, "WebEngine.Media.AudioBitrate:40"}, + {time_base_ + 16 * kSecond, "WebEngine.Media.VideoBitrate:80"}, + }); +} + +TEST_F(FuchsiaPlaybackEventsRecorderTest, BitrateAfterBuffering) { + recorder_.OnPlaying(); + recorder_.OnBufferingComplete(); + + PipelineStatistics stats; + recorder_.OnPipelineStatistics(stats); + + for (int i = 0; i < 3; ++i) { + stats.audio_bytes_decoded += 5000; + stats.video_bytes_decoded += 10000; + + task_environment_.AdvanceClock(kSecond); + recorder_.OnPipelineStatistics(stats); + } + + recorder_.OnBuffering(); + task_environment_.AdvanceClock(10 * kSecond); + recorder_.OnBufferingComplete(); + + for (int i = 0; i < 3; ++i) { + stats.audio_bytes_decoded += 5000; + stats.video_bytes_decoded += 10000; + + task_environment_.AdvanceClock(kSecond); + recorder_.OnPipelineStatistics(stats); + } + + ExpectEvents({ + {time_base_, "WebEngine.Media.Playing"}, + {time_base_ + 13 * kSecond, + "WebEngine.Media.PlayTimeBeforeAutoPause:3000"}, + {time_base_ + 13 * kSecond, "WebEngine.Media.AutoPauseTime:10000"}, + {time_base_ + 16 * kSecond, "WebEngine.Media.AudioBitrate:40"}, + {time_base_ + 16 * kSecond, "WebEngine.Media.VideoBitrate:80"}, + }); +} +} // namespace media
\ No newline at end of file |