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(); ) { |
| 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, |
| 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 |