chromium/chromeos/ash/services/recording/audio_stream_mixer.h

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

#ifndef CHROMEOS_ASH_SERVICES_RECORDING_AUDIO_STREAM_MIXER_H_
#define CHROMEOS_ASH_SERVICES_RECORDING_AUDIO_STREAM_MIXER_H_

#include <memory>
#include <string_view>
#include <vector>

#include "base/sequence_checker.h"
#include "base/threading/sequence_bound.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "media/mojo/mojom/audio_stream_factory.mojom-forward.h"
#include "mojo/public/cpp/bindings/pending_remote.h"

namespace capture_mode {
class AudioCapturer;
}  // namespace capture_mode

namespace media {
class AudioBus;
}  // namespace media

namespace recording {

class AudioStream;

// Defines a type for the callback that the mixer uses to provide the mixed
// audio bus output to its client.
using OnAudioMixerOutputCallback =
    base::RepeatingCallback<void(std::unique_ptr<media::AudioBus> audio_bus,
                                 base::TimeTicks audio_capture_time)>;

// Defines an audio stream mixer which can be used to create audio capturers
// whose `device_id`s are given to `AddAudioCapturer()`. Each capturer will be
// associated with an `AudioStream` instance that will queue all the unmixed /
// unconsumed audio buses received so far from the corresponding audio capturer.
// Once possible, all mix-able audio frames from all audio streams will be mixed
// and consumed, and will be provided to the client via the given `callback` as
// a single audio bus that contains all the mixed audio frames so far.
// All the operations of this class, including construction and destruction must
// happen on the same sequence.
class AudioStreamMixer {
 private:
  using PassKey = base::PassKey<AudioStreamMixer>;

 public:
  explicit AudioStreamMixer(PassKey);
  // A constructor used only by tests.
  explicit AudioStreamMixer(PassKey, OnAudioMixerOutputCallback callback);
  AudioStreamMixer(const AudioStreamMixer&) = delete;
  AudioStreamMixer& operator=(const AudioStreamMixer&) = delete;
  ~AudioStreamMixer();

  // Creates an instance of the mixer that is bound to the given `task_runner`.
  // All the operations of the mixer, including its construction and destruction
  // will be done on this `task_runner`.
  static base::SequenceBound<AudioStreamMixer> Create(
      scoped_refptr<base::SequencedTaskRunner> task_runner);

  // Creates a new `AudioCapturer` and a corresponding `AudioStream` to capture
  // the audio input device whose ID is the given `device_id`. The pending
  // remote will be used to communicate with the audio stream factory in the
  // audio service. Depending on the values of `use_automatic_gain_control` and
  // `use_echo_canceller`, automatic gain control and echo cancelation will be
  // used respectively.
  // The captured audio frames from this device will be mixed with the captured
  // audio frames from all audio capturers managed by this mixer. The mixed
  // output will be provided to the client via the `callback` given to the
  // constructor.
  void AddAudioCapturer(std::string_view device_id,
                        mojo::PendingRemote<media::mojom::AudioStreamFactory>
                            audio_stream_factory,
                        bool use_automatic_gain_control,
                        bool use_echo_canceller);

  // Starts and stops capturing the so-far added audio capturers. `callback`
  // will be called repeatedly to provide the output mixed audio buses.
  void Start(OnAudioMixerOutputCallback callback);
  void Stop();

 private:
  friend class RecordingServiceTestApi;
  friend class AudioStreamMixerTest;

  static PassKey PassKeyForTesting();

  // Returns the number of audio capturers managed by this mixer.
  int GetNumberOfCapturers() const;

  // Will be called by the audio capturer that corresponds with the given
  // `audio_stream` to provide an `audio_bus` that was captured at
  // `audio_capture_time`. This `audio_bus` will be appended to `audio_stream`
  // and a mix and consume attempt will be made. If possible, audio frames from
  // all the managed audio streams will be mixed and consumed to provide an
  // output audio bus to the client.
  void OnAudioCaptured(AudioStream* audio_stream,
                       std::unique_ptr<media::AudioBus> audio_bus,
                       base::TimeTicks audio_capture_time);

  // Attempts to mix the available audio frames from all managed audio streams,
  // and if successful, a new audio bus containing the mixed output will be
  // provided to the client. If `flush` is true, all the available frames in all
  // streams will be mixed together and provided to the client regardless of the
  // overlap.
  void MaybeMixAndOutput(bool flush);

  // Creates and returns an audio bus that is big enough to contain all the
  // mixable audio frames from all the managed audio streams.
  // `out_bus_timestamp` will be filled with the timestamp that should be used
  // as the capture time of the mixer bus (which is the timestamp of the
  // earliest audio frame that's being mixed).
  // If nothing can be mixed at the moment (e.g. not all streams have frames),
  // `nullptr` will be returned.
  // If `flush` is true, it returns an audio bus that spans from the beginning
  // of the earliest frame in all streams, to the end of the latest frame in all
  // streams, so that it can be used to mix all frames available in all streams.
  std::unique_ptr<media::AudioBus> CreateMixerBus(
      bool flush,
      base::TimeTicks& out_bus_timestamp) const;

  SEQUENCE_CHECKER(sequence_checker_);

  // The callback that will be called repeatedly to provide the client with the
  // mixed audio buses.
  OnAudioMixerOutputCallback on_mixer_output_callback_;

  // A list of audio capturers and their corresponding audio streams.
  std::vector<std::unique_ptr<AudioStream>> streams_;
  std::vector<std::unique_ptr<capture_mode::AudioCapturer>> audio_capturers_;

  base::WeakPtrFactory<AudioStreamMixer> weak_ptr_factory_{this};
};

}  // namespace recording

#endif  // CHROMEOS_ASH_SERVICES_RECORDING_AUDIO_STREAM_MIXER_H_