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

Side by Side Diff: examples/audio_play_test/play_tone.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/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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698