#include "chromecast/media/audio/cast_audio_manager_alsa.h"
#include <string_view>
#include <utility>
#include "base/logging.h"
#include "base/memory/free_deleter.h"
#include "base/notimplemented.h"
#include "base/strings/string_view_util.h"
#include "base/task/single_thread_task_runner.h"
#include "chromecast/media/api/cma_backend_factory.h"
#include "chromecast/media/audio/audio_buildflags.h"
#include "chromecast/media/audio/cast_audio_input_stream.h"
#include "media/audio/alsa/alsa_input.h"
#include "media/audio/alsa/alsa_wrapper.h"
namespace chromecast {
namespace media {
namespace {
const int kDefaultSampleRate = BUILDFLAG(AUDIO_INPUT_SAMPLE_RATE);
const int kDefaultInputBufferSize = 1024;
#if BUILDFLAG(ENABLE_AUDIO_CAPTURE_SERVICE)
const int kCommunicationsSampleRate = 16000;
const int kCommunicationsInputBufferSize = 160;
#endif
constexpr std::string_view kInvalidAudioInputDevices[] = {
"default",
"dmix",
"null",
"communications",
};
constexpr char kPcmInterfaceName[] = "pcm";
constexpr char kIoHintName[] = "IOID";
constexpr char kNameHintName[] = "NAME";
constexpr char kDescriptionHintName[] = "DESC";
bool IsAlsaDeviceAvailable(CastAudioManagerAlsa::StreamType type,
const char* device_name) {
if (!device_name)
return false;
if (type == CastAudioManagerAlsa::kStreamCapture) {
for (const auto& invalid_audio_input_device : kInvalidAudioInputDevices) {
if (invalid_audio_input_device == device_name) {
return false;
}
}
return true;
} else {
DCHECK_EQ(CastAudioManagerAlsa::kStreamPlayback, type);
const std::string kDeviceTypeDesired = "plughw";
return kDeviceTypeDesired == device_name;
}
}
std::string UnwantedDeviceTypeWhenEnumerating(
CastAudioManagerAlsa::StreamType wanted_type) {
return wanted_type == CastAudioManagerAlsa::kStreamPlayback ? "Input"
: "Output";
}
}
CastAudioManagerAlsa::CastAudioManagerAlsa(
std::unique_ptr<::media::AudioThread> audio_thread,
::media::AudioLogFactory* audio_log_factory,
CastAudioManagerHelper::Delegate* delegate,
base::RepeatingCallback<CmaBackendFactory*()> backend_factory_getter,
scoped_refptr<base::SingleThreadTaskRunner> browser_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
bool use_mixer)
: CastAudioManager(std::move(audio_thread),
audio_log_factory,
delegate,
std::move(backend_factory_getter),
browser_task_runner,
media_task_runner,
use_mixer),
wrapper_(new ::media::AlsaWrapper()) {}
CastAudioManagerAlsa::~CastAudioManagerAlsa() {}
bool CastAudioManagerAlsa::HasAudioInputDevices() {
return true;
}
void CastAudioManagerAlsa::GetAudioInputDeviceNames(
::media::AudioDeviceNames* device_names) {
DCHECK(device_names->empty());
device_names->push_front(::media::AudioDeviceName::CreateDefault());
#if BUILDFLAG(ENABLE_AUDIO_CAPTURE_SERVICE)
device_names->push_back(::media::AudioDeviceName::CreateCommunications());
#endif
GetAlsaAudioDevices(kStreamCapture, device_names);
}
::media::AudioParameters CastAudioManagerAlsa::GetInputStreamParameters(
const std::string& device_id) {
if (device_id == ::media::AudioDeviceDescription::kCommunicationsDeviceId) {
#if !BUILDFLAG(ENABLE_AUDIO_CAPTURE_SERVICE)
NOTIMPLEMENTED()
<< "Capture Service is not enabled, return a fake AudioParameters.";
return ::media::AudioParameters();
#else
return ::media::AudioParameters(::media::AudioParameters::AUDIO_PCM_LINEAR,
::media::CHANNEL_LAYOUT_MONO,
kCommunicationsSampleRate,
kCommunicationsInputBufferSize);
#endif
}
return ::media::AudioParameters(
::media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
::media::ChannelLayoutConfig::Stereo(), kDefaultSampleRate,
kDefaultInputBufferSize);
}
::media::AudioInputStream* CastAudioManagerAlsa::MakeLinearInputStream(
const ::media::AudioParameters& params,
const std::string& device_id,
const ::media::AudioManager::LogCallback& log_callback) {
DCHECK_EQ(::media::AudioParameters::AUDIO_PCM_LINEAR, params.format());
return MakeInputStream(params, device_id);
}
::media::AudioInputStream* CastAudioManagerAlsa::MakeLowLatencyInputStream(
const ::media::AudioParameters& params,
const std::string& device_id,
const ::media::AudioManager::LogCallback& log_callback) {
DCHECK_EQ(::media::AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
return MakeInputStream(params, device_id);
}
::media::AudioInputStream* CastAudioManagerAlsa::MakeInputStream(
const ::media::AudioParameters& params,
const std::string& device_id) {
std::string device_name =
(device_id == ::media::AudioDeviceDescription::kDefaultDeviceId)
? ::media::AlsaPcmInputStream::kAutoSelectDevice
: device_id;
if (device_name == ::media::AudioDeviceDescription::kCommunicationsDeviceId) {
#if !BUILDFLAG(ENABLE_AUDIO_CAPTURE_SERVICE)
NOTIMPLEMENTED() << "Capture Service is not enabled, return nullptr.";
return nullptr;
#else
return new CastAudioInputStream(this, params, device_name);
#endif
}
return new ::media::AlsaPcmInputStream(this, device_name, params,
wrapper_.get());
}
void CastAudioManagerAlsa::GetAlsaAudioDevices(
StreamType type,
::media::AudioDeviceNames* device_names) {
int card = -1;
while (!wrapper_->CardNext(&card) && card >= 0) {
void** hints = NULL;
int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
if (!error) {
GetAlsaDevicesInfo(type, hints, device_names);
wrapper_->DeviceNameFreeHint(hints);
} else {
DLOG(WARNING) << "GetAlsaAudioDevices: unable to get device hints: "
<< wrapper_->StrError(error);
}
}
}
void CastAudioManagerAlsa::GetAlsaDevicesInfo(
StreamType type,
void** hints,
::media::AudioDeviceNames* device_names) {
const std::string unwanted_device_type =
UnwantedDeviceTypeWhenEnumerating(type);
for (void** hint_iter = hints; *hint_iter != NULL; UNSAFE_TODO(hint_iter++)) {
auto io = wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName);
if (unwanted_device_type == base::as_string_view(io)) {
continue;
}
auto unique_device_name =
wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName);
if (IsAlsaDeviceAvailable(type, unique_device_name.data())) {
auto desc = wrapper_->DeviceNameGetHint(*hint_iter, kDescriptionHintName);
::media::AudioDeviceName name;
name.unique_id = base::as_string_view(unique_device_name);
if (!desc.empty()) {
std::ranges::replace(desc, '\n', '-');
name.device_name = base::as_string_view(desc);
} else {
name.device_name = base::as_string_view(unique_device_name);
}
device_names->push_back(name);
}
}
}
}
}