| Index: examples/audio_play_test/play_wav.cc
|
| diff --git a/examples/audio_play_test/play_wav.cc b/examples/audio_play_test/play_wav.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d4706b02c6ae74464e954831d0b4f0d6b6c4349b
|
| --- /dev/null
|
| +++ b/examples/audio_play_test/play_wav.cc
|
| @@ -0,0 +1,568 @@
|
| +// 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 <memory>
|
| +
|
| +#include "mojo/public/c/system/main.h"
|
| +#include "mojo/public/cpp/application/application_delegate.h"
|
| +#include "mojo/public/cpp/application/application_impl.h"
|
| +#include "mojo/public/cpp/application/application_runner.h"
|
| +#include "mojo/public/cpp/system/data_pipe.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/network/interfaces/network_service.mojom.h"
|
| +#include "mojo/services/network/interfaces/url_loader.mojom.h"
|
| +
|
| +#define PACKED __attribute__((packed))
|
| +
|
| +static inline constexpr uint32_t make_fourcc(uint8_t a, uint8_t b,
|
| + uint8_t c, uint8_t d) {
|
| + return (static_cast<uint32_t>(a) << 24) |
|
| + (static_cast<uint32_t>(b) << 16) |
|
| + (static_cast<uint32_t>(c) << 8) |
|
| + static_cast<uint32_t>(d);
|
| +}
|
| +
|
| +static inline constexpr uint32_t fetch_fourcc(const void* source) {
|
| + return (static_cast<uint32_t>(static_cast<const uint8_t*>(source)[0]) << 24) |
|
| + (static_cast<uint32_t>(static_cast<const uint8_t*>(source)[1]) << 16) |
|
| + (static_cast<uint32_t>(static_cast<const uint8_t*>(source)[2]) << 8) |
|
| + static_cast<uint32_t>(static_cast<const uint8_t*>(source)[3]);
|
| +}
|
| +
|
| +namespace mojo {
|
| +namespace media {
|
| +namespace audio {
|
| +namespace examples {
|
| +
|
| +static constexpr const char* TEST_FILE =
|
| + "http://localhost/test_content/piano2.wav";
|
| +static constexpr uint32_t BUF_DEPTH_USEC = 500000;
|
| +static constexpr uint32_t BUF_LO_WATER_USEC = 400000;
|
| +static constexpr uint32_t BUF_HI_WATER_USEC = 450000;
|
| +static constexpr uint32_t CHUNK_SIZE_USEC = 10000;
|
| +
|
| +class PlayWAVApp : public ApplicationDelegate {
|
| + public:
|
| + ~PlayWAVApp() override { Quit(); }
|
| +
|
| + // ApplicationDelegate
|
| + void Initialize(ApplicationImpl* app) override;
|
| + void Quit() override;
|
| +
|
| + private:
|
| + using AudioPipePtr = std::unique_ptr<CircularBufferMediaPipeAdapter>;
|
| + using AudioPacket = CircularBufferMediaPipeAdapter::MappedPacket;
|
| + using PacketCbk = MediaPipe::SendPacketCallback;
|
| +
|
| + // TODO(johngro): endianness!
|
| + struct PACKED RIFFChunkHeader {
|
| + uint32_t four_cc;
|
| + uint32_t length;
|
| + };
|
| +
|
| + struct PACKED WAVHeader {
|
| + uint32_t wave_four_cc;
|
| + uint32_t fmt_four_cc;
|
| + uint32_t fmt_chunk_len;
|
| + uint16_t format;
|
| + uint16_t channel_count;
|
| + uint32_t frame_rate;
|
| + uint32_t average_byte_rate;
|
| + uint16_t frame_size;
|
| + uint16_t bits_per_sample;
|
| + };
|
| +
|
| + // TODO(johngro): as mentioned before... endianness!
|
| + static constexpr uint32_t RIFF_FOUR_CC = make_fourcc('R', 'I', 'F', 'F');
|
| + static constexpr uint32_t WAVE_FOUR_CC = make_fourcc('W', 'A', 'V', 'E');
|
| + static constexpr uint32_t FMT_FOUR_CC = make_fourcc('f', 'm', 't', ' ');
|
| + static constexpr uint32_t DATA_FOUR_CC = make_fourcc('d', 'a', 't', 'a');
|
| +
|
| + static constexpr uint16_t FORMAT_LPCM = 0x0001;
|
| + static constexpr uint16_t FORMAT_MULAW = 0x0101;
|
| + static constexpr uint16_t FORMAT_ALAW = 0x0102;
|
| + static constexpr uint16_t FORMAT_ADPCM = 0x0103;
|
| +
|
| + static const std::set<std::string> VALID_MIME_TYPES;
|
| + static const std::set<uint16_t> VALID_FRAME_RATES;
|
| + static const std::set<uint16_t> VALID_BITS_PER_SAMPLES;
|
| +
|
| + bool BlockingRead(void* buf, uint32_t len);
|
| + void ProcessHTTPResponse(URLResponsePtr resp);
|
| +
|
| + bool ReadAndValidateRIFFHeader();
|
| + bool ReadAndValidateWAVHeader();
|
| + bool ReadAndValidateDATAHeader();
|
| +
|
| + bool OnNeedsData(MediaResult res);
|
| + void OnPlayoutComplete(MediaResult res);
|
| + void OnConnectionError(const std::string& connection_name);
|
| + void BeginShutdown();
|
| +
|
| + uint32_t USecToFrames(uint32_t usec) {
|
| + uint64_t ret = (static_cast<uint64_t>(usec) * wav_info_.frame_rate)
|
| + / 1000000;
|
| + MOJO_DCHECK(ret < std::numeric_limits<uint32_t>::max());
|
| + return ret;
|
| + }
|
| +
|
| + uint32_t USecToBytes(uint32_t usec) {
|
| + uint32_t frames = USecToFrames(usec);
|
| +
|
| + MOJO_DCHECK(wav_info_.frame_size);
|
| + MOJO_DCHECK(frames <
|
| + std::numeric_limits<uint32_t>::max() / wav_info_.frame_size);
|
| +
|
| + return frames * wav_info_.frame_size;
|
| + }
|
| +
|
| + AudioServerPtr audio_server_;
|
| + AudioTrackPtr audio_track_;
|
| + AudioPipePtr audio_pipe_;
|
| + RateControlPtr rate_control_;
|
| + AudioPacket audio_packet_;
|
| + PacketCbk playout_complete_cbk_;
|
| + NetworkServicePtr network_service_;
|
| + URLLoaderPtr url_loader_;
|
| + ScopedDataPipeConsumerHandle payload_;
|
| + uint32_t payload_len_;
|
| + RIFFChunkHeader riff_hdr_;
|
| + WAVHeader wav_info_;
|
| + RIFFChunkHeader data_hdr_;
|
| + bool sent_first_packet_ = false;
|
| + bool clock_started_ = false;
|
| + bool shutting_down_ = false;
|
| +};
|
| +
|
| +const std::set<std::string> PlayWAVApp::VALID_MIME_TYPES({
|
| + "audio/x-wav",
|
| + "audio/wav",
|
| +});
|
| +
|
| +const std::set<uint16_t> PlayWAVApp::VALID_FRAME_RATES({
|
| + 8000, 16000, 24000, 32000, 48000,
|
| + 11025, 22050, 44100,
|
| +});
|
| +
|
| +const std::set<uint16_t> PlayWAVApp::VALID_BITS_PER_SAMPLES({
|
| + 8, 16,
|
| +});
|
| +
|
| +void PlayWAVApp::Initialize(ApplicationImpl* app) {
|
| + app->ConnectToService("mojo:audio_server", &audio_server_);
|
| + audio_server_.set_connection_error_handler([this]() {
|
| + OnConnectionError("audio_server");
|
| + });
|
| +
|
| + app->ConnectToService("mojo:network_service", &network_service_);
|
| + audio_server_.set_connection_error_handler([this]() {
|
| + OnConnectionError("network_service");
|
| + });
|
| +
|
| + network_service_->CreateURLLoader(GetProxy(&url_loader_));
|
| + url_loader_.set_connection_error_handler([this]() {
|
| + OnConnectionError("url_loader");
|
| + });
|
| +
|
| + playout_complete_cbk_ = PacketCbk([this](MediaResult res) {
|
| + this->OnPlayoutComplete(res);
|
| + });
|
| +
|
| + URLRequestPtr req(URLRequest::New());
|
| + req->url = TEST_FILE;
|
| + req->method = "GET";
|
| +
|
| + auto cbk = [this](URLResponsePtr resp) { ProcessHTTPResponse(resp.Pass()); };
|
| + url_loader_->Start(req.Pass(), URLLoader::StartCallback(cbk));
|
| +}
|
| +
|
| +void PlayWAVApp::Quit() {
|
| + if (audio_packet_.packet()) {
|
| + MOJO_DCHECK(audio_pipe_);
|
| + audio_pipe_->CancelMediaPacket(&audio_packet_);
|
| + }
|
| +
|
| + payload_.reset();
|
| + url_loader_.reset();
|
| + network_service_.reset();
|
| + audio_pipe_.reset();
|
| + rate_control_.reset();
|
| + audio_track_.reset();
|
| + audio_server_.reset();
|
| +}
|
| +
|
| +bool PlayWAVApp::BlockingRead(void* buf, uint32_t op_len) {
|
| + MojoResult res;
|
| + uint32_t amt;
|
| +
|
| + while (true) {
|
| + amt = op_len;
|
| + res = ReadDataRaw(payload_.get(), buf, &amt,
|
| + MOJO_READ_DATA_FLAG_ALL_OR_NONE);
|
| +
|
| + if ((res == MOJO_RESULT_SHOULD_WAIT) ||
|
| + (res == MOJO_RESULT_OUT_OF_RANGE)) {
|
| + Wait(payload_.get(),
|
| + MOJO_HANDLE_SIGNAL_READABLE,
|
| + MOJO_DEADLINE_INDEFINITE,
|
| + nullptr);
|
| + continue;
|
| + }
|
| +
|
| + break;
|
| + }
|
| +
|
| + return ((res == MOJO_RESULT_OK) && (amt == op_len));
|
| +}
|
| +
|
| +void PlayWAVApp::ProcessHTTPResponse(URLResponsePtr resp) {
|
| + if (resp->mime_type.is_null() ||
|
| + (VALID_MIME_TYPES.find(resp->mime_type) == VALID_MIME_TYPES.end())) {
|
| + MOJO_LOG(ERROR) << "Bad MimeType \""
|
| + << (resp->mime_type.is_null() ? "<null>" : resp->mime_type)
|
| + << "\"";
|
| + BeginShutdown();
|
| + return;
|
| + }
|
| +
|
| + payload_ = resp->body.Pass();
|
| +
|
| + if (!ReadAndValidateRIFFHeader() ||
|
| + !ReadAndValidateWAVHeader() ||
|
| + !ReadAndValidateDATAHeader()) {
|
| + BeginShutdown();
|
| + return;
|
| + }
|
| +
|
| + MOJO_LOG(INFO) << "Preparing to play...";
|
| + MOJO_LOG(INFO) << "File : " << TEST_FILE;
|
| + MOJO_LOG(INFO) << "Rate : " << wav_info_.frame_rate;
|
| + MOJO_LOG(INFO) << "Chan : " << wav_info_.channel_count;
|
| + MOJO_LOG(INFO) << "BPS : " << wav_info_.bits_per_sample;
|
| +
|
| + // Create the audio sink we will use to play this WAV file and start to
|
| + // configure it.
|
| + audio_server_->CreateTrack(GetProxy(&audio_track_));
|
| +
|
| + LinearTransform::Ratio audio_rate(wav_info_.frame_rate, 1);
|
| + LinearTransform::Ratio local_rate(LocalDuration::period::num,
|
| + LocalDuration::period::den);
|
| + LinearTransform::Ratio tmp;
|
| + bool success = LinearTransform::Ratio::Compose(audio_rate, local_rate, &tmp);
|
| + MOJO_DCHECK(success);
|
| +
|
| + AudioTrackConfigurationPtr cfg;
|
| + cfg = AudioTrackConfiguration::New();
|
| + cfg->max_frames = USecToFrames(BUF_DEPTH_USEC);
|
| + cfg->audio_frame_ratio = tmp.numerator;
|
| + cfg->media_time_ratio = tmp.denominator;
|
| +
|
| + LpcmMediaTypeDetailsPtr pcm_cfg = LpcmMediaTypeDetails::New();
|
| + pcm_cfg->sample_format = (wav_info_.bits_per_sample == 8)
|
| + ? LpcmSampleFormat::UNSIGNED_8
|
| + : LpcmSampleFormat::SIGNED_16;
|
| + pcm_cfg->samples_per_frame = wav_info_.channel_count;
|
| + pcm_cfg->frames_per_second = wav_info_.frame_rate;
|
| +
|
| + cfg->media_type = MediaType::New();
|
| + cfg->media_type->scheme = MediaTypeScheme::LPCM;
|
| + cfg->media_type->details = MediaTypeDetails::New();
|
| + cfg->media_type->details->set_lpcm(pcm_cfg.Pass());
|
| +
|
| + // Configure the track based on the WAV header information.
|
| + MediaPipePtr media_pipe;
|
| + audio_track_->Configure(cfg.Pass(), GetProxy(&media_pipe),
|
| + [this](MediaResult res) {
|
| + if (res != MediaResult::OK) {
|
| + MOJO_LOG(ERROR) << "Failed to configure audio track (res = "
|
| + << res << ")";
|
| + BeginShutdown();
|
| + }
|
| + });
|
| +
|
| + // Grab the rate control interface for our audio renderer.
|
| + audio_track_->GetRateControl(GetProxy(&rate_control_),
|
| + [this](MediaResult res) {
|
| + if (res != MediaResult::OK) {
|
| + MOJO_LOG(ERROR) << "Failed to fetch rate control interface "
|
| + << "(res = " << res << ")";
|
| + BeginShutdown();
|
| + }
|
| + });
|
| +
|
| + // Set up our connection error handlers for the various interfaces we are
|
| + // holding on to. Right now, we make no attempt to recover from a closed
|
| + // connection. If one of our connections is closed, its time to shut down.
|
| + //
|
| + // TODO(johngro): when there is some better diagnostic information made
|
| + // available to us, make sure that we log it so we have some way to proceed
|
| + // with debugging.
|
| + audio_track_.set_connection_error_handler([this]() {
|
| + OnConnectionError("audio_track");
|
| + });
|
| +
|
| + // TODO(johngro): Once the media pipe adapter (audio_pipe) has been changed to
|
| + // be the owner of the connection error handler on the media_pipe object,
|
| + // remove this. Use the error handler in the adapter instead.
|
| + media_pipe.set_connection_error_handler([this]() {
|
| + OnConnectionError("media_pipe");
|
| + });
|
| +
|
| + rate_control_.set_connection_error_handler([this]() {
|
| + OnConnectionError("rate_control");
|
| + });
|
| +
|
| + // Set up our media pipe helper, configure its callback and water marks to
|
| + // kick off the playback process.
|
| + audio_pipe_.reset(new CircularBufferMediaPipeAdapter(media_pipe.Pass()));
|
| + audio_pipe_->SetWatermarks(USecToBytes(BUF_HI_WATER_USEC),
|
| + USecToBytes(BUF_LO_WATER_USEC));
|
| + audio_pipe_->SetSignalCallback(
|
| + [this](MediaResult res) -> bool {
|
| + return OnNeedsData(res);
|
| + });
|
| +}
|
| +
|
| +bool PlayWAVApp::ReadAndValidateRIFFHeader() {
|
| + // Read and sanity check the top level RIFF header
|
| + if (!BlockingRead(&riff_hdr_, sizeof(riff_hdr_))) {
|
| + MOJO_LOG(ERROR) << "Failed to read top level RIFF header!";
|
| + return false;
|
| + }
|
| +
|
| + if (fetch_fourcc(&riff_hdr_.four_cc) != RIFF_FOUR_CC) {
|
| + MOJO_LOG(ERROR) << "Missing expected 'RIFF' 4CC "
|
| + << "(expected 0x " << std::hex << RIFF_FOUR_CC
|
| + << " got 0x" << std::hex
|
| + << fetch_fourcc(&riff_hdr_.four_cc)
|
| + << ")";
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool PlayWAVApp::ReadAndValidateWAVHeader() {
|
| + // Read the WAVE header along with its required format chunk.
|
| + if (!BlockingRead(&wav_info_, sizeof(wav_info_))) {
|
| + MOJO_LOG(ERROR) << "Failed to read top level WAVE header!";
|
| + return false;
|
| + }
|
| +
|
| + if (fetch_fourcc(&wav_info_.wave_four_cc) != WAVE_FOUR_CC) {
|
| + MOJO_LOG(ERROR) << "Missing expected 'WAVE' 4CC "
|
| + << "(expected 0x " << std::hex << WAVE_FOUR_CC
|
| + << " got 0x"
|
| + << std::hex << fetch_fourcc(&wav_info_.wave_four_cc)
|
| + << ")";
|
| + return false;
|
| + }
|
| +
|
| + if (fetch_fourcc(&wav_info_.fmt_four_cc) != FMT_FOUR_CC) {
|
| + MOJO_LOG(ERROR) << "Missing expected 'fmt ' 4CC "
|
| + << "(expected 0x " << std::hex << FMT_FOUR_CC
|
| + << " got 0x"
|
| + << std::hex << fetch_fourcc(&wav_info_.fmt_four_cc)
|
| + << ")";
|
| + return false;
|
| + }
|
| +
|
| + // Sanity check the format of the wave file. This demo only support a limited
|
| + // subset of the possible formats.
|
| + if (wav_info_.format != FORMAT_LPCM) {
|
| + MOJO_LOG(ERROR) << "Unsupported format (0x"
|
| + << std::hex << wav_info_.format
|
| + << ") must be LPCM (0x"
|
| + << std::hex << FORMAT_LPCM
|
| + << ")";
|
| + return false;
|
| + }
|
| +
|
| + if ((wav_info_.channel_count != 1) && (wav_info_.channel_count != 2)) {
|
| + MOJO_LOG(ERROR) << "Unsupported channel count ("
|
| + << wav_info_.channel_count
|
| + << ") must be either mono or stereo";
|
| + return false;
|
| + }
|
| +
|
| + if (VALID_FRAME_RATES.find(wav_info_.frame_rate) == VALID_FRAME_RATES.end()) {
|
| + MOJO_LOG(ERROR) << "Unsupported frame_rate ("
|
| + << wav_info_.frame_rate << ")";
|
| + return false;
|
| + }
|
| +
|
| + if (VALID_BITS_PER_SAMPLES.find(wav_info_.bits_per_sample) ==
|
| + VALID_BITS_PER_SAMPLES.end()) {
|
| + MOJO_LOG(ERROR) << "Unsupported bits per sample ("
|
| + << wav_info_.bits_per_sample << ")";
|
| + return false;
|
| + }
|
| +
|
| + uint16_t expected_frame_size;
|
| + expected_frame_size = (wav_info_.channel_count * wav_info_.bits_per_sample)
|
| + >> 3;
|
| + if (wav_info_.frame_size != expected_frame_size) {
|
| + MOJO_LOG(ERROR) << "Frame size sanity check failed. (expected "
|
| + << expected_frame_size << " got "
|
| + << wav_info_.frame_size << ")";
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool PlayWAVApp::ReadAndValidateDATAHeader() {
|
| + // Technically, there could be format specific member of the wave format
|
| + // chunk, or other riff chunks which could come after this, but for this demo,
|
| + // we only handle getting the 'data' chunk at this point.
|
| + if (!BlockingRead(&data_hdr_, sizeof(data_hdr_))) {
|
| + MOJO_LOG(ERROR) << "Failed to read data header!";
|
| + return false;
|
| + }
|
| +
|
| + if (fetch_fourcc(&data_hdr_.four_cc) != DATA_FOUR_CC) {
|
| + MOJO_LOG(ERROR) << "Missing expected 'data' 4CC "
|
| + << "(expected 0x " << std::hex << DATA_FOUR_CC
|
| + << " got 0x" << std::hex
|
| + << fetch_fourcc(&data_hdr_.four_cc)
|
| + << ")";
|
| + return false;
|
| + }
|
| +
|
| + if ((data_hdr_.length + sizeof(WAVHeader) + sizeof(RIFFChunkHeader))
|
| + != riff_hdr_.length) {
|
| + MOJO_LOG(ERROR) << "Header length sanity check failure ("
|
| + << data_hdr_.length << " + "
|
| + << sizeof(WAVHeader) + sizeof(RIFFChunkHeader) << " != "
|
| + << riff_hdr_.length << ")";
|
| + return false;
|
| + }
|
| +
|
| + // If the length of the data chunk is not a multiple of the frame size, log a
|
| + // warning and truncate the length.
|
| + uint16_t leftover;
|
| + payload_len_ = data_hdr_.length;
|
| + leftover = payload_len_ % wav_info_.frame_size;
|
| + if (leftover) {
|
| + MOJO_LOG(WARNING)
|
| + << "Data chunk length (" << payload_len_
|
| + << ") not a multiple of frame size (" << wav_info_.frame_size
|
| + << ")";
|
| + payload_len_ -= leftover;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool PlayWAVApp::OnNeedsData(MediaResult res) {
|
| + if (res != MediaResult::OK) {
|
| + MOJO_LOG(ERROR) << "Error during playback! (res = " << res << ")";
|
| + BeginShutdown();
|
| + return false;
|
| + }
|
| +
|
| + uint64_t bytes = USecToBytes(CHUNK_SIZE_USEC);
|
| + if (bytes > payload_len_) {
|
| + bytes = payload_len_;
|
| + }
|
| +
|
| + res = audio_pipe_->CreateMediaPacket(bytes, false, &audio_packet_);
|
| + if (res != MediaResult::OK) {
|
| + MOJO_LOG(ERROR) << "Failed to create " << bytes << " byte media packet! "
|
| + << "(res = " << res << ")";
|
| + BeginShutdown();
|
| + return false;
|
| + }
|
| +
|
| + if (!sent_first_packet_) {
|
| + MOJO_DCHECK(audio_packet_.packet());
|
| + audio_packet_.packet()->pts = 0;
|
| + sent_first_packet_ = true;
|
| + }
|
| +
|
| + for (size_t i = 0; i < AudioPacket::kMaxRegions; ++i) {
|
| + if (audio_packet_.data(i)) {
|
| + MOJO_DCHECK(audio_packet_.length(i));
|
| + MOJO_DCHECK(audio_packet_.length(i) <= payload_len_);
|
| +
|
| + if (!BlockingRead(audio_packet_.data(i),
|
| + audio_packet_.length(i))) {
|
| + MOJO_LOG(ERROR) << "Failed to read source, shutting down...";
|
| + BeginShutdown();
|
| + return false;
|
| + }
|
| +
|
| + payload_len_ -= audio_packet_.length(i);
|
| + }
|
| + }
|
| +
|
| + if (payload_len_) {
|
| + res = audio_pipe_->SendMediaPacket(&audio_packet_);
|
| + } else {
|
| + res = audio_pipe_->SendMediaPacket(&audio_packet_, playout_complete_cbk_);
|
| + }
|
| +
|
| + if (res != MediaResult::OK) {
|
| + MOJO_LOG(ERROR) << "Failed to send media packet! "
|
| + << "(res = " << res << ")";
|
| + BeginShutdown();
|
| + return false;
|
| + }
|
| +
|
| + if (!clock_started_ && (audio_pipe_->AboveHiWater() || !payload_len_)) {
|
| + LocalTime sched = LocalClock::now() + local_time::from_msec(50);
|
| + rate_control_->SetRateAtTargetTime(1, 1, sched.time_since_epoch().count());
|
| + clock_started_ = true;
|
| + }
|
| +
|
| + return (payload_len_ != 0);
|
| +}
|
| +
|
| +void PlayWAVApp::OnPlayoutComplete(MediaResult res) {
|
| + MOJO_DCHECK(!audio_pipe_->GetPending());
|
| + BeginShutdown();
|
| +}
|
| +
|
| +void PlayWAVApp::OnConnectionError(const std::string& connection_name) {
|
| + if (!shutting_down_) {
|
| + MOJO_LOG(ERROR) << connection_name << " connection closed unexpectedly!";
|
| + BeginShutdown();
|
| + }
|
| +}
|
| +
|
| +// TODO(johngro): remove this when we can. Right now, the proper way to cleanly
|
| +// shut down a running mojo application is a bit unclear to me. Calling
|
| +// RunLoop::current()->Quit() seems like the best option, but the run loop does
|
| +// not seep to call our application's quit method. Instead, it starts to close
|
| +// all of our connections (triggering all of our connection error handlers we
|
| +// have registered on interfaces) before finally destroying our application
|
| +// object.
|
| +//
|
| +// The net result is that we end up spurrious "connection closed unexpectedly"
|
| +// error messages when we are actually shutting down cleanly. For now, we
|
| +// suppress this by having a shutting_down_ flag and suppressing the error
|
| +// message which show up after shutdown has been triggered. When the proper
|
| +// pattern for shutting down an app has been established, come back here and
|
| +// remove all this junk.
|
| +void PlayWAVApp::BeginShutdown() {
|
| + if (!shutting_down_) {
|
| + shutting_down_ = true;
|
| + RunLoop::current()->Quit();
|
| + }
|
| +}
|
| +
|
| +} // namespace examples
|
| +} // namespace audio
|
| +} // namespace media
|
| +} // namespace mojo
|
| +
|
| +MojoResult MojoMain(MojoHandle app_request) {
|
| + mojo::ApplicationRunner runner(new mojo::media::audio::examples::PlayWAVApp);
|
| + return runner.Run(app_request);
|
| +}
|
|
|