blob: 2405b85b338ce23869f7264532ee7112f6b60dcf [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.
#include <fuchsia/media/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/media/cpp/timeline_function.h>
#include <lib/media/cpp/timeline_rate.h>
#include <memory>
#include <dispatcher-pool/dispatcher-channel.h>
#include <dispatcher-pool/dispatcher-timer.h>
#include <dispatcher-pool/dispatcher-wakeup-event.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/slab_allocator.h>
#include "src/media/audio/audio_core/audio_driver.h"
#include "src/media/audio/audio_core/audio_link.h"
#include "src/media/audio/audio_core/audio_object.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/utils.h"
namespace media::audio {
class AudioCoreImpl;
class AudioCapturerImpl : public AudioObject,
public fuchsia::media::AudioCapturer,
public fuchsia::media::audio::GainControl,
public fbl::DoublyLinkedListable<fbl::RefPtr<AudioCapturerImpl>> {
static fbl::RefPtr<AudioCapturerImpl> Create(
bool loopback, fidl::InterfaceRequest<fuchsia::media::AudioCapturer> audio_capturer_request,
AudioCoreImpl* owner);
bool loopback() const { return loopback_; }
void SetInitialFormat(fuchsia::media::AudioStreamType format)
void Shutdown() FXL_LOCKS_EXCLUDED(mix_domain_->token());
void SetUsage(fuchsia::media::AudioCaptureUsage usage) override;
fuchsia::media::AudioCaptureUsage GetUsage() { return usage_; };
friend class fbl::RefPtr<AudioCapturerImpl>;
~AudioCapturerImpl() override;
zx_status_t InitializeSourceLink(const fbl::RefPtr<AudioLink>& link) override;
// Notes about the AudioCapturerImpl state machine.
// TODO(mpuryear): Update this comment block.
// :: WaitingForVmo ::
// AudioCapturers start in this mode. They should have a default capture mode
// set, and will accept a mode change up until the point where they have a
// shared payload VMO assigned to them. At this point they transition into the
// OperatingSync state. Only the main service thread may transition out of
// this state.
// :: OperatingSync ::
// After a mode has been assigned and a shared payload VMO has provided, the
// AudioCapturer is now operating in synchronous mode. Clients may provided
// buffers to be filled using the CaptureAt method and may cancel these
// buffers using the Flush method. They may also transition to asynchronous
// mode by calling StartAsyncCapture, but only when there are no pending
// buffers in flight. Only the main service thread may transition out of
// this state.
// :: OperatingAsync ::
// AudioCapturers enter OperatingAsync after a successful call to
// StartAsyncCapture. Threads from the mix_domain allocate and fill pending
// payload buffers, then signal the main service thread in order to send them
// back to the client over the AudioCapturerClient interface provided when
// starting. CaptureAt and Flush are illegal operations while in this state.
// clients may begin the process of returning to synchronous capture mode by
// calling StopAsyncCapture. Only the main service thread may transition out
// of this state.
// :: AsyncStopping ::
// AudioCapturers enter AsyncStopping after a successful call to
// StopAsyncCapture. A thread from the mix_domain will handle the details of
// stopping, including transferring all partially filled pending buffers to
// the finished queue. 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. Only a mix domain thread may transition out
// of this state.
// :: AsyncStoppingCallbackPending ::
// AudioCapturers enter AsyncStoppingCallbackPending after a mix domain thread
// has finished the process of shutting down the capture process and is ready
// to signal to the client that the AudioCapturer is now in synchronous
// capture mode again. The main service thread will send all partially and
// completely filled buffers to the user, ensuring that there is at least one
// buffer sent indicating end-of-stream, even if that buffer needs to be of
// zero length. Finally, the main service thread will signal that the stopping
// process is finished using the client supplied callback (if any), and
// finally transition back to the OperatingSync state.
enum class State {
struct PendingCaptureBuffer;
using PcbAllocatorTraits =
using PcbAllocator = ::fbl::SlabAllocator<PcbAllocatorTraits>;
using PcbList = ::fbl::DoublyLinkedList<std::unique_ptr<PendingCaptureBuffer>>;
struct PendingCaptureBuffer
: public fbl::SlabAllocated<PcbAllocatorTraits>,
public fbl::DoublyLinkedListable<std::unique_ptr<PendingCaptureBuffer>> {
PendingCaptureBuffer(uint32_t of, uint32_t nf, CaptureAtCallback c)
: offset_frames(of), num_frames(nf), cbk(std::move(c)) {}
static AtomicGenerationId sequence_generator;
const uint32_t offset_frames;
const uint32_t num_frames;
const CaptureAtCallback cbk;
int64_t capture_timestamp = fuchsia::media::NO_TIMESTAMP;
uint32_t flags = 0;
uint32_t filled_frames = 0;
const uint32_t sequence_number = sequence_generator.Next();
friend PcbAllocator;
std::vector<fuchsia::media::AudioCaptureUsage> allowed_usages_;
fuchsia::media::AudioCaptureUsage usage_;
AudioCapturerImpl(bool loopback,
fidl::InterfaceRequest<fuchsia::media::AudioCapturer> audio_capturer_request,
AudioCoreImpl* owner);
// AudioCapturer FIDL implementation
void GetStreamType(GetStreamTypeCallback cbk) final;
void SetPcmStreamType(fuchsia::media::AudioStreamType stream_type) final;
void AddPayloadBuffer(uint32_t id, zx::vmo payload_buf_vmo) final;
void RemovePayloadBuffer(uint32_t id) 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;
void BindGainControl(fidl::InterfaceRequest<fuchsia::media::audio::GainControl> request) final;
// GainControl interface.
void SetGain(float gain_db) final;
void SetGainWithRamp(float gain_db, zx_duration_t duration_ns,
fuchsia::media::audio::RampType ramp_type) final {
void SetMute(bool mute) final;
void NotifyGainMuteChanged();
// Methods used by capture/mixer thread(s). Must be called from mix_domain.
zx_status_t Process() FXL_EXCLUSIVE_LOCKS_REQUIRED(mix_domain_->token());
bool MixToIntermediate(uint32_t mix_frames) FXL_EXCLUSIVE_LOCKS_REQUIRED(mix_domain_->token());
void UpdateTransformation(Bookkeeping* bk, const AudioDriver::RingBufferSnapshot& rb_snap)
void DoStopAsyncCapture() FXL_EXCLUSIVE_LOCKS_REQUIRED(mix_domain_->token());
bool QueueNextAsyncPendingBuffer() FXL_EXCLUSIVE_LOCKS_REQUIRED(mix_domain_->token())
void ShutdownFromMixDomain() FXL_EXCLUSIVE_LOCKS_REQUIRED(mix_domain_->token());
// Thunk to send finished buffers 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 pending capture buffers to a user.
void FinishBuffers(const PcbList& finished_buffers) FXL_LOCKS_EXCLUDED(mix_domain_->token());
// Bookkeeping helper.
void UpdateFormat(fuchsia::media::AudioSampleFormat sample_format, uint32_t channels,
uint32_t frames_per_second) FXL_LOCKS_EXCLUDED(mix_domain_->token());
// Select a mixer for the link supplied and return true, or return false if
// one cannot be found.
zx_status_t ChooseMixer(const fbl::RefPtr<AudioLink>& link);
fidl::Binding<fuchsia::media::AudioCapturer> binding_;
fidl::BindingSet<fuchsia::media::audio::GainControl> gain_control_bindings_;
AudioCoreImpl* owner_ = nullptr;
std::atomic<State> state_;
const bool loopback_;
// Capture format and gain state.
fuchsia::media::AudioStreamTypePtr format_;
uint32_t bytes_per_frame_;
TimelineRate frames_to_clock_mono_rate_;
uint32_t max_frames_per_capture_;
std::atomic<float> stream_gain_db_;
bool mute_;
// Shared buffer state
zx::vmo payload_buf_vmo_;
void* payload_buf_virt_ = nullptr;
uint64_t payload_buf_size_ = 0;
uint32_t payload_buf_frames_ = 0;
// Execution domain/dispatcher stuff for mixing.
fbl::RefPtr<dispatcher::ExecutionDomain> mix_domain_;
fbl::RefPtr<dispatcher::WakeupEvent> mix_wakeup_;
fbl::RefPtr<dispatcher::Timer> mix_timer_;
// Queues of capture buffers supplied by the client and waiting to be filled,
// or waiting to be returned.
fbl::Mutex pending_lock_;
PcbList pending_capture_buffers_ FXL_GUARDED_BY(pending_lock_);
PcbList finished_capture_buffers_ FXL_GUARDED_BY(pending_lock_);
// Intermediate mixing buffer and output producer
std::unique_ptr<OutputProducer> output_producer_;
std::unique_ptr<float[]> mix_buf_;
// Vector used to hold references to our source links while we are mixing
// (instead of holding the lock which prevents source_links_ mutation for the
// entire mix job)
std::vector<fbl::RefPtr<AudioLink>> source_link_refs_ FXL_GUARDED_BY(mix_domain_->token());
// Capture bookkeeping
bool async_mode_ = false;
TimelineFunction frames_to_clock_mono_ FXL_GUARDED_BY(mix_domain_->token());
GenerationId frames_to_clock_mono_gen_ FXL_GUARDED_BY(mix_domain_->token());
int64_t frame_count_ FXL_GUARDED_BY(mix_domain_->token()) = 0;
uint32_t async_frames_per_packet_;
uint32_t async_next_frame_offset_ FXL_GUARDED_BY(mix_domain_->token()) = 0;
StopAsyncCaptureCallback pending_async_stop_cbk_;
} // namespace media::audio