chromium/chromecast/media/cma/backend/alsa/scoped_alsa_mixer.cc

// Copyright 2022 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/media/cma/backend/alsa/scoped_alsa_mixer.h"

#include "base/check.h"
#include "base/logging.h"
#include "base/task/current_thread.h"

#define ALSA_ASSERT(func, ...)                                        \
  do {                                                                \
    int err = alsa_->func(__VA_ARGS__);                               \
    LOG_ASSERT(err >= 0) << #func " error: " << alsa_->StrError(err); \
  } while (0)

namespace chromecast {
namespace media {

ScopedAlsaMixer::ScopedAlsaMixer(::media::AlsaWrapper* alsa,
                                 const std::string& mixer_device_name,
                                 const std::string& mixer_element_name)
    : alsa_(alsa),
      mixer_device_name_(mixer_device_name),
      mixer_element_name_(mixer_element_name) {
  DCHECK(alsa_);
  RefreshElement();
}

ScopedAlsaMixer::~ScopedAlsaMixer() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  file_descriptor_watchers_.clear();

  if (element) {
    alsa_->MixerElemSetCallback(element, nullptr);
    alsa_->MixerElemSetCallbackPrivate(element, nullptr);
  }
  if (mixer_) {
    alsa_->MixerClose(mixer_);
  }
}

snd_mixer_t* ScopedAlsaMixer::GetMixerForTest() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return mixer_;
}

void ScopedAlsaMixer::RefreshElement() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (mixer_) {
    alsa_->MixerClose(mixer_);
    DVLOG(2) << "Reopening mixer element \"" << mixer_element_name_
             << "\" on device \"" << mixer_device_name_ << "\"";
  } else {
    LOG(INFO) << "Opening mixer element \"" << mixer_element_name_
              << "\" on device \"" << mixer_device_name_ << "\"";
  }
  mixer_ = nullptr;
  element = nullptr;
  int alsa_err = alsa_->MixerOpen(&mixer_, 0);
  if (alsa_err < 0) {
    LOG(ERROR) << "MixerOpen error: " << alsa_->StrError(alsa_err);
    mixer_ = nullptr;
    return;
  }
  alsa_err = alsa_->MixerAttach(mixer_, mixer_device_name_.c_str());
  if (alsa_err < 0) {
    LOG(ERROR) << "MixerAttach error: " << alsa_->StrError(alsa_err);
    alsa_->MixerClose(mixer_);
    mixer_ = nullptr;
    return;
  }
  ALSA_ASSERT(MixerElementRegister, mixer_, nullptr, nullptr);
  alsa_err = alsa_->MixerLoad(mixer_);
  if (alsa_err < 0) {
    LOG(ERROR) << "MixerLoad error: " << alsa_->StrError(alsa_err);
    alsa_->MixerClose(mixer_);
    mixer_ = nullptr;
    return;
  }
  snd_mixer_selem_id_t* sid = nullptr;
  ALSA_ASSERT(MixerSelemIdMalloc, &sid);
  alsa_->MixerSelemIdSetIndex(sid, 0);
  alsa_->MixerSelemIdSetName(sid, mixer_element_name_.c_str());
  element = alsa_->MixerFindSelem(mixer_, sid);
  if (!element) {
    LOG(ERROR) << "Simple mixer control element \"" << mixer_element_name_
               << "\" not found.";
  }
  alsa_->MixerSelemIdFree(sid);
}

void ScopedAlsaMixer::WatchForEvents(snd_mixer_elem_callback_t cb,
                                     void* cb_private_value) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!element || !cb) {
    return;
  }

  alsa_->MixerElemSetCallback(element, cb);
  alsa_->MixerElemSetCallbackPrivate(element, cb_private_value);

  int num_fds = alsa_->MixerPollDescriptorsCount(mixer_);
  DCHECK_GT(num_fds, 0);
  struct pollfd pfds[num_fds];
  num_fds = alsa_->MixerPollDescriptors(mixer_, pfds, num_fds);
  DCHECK_GT(num_fds, 0);
  file_descriptor_watchers_.clear();
  for (int i = 0; i < num_fds; ++i) {
    auto watcher =
        std::make_unique<base::MessagePumpForIO::FdWatchController>(FROM_HERE);
    base::CurrentIOThread::Get()->WatchFileDescriptor(
        pfds[i].fd, true /* persistent */, base::MessagePumpForIO::WATCH_READ,
        watcher.get(), this);
    file_descriptor_watchers_.push_back(std::move(watcher));
  }
}

void ScopedAlsaMixer::OnFileCanReadWithoutBlocking(int fd) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!mixer_) {
    return;
  }
  alsa_->MixerHandleEvents(mixer_);
}

void ScopedAlsaMixer::OnFileCanWriteWithoutBlocking(int fd) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Nothing to do.
}

}  // namespace media
}  // namespace chromecast