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 <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, <_to_track_frames.b_zero); | |
390 DCHECK(good); | |
391 good = LinearTransform::Ratio::Compose(scale.scale, | |
392 tmp.scale, | |
393 <_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 | |
OLD | NEW |