| 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 <math.h> |
| 6 #include <memory> |
| 7 |
| 8 #include "mojo/public/c/system/main.h" |
| 9 #include "mojo/public/cpp/application/application_delegate.h" |
| 10 #include "mojo/public/cpp/application/application_impl.h" |
| 11 #include "mojo/public/cpp/application/application_runner.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/media/common/interfaces/rate_control.mojom.h" |
| 19 |
| 20 namespace mojo { |
| 21 namespace media { |
| 22 namespace audio { |
| 23 namespace examples { |
| 24 |
| 25 static constexpr uint32_t SAMP_FREQ = 48000; |
| 26 static constexpr uint32_t CHUNK_USEC = 1000; |
| 27 static constexpr uint32_t BUF_LO_WATER_USEC = 50000; |
| 28 static constexpr uint32_t BUF_HI_WATER_USEC = BUF_LO_WATER_USEC |
| 29 + (4 * CHUNK_USEC); |
| 30 static constexpr uint32_t BUF_DEPTH_USEC = BUF_HI_WATER_USEC |
| 31 + (4 * CHUNK_USEC); |
| 32 static constexpr uint32_t FRAME_BYTES = sizeof(int16_t); |
| 33 |
| 34 static inline constexpr uint32_t USecToBytes(uint64_t usec) { |
| 35 return ((usec * SAMP_FREQ) / 1000000) * FRAME_BYTES; |
| 36 } |
| 37 |
| 38 class PlayToneApp : public ApplicationDelegate { |
| 39 public: |
| 40 void Initialize(ApplicationImpl* app) override; |
| 41 |
| 42 private: |
| 43 bool GenerateToneCbk(MediaResult res); |
| 44 void PlayTone(double freq_hz, double amplitude, double duration_sec); |
| 45 void Cleanup(); |
| 46 |
| 47 AudioServerPtr audio_server_; |
| 48 AudioTrackPtr audio_track_; |
| 49 RateControlPtr rate_control_; |
| 50 std::unique_ptr<CircularBufferMediaPipeAdapter> pipe_; |
| 51 |
| 52 bool clock_started_ = false; |
| 53 uint64_t media_time_ = 0; |
| 54 double freq_hz_ = 440.0; |
| 55 double amplitude_ = 1.0; |
| 56 }; |
| 57 |
| 58 void PlayToneApp::Initialize(ApplicationImpl* app) { |
| 59 MediaResult result = MediaResult::UNKNOWN_ERROR; |
| 60 |
| 61 app->ConnectToService("mojo:audio_server", &audio_server_); |
| 62 audio_server_->CreateTrack(GetProxy(&audio_track_)); |
| 63 |
| 64 // Query the sink's format capabilities. |
| 65 AudioTrackDescriptorPtr sink_desc; |
| 66 auto desc_cbk = [&sink_desc](AudioTrackDescriptorPtr desc) { |
| 67 sink_desc = desc.Pass(); |
| 68 }; |
| 69 audio_track_->Describe(AudioTrack::DescribeCallback(desc_cbk)); |
| 70 |
| 71 // TODO(johngro): this pattern is awkward. We really don't want to be |
| 72 // calling WaitForIncomingResponse, even if we were able supply a timeout. |
| 73 // The best practice would be to defer to a handler for the message we are |
| 74 // expecting to eventually come back. |
| 75 // |
| 76 // But... what if the message never comes back? Perhaps the service is not |
| 77 // implemented properly, or perhaps the service is malicious. We could |
| 78 // queue a delayed message on our run loop which indicates a timeout, but |
| 79 // then what happens when when the response to Describe comes back (as |
| 80 // expected). We don't really have a good way to cancel the "timeout" |
| 81 // message once we have queued it. Maintaining all of the bookkeeping |
| 82 // required to nerf the callback when it happens and is discovered to be |
| 83 // useless is going to get very old, very fast. |
| 84 // |
| 85 // For now, we just do the evil thing and block during init, but I sure do |
| 86 // wish there was something nicer we could do. |
| 87 if (!audio_track_.WaitForIncomingResponse()) { |
| 88 MOJO_LOG(ERROR) |
| 89 << "Failed to fetch sync capabilities; no response received."; |
| 90 Cleanup(); |
| 91 return; |
| 92 } |
| 93 |
| 94 // TODO(johngro): do something useful with our capabilities description. |
| 95 sink_desc.reset(); |
| 96 |
| 97 // Grab the rate control interface for our audio renderer. |
| 98 auto get_rc_cbk = [&result](MediaResult res) { result = res; }; |
| 99 audio_track_->GetRateControl(GetProxy(&rate_control_), get_rc_cbk); |
| 100 if (!audio_track_.WaitForIncomingResponse()) { |
| 101 MOJO_LOG(ERROR) << |
| 102 "Failed to fetch rate control interface; no response received."; |
| 103 Cleanup(); |
| 104 return; |
| 105 } |
| 106 |
| 107 if (result != MediaResult::OK) { |
| 108 MOJO_LOG(ERROR) << "Failed to get rate control interface. (res = " |
| 109 << result << ")"; |
| 110 Cleanup(); |
| 111 return; |
| 112 } |
| 113 |
| 114 // Configure our sink for 16-bit 48KHz mono. |
| 115 AudioTrackConfigurationPtr cfg = AudioTrackConfiguration::New(); |
| 116 cfg->max_frames = USecToBytes(BUF_DEPTH_USEC) / FRAME_BYTES; |
| 117 |
| 118 LpcmMediaTypeDetailsPtr pcm_cfg = LpcmMediaTypeDetails::New(); |
| 119 pcm_cfg->sample_format = LpcmSampleFormat::SIGNED_16; |
| 120 pcm_cfg->samples_per_frame = 1; |
| 121 pcm_cfg->frames_per_second = SAMP_FREQ; |
| 122 |
| 123 cfg->media_type = MediaType::New(); |
| 124 cfg->media_type->scheme = MediaTypeScheme::LPCM; |
| 125 cfg->media_type->details = MediaTypeDetails::New(); |
| 126 cfg->media_type->details->set_lpcm(pcm_cfg.Pass()); |
| 127 |
| 128 MediaPipePtr pipe; |
| 129 { |
| 130 auto cbk = [&result](MediaResult res) { |
| 131 result = res; |
| 132 }; |
| 133 audio_track_->Configure(cfg.Pass(), GetProxy(&pipe), cbk); |
| 134 } |
| 135 |
| 136 if (!audio_track_.WaitForIncomingResponse()) { |
| 137 MOJO_LOG(ERROR) << "Failed to configure sink; no response received."; |
| 138 Cleanup(); |
| 139 return; |
| 140 } |
| 141 |
| 142 if (result != MediaResult::OK) { |
| 143 MOJO_LOG(ERROR) << "Failed to configure sink. (res = " |
| 144 << result << ")"; |
| 145 Cleanup(); |
| 146 return; |
| 147 } |
| 148 |
| 149 // Now that we are configured and have our media pipe, pass its interface to |
| 150 // our circular buffer helper, set up our high/low water marks, register our |
| 151 // callback, and start to buffer our audio. |
| 152 pipe_.reset(new CircularBufferMediaPipeAdapter(pipe.Pass())); |
| 153 pipe_->SetSignalCallback( |
| 154 [this](MediaResult res) -> bool { |
| 155 return GenerateToneCbk(res); |
| 156 }); |
| 157 pipe_->SetWatermarks(USecToBytes(BUF_HI_WATER_USEC), |
| 158 USecToBytes(BUF_LO_WATER_USEC)); |
| 159 } |
| 160 |
| 161 bool PlayToneApp::GenerateToneCbk(MediaResult res) { |
| 162 using MappedPacket = CircularBufferMediaPipeAdapter::MappedPacket; |
| 163 MappedPacket mapped_pkt; |
| 164 |
| 165 MOJO_DCHECK(freq_hz_ > 0.0); |
| 166 MOJO_DCHECK(amplitude_ >= 0.0); |
| 167 MOJO_DCHECK(amplitude_ <= 1.0); |
| 168 |
| 169 if (res != MediaResult::OK) { |
| 170 MOJO_LOG(ERROR) << "Fatal error in cbuf (" << res << ")."; |
| 171 Cleanup(); |
| 172 return false; |
| 173 } |
| 174 |
| 175 while (!pipe_->AboveHiWater()) { |
| 176 res = pipe_->CreateMediaPacket(USecToBytes(CHUNK_USEC), |
| 177 false, |
| 178 &mapped_pkt); |
| 179 if (res != MediaResult::OK) { |
| 180 MOJO_LOG(ERROR) << "Unexpected error when creating media packet (" |
| 181 << res << ")."; |
| 182 Cleanup(); |
| 183 return false; |
| 184 } |
| 185 |
| 186 mapped_pkt.packet()->pts = media_time_; |
| 187 |
| 188 for (uint32_t i = 0; i < MappedPacket::kMaxRegions; ++i) { |
| 189 int16_t* data = reinterpret_cast<int16_t*>(mapped_pkt.data(i)); |
| 190 uint64_t len; |
| 191 |
| 192 if (!data) continue; |
| 193 len = mapped_pkt.length(i); |
| 194 |
| 195 MOJO_DCHECK(len && !(len % FRAME_BYTES)); |
| 196 len /= FRAME_BYTES; |
| 197 for (uint64_t i = 0; i < len; ++i, ++media_time_) { |
| 198 double tmp = ((M_PI * 2.0) / SAMP_FREQ) * freq_hz_ * media_time_; |
| 199 data[i] = std::numeric_limits<int16_t>::max() * amplitude_ * sin(tmp); |
| 200 } |
| 201 } |
| 202 |
| 203 res = pipe_->SendMediaPacket(&mapped_pkt); |
| 204 if (res != MediaResult::OK) { |
| 205 MOJO_LOG(ERROR) << "Unexpected error when sending media packet (" |
| 206 << res << ")."; |
| 207 pipe_->CancelMediaPacket(&mapped_pkt); |
| 208 Cleanup(); |
| 209 return false; |
| 210 } |
| 211 } |
| 212 |
| 213 if (!clock_started_) { |
| 214 // In theory, this could be done at compile time using std::ratio, but |
| 215 // std::ratio is prohibited. |
| 216 LinearTransform::Ratio audio_rate(SAMP_FREQ, 1); |
| 217 LinearTransform::Ratio local_time_rate(LocalDuration::period::num, |
| 218 LocalDuration::period::den); |
| 219 LinearTransform::Ratio rate; |
| 220 bool success = LinearTransform::Ratio::Compose(local_time_rate, |
| 221 audio_rate, |
| 222 &rate); |
| 223 MOJO_DCHECK(success); // assert that there was no loss of precision. |
| 224 |
| 225 MOJO_LOG(INFO) << "Setting rate " << rate; |
| 226 |
| 227 rate_control_->SetRate(rate.numerator, rate.denominator); |
| 228 clock_started_ = true; |
| 229 } |
| 230 |
| 231 return true; |
| 232 } |
| 233 |
| 234 void PlayToneApp::Cleanup() { |
| 235 audio_track_.reset(); |
| 236 audio_server_.reset(); |
| 237 RunLoop::current()->Quit(); |
| 238 } |
| 239 |
| 240 } // namespace examples |
| 241 } // namespace audio |
| 242 } // namespace media |
| 243 } // namespace mojo |
| 244 |
| 245 MojoResult MojoMain(MojoHandle app_request) { |
| 246 mojo::ApplicationRunner runner(new mojo::media::audio::examples::PlayToneApp); |
| 247 return runner.Run(app_request); |
| 248 } |
| OLD | NEW |