Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3719)

Unified Diff: examples/audio_play_test/play_wav.cc

Issue 1406393004: Add two demos which exercise the audio server. (Closed) Base URL: https://github.com/domokit/mojo.git@change6
Patch Set: fix android build Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « examples/audio_play_test/play_tone.cc ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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);
+}
« no previous file with comments | « examples/audio_play_test/play_tone.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698