Audacity 3.2.0
Classes | Public Member Functions | Private Attributes | List of all members
PlaybackSchedule::TimeQueue Class Reference

#include <PlaybackSchedule.h>

Collaboration diagram for PlaybackSchedule::TimeQueue:
[legend]

Classes

struct  Node
 

Public Member Functions

 TimeQueue ()
 
 TimeQueue (const TimeQueue &)=delete
 
TimeQueueoperator= (const TimeQueue &)=delete
 
void Clear ()
 
void Init (size_t size)
 
void Producer (PlaybackSchedule &schedule, PlaybackSlice slice)
 Enqueue track time value advanced by the slice according to schedule's PlaybackPolicy. More...
 
double GetLastTime () const
 Return the last time saved by Producer. More...
 
void SetLastTime (double time)
 
double Consumer (size_t nSamples, double rate)
 Find the track time value nSamples after the last consumed sample. More...
 
void Prime (double time)
 Empty the queue and reassign the last produced time. More...
 

Private Attributes

double mLastTime {}
 
NodemConsumerNode {}
 
NodemProducerNode {}
 
std::vector< std::unique_ptr< Node > > mNodePool
 

Detailed Description

Holds track time values corresponding to every nth sample in the playback buffers, for the large n == TimeQueueGrainSize.

The "producer" is the Audio thread that fetches samples from tracks and fills the playback RingBuffers. The "consumer" is the high-latency PortAudio thread that drains the RingBuffers. The atomics in the RingBuffer implement lock-free synchronization.

This other structure adds other information to the stream of samples: which track times they correspond to.

The consumer thread uses that information, and also makes known to the main thread, what the last consumed track time is. The main thread can use that for other purposes such as refreshing the display of the play head position.

Definition at line 207 of file PlaybackSchedule.h.

Constructor & Destructor Documentation

◆ TimeQueue() [1/2]

PlaybackSchedule::TimeQueue::TimeQueue ( )
default

◆ TimeQueue() [2/2]

PlaybackSchedule::TimeQueue::TimeQueue ( const TimeQueue )
delete

Member Function Documentation

◆ Clear()

void PlaybackSchedule::TimeQueue::Clear ( )

by main thread

Definition at line 270 of file PlaybackSchedule.cpp.

271{
272 mNodePool.clear();
273 mProducerNode = nullptr;
274 mConsumerNode = nullptr;
275}
std::vector< std::unique_ptr< Node > > mNodePool

References mConsumerNode, mNodePool, and mProducerNode.

Referenced by AudioIO::StartStream(), AudioIO::StartStreamCleanup(), and AudioIO::StopStream().

Here is the caller graph for this function:

◆ Consumer()

double PlaybackSchedule::TimeQueue::Consumer ( size_t  nSamples,
double  rate 
)

Find the track time value nSamples after the last consumed sample.

by main thread

Definition at line 403 of file PlaybackSchedule.cpp.

404{
405 auto node = mConsumerNode;
406
407 if ( node == nullptr ) {
408 // Recording only. No scrub or playback time warp. Don't use the queue.
409 return ( mLastTime += nSamples / rate );
410 }
411
412 auto head = node->head.load(std::memory_order_acquire);
413 auto tail = node->tail.load(std::memory_order_relaxed);
414
415 auto offset = node->offset;
416 auto available = TimeQueueGrainSize - offset;
417
418 if(nSamples >= available)
419 {
420 do
421 {
422 offset = 0;
423 nSamples -= available;
424 if ( head == tail )
425 {
426 //Check if circular buffer was reallocated
427 if(const auto next = node->next.load())
428 {
429 node->offset = 0;
430 node->active.clear();
431
432 mConsumerNode = node = next;
433 head = 0;
434 tail = node->tail.load(std::memory_order_relaxed);
435 available = TimeQueueGrainSize;
436 }
437 else
438 {
439 //consumer is ahead of producer...
440 return node->records[head].timeValue;
441 }
442 }
443 else
444 {
445 head = (head + 1) % static_cast<int>(node->records.size());
446 available = TimeQueueGrainSize;
447 }
448 } while (nSamples >= available);
449 node->head.store(head, std::memory_order_release);
450 }
451 node->offset = offset + nSamples;
452 return node->records[head].timeValue;
453}
constexpr size_t TimeQueueGrainSize

References TimeQueueGrainSize.

Referenced by AudioIoCallback::UpdateTimePosition().

Here is the caller graph for this function:

◆ GetLastTime()

double PlaybackSchedule::TimeQueue::GetLastTime ( ) const

Return the last time saved by Producer.

Definition at line 393 of file PlaybackSchedule.cpp.

394{
395 return mLastTime;
396}

Referenced by DefaultPlaybackPolicy::RepositionPlayback(), and DefaultPlaybackPolicy::RevertToOldDefault().

Here is the caller graph for this function:

◆ Init()

void PlaybackSchedule::TimeQueue::Init ( size_t  size)

Definition at line 277 of file PlaybackSchedule.cpp.

278{
279 auto node = std::make_unique<Node>();
280 mProducerNode = mConsumerNode = node.get();
281 mProducerNode->active.test_and_set();
282 mProducerNode->records.resize(size);
283 mNodePool.clear();
284 mNodePool.emplace_back( std::move(node) );
285}
std::atomic_flag active
Flag is set when used by at least consumer thread. Once node is not used by neither it's flag is clea...

References size.

Referenced by AudioIO::AllocateBuffers().

Here is the caller graph for this function:

◆ operator=()

TimeQueue & PlaybackSchedule::TimeQueue::operator= ( const TimeQueue )
delete

◆ Prime()

void PlaybackSchedule::TimeQueue::Prime ( double  time)

Empty the queue and reassign the last produced time.

by main thread

Assumes producer and consumer are suspended

Definition at line 455 of file PlaybackSchedule.cpp.

456{
457 //TODO: check that consumer and producer indeed suspended when called from AudioIoCallback
458 mLastTime = time;
459 if(mProducerNode != nullptr)
460 {
462 mConsumerNode->next.store(nullptr);
463 mConsumerNode->head.store(0);
464 mConsumerNode->tail.store(0);
467 mConsumerNode->records[0].timeValue = time;
468 }
469}
size_t offset
Number of samples advanced from the beginning of the current head. Accessed only by consumer thread.
size_t written
Number of samples counted by producer thread at the current tail. Accessed only by producer thread.
std::atomic< Node * > next
Points to a node which should be used instead of current one when it becomes exhausted by a consumer ...

Referenced by AudioIoCallback::CallbackDoSeek(), and AudioIO::StartStream().

Here is the caller graph for this function:

◆ Producer()

void PlaybackSchedule::TimeQueue::Producer ( PlaybackSchedule schedule,
PlaybackSlice  slice 
)

Enqueue track time value advanced by the slice according to schedule's PlaybackPolicy.

by the main thread

Definition at line 287 of file PlaybackSchedule.cpp.

289{
290 auto &policy = schedule.GetPolicy();
291
292 auto node = mProducerNode;
293
294 if ( node == nullptr )
295 // Recording only. Don't fill the queue.
296 return;
297
298
299 auto written = node->written;
300 auto tail = node->tail.load(std::memory_order_acquire);
301 auto head = node->head.load(std::memory_order_relaxed);
302 auto time = mLastTime;
303
304 auto frames = slice.toProduce;
305
306 auto advanceTail = [&](double time)
307 {
308 auto newTail = (tail + 1) % static_cast<int>(node->records.size());
309 if((newTail > head && static_cast<size_t>(newTail - head) == node->records.size() - 1) ||
310 (newTail < head && static_cast<size_t>(head - newTail) == node->records.size() - 1))
311 {
312 try
313 {
314 Node* next = nullptr;
315 for(auto& p : mNodePool)
316 {
317 if(p.get() == node || p->active.test_and_set())
318 continue;
319
320 next = p.get();
321 //next->offset = 0; set on consumer thread
322 next->next.store(nullptr);
323 next->head.store(0);
324 next->tail.store(0);
325 break;
326 }
327 if(next == nullptr)
328 {
329 mNodePool.emplace_back(std::make_unique<Node>());
330 next = mNodePool.back().get();
331 }
332 //previous node had too low capacity to fit all slices,
333 //try enlarge capacity to avoid more reallocaitons
334 next->records.resize(node->records.size() * 2);
335 next->records[0].timeValue = time;
336
337 node->next.store(next);//make it visible to the consumer
338 mProducerNode = node = next;
339 head = 0;
340 newTail = 0;
341 }
342 catch(...)
343 {
344 //overwrite last grain...
345 newTail = tail;
346 }
347 }
348 else
349 node->records[newTail].timeValue = time;
350 tail = newTail;
351 node->written = 0;
352 };
353
354 //inv: space > 0
355 auto space = TimeQueueGrainSize - written;
356 while ( frames >= space )
357 {
358 const auto times = policy.AdvancedTrackTime( schedule, time, space);
359 time = times.second;
360 if (!std::isfinite(time))
361 time = times.first;
362 advanceTail(time);
363 written = 0;
364 frames -= space;
365 space = TimeQueueGrainSize;
366 }
367 // Last odd lot
368 if ( frames > 0 )
369 {
370 const auto times = policy.AdvancedTrackTime( schedule, time, frames );
371 time = times.second;
372 if (!std::isfinite(time))
373 time = times.first;
374 written += frames;
375 space -= frames;
376 }
377 // Produce constant times if there is also some silence in the slice
378 frames = slice.frames - slice.toProduce;
379 while (frames > 0 && frames >= space )
380 {
381 advanceTail(time);
382
383 frames -= space;
384 written = 0;
385 space = TimeQueueGrainSize;
386 }
387
388 mLastTime = time;
389 node->written = written + frames;
390 node->tail.store(tail, std::memory_order_release);
391}
PlaybackPolicy & GetPolicy()
const size_t toProduce
Not more than frames; the difference will be trailing silence.
const size_t frames
Total number of frames to be buffered.

References PlaybackSlice::frames, PlaybackSchedule::GetPolicy(), PlaybackSchedule::TimeQueue::Node::head, PlaybackSchedule::TimeQueue::Node::next, PlaybackSchedule::TimeQueue::Node::records, PlaybackSchedule::TimeQueue::Node::tail, TimeQueueGrainSize, and PlaybackSlice::toProduce.

Referenced by AudioIO::ProcessPlaybackSlices().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ SetLastTime()

void PlaybackSchedule::TimeQueue::SetLastTime ( double  time)

Definition at line 398 of file PlaybackSchedule.cpp.

399{
400 mLastTime = time;
401}

Referenced by DefaultPlaybackPolicy::RepositionPlayback().

Here is the caller graph for this function:

Member Data Documentation

◆ mConsumerNode

Node* PlaybackSchedule::TimeQueue::mConsumerNode {}
private

Definition at line 275 of file PlaybackSchedule.h.

Referenced by Clear().

◆ mLastTime

double PlaybackSchedule::TimeQueue::mLastTime {}
private

Definition at line 242 of file PlaybackSchedule.h.

◆ mNodePool

std::vector<std::unique_ptr<Node> > PlaybackSchedule::TimeQueue::mNodePool
private

When node's buffer becomes full consumer will pick up a new one from the pool, which also will be linked to the previous node, so that producer could pick it up too.

Definition at line 281 of file PlaybackSchedule.h.

Referenced by Clear().

◆ mProducerNode

Node* PlaybackSchedule::TimeQueue::mProducerNode {}
private

Definition at line 276 of file PlaybackSchedule.h.

Referenced by Clear().


The documentation for this class was generated from the following files: