blob: d35ce03d9870c20423de2a5301f53744e041ed00 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/media/sounds/soundplayer/sound_player_impl.h"
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/fdio/fd.h>
#include <lib/syslog/cpp/macros.h>
#include <fbl/unique_fd.h>
#include "src/media/sounds/soundplayer/ogg_demux.h"
#include "src/media/sounds/soundplayer/wav_reader.h"
namespace soundplayer {
SoundPlayerImpl::SoundPlayerImpl(fuchsia::media::AudioPtr audio_service,
fidl::InterfaceRequest<fuchsia::media::sounds::Player> request)
: binding_(this), audio_service_(std::move(audio_service)) {
FX_CHECK(request);
FX_CHECK(audio_service_);
audio_service_.set_error_handler([this](zx_status_t status) {
FX_LOGS(WARNING) << "SoundPlayerImpl: fuchsia.media.Audio connection closed, status " << status;
DeleteThis();
});
WhenAudioServiceIsWarm([this, request = std::move(request)]() mutable {
binding_.Bind(std::move(request));
binding_.set_error_handler([this](zx_status_t status) {
FX_LOGS(WARNING) << "SoundPlayerImpl: client connection closed, status " << status;
DeleteThis();
});
});
}
SoundPlayerImpl::~SoundPlayerImpl() {}
void SoundPlayerImpl::AddSoundFromFile(uint32_t id,
fidl::InterfaceHandle<class fuchsia::io::File> file,
AddSoundFromFileCallback callback) {
if (sounds_by_id_.find(id) != sounds_by_id_.end()) {
FX_LOGS(WARNING) << "AddSoundFromFile called with id " << id
<< " already in use, closing client connection";
DeleteThis();
return;
}
auto result = SoundFromFile(std::move(file));
if (result.is_error()) {
callback(fuchsia::media::sounds::Player_AddSoundFromFile_Result::WithErr(
static_cast<int32_t>(result.error())));
return;
}
callback(fuchsia::media::sounds::Player_AddSoundFromFile_Result::WithResponse(
fuchsia::media::sounds::Player_AddSoundFromFile_Response(
std::make_tuple(result.value()->duration().get()))));
sounds_by_id_.emplace(id, result.take_value());
}
void SoundPlayerImpl::AddSoundBuffer(uint32_t id, fuchsia::mem::Buffer buffer,
fuchsia::media::AudioStreamType stream_type) {
if (sounds_by_id_.find(id) != sounds_by_id_.end()) {
FX_LOGS(WARNING) << "AddSoundBuffer called with id " << id
<< " already in use, closing client connection";
DeleteThis();
return;
}
sounds_by_id_.emplace(id, std::make_shared<UndiscardableSound>(std::move(buffer.vmo), buffer.size,
std::move(stream_type)));
}
void SoundPlayerImpl::RemoveSound(uint32_t id) { sounds_by_id_.erase(id); }
void SoundPlayerImpl::PlaySound(uint32_t id, fuchsia::media::AudioRenderUsage usage,
PlaySoundCallback callback) {
auto iter = sounds_by_id_.find(id);
if (iter == sounds_by_id_.end()) {
callback(fuchsia::media::sounds::Player_PlaySound_Result::WithErr(
fuchsia::media::sounds::PlaySoundError::NO_SUCH_SOUND));
return;
}
fuchsia::media::AudioRendererPtr audio_renderer;
audio_service_->CreateAudioRenderer(audio_renderer.NewRequest());
auto renderer = std::make_unique<Renderer>(std::move(audio_renderer), usage);
auto renderer_raw_ptr = renderer.get();
if (renderer->PlaySound(
id, iter->second,
[this, id, renderer_raw_ptr,
callback = std::move(callback)](fuchsia::media::sounds::Player_PlaySound_Result result) {
auto iter = renderers_by_sound_id_.find(id);
if (iter != renderers_by_sound_id_.end() && iter->second == renderer_raw_ptr) {
renderers_by_sound_id_.erase(iter);
}
renderers_.erase(renderer_raw_ptr);
callback(std::move(result));
}) == ZX_OK) {
renderers_by_sound_id_.insert_or_assign(id, renderer_raw_ptr);
renderers_.emplace(renderer_raw_ptr, std::move(renderer));
} else {
callback(fuchsia::media::sounds::Player_PlaySound_Result::WithErr(
fuchsia::media::sounds::PlaySoundError::RENDERER_FAILED));
}
}
void SoundPlayerImpl::StopPlayingSound(uint32_t id) {
auto iter = renderers_by_sound_id_.find(id);
if (iter == renderers_by_sound_id_.end()) {
// The specified sound isn't playing.
return;
}
iter->second->StopPlayingSound();
}
void SoundPlayerImpl::DeleteThis() {
audio_service_.set_error_handler(nullptr);
audio_service_ = nullptr;
binding_.set_error_handler(nullptr);
binding_.Unbind();
delete this;
}
void SoundPlayerImpl::WhenAudioServiceIsWarm(fit::closure callback) {
audio_service_->CreateAudioRenderer(audio_renderer_.NewRequest());
audio_renderer_->SetPcmStreamType(
fuchsia::media::AudioStreamType{.sample_format = fuchsia::media::AudioSampleFormat::SIGNED_16,
.channels = 1,
.frames_per_second = 48000});
audio_renderer_->EnableMinLeadTimeEvents(true);
audio_renderer_.events().OnMinLeadTimeChanged =
[this, callback = std::move(callback)](int64_t min_lead_time_nsec) {
if (min_lead_time_nsec > 0) {
callback();
audio_renderer_ = nullptr;
}
};
audio_renderer_.set_error_handler([this](zx_status_t status) {
FX_LOGS(WARNING) << "fuchsia.media.AudioRenderer connection closed waiting for warm-up, "
"closing client connection.";
audio_renderer_.set_error_handler(nullptr);
audio_renderer_ = nullptr;
DeleteThis();
});
}
fpromise::result<std::shared_ptr<Sound>, zx_status_t> SoundPlayerImpl::SoundFromFile(
fidl::InterfaceHandle<fuchsia::io::File> file) {
FX_DCHECK(file);
fbl::unique_fd fd;
zx_status_t status = fdio_fd_create(file.TakeChannel().release(), fd.reset_and_get_address());
if (status != ZX_OK) {
return fpromise::error(status);
}
auto sound = std::make_shared<DiscardableSound>(std::move(fd));
{
OggDemux demux;
if (demux.Process(*sound) == ZX_OK) {
// The raw pointer to |sound| is safe here, because the sound owns the callback.
sound->SetRestoreCallback([sound = sound.get()]() {
OggDemux demux;
zx_status_t status = demux.Process(*sound);
if (status != ZX_OK) {
FX_PLOGS(WARNING, status) << "Failed to restore discarded ogg/opus sound";
}
});
return fpromise::ok(std::move(sound));
}
}
WavReader wav_reader;
status = wav_reader.Process(*sound);
if (status == ZX_OK) {
// The raw pointer to |sound| is safe here, because the sound owns the callback.
sound->SetRestoreCallback([sound = sound.get()]() {
WavReader wav_reader;
zx_status_t status = wav_reader.Process(*sound);
if (status != ZX_OK) {
FX_PLOGS(WARNING, status) << "Failed to restore discarded WAV sound";
}
});
return fpromise::ok(std::move(sound));
}
return fpromise::error(status);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// SoundPlayerImpl::Renderer
SoundPlayerImpl::Renderer::Renderer(fuchsia::media::AudioRendererPtr audio_renderer,
fuchsia::media::AudioRenderUsage usage)
: audio_renderer_(std::move(audio_renderer)) {
audio_renderer_.set_error_handler([this](zx_status_t status) {
if (locked_sound_) {
locked_sound_->Unlock();
locked_sound_ = nullptr;
}
if (play_sound_callback_) {
// This renderer may be deleted during the callback, so we move the callback to prevent
// it from being deleted out from under us.
auto callback = std::move(play_sound_callback_);
callback(fuchsia::media::sounds::Player_PlaySound_Result::WithErr(
fuchsia::media::sounds::PlaySoundError::RENDERER_FAILED));
}
});
audio_renderer_->SetUsage(usage);
}
SoundPlayerImpl::Renderer::~Renderer() {}
zx_status_t SoundPlayerImpl::Renderer::PlaySound(uint32_t id, std::shared_ptr<Sound> sound,
PlaySoundCallback completion_callback) {
audio_renderer_->SetPcmStreamType(fidl::Clone(sound->stream_type()));
zx::vmo vmo;
auto status = sound->LockForRead().duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo);
if (status != ZX_OK) {
FX_PLOGS(WARNING, status) << "Failed to duplicate VMO handle";
sound->Unlock();
return status;
}
locked_sound_ = sound;
play_sound_callback_ = std::move(completion_callback);
audio_renderer_->AddPayloadBuffer(0, std::move(vmo));
uint64_t frames_remaining = sound->frame_count();
uint64_t offset = 0;
while (frames_remaining != 0) {
uint64_t frames_to_send = std::min(
frames_remaining, static_cast<uint64_t>(fuchsia::media::MAX_FRAMES_PER_RENDERER_PACKET));
fuchsia::media::StreamPacket packet{
.pts = fuchsia::media::NO_TIMESTAMP,
.payload_buffer_id = 0,
.payload_offset = offset,
.payload_size = frames_to_send * sound->frame_size(),
.flags = 0,
.buffer_config = 0,
.stream_segment_id = 0,
};
if (frames_to_send == frames_remaining) {
audio_renderer_->SendPacket(std::move(packet), [this]() {
locked_sound_->Unlock();
locked_sound_ = nullptr;
// This renderer may be deleted during the callback, so we move the callback to prevent
// it from being deleted out from under us.
auto callback = std::move(play_sound_callback_);
callback(fuchsia::media::sounds::Player_PlaySound_Result::WithResponse(
fuchsia::media::sounds::Player_PlaySound_Response()));
});
} else {
audio_renderer_->SendPacketNoReply(std::move(packet));
}
frames_remaining -= frames_to_send;
offset += frames_to_send * sound->frame_size();
}
audio_renderer_->PlayNoReply(fuchsia::media::NO_TIMESTAMP, 0);
// No cleanup here. This renderer is deleted when the sound finishes playing or when playback
// is stopped by |StopPlayingSound|. When the renderer is deleted, the FIDL AudioRenderer is
// destroyed, so no cleanup of the AudioRenderer is required.
return ZX_OK;
}
void SoundPlayerImpl::Renderer::StopPlayingSound() {
audio_renderer_ = nullptr;
if (play_sound_callback_) {
locked_sound_->Unlock();
locked_sound_ = nullptr;
// This renderer may be deleted during the callback, so we move the callback to prevent
// it from being deleted out from under us.
auto callback = std::move(play_sound_callback_);
callback(fuchsia::media::sounds::Player_PlaySound_Result::WithErr(
fuchsia::media::sounds::PlaySoundError::STOPPED));
}
}
} // namespace soundplayer