summaryrefslogtreecommitdiff
path: root/chromium/media/fuchsia
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-06 12:48:11 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:33:43 +0000
commit7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (patch)
treefa14ba0ca8d2683ba2efdabd246dc9b18a1229c6 /chromium/media/fuchsia
parent79b4f909db1049fca459c07cca55af56a9b54fe3 (diff)
downloadqtwebengine-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')
-rw-r--r--chromium/media/fuchsia/audio/BUILD.gn14
-rw-r--r--chromium/media/fuchsia/audio/fake_audio_consumer.cc268
-rw-r--r--chromium/media/fuchsia/audio/fake_audio_consumer.h164
-rw-r--r--chromium/media/fuchsia/audio/fuchsia_audio_renderer.cc8
-rw-r--r--chromium/media/fuchsia/camera/BUILD.gn19
-rw-r--r--chromium/media/fuchsia/camera/fake_fuchsia_camera.cc517
-rw-r--r--chromium/media/fuchsia/camera/fake_fuchsia_camera.h194
-rw-r--r--chromium/media/fuchsia/cdm/fuchsia_decryptor.cc3
-rw-r--r--chromium/media/fuchsia/common/sysmem_buffer_pool.cc20
-rw-r--r--chromium/media/fuchsia/common/sysmem_buffer_pool.h5
-rw-r--r--chromium/media/fuchsia/common/sysmem_buffer_reader.cc71
-rw-r--r--chromium/media/fuchsia/common/sysmem_buffer_reader.h25
-rw-r--r--chromium/media/fuchsia/metrics/BUILD.gn28
-rw-r--r--chromium/media/fuchsia/metrics/DEPS3
-rw-r--r--chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder.cc139
-rw-r--r--chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder.h70
-rw-r--r--chromium/media/fuchsia/metrics/fuchsia_playback_events_recorder_test.cc202
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