chromium/chromecast/media/audio/cast_audio_output_stream.h

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROMECAST_MEDIA_AUDIO_CAST_AUDIO_OUTPUT_STREAM_H_
#define CHROMECAST_MEDIA_AUDIO_CAST_AUDIO_OUTPUT_STREAM_H_

#include <memory>
#include <string>

#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chromecast/base/task_runner_impl.h"
#include "media/audio/audio_io.h"
#include "media/base/audio_parameters.h"
#include "media/base/audio_timestamp_helper.h"

namespace chromecast {
namespace media {

class CmaAudioOutputStream;
class CastAudioManagerHelper;

// Chromecast implementation of AudioOutputStream.
// This class forwards to MixerService if valid
// |mixer_service_connection_factory| is passed in on the construction call,
// for a lower latency audio playback (using MixerServiceWrapper). Otherwise,
// when a nullptr is passed in as |mixer_service_connection_factory| it forwards
// to CMA backend (using CmaWrapper).
//
// In either case, involved components live on two threads:
// 1. Audio thread
//    |CastAudioOutputStream|
//    Where the object gets construction from AudioManager.
//    How the object gets controlled from AudioManager.
// 2. Media thread or an IO thread opened within |AudioOutputStream|.
//    |CastAudioOutputStream::CmaWrapper| or |MixerServiceWrapper| lives on
//    this thread.
//
// The interface between AudioManager and AudioOutputStream is synchronous, so
// in order to allow asynchronous thread hops, we:
// * Maintain the current state independently in each thread.
// * Cache function calls like Start() and SetVolume() as bound callbacks.
//
// The individual thread states should nearly always be the same. The only time
// they are expected to be different is when the audio thread has executed a
// task and posted to the media thread/IO thread, but the media thread has not
// executed yet.
//
// The below illustrates the case when CMA backend is used for playback.
//
//  Audio Thread |CAOS|                         Media Thread |CmaWrapper|
//  ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                         ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//  *[ Closed ]                                 *[ Closed ]
//      |                                           |
//      |                                           |
//      v                                           v
//   [   Opened    ]    --post open-->           [   Opened    ]
//      |     |  ^                                  |     |  ^
//      |     |  |                                  |     |  |
//      |     | Stop()  --post stop-->              |     | Stop()
//      |     v  |                                  |     v  |
//      |  [ Started ]  --post start-->             |  [ Started ]
//      |     |                                     |     |
//      |     |                                     |     |
//      v     v                                     v     v
// **[ Pending Close ]  --post close-->        **[ Pending Close ]
//      |                                           |
//      |                                           |
//   ( waits for closure )         <--post closure--'
//      |
//      v
//   ( released)
// *  Initial states.
// ** Final states.
//
// When MixerService is used in place of CMA backend, the state transition is
// similar but a little simpler.
// MixerServiceWrapper creates a new mixer service connection at Start() and
// destroys the connection at Stop(). When the volume is adjusted between a
// Stop() and the next Start(), the volume is recorded and then applied to the
// mixer service connection after the connection is established on the Start()
// call.

// TODO(b/117980762): CastAudioOutputStream should be refactored
// to be more unit test friendly. And the unit tests can be improved.

class CastAudioOutputStream : public ::media::AudioOutputStream {
 public:
  // When nullptr is passed as |mixer_service_connection_factory|, CmaWrapper
  // will be used for audio playback.
  // |device_id_or_group_id| describes either the |device_id_| or |group_id_|.
  // If the |device_id_or_group_id| matches a valid device_id then the
  // |group_id_| is filled in empty. If the |device_id_or_group_id_| does not
  // match a valid |device_id|, then the |group_id_| is set to that value, and
  // the |device_id_| is set to kDefaultDeviceId. Valid device_id's are either
  // ::media::AudioDeviceDescription::kDefaultDeviceId or
  // ::media::AudioDeviceDescription::kCommunicationsId.
  CastAudioOutputStream(CastAudioManagerHelper* audio_manager,
                        const ::media::AudioParameters& audio_params,
                        const std::string& device_id_or_group_id,
                        bool use_mixer_service);

  CastAudioOutputStream(const CastAudioOutputStream&) = delete;
  CastAudioOutputStream& operator=(const CastAudioOutputStream&) = delete;

  ~CastAudioOutputStream() override;

  // ::media::AudioOutputStream implementation.
  bool Open() override;
  void Close() override;
  void Start(AudioSourceCallback* source_callback) override;
  void Stop() override;
  void SetVolume(double volume) override;
  void GetVolume(double* volume) override;
  void Flush() override;

 private:
  enum class AudioOutputState {
    kClosed,
    kOpened,
    kStarted,
    kPendingClose,
  };

  class MixerServiceWrapper;

  void FinishClose();

  double volume_;
  AudioOutputState audio_thread_state_;
  CastAudioManagerHelper* const audio_manager_;
  const ::media::AudioParameters audio_params_;
  // Valid |device_id_| are kDefaultDeviceId, and kCommunicationsDeviceId
  const std::string device_id_;
  // |group_id_|s are uuids mapped to session_ids for multizone. Should be an
  // empty string if group_id is unused.
  const std::string group_id_;
  const bool use_mixer_service_;
  std::unique_ptr<CmaAudioOutputStream> cma_wrapper_;
  std::unique_ptr<MixerServiceWrapper> mixer_service_wrapper_;

  THREAD_CHECKER(audio_thread_checker_);
  base::WeakPtr<CastAudioOutputStream> audio_weak_this_;
  base::WeakPtrFactory<CastAudioOutputStream> audio_weak_factory_;
};

}  // namespace media
}  // namespace chromecast

#endif  // CHROMECAST_MEDIA_AUDIO_CAST_AUDIO_OUTPUT_STREAM_H_