AudioMixer: Review and rework synchronization

* The event time is managed through the main node control
loop.
* Make the mix thread to activate only when needed.
* Make the locking more simple and remove a race condition,
this will make the thread to be more silent too.
This commit is contained in:
Dario Casalinuovo
2016-04-12 02:33:48 +02:00
parent ec2c5619c1
commit b0dd37b7a0
3 changed files with 82 additions and 40 deletions
@@ -212,6 +212,14 @@ status_t
AudioMixer::HandleMessage(int32 message, const void *data, size_t size) AudioMixer::HandleMessage(int32 message, const void *data, size_t size)
{ {
// since we're using a mediaeventlooper, there shouldn't be any messages // since we're using a mediaeventlooper, there shouldn't be any messages
// except the message we are using to schedule output events for the
// process thread.
if (message == MIXER_SCHEDULE_EVENT) {
RealTimeQueue()->AddEvent(*(const media_timed_event*)data);
return B_OK;
}
return B_ERROR; return B_ERROR;
} }
@@ -1131,6 +1139,10 @@ AudioMixer::HandleEvent(const media_timed_event *event, bigtime_t lateness,
break; break;
} }
case MIXER_PROCESS_EVENT:
fCore->Process();
break;
default: default:
break; break;
} }
@@ -1,9 +1,10 @@
/* /*
* Copyright 2003-2010 Haiku Inc. All rights reserved. * Copyright 2003-2016 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
* *
* Authors: * Authors:
* Marcus Overhagen * Marcus Overhagen
* Dario Casalinuovo
*/ */
@@ -83,6 +84,7 @@ MixerCore::MixerCore(AudioMixer *node)
fTimeSource(0), fTimeSource(0),
fMixThread(-1), fMixThread(-1),
fMixThreadWaitSem(-1), fMixThreadWaitSem(-1),
fHasEvent(false),
fOutputGain(1.0) fOutputGain(1.0)
{ {
} }
@@ -377,13 +379,13 @@ MixerCore::StopMixThread()
ASSERT(fMixThread > 0); ASSERT(fMixThread > 0);
ASSERT(fMixThreadWaitSem > 0); ASSERT(fMixThreadWaitSem > 0);
fRunning = false;
status_t unused; status_t unused;
delete_sem(fMixThreadWaitSem); delete_sem(fMixThreadWaitSem);
wait_for_thread(fMixThread, &unused); wait_for_thread(fMixThread, &unused);
fMixThread = -1; fMixThread = -1;
fMixThreadWaitSem = -1; fMixThreadWaitSem = -1;
fRunning = false;
} }
@@ -477,7 +479,7 @@ MixerCore::_MixThread()
// The broken BeOS R5 multiaudio node starts with time 0, // The broken BeOS R5 multiaudio node starts with time 0,
// then publishes negative times for about 50ms, publishes 0 // then publishes negative times for about 50ms, publishes 0
// again until it finally reaches time values > 0 // again until it finally reaches time values > 0
if (!LockFromMixThread()) if (!Lock())
return; return;
bigtime_t start = fTimeSource->Now(); bigtime_t start = fTimeSource->Now();
Unlock(); Unlock();
@@ -485,15 +487,13 @@ MixerCore::_MixThread()
TRACE("MixerCore: delaying _MixThread start, timesource is at %Ld\n", TRACE("MixerCore: delaying _MixThread start, timesource is at %Ld\n",
start); start);
snooze(5000); snooze(5000);
if (!LockFromMixThread()) if (!Lock())
return; return;
start = fTimeSource->Now(); start = fTimeSource->Now();
Unlock(); Unlock();
} }
if (!LockFromMixThread()) fEventLatency = max((bigtime_t)3600, bigtime_t(0.4 * buffer_duration(
return;
bigtime_t latency = max((bigtime_t)3600, bigtime_t(0.4 * buffer_duration(
fOutput->MediaOutput().format.u.raw_audio))); fOutput->MediaOutput().format.u.raw_audio)));
// TODO: when the format changes while running, everything is wrong! // TODO: when the format changes while running, everything is wrong!
@@ -510,7 +510,6 @@ MixerCore::_MixThread()
int64 frameBase = ((temp / fMixBufferFrameCount) + 1) int64 frameBase = ((temp / fMixBufferFrameCount) + 1)
* fMixBufferFrameCount; * fMixBufferFrameCount;
bigtime_t timeBase = duration_for_frames(fMixBufferFrameRate, frameBase); bigtime_t timeBase = duration_for_frames(fMixBufferFrameRate, frameBase);
Unlock();
TRACE("MixerCore: starting _MixThread, start %Ld, timeBase %Ld, " TRACE("MixerCore: starting _MixThread, start %Ld, timeBase %Ld, "
"frameBase %Ld\n", start, timeBase, frameBase); "frameBase %Ld\n", start, timeBase, frameBase);
@@ -526,21 +525,23 @@ MixerCore::_MixThread()
BStackOrHeapArray<chan_info_list, 16> mixChanInfos(fMixBufferChannelCount); BStackOrHeapArray<chan_info_list, 16> mixChanInfos(fMixBufferChannelCount);
// TODO: this does not support changing output channel count // TODO: this does not support changing output channel count
bigtime_t eventTime = timeBase; fEventTime = timeBase;
int64 framePos = 0; int64 framePos = 0;
for (;;) { status_t ret = B_ERROR;
if (!LockFromMixThread())
return; while(fRunning == true) {
bigtime_t waitUntil = fTimeSource->RealTimeFor(eventTime, 0) if (fHasEvent == false)
- latency - fDownstreamLatency; goto schedule_next_event;
Unlock();
status_t rv = acquire_sem_etc(fMixThreadWaitSem, 1, B_ABSOLUTE_TIMEOUT, ret = acquire_sem_etc(fMixThreadWaitSem, 1,
waitUntil); B_RELATIVE_TIMEOUT | B_DO_NOT_RESCHEDULE, 100000);
if (rv == B_INTERRUPTED) if (ret == B_TIMED_OUT)
continue; continue;
if (rv != B_TIMED_OUT && rv < B_OK) else if (ret != B_OK)
return; return;
fHasEvent = false;
if (!LockWithTimeout(10000)) { if (!LockWithTimeout(10000)) {
ERROR("MixerCore: LockWithTimeout failed\n"); ERROR("MixerCore: LockWithTimeout failed\n");
continue; continue;
@@ -559,7 +560,7 @@ MixerCore::_MixThread()
hdr->type = B_MEDIA_RAW_AUDIO; hdr->type = B_MEDIA_RAW_AUDIO;
hdr->size_used = size; hdr->size_used = size;
hdr->time_source = fTimeSource->ID(); hdr->time_source = fTimeSource->ID();
hdr->start_time = eventTime; hdr->start_time = fEventTime;
if (fNode->SendBuffer(buffer, fOutput) != B_OK) { if (fNode->SendBuffer(buffer, fOutput) != B_OK) {
#if DEBUG #if DEBUG
ERROR("MixerCore: SendBuffer failed for buffer %Ld\n", ERROR("MixerCore: SendBuffer failed for buffer %Ld\n",
@@ -587,7 +588,7 @@ MixerCore::_MixThread()
ASSERT(currentFramePos % fMixBufferFrameCount == 0); ASSERT(currentFramePos % fMixBufferFrameCount == 0);
PRINT(4, "create new buffer event at %Ld, reading input frames at " PRINT(4, "create new buffer event at %Ld, reading input frames at "
"%Ld\n", eventTime, currentFramePos); "%Ld\n", fEventTime, currentFramePos);
// Init the channel information for each MixerInput. // Init the channel information for each MixerInput.
for (int i = 0; MixerInput* input = Input(i); i++) { for (int i = 0; MixerInput* input = Input(i); i++) {
@@ -598,7 +599,7 @@ MixerCore::_MixThread()
uint32 sampleOffset; uint32 sampleOffset;
float gain; float gain;
if (!input->GetMixerChannelInfo(channel, currentFramePos, if (!input->GetMixerChannelInfo(channel, currentFramePos,
eventTime, &base, &sampleOffset, &type, &gain)) { fEventTime, &base, &sampleOffset, &type, &gain)) {
continue; continue;
} }
if (type < 0 || type >= MAX_CHANNEL_TYPES) if (type < 0 || type >= MAX_CHANNEL_TYPES)
@@ -689,7 +690,7 @@ MixerCore::_MixThread()
hdr->size_used hdr->size_used
= fOutput->MediaOutput().format.u.raw_audio.buffer_size; = fOutput->MediaOutput().format.u.raw_audio.buffer_size;
hdr->time_source = fTimeSource->ID(); hdr->time_source = fTimeSource->ID();
hdr->start_time = eventTime; hdr->start_time = fEventTime;
// swap byte order if necessary // swap byte order if necessary
fOutput->AdjustByteOrder(buffer); fOutput->AdjustByteOrder(buffer);
@@ -721,11 +722,23 @@ MixerCore::_MixThread()
mixChanInfos[i].MakeEmpty(); mixChanInfos[i].MakeEmpty();
schedule_next_event: schedule_next_event:
Unlock();
// schedule next event // schedule next event
framePos += fMixBufferFrameCount; framePos += fMixBufferFrameCount;
eventTime = timeBase + bigtime_t((1000000LL * framePos) fEventTime = timeBase + bigtime_t((1000000LL * framePos)
/ fMixBufferFrameRate); / fMixBufferFrameRate);
Unlock();
media_timed_event mixerEvent(PickEvent(),
MIXER_PROCESS_EVENT, 0, BTimedEventQueue::B_NO_CLEANUP);
ret = write_port(fNode->ControlPort(), MIXER_SCHEDULE_EVENT,
&mixerEvent, sizeof(mixerEvent));
if (ret != B_OK)
TRACE("MixerCore::_MixThread: can't write to owner port\n");
fHasEvent = true;
#if DEBUG #if DEBUG
bufferIndex++; bufferIndex++;
#endif #endif
@@ -30,6 +30,10 @@ class Resampler;
// but for now we redefine type 12 // but for now we redefine type 12
#define B_CHANNEL_MONO B_CHANNEL_TOP_CENTER #define B_CHANNEL_MONO B_CHANNEL_TOP_CENTER
#define MIXER_PROCESS_EVENT BTimedEventQueue::B_USER_EVENT+10
#define MIXER_SCHEDULE_EVENT BTimedEventQueue::B_USER_EVENT+11
class MixerCore { class MixerCore {
public: public:
MixerCore(AudioMixer* node); MixerCore(AudioMixer* node);
@@ -53,11 +57,14 @@ public:
MixerInput* Input(int index); MixerInput* Input(int index);
MixerOutput* Output(); MixerOutput* Output();
void Lock(); bool Lock();
bool LockWithTimeout(bigtime_t timeout); bool LockWithTimeout(bigtime_t timeout);
bool LockFromMixThread(); bool IsLocked() const;
void Unlock(); void Unlock();
void Process();
bigtime_t PickEvent();
void BufferReceived(BBuffer* buffer, void BufferReceived(BBuffer* buffer,
bigtime_t lateness); bigtime_t lateness);
@@ -114,6 +121,9 @@ private:
BTimeSource* fTimeSource; BTimeSource* fTimeSource;
thread_id fMixThread; thread_id fMixThread;
sem_id fMixThreadWaitSem; sem_id fMixThreadWaitSem;
bool fHasEvent;
bigtime_t fEventTime;
bigtime_t fEventLatency;
float fOutputGain; float fOutputGain;
friend class MixerInput; friend class MixerInput;
@@ -121,10 +131,10 @@ private:
}; };
inline void inline bool
MixerCore::Lock() MixerCore::Lock()
{ {
fLocker->Lock(); return fLocker->Lock();
} }
@@ -135,6 +145,13 @@ MixerCore::LockWithTimeout(bigtime_t timeout)
} }
inline bool
MixerCore::IsLocked() const
{
return fLocker->IsLocked();
}
inline void inline void
MixerCore::Unlock() MixerCore::Unlock()
{ {
@@ -142,18 +159,18 @@ MixerCore::Unlock()
} }
inline bool inline void
MixerCore::LockFromMixThread() MixerCore::Process()
{ {
for (;;) { release_sem(fMixThreadWaitSem);
if (LockWithTimeout(10000)) }
return true;
// XXX accessing fMixThreadWaitSem is still a race condition :(
if (acquire_sem_etc(fMixThreadWaitSem, 1, B_RELATIVE_TIMEOUT, 0) inline bigtime_t
!= B_WOULD_BLOCK) { MixerCore::PickEvent()
return false; {
} return fTimeSource->RealTimeFor(fEventTime, 0)
} - fEventLatency - fDownstreamLatency;
} }