blob: b522f09024cb274daf1aea6adfe5a1775ded5788 [file] [log] [blame]
// Copyright 2015 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 CHROMECAST_MEDIA_CMA_BACKEND_ALSA_STREAM_MIXER_ALSA_H_
#define CHROMECAST_MEDIA_CMA_BACKEND_ALSA_STREAM_MIXER_ALSA_H_
#include <alsa/asoundlib.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/threading/thread.h"
#include "base/timer/timer.h"
#include "chromecast/media/cma/backend/alsa/audio_filter_interface.h"
#include "chromecast/media/cma/backend/alsa/media_pipeline_backend_alsa.h"
#include "chromecast/media/cma/backend/alsa/stream_mixer_alsa_input.h"
#include "chromecast/public/cast_media_shlib.h"
namespace media {
class AudioBus;
} // namespace media
namespace chromecast {
namespace media {
class AlsaWrapper;
class FilterGroup;
// Mixer implementation. The mixer has one or more input queues; these can be
// added/removed at any time. When an input source pushes frames to an input
// queue, the queue should call StreamMixerAlsa::WriteFrames(); this causes
// the mixer to attempt to mix and write out as many frames as possible. To do
// this, the mixer determines how many frames can be read from all inputs (ie,
// it gets the maximum number of frames that can be read from each input, and
// uses the minimum value). Assuming that all primary inputs have some data
// available, the calculated number of frames are pulled from each input (maybe
// resampled, if the input's incoming sample rate is not equal to the mixer's
// output sample rate) and written to the ALSA stack.
//
// The rendering delay is recalculated after every successful write to the ALSA
// stack. This delay is passed up to the input sources whenever some new data
// is sucessfully added to the input queue (which happens whenever the amount
// of data in the queue drops below the maximum limit, if data is pending). Note
// that the rendering delay is not accurate while the mixer is gathering frames
// to write, so the rendering delay and the queue size for each input must be
// updated atomically after each write is complete (ie, in AfterWriteFrames()).
class StreamMixerAlsa {
public:
// This mixer will pull data from InputQueues which are added to it, mix the
// data from the streams, and write the mixed data as a single stream to ALSA.
// These methods will be called on the mixer thread only.
class InputQueue {
public:
using OnReadyToDeleteCb = base::Callback<void(InputQueue*)>;
virtual ~InputQueue() {}
// Returns the sample rate of this stream *before* data is resampled to
// match the sample rate expected by the mixer. The returned value must be
// positive.
virtual int input_samples_per_second() const = 0;
// Returns true if the stream is primary. Primary streams will be given
// precedence for sample rates and will dictate when data is polled.
virtual bool primary() const = 0;
// Returns a string describing the content type class.
// Should be from chromecast/media/base/audio_device_ids.h
// or media/audio/audio_device_description.h
virtual std::string device_id() const = 0;
// Returns true if PrepareToDelete() has been called.
virtual bool IsDeleting() const = 0;
// Initializes the InputQueue after the mixer is set up. At this point the
// input can correctly determine the mixer's output sample rate.
virtual void Initialize(const MediaPipelineBackendAlsa::RenderingDelay&
mixer_rendering_delay) = 0;
// Sets and gets the FilterGroup the InputQueue matches.
// This is determined at creation to save time matching the InputQueue
// to a FilterGroup every time it needs to be mixed.
virtual void set_filter_group(FilterGroup* filter_group) = 0;
virtual FilterGroup* filter_group() = 0;
// Returns the maximum number of frames that can be read from this input
// stream without filling with zeros. This should return 0 if the queue is
// empty and EOS has not been queued.
virtual int MaxReadSize() = 0;
// Pulls data from the input stream. The input stream should populate |dest|
// with |frames| frames of data to be mixed. The mixer expects data to be
// at a sample rate of |output_samples_per_second()|, so each input stream
// should resample as necessary before returning. |frames| is guaranteed to
// be no larger than the value returned by the most recent call to
// MaxReadSize(), and |dest->frames()| shall be >= |frames|.
virtual void GetResampledData(::media::AudioBus* dest, int frames) = 0;
// Scale |frames| frames at |src| by the current volume (smoothing as
// needed). Add the scaled result to |dest|.
// VolumeScaleAccumulate will be called once for each channel of audio
// present and |repeat_transition| will be true for channels 2 through n.
// |src| and |dest| should be 16-byte aligned.
virtual void VolumeScaleAccumulate(bool repeat_transition,
const float* src,
int frames,
float* dest) = 0;
// Called when this input has been skipped for output due to not having any
// data available. This indicates that there will be a gap in the playback
// from this stream.
virtual void OnSkipped() = 0;
// This is called for every InputQueue when the mixer writes data to ALSA
// for any of its input streams.
virtual void AfterWriteFrames(
const MediaPipelineBackendAlsa::RenderingDelay&
mixer_rendering_delay) = 0;
// This will be called when a fatal error occurs in the mixer.
virtual void SignalError(StreamMixerAlsaInput::MixerError error) = 0;
// Notifies the input that it is being removed by the upper layers, and
// should do whatever is necessary to become ready to delete from the mixer.
// Once the input is ready to be removed, it should call the supplied
// |delete_cb|; this should only happen once per input.
virtual void PrepareToDelete(const OnReadyToDeleteCb& delete_cb) = 0;
};
enum State {
kStateUninitialized,
kStateNormalPlayback,
kStateError,
};
static StreamMixerAlsa* Get();
static void MakeSingleThreadedForTest();
int output_samples_per_second() const { return output_samples_per_second_; }
bool empty() const { return inputs_.empty(); }
State state() const { return state_; }
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner() const {
return mixer_task_runner_;
}
// Adds an input to the mixer. The input will live at least until
// RemoveInput(input) is called. Can be called on any thread.
void AddInput(std::unique_ptr<InputQueue> input);
// Instructs the mixer to remove an input. The input should not be referenced
// after this is called. Can be called on any thread.
void RemoveInput(InputQueue* input);
// Attempts to write some frames of audio to ALSA. Must only be called on the
// mixer thread.
void OnFramesQueued();
void SetAlsaWrapperForTest(std::unique_ptr<AlsaWrapper> alsa_wrapper);
void WriteFramesForTest(); // Can be called on any thread.
void ClearInputsForTest(); // Removes all inputs.
void AddLoopbackAudioObserver(
CastMediaShlib::LoopbackAudioObserver* observer);
void RemoveLoopbackAudioObserver(
CastMediaShlib::LoopbackAudioObserver* observer);
protected:
StreamMixerAlsa();
virtual ~StreamMixerAlsa();
private:
void ResetTaskRunnerForTest();
void FinalizeOnMixerThread();
void FinishFinalize();
// Reads the buffer size, period size, start threshold, and avail min value
// from the provided command line flags or uses default values if no flags are
// provided.
void DefineAlsaParameters();
// Takes the provided ALSA config and sets all ALSA output hardware/software
// playback parameters. It will try to select sane fallback parameters based
// on what the output hardware supports and will log warnings if it does so.
// If any ALSA function returns an unexpected error code, the error code will
// be returned by this function. Otherwise, it will return 0.
int SetAlsaPlaybackParams();
void Start();
void Stop();
void Close();
void SignalError();
void CheckChangeOutputRate(int input_samples_per_second);
unsigned int DetermineOutputRate(unsigned int requested_rate);
// Deletes an input queue that has finished preparing to delete itself.
// May be called on any thread.
void DeleteInputQueue(InputQueue* input);
// Runs on mixer thread to complete input queue deletion.
void DeleteInputQueueInternal(InputQueue* input);
// Called after a timeout period to close the PCM handle if no inputs are
// present.
void CheckClose();
void WriteFrames();
bool TryWriteFrames();
void WriteMixedPcm(std::vector<uint8_t>* interleaved, int frames);
void UpdateRenderingDelay(int newly_pushed_frames);
size_t InterleavedSize(int frames);
ssize_t BytesPerOutputFormatSample();
void ResizeBuffersIfNecessary(int chunk_size);
static bool single_threaded_for_test_;
std::unique_ptr<AlsaWrapper> alsa_;
std::unique_ptr<base::Thread> mixer_thread_;
scoped_refptr<base::SingleThreadTaskRunner> mixer_task_runner_;
unsigned int fixed_output_samples_per_second_;
unsigned int low_sample_rate_cutoff_;
int requested_output_samples_per_second_;
int output_samples_per_second_;
snd_pcm_t* pcm_;
snd_pcm_hw_params_t* pcm_hw_params_;
snd_pcm_status_t* pcm_status_;
snd_pcm_format_t pcm_format_;
// User-configurable ALSA parameters. This caches the results, so the code
// only has to interact with the command line parameters once.
std::string alsa_device_name_;
snd_pcm_uframes_t alsa_buffer_size_;
bool alsa_period_explicitly_set;
snd_pcm_uframes_t alsa_period_size_;
snd_pcm_uframes_t alsa_start_threshold_;
snd_pcm_uframes_t alsa_avail_min_;
State state_;
std::vector<std::unique_ptr<InputQueue>> inputs_;
std::vector<std::unique_ptr<InputQueue>> ignored_inputs_;
MediaPipelineBackendAlsa::RenderingDelay rendering_delay_;
std::unique_ptr<base::Timer> retry_write_frames_timer_;
int check_close_timeout_;
std::unique_ptr<base::Timer> check_close_timer_;
std::vector<std::unique_ptr<FilterGroup>> filter_groups_;
std::vector<CastMediaShlib::LoopbackAudioObserver*> loopback_observers_;
DISALLOW_COPY_AND_ASSIGN(StreamMixerAlsa);
};
} // namespace media
} // namespace chromecast
#endif // CHROMECAST_MEDIA_CMA_BACKEND_ALSA_STREAM_MIXER_ALSA_H_