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

Side by Side Diff: services/media/audio/platform/generic/standard_output_base.cc

Issue 1424933002: Add an initial revision of an audio server. (Closed) Base URL: https://github.com/domokit/mojo.git@change4
Patch Set: refactor MixerKernel into a class to prepare for the addition of a linear interpolation sampler 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 <limits>
6
7 #include "base/logging.h"
8 #include "services/media/audio/audio_track_impl.h"
9 #include "services/media/audio/audio_track_to_output_link.h"
10 #include "services/media/audio/platform/generic/mixer.h"
11 #include "services/media/audio/platform/generic/standard_output_base.h"
12
13 namespace mojo {
14 namespace media {
15 namespace audio {
16
17 static constexpr LocalDuration MAX_TRIM_PERIOD = local_time::from_msec(10);
18 constexpr uint32_t StandardOutputBase::MixJob::INVALID_GENERATION;
19
20 StandardOutputBase::TrackBookkeeping::TrackBookkeeping() {}
21 StandardOutputBase::TrackBookkeeping::~TrackBookkeeping() {}
22
23 StandardOutputBase::StandardOutputBase(AudioOutputManager* manager)
24 : AudioOutput(manager) {
25 setup_mix_ =
26 [this] (const AudioTrackImplPtr& track, TrackBookkeeping* info) -> bool {
27 return SetupMix(track, info);
28 };
29
30 process_mix_ =
31 [this] (const AudioTrackImplPtr& track,
32 TrackBookkeeping* info,
33 const AudioPipe::AudioPacketRefPtr& pkt_ref) -> bool {
34 return ProcessMix(track, info, pkt_ref);
35 };
36
37 setup_trim_ =
38 [this] (const AudioTrackImplPtr& track, TrackBookkeeping* info) -> bool {
39 return SetupTrim(track, info);
40 };
41
42 process_trim_ =
43 [this] (const AudioTrackImplPtr& track,
44 TrackBookkeeping* info,
45 const AudioPipe::AudioPacketRefPtr& pkt_ref) -> bool {
46 return ProcessTrim(track, info, pkt_ref);
47 };
48
49 next_sched_time_ = LocalClock::now();
50 next_sched_time_known_ = true;
51 }
52
53 StandardOutputBase::~StandardOutputBase() {}
54
55 void StandardOutputBase::Process() {
56 bool mixed = false;
57 LocalTime now = LocalClock::now();
58
59 // At this point, we should always know when our implementation would like to
60 // be called to do some mixing work next. If we do not know, then we should
61 // have already shut down.
62 //
63 // If the next sched time has not arrived yet, don't attempt to mix anything.
64 // Just trim the queues and move on.
65 DCHECK(next_sched_time_known_);
66 if (now >= next_sched_time_) {
67 // Clear the flag, if the implementation does not set this flag by calling
68 // SetNextSchedTime during the cycle, we consider it to be an error and shut
69 // down.
70 next_sched_time_known_ = false;
71
72 // As long as our implementation wants to mix more and has not run into a
73 // problem trying to finish the mix job, mix some more.
74 do {
75 ::memset(&cur_mix_job_, 0, sizeof(cur_mix_job_));
76
77 if (!StartMixJob(&cur_mix_job_, now)) {
78 break;
79 }
80
81 ForeachTrack(setup_mix_, process_mix_);
82 mixed = true;
83 } while (FinishMixJob(cur_mix_job_));
84 }
85
86 if (!next_sched_time_known_) {
87 // TODO(johngro): log this as an error.
88 ShutdownSelf();
89 return;
90 }
91
92 // If we mixed nothing this time, make sure that we trim all of our track
93 // queues. No matter what is going on with the output hardware, we are not
94 // allowed to hold onto the queued data past its presentation time.
95 if (!mixed) {
96 ForeachTrack(setup_trim_, process_trim_);
97 }
98
99 // Figure out when we should wake up to do more work again. No matter how
100 // long our implementation wants to wait, we need to make sure to wake up and
101 // periodically trim our input queues.
102 LocalTime max_sched_time = now + MAX_TRIM_PERIOD;
103 ScheduleCallback((next_sched_time_ > max_sched_time)
104 ? max_sched_time
105 : next_sched_time_);
106 }
107
108 MediaResult StandardOutputBase::InitializeLink(
109 const AudioTrackToOutputLinkPtr& link) {
110 TrackBookkeeping* bk = AllocBookkeeping();
111 AudioTrackToOutputLink::BookkeepingPtr ref(bk);
112
113 // We should never fail to allocate our bookkeeping. The only way this can
114 // happen is if we have a badly behaved implementation.
115 if (!bk) { return MediaResult::INTERNAL_ERROR; }
116
117 // We cannot proceed if our track has somehow managed to go away already.
118 AudioTrackImplPtr track = link->GetTrack();
119 if (!track) { return MediaResult::INVALID_ARGUMENT; }
120
121 // Pick a mixer based on the input and output formats.
122 bk->mixer = Mixer::Select(track->Format(), output_format_);
123 if (bk->mixer == nullptr) { return MediaResult::UNSUPPORTED_CONFIG; }
124
125 // Looks like things went well. Stash a reference to our bookkeeping and get
126 // out.
127 link->output_bookkeeping() = std::move(ref);
128 return MediaResult::OK;
129 }
130
131 StandardOutputBase::TrackBookkeeping* StandardOutputBase::AllocBookkeeping() {
132 return new TrackBookkeeping();
133 }
134
135 void StandardOutputBase::ForeachTrack(const TrackSetupTask& setup,
136 const TrackProcessTask& process) {
137 for (auto iter = links_.begin(); iter != links_.end(); ) {
jeffbrown 2015/11/04 23:43:34 I'm actually kind of surprised that you designed t
johngro 2015/11/06 02:20:27 Keep in mind that the outputs are independent and
138 if (shutting_down()) { return; }
139
140 // Is the track still around? If so, process it. Otherwise, remove the
141 // track entry and move on.
142 const AudioTrackToOutputLinkPtr& link = *iter;
143 AudioTrackImplPtr track(link->GetTrack());
144
145 auto tmp_iter = iter++;
146 if (!track) {
147 links_.erase(tmp_iter);
148 continue;
149 }
150
151 // It would be nice to be able to use a dynamic cast for this, but currently
152 // we are building with no-rtti
153 TrackBookkeeping* info =
154 static_cast<TrackBookkeeping*>(link->output_bookkeeping().get());
155 DCHECK(info);
156
157 // Make sure that the mapping between the track's frame time domain and
158 // local time is up to date.
159 info->UpdateTrackTrans(track);
160
161 bool setup_done = false;
162 AudioPipe::AudioPacketRefPtr pkt_ref;
163 while (true) {
164 // Try to grab the front of the packet queue. If it has been flushed
165 // since the last time we grabbed it, be sure to reset our mixer's
166 // internal filter state.
167 bool was_flushed;
168 pkt_ref = link->LockPendingQueueFront(&was_flushed);
169 if (was_flushed) {
170 info->mixer->Reset();
171 }
172
173 // If the queue is empty, then we are done.
174 if (!pkt_ref) { break; }
175
176 // If we have not set up for this track yet, do so. If the setup fails
177 // for any reason, stop processing packets for this track.
178 if (!setup_done) {
179 setup_done = setup(track, info);
180 if (!setup_done) { break; }
181 }
182
183 // Now process the packet which is at the front of the track's queue. If
184 // the packet has been entirely consumed, pop it off the front and proceed
185 // to the next one. Otherwise, we are finished.
186 if (!process(track, info, pkt_ref)) { break; }
187 link->UnlockPendingQueueFront(&pkt_ref, true);
188 }
189
190 // Unlock the queue and proceed to the next track.
191 link->UnlockPendingQueueFront(&pkt_ref, false);
192
193 // Note: there is no point in doing this for the trim task, but it dosn't
194 // hurt anything, and its easier then introducing another function to the
195 // ForeachTrack arguments to run after each track is processed just for the
196 // purpose of setting this flag.
197 cur_mix_job_.accumulate = true;
198 }
199 }
200
201 bool StandardOutputBase::SetupMix(const AudioTrackImplPtr& track,
202 TrackBookkeeping* info) {
203 // If we need to recompose our transformation from output frame space to input
204 // fractional frames, do so now.
205 DCHECK(info);
206 info->UpdateOutputTrans(cur_mix_job_);
207 cur_mix_job_.frames_produced = 0;
208
209 return true;
210 }
211
212 bool StandardOutputBase::ProcessMix(
213 const AudioTrackImplPtr& track,
214 TrackBookkeeping* info,
215 const AudioPipe::AudioPacketRefPtr& pkt_ref) {
216 // Sanity check our parameters.
217 DCHECK(info);
218 DCHECK(pkt_ref);
219
220 // We had better have a valid job, or why are we here?
221 DCHECK(cur_mix_job_.buf);
222 DCHECK(cur_mix_job_.buf_frames);
223 DCHECK(cur_mix_job_.frames_produced <= cur_mix_job_.buf_frames);
224
225 // Have we produced all that we are supposed to? If so, hold the current
226 // packet and move on to the next track.
227 if (cur_mix_job_.frames_produced >= cur_mix_job_.buf_frames) {
228 return false;
229 }
230
231 uint32_t frames_left = cur_mix_job_.buf_frames - cur_mix_job_.frames_produced;
232 void* buf = static_cast<uint8_t*>(cur_mix_job_.buf)
233 + (cur_mix_job_.frames_produced * output_bytes_per_frame_);
234
235 // Figure out where this job starts, expressed in fractional input frames.
236 int64_t start_pts_ftf;
237 bool good = info->out_frames_to_track_frames.DoForwardTransform(
238 cur_mix_job_.start_pts_of + cur_mix_job_.frames_produced,
239 &start_pts_ftf);
240 DCHECK(good);
241
242 // If the start of this mix job is past the end of this packet presentation,
243 // do no mixing. Let the ForeachTrack loop know that we are done with the
244 // packet and it can be released.
245 if (start_pts_ftf >= pkt_ref->end_pts()) {
246 return true;
247 }
248
249 // If this track is currently paused (or being sampled extremely slowly), our
250 // step size will be zero. We know that this packet will be relevant at some
251 // point in the future, but right now it contributes nothing. Tell the
252 // ForeachTrack loop that we are done and to hold onto this packet for now.
253 if (!info->step_size) {
254 return false;
255 }
256
257 // Figure out how many output samples into the current job this packet starts.
258 int64_t delta;
259 int64_t output_offset_64;
260 if (pkt_ref->start_pts() > start_pts_ftf) {
261 delta = pkt_ref->start_pts() - start_pts_ftf;
262 output_offset_64 = delta + info->step_size - 1;
263 output_offset_64 /= info->step_size;
264 } else {
265 output_offset_64 = 0;
266 }
267 DCHECK_GE(output_offset_64, 0);
268
269 // If this packet starts after the end of this job (entirely in the future),
270 // then we are done for now.
271 if (output_offset_64 >= frames_left) {
272 return false;
273 }
274
275 // Figure out the offset (in fractional frames) into this packet where we want
276 // to start sampling.
277 int64_t input_offset_64;
278 if (output_offset_64) {
279 input_offset_64 = output_offset_64 * info->step_size;
280 input_offset_64 -= delta;
281 DCHECK_LT(input_offset_64, info->step_size);
282 } else {
283 input_offset_64 = start_pts_ftf - pkt_ref->start_pts();
284 }
285 DCHECK_GE(input_offset_64, 0);
286 DCHECK_LE(input_offset_64, std::numeric_limits<int32_t>::max());
287 DCHECK_LT(input_offset_64, pkt_ref->end_pts() - pkt_ref->start_pts());
288
289 uint32_t input_offset = static_cast<uint32_t>(input_offset_64);
290 uint32_t output_offset = static_cast<uint32_t>(output_offset_64);
291 const auto& regions = pkt_ref->regions();
292 DCHECK(info->mixer != nullptr);
293
294 for (size_t i = 0;
295 (i < regions.size()) && (output_offset < frames_left);
296 ++i) {
297 const auto& region = regions[i];
298
299 if (input_offset >= region.frac_frame_len) {
300 input_offset -= region.frac_frame_len;
301 continue;
302 }
303
304 bool consumed_source = info->mixer->Mix(buf,
jeffbrown 2015/11/04 23:43:34 As designed, we're going to have problems replacin
johngro 2015/11/06 02:20:27 Acknowledged. This is a complicated optimization
305 frames_left,
306 &output_offset,
307 region.base,
308 region.frac_frame_len,
309 &input_offset,
310 info->step_size,
311 cur_mix_job_.accumulate);
312 DCHECK_LE(output_offset, frames_left);
313
314 if (!consumed_source) {
315 // Looks like we didn't consume all of this region. Assert that we have
316 // produced all of our frames and we are done.
317 DCHECK(output_offset == frames_left);
318 return false;
319 }
320
321 input_offset -= region.frac_frame_len;
322 }
323
324 cur_mix_job_.frames_produced += output_offset;
325 DCHECK(cur_mix_job_.frames_produced <= cur_mix_job_.buf_frames);
326 return true;
327 }
328
329 bool StandardOutputBase::SetupTrim(const AudioTrackImplPtr& track,
330 TrackBookkeeping* info) {
331 // Compute the cutoff time we will use to decide wether or not to trim
332 // packets. ForeachTracks has already updated our transformation, no need
333 // for us to do so here.
334 DCHECK(info);
335
336 int64_t local_now_ticks = LocalClock::now().time_since_epoch().count();
337
338 // The behavior of the RateControlBase implementation guarantees that the
339 // transformation into the media timeline is never singular. If the
340 // forward transformation fails it can only be because of an overflow,
341 // which should be impossible unless the user has defined a playback rate
342 // where the ratio between media time ticks and local time ticks is
343 // greater than one.
344 //
345 // IOW - this should never happen. If it does, we just stop processing
346 // payloads.
347 //
348 // TODO(johngro): Log an error? Communicate this to the user somehow?
349 if (!info->lt_to_track_frames.DoForwardTransform(local_now_ticks,
350 &trim_threshold_)) {
351 return false;
352 }
353
354 return true;
355 }
356
357 bool StandardOutputBase::ProcessTrim(
358 const AudioTrackImplPtr& track,
359 TrackBookkeeping* info,
360 const AudioPipe::AudioPacketRefPtr& pkt_ref) {
361 DCHECK(pkt_ref);
362
363 // If the presentation end of this packet is in the future, stop trimming.
364 if (pkt_ref->end_pts() > trim_threshold_) {
365 return false;
366 }
367
368 return true;
369 }
370
371 void StandardOutputBase::TrackBookkeeping::UpdateTrackTrans(
372 const AudioTrackImplPtr& track) {
373 LinearTransform tmp;
374 uint32_t gen;
375
376 DCHECK(track);
377 track->SnapshotRateTrans(&tmp, &gen);
378
379 // If the local time -> media time transformation has not changed since the
380 // last time we examines it, just get out now.
381 if (lt_to_track_frames_gen == gen) { return; }
382
383 // The transformation has changed, re-compute the local time -> track frame
384 // transformation.
385 LinearTransform scale(0, track->FractionalFrameToMediaTimeRatio(), 0);
386 bool good;
387
388 lt_to_track_frames.a_zero = tmp.a_zero;
389 good = scale.DoReverseTransform(tmp.b_zero, &lt_to_track_frames.b_zero);
390 DCHECK(good);
391 good = LinearTransform::Ratio::Compose(scale.scale,
392 tmp.scale,
393 &lt_to_track_frames.scale);
394 DCHECK(good);
395
396 // Update the generation, and invalidate the output to track generation.
397 lt_to_track_frames_gen = gen;
398 out_frames_to_track_frames_gen = MixJob::INVALID_GENERATION;
399 }
400
401 void StandardOutputBase::TrackBookkeeping::UpdateOutputTrans(
402 const MixJob& job) {
403 // We should not be here unless we have a valid mix job. From our point of
404 // view, this means that we have a job which supplies a valid transformation
405 // from local time to output frames.
406 DCHECK(job.local_to_output);
407 DCHECK(job.local_to_output_gen != MixJob::INVALID_GENERATION);
408
409 // If our generations match, we don't need to re-compute anything. Just use
410 // what we have already.
411 if (out_frames_to_track_frames_gen == job.local_to_output_gen) { return; }
412
413 // Assert that we have a good mapping from local time to fractional track
414 // frames.
415 //
416 // TODO(johngro): Don't assume that 0 means invalid. Make it a proper
417 // constant defined somewhere.
418 DCHECK(lt_to_track_frames_gen);
419
420 // Compose the job supplied transformation from local to output with the
421 // track supplied mapping from local to fraction input frames to produce a
422 // transformation which maps from output frames to fractional input frames.
423 //
424 // TODO(johngro): Make this composition operation part of the LinearTransform
425 // class instead of doing it by hand here. Its a more complicated task that
426 // one might initially think, because of the need to deal with the
427 // intermediate offset term, and distributing it to either side of the end of
428 // the transformation with a minimum amt of loss, while avoiding overflow.
429 //
430 // For now, we punt, do it by hand and just assume that everything went well.
431 LinearTransform& dst = out_frames_to_track_frames;
432
433 // Distribute the intermediate offset entirely to the fractional frame domain
434 // for now. We can do better by extracting portions of the intermedate
435 // offset that can be scaled by the ratios on either side of with without
436 // loss, but for now this should be close enough.
437 int64_t intermediate = job.local_to_output->a_zero
438 - lt_to_track_frames.a_zero;
439 int64_t track_frame_offset;
440
441 // TODO(johngro): add routines to LinearTransform::Ratio which allow us to
442 // scale using just a ratio without needing to create a linear transform with
443 // empty offsets.
444 LinearTransform tmp(0, lt_to_track_frames.scale, 0);
445 bool good = tmp.DoForwardTransform(intermediate, &track_frame_offset);
446 DCHECK(good);
447
448 dst.a_zero = job.local_to_output->b_zero;
449 dst.b_zero = lt_to_track_frames.b_zero + track_frame_offset;
450
451 // TODO(johngro): Add options to allow us to invert one or both of the ratios
452 // during composition instead of needing to make a temporary ratio to
453 // acomplish the task.
454 LinearTransform::Ratio tmp_ratio(job.local_to_output->scale.denominator,
455 job.local_to_output->scale.numerator);
456 good = LinearTransform::Ratio::Compose(tmp_ratio,
457 lt_to_track_frames.scale,
458 &dst.scale);;
459 DCHECK(good);
460
461 // Finally, compute the step size in fractional frames. IOW, every time we
462 // move forward one output frame, how many fractional frames of input do we
463 // consume. Don't bother doing the multiplication if we already know that the
464 // numerator is zero.
465 //
466 // TODO(johngro): same complaint as before... Do this without a temp. The
467 // special casing should be handled in the routine added to
468 // LinearTransform::Ratio.
469 DCHECK(dst.scale.denominator);
470 if (!dst.scale.numerator) {
471 step_size = 0;
472 } else {
473 LinearTransform tmp(0, dst.scale, 0);
474 int64_t tmp_step_size;
475
476 good = tmp.DoForwardTransform(1, &tmp_step_size);
477
478 DCHECK(good);
479 DCHECK_GE(tmp_step_size, 0);
480 DCHECK_LE(tmp_step_size, std::numeric_limits<uint32_t>::max());
481
482 step_size = static_cast<uint32_t>(tmp_step_size);
483 }
484
485 // Done, update our generation.
486 out_frames_to_track_frames_gen = job.local_to_output_gen;
487 }
488
489 } // namespace audio
490 } // namespace media
491 } // namespace mojo
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698