Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 } | |
| OLD | NEW |