blob: 4d9e319a7e6f48530089f1282473b4c59089ceff [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/examples/media/simple_sine/simple_sine.h"
#include "lib/async-loop/cpp/loop.h"
#include "lib/fxl/logging.h"
namespace {
// Set the AudioRenderer stream type to: 48 kHz, mono, 32-bit float.
constexpr float kFrameRate = 48000.0f;
// This example feeds the system 1 second of audio, in 10-millisecond payloads.
constexpr size_t kNumPayloads = 100;
constexpr size_t kFramesPerPayload = kFrameRate / kNumPayloads;
// Play a 439 Hz sine wave at 1/8 of full-scale volume.
constexpr double kFrequency = 439.0;
constexpr double kAmplitude = 0.125;
} // namespace
namespace examples {
MediaApp::MediaApp(fit::closure quit_callback)
: quit_callback_(std::move(quit_callback)) {
FXL_DCHECK(quit_callback_);
}
// Prepare for playback, submit initial data and start the presentation timeline
void MediaApp::Run(component::StartupContext* app_context) {
AcquireAudioRenderer(app_context);
SetStreamType();
if (CreateMemoryMapping() != ZX_OK) {
Shutdown();
return;
}
WriteAudioIntoBuffer();
for (size_t payload_num = 0; payload_num < kNumPayloads; ++payload_num) {
SendPacket(CreatePacket(payload_num));
}
// By not explicitly setting timestamp values for reference clock or media
// clock, we indicate that we want to start playback, with default timing.
// I.e., at a system reference_time of "as soon as safely possible", we will
// present audio corresponding to an initial media_time (PTS) of zero.
//
// AudioRenderer defaults to unity gain, unmuted; we need not change our
// volume. (Although not shown here, we would do so via the GainControl
// interface.)
audio_renderer_->PlayNoReply(fuchsia::media::NO_TIMESTAMP,
fuchsia::media::NO_TIMESTAMP);
}
// Use StartupContext to acquire AudioPtr, which we only need in order to get
// an AudioRendererPtr. Set an error handler, in case of channel closure.
void MediaApp::AcquireAudioRenderer(component::StartupContext* app_context) {
fuchsia::media::AudioPtr audio =
app_context->ConnectToEnvironmentService<fuchsia::media::Audio>();
audio->CreateAudioRenderer(audio_renderer_.NewRequest());
audio_renderer_.set_error_handler([this](zx_status_t status) {
FXL_LOG(ERROR)
<< "fuchsia::media::AudioRenderer connection lost. Quitting.";
Shutdown();
});
}
// Set the AudioRenderer's audio stream_type: mono 48kHz 32-bit float.
void MediaApp::SetStreamType() {
FXL_DCHECK(audio_renderer_);
fuchsia::media::AudioStreamType stream_type;
stream_type.sample_format = fuchsia::media::AudioSampleFormat::FLOAT;
stream_type.channels = 1;
stream_type.frames_per_second = kFrameRate;
audio_renderer_->SetPcmStreamType(std::move(stream_type));
}
// Create a Virtual Memory Object, and map enough memory for audio buffers.
// Send a reduced-rights handle to AudioRenderer to act as a shared buffer.
zx_status_t MediaApp::CreateMemoryMapping() {
zx::vmo payload_vmo;
payload_size_ = kFramesPerPayload * sizeof(float);
total_mapping_size_ = payload_size_ * kNumPayloads;
zx_status_t status = payload_buffer_.CreateAndMap(
total_mapping_size_, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr,
&payload_vmo, ZX_RIGHT_READ | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "VmoMapper:::CreateAndMap failed - " << status;
return status;
}
audio_renderer_->AddPayloadBuffer(0, std::move(payload_vmo));
return ZX_OK;
}
// Write a sine wave into our buffer; we'll submit packets that point to it.
void MediaApp::WriteAudioIntoBuffer() {
float* float_buffer = reinterpret_cast<float*>(payload_buffer_.start());
for (size_t frame = 0; frame < kFramesPerPayload * kNumPayloads; ++frame) {
float_buffer[frame] =
kAmplitude * sin(frame * kFrequency * 2 * M_PI / kFrameRate);
}
}
// We divide our cross-proc buffer into different zones, called payloads.
// Create a packet that corresponds to this particular payload.
// By specifying NO_TIMESTAMP for each packet's presentation timestamp, we rely
// on the AudioRenderer to treat the sequence of packets as a contiguous
// unbroken stream of audio. We just need to make sure we present packets early
// enough, and for this example we actually submit all packets before starting
// playback.
fuchsia::media::StreamPacket MediaApp::CreatePacket(size_t payload_num) {
fuchsia::media::StreamPacket packet;
// leave packet.pts as the default (fuchsia::media::NO_TIMESTAMP)
// leave packet.payload_buffer_id as default (0): we only map a single buffer
packet.payload_offset = (payload_num * payload_size_) % total_mapping_size_;
packet.payload_size = payload_size_;
return packet;
}
// Submit a packet, incrementing our count of packets sent. When it returns:
// a. if there are more packets to send, create and send the next packet;
// b. if all expected packets have completed, begin closing down the system.
void MediaApp::SendPacket(fuchsia::media::StreamPacket packet) {
++num_packets_sent_;
audio_renderer_->SendPacket(std::move(packet),
[this]() { OnSendPacketComplete(); });
}
void MediaApp::OnSendPacketComplete() {
++num_packets_completed_;
FXL_DCHECK(num_packets_completed_ <= kNumPayloads);
if (num_packets_sent_ < kNumPayloads) {
SendPacket(CreatePacket(num_packets_sent_));
} else if (num_packets_completed_ >= kNumPayloads) {
Shutdown();
}
}
// Unmap memory, quit message loop (FIDL interfaces auto-delete upon ~MediaApp).
void MediaApp::Shutdown() {
payload_buffer_.Unmap();
quit_callback_();
}
} // namespace examples
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToThread);
auto startup_context = component::StartupContext::CreateFromStartupInfo();
examples::MediaApp media_app([&loop]() {
async::PostTask(loop.dispatcher(), [&loop]() { loop.Quit(); });
});
media_app.Run(startup_context.get());
loop.Run(); // Now wait for the message loop to return...
return 0;
}