| // Copyright 2019 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/playback/mediaplayer/audio_consumer_impl.h" |
| |
| #include <fuchsia/media/cpp/fidl.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <lib/fidl/cpp/clone.h> |
| #include <lib/fidl/cpp/optional.h> |
| #include <lib/fidl/cpp/type_converter.h> |
| #include <lib/fit/function.h> |
| #include <lib/media/cpp/type_converters.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/vfs/cpp/pseudo_file.h> |
| #include <lib/zx/clock.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| #include <sstream> |
| |
| #include <fs/pseudo_file.h> |
| |
| #include "lib/fidl/cpp/interface_request.h" |
| #include "lib/fit/result.h" |
| #include "lib/media/cpp/timeline_rate.h" |
| #include "src/media/playback/mediaplayer/core/renderer_sink_segment.h" |
| #include "src/media/playback/mediaplayer/fidl/fidl_audio_renderer.h" |
| #include "src/media/playback/mediaplayer/fidl/fidl_reader.h" |
| #include "src/media/playback/mediaplayer/fidl/fidl_type_conversions.h" |
| #include "src/media/playback/mediaplayer/fidl/fidl_video_renderer.h" |
| #include "src/media/playback/mediaplayer/fidl/simple_stream_sink_impl.h" |
| #include "src/media/playback/mediaplayer/graph/formatting.h" |
| #include "src/media/playback/mediaplayer/graph/thread_priority.h" |
| #include "src/media/playback/mediaplayer/graph/types/stream_type.h" |
| #include "src/media/playback/mediaplayer/source_impl.h" |
| #include "src/media/playback/mediaplayer/util/safe_clone.h" |
| #include "zircon/third_party/uapp/dash/src/output.h" |
| |
| namespace media_player { |
| |
| static const char* kDumpEntry = "dump"; |
| static constexpr size_t kMaxBufferSize = 32 * 1024; |
| |
| // static |
| std::unique_ptr<SessionAudioConsumerFactoryImpl> SessionAudioConsumerFactoryImpl::Create( |
| fidl::InterfaceRequest<fuchsia::media::SessionAudioConsumerFactory> request, |
| sys::ComponentContext* component_context, fit::closure quit_callback) { |
| return std::make_unique<SessionAudioConsumerFactoryImpl>(std::move(request), component_context, |
| std::move(quit_callback)); |
| } |
| |
| SessionAudioConsumerFactoryImpl::SessionAudioConsumerFactoryImpl( |
| fidl::InterfaceRequest<fuchsia::media::SessionAudioConsumerFactory> request, |
| sys::ComponentContext* component_context, fit::closure quit_callback) |
| : component_context_(component_context), |
| quit_callback_(std::move(quit_callback)), |
| binding_(this, std::move(request)) { |
| FX_DCHECK(component_context_); |
| FX_DCHECK(quit_callback_); |
| |
| binding_.set_error_handler([this](zx_status_t status) { MaybeQuit(); }); |
| } |
| |
| void SessionAudioConsumerFactoryImpl::CreateAudioConsumer( |
| uint64_t session_id, |
| fidl::InterfaceRequest<fuchsia::media::AudioConsumer> audio_consumer_request) { |
| FX_DCHECK(audio_consumer_request); |
| |
| auto audio_consumer = |
| AudioConsumerImpl::Create(session_id, std::move(audio_consumer_request), component_context_); |
| audio_consumer->SetQuitCallback([this, audio_consumer]() { |
| audio_consumers_.erase(audio_consumer); |
| MaybeQuit(); |
| }); |
| audio_consumers_.insert(audio_consumer); |
| } |
| |
| void SessionAudioConsumerFactoryImpl::MaybeQuit() { |
| // don't quit when factory channel closes unless no audio consumers are active |
| if (audio_consumers_.empty() && quit_callback_) { |
| auto callback = std::move(quit_callback_); |
| callback(); |
| } |
| } |
| |
| // static |
| std::shared_ptr<AudioConsumerImpl> AudioConsumerImpl::Create( |
| uint64_t session_id, fidl::InterfaceRequest<fuchsia::media::AudioConsumer> request, |
| sys::ComponentContext* component_context) { |
| return std::make_shared<AudioConsumerImpl>(session_id, std::move(request), component_context); |
| } |
| |
| AudioConsumerImpl::AudioConsumerImpl(uint64_t session_id, |
| fidl::InterfaceRequest<fuchsia::media::AudioConsumer> request, |
| sys::ComponentContext* component_context) |
| : binding_(this, std::move(request)), |
| dispatcher_(async_get_default_dispatcher()), |
| component_context_(component_context), |
| core_(dispatcher_), |
| renderer_primed_(false), |
| timeline_started_(false), |
| rate_(kDefaultRate), |
| status_dirty_(true) { |
| FX_DCHECK(component_context_); |
| |
| ThreadPriority::SetToHigh(); |
| |
| binding_.set_error_handler([this](zx_status_t status) { |
| core_.SetUpdateCallback(nullptr); |
| if (quit_callback_) { |
| // after quit_callback_ returns last reference could be dropped, so don't reference 'this' |
| // after |
| auto callback = std::move(quit_callback_); |
| callback(); |
| } |
| }); |
| |
| decoder_factory_ = DecoderFactory::Create(this); |
| FX_DCHECK(decoder_factory_); |
| |
| component_context_->outgoing()->debug_dir()->AddEntry( |
| kDumpEntry, |
| std::make_unique<vfs::PseudoFile>( |
| kMaxBufferSize, [this](std::vector<uint8_t>* out, size_t max_bytes) -> zx_status_t { |
| std::ostringstream os; |
| |
| core_.Dump(os << std::boolalpha); |
| os << "\n"; |
| |
| auto out_str = os.str(); |
| auto end = out_str.size() > max_bytes ? out_str.begin() + max_bytes : out_str.end(); |
| *out = std::vector<uint8_t>(out_str.begin(), end); |
| |
| return ZX_OK; |
| })); |
| } |
| |
| AudioConsumerImpl::~AudioConsumerImpl() { core_.SetUpdateCallback(nullptr); } |
| |
| void AudioConsumerImpl::HandlePlayerStatusUpdate() { |
| if (core_.problem()) { |
| if (core_.problem()->type == fuchsia::media::playback::PROBLEM_AUDIO_ENCODING_NOT_SUPPORTED) { |
| FX_LOGS(WARNING) << "Unsupported codec"; |
| if (simple_stream_sink_) { |
| simple_stream_sink_->Close(ZX_ERR_INVALID_ARGS); |
| simple_stream_sink_ = nullptr; |
| } |
| } |
| } |
| } |
| |
| void AudioConsumerImpl::CreateStreamSink( |
| std::vector<zx::vmo> buffers, fuchsia::media::AudioStreamType audio_stream_type, |
| std::unique_ptr<fuchsia::media::Compression> compression, |
| fidl::InterfaceRequest<fuchsia::media::StreamSink> stream_sink_request) { |
| FX_DCHECK(stream_sink_request); |
| |
| std::string encoding = fuchsia::media::AUDIO_ENCODING_LPCM; |
| std::unique_ptr<Bytes> encoding_parameters; |
| |
| if (compression && !compression->type.empty()) { |
| encoding = std::move(compression->type); |
| if (compression->parameters) { |
| encoding_parameters = fidl::To<std::unique_ptr<media_player::Bytes>>(compression->parameters); |
| } |
| } |
| |
| media_player::AudioStreamType stream_type( |
| nullptr, encoding, std::move(encoding_parameters), |
| fidl::To<media_player::AudioStreamType::SampleFormat>(audio_stream_type.sample_format), |
| audio_stream_type.channels, audio_stream_type.frames_per_second); |
| |
| // Only allow one pending stream sink. |
| // Setup timeline rate in ns units per interface docs. |
| pending_simple_stream_sink_ = SimpleStreamSinkImpl::Create( |
| stream_type, media::TimelineRate::NsPerSecond, |
| /* discard_requested_callback= */ |
| [shared_this = shared_from_this(), this]() { core_.Flush(false, []() {}); }, |
| std::move(stream_sink_request), |
| /* connection_failure_callback= */ |
| [shared_this = shared_from_this(), this]() { |
| // On disconnect, check for any pending sinks |
| MaybeSetNewSource(); |
| }); |
| |
| pending_buffers_ = std::move(buffers); |
| |
| simple_stream_sink_ = pending_simple_stream_sink_; |
| |
| if (!core_.has_source_segment()) { |
| MaybeSetNewSource(); |
| } |
| } |
| |
| void AudioConsumerImpl::MaybeSetNewSource() { |
| if (core_.has_source_segment()) { |
| // reset state for next stream sink |
| timeline_started_ = false; |
| rate_ = kDefaultRate; |
| status_dirty_ = true; |
| core_.ClearSourceSegment(); |
| } |
| |
| if (!pending_simple_stream_sink_) { |
| return; |
| } |
| |
| auto simple_stream_sink = std::move(pending_simple_stream_sink_); |
| auto buffers = std::move(pending_buffers_); |
| |
| auto audio_consumer_source = AudioConsumerSourceImpl::Create(core_.graph(), []() {}); |
| audio_consumer_source->AddStream(simple_stream_sink, simple_stream_sink->output_stream_type()); |
| |
| EnsureRenderer(); |
| |
| core_.SetSourceSegment( |
| audio_consumer_source->TakeSourceSegment(), |
| [shared_this = shared_from_this(), simple_stream_sink = std::move(simple_stream_sink), |
| buffers = std::move(buffers)]() mutable { |
| // capture shared this to make sure we live long enough for |core_| to complete setting |
| // source. |
| for (size_t i = 0; i < buffers.size(); ++i) { |
| simple_stream_sink->AddPayloadBuffer(i, std::move(buffers[i])); |
| } |
| }); |
| } |
| |
| void AudioConsumerImpl::EnsureRenderer() { |
| if (core_.has_sink_segment(StreamType::Medium::kAudio)) { |
| // Renderer already exists. |
| return; |
| } |
| |
| if (!audio_renderer_) { |
| auto audio = ServiceProvider::ConnectToService<fuchsia::media::Audio>(); |
| fuchsia::media::AudioRendererPtr audio_renderer; |
| audio->CreateAudioRenderer(audio_renderer.NewRequest()); |
| audio_renderer_ = FidlAudioRenderer::Create(std::move(audio_renderer)); |
| core_.SetSinkSegment(RendererSinkSegment::Create(audio_renderer_, decoder_factory_.get()), |
| StreamType::Medium::kAudio); |
| core_.SetProgramRange(0, 0, Packet::kMaxPts); |
| |
| core_.SetUpdateCallback( |
| [shared_this = shared_from_this(), this]() { HandlePlayerStatusUpdate(); }); |
| } |
| } |
| |
| void AudioConsumerImpl::OnTimelineUpdated(float rate) { |
| if (rate > 0.0f && !renderer_primed_) { |
| core_.Prime([]() {}); |
| renderer_primed_ = true; |
| } else if (rate == 0.0f) { |
| renderer_primed_ = false; |
| } |
| SendStatusUpdate(); |
| } |
| |
| void AudioConsumerImpl::SetTimelineFunction(float rate, int64_t subject_time, |
| int64_t reference_time, fit::closure callback) { |
| core_.SetTimelineFunction( |
| media::TimelineFunction(subject_time, reference_time, media::TimelineRate(rate)), |
| [shared_this = shared_from_this(), rate, callback = std::move(callback), this]() { |
| OnTimelineUpdated(rate); |
| callback(); |
| }); |
| } |
| |
| void AudioConsumerImpl::Start(fuchsia::media::AudioConsumerStartFlags flags, int64_t reference_time, |
| int64_t media_time) { |
| timeline_started_ = true; |
| |
| if (reference_time == 0) { |
| // TODO(afoxley) set lead time based on flags? |
| reference_time = zx::clock::get_monotonic().get() + kMinimumLeadTime; |
| } |
| |
| if (media_time == fuchsia::media::NO_TIMESTAMP) { |
| media_time = 0; |
| } |
| |
| SetTimelineFunction(rate_, media_time, reference_time, []() {}); |
| } |
| |
| void AudioConsumerImpl::SetRate(float rate) { |
| // AudioRenderer currently only supports 0/1 (paused) or 1/1 (normal playback rate). |
| if (rate != 0.0f) { |
| rate = 1.0f; |
| } |
| rate_ = rate; |
| |
| SetTimelineFunction(rate_, core_.timeline_function().subject_time(), |
| zx::clock::get_monotonic().get() + kMinimumLeadTime, []() {}); |
| } |
| |
| void AudioConsumerImpl::BindVolumeControl( |
| fidl::InterfaceRequest<fuchsia::media::audio::VolumeControl> request) { |
| // TODO(afoxley) setup volume control |
| } |
| |
| void AudioConsumerImpl::Stop() { |
| SetTimelineFunction(0.0f, core_.timeline_function().subject_time(), |
| core_.timeline_function().reference_time(), []() {}); |
| } |
| |
| void AudioConsumerImpl::WatchStatus(fuchsia::media::AudioConsumer::WatchStatusCallback callback) { |
| watch_status_callback_ = std::move(callback); |
| |
| if (status_dirty_) { |
| status_dirty_ = false; |
| SendStatusUpdate(); |
| } |
| } |
| |
| void AudioConsumerImpl::ConnectToService(std::string service_path, zx::channel channel) { |
| FX_DCHECK(component_context_); |
| component_context_->svc()->Connect(service_path, std::move(channel)); |
| } |
| |
| void AudioConsumerImpl::SendStatusUpdate() { |
| if (!watch_status_callback_) { |
| status_dirty_ = true; |
| return; |
| } |
| |
| fuchsia::media::AudioConsumerStatus status; |
| |
| status.set_max_lead_time(kMaximumLeadTime); |
| status.set_min_lead_time(kMinimumLeadTime); |
| |
| if (timeline_started_) { |
| status.set_presentation_timeline( |
| fidl::To<fuchsia::media::TimelineFunction>(core_.timeline_function())); |
| } |
| |
| // TODO(afoxley) set any error here |
| |
| watch_status_callback_(std::move(status)); |
| watch_status_callback_ = nullptr; |
| } |
| |
| } // namespace media_player |