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

#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_TRACK_AUDIO_RENDERER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_TRACK_AUDIO_RENDERER_H_

#include <stdint.h>

#include <memory>

#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "media/base/audio_renderer_sink.h"
#include "third_party/blink/public/platform/modules/mediastream/web_media_stream_audio_sink.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_audio_renderer.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
#include "third_party/blink/renderer/platform/wtf/deque.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"

namespace media {
class AudioBus;
class AudioShifter;
class AudioParameters;
}  // namespace media

namespace blink {

class LocalFrame;

// TrackAudioRenderer is a MediaStreamAudioRenderer for plumbing audio
// data generated from either local or remote (but not
// PeerConnection/WebRTC-sourced) MediaStreamAudioTracks to an audio output
// device, reconciling differences in the rates of production and consumption of
// the audio data.  Note that remote PeerConnection-sourced tracks are NOT
// rendered by this implementation (see MediaStreamRendererFactory).
//
// This class uses AudioDeviceFactory to create media::AudioRendererSink and
// owns/manages their lifecycles.  Output devices are automatically re-created
// in response to audio format changes, or use of the SwitchOutputDevice() API
// by client code.
//
// Audio data is feed-in from the source via calls to OnData().  The
// internally-owned media::AudioOutputDevice calls Render() to pull-out that
// audio data.  However, because of clock differences and other environmental
// factors, the audio will inevitably feed-in at a rate different from the rate
// it is being rendered-out.  media::AudioShifter is used to buffer, stretch
// and skip audio to maintain time synchronization between the producer and
// consumer.
class MODULES_EXPORT TrackAudioRenderer
    : public MediaStreamAudioRenderer,
      public WebMediaStreamAudioSink,
      public media::AudioRendererSink::RenderCallback {
 public:
  // Creates a renderer for the given |audio_track|.  |playout_render_frame|
  // refers to the RenderFrame that owns this instance (e.g., it contains the
  // DOM widget representing the player).  |session_id| and |device_id| are
  // optional, and are used to direct audio output to a pre-selected device;
  // otherwise, audio is output to the default device for the system.
  //
  // Called on the main thread.
  TrackAudioRenderer(MediaStreamComponent* audio_component,
                     LocalFrame& playout_web_frame,
                     const String& device_id,
                     base::RepeatingClosure on_render_error_callback);

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

  // MediaStreamAudioRenderer implementation.
  // Called on the main thread.
  void Start() override;
  void Stop() override;
  void Play() override;
  void Pause() override;
  void SetVolume(float volume) override;
  base::TimeDelta GetCurrentRenderTime() override;
  void SwitchOutputDevice(const std::string& device_id,
                          media::OutputDeviceStatusCB callback) override;

  int TotalFramesPushedForTesting() const;
  int FramesInAudioShifterForTesting() const;

 protected:
  ~TrackAudioRenderer() override;

 private:
  struct PendingData {
    PendingData(const media::AudioBus& audio_bus, base::TimeTicks ref_time);

    PendingData(PendingData&& other) = default;
    ~PendingData() = default;

    base::TimeTicks reference_time;
    std::unique_ptr<media::AudioBus> audio;
  };

  using PendingDataQueue = Deque<PendingData>;

  struct PendingReconfig {
    PendingReconfig(const media::AudioParameters& format, int generation);

    PendingDataQueue data;

    // Used for validation purposes.
    const int reconfig_number;
    const media::AudioParameters format;
  };

  using PendingReconfigQueue = Deque<PendingReconfig>;

  // WebMediaStreamAudioSink implementation.

  // Called on the AudioInputDevice worker thread.
  void OnData(const media::AudioBus& audio_bus,
              base::TimeTicks reference_time) override;

  // Called on the AudioInputDevice worker thread.
  void OnSetFormat(const media::AudioParameters& params) override;

  // media::AudioRendererSink::RenderCallback implementation.
  // Render() is called on the AudioOutputDevice thread and OnRenderError()
  // on the IO thread.
  int Render(base::TimeDelta delay,
             base::TimeTicks delay_timestamp,
             const media::AudioGlitchInfo& glitch_info,
             media::AudioBus* audio_bus) override;
  void OnRenderError() override;

  void OnRenderErrorCrossThread();

  // Initializes and starts the |sink_| if we have received valid
  // |source_params_| and we are |playing_|.
  void MaybeStartSink(bool reconfiguring = false);

  // Sets new |source_params_| and then re-initializes and restarts |sink_|.
  void ReconfigureSink(const media::AudioParameters new_format,
                       int reconfig_number);

  // Creates a new AudioShifter, destroying the old one (if any).  This is
  // called any time playback is started/stopped, or the sink changes.
  // If we are |reconfiguring|, we will push any PendingData saved in
  // |pending_reconfigs_.front()| into the new |audio_shifter_|.
  void CreateAudioShifter(bool reconfiguring);

  // Called when either the source or sink has changed somehow, or audio has
  // been paused.  Drops the AudioShifter and updates
  // |prior_elapsed_render_time_|.  May be called from either the main thread or
  // the audio thread.  Assumption: |thread_lock_| is already acquired.
  void HaltAudioFlow_Locked();

  // Takes |pending_reconfigs_.front()|, pushing its data into |audio_shifter_|.
  void ConsumePendingReconfigsFront_Locked();

  // Utility function which updates |total_frames_pushed_for_testing_| and calls
  // |audio_shifter_->push()|.
  void PushDataIntoShifter_Locked(std::unique_ptr<media::AudioBus>,
                                  base::TimeTicks);

  // The audio track which provides access to the source data to render.
  //
  // This class is calling WebMediaStreamAudioSink::AddToAudioTrack() and
  // WebMediaStreamAudioSink::RemoveFromAudioTrack() to connect and
  // disconnect with the audio track.
  Persistent<MediaStreamComponent> audio_component_;

  // The LocalFrame in which the audio is rendered into |sink_|.
  WeakPersistent<LocalFrame> playout_frame_;

  // MessageLoop associated with the single thread that performs all control
  // tasks.  Set to the MessageLoop that invoked the ctor.
  const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;

  // The sink (destination) for rendered audio.
  scoped_refptr<media::AudioRendererSink> sink_;

  // This does all the synchronization/resampling/smoothing.
  std::unique_ptr<media::AudioShifter> audio_shifter_ GUARDED_BY(thread_lock_);

  // These track the time duration of all the audio rendered so far by this
  // instance.  |prior_elapsed_render_time_| tracks the time duration of all
  // audio rendered before the last format change.  |num_samples_rendered_|
  // tracks the number of audio samples rendered since the last format change.
  base::TimeDelta prior_elapsed_render_time_ GUARDED_BY(thread_lock_);
  int64_t num_samples_rendered_ GUARDED_BY(thread_lock_) = 0;

  // The audio parameters of the track's source.
  // Must only be touched on the main thread.
  media::AudioParameters source_params_;

  base::RepeatingClosure on_render_error_callback_;

  // Set when playing, cleared when paused.
  bool playing_ = false;

  // Protects |audio_shifter_|, |prior_elapsed_render_time_|,
  // |num_samples_rendered_|, and PendingReconfigs.
  mutable base::Lock thread_lock_;

  // The preferred device id of the output device or empty for the default
  // output device.
  String output_device_id_;

  // Cache value for the volume.  Whenever |sink_| is re-created, its volume
  // should be set to this.
  float volume_ = 0.0;

  // Flag to indicate whether |sink_| has been started yet.
  bool sink_started_ = false;

  // Each entry corresponds to a posted ReconfigureSink() call. Entries are
  // queued in OnSetFormat() on the audio capture sequence, and popped in
  // ReconfigureSink() on the main thread. If this queue is not empty, there is
  // still a pending reconfiguration. In that case, we accumulate data (incoming
  // from OnData() on the capture thread) in |pending_reconfigs_.back()|, until
  // the reconfiguration completes on the main thread. Upon completing the
  // reconfiguration, accumulated data is pushed into the new |audio_shifter_|,
  // to be rendered.
  PendingReconfigQueue GUARDED_BY(thread_lock_) pending_reconfigs_;

  // Used to drop incoming ReconfigureSink() calls, by comparing the call's
  // |reconfig_number| against the latest |sink_reconfig_count_|. Incremented
  // on the audio capture sequence, and checked on the main thread.
  int sink_reconfig_count_ GUARDED_BY(thread_lock_) = 0;

  // The last format posted to the main thread, via ReconfigureSink(). Used to
  // avoid calling ReconfigureSink() when consecutive OnSetFormat() calls have
  // compatible formats.
  // Only accessed on the audio capture thread.
  media::AudioParameters last_reconfig_format_;

  int total_frames_pushed_for_testing_ GUARDED_BY(thread_lock_) = 0;
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_TRACK_AUDIO_RENDERER_H_
