xref: /haiku/src/add-ons/media/media-add-ons/mixer/MixerCore.cpp (revision 9a6a20d4689307142a7ed26a1437ba47e244e73f)
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 	fOutputGain(1.0)
88 {
89 }
90 
91 
92 MixerCore::~MixerCore()
93 {
94 	delete fSettings;
95 
96 	delete fLocker;
97 	delete fInputs;
98 
99 	ASSERT(fMixThreadWaitSem == -1);
100 	ASSERT(fMixThread == -1);
101 
102 	if (fMixBuffer)
103 		rtm_free(fMixBuffer);
104 
105 	if (fTimeSource)
106 		fTimeSource->Release();
107 
108 	if (fResampler) {
109 		for (int i = 0; i < fMixBufferChannelCount; i++)
110 			delete fResampler[i];
111 		delete[] fResampler;
112 	}
113 
114 	delete[] fMixBufferChannelTypes;
115 }
116 
117 
118 MixerSettings *
119 MixerCore::Settings()
120 {
121 	return fSettings;
122 }
123 
124 
125 void
126 MixerCore::UpdateResamplingAlgorithm()
127 {
128 	ASSERT_LOCKED();
129 
130 	_UpdateResamplers(fOutput->MediaOutput().format.u.raw_audio);
131 
132 	for (int32 i = fInputs->CountItems() - 1; i >= 0; i--) {
133 		MixerInput* input
134 			= reinterpret_cast<MixerInput*>(fInputs->ItemAtFast(i));
135 		input->UpdateResamplingAlgorithm();
136 	}
137 }
138 
139 
140 void
141 MixerCore::SetOutputAttenuation(float gain)
142 {
143 	ASSERT_LOCKED();
144 	fOutputGain = gain;
145 }
146 
147 
148 MixerInput*
149 MixerCore::AddInput(const media_input& input)
150 {
151 	ASSERT_LOCKED();
152 	MixerInput* in = new MixerInput(this, input, fMixBufferFrameRate,
153 		fMixBufferFrameCount);
154 	fInputs->AddItem(in);
155 	return in;
156 }
157 
158 
159 MixerOutput*
160 MixerCore::AddOutput(const media_output& output)
161 {
162 	ASSERT_LOCKED();
163 	if (fOutput != NULL) {
164 		ERROR("MixerCore::AddOutput: already connected\n");
165 		return fOutput;
166 	}
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 == NULL)
201 		return false;
202 
203 	if (fRunning)
204 		_StopMixThread();
205 
206 	delete fOutput;
207 	fOutput = NULL;
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 
268 	if (fRunning)
269 		_StopMixThread();
270 
271 	fOutput->ChangeFormat(format);
272 	_ApplyOutputFormat();
273 
274 	if (fStarted)
275 		_StartMixThread();
276 }
277 
278 
279 void
280 MixerCore::SetOutputBufferGroup(BBufferGroup *group)
281 {
282 	ASSERT_LOCKED();
283 	fBufferGroup = group;
284 }
285 
286 
287 void
288 MixerCore::SetTimingInfo(BTimeSource *ts, bigtime_t downstream_latency)
289 {
290 	ASSERT_LOCKED();
291 	if (fTimeSource)
292 		fTimeSource->Release();
293 
294 	fTimeSource = dynamic_cast<BTimeSource *>(ts->Acquire());
295 	fDownstreamLatency = downstream_latency;
296 
297 	TRACE("MixerCore::SetTimingInfo, now = %lld, downstream latency %lld\n",
298 		fTimeSource->Now(), fDownstreamLatency);
299 }
300 
301 
302 void
303 MixerCore::EnableOutput(bool enabled)
304 {
305 	ASSERT_LOCKED();
306 	TRACE("MixerCore::EnableOutput %d\n", enabled);
307 	fOutputEnabled = enabled;
308 
309 	if (fRunning && !fOutputEnabled)
310 		_StopMixThread();
311 
312 	if (!fRunning && fOutput != NULL && fStarted && fOutputEnabled)
313 		_StartMixThread();
314 }
315 
316 
317 uint32
318 MixerCore::OutputChannelCount()
319 {
320 	return (fOutput) ? fOutput->GetOutputChannelCount() : 0;
321 }
322 
323 
324 bool
325 MixerCore::Start()
326 {
327 	ASSERT_LOCKED();
328 	TRACE("MixerCore::Start\n");
329 	if (fStarted)
330 		return false;
331 
332 	fStarted = true;
333 
334 	ASSERT(!fRunning);
335 
336 	// only start the mix thread if we have an output
337 	if (fOutput != NULL && fOutputEnabled)
338 		_StartMixThread();
339 
340 	return true;
341 }
342 
343 
344 bool
345 MixerCore::Stop()
346 {
347 	ASSERT_LOCKED();
348 	TRACE("MixerCore::Stop\n");
349 	if (!fStarted)
350 		return false;
351 
352 	if (fRunning)
353 		_StopMixThread();
354 
355 	fStarted = false;
356 	return true;
357 }
358 
359 
360 void
361 MixerCore::_StartMixThread()
362 {
363 	ASSERT(fOutputEnabled);
364 	ASSERT(!fRunning);
365 	ASSERT(fOutput != NULL);
366 
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);
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 	if (!Lock())
480 		return;
481 	{
482 		// Delay starting the mixer until we have a valid time source.
483 		bigtime_t performanceTime, realTime;
484 		float drift;
485 		while (fTimeSource->GetTime(&performanceTime, &realTime, &drift) != B_OK
486 				|| performanceTime <= 0 || realTime <= 0
487 				|| (realTime + 1 * 1000 * 1000) <= system_time()) {
488 			TRACE("MixerCore: delaying _MixThread start, timesource is at %" B_PRIdBIGTIME "\n",
489 				performanceTime);
490 			Unlock();
491 			snooze(5000);
492 			while (!LockWithTimeout(10000)) {
493 				if (!fRunning)
494 					return;
495 			}
496 		}
497 	}
498 	Unlock();
499 
500 	const bigtime_t start = fTimeSource->Now();
501 
502 	bigtime_t eventLatency = max((bigtime_t)3600, bigtime_t(0.4 * buffer_duration(
503 		fOutput->MediaOutput().format.u.raw_audio)));
504 
505 	// TODO: when the format changes while running, everything is wrong!
506 	bigtime_t bufferRequestTimeout = buffer_duration(
507 		fOutput->MediaOutput().format.u.raw_audio) / 2;
508 
509 	TRACE("MixerCore: starting _MixThread at %lld with latency %lld and "
510 		"downstream latency %lld, bufferRequestTimeout %lld\n", start,
511 		eventLatency, fDownstreamLatency, bufferRequestTimeout);
512 
513 	// We must read from the input buffer at a position (pos) that is always
514 	// a multiple of fMixBufferFrameCount.
515 	int64 temp = frames_for_duration(fMixBufferFrameRate, start);
516 	const int64 frameBase = ((temp / fMixBufferFrameCount) + 1)
517 		* fMixBufferFrameCount;
518 	const bigtime_t timeBase = duration_for_frames(fMixBufferFrameRate, frameBase);
519 
520 	TRACE("MixerCore: starting _MixThread, start %lld, timeBase %lld, "
521 		"frameBase %lld\n", start, timeBase, frameBase);
522 
523 	ASSERT(fMixBufferFrameCount > 0);
524 
525 	typedef RtList<chan_info> chan_info_list;
526 	chan_info_list inputChanInfos[MAX_CHANNEL_TYPES];
527 	BStackOrHeapArray<chan_info_list, 16> mixChanInfos(fMixBufferChannelCount);
528 		// TODO: this does not support changing output channel count
529 	if (!mixChanInfos.IsValid()) {
530 		ERROR("MixerCore::_MixThread mixChanInfos allocation failed\n");
531 		return;
532 	}
533 
534 	int64 framePos = 0;
535 	uint64 bufferIndex = 0;
536 	bigtime_t eventTime = 0, nextRun = B_INFINITE_TIMEOUT;
537 	while (fRunning) {
538 		if (nextRun == B_INFINITE_TIMEOUT) {
539 			eventTime = timeBase + bigtime_t((1000000LL * framePos)
540 				/ fMixBufferFrameRate);
541 			nextRun = fTimeSource->RealTimeFor(eventTime, 0)
542 				- eventLatency - fDownstreamLatency;
543 		}
544 
545 		status_t status = acquire_sem_etc(fMixThreadWaitSem, 1,
546 			B_ABSOLUTE_TIMEOUT, nextRun);
547 		if (status != B_TIMED_OUT) {
548 			if (status == B_OK || status == B_INTERRUPTED)
549 				continue;
550 			return;
551 		}
552 		nextRun = B_INFINITE_TIMEOUT;
553 
554 		if (!LockWithTimeout(10000)) {
555 			ERROR("MixerCore: LockWithTimeout failed\n");
556 			continue;
557 		}
558 
559 		// no inputs or output muted, skip further processing and just send an
560 		// empty buffer
561 		if (fInputs->IsEmpty() || fOutput->IsMuted()) {
562 			int size = fOutput->MediaOutput().format.u.raw_audio.buffer_size;
563 			BBuffer* buffer = fBufferGroup->RequestBuffer(size,
564 				bufferRequestTimeout);
565 			if (buffer != NULL) {
566 				int middle = 0;
567 				if (fOutput->MediaOutput().format.u.raw_audio.format
568 						== media_raw_audio_format::B_AUDIO_UCHAR)
569 					middle = 128;
570 				memset(buffer->Data(), middle, size);
571 				// fill in the buffer header
572 				media_header* hdr = buffer->Header();
573 				hdr->type = B_MEDIA_RAW_AUDIO;
574 				hdr->size_used = size;
575 				hdr->time_source = fTimeSource->ID();
576 				hdr->start_time = eventTime;
577 				if (fNode->SendBuffer(buffer, fOutput) != B_OK) {
578 					ERROR("MixerCore: SendBuffer failed for buffer %lld\n",
579 						bufferIndex);
580 					buffer->Recycle();
581 				}
582 			} else {
583 				ERROR("MixerCore: RequestBuffer failed for buffer %lld\n",
584 					bufferIndex);
585 			}
586 
587 			bufferIndex++;
588 			framePos += fMixBufferFrameCount;
589 
590 			Unlock();
591 			continue;
592 		}
593 
594 		int64 currentFramePos;
595 		currentFramePos = frameBase + framePos;
596 
597 		// mix all data from all inputs into the mix buffer
598 		ASSERT(currentFramePos % fMixBufferFrameCount == 0);
599 
600 		PRINT(4, "create new buffer event at %lld, reading input frames at "
601 			"%lld\n", eventTime, currentFramePos);
602 
603 		// Init the channel information for each MixerInput.
604 		for (int i = 0; MixerInput* input = Input(i); i++) {
605 			int count = input->GetMixerChannelCount();
606 			for (int channel = 0; channel < count; channel++) {
607 				int type;
608 				const float* base;
609 				uint32 sampleOffset;
610 				float gain;
611 				if (!input->GetMixerChannelInfo(channel, currentFramePos,
612 						eventTime, &base, &sampleOffset, &type, &gain)) {
613 					continue;
614 				}
615 				if (type < 0 || type >= MAX_CHANNEL_TYPES)
616 					continue;
617 				chan_info* info = inputChanInfos[type].Create();
618 				info->base = (const char*)base;
619 				info->sample_offset = sampleOffset;
620 				info->gain = gain;
621 			}
622 		}
623 
624 		for (int channel = 0; channel < fMixBufferChannelCount; channel++) {
625 			int sourceCount = fOutput->GetOutputChannelSourceCount(channel);
626 			for (int i = 0; i < sourceCount; i++) {
627 				int type;
628 				float gain;
629 				fOutput->GetOutputChannelSourceInfoAt(channel, i, &type,
630 					&gain);
631 				if (type < 0 || type >= MAX_CHANNEL_TYPES)
632 					continue;
633 				int count = inputChanInfos[type].CountItems();
634 				for (int j = 0; j < count; j++) {
635 					chan_info* info = inputChanInfos[type].ItemAt(j);
636 					chan_info* newInfo = mixChanInfos[channel].Create();
637 					newInfo->base = info->base;
638 					newInfo->sample_offset = info->sample_offset;
639 					newInfo->gain = info->gain * gain;
640 				}
641 			}
642 		}
643 
644 		memset(fMixBuffer, 0,
645 			fMixBufferChannelCount * fMixBufferFrameCount * sizeof(float));
646 		for (int channel = 0; channel < fMixBufferChannelCount; channel++) {
647 			PRINT(5, "_MixThread: channel %d has %d sources\n", channel,
648 				mixChanInfos[channel].CountItems());
649 
650 			int count = mixChanInfos[channel].CountItems();
651 			for (int i = 0; i < count; i++) {
652 				chan_info* info = mixChanInfos[channel].ItemAt(i);
653 				PRINT(5, "_MixThread:   base %p, sample-offset %2d, gain %.3f\n",
654 					info->base, info->sample_offset, info->gain);
655 				// This looks slightly ugly, but the current GCC will generate
656 				// the fastest code this way.
657 				// fMixBufferFrameCount is always > 0.
658 				uint32 dstSampleOffset
659 					= fMixBufferChannelCount * sizeof(float);
660 				uint32 srcSampleOffset = info->sample_offset;
661 				char* dst = (char*)&fMixBuffer[channel];
662 				char* src = (char*)info->base;
663 				float gain = info->gain;
664 				int j = fMixBufferFrameCount;
665 				do {
666 					*(float*)dst += *(const float*)src * gain;
667 					dst += dstSampleOffset;
668 					src += srcSampleOffset;
669 				 } while (--j);
670 			}
671 		}
672 
673 		// request a buffer
674 		BBuffer* buffer;
675 		buffer = fBufferGroup->RequestBuffer(
676 			fOutput->MediaOutput().format.u.raw_audio.buffer_size,
677 			bufferRequestTimeout);
678 		if (buffer != NULL) {
679 			// copy data from mix buffer into output buffer
680 			for (int i = 0; i < fMixBufferChannelCount; i++) {
681 				fResampler[i]->Resample(
682 					reinterpret_cast<char*>(fMixBuffer) + i * sizeof(float),
683 					fMixBufferChannelCount * sizeof(float),
684 					fMixBufferFrameCount,
685 					reinterpret_cast<char*>(buffer->Data())
686 						+ (i * bytes_per_sample(
687 							fOutput->MediaOutput().format.u.raw_audio)),
688 					bytes_per_frame(fOutput->MediaOutput().format.u.raw_audio),
689 					frames_per_buffer(
690 						fOutput->MediaOutput().format.u.raw_audio),
691 					fOutputGain * fOutput->GetOutputChannelGain(i));
692 			}
693 			PRINT(4, "send buffer, inframes %ld, outframes %ld\n",
694 				fMixBufferFrameCount,
695 				frames_per_buffer(fOutput->MediaOutput().format.u.raw_audio));
696 
697 			// fill in the buffer header
698 			media_header* hdr = buffer->Header();
699 			hdr->type = B_MEDIA_RAW_AUDIO;
700 			hdr->size_used
701 				= fOutput->MediaOutput().format.u.raw_audio.buffer_size;
702 			hdr->time_source = fTimeSource->ID();
703 			hdr->start_time = eventTime;
704 
705 			// swap byte order if necessary
706 			fOutput->AdjustByteOrder(buffer);
707 
708 			// send the buffer
709 			status_t res = fNode->SendBuffer(buffer, fOutput);
710 			if (res != B_OK) {
711 				ERROR("MixerCore: SendBuffer failed for buffer %lld\n",
712 					bufferIndex);
713 				buffer->Recycle();
714 			}
715 		} else {
716 			ERROR("MixerCore: RequestBuffer failed for buffer %lld\n",
717 				bufferIndex);
718 		}
719 
720 		// make all lists empty
721 		for (int i = 0; i < MAX_CHANNEL_TYPES; i++)
722 			inputChanInfos[i].MakeEmpty();
723 		for (int i = 0; i < fOutput->GetOutputChannelCount(); i++)
724 			mixChanInfos[i].MakeEmpty();
725 
726 		bufferIndex++;
727 		framePos += fMixBufferFrameCount;
728 
729 		Unlock();
730 	}
731 }
732