Commit 0245a5fe authored by Sergey Ulanov's avatar Sergey Ulanov Committed by Commit Bot

[Fuchsia] Update WebEngine tests to use FuchsiaAudioRenderer

Updated PlayAudio test in web_engine_integration_tests to enable
FuchsiaAudioConsumer and verify that the audio played as expected. It
uses a fake implementation of fuchsia.media.AudioConsumer interface
that's injected to the web context.
Also updated the tests to play two files with different sample rate
to verify that the audio renderer handles mid-stream config changes.

Bug: 1027048
Change-Id: I14b0b63d9dc7444c749ee0da348b39df93c1242b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1940726Reviewed-by: default avatarWez <wez@chromium.org>
Commit-Queue: Sergey Ulanov <sergeyu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#756451}
parent d6abb31c
......@@ -315,6 +315,8 @@ test("web_engine_integration_tests") {
"//base/test:run_all_unittests",
"//fuchsia/base",
"//fuchsia/base:test_support",
"//media",
"//media/fuchsia/audio:test_support",
"//third_party/fuchsia-sdk/sdk/fidl/fuchsia.sys",
"//third_party/fuchsia-sdk/sdk/fidl/fuchsia.web",
]
......
......@@ -5,6 +5,7 @@ include_rules = [
"+gpu/command_buffer/service",
"+gpu/config/gpu_finch_features.h",
"+media/base",
"+media/fuchsia",
"+mojo/public",
"+services/service_manager",
"+third_party/widevine/cdm/widevine_cdm_common.h",
......
<html>
<body>
<video></video>
<audio></audio>
<script>
window.onload = () => {
var video = document.getElementsByTagName("video")[0]
video.onerror = function() { document.title = 'error'; }
video.onended = function() { document.title = 'ended'; }
video.src = './bear-opus.webm';
video.play();
var audio = document.querySelector('audio'),
mediaSource = new MediaSource();
audio.src = URL.createObjectURL(mediaSource);
// Play two files with different sample rate to force mid-stream
// re-initialization.
var files = ["bear-44.1kHz.webm", "bear-48kHz.webm"];
mediaSource.addEventListener('sourceopen', function() {
var index = 0;
var sourceBuffer = mediaSource.addSourceBuffer('audio/webm;codecs="vorbis"');
sourceBuffer.addEventListener('updateend', function (_) {
if (++index == files.length) {
mediaSource.endOfStream();
return;
}
loadFile(sourceBuffer, index);
});
loadFile(sourceBuffer, index);
});
function loadFile(sourceBuffer, index) {
var xhr = new XMLHttpRequest;
xhr.open('GET', './' + files[index]);
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
if (this.status != 200) {
console.error('Failed to fetch the file ' + files[index]);
return;
}
sourceBuffer.timestampOffset = index;
sourceBuffer.appendBuffer(this.response);
};
xhr.send();
}
audio.play();
audio.onended = function() { document.title = 'ended'; }
</script>
</body>
</html>
......@@ -10,9 +10,12 @@
#include <zircon/processargs.h>
#include "base/command_line.h"
#include "base/files/file_enumerator.h"
#include "base/fuchsia/default_context.h"
#include "base/fuchsia/file_utils.h"
#include "base/fuchsia/filtered_service_directory.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/scoped_service_binding.h"
#include "base/macros.h"
#include "base/path_service.h"
#include "base/test/task_environment.h"
......@@ -22,6 +25,8 @@
#include "fuchsia/base/result_receiver.h"
#include "fuchsia/base/test_devtools_list_fetcher.h"
#include "fuchsia/base/test_navigation_listener.h"
#include "media/base/media_switches.h"
#include "media/fuchsia/audio/fake_audio_consumer.h"
#include "net/http/http_request_headers.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
......@@ -35,6 +40,16 @@ constexpr char kValidUserAgentProductAndVersion[] = "TestProduct/dev.12345";
constexpr char kInvalidUserAgentProduct[] = "Test/Product";
constexpr char kInvalidUserAgentVersion[] = "dev/12345";
fuchsia::web::ContentDirectoryProvider CreateTestDataDirectoryProvider() {
fuchsia::web::ContentDirectoryProvider provider;
provider.set_name("testdata");
base::FilePath pkg_path;
CHECK(base::PathService::Get(base::DIR_ASSETS, &pkg_path));
provider.set_directory(base::fuchsia::OpenDirectory(
pkg_path.AppendASCII("fuchsia/engine/test/data")));
return provider;
}
} // namespace
class WebEngineIntegrationTest : public testing::Test {
......@@ -50,9 +65,10 @@ class WebEngineIntegrationTest : public testing::Test {
ASSERT_TRUE(embedded_test_server_.Start());
}
void StartWebEngine() {
web_context_provider_ =
cr_fuchsia::ConnectContextProvider(web_engine_controller_.NewRequest());
void StartWebEngine(base::CommandLine command_line =
base::CommandLine(base::CommandLine::NO_PROGRAM)) {
web_context_provider_ = cr_fuchsia::ConnectContextProvider(
web_engine_controller_.NewRequest(), std::move(command_line));
web_context_provider_.set_error_handler(
[](zx_status_t status) { ADD_FAILURE(); });
}
......@@ -391,16 +407,39 @@ TEST_F(WebEngineIntegrationTest, ContentDirectoryProvider) {
}
TEST_F(WebEngineIntegrationTest, PlayAudio) {
StartWebEngine();
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
command_line.AppendSwitch(switches::kEnableFuchsiaAudioConsumer);
StartWebEngine(std::move(command_line));
// Use a FilteredServiceDirectory in order to inject a fake AudioConsumer
// service.
base::fuchsia::FilteredServiceDirectory filtered_services(
base::fuchsia::ComponentContextForCurrentProcess()->svc().get());
fidl::InterfaceHandle<fuchsia::io::Directory> svc_dir;
filtered_services.ConnectClient(svc_dir.NewRequest());
// Push all services from /svc to the service directory.
base::FileEnumerator file_enum(base::FilePath("/svc"), false,
base::FileEnumerator::FILES);
for (auto file = file_enum.Next(); !file.empty(); file = file_enum.Next()) {
filtered_services.AddService(file.BaseName().value().c_str());
}
fuchsia::web::CreateContextParams create_params =
DefaultContextParamsWithTestData();
auto features = fuchsia::web::ContextFeatureFlags::AUDIO;
if (create_params.has_features())
features |= create_params.features();
create_params.set_features(features);
// Publish fake AudioConsumer in the service directory.
media::FakeAudioConsumerService fake_audio_consumer_service(
filtered_services.outgoing_directory()->GetOrCreateDirectory("svc"));
// Create Context and Frame.
fuchsia::web::CreateContextParams create_params;
create_params.set_service_directory(std::move(svc_dir));
create_params.mutable_content_directories()->push_back(
CreateTestDataDirectoryProvider());
create_params.set_features(fuchsia::web::ContextFeatureFlags::AUDIO);
CreateContextAndFrame(std::move(create_params));
static uint16_t kTestMediaSessionId = 43;
frame_->SetMediaSessionId(kTestMediaSessionId);
fuchsia::web::LoadUrlParams load_url_params;
// |was_user_activated| needs to be set to ensure the page can play audio
......@@ -412,6 +451,17 @@ TEST_F(WebEngineIntegrationTest, PlayAudio) {
"fuchsia-dir://testdata/play_audio.html"));
navigation_listener_->RunUntilTitleEquals("ended");
ASSERT_EQ(fake_audio_consumer_service.num_instances(), 1U);
auto pos = fake_audio_consumer_service.instance(0)->GetMediaPosition();
EXPECT_GT(pos, base::TimeDelta::FromSecondsD(2.0));
EXPECT_LT(pos, base::TimeDelta::FromSecondsD(2.5));
EXPECT_EQ(fake_audio_consumer_service.instance(0)->session_id(),
kTestMediaSessionId);
EXPECT_EQ(fake_audio_consumer_service.instance(0)->volume(), 1.0);
EXPECT_FALSE(fake_audio_consumer_service.instance(0)->is_muted());
}
void WebEngineIntegrationTest::RunPermissionTest(bool grant) {
......
......@@ -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
......
// 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
// 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_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment