blob: 9b4f1c53192e066ea326ce75f06765ba04e13d3e [file] [log] [blame]
// Copyright 2015 The Chromium 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 <math.h>
#include <memory>
#include "mojo/public/c/system/main.h"
#include "mojo/public/cpp/application/application_impl_base.h"
#include "mojo/public/cpp/application/connect.h"
#include "mojo/public/cpp/application/run_application.h"
#include "mojo/public/cpp/utility/run_loop.h"
#include "mojo/services/media/audio/interfaces/audio_server.mojom.h"
#include "mojo/services/media/audio/interfaces/audio_track.mojom.h"
#include "mojo/services/media/common/cpp/circular_buffer_media_pipe_adapter.h"
#include "mojo/services/media/common/cpp/linear_transform.h"
#include "mojo/services/media/common/cpp/local_time.h"
#include "mojo/services/media/common/interfaces/timelines.mojom.h"
#include "mojo/services/media/core/interfaces/media_renderer.mojom.h"
namespace mojo {
namespace media {
namespace audio {
namespace examples {
static constexpr uint32_t SAMP_FREQ = 48000;
static constexpr uint32_t CHUNK_USEC = 10000;
static constexpr uint32_t BUF_LO_WATER_USEC = 500000;
static constexpr uint32_t BUF_HI_WATER_USEC = BUF_LO_WATER_USEC
+ (4 * CHUNK_USEC);
static constexpr uint32_t BUF_DEPTH_USEC = BUF_HI_WATER_USEC
+ (4 * CHUNK_USEC);
static constexpr uint32_t FRAME_BYTES = sizeof(int16_t);
static inline constexpr uint32_t USecToBytes(uint64_t usec) {
return ((usec * SAMP_FREQ) / 1000000) * FRAME_BYTES;
}
class PlayToneApp : public ApplicationImplBase {
public:
~PlayToneApp() override { OnQuit(); }
// ApplicationImplBase overrides:
void OnInitialize() override;
void OnQuit() override;
private:
void GenerateToneCbk(MediaResult res);
void PlayTone(double freq_hz, double amplitude, double duration_sec);
void PostShutdown();
void Shutdown();
void OnConnectionError(const std::string& connection_name);
AudioServerPtr audio_server_;
AudioTrackPtr audio_track_;
MediaRendererPtr media_renderer_;
TimelineConsumerPtr timeline_consumer_;
std::unique_ptr<CircularBufferMediaPipeAdapter> audio_pipe_;
bool clock_started_ = false;
uint64_t media_time_ = 0;
double freq_hz_ = 440.0;
double amplitude_ = 1.0;
bool shutting_down_ = false;
};
void PlayToneApp::OnQuit() {
timeline_consumer_.reset();
audio_pipe_.reset();
audio_track_.reset();
media_renderer_.reset();
audio_server_.reset();
}
void PlayToneApp::OnInitialize() {
mojo::ConnectToService(shell(), "mojo:audio_server",
GetProxy(&audio_server_));
audio_server_.set_connection_error_handler([this]() {
OnConnectionError("audio_server");
});
audio_server_->CreateTrack(
GetProxy(&audio_track_), GetProxy(&media_renderer_));
audio_track_.set_connection_error_handler([this]() {
OnConnectionError("audio_track");
});
media_renderer_.set_connection_error_handler([this]() {
OnConnectionError("media_renderer");
});
// Query the sink's format capabilities.
Array<MediaTypeSetPtr> supported_media_types;
auto desc_cbk = [&supported_media_types](Array<MediaTypeSetPtr> desc) {
supported_media_types = desc.Pass();
};
media_renderer_->GetSupportedMediaTypes(desc_cbk);
// TODO(johngro): this pattern is awkward. We really don't want to be
// calling WaitForIncomingResponse, even if we were able supply a timeout.
// The best practice would be to defer to a handler for the message we are
// expecting to eventually come back.
//
// But... what if the message never comes back? Perhaps the service is not
// implemented properly, or perhaps the service is malicious. We could
// queue a delayed message on our run loop which indicates a timeout, but
// then what happens when when the response to Describe comes back (as
// expected). We don't really have a good way to cancel the "timeout"
// message once we have queued it. Maintaining all of the bookkeeping
// required to nerf the callback when it happens and is discovered to be
// useless is going to get very old, very fast.
//
// For now, we just do the evil thing and block during init, but I sure do
// wish there was something nicer we could do.
if (!media_renderer_.WaitForIncomingResponse()) {
MOJO_LOG(ERROR)
<< "Failed to fetch sync capabilities; no response received.";
Shutdown();
return;
}
// TODO(johngro): do something useful with our capabilities description.
supported_media_types.reset();
// Grab the timeline consumer interface for our audio renderer.
MediaTimelineControlPointPtr timeline_control_point;
media_renderer_->GetTimelineControlPoint(GetProxy(&timeline_control_point));
timeline_control_point->GetTimelineConsumer(GetProxy(&timeline_consumer_));
timeline_consumer_.set_connection_error_handler(
[this]() { OnConnectionError("timeline_consumer"); });
// Configure our sink for 16-bit 48KHz mono.
AudioMediaTypeDetailsPtr pcm_cfg = AudioMediaTypeDetails::New();
pcm_cfg->sample_format = AudioSampleFormat::SIGNED_16;
pcm_cfg->channels = 1;
pcm_cfg->frames_per_second = SAMP_FREQ;
MediaTypePtr media_type = MediaType::New();
media_type->medium = MediaTypeMedium::AUDIO;
media_type->details = MediaTypeDetails::New();
media_type->details->set_audio(pcm_cfg.Pass());
media_type->encoding = MediaType::kAudioEncodingLpcm;
media_renderer_->SetMediaType(media_type.Pass());
MediaPacketConsumerPtr pipe;
media_renderer_->GetPacketConsumer(GetProxy(&pipe));
// Now that the configuration request is in-flight and we our media pipe
// proxy, pass its interface to our circular buffer helper, set up our
// high/low water marks, register our callback, and start to buffer our audio.
audio_pipe_.reset(new CircularBufferMediaPipeAdapter(pipe.Pass()));
audio_pipe_->Init(USecToBytes(BUF_DEPTH_USEC));
audio_pipe_->SetSignalCallback(
[this](MediaResult res) -> void {
GenerateToneCbk(res);
});
audio_pipe_->SetWatermarks(USecToBytes(BUF_HI_WATER_USEC),
USecToBytes(BUF_LO_WATER_USEC));
}
void PlayToneApp::GenerateToneCbk(MediaResult res) {
using MappedPacket = CircularBufferMediaPipeAdapter::MappedPacket;
MappedPacket mapped_pkt;
MOJO_DCHECK(freq_hz_ > 0.0);
MOJO_DCHECK(amplitude_ >= 0.0);
MOJO_DCHECK(amplitude_ <= 1.0);
if (res != MediaResult::OK) {
MOJO_LOG(ERROR) << "Fatal error in media pipe (" << res << ").";
PostShutdown();
return;
}
while (!audio_pipe_->AboveHiWater()) {
res = audio_pipe_->CreateMediaPacket(USecToBytes(CHUNK_USEC),
false,
&mapped_pkt);
if (res != MediaResult::OK) {
MOJO_LOG(ERROR) << "Unexpected error when creating media packet ("
<< res << ").";
PostShutdown();
return;
}
mapped_pkt.packet()->pts = media_time_;
for (uint32_t i = 0; i < MappedPacket::kMaxRegions; ++i) {
int16_t* data = reinterpret_cast<int16_t*>(mapped_pkt.data(i));
uint64_t len;
if (!data) continue;
len = mapped_pkt.length(i);
MOJO_DCHECK(len && !(len % FRAME_BYTES));
len /= FRAME_BYTES;
for (uint64_t i = 0; i < len; ++i, ++media_time_) {
double tmp = ((M_PI * 2.0) / SAMP_FREQ) * freq_hz_ * media_time_;
data[i] = std::numeric_limits<int16_t>::max() * amplitude_ * sin(tmp);
}
}
res = audio_pipe_->SendMediaPacket(&mapped_pkt);
if (res != MediaResult::OK) {
MOJO_LOG(ERROR) << "Unexpected error when sending media packet ("
<< res << ").";
audio_pipe_->CancelMediaPacket(&mapped_pkt);
PostShutdown();
return;
}
}
if (!clock_started_) {
MOJO_LOG(INFO) << "Setting rate 1/1";
TimelineTransformPtr timeline_transform = TimelineTransform::New();
timeline_transform->reference_time = kUnspecifiedTime;
timeline_transform->subject_time = kUnspecifiedTime;
timeline_transform->reference_delta = 1;
timeline_transform->subject_delta = 1;
timeline_consumer_->SetTimelineTransform(
timeline_transform.Pass(),
[](bool completed) {});
clock_started_ = true;
}
}
void PlayToneApp::OnConnectionError(const std::string& connection_name) {
if (!shutting_down_) {
MOJO_LOG(ERROR) << connection_name << " connection closed unexpectedly!";
Shutdown();
}
}
void PlayToneApp::PostShutdown() {
if (audio_pipe_) {
audio_pipe_->ResetSignalCallback();
}
audio_pipe_ = nullptr;
mojo::RunLoop::current()->PostDelayedTask([this]() -> void {
Shutdown();
}, 0);
}
void PlayToneApp::Shutdown() {
OnQuit();
RunLoop::current()->Quit();
}
} // namespace examples
} // namespace audio
} // namespace media
} // namespace mojo
MojoResult MojoMain(MojoHandle app_request) {
mojo::media::audio::examples::PlayToneApp play_tone_app;
return mojo::RunApplication(app_request, &play_tone_app);
}