xref: /haiku/src/add-ons/media/media-add-ons/mixer/MixerCore.cpp (revision 688acf41a377f25d4048dc6092edb86ac9179677)
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 = %Ld, downstream latency %Ld\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 %Ld\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 %Ld with latency %Ld and "
504  		"downstream latency %Ld, bufferRequestTimeout %Ld\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 %Ld, timeBase %Ld, "
515  		"frameBase %Ld\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 %Ld\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 %Ld\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 %Ld, reading input frames at "
598  			"%Ld\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 %Ld\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 %Ld\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