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