blob: 94b770229c5ef3232c2518c3d742f9ed0e6cf106 [file] [log] [blame]
// Copyright 2017 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.
#ifndef SRC_MEDIA_AUDIO_AUDIO_CORE_BASE_CAPTURER_H_
#define SRC_MEDIA_AUDIO_AUDIO_CORE_BASE_CAPTURER_H_
#include <fuchsia/media/audio/cpp/fidl.h>
#include <fuchsia/media/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/zx/clock.h>
#include <atomic>
#include <memory>
#include <mutex>
#include <fbl/intrusive_double_list.h>
#include "src/media/audio/audio_core/audio_object.h"
#include "src/media/audio/audio_core/capture_packet_queue.h"
#include "src/media/audio/audio_core/context.h"
#include "src/media/audio/audio_core/mixer/mixer.h"
#include "src/media/audio/audio_core/mixer/output_producer.h"
#include "src/media/audio/audio_core/reporter.h"
#include "src/media/audio/audio_core/route_graph.h"
#include "src/media/audio/audio_core/stream_volume_manager.h"
#include "src/media/audio/audio_core/usage_settings.h"
#include "src/media/audio/audio_core/utils.h"
#include "src/media/audio/lib/clock/audio_clock.h"
#include "src/media/audio/lib/timeline/timeline_function.h"
#include "src/media/audio/lib/timeline/timeline_rate.h"
namespace media::audio {
class BaseCapturer : public AudioObject,
public fuchsia::media::AudioCapturer,
public std::enable_shared_from_this<BaseCapturer> {
public:
AudioClock& reference_clock() { return *audio_clock_; }
protected:
using RouteGraphRemover = void (RouteGraph::*)(const AudioObject&);
BaseCapturer(std::optional<Format> format,
fidl::InterfaceRequest<fuchsia::media::AudioCapturer> audio_capturer_request,
Context* context);
~BaseCapturer() override;
Context& context() const { return context_; }
ExecutionDomain& mix_domain() const { return *mix_domain_; }
// The BaseCapturer state machine:
//
// (start)
// |
// V
// WaitingForVmo
// |
// | (client provides a VMO)
// V
// WaitingForRequest
// | ^ ^ |
// | | | | (client calls CaptureAt)
// | | ( no more ) | |
// | | (CaptureAt) | |
// | | ( pending ) | V
// | | SyncOperating
// | |
// (client calls ) | |
// (StartAsyncCapture) | +------------------+
// V |
// AsyncOperating |
// | |
// (client calls ) | |
// (StopAsyncCapture) | |
// V |
// AsyncStopping |
// | |
// (mixer thread ) | |
// (finishes cleanup) | |
// V |
// AsyncStoppingCallbackPending |
// | |
// (FIDL thread ) | |
// (delivers callback) +--------------------+
//
//
// :: WaitingForVmo ::
// AudioCapturers start in this state. They should have a default capture
// format set, and will accept a state change up until the point where they
// have a shared payload VMO assigned to them.
//
// :: WaitingForRequest ::
// After a format has been assigned and a shared payload VMO has provided, the
// AudioCapturer is waiting to operate in Sync or Async mode.
//
// :: SyncOperating ::
// AudioCapturers enter SyncOperating after a successful call to CaptureAt.
// They remain in this state until all pending CaptureAt requests are handled
// or flushed.
//
// :: AsyncOperating ::
// AudioCapturers enter AsyncOperating after a successful call to
// StartAsyncCapture. CaptureAt and Flush are illegal while in this state.
//
// :: AsyncStopping ::
// :: AsyncStoppingCallbackPending ::
// AudioCapturers enter AsyncStopping after a successful call to
// StopAsyncCapture. A thread from the mix_domain will handle the details of
// stopping. Aside from setting the gain, all operations are illegal while the
// AudioCapturer is in the process of stopping. Once the mix domain thread has
// finished cleaning up, it will transition to the AsyncStoppingCallbackPending
// state and signal the main service thread in order to complete the process.
//
// :: Shutdown ::
// AudioCapturers enter this state when the connection is closing. We might
// transition to this state from any other state.
enum class State {
WaitingForVmo,
WaitingForRequest,
SyncOperating,
AsyncOperating,
AsyncStopping,
AsyncStoppingCallbackPending,
Shutdown,
};
State capture_state() const { return state_.load(); }
bool has_pending_packets() {
auto pq = packet_queue();
return pq && pq->PendingSize() > 0;
}
bool IsOperating();
void UpdateFormat(Format format) FXL_LOCKS_EXCLUDED(mix_domain_->token());
// Removes the capturer from its owner, the route graph, triggering shutdown and drop.
void BeginShutdown();
virtual void ReportStart();
virtual void ReportStop();
virtual void OnStateChanged(State old_state, State new_stage);
virtual void SetRoutingProfile(bool routable) = 0;
static bool StateIsRoutable(BaseCapturer::State state) {
return state != BaseCapturer::State::WaitingForVmo && state != BaseCapturer::State::Shutdown;
}
// |media::audio::AudioObject|
fpromise::result<std::pair<std::shared_ptr<Mixer>, ExecutionDomain*>, zx_status_t>
InitializeSourceLink(const AudioObject& source, std::shared_ptr<ReadableStream> stream) override;
void CleanupSourceLink(const AudioObject& source,
std::shared_ptr<ReadableStream> stream) override;
void OnLinkAdded() override;
fidl::Binding<fuchsia::media::AudioCapturer>& binding() { return binding_; }
// AudioCore treats client-provided clocks as not-rate-adjustable.
void SetClock(std::unique_ptr<AudioClock> audio_clock) { audio_clock_ = std::move(audio_clock); }
Reporter::Capturer& reporter() { return *reporter_; }
private:
void UpdateState(State new_state);
// |fuchsia::media::AudioCapturer|
void GetStreamType(GetStreamTypeCallback cbk) final;
void AddPayloadBuffer(uint32_t id, zx::vmo payload_buf_vmo) final;
void RemovePayloadBuffer(uint32_t id) final;
void GetReferenceClock(GetReferenceClockCallback callback) final;
void CaptureAt(uint32_t payload_buffer_id, uint32_t offset_frames, uint32_t num_frames,
CaptureAtCallback cbk) final;
void ReleasePacket(fuchsia::media::StreamPacket packet) final;
void DiscardAllPackets(DiscardAllPacketsCallback cbk) final;
void DiscardAllPacketsNoReply() final;
void StartAsyncCapture(uint32_t frames_per_packet) final;
void StopAsyncCapture(StopAsyncCaptureCallback cbk) final;
void StopAsyncCaptureNoReply() final;
// Methods used by capture/mixer thread(s). Must be called from mix_domain.
zx_status_t Process() FXL_EXCLUSIVE_LOCKS_REQUIRED(mix_domain_->token());
void DoStopAsyncCapture() FXL_EXCLUSIVE_LOCKS_REQUIRED(mix_domain_->token());
void ShutdownFromMixDomain() FXL_EXCLUSIVE_LOCKS_REQUIRED(mix_domain_->token());
void ReportOverflow(zx::time start_time, zx::time end_time)
FXL_EXCLUSIVE_LOCKS_REQUIRED(mix_domain_->token());
// Thunk to send ready packets back to the user, and to finish an async
// mode stop operation.
void FinishAsyncStopThunk() FXL_LOCKS_EXCLUDED(mix_domain_->token());
void FinishBuffersThunk() FXL_LOCKS_EXCLUDED(mix_domain_->token());
// Helper function used to return a set of ready packets to a user.
void FinishBuffers() FXL_LOCKS_EXCLUDED(mix_domain_->token());
fpromise::promise<> Cleanup() FXL_LOCKS_EXCLUDED(mix_domain_->token());
void CleanupFromMixThread() FXL_EXCLUSIVE_LOCKS_REQUIRED(mix_domain_->token());
void MixTimerThunk() {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, mix_domain_);
Process();
}
void RecomputePresentationDelay();
TimelineRate dest_frames_to_ref_clock_rate() {
return TimelineRate(ZX_SEC(1), format_->frames_per_second());
}
fidl::Binding<fuchsia::media::AudioCapturer> binding_;
Context& context_;
ThreadingModel::OwnedDomainPtr mix_domain_;
std::atomic<State> state_;
zx::duration presentation_delay_;
// Capture format and gain state.
std::optional<Format> format_;
uint32_t max_frames_per_capture_;
// Shared buffer state
fzl::VmoMapper payload_buf_;
WakeupEvent mix_wakeup_;
WakeupEvent ready_packets_wakeup_;
async::TaskClosureMethod<BaseCapturer, &BaseCapturer::MixTimerThunk> mix_timer_
FXL_GUARDED_BY(mix_domain_->token()){this};
// Queue of pending and ready packets.
//
// Concurrency notes: Initially this is nullptr. When we transition to state SyncOperating
// or AsyncOperating, we create a new queue and start mixing. Later, in response to a FIDL
// call, we might change operating modes from Sync -> Async or visa versa. To do this, we
// create a new queue, but as this happens, the mixer may be concurrently mixing on the old
// queue. We use a shared_ptr to ensure that the mixer can hold a valid reference for the
// duration of the mix operation, even in the presence of a concurrent mode switch.
//
// To illustrate:
//
// fidl_thread {
// // Switch from sync -> async.
// packet_queue()->DiscardPendingPackets();
// set_packet_queue_(CapturePacketQueue::CreatePreallocated(...));
// }
// mixer_thread {
// auto pq = packet_queue();
// auto state = pq->NextMixerJob();
// // ... FIDL thread might run here ...
// auto status = pq->FinishMixerJob(state);
// // ... will have status == Discarded ...
// }
//
std::mutex packet_queue_lock_;
std::shared_ptr<CapturePacketQueue> packet_queue_ FXL_GUARDED_BY(packet_queue_lock_);
std::shared_ptr<CapturePacketQueue> packet_queue() {
std::lock_guard<std::mutex> lock(packet_queue_lock_);
return packet_queue_;
}
void set_packet_queue(std::shared_ptr<CapturePacketQueue> pq) {
std::lock_guard<std::mutex> lock(packet_queue_lock_);
packet_queue_ = pq;
}
// Intermediate mixing buffer and output producer
std::unique_ptr<OutputProducer> output_producer_;
std::vector<LinkMatrix::LinkHandle> source_links_ FXL_GUARDED_BY(mix_domain_->token());
// Capture bookkeeping
fbl::RefPtr<VersionedTimelineFunction> ref_pts_to_fractional_frame_ =
fbl::MakeRefCounted<VersionedTimelineFunction>();
bool discontinuity_ FXL_GUARDED_BY(mix_domain_->token()) = true;
// Running frame number in the capture stream.
int64_t frame_pointer_ FXL_GUARDED_BY(mix_domain_->token()) = 0;
uint64_t overflow_count_ FXL_GUARDED_BY(mix_domain_->token()) = 0;
StopAsyncCaptureCallback pending_async_stop_cbk_;
std::shared_ptr<MixStage> mix_stage_;
Reporter::Container<Reporter::Capturer, Reporter::kObjectsToCache>::Ptr reporter_;
std::unique_ptr<AudioClock> audio_clock_;
};
} // namespace media::audio
#endif // SRC_MEDIA_AUDIO_AUDIO_CORE_BASE_CAPTURER_H_