chromium/chromecast/media/common/media_pipeline_backend_manager.h

// Copyright 2016 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_COMMON_MEDIA_PIPELINE_BACKEND_MANAGER_H_
#define CHROMECAST_MEDIA_COMMON_MEDIA_PIPELINE_BACKEND_MANAGER_H_

#include <map>
#include <memory>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list_threadsafe.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/timer/timer.h"
#include "chromecast/media/api/cma_backend_factory.h"
#include "chromecast/public/media/decoder_config.h"
#include "chromecast/public/media/media_pipeline_device_params.h"

namespace chromecast {
namespace media {

enum class AudioContentType;
class CastDecoderBuffer;
class CmaBackend;
class MediaPipelineBackendWrapper;
class ActiveMediaPipelineBackendWrapper;
class MediaResourceTracker;

// This class tracks all created media backends, tracking whether or not volume
// feedback sounds should be enabled based on the currently active backends.
// Volume feedback sounds are only enabled when there are no active audio
// streams (apart from sound-effects streams).
class MediaPipelineBackendManager : public media::CmaBackendFactory {
 public:
  class ActiveAudioStreamObserver {
   public:
    // Called when we transition between "no active audio streams" and
    // "have at least one active audio stream" states. For this purpose, sound
    // effects streams are ignored.
    virtual void OnActiveAudioStreamChange(bool have_active_streams) = 0;

   protected:
    virtual ~ActiveAudioStreamObserver() = default;
  };

  // Delegate which can process Audio buffers sent to us.
  class BufferDelegate {
   public:
    // Returns |true| if the delegate is accepting buffers.
    virtual bool IsActive() = 0;

    // Called when calls to |OnPushBuffer| will start.
    virtual void OnStreamStarted() = 0;

    // Called when calls to |OnPushBuffer| will stop.
    virtual void OnStreamStopped() = 0;

    // If |IsActive| returns true, the media stream's audio buffers will be sent
    // to the delegate and the media stream's volume will be set to 0.
    //
    // The client may only assume the buffer is in scope during the callback.
    // If the client needs to use the buffer out of scope of the callback (e.g.
    // posted onto a different thread), it must make a copy.
    virtual void OnPushBuffer(const CastDecoderBuffer* buffer) = 0;

    // Called when the media stream's audio config changes. After this call, all
    // subsequent buffers will have the new config. This method will be called
    // regardless if |IsActive| returs true or false.
    virtual void OnSetConfig(const AudioConfig& config) = 0;

    // Called when volume changes. |volume| is from [0.0, 1.0].
    virtual void OnSetVolume(float volume) = 0;

   protected:
    virtual ~BufferDelegate() = default;
  };

  enum DecoderType {
    AUDIO_DECODER,
    VIDEO_DECODER,
    SFX_DECODER,
    NUM_DECODER_TYPES
  };

  MediaPipelineBackendManager(
      scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
      MediaResourceTracker* media_resource_tracker);

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

  ~MediaPipelineBackendManager() override;

  // media::CmaBackendFactory implementation:
  std::unique_ptr<CmaBackend> CreateBackend(
      const MediaPipelineDeviceParams& params) override;
  scoped_refptr<base::SequencedTaskRunner> GetMediaTaskRunner() override;

  // Inform that a backend previously created is destroyed.
  // Must be called on the same thread as |media_task_runner_|.
  void BackendDestroyed(MediaPipelineBackendWrapper* backend_wrapper);
  // |backend_wrapper| will use a VideoDecoder.
  // MediaPipelineBackendManager needs to record the backend that uses the
  // VideoDecoder; and if there is an active backend using VideoDecoder, that
  // backend needs to be revoked.
  // Must be called on the same thread as |media_task_runner_|.
  void BackendUseVideoDecoder(MediaPipelineBackendWrapper* backend_wrapper);

  base::SingleThreadTaskRunner* task_runner() const {
    return media_task_runner_.get();
  }

  // Adds/removes an observer for when volume feedback sounds are allowed.
  // An observer must be removed on the same thread that added it.
  void AddActiveAudioStreamObserver(ActiveAudioStreamObserver* observer);
  void RemoveActiveAudioStreamObserver(ActiveAudioStreamObserver* observer);

  // Add/remove a playing audio stream that is not accounted for by a
  // CmaBackend instance. |sfx| indicates whether or not the stream is a sound
  // effects stream (has no effect on volume feedback).
  void AddExtraPlayingStream(bool sfx, const AudioContentType type);
  void RemoveExtraPlayingStream(bool sfx, const AudioContentType type);

  // |buffer_delegate| will get notified for all buffers on the media stream.
  // |buffer_delegate| must outlive |this|.
  // Can only be set once.
  void SetBufferDelegate(BufferDelegate* buffer_delegate);

  BufferDelegate* buffer_delegate() const { return buffer_delegate_; }

  // If |power_save_enabled| is |false|, power save will be turned off and
  // automatic power save will be disabled until this is called with |true|.
  void SetPowerSaveEnabled(bool power_save_enabled);

  // Temporarily disables power save mode even if there are no currently-playing
  // audio streams. Useful to disable power save ahead of time if audio will
  // start playing soon (within 5 seconds).
  void TemporaryDisablePowerSave();

 private:
  friend class ActiveMediaPipelineBackendWrapper;

  class MixerConnection {
   public:
    virtual ~MixerConnection() = default;
  };

  void CreateMixerConnection();

  // Backend wrapper instances must use these APIs when allocating and releasing
  // decoder objects, so we can enforce global limit on #concurrent decoders.
  bool IncrementDecoderCount(DecoderType type);
  void DecrementDecoderCount(DecoderType type);

  // Update the count of playing non-effects audio streams.
  void UpdatePlayingAudioCount(bool sfx,
                               const AudioContentType type,
                               int change);
  void OnMixerStreamCountChange(int primary_streams, int sfx_streams);
  void HandlePlayingAudioStreamsChange(bool had_playing_audio_streams,
                                       bool had_playing_primary_streams);
  int TotalPlayingAudioStreamsCount();
  int TotalPlayingNoneffectsAudioStreamsCount();

  void EnterPowerSaveMode();

  const scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_;
  MediaResourceTracker* const media_resource_tracker_;

  // Total count of decoders created
  int decoder_count_[NUM_DECODER_TYPES];

  // Total number of playing audio streams.
  base::flat_map<AudioContentType, int> playing_audio_streams_count_;

  // Total number of playing non-effects streams.
  base::flat_map<AudioContentType, int> playing_noneffects_audio_streams_count_;

  scoped_refptr<base::ObserverListThreadSafe<ActiveAudioStreamObserver>>
      active_audio_stream_observers_;

  // Previously issued MediaPipelineBackendWrapper that uses a video decoder.
  MediaPipelineBackendWrapper* backend_wrapper_using_video_decoder_;

  BufferDelegate* buffer_delegate_;

  bool power_save_enabled_ = true;

  base::OneShotTimer power_save_timer_;

  std::unique_ptr<MixerConnection> mixer_connection_;
  int mixer_primary_stream_count_ = 0;
  int mixer_sfx_stream_count_ = 0;

  base::WeakPtrFactory<MediaPipelineBackendManager> weak_factory_;
};

}  // namespace media
}  // namespace chromecast

#endif  // CHROMECAST_MEDIA_COMMON_MEDIA_PIPELINE_BACKEND_MANAGER_H_