xref: /haiku/src/add-ons/media/media-add-ons/mixer/MixerCore.cpp (revision 8e8f7748d39f8407894a5ab79f7b4f93bc4f4652)
1 /*
2  * Copyright 2003-2010 Haiku Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Marcus Overhagen
7  */
8 
9 
10 #include "MixerCore.h"
11 
12 #include <string.h>
13 
14 #include <Buffer.h>
15 #include <BufferGroup.h>
16 #include <BufferProducer.h>
17 #include <MediaNode.h>
18 #include <RealtimeAlloc.h>
19 #include <StackOrHeapArray.h>
20 #include <StopWatch.h>
21 #include <TimeSource.h>
22 
23 #include "AudioMixer.h"
24 #include "Interpolate.h"
25 #include "MixerInput.h"
26 #include "MixerOutput.h"
27 #include "MixerUtils.h"
28 #include "Resampler.h"
29 #include "RtList.h"
30 
31 
32 #define DOUBLE_RATE_MIXING 	0
33 
34 #if DEBUG > 1
35 #	define ASSERT_LOCKED()	if (fLocker->IsLocked()) {} \
36 	else debugger("core not locked, meltdown occurred")
37 #else
38 #	define ASSERT_LOCKED()	((void)0)
39 #endif
40 
41 /*!	Mixer channels are identified by a type number, each type number corresponds
42 	to the one of the channel masks of enum media_multi_channels.
43 
44 	The mixer buffer uses either the same frame rate and same count of frames as
45 	the output buffer, or the double frame rate and frame count.
46 
47 	All mixer input ring buffers must be an exact multiple of the mixer buffer
48 	size, so that we do not get any buffer wrap around during reading from the
49 	input buffers.
50 	The mixer input is told by constructor (or after a format change by
51 	SetMixBufferFormat() of the current mixer buffer propertys, and must
52 	allocate a buffer that is an exact multiple,
53 */
54 
55 
56 struct chan_info {
57 	const char 	*base;
58 	uint32		sample_offset;
59 	float		gain;
60 };
61 
62 
63 MixerCore::MixerCore(AudioMixer *node)
64 	:
65 	fLocker(new BLocker("mixer core lock")),
66 	fInputs(new BList),
67 	fOutput(0),
68 	fNextInputID(1),
69 	fRunning(false),
70 	fStarted(false),
71 	fOutputEnabled(true),
72 	fResampler(0),
73 	fMixBuffer(0),
74 	fMixBufferFrameRate(0),
75 	fMixBufferFrameCount(0),
76 	fMixBufferChannelCount(0),
77 	fMixBufferChannelTypes(0),
78 	fDoubleRateMixing(DOUBLE_RATE_MIXING),
79 	fDownstreamLatency(1),
80 	fSettings(new MixerSettings),
81 	fNode(node),
82 	fBufferGroup(0),
83 	fTimeSource(0),
84 	fMixThread(-1),
85 	fMixThreadWaitSem(-1),
86 	fOutputGain(1.0)
87 {
88 }
89 
90 
91 MixerCore::~MixerCore()
92 {
93 	delete fSettings;
94 
95 	delete fLocker;
96 	delete fInputs;
97 
98 	ASSERT(fMixThreadWaitSem == -1);
99 	ASSERT(fMixThread == -1);
100 
101 	if (fMixBuffer)
102 		rtm_free(fMixBuffer);
103 
104 	if (fTimeSource)
105 		fTimeSource->Release();
106 
107 	if (fResampler) {
108 		for (int i = 0; i < fMixBufferChannelCount; i++)
109 			delete fResampler[i];
110 		delete[] fResampler;
111 	}
112 
113 	delete fMixBufferChannelTypes;
114 }
115 
116 
117 MixerSettings *
118 MixerCore::Settings()
119 {
120 	return fSettings;
121 }
122 
123 
124 void
125 MixerCore::UpdateResamplingAlgorithm()
126 {
127 	ASSERT_LOCKED();
128 
129 	_UpdateResamplers(fOutput->MediaOutput().format.u.raw_audio);
130 
131 	for (int32 i = fInputs->CountItems() - 1; i >= 0; i--) {
132 		MixerInput* input
133 			= reinterpret_cast<MixerInput*>(fInputs->ItemAtFast(i));
134 		input->UpdateResamplingAlgorithm();
135 	}
136 }
137 
138 
139 void
140 MixerCore::SetOutputAttenuation(float gain)
141 {
142 	ASSERT_LOCKED();
143 	fOutputGain = gain;
144 }
145 
146 
147 MixerInput*
148 MixerCore::AddInput(const media_input& input)
149 {
150 	ASSERT_LOCKED();
151 	MixerInput* in = new MixerInput(this, input, fMixBufferFrameRate,
152 		fMixBufferFrameCount);
153 	fInputs->AddItem(in);
154 	return in;
155 }
156 
157 
158 MixerOutput*
159 MixerCore::AddOutput(const media_output& output)
160 {
161 	ASSERT_LOCKED();
162 	if (fOutput) {
163 		ERROR("MixerCore::AddOutput: already connected\n");
164 		return fOutput;
165 	}
166 	fOutput = new MixerOutput(this, output);
167 	// the output format might have been adjusted inside MixerOutput
168 	_ApplyOutputFormat();
169 
170 	ASSERT(!fRunning);
171 	if (fStarted && fOutputEnabled)
172 		StartMixThread();
173 
174 	return fOutput;
175 }
176 
177 
178 bool
179 MixerCore::RemoveInput(int32 inputID)
180 {
181 	ASSERT_LOCKED();
182 	MixerInput *input;
183 	for (int i = 0; (input = Input(i)) != 0; i++) {
184 		if (input->ID() == inputID) {
185 			fInputs->RemoveItem(i);
186 			delete input;
187 			return true;
188 		}
189 	}
190 	return false;
191 }
192 
193 
194 bool
195 MixerCore::RemoveOutput()
196 {
197 	ASSERT_LOCKED();
198 	if (!fOutput)
199 		return false;
200 
201 	if (fStarted)
202 		StopMixThread();
203 
204 	delete fOutput;
205 	fOutput = 0;
206 	fOutputEnabled = true;
207 	return true;
208 }
209 
210 
211 int32
212 MixerCore::CreateInputID()
213 {
214 	ASSERT_LOCKED();
215 	return fNextInputID++;
216 }
217 
218 
219 MixerInput *
220 MixerCore::Input(int i)
221 {
222 	ASSERT_LOCKED();
223 	return (MixerInput *)fInputs->ItemAt(i);
224 }
225 
226 
227 MixerOutput *
228 MixerCore::Output()
229 {
230 	ASSERT_LOCKED();
231 	return fOutput;
232 }
233 
234 
235 void
236 MixerCore::BufferReceived(BBuffer *buffer, bigtime_t lateness)
237 {
238 	ASSERT_LOCKED();
239 	MixerInput *input;
240 	int32 id = buffer->Header()->destination;
241 	for (int i = 0; (input = Input(i)) != 0; i++) {
242 		if (input->ID() == id) {
243 			input->BufferReceived(buffer);
244 			return;
245 		}
246 	}
247 	ERROR("MixerCore::BufferReceived: received buffer for unknown id %ld\n",
248 		id);
249 }
250 
251 
252 void
253 MixerCore::InputFormatChanged(int32 inputID,
254 	const media_multi_audio_format &format)
255 {
256 	ASSERT_LOCKED();
257 	ERROR("MixerCore::InputFormatChanged not handled\n");
258 }
259 
260 
261 void
262 MixerCore::OutputFormatChanged(const media_multi_audio_format &format)
263 {
264 	ASSERT_LOCKED();
265 	bool was_started = fStarted;
266 
267 	if (was_started)
268 		Stop();
269 
270 	fOutput->ChangeFormat(format);
271 	_ApplyOutputFormat();
272 
273 	if (was_started)
274 		Start();
275 }
276 
277 
278 void
279 MixerCore::SetOutputBufferGroup(BBufferGroup *group)
280 {
281 	ASSERT_LOCKED();
282 	fBufferGroup = group;
283 }
284 
285 
286 void
287 MixerCore::SetTimingInfo(BTimeSource *ts, bigtime_t downstream_latency)
288 {
289 	ASSERT_LOCKED();
290 	if (fTimeSource)
291 		fTimeSource->Release();
292 
293 	fTimeSource = dynamic_cast<BTimeSource *>(ts->Acquire());
294 	fDownstreamLatency = downstream_latency;
295 
296 	TRACE("MixerCore::SetTimingInfo, now = %Ld, downstream latency %Ld\n",
297 		fTimeSource->Now(), fDownstreamLatency);
298 }
299 
300 
301 void
302 MixerCore::EnableOutput(bool enabled)
303 {
304 	ASSERT_LOCKED();
305 	TRACE("MixerCore::EnableOutput %d\n", enabled);
306 	fOutputEnabled = enabled;
307 
308 	if (fRunning && !fOutputEnabled)
309 		StopMixThread();
310 
311 	if (!fRunning && fOutput && fStarted && fOutputEnabled)
312 		StartMixThread();
313 }
314 
315 
316 uint32
317 MixerCore::OutputChannelCount()
318 {
319 	return (fOutput) ? fOutput->GetOutputChannelCount() : 0;
320 }
321 
322 
323 bool
324 MixerCore::Start()
325 {
326 	ASSERT_LOCKED();
327 	TRACE("MixerCore::Start\n");
328 	if (fStarted)
329 		return false;
330 
331 	fStarted = true;
332 
333 	ASSERT(!fRunning);
334 
335 	// only start the mix thread if we have an output
336 	if (fOutput && fOutputEnabled)
337 		StartMixThread();
338 
339 	return true;
340 }
341 
342 
343 bool
344 MixerCore::Stop()
345 {
346 	ASSERT_LOCKED();
347 	TRACE("MixerCore::Stop\n");
348 	if (!fStarted)
349 		return false;
350 
351 	if (fRunning)
352 		StopMixThread();
353 
354 	fStarted = false;
355 	return true;
356 }
357 
358 
359 void
360 MixerCore::StartMixThread()
361 {
362 	ASSERT(fOutputEnabled == true);
363 	ASSERT(fRunning == false);
364 	ASSERT(fOutput);
365 	fRunning = true;
366 	fMixThreadWaitSem = create_sem(0, "mix thread wait");
367 	fMixThread = spawn_thread(_MixThreadEntry, "Yeah baby, very shagadelic",
368 		120, this);
369 	resume_thread(fMixThread);
370 }
371 
372 
373 void
374 MixerCore::StopMixThread()
375 {
376 	ASSERT(fRunning == true);
377 	ASSERT(fMixThread > 0);
378 	ASSERT(fMixThreadWaitSem > 0);
379 
380 	status_t unused;
381 	delete_sem(fMixThreadWaitSem);
382 	wait_for_thread(fMixThread, &unused);
383 
384 	fMixThread = -1;
385 	fMixThreadWaitSem = -1;
386 	fRunning = false;
387 }
388 
389 
390 // #pragma mark - private
391 
392 
393 void
394 MixerCore::_UpdateResamplers(const media_multi_audio_format& format)
395 {
396 	ASSERT_LOCKED();
397 
398 	if (fResampler != NULL) {
399 		for (int i = 0; i < fMixBufferChannelCount; i++)
400 			delete fResampler[i];
401 		delete[] fResampler;
402 	}
403 
404 	fResampler = new Resampler*[fMixBufferChannelCount];
405 	for (int i = 0; i < fMixBufferChannelCount; i++) {
406 		switch (Settings()->ResamplingAlgorithm()) {
407 			case 2:
408 				fResampler[i] = new Interpolate(
409 					media_raw_audio_format::B_AUDIO_FLOAT, format.format);
410 				break;
411 			default:
412 				fResampler[i] = new Resampler(
413 					media_raw_audio_format::B_AUDIO_FLOAT, format.format);
414 		}
415 	}
416 }
417 
418 
419 void
420 MixerCore::_ApplyOutputFormat()
421 {
422 	ASSERT_LOCKED();
423 
424 	const media_multi_audio_format& format
425 		= fOutput->MediaOutput().format.u.raw_audio;
426 
427 	if (fMixBuffer != NULL)
428 		rtm_free(fMixBuffer);
429 
430 	delete fMixBufferChannelTypes;
431 
432 	fMixBufferFrameRate = (int32)(0.5 + format.frame_rate);
433 	fMixBufferFrameCount = frames_per_buffer(format);
434 	if (fDoubleRateMixing) {
435 		fMixBufferFrameRate *= 2;
436 		fMixBufferFrameCount *= 2;
437 	}
438 	fMixBufferChannelCount = format.channel_count;
439 	ASSERT(fMixBufferChannelCount == fOutput->GetOutputChannelCount());
440 	fMixBufferChannelTypes = new int32 [format.channel_count];
441 
442 	for (int i = 0; i < fMixBufferChannelCount; i++) {
443 		 fMixBufferChannelTypes[i]
444 		 	= ChannelMaskToChannelType(GetChannelMask(i, format.channel_mask));
445 	}
446 
447 	fMixBuffer = (float*)rtm_alloc(NULL, sizeof(float) * fMixBufferFrameCount
448 		* fMixBufferChannelCount);
449 	ASSERT(fMixBuffer != NULL);
450 
451 	_UpdateResamplers(format);
452 
453 	TRACE("MixerCore::OutputFormatChanged:\n");
454 	TRACE("  fMixBufferFrameRate %ld\n", fMixBufferFrameRate);
455 	TRACE("  fMixBufferFrameCount %ld\n", fMixBufferFrameCount);
456 	TRACE("  fMixBufferChannelCount %ld\n", fMixBufferChannelCount);
457 	for (int i = 0; i < fMixBufferChannelCount; i++)
458 		TRACE("  fMixBufferChannelTypes[%i] %ld\n", i, fMixBufferChannelTypes[i]);
459 
460 	MixerInput *input;
461 	for (int i = 0; (input = Input(i)); i++)
462 		input->SetMixBufferFormat(fMixBufferFrameRate, fMixBufferFrameCount);
463 }
464 
465 
466 int32
467 MixerCore::_MixThreadEntry(void* arg)
468 {
469 	static_cast<MixerCore*>(arg)->_MixThread();
470 	return 0;
471 }
472 
473 
474 void
475 MixerCore::_MixThread()
476 {
477 	// The broken BeOS R5 multiaudio node starts with time 0,
478 	// then publishes negative times for about 50ms, publishes 0
479 	// again until it finally reaches time values > 0
480 	if (!LockFromMixThread())
481 		return;
482 	bigtime_t start = fTimeSource->Now();
483 	Unlock();
484 	while (start <= 0) {
485 		TRACE("MixerCore: delaying _MixThread start, timesource is at %Ld\n",
486 			start);
487 		snooze(5000);
488 		if (!LockFromMixThread())
489 			return;
490 		start = fTimeSource->Now();
491 		Unlock();
492 	}
493 
494 	if (!LockFromMixThread())
495 		return;
496 	bigtime_t latency = max((bigtime_t)3600, bigtime_t(0.4 * buffer_duration(
497 		fOutput->MediaOutput().format.u.raw_audio)));
498 
499 	// TODO: when the format changes while running, everything is wrong!
500 	bigtime_t bufferRequestTimeout = buffer_duration(
501 		fOutput->MediaOutput().format.u.raw_audio) / 2;
502 
503 	TRACE("MixerCore: starting _MixThread at %Ld with latency %Ld and "
504 		"downstream latency %Ld, bufferRequestTimeout %Ld\n", start, latency,
505 		fDownstreamLatency, bufferRequestTimeout);
506 
507 	// We must read from the input buffer at a position (pos) that is always
508 	// a multiple of fMixBufferFrameCount.
509 	int64 temp = frames_for_duration(fMixBufferFrameRate, start);
510 	int64 frameBase = ((temp / fMixBufferFrameCount) + 1)
511 		* fMixBufferFrameCount;
512 	bigtime_t timeBase = duration_for_frames(fMixBufferFrameRate, frameBase);
513 	Unlock();
514 
515 	TRACE("MixerCore: starting _MixThread, start %Ld, timeBase %Ld, "
516 		"frameBase %Ld\n", start, timeBase, frameBase);
517 
518 	ASSERT(fMixBufferFrameCount > 0);
519 
520 #if DEBUG
521 	uint64 bufferIndex = 0;
522 #endif
523 
524 	typedef RtList<chan_info> chan_info_list;
525 	chan_info_list inputChanInfos[MAX_CHANNEL_TYPES];
526 	BStackOrHeapArray<chan_info_list, 16> mixChanInfos(fMixBufferChannelCount);
527 		// TODO: this does not support changing output channel count
528 
529 	bigtime_t eventTime = timeBase;
530 	int64 framePos = 0;
531 	for (;;) {
532 		if (!LockFromMixThread())
533 			return;
534 		bigtime_t waitUntil = fTimeSource->RealTimeFor(eventTime, 0)
535 			- latency - fDownstreamLatency;
536 		Unlock();
537 		status_t rv = acquire_sem_etc(fMixThreadWaitSem, 1, B_ABSOLUTE_TIMEOUT,
538 			waitUntil);
539 		if (rv == B_INTERRUPTED)
540 			continue;
541 		if (rv != B_TIMED_OUT && rv < B_OK)
542 			return;
543 
544 		if (!LockWithTimeout(10000)) {
545 			ERROR("MixerCore: LockWithTimeout failed\n");
546 			continue;
547 		}
548 
549 		// no inputs or output muted, skip further processing and just send an
550 		// empty buffer
551 		if (fInputs->IsEmpty() || fOutput->IsMuted()) {
552 			int size = fOutput->MediaOutput().format.u.raw_audio.buffer_size;
553 			BBuffer* buffer = fBufferGroup->RequestBuffer(size,
554 				bufferRequestTimeout);
555 			if (buffer != NULL) {
556 				memset(buffer->Data(), 0, size);
557 				// fill in the buffer header
558 				media_header* hdr = buffer->Header();
559 				hdr->type = B_MEDIA_RAW_AUDIO;
560 				hdr->size_used = size;
561 				hdr->time_source = fTimeSource->ID();
562 				hdr->start_time = eventTime;
563 				if (fNode->SendBuffer(buffer, fOutput) != B_OK) {
564 #if DEBUG
565 					ERROR("MixerCore: SendBuffer failed for buffer %Ld\n",
566 						bufferIndex);
567 #else
568 					ERROR("MixerCore: SendBuffer failed\n");
569 #endif
570 					buffer->Recycle();
571 				}
572 			} else {
573 #if DEBUG
574 				ERROR("MixerCore: RequestBuffer failed for buffer %Ld\n",
575 					bufferIndex);
576 #else
577 				ERROR("MixerCore: RequestBuffer failed\n");
578 #endif
579 			}
580 			goto schedule_next_event;
581 		}
582 
583 		int64 currentFramePos;
584 		currentFramePos = frameBase + framePos;
585 
586 		// mix all data from all inputs into the mix buffer
587 		ASSERT(currentFramePos % fMixBufferFrameCount == 0);
588 
589 		PRINT(4, "create new buffer event at %Ld, reading input frames at "
590 			"%Ld\n", eventTime, currentFramePos);
591 
592 		// Init the channel information for each MixerInput.
593 		for (int i = 0; MixerInput* input = Input(i); i++) {
594 			int count = input->GetMixerChannelCount();
595 			for (int channel = 0; channel < count; channel++) {
596 				int type;
597 				const float* base;
598 				uint32 sampleOffset;
599 				float gain;
600 				if (!input->GetMixerChannelInfo(channel, currentFramePos,
601 						eventTime, &base, &sampleOffset, &type, &gain)) {
602 					continue;
603 				}
604 				if (type < 0 || type >= MAX_CHANNEL_TYPES)
605 					continue;
606 				chan_info* info = inputChanInfos[type].Create();
607 				info->base = (const char*)base;
608 				info->sample_offset = sampleOffset;
609 				info->gain = gain;
610 			}
611 		}
612 
613 		for (int channel = 0; channel < fMixBufferChannelCount; channel++) {
614 			int sourceCount = fOutput->GetOutputChannelSourceCount(channel);
615 			for (int i = 0; i < sourceCount; i++) {
616 				int type;
617 				float gain;
618 				fOutput->GetOutputChannelSourceInfoAt(channel, i, &type,
619 					&gain);
620 				if (type < 0 || type >= MAX_CHANNEL_TYPES)
621 					continue;
622 				int count = inputChanInfos[type].CountItems();
623 				for (int j = 0; j < count; j++) {
624 					chan_info* info = inputChanInfos[type].ItemAt(j);
625 					chan_info* newInfo = mixChanInfos[channel].Create();
626 					newInfo->base = info->base;
627 					newInfo->sample_offset = info->sample_offset;
628 					newInfo->gain = info->gain * gain;
629 				}
630 			}
631 		}
632 
633 		memset(fMixBuffer, 0,
634 			fMixBufferChannelCount * fMixBufferFrameCount * sizeof(float));
635 		for (int channel = 0; channel < fMixBufferChannelCount; channel++) {
636 			PRINT(5, "_MixThread: channel %d has %d sources\n", channel,
637 				mixChanInfos[channel].CountItems());
638 
639 			int count = mixChanInfos[channel].CountItems();
640 			for (int i = 0; i < count; i++) {
641 				chan_info* info = mixChanInfos[channel].ItemAt(i);
642 				PRINT(5, "_MixThread:   base %p, sample-offset %2d, gain %.3f\n",
643 					info->base, info->sample_offset, info->gain);
644 				// This looks slightly ugly, but the current GCC will generate
645 				// the fastest code this way.
646 				// fMixBufferFrameCount is always > 0.
647 				uint32 dstSampleOffset
648 					= fMixBufferChannelCount * sizeof(float);
649 				uint32 srcSampleOffset = info->sample_offset;
650 				register char* dst = (char*)&fMixBuffer[channel];
651 				register char* src = (char*)info->base;
652 				register float gain = info->gain;
653 				register int j = fMixBufferFrameCount;
654 				do {
655 					*(float*)dst += *(const float*)src * gain;
656 					dst += dstSampleOffset;
657 					src += srcSampleOffset;
658 				 } while (--j);
659 			}
660 		}
661 
662 		// request a buffer
663 		BBuffer* buffer;
664 		buffer = fBufferGroup->RequestBuffer(
665 			fOutput->MediaOutput().format.u.raw_audio.buffer_size,
666 			bufferRequestTimeout);
667 		if (buffer != NULL) {
668 			// copy data from mix buffer into output buffer
669 			for (int i = 0; i < fMixBufferChannelCount; i++) {
670 				fResampler[i]->Resample(
671 					reinterpret_cast<char*>(fMixBuffer) + i * sizeof(float),
672 					fMixBufferChannelCount * sizeof(float),
673 					fMixBufferFrameCount,
674 					reinterpret_cast<char*>(buffer->Data())
675 						+ (i * bytes_per_sample(
676 							fOutput->MediaOutput().format.u.raw_audio)),
677 					bytes_per_frame(fOutput->MediaOutput().format.u.raw_audio),
678 					frames_per_buffer(
679 						fOutput->MediaOutput().format.u.raw_audio),
680 					fOutputGain * fOutput->GetOutputChannelGain(i));
681 			}
682 			PRINT(4, "send buffer, inframes %ld, outframes %ld\n",
683 				fMixBufferFrameCount,
684 				frames_per_buffer(fOutput->MediaOutput().format.u.raw_audio));
685 
686 			// fill in the buffer header
687 			media_header* hdr = buffer->Header();
688 			hdr->type = B_MEDIA_RAW_AUDIO;
689 			hdr->size_used
690 				= fOutput->MediaOutput().format.u.raw_audio.buffer_size;
691 			hdr->time_source = fTimeSource->ID();
692 			hdr->start_time = eventTime;
693 
694 			// swap byte order if necessary
695 			fOutput->AdjustByteOrder(buffer);
696 
697 			// send the buffer
698 			status_t res = fNode->SendBuffer(buffer, fOutput);
699 			if (res != B_OK) {
700 #if DEBUG
701 				ERROR("MixerCore: SendBuffer failed for buffer %Ld\n",
702 					bufferIndex);
703 #else
704 				ERROR("MixerCore: SendBuffer failed\n");
705 #endif
706 				buffer->Recycle();
707 			}
708 		} else {
709 #if DEBUG
710 			ERROR("MixerCore: RequestBuffer failed for buffer %Ld\n",
711 				bufferIndex);
712 #else
713 			ERROR("MixerCore: RequestBuffer failed\n");
714 #endif
715 		}
716 
717 		// make all lists empty
718 		for (int i = 0; i < MAX_CHANNEL_TYPES; i++)
719 			inputChanInfos[i].MakeEmpty();
720 		for (int i = 0; i < fOutput->GetOutputChannelCount(); i++)
721 			mixChanInfos[i].MakeEmpty();
722 
723 schedule_next_event:
724 		// schedule next event
725 		framePos += fMixBufferFrameCount;
726 		eventTime = timeBase + bigtime_t((1000000LL * framePos)
727 			/ fMixBufferFrameRate);
728 		Unlock();
729 #if DEBUG
730 		bufferIndex++;
731 #endif
732 	}
733 }
734