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

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: 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
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 "base/bind.h"
8 #include "base/logging.h"
9 #include "mojo/public/c/system/main.h"
10 #include "mojo/public/cpp/application/application_delegate.h"
11 #include "mojo/public/cpp/application/application_impl.h"
12 #include "mojo/public/cpp/application/application_runner.h"
13 #include "mojo/public/cpp/system/data_pipe.h"
14 #include "mojo/public/cpp/utility/run_loop.h"
15 #include "mojo/services/media/audio/interfaces/audio_server.mojom.h"
16 #include "mojo/services/media/audio/interfaces/audio_track.mojom.h"
17 #include "mojo/services/media/common/cpp/circular_buffer_media_pipe_adapter.h"
18 #include "mojo/services/media/common/cpp/linear_transform.h"
19 #include "mojo/services/media/common/cpp/local_time.h"
20 #include "mojo/services/network/interfaces/network_service.mojom.h"
21 #include "mojo/services/network/interfaces/url_loader.mojom.h"
22
23 #define PACKED __attribute__((packed))
24
25 namespace mojo {
26 namespace media {
27 namespace audio {
28 namespace examples {
29
30 #if 0
31 static constexpr const char* TEST_FILE =
32 "http://localhost/test_content/piano2.wav";
33 #else
34 static constexpr const char* TEST_FILE =
35 "http://www.thesoundarchive.com/starwars/swvader04.wav";
jeffbrown 2015/11/04 19:34:44 Is this copyrighted material?
johngro 2015/11/05 00:25:16 The URL? I don't think that you can copyright a U
36 #endif
37 static constexpr uint32_t BUF_DEPTH_USEC = 500000;
38 static constexpr uint32_t BUF_LO_WATER_USEC = 400000;
39 static constexpr uint32_t BUF_HI_WATER_USEC = 450000;
40 static constexpr uint32_t CHUNK_SIZE_USEC = 10000;
41
42 class PlayWAVApp : public ApplicationDelegate {
43 public:
44 ~PlayWAVApp() override { Quit(); }
45
46 // ApplicationDelegate
47 void Initialize(ApplicationImpl* app) override;
48 void Quit() override;
49
50 private:
51 using AudioPipePtr = std::unique_ptr<CircularBufferMediaPipeAdapter>;
52 using AudioPacket = CircularBufferMediaPipeAdapter::MappedPacket;
53 using PacketCbk = MediaPipe::SendPacketCallback;
54
55 // TODO(johngro): endianness!
56 struct PACKED RIFFChunkHeader {
57 uint32_t four_cc;
58 uint32_t length;
59 };
60
61 struct PACKED WAVHeader {
62 uint32_t wave_four_cc;
63 uint32_t fmt_four_cc;
64 uint32_t fmt_chunk_len;
65 uint16_t format;
66 uint16_t channel_count;
67 uint32_t frame_rate;
68 uint32_t average_byte_rate;
69 uint16_t frame_size;
70 uint16_t bits_per_sample;
71 };
72
73 // TODO(johngro): as mentioned before... endianness!
jeffbrown 2015/11/04 19:34:44 Mentioned where?
johngro 2015/11/05 00:25:16 line 55.
74 static constexpr uint32_t RIFF_FOUR_CC = 'FFIR';
75 static constexpr uint32_t WAVE_FOUR_CC = 'EVAW';
76 static constexpr uint32_t FMT_FOUR_CC = ' tmf';
77 static constexpr uint32_t DATA_FOUR_CC = 'atad';
78
79 static constexpr uint16_t FORMAT_LPCM = 0x0001;
80 static constexpr uint16_t FORMAT_MULAW = 0x0101;
81 static constexpr uint16_t FORMAT_ALAW = 0x0102;
82 static constexpr uint16_t FORMAT_ADPCM = 0x0103;
83
84 static const std::set<std::string> VALID_MIME_TYPES;
85 static const std::set<uint16_t> VALID_FRAME_RATES;
86 static const std::set<uint16_t> VALID_BITS_PER_SAMPLES;
87
88 bool BlockingRead(void* buf, uint32_t len);
89 void ProcessHTTPResponse(URLResponsePtr resp);
90 void PlayWAV();
91
92 void OnAudioConfigured(MediaResult res);
93 void OnHasRateControl(MediaResult res);
94 bool OnNeedsData(MediaResult res);
95 void OnPlayoutComplete(MediaResult res);
96
97 uint32_t USecToFrames(uint32_t usec) {
98 uint64_t ret = (static_cast<uint64_t>(usec) * wav_info_.frame_rate)
99 / 1000000;
100 DCHECK_LT(ret, std::numeric_limits<uint32_t>::max());
101 return ret;
102 }
103
104 uint32_t USecToBytes(uint32_t usec) {
105 uint32_t frames = USecToFrames(usec);
106
107 DCHECK(wav_info_.frame_size);
108 DCHECK_LT(frames,
109 std::numeric_limits<uint32_t>::max() / wav_info_.frame_size);
110
111 return frames * wav_info_.frame_size;
112 }
113
114 AudioServerPtr audio_server_;
115 AudioTrackPtr audio_track_;
116 AudioPipePtr audio_pipe_;
117 MediaPipePtr media_pipe_;
118 RateControlPtr rate_control_;
119 AudioPacket audio_packet_;
120 PacketCbk playout_complete_cbk_;
121 NetworkServicePtr network_service_;
122 URLLoaderPtr url_loader_;
123 ScopedDataPipeConsumerHandle payload_;
124 uint32_t payload_len_;
125 WAVHeader wav_info_;
126 bool sent_first_packet_ = false;
127 bool clock_started_ = false;
128 };
129
130 const std::set<std::string> PlayWAVApp::VALID_MIME_TYPES({
131 "audio/x-wav",
132 "audio/wav",
133 });
134
135 const std::set<uint16_t> PlayWAVApp::VALID_FRAME_RATES({
136 8000, 16000, 24000, 32000, 48000,
137 11025, 22050, 44100,
138 });
139
140 const std::set<uint16_t> PlayWAVApp::VALID_BITS_PER_SAMPLES({
141 8, 16,
142 });
143
144 void PlayWAVApp::Initialize(ApplicationImpl* app) {
145 app->ConnectToService("mojo:audio_server", &audio_server_);
146 app->ConnectToService("mojo:network_service", &network_service_);
147
148 network_service_->CreateURLLoader(GetProxy(&url_loader_));
149
150 playout_complete_cbk_ = PacketCbk([this](MediaResult res) {
151 this->OnPlayoutComplete(res);
152 });
153
154 URLRequestPtr req(URLRequest::New());
155 req->url = TEST_FILE;
156 req->method = "GET";
157
158 auto cbk = [this](URLResponsePtr resp) { ProcessHTTPResponse(resp.Pass()); };
159 url_loader_->Start(req.Pass(), URLLoader::StartCallback(cbk));
160 }
161
162 void PlayWAVApp::Quit() {
163 if (audio_packet_.packet()) {
164 DCHECK(audio_pipe_);
165 audio_pipe_->CancelMediaPacket(&audio_packet_);
166 }
167
168 payload_.reset();
169 url_loader_.reset();
170 network_service_.reset();
171 media_pipe_.reset();
172 audio_pipe_.reset();
173 audio_track_.reset();
174 audio_server_.reset();
175 }
176
177 bool PlayWAVApp::BlockingRead(void* buf, uint32_t op_len) {
178 MojoResult res;
179 uint32_t amt;
180
181 while (true) {
182 amt = op_len;
183 res = ReadDataRaw(payload_.get(), buf, &amt,
184 MOJO_READ_DATA_FLAG_ALL_OR_NONE);
185
186 if ((res == MOJO_RESULT_SHOULD_WAIT) ||
187 (res == MOJO_RESULT_OUT_OF_RANGE)) {
188 Wait(payload_.get(),
jeffbrown 2015/11/04 19:34:44 Isn't there a non-blocking way to do this?
johngro 2015/11/05 00:25:16 yes; if I understand correctly it involves creatin
189 MOJO_HANDLE_SIGNAL_READABLE,
190 MOJO_DEADLINE_INDEFINITE,
191 nullptr);
192 continue;
193 }
194
195 break;
196 }
197
198 return ((res == MOJO_RESULT_OK) && (amt == op_len));
199 }
200
201 void PlayWAVApp::ProcessHTTPResponse(URLResponsePtr resp) {
202 if (resp->mime_type.is_null() ||
203 (VALID_MIME_TYPES.find(resp->mime_type) == VALID_MIME_TYPES.end())) {
204 LOG(ERROR) << "Bad MimeType \""
205 << (resp->mime_type.is_null() ? "<null>" : resp->mime_type)
206 << "\"";
207 RunLoop::current()->Quit();
208 return;
209 }
210
211 payload_ = resp->body.Pass();
212
213 // Read and sanity check the top level RIFF header
214 RIFFChunkHeader riff_hdr;
215 if (!BlockingRead(&riff_hdr, sizeof(riff_hdr))) {
216 LOG(ERROR) << "Failed to read top level RIFF header!";
217 RunLoop::current()->Quit();
jeffbrown 2015/11/04 19:34:44 This is telling me that Mojo needs a better patter
johngro 2015/11/05 00:25:16 Acknowledged. There is some discussion on the sub
218 return;
219 }
220
221 if (riff_hdr.four_cc != RIFF_FOUR_CC) {
222 LOG(ERROR) << "Missing expected 'RIFF' 4CC "
223 << "(expected 0x " << std::hex << RIFF_FOUR_CC
224 << " got 0x" << std::hex << riff_hdr.four_cc
225 << ")";
226 RunLoop::current()->Quit();
227 return;
228 }
229
230 // Now read the WAVE header along with its required format chunk.
231 if (!BlockingRead(&wav_info_, sizeof(wav_info_))) {
232 LOG(ERROR) << "Failed to read top level WAVE header!";
233 RunLoop::current()->Quit();
234 return;
235 }
236
237 if (wav_info_.wave_four_cc != WAVE_FOUR_CC) {
238 LOG(ERROR) << "Missing expected 'WAVE' 4CC "
239 << "(expected 0x " << std::hex << WAVE_FOUR_CC
240 << " got 0x" << std::hex << wav_info_.wave_four_cc
241 << ")";
242 RunLoop::current()->Quit();
243 return;
244 }
245
246 // Sanity check the format of the wave file. This demo only support a limited
247 // subset of the possible formats.
248 if (wav_info_.format != FORMAT_LPCM) {
249 LOG(ERROR) << "Unsupported format (0x"
250 << std::hex << wav_info_.format
251 << ") must be LPCM (0x"
252 << std::hex << FORMAT_LPCM
253 << ")";
254 RunLoop::current()->Quit();
255 return;
256 }
257
258 if ((wav_info_.channel_count != 1) && (wav_info_.channel_count != 2)) {
259 LOG(ERROR) << "Unsupported channel count ("
260 << wav_info_.channel_count
261 << ") must be either mono or stereo";
262 RunLoop::current()->Quit();
263 return;
264 }
265
266 if ((wav_info_.channel_count != 1) && (wav_info_.channel_count != 2)) {
jeffbrown 2015/11/04 19:34:44 duplicate code
johngro 2015/11/05 00:25:16 Done. good catch.
267 LOG(ERROR) << "Unsupported channel count ("
268 << wav_info_.channel_count
269 << ") must be either mono or stereo";
270 RunLoop::current()->Quit();
271 return;
272 }
273
274 if (VALID_FRAME_RATES.find(wav_info_.frame_rate) == VALID_FRAME_RATES.end()) {
275 LOG(ERROR) << "Unsupported frame_rate (" << wav_info_.frame_rate << ")";
276 RunLoop::current()->Quit();
277 return;
278 }
279
280 if (VALID_BITS_PER_SAMPLES.find(wav_info_.bits_per_sample) ==
281 VALID_BITS_PER_SAMPLES.end()) {
282 LOG(ERROR) << "Unsupported bits per sample (" << wav_info_.bits_per_sample
283 << ")";
284 RunLoop::current()->Quit();
285 return;
286 }
287
288 uint16_t expected_frame_size;
289 expected_frame_size = (wav_info_.channel_count * wav_info_.bits_per_sample)
290 >> 3;
291 if (wav_info_.frame_size != expected_frame_size) {
292 LOG(ERROR) << "Frame size sanity check failed. (expected "
293 << expected_frame_size << " got "
294 << wav_info_.frame_size << ")";
295 RunLoop::current()->Quit();
jeffbrown 2015/11/04 19:34:44 FWIW, you might be better off making a single Vali
johngro 2015/11/05 00:25:16 Agreed, Done.
296 return;
297 }
298
299 // Technically, there could be format specific member of the wave format
300 // chunk, or other riff chunks which could come after this, but for this demo,
301 // we only handle getting the 'data' chunk at this point.
302 RIFFChunkHeader data_hdr;
303 if (!BlockingRead(&data_hdr, sizeof(data_hdr))) {
304 LOG(ERROR) << "Failed to read data header!";
305 RunLoop::current()->Quit();
306 return;
307 }
308
309 if (data_hdr.four_cc != DATA_FOUR_CC) {
310 LOG(ERROR) << "Missing expected 'data' 4CC "
311 << "(expected 0x " << std::hex << DATA_FOUR_CC
312 << " got 0x" << std::hex << data_hdr.four_cc
313 << ")";
314 RunLoop::current()->Quit();
315 return;
316 }
317
318 if ((data_hdr.length + sizeof(WAVHeader) + sizeof(RIFFChunkHeader))
319 != riff_hdr.length) {
320 LOG(ERROR) << "Header length sanity check failure ("
321 << data_hdr.length << " + "
322 << sizeof(WAVHeader) + sizeof(RIFFChunkHeader) << " != "
323 << riff_hdr.length << ")";
324 RunLoop::current()->Quit();
325 return;
326 }
327
328 // If the length of the data chunk is not a multiple of the frame size, log a
329 // warning and truncate the length.
330 uint16_t leftover;
331 payload_len_ = data_hdr.length;
332 leftover = payload_len_ % wav_info_.frame_size;
333 if (leftover) {
334 LOG(WARNING) << "Data chunk length (" << payload_len_
335 << ") not a multiple of frame size (" << wav_info_.frame_size
336 << ")";
337 payload_len_ -= leftover;
338 }
339
340 LOG(INFO) << "Preparing to play...";
341 LOG(INFO) << "File : " << TEST_FILE;
342 LOG(INFO) << "Rate : " << wav_info_.frame_rate;
343 LOG(INFO) << "Chan : " << wav_info_.channel_count;
344 LOG(INFO) << "BPS : " << wav_info_.bits_per_sample;
345
346 // Create the audio sink we will use to play this WAV file and start to
347 // configure it.
348 audio_server_->CreateTrack(GetProxy(&audio_track_));
349
350 LinearTransform::Ratio audio_rate(wav_info_.frame_rate, 1);
351 LinearTransform::Ratio local_rate(LocalDuration::period::num,
352 LocalDuration::period::den);
353 LinearTransform::Ratio tmp;
354 bool success = LinearTransform::Ratio::Compose(audio_rate, local_rate, &tmp);
355 DCHECK(success);
356
357 AudioTrackConfigurationPtr cfg;
358 cfg = AudioTrackConfiguration::New();
359 cfg->max_frames = USecToFrames(BUF_DEPTH_USEC);
360 cfg->audio_frame_ratio = tmp.numerator;
361 cfg->media_time_ratio = tmp.denominator;
362
363 LpcmMediaTypeDetailsPtr pcm_cfg = LpcmMediaTypeDetails::New();
364 pcm_cfg->sample_format = (wav_info_.bits_per_sample == 8)
365 ? LpcmSampleFormat::UNSIGNED_8
366 : LpcmSampleFormat::SIGNED_16;
367 pcm_cfg->samples_per_frame = wav_info_.channel_count;
368 pcm_cfg->frames_per_second = wav_info_.frame_rate;
369
370 cfg->media_type = MediaType::New();
371 cfg->media_type->scheme = MediaTypeScheme::LPCM;
372 cfg->media_type->details = MediaTypeDetails::New();
373 cfg->media_type->details->set_lpcm(pcm_cfg.Pass());
374
375
376 audio_track_->Configure(cfg.Pass(),
377 GetProxy(&media_pipe_),
378 [this](MediaResult res) {
379 this->OnAudioConfigured(res);
jeffbrown 2015/11/04 19:34:44 Why can't we just send the rate control request im
johngro 2015/11/05 00:25:16 will change, I'm going to finish responses to init
380 });
381 }
382
383 void PlayWAVApp::OnAudioConfigured(MediaResult res) {
384 if (res != MediaResult::OK) {
385 LOG(ERROR) << "Failed to configure audio track (res = " << res << ")";
386 RunLoop::current()->Quit();
387 return;
388 }
389
390 // Grab the rate control interface for our audio renderer.
391 audio_track_->GetRateControl(GetProxy(&rate_control_),
392 [this](MediaResult res) {
393 this->OnHasRateControl(res);
jeffbrown 2015/11/04 19:34:44 Likewise, no need to wait for the callback before
johngro 2015/11/05 00:25:16 Acknowledged.
394 });
395 }
396
397 void PlayWAVApp::OnHasRateControl(MediaResult res) {
398 if (res != MediaResult::OK) {
399 LOG(ERROR) << "Failed to fetch rate control interface "
400 << "(res = " << res << ")";
401 RunLoop::current()->Quit();
402 return;
403 }
404
405 // Set up our media pipe helper, configure its callback and water marks to
406 // kick off the playback process.
407 audio_pipe_.reset(new CircularBufferMediaPipeAdapter(media_pipe_.Pass()));
408 audio_pipe_->SetWatermarks(USecToBytes(BUF_HI_WATER_USEC),
409 USecToBytes(BUF_LO_WATER_USEC));
410 audio_pipe_->SetSignalCallback(
411 [this](MediaResult res) -> bool {
412 return OnNeedsData(res);
413 });
414 }
415
416 bool PlayWAVApp::OnNeedsData(MediaResult res) {
417 if (res != MediaResult::OK) {
418 LOG(ERROR) << "Error during playback! (res = " << res << ")";
419 RunLoop::current()->Quit();
420 return false;
421 }
422
423 uint64_t bytes = USecToBytes(CHUNK_SIZE_USEC);
424 if (bytes > payload_len_) {
425 bytes = payload_len_;
426 }
427
428 res = audio_pipe_->CreateMediaPacket(bytes, false, &audio_packet_);
429 if (res != MediaResult::OK) {
430 LOG(ERROR) << "Failed to create " << bytes << " byte media packet! "
431 << "(res = " << res << ")";
432 RunLoop::current()->Quit();
433 return false;
434 }
435
436 if (!sent_first_packet_) {
437 DCHECK(audio_packet_.packet());
438 audio_packet_.packet()->pts = 0;
439 sent_first_packet_ = true;
440 }
441
442 for (size_t i = 0; i < AudioPacket::kMaxRegions; ++i) {
443 if (audio_packet_.data(i)) {
444 DCHECK(audio_packet_.length(i));
445 DCHECK(audio_packet_.length(i) <= payload_len_);
446
447 if (!BlockingRead(audio_packet_.data(i),
448 audio_packet_.length(i))) {
449 LOG(ERROR) << "Failed to read source, shutting down...";
450 RunLoop::current()->Quit();
451 return false;
452 }
453
454 payload_len_ -= audio_packet_.length(i);
455 }
456 }
457
458 if (payload_len_) {
459 res = audio_pipe_->SendMediaPacket(&audio_packet_);
460 } else {
461 res = audio_pipe_->SendMediaPacket(&audio_packet_, playout_complete_cbk_);
462 }
463
464 if (res != MediaResult::OK) {
465 LOG(ERROR) << "Failed to send media packet! "
466 << "(res = " << res << ")";
467 RunLoop::current()->Quit();
468 return false;
469 }
470
471 if (!clock_started_ && (audio_pipe_->AboveHiWater() || !payload_len_)) {
472 LocalTime sched = LocalClock::now() + local_time::from_msec(50);
473 rate_control_->SetRateAtTargetTime(1, 1, sched.time_since_epoch().count());
474 clock_started_ = true;
475 }
476
477 return (payload_len_ != 0);
478 }
479
480 void PlayWAVApp::OnPlayoutComplete(MediaResult res) {
481 DCHECK(!audio_pipe_->GetPending());
482 RunLoop::current()->Quit();
483 }
484
485 } // namespace examples
486 } // namespace audio
487 } // namespace media
488 } // namespace mojo
489
490 MojoResult MojoMain(MojoHandle app_request) {
491 mojo::ApplicationRunner runner(new mojo::media::audio::examples::PlayWAVApp);
492 return runner.Run(app_request);
493 }
OLDNEW
« examples/audio_play_test/play_tone.cc ('K') | « 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