chromium/chromecast/media/cma/backend/desktop/volume_control_desktop.cc

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

#include "chromecast/public/volume_control.h"

#include <algorithm>
#include <map>
#include <string>
#include <vector>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/message_loop/message_pump_type.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/synchronization/lock.h"
#include "base/threading/thread.h"

namespace chromecast {
namespace media {

namespace {

const float kMinVolumeDbfs = -60.0f;
const float kMaxVolumeDbfs = 0.0f;

class VolumeControlInternal {
 public:
  VolumeControlInternal() : thread_("VolumeControl") {
    for (auto type :
         {AudioContentType::kMedia, AudioContentType::kAlarm,
          AudioContentType::kCommunication, AudioContentType::kOther}) {
      volumes_[type] = 1.0f;
      muted_[type] = false;
    }

    base::Thread::Options options;
    options.message_pump_type = base::MessagePumpType::IO;
    thread_.StartWithOptions(std::move(options));
  }

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

  ~VolumeControlInternal() = default;

  void AddVolumeObserver(VolumeObserver* observer) {
    base::AutoLock lock(observer_lock_);
    volume_observers_.push_back(observer);
  }

  void RemoveVolumeObserver(VolumeObserver* observer) {
    base::AutoLock lock(observer_lock_);
    volume_observers_.erase(std::remove(volume_observers_.begin(),
                                        volume_observers_.end(), observer),
                            volume_observers_.end());
  }

  float GetVolume(AudioContentType type) {
    base::AutoLock lock(volume_lock_);
    return volumes_[type];
  }

  void SetVolume(media::VolumeChangeSource source,
                 AudioContentType type,
                 float level) {
    if (type == AudioContentType::kOther) {
      NOTREACHED_IN_MIGRATION() << "Can't set volume for content type kOther";
      return;
    }

    level = std::clamp(level, 0.0f, 1.0f);
    thread_.task_runner()->PostTask(
        FROM_HERE, base::BindOnce(&VolumeControlInternal::SetVolumeOnThread,
                                  base::Unretained(this), source, type, level));
  }

  bool IsMuted(AudioContentType type) {
    base::AutoLock lock(volume_lock_);
    return muted_[type];
  }

  void SetMuted(media::VolumeChangeSource source,
                AudioContentType type,
                bool muted) {
    if (type == AudioContentType::kOther) {
      NOTREACHED_IN_MIGRATION()
          << "Can't set mute state for content type kOther";
      return;
    }

    thread_.task_runner()->PostTask(
        FROM_HERE, base::BindOnce(&VolumeControlInternal::SetMutedOnThread,
                                  base::Unretained(this), source, type, muted));
  }

 private:
  void SetVolumeOnThread(media::VolumeChangeSource source,
                         AudioContentType type,
                         float level) {
    DCHECK(thread_.task_runner()->BelongsToCurrentThread());
    DCHECK(type != AudioContentType::kOther);

    {
      base::AutoLock lock(volume_lock_);
      if (level == volumes_[type]) {
        return;
      }
      volumes_[type] = level;
    }

    {
      base::AutoLock lock(observer_lock_);
      for (VolumeObserver* observer : volume_observers_) {
        observer->OnVolumeChange(source, type, level);
      }
    }
  }

  void SetMutedOnThread(media::VolumeChangeSource source,
                        AudioContentType type,
                        bool muted) {
    DCHECK(thread_.task_runner()->BelongsToCurrentThread());
    DCHECK(type != AudioContentType::kOther);

    {
      base::AutoLock lock(volume_lock_);
      if (muted == muted_[type]) {
        return;
      }
      muted_[type] = muted;
    }

    {
      base::AutoLock lock(observer_lock_);
      for (VolumeObserver* observer : volume_observers_) {
        observer->OnMuteChange(source, type, muted);
      }
    }
  }

  base::Lock volume_lock_;
  std::map<AudioContentType, float> volumes_;
  std::map<AudioContentType, bool> muted_;

  base::Lock observer_lock_;
  std::vector<VolumeObserver*> volume_observers_;

  base::Thread thread_;
};

VolumeControlInternal& GetVolumeControl() {
  static base::NoDestructor<VolumeControlInternal> g_volume_control;
  return *g_volume_control;
}

}  // namespace

// static
void VolumeControl::Initialize(const std::vector<std::string>& argv) {
  GetVolumeControl();
}

// static
void VolumeControl::Finalize() {}

// static
void VolumeControl::AddVolumeObserver(VolumeObserver* observer) {
  GetVolumeControl().AddVolumeObserver(observer);
}

// static
void VolumeControl::RemoveVolumeObserver(VolumeObserver* observer) {
  GetVolumeControl().RemoveVolumeObserver(observer);
}

// static
float VolumeControl::GetVolume(AudioContentType type) {
  return GetVolumeControl().GetVolume(type);
}

// static
void VolumeControl::SetVolume(media::VolumeChangeSource source,
                              AudioContentType type,
                              float level) {
  GetVolumeControl().SetVolume(source, type, level);
}

// static
bool VolumeControl::IsMuted(AudioContentType type) {
  return GetVolumeControl().IsMuted(type);
}

// static
void VolumeControl::SetMuted(media::VolumeChangeSource source,
                             AudioContentType type,
                             bool muted) {
  GetVolumeControl().SetMuted(source, type, muted);
}

// static
void VolumeControl::SetOutputLimit(AudioContentType type, float limit) {}

// static
float VolumeControl::VolumeToDbFS(float volume) {
  volume = std::clamp(volume, 0.0f, 1.0f);
  return kMinVolumeDbfs + volume * (kMaxVolumeDbfs - kMinVolumeDbfs);
}

// static
float VolumeControl::DbFSToVolume(float db) {
  db = std::clamp(db, kMinVolumeDbfs, kMaxVolumeDbfs);
  return (db - kMinVolumeDbfs) / (kMaxVolumeDbfs - kMinVolumeDbfs);
}

}  // namespace media
}  // namespace chromecast