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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « examples/audio_play_test/play_tone.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <memory>
6
7 #include "mojo/public/c/system/main.h"
8 #include "mojo/public/cpp/application/application_delegate.h"
9 #include "mojo/public/cpp/application/application_impl.h"
10 #include "mojo/public/cpp/application/application_runner.h"
11 #include "mojo/public/cpp/system/data_pipe.h"
12 #include "mojo/public/cpp/utility/run_loop.h"
13 #include "mojo/services/media/audio/interfaces/audio_server.mojom.h"
14 #include "mojo/services/media/audio/interfaces/audio_track.mojom.h"
15 #include "mojo/services/media/common/cpp/circular_buffer_media_pipe_adapter.h"
16 #include "mojo/services/media/common/cpp/linear_transform.h"
17 #include "mojo/services/media/common/cpp/local_time.h"
18 #include "mojo/services/network/interfaces/network_service.mojom.h"
19 #include "mojo/services/network/interfaces/url_loader.mojom.h"
20
21 #define PACKED __attribute__((packed))
22
23 static inline constexpr uint32_t make_fourcc(uint8_t a, uint8_t b,
24 uint8_t c, uint8_t d) {
25 return (static_cast<uint32_t>(a) << 24) |
26 (static_cast<uint32_t>(b) << 16) |
27 (static_cast<uint32_t>(c) << 8) |
28 static_cast<uint32_t>(d);
29 }
30
31 static inline constexpr uint32_t fetch_fourcc(const void* source) {
32 return (static_cast<uint32_t>(static_cast<const uint8_t*>(source)[0]) << 24) |
33 (static_cast<uint32_t>(static_cast<const uint8_t*>(source)[1]) << 16) |
34 (static_cast<uint32_t>(static_cast<const uint8_t*>(source)[2]) << 8) |
35 static_cast<uint32_t>(static_cast<const uint8_t*>(source)[3]);
36 }
37
38 namespace mojo {
39 namespace media {
40 namespace audio {
41 namespace examples {
42
43 static constexpr const char* TEST_FILE =
44 "http://localhost/test_content/piano2.wav";
45 static constexpr uint32_t BUF_DEPTH_USEC = 500000;
46 static constexpr uint32_t BUF_LO_WATER_USEC = 400000;
47 static constexpr uint32_t BUF_HI_WATER_USEC = 450000;
48 static constexpr uint32_t CHUNK_SIZE_USEC = 10000;
49
50 class PlayWAVApp : public ApplicationDelegate {
51 public:
52 ~PlayWAVApp() override { Quit(); }
53
54 // ApplicationDelegate
55 void Initialize(ApplicationImpl* app) override;
56 void Quit() override;
57
58 private:
59 using AudioPipePtr = std::unique_ptr<CircularBufferMediaPipeAdapter>;
60 using AudioPacket = CircularBufferMediaPipeAdapter::MappedPacket;
61 using PacketCbk = MediaPipe::SendPacketCallback;
62
63 // TODO(johngro): endianness!
64 struct PACKED RIFFChunkHeader {
65 uint32_t four_cc;
66 uint32_t length;
67 };
68
69 struct PACKED WAVHeader {
70 uint32_t wave_four_cc;
71 uint32_t fmt_four_cc;
72 uint32_t fmt_chunk_len;
73 uint16_t format;
74 uint16_t channel_count;
75 uint32_t frame_rate;
76 uint32_t average_byte_rate;
77 uint16_t frame_size;
78 uint16_t bits_per_sample;
79 };
80
81 // TODO(johngro): as mentioned before... endianness!
82 static constexpr uint32_t RIFF_FOUR_CC = make_fourcc('R', 'I', 'F', 'F');
83 static constexpr uint32_t WAVE_FOUR_CC = make_fourcc('W', 'A', 'V', 'E');
84 static constexpr uint32_t FMT_FOUR_CC = make_fourcc('f', 'm', 't', ' ');
85 static constexpr uint32_t DATA_FOUR_CC = make_fourcc('d', 'a', 't', 'a');
86
87 static constexpr uint16_t FORMAT_LPCM = 0x0001;
88 static constexpr uint16_t FORMAT_MULAW = 0x0101;
89 static constexpr uint16_t FORMAT_ALAW = 0x0102;
90 static constexpr uint16_t FORMAT_ADPCM = 0x0103;
91
92 static const std::set<std::string> VALID_MIME_TYPES;
93 static const std::set<uint16_t> VALID_FRAME_RATES;
94 static const std::set<uint16_t> VALID_BITS_PER_SAMPLES;
95
96 bool BlockingRead(void* buf, uint32_t len);
97 void ProcessHTTPResponse(URLResponsePtr resp);
98
99 bool ReadAndValidateRIFFHeader();
100 bool ReadAndValidateWAVHeader();
101 bool ReadAndValidateDATAHeader();
102
103 bool OnNeedsData(MediaResult res);
104 void OnPlayoutComplete(MediaResult res);
105 void OnConnectionError(const std::string& connection_name);
106 void BeginShutdown();
107
108 uint32_t USecToFrames(uint32_t usec) {
109 uint64_t ret = (static_cast<uint64_t>(usec) * wav_info_.frame_rate)
110 / 1000000;
111 MOJO_DCHECK(ret < std::numeric_limits<uint32_t>::max());
112 return ret;
113 }
114
115 uint32_t USecToBytes(uint32_t usec) {
116 uint32_t frames = USecToFrames(usec);
117
118 MOJO_DCHECK(wav_info_.frame_size);
119 MOJO_DCHECK(frames <
120 std::numeric_limits<uint32_t>::max() / wav_info_.frame_size);
121
122 return frames * wav_info_.frame_size;
123 }
124
125 AudioServerPtr audio_server_;
126 AudioTrackPtr audio_track_;
127 AudioPipePtr audio_pipe_;
128 RateControlPtr rate_control_;
129 AudioPacket audio_packet_;
130 PacketCbk playout_complete_cbk_;
131 NetworkServicePtr network_service_;
132 URLLoaderPtr url_loader_;
133 ScopedDataPipeConsumerHandle payload_;
134 uint32_t payload_len_;
135 RIFFChunkHeader riff_hdr_;
136 WAVHeader wav_info_;
137 RIFFChunkHeader data_hdr_;
138 bool sent_first_packet_ = false;
139 bool clock_started_ = false;
140 bool shutting_down_ = false;
141 };
142
143 const std::set<std::string> PlayWAVApp::VALID_MIME_TYPES({
144 "audio/x-wav",
145 "audio/wav",
146 });
147
148 const std::set<uint16_t> PlayWAVApp::VALID_FRAME_RATES({
149 8000, 16000, 24000, 32000, 48000,
150 11025, 22050, 44100,
151 });
152
153 const std::set<uint16_t> PlayWAVApp::VALID_BITS_PER_SAMPLES({
154 8, 16,
155 });
156
157 void PlayWAVApp::Initialize(ApplicationImpl* app) {
158 app->ConnectToService("mojo:audio_server", &audio_server_);
159 audio_server_.set_connection_error_handler([this]() {
160 OnConnectionError("audio_server");
161 });
162
163 app->ConnectToService("mojo:network_service", &network_service_);
164 audio_server_.set_connection_error_handler([this]() {
165 OnConnectionError("network_service");
166 });
167
168 network_service_->CreateURLLoader(GetProxy(&url_loader_));
169 url_loader_.set_connection_error_handler([this]() {
170 OnConnectionError("url_loader");
171 });
172
173 playout_complete_cbk_ = PacketCbk([this](MediaResult res) {
174 this->OnPlayoutComplete(res);
175 });
176
177 URLRequestPtr req(URLRequest::New());
178 req->url = TEST_FILE;
179 req->method = "GET";
180
181 auto cbk = [this](URLResponsePtr resp) { ProcessHTTPResponse(resp.Pass()); };
182 url_loader_->Start(req.Pass(), URLLoader::StartCallback(cbk));
183 }
184
185 void PlayWAVApp::Quit() {
186 if (audio_packet_.packet()) {
187 MOJO_DCHECK(audio_pipe_);
188 audio_pipe_->CancelMediaPacket(&audio_packet_);
189 }
190
191 payload_.reset();
192 url_loader_.reset();
193 network_service_.reset();
194 audio_pipe_.reset();
195 rate_control_.reset();
196 audio_track_.reset();
197 audio_server_.reset();
198 }
199
200 bool PlayWAVApp::BlockingRead(void* buf, uint32_t op_len) {
201 MojoResult res;
202 uint32_t amt;
203
204 while (true) {
205 amt = op_len;
206 res = ReadDataRaw(payload_.get(), buf, &amt,
207 MOJO_READ_DATA_FLAG_ALL_OR_NONE);
208
209 if ((res == MOJO_RESULT_SHOULD_WAIT) ||
210 (res == MOJO_RESULT_OUT_OF_RANGE)) {
211 Wait(payload_.get(),
212 MOJO_HANDLE_SIGNAL_READABLE,
213 MOJO_DEADLINE_INDEFINITE,
214 nullptr);
215 continue;
216 }
217
218 break;
219 }
220
221 return ((res == MOJO_RESULT_OK) && (amt == op_len));
222 }
223
224 void PlayWAVApp::ProcessHTTPResponse(URLResponsePtr resp) {
225 if (resp->mime_type.is_null() ||
226 (VALID_MIME_TYPES.find(resp->mime_type) == VALID_MIME_TYPES.end())) {
227 MOJO_LOG(ERROR) << "Bad MimeType \""
228 << (resp->mime_type.is_null() ? "<null>" : resp->mime_type)
229 << "\"";
230 BeginShutdown();
231 return;
232 }
233
234 payload_ = resp->body.Pass();
235
236 if (!ReadAndValidateRIFFHeader() ||
237 !ReadAndValidateWAVHeader() ||
238 !ReadAndValidateDATAHeader()) {
239 BeginShutdown();
240 return;
241 }
242
243 MOJO_LOG(INFO) << "Preparing to play...";
244 MOJO_LOG(INFO) << "File : " << TEST_FILE;
245 MOJO_LOG(INFO) << "Rate : " << wav_info_.frame_rate;
246 MOJO_LOG(INFO) << "Chan : " << wav_info_.channel_count;
247 MOJO_LOG(INFO) << "BPS : " << wav_info_.bits_per_sample;
248
249 // Create the audio sink we will use to play this WAV file and start to
250 // configure it.
251 audio_server_->CreateTrack(GetProxy(&audio_track_));
252
253 LinearTransform::Ratio audio_rate(wav_info_.frame_rate, 1);
254 LinearTransform::Ratio local_rate(LocalDuration::period::num,
255 LocalDuration::period::den);
256 LinearTransform::Ratio tmp;
257 bool success = LinearTransform::Ratio::Compose(audio_rate, local_rate, &tmp);
258 MOJO_DCHECK(success);
259
260 AudioTrackConfigurationPtr cfg;
261 cfg = AudioTrackConfiguration::New();
262 cfg->max_frames = USecToFrames(BUF_DEPTH_USEC);
263 cfg->audio_frame_ratio = tmp.numerator;
264 cfg->media_time_ratio = tmp.denominator;
265
266 LpcmMediaTypeDetailsPtr pcm_cfg = LpcmMediaTypeDetails::New();
267 pcm_cfg->sample_format = (wav_info_.bits_per_sample == 8)
268 ? LpcmSampleFormat::UNSIGNED_8
269 : LpcmSampleFormat::SIGNED_16;
270 pcm_cfg->samples_per_frame = wav_info_.channel_count;
271 pcm_cfg->frames_per_second = wav_info_.frame_rate;
272
273 cfg->media_type = MediaType::New();
274 cfg->media_type->scheme = MediaTypeScheme::LPCM;
275 cfg->media_type->details = MediaTypeDetails::New();
276 cfg->media_type->details->set_lpcm(pcm_cfg.Pass());
277
278 // Configure the track based on the WAV header information.
279 MediaPipePtr media_pipe;
280 audio_track_->Configure(cfg.Pass(), GetProxy(&media_pipe),
281 [this](MediaResult res) {
282 if (res != MediaResult::OK) {
283 MOJO_LOG(ERROR) << "Failed to configure audio track (res = "
284 << res << ")";
285 BeginShutdown();
286 }
287 });
288
289 // Grab the rate control interface for our audio renderer.
290 audio_track_->GetRateControl(GetProxy(&rate_control_),
291 [this](MediaResult res) {
292 if (res != MediaResult::OK) {
293 MOJO_LOG(ERROR) << "Failed to fetch rate control interface "
294 << "(res = " << res << ")";
295 BeginShutdown();
296 }
297 });
298
299 // Set up our connection error handlers for the various interfaces we are
300 // holding on to. Right now, we make no attempt to recover from a closed
301 // connection. If one of our connections is closed, its time to shut down.
302 //
303 // TODO(johngro): when there is some better diagnostic information made
304 // available to us, make sure that we log it so we have some way to proceed
305 // with debugging.
306 audio_track_.set_connection_error_handler([this]() {
307 OnConnectionError("audio_track");
308 });
309
310 // TODO(johngro): Once the media pipe adapter (audio_pipe) has been changed to
311 // be the owner of the connection error handler on the media_pipe object,
312 // remove this. Use the error handler in the adapter instead.
313 media_pipe.set_connection_error_handler([this]() {
314 OnConnectionError("media_pipe");
315 });
316
317 rate_control_.set_connection_error_handler([this]() {
318 OnConnectionError("rate_control");
319 });
320
321 // Set up our media pipe helper, configure its callback and water marks to
322 // kick off the playback process.
323 audio_pipe_.reset(new CircularBufferMediaPipeAdapter(media_pipe.Pass()));
324 audio_pipe_->SetWatermarks(USecToBytes(BUF_HI_WATER_USEC),
325 USecToBytes(BUF_LO_WATER_USEC));
326 audio_pipe_->SetSignalCallback(
327 [this](MediaResult res) -> bool {
328 return OnNeedsData(res);
329 });
330 }
331
332 bool PlayWAVApp::ReadAndValidateRIFFHeader() {
333 // Read and sanity check the top level RIFF header
334 if (!BlockingRead(&riff_hdr_, sizeof(riff_hdr_))) {
335 MOJO_LOG(ERROR) << "Failed to read top level RIFF header!";
336 return false;
337 }
338
339 if (fetch_fourcc(&riff_hdr_.four_cc) != RIFF_FOUR_CC) {
340 MOJO_LOG(ERROR) << "Missing expected 'RIFF' 4CC "
341 << "(expected 0x " << std::hex << RIFF_FOUR_CC
342 << " got 0x" << std::hex
343 << fetch_fourcc(&riff_hdr_.four_cc)
344 << ")";
345 return false;
346 }
347
348 return true;
349 }
350
351 bool PlayWAVApp::ReadAndValidateWAVHeader() {
352 // Read the WAVE header along with its required format chunk.
353 if (!BlockingRead(&wav_info_, sizeof(wav_info_))) {
354 MOJO_LOG(ERROR) << "Failed to read top level WAVE header!";
355 return false;
356 }
357
358 if (fetch_fourcc(&wav_info_.wave_four_cc) != WAVE_FOUR_CC) {
359 MOJO_LOG(ERROR) << "Missing expected 'WAVE' 4CC "
360 << "(expected 0x " << std::hex << WAVE_FOUR_CC
361 << " got 0x"
362 << std::hex << fetch_fourcc(&wav_info_.wave_four_cc)
363 << ")";
364 return false;
365 }
366
367 if (fetch_fourcc(&wav_info_.fmt_four_cc) != FMT_FOUR_CC) {
368 MOJO_LOG(ERROR) << "Missing expected 'fmt ' 4CC "
369 << "(expected 0x " << std::hex << FMT_FOUR_CC
370 << " got 0x"
371 << std::hex << fetch_fourcc(&wav_info_.fmt_four_cc)
372 << ")";
373 return false;
374 }
375
376 // Sanity check the format of the wave file. This demo only support a limited
377 // subset of the possible formats.
378 if (wav_info_.format != FORMAT_LPCM) {
379 MOJO_LOG(ERROR) << "Unsupported format (0x"
380 << std::hex << wav_info_.format
381 << ") must be LPCM (0x"
382 << std::hex << FORMAT_LPCM
383 << ")";
384 return false;
385 }
386
387 if ((wav_info_.channel_count != 1) && (wav_info_.channel_count != 2)) {
388 MOJO_LOG(ERROR) << "Unsupported channel count ("
389 << wav_info_.channel_count
390 << ") must be either mono or stereo";
391 return false;
392 }
393
394 if (VALID_FRAME_RATES.find(wav_info_.frame_rate) == VALID_FRAME_RATES.end()) {
395 MOJO_LOG(ERROR) << "Unsupported frame_rate ("
396 << wav_info_.frame_rate << ")";
397 return false;
398 }
399
400 if (VALID_BITS_PER_SAMPLES.find(wav_info_.bits_per_sample) ==
401 VALID_BITS_PER_SAMPLES.end()) {
402 MOJO_LOG(ERROR) << "Unsupported bits per sample ("
403 << wav_info_.bits_per_sample << ")";
404 return false;
405 }
406
407 uint16_t expected_frame_size;
408 expected_frame_size = (wav_info_.channel_count * wav_info_.bits_per_sample)
409 >> 3;
410 if (wav_info_.frame_size != expected_frame_size) {
411 MOJO_LOG(ERROR) << "Frame size sanity check failed. (expected "
412 << expected_frame_size << " got "
413 << wav_info_.frame_size << ")";
414 return false;
415 }
416
417 return true;
418 }
419
420 bool PlayWAVApp::ReadAndValidateDATAHeader() {
421 // Technically, there could be format specific member of the wave format
422 // chunk, or other riff chunks which could come after this, but for this demo,
423 // we only handle getting the 'data' chunk at this point.
424 if (!BlockingRead(&data_hdr_, sizeof(data_hdr_))) {
425 MOJO_LOG(ERROR) << "Failed to read data header!";
426 return false;
427 }
428
429 if (fetch_fourcc(&data_hdr_.four_cc) != DATA_FOUR_CC) {
430 MOJO_LOG(ERROR) << "Missing expected 'data' 4CC "
431 << "(expected 0x " << std::hex << DATA_FOUR_CC
432 << " got 0x" << std::hex
433 << fetch_fourcc(&data_hdr_.four_cc)
434 << ")";
435 return false;
436 }
437
438 if ((data_hdr_.length + sizeof(WAVHeader) + sizeof(RIFFChunkHeader))
439 != riff_hdr_.length) {
440 MOJO_LOG(ERROR) << "Header length sanity check failure ("
441 << data_hdr_.length << " + "
442 << sizeof(WAVHeader) + sizeof(RIFFChunkHeader) << " != "
443 << riff_hdr_.length << ")";
444 return false;
445 }
446
447 // If the length of the data chunk is not a multiple of the frame size, log a
448 // warning and truncate the length.
449 uint16_t leftover;
450 payload_len_ = data_hdr_.length;
451 leftover = payload_len_ % wav_info_.frame_size;
452 if (leftover) {
453 MOJO_LOG(WARNING)
454 << "Data chunk length (" << payload_len_
455 << ") not a multiple of frame size (" << wav_info_.frame_size
456 << ")";
457 payload_len_ -= leftover;
458 }
459
460 return true;
461 }
462
463 bool PlayWAVApp::OnNeedsData(MediaResult res) {
464 if (res != MediaResult::OK) {
465 MOJO_LOG(ERROR) << "Error during playback! (res = " << res << ")";
466 BeginShutdown();
467 return false;
468 }
469
470 uint64_t bytes = USecToBytes(CHUNK_SIZE_USEC);
471 if (bytes > payload_len_) {
472 bytes = payload_len_;
473 }
474
475 res = audio_pipe_->CreateMediaPacket(bytes, false, &audio_packet_);
476 if (res != MediaResult::OK) {
477 MOJO_LOG(ERROR) << "Failed to create " << bytes << " byte media packet! "
478 << "(res = " << res << ")";
479 BeginShutdown();
480 return false;
481 }
482
483 if (!sent_first_packet_) {
484 MOJO_DCHECK(audio_packet_.packet());
485 audio_packet_.packet()->pts = 0;
486 sent_first_packet_ = true;
487 }
488
489 for (size_t i = 0; i < AudioPacket::kMaxRegions; ++i) {
490 if (audio_packet_.data(i)) {
491 MOJO_DCHECK(audio_packet_.length(i));
492 MOJO_DCHECK(audio_packet_.length(i) <= payload_len_);
493
494 if (!BlockingRead(audio_packet_.data(i),
495 audio_packet_.length(i))) {
496 MOJO_LOG(ERROR) << "Failed to read source, shutting down...";
497 BeginShutdown();
498 return false;
499 }
500
501 payload_len_ -= audio_packet_.length(i);
502 }
503 }
504
505 if (payload_len_) {
506 res = audio_pipe_->SendMediaPacket(&audio_packet_);
507 } else {
508 res = audio_pipe_->SendMediaPacket(&audio_packet_, playout_complete_cbk_);
509 }
510
511 if (res != MediaResult::OK) {
512 MOJO_LOG(ERROR) << "Failed to send media packet! "
513 << "(res = " << res << ")";
514 BeginShutdown();
515 return false;
516 }
517
518 if (!clock_started_ && (audio_pipe_->AboveHiWater() || !payload_len_)) {
519 LocalTime sched = LocalClock::now() + local_time::from_msec(50);
520 rate_control_->SetRateAtTargetTime(1, 1, sched.time_since_epoch().count());
521 clock_started_ = true;
522 }
523
524 return (payload_len_ != 0);
525 }
526
527 void PlayWAVApp::OnPlayoutComplete(MediaResult res) {
528 MOJO_DCHECK(!audio_pipe_->GetPending());
529 BeginShutdown();
530 }
531
532 void PlayWAVApp::OnConnectionError(const std::string& connection_name) {
533 if (!shutting_down_) {
534 MOJO_LOG(ERROR) << connection_name << " connection closed unexpectedly!";
535 BeginShutdown();
536 }
537 }
538
539 // TODO(johngro): remove this when we can. Right now, the proper way to cleanly
540 // shut down a running mojo application is a bit unclear to me. Calling
541 // RunLoop::current()->Quit() seems like the best option, but the run loop does
542 // not seep to call our application's quit method. Instead, it starts to close
543 // all of our connections (triggering all of our connection error handlers we
544 // have registered on interfaces) before finally destroying our application
545 // object.
546 //
547 // The net result is that we end up spurrious "connection closed unexpectedly"
548 // error messages when we are actually shutting down cleanly. For now, we
549 // suppress this by having a shutting_down_ flag and suppressing the error
550 // message which show up after shutdown has been triggered. When the proper
551 // pattern for shutting down an app has been established, come back here and
552 // remove all this junk.
553 void PlayWAVApp::BeginShutdown() {
554 if (!shutting_down_) {
555 shutting_down_ = true;
556 RunLoop::current()->Quit();
557 }
558 }
559
560 } // namespace examples
561 } // namespace audio
562 } // namespace media
563 } // namespace mojo
564
565 MojoResult MojoMain(MojoHandle app_request) {
566 mojo::ApplicationRunner runner(new mojo::media::audio::examples::PlayWAVApp);
567 return runner.Run(app_request);
568 }
OLDNEW
« 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