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/utility/run_loop.h" | |
| 14 #include "mojo/services/media/audio/interfaces/audio_server.mojom.h" | |
| 15 #include "mojo/services/media/audio/interfaces/audio_track.mojom.h" | |
| 16 #include "mojo/services/media/common/cpp/circular_buffer_media_pipe_adapter.h" | |
| 17 #include "mojo/services/media/common/cpp/linear_transform.h" | |
| 18 #include "mojo/services/media/common/cpp/local_time.h" | |
| 19 #include "mojo/services/media/common/interfaces/rate_control.mojom.h" | |
| 20 | |
| 21 namespace mojo { | |
| 22 namespace media { | |
| 23 namespace audio { | |
| 24 namespace examples { | |
| 25 | |
| 26 static constexpr uint32_t SAMP_FREQ = 48000; | |
| 27 static constexpr uint32_t CHUNK_USEC = 1000; | |
| 28 static constexpr uint32_t BUF_LO_WATER_USEC = 50000; | |
| 29 static constexpr uint32_t BUF_HI_WATER_USEC = BUF_LO_WATER_USEC | |
| 30 + (4 * CHUNK_USEC); | |
| 31 static constexpr uint32_t BUF_DEPTH_USEC = BUF_HI_WATER_USEC | |
| 32 + (4 * CHUNK_USEC); | |
|
jeffbrown
2015/11/04 19:34:43
Is there anything significant about the fact the t
johngro
2015/11/05 00:25:16
not really. There needs to be some distance betwe
| |
| 33 static constexpr uint32_t FRAME_BYTES = 2; | |
|
jeffbrown
2015/11/04 19:34:43
sizeof(uint16_t)?
johngro
2015/11/05 00:25:15
sure; but
<pedantic>
sizeof(int16_t) since this is
| |
| 34 | |
| 35 static inline constexpr uint32_t USecToBytes(uint64_t usec) { | |
| 36 return ((usec * SAMP_FREQ) / 1000000) * FRAME_BYTES; | |
| 37 } | |
| 38 | |
| 39 class PlayToneApp : public ApplicationDelegate { | |
| 40 public: | |
| 41 void Initialize(ApplicationImpl* app) override; | |
| 42 | |
| 43 private: | |
| 44 bool GenerateToneCbk(MediaResult res); | |
| 45 void PlayTone(double freq_hz, double amplitude, double duration_sec); | |
| 46 void Cleanup(); | |
| 47 | |
| 48 AudioServerPtr audio_server_; | |
| 49 AudioTrackPtr audio_track_; | |
| 50 RateControlPtr rate_control_; | |
| 51 std::unique_ptr<CircularBufferMediaPipeAdapter> pipe_; | |
| 52 | |
| 53 bool clock_started_ = false; | |
| 54 uint64_t media_time_ = 0; | |
| 55 double freq_hz_ = 440.0; | |
| 56 double amplitude_ = 1.0; | |
| 57 }; | |
| 58 | |
| 59 void PlayToneApp::Initialize(ApplicationImpl* app) { | |
| 60 MediaResult result = MediaResult::UNKNOWN_ERROR; | |
| 61 | |
| 62 app->ConnectToService("mojo:audio_server", &audio_server_); | |
| 63 audio_server_->CreateTrack(GetProxy(&audio_track_)); | |
| 64 | |
| 65 do { | |
|
jeffbrown
2015/11/04 19:34:43
This construct with early breaks is a little hard
johngro
2015/11/05 00:25:16
yeah... Classically, I would use gotos for this pa
| |
| 66 // Query the sink's format capabilities. | |
| 67 AudioTrackDescriptorPtr sink_desc; | |
| 68 auto desc_cbk = [&sink_desc](AudioTrackDescriptorPtr desc) { | |
| 69 sink_desc = desc.Pass(); | |
| 70 }; | |
| 71 audio_track_->Describe(AudioTrack::DescribeCallback(desc_cbk)); | |
| 72 | |
| 73 // TODO(johngro): this pattern is awkward. We really don't want to be | |
| 74 // calling WaitForIncomingResponse, even if we were able supply a timeout. | |
| 75 // The best practice would be to defer to a handler for the message we are | |
| 76 // expecting to eventually come back. | |
| 77 // | |
| 78 // But... what if the message never comes back? Perhaps the service is not | |
| 79 // implemented properly, or perhaps the service is malicious. We could | |
| 80 // queue a delayed message on our run loop which indicates a timeout, but | |
| 81 // then what happens when when the response to Describe comes back (as | |
| 82 // expected). We don't really have a good way to cancel the "timeout" | |
| 83 // message once we have queued it. Maintaining all of the bookkeeping | |
| 84 // required to nerf the callback when it happens and is discovered to be | |
| 85 // useless is going to get very old, very fast. | |
| 86 // | |
| 87 // For now, we just do the evil thing and block during init, but I sure do | |
| 88 // wish there was something nicer we could do. | |
| 89 if (!audio_track_.WaitForIncomingResponse()) { | |
|
jeffbrown
2015/11/04 19:34:43
The pattern you're looking for here is to do two t
johngro
2015/11/05 00:25:16
Acknowledged.
We should talk sometime about this.
| |
| 90 LOG(ERROR) << "Failed to fetch sync capabilities; no response received."; | |
| 91 break; | |
| 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()) { | |
|
jeffbrown
2015/11/04 19:34:43
This is our second WaitForIncomingResponse.
I thi
johngro
2015/11/05 00:25:16
Ack.
Re: "why should retrieving this interface ev
| |
| 101 LOG(ERROR) << | |
| 102 "Failed to fetch rate control interface; no response received."; | |
| 103 break; | |
| 104 } | |
| 105 if (result != MediaResult::OK) { | |
| 106 LOG(ERROR) << "Failed to get rate control interface. (res = " | |
| 107 << result << ")"; | |
| 108 break; | |
| 109 } | |
| 110 | |
| 111 // Configure our sink for 16-bit 48KHz mono. | |
| 112 AudioTrackConfigurationPtr cfg = AudioTrackConfiguration::New(); | |
| 113 cfg->max_frames = USecToBytes(BUF_DEPTH_USEC) / FRAME_BYTES; | |
| 114 | |
| 115 LpcmMediaTypeDetailsPtr pcm_cfg = LpcmMediaTypeDetails::New(); | |
| 116 pcm_cfg->sample_format = LpcmSampleFormat::SIGNED_16; | |
| 117 pcm_cfg->samples_per_frame = 1; | |
| 118 pcm_cfg->frames_per_second = SAMP_FREQ; | |
| 119 | |
| 120 cfg->media_type = MediaType::New(); | |
| 121 cfg->media_type->scheme = MediaTypeScheme::LPCM; | |
| 122 cfg->media_type->details = MediaTypeDetails::New(); | |
| 123 cfg->media_type->details->set_lpcm(pcm_cfg.Pass()); | |
| 124 | |
| 125 MediaPipePtr pipe; | |
| 126 { | |
| 127 auto cbk = [&result](MediaResult res) { | |
| 128 result = res; | |
| 129 }; | |
| 130 audio_track_->Configure(cfg.Pass(), GetProxy(&pipe), cbk); | |
| 131 } | |
| 132 | |
| 133 if (!audio_track_.WaitForIncomingResponse()) { | |
|
jeffbrown
2015/11/04 19:34:43
We already have the pipe so we can start using it
johngro
2015/11/05 00:25:16
See above. I will switch to this pattern, and re-
| |
| 134 LOG(ERROR) << "Failed to configure sink; no response received."; | |
| 135 break; | |
| 136 } | |
| 137 | |
| 138 if (result != MediaResult::OK) { | |
| 139 LOG(ERROR) << "Failed to configure sink. (res = " | |
| 140 << result << ")"; | |
| 141 break; | |
| 142 } | |
| 143 | |
| 144 | |
| 145 // Now that we are configured and have our media pipe, pass its interface to | |
| 146 // our circular buffer helper, set up our high/low water marks, register our | |
| 147 // callback, and start to buffer our audio. | |
| 148 pipe_.reset(new CircularBufferMediaPipeAdapter(pipe.Pass())); | |
| 149 pipe_->SetSignalCallback( | |
|
jeffbrown
2015/11/04 19:34:43
Oddly enough base::Bind is cleaner for stuff like
johngro
2015/11/05 00:25:16
Acknowledged.
base::Bind has its own issues. Wit
| |
| 150 [this](MediaResult res) -> bool { | |
|
jeffbrown
2015/11/04 19:34:43
Note the inherent assumption that "this" will outl
johngro
2015/11/05 00:25:16
Indeed. This is one of the other issues I have wi
| |
| 151 return GenerateToneCbk(res); | |
| 152 }); | |
| 153 pipe_->SetWatermarks(USecToBytes(BUF_HI_WATER_USEC), | |
| 154 USecToBytes(BUF_LO_WATER_USEC)); | |
| 155 result = MediaResult::OK; | |
| 156 } while (false); | |
| 157 | |
| 158 if (result != MediaResult::OK) { | |
| 159 Cleanup(); | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 bool PlayToneApp::GenerateToneCbk(MediaResult res) { | |
| 164 using MappedPacket = CircularBufferMediaPipeAdapter::MappedPacket; | |
| 165 MappedPacket mapped_pkt; | |
| 166 | |
| 167 DCHECK_GT(freq_hz_, 0.0); | |
| 168 DCHECK_GE(amplitude_, 0.0); | |
| 169 DCHECK_LE(amplitude_, 1.0); | |
| 170 | |
| 171 if (res != MediaResult::OK) { | |
|
jeffbrown
2015/11/04 19:34:43
As we discussed before, it would be more Mojo-like
johngro
2015/11/05 00:25:16
As we discussed before, I will clean all of this u
| |
| 172 LOG(ERROR) << "Fatal error in cbuf (" << res << ")."; | |
| 173 goto error; | |
|
jeffbrown
2015/11/04 19:34:43
Not sure what the style guide has to say about got
johngro
2015/11/05 00:25:16
The external style guide actually sidesteps the is
| |
| 174 } | |
| 175 | |
| 176 while (!pipe_->AboveHiWater()) { | |
| 177 res = pipe_->CreateMediaPacket(USecToBytes(CHUNK_USEC), | |
| 178 false, | |
| 179 &mapped_pkt); | |
| 180 if (res != MediaResult::OK) { | |
|
jeffbrown
2015/11/04 19:34:43
Why would this ever fail? Is it DCHECK worthy?
johngro
2015/11/05 00:25:16
Please refer to the documentation present for this
| |
| 181 LOG(ERROR) << "Unexpected error when creating media packet (" | |
| 182 << res << ")."; | |
| 183 goto error; | |
| 184 } | |
| 185 | |
| 186 mapped_pkt.packet()->pts = media_time_; | |
|
jeffbrown
2015/11/04 19:34:43
Avoid abbreviations. Prefer "presentation_time".
johngro
2015/11/05 00:25:16
I responded to this already, 6 patch sets ago. As
| |
| 187 | |
| 188 for (uint32_t i = 0; i < MappedPacket::kMaxRegions; ++i) { | |
| 189 int16_t* data = reinterpret_cast<int16_t*>(mapped_pkt.data(i)); | |
|
jeffbrown
2015/11/04 19:34:43
Would it make sense to embed the data type into Ma
johngro
2015/11/05 00:25:16
no, I don't think so. If it would make you feel b
| |
| 190 uint64_t len = mapped_pkt.length(i); | |
| 191 if (!data) continue; | |
|
jeffbrown
2015/11/04 19:34:43
swap the above two lines
johngro
2015/11/05 00:25:16
Done.
| |
| 192 | |
| 193 DCHECK(len && !(len % FRAME_BYTES)); | |
| 194 len /= FRAME_BYTES; | |
| 195 for (uint64_t i = 0; i < len; ++i, ++media_time_) { | |
| 196 double tmp = ((M_PI * 2.0) / SAMP_FREQ) * freq_hz_ * media_time_; | |
| 197 data[i] = std::numeric_limits<int16_t>::max() * amplitude_ * sin(tmp); | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 res = pipe_->SendMediaPacket(&mapped_pkt); | |
|
jeffbrown
2015/11/04 19:34:43
Is this blocking behind the scenes?
Kind of annoy
johngro
2015/11/05 00:25:15
no; this is interacting with a client side helper
| |
| 202 if (res != MediaResult::OK) { | |
| 203 LOG(ERROR) << "Unexpected error when sending media packet (" | |
| 204 << res << ")."; | |
| 205 pipe_->CancelMediaPacket(&mapped_pkt); | |
| 206 goto error; | |
| 207 } | |
| 208 } | |
| 209 | |
| 210 if (!clock_started_) { | |
| 211 // In theory, this could be done at compile time using std::ratio, but | |
| 212 // std::ratio is prohibited. | |
| 213 LinearTransform::Ratio audio_rate(SAMP_FREQ, 1); | |
| 214 LinearTransform::Ratio local_time_rate(LocalDuration::period::num, | |
| 215 LocalDuration::period::den); | |
| 216 LinearTransform::Ratio rate; | |
| 217 bool success = LinearTransform::Ratio::Compose(local_time_rate, | |
| 218 audio_rate, | |
| 219 &rate); | |
| 220 DCHECK(success); // assert that there was no loss of precision. | |
| 221 | |
| 222 LOG(INFO) << "Setting rate " << rate; | |
| 223 | |
| 224 rate_control_->SetRate(rate.numerator, rate.denominator); | |
| 225 clock_started_ = true; | |
| 226 } | |
| 227 | |
| 228 return true; | |
| 229 | |
| 230 error: | |
| 231 Cleanup(); | |
| 232 return false; | |
| 233 } | |
| 234 | |
| 235 void PlayToneApp::Cleanup() { | |
| 236 audio_track_.reset(); | |
| 237 audio_server_.reset(); | |
| 238 RunLoop::current()->Quit(); | |
| 239 } | |
| 240 | |
| 241 } // namespace examples | |
| 242 } // namespace audio | |
| 243 } // namespace media | |
| 244 } // namespace mojo | |
| 245 | |
| 246 MojoResult MojoMain(MojoHandle app_request) { | |
| 247 mojo::ApplicationRunner runner(new mojo::media::audio::examples::PlayToneApp); | |
| 248 return runner.Run(app_request); | |
| 249 } | |
| OLD | NEW |