blob: a02981af175b62c3846ba4abc187a9e1bd63de37 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/fuchsia/audio_input_stream_fuchsia.h"
#include <lib/sys/cpp/component_context.h>
#include <lib/zx/vmo.h>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/process_context.h"
#include "base/logging.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/fuchsia/audio_manager_fuchsia.h"
namespace media {
namespace {
// Currently AudioCapturer supports only one payload buffer with id=0.
constexpr uint32_t kBufferId = 0;
// Number of audio packets that should fit in the capture buffer.
constexpr size_t kBufferPacketCapacity = 10;
} // namespace
AudioInputStreamFuchsia::AudioInputStreamFuchsia(
AudioManagerFuchsia* manager,
const AudioParameters& parameters,
std::string device_id)
: manager_(manager),
parameters_(parameters),
device_id_(std::move(device_id)) {
DCHECK(device_id_.empty() ||
device_id_ == AudioDeviceDescription::kLoopbackInputDeviceId ||
device_id_ == AudioDeviceDescription::kDefaultDeviceId)
<< "AudioInput from " << device_id_ << " not implemented!";
DCHECK(parameters_.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY ||
parameters_.format() == AudioParameters::AUDIO_PCM_LINEAR);
}
AudioInputStreamFuchsia::~AudioInputStreamFuchsia() = default;
AudioInputStream::OpenOutcome AudioInputStreamFuchsia::Open() {
// Open() can be called only once.
DCHECK(!capturer_);
auto factory = base::ComponentContextForProcess()
->svc()
->Connect<fuchsia::media::Audio>();
bool is_loopback =
device_id_ == AudioDeviceDescription::kLoopbackInputDeviceId;
factory->CreateAudioCapturer(capturer_.NewRequest(), is_loopback);
capturer_.set_error_handler([this](zx_status_t status) {
ZX_LOG(ERROR, status) << "AudioCapturer disconnected";
ReportError();
});
// Bind the event for incoming packets.
capturer_.events().OnPacketProduced =
fit::bind_member(this, &AudioInputStreamFuchsia::OnPacketProduced);
// Configure stream format.
fuchsia::media::AudioStreamType stream_type;
stream_type.sample_format = fuchsia::media::AudioSampleFormat::FLOAT;
stream_type.channels = parameters_.channels();
stream_type.frames_per_second = parameters_.sample_rate();
capturer_->SetPcmStreamType(std::move(stream_type));
// Allocate shared buffer.
size_t capture_buffer_size =
parameters_.GetBytesPerBuffer(kSampleFormatF32) * kBufferPacketCapacity;
capture_buffer_size = base::bits::AlignUp(capture_buffer_size, ZX_PAGE_SIZE);
zx::vmo buffer_vmo;
zx_status_t status = zx::vmo::create(capture_buffer_size, 0, &buffer_vmo);
ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create";
constexpr char kName[] = "cr-audio-capturer";
status = buffer_vmo.set_property(ZX_PROP_NAME, kName, std::size(kName) - 1);
ZX_DCHECK(status == ZX_OK, status);
bool mapped =
capture_buffer_.Initialize(std::move(buffer_vmo), /*writable=*/false,
/*offset=*/0, /*size=*/capture_buffer_size,
fuchsia::sysmem::CoherencyDomain::CPU);
if (!mapped)
return OpenOutcome::kFailed;
// Pass the buffer to the capturer.
capturer_->AddPayloadBuffer(kBufferId,
capture_buffer_.Duplicate(/*writable=*/true));
return OpenOutcome::kSuccess;
}
void AudioInputStreamFuchsia::Start(AudioInputCallback* callback) {
if (!capturer_) {
callback->OnError();
return;
}
callback_ = callback;
if (!is_capturer_started_) {
is_capturer_started_ = true;
capturer_->StartAsyncCapture(parameters_.frames_per_buffer());
}
}
void AudioInputStreamFuchsia::Stop() {
// Normally Close() is called immediately after Stop(), so there is no need to
// stop the capturer. Just release the |callback_| to ensure it's not called
// again.
callback_ = nullptr;
}
void AudioInputStreamFuchsia::Close() {
Stop();
if (manager_)
manager_->ReleaseInputStream(this);
}
double AudioInputStreamFuchsia::GetMaxVolume() {
return 1.0;
}
void AudioInputStreamFuchsia::SetVolume(double volume) {
NOTIMPLEMENTED();
}
double AudioInputStreamFuchsia::GetVolume() {
return 1.0;
}
bool AudioInputStreamFuchsia::SetAutomaticGainControl(bool enabled) {
NOTIMPLEMENTED();
return false;
}
bool AudioInputStreamFuchsia::GetAutomaticGainControl() {
return false;
}
bool AudioInputStreamFuchsia::IsMuted() {
return false;
}
void AudioInputStreamFuchsia::SetOutputDeviceForAec(
const std::string& output_device_id) {
NOTIMPLEMENTED();
}
void AudioInputStreamFuchsia::OnPacketProduced(
fuchsia::media::StreamPacket packet) {
size_t bytes_per_frame = parameters_.GetBytesPerFrame(kSampleFormatF32);
if (packet.payload_buffer_id != kBufferId ||
packet.payload_offset + packet.payload_size > capture_buffer_.size() ||
packet.payload_size % bytes_per_frame != 0 ||
packet.payload_size < bytes_per_frame) {
LOG(ERROR) << "Received invalid packet from AudioCapturer.";
ReportError();
return;
}
if (callback_) {
int num_frames = packet.payload_size / bytes_per_frame;
if (!audio_bus_ || num_frames != audio_bus_->frames())
audio_bus_ = AudioBus::Create(parameters_.channels(), num_frames);
audio_bus_->FromInterleaved<Float32SampleTypeTraits>(
reinterpret_cast<const float*>(capture_buffer_.GetMemory().data() +
packet.payload_offset),
num_frames);
callback_->OnData(audio_bus_.get(), base::TimeTicks::FromZxTime(packet.pts),
/*volume=*/1.0, {});
}
capturer_->ReleasePacket(std::move(packet));
}
void AudioInputStreamFuchsia::ReportError() {
capturer_.Unbind();
if (callback_)
callback_->OnError();
}
} // namespace media