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