| 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 "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 } |
| OLD | NEW |