1/**********************************************************************************************************************************
2*
3*   OpenAL cross platform audio library
4*   Copyright (c) 2004, Apple Computer, Inc. All rights reserved.
5*
6*   Redistribution and use in source and binary forms, with or without modification, are permitted provided
7*   that the following conditions are met:
8*
9*   1.  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
10*   2.  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
11*       disclaimer in the documentation and/or other materials provided with the distribution.
12*   3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of its contributors may be used to endorse or promote
13*       products derived from this software without specific prior written permission.
14*
15*   THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16*   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS
17*   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
18*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
19*   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
20*   USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21*
22**********************************************************************************************************************************/
23
24#include "oalBuffer.h"
25#include "oalSource.h"
26#include "oalImp.h"
27
28#include "CAGuard.h"
29
30#define LOG_VERBOSE         0
31
32// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33#pragma mark _____Support Methods_____
34// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35
36// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
38// OALBuffers
39// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
40// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41#pragma mark ***** OALBuffers *****
42OALBuffer::OALBuffer (ALuint	inSelfToken)
43	: 	mSelfToken (inSelfToken),
44#if USE_SOURCE_LIST_MUTEX
45		mSourceListGuard ("OALBuffer::SourceListGuard"),
46#endif
47		mBufferLock ("OALBuffer::EditLock"),
48		mInUseFlag(0),
49		mData(NULL),
50		mAppOwnsBufferMemory(false),
51		mDataSize (0),
52		mPreConvertedDataSize(0),
53		mDataHasBeenConverted(false),
54		mAttachedSourceList(NULL),
55        mIsInPostRenderMessageQueue(false)
56{
57#if LOG_VERBOSE
58	DebugMessageN1("OALBuffer::OALBuffer() - OALBuffer = %ld", (long int) mSelfToken);
59#endif
60	mAttachedSourceList = new AttachedSourceList ();			// create a source list map
61	mAttachedSourceList->Reserve(128);
62}
63
64// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
65OALBuffer::~OALBuffer()
66{
67#if LOG_VERBOSE
68	DebugMessageN1("OALBuffer::~OALBuffer() - OALBuffer = %ld", (long int) mSelfToken);
69#endif
70	if (!mAppOwnsBufferMemory && (mData != NULL))
71		free (mData);
72
73// should never be deleted unless this object said it was ok
74#if USE_SOURCE_LIST_MUTEX
75	bool wasLocked = mSourceListGuard.Lock();
76#endif
77
78	if (mAttachedSourceList)
79		delete mAttachedSourceList;
80
81#if USE_SOURCE_LIST_MUTEX
82	if (wasLocked) mSourceListGuard.Unlock();
83#endif
84}
85// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
86bool		OALBuffer::CanBeRemovedFromBufferMap()
87{
88#if LOG_VERBOSE
89	DebugMessageN1("OALBuffer::CanBeRemovedFromBufferMap() - OALBuffer = %ld", (long int) mSelfToken);
90#endif
91	bool	returnValue = true;
92
93#if USE_SOURCE_LIST_MUTEX
94	bool wasLocked = mSourceListGuard.Lock();
95#endif
96
97	// if the buffer object is not attached to a source, it's ok to remove
98	if (mAttachedSourceList->Size() == 0)
99		returnValue = true;
100	else
101	{
102		// if all attached sources are transitioning to flush, it can be removed from buffer map,
103		// but the OALBuffer object must be deleted at a later time
104		for (UInt32	i = 0; i < mAttachedSourceList->Size(); i++)
105		{
106			OALSource	*curSource = mAttachedSourceList->GetSourceByIndex(i);
107			if (curSource)
108			{
109				if (!curSource->IsSourceTransitioningToFlushQ())
110				{
111					returnValue = false;
112					goto Finished;
113				}
114			}
115		}
116	}
117
118Finished:
119
120#if USE_SOURCE_LIST_MUTEX
121	if (wasLocked) mSourceListGuard.Unlock();
122#endif
123
124	return returnValue; // all attached sources are transitioning to flush
125}
126
127// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
128bool	OALBuffer::IsPurgable()
129{
130#if LOG_VERBOSE
131	DebugMessageN1("OALBuffer::IsPurgable() - OALBuffer = %ld", (long int) mSelfToken);
132#endif
133	bool returnValue = false;
134
135#if USE_SOURCE_LIST_MUTEX
136	bool wasLocked = mSourceListGuard.Lock();
137#endif
138
139	// make sure that no other source has attached this buffer, and that no other thread is editing it
140	if ((mAttachedSourceList->Size() == 0) && mBufferLock.IsFree() && (mInUseFlag <= 0) && (!IsInPostRenderMessageQueue()))
141		returnValue = true;
142
143#if USE_SOURCE_LIST_MUTEX
144	if (wasLocked) mSourceListGuard.Unlock();
145#endif
146
147	return returnValue;
148}
149
150// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
151OSStatus	OALBuffer::AddAudioDataStatic(char*	inAudioData, UInt32	inAudioDataSize, ALenum format, ALsizei freq)
152{
153#if LOG_VERBOSE
154	DebugMessageN5("OALBuffer::AddAudioDataStatic() - OALBuffer:inAudioData:inAudioDataSize:format:freq = %ld:%p:%ld:%d:%d", (long int) mSelfToken, inAudioData, (long int) inAudioDataSize, format, freq);
155#endif
156    #if LOG_EXTRAS
157        DebugMessage("AddAudioDataStatic called: Converting Data Now");
158    #endif
159
160	CAGuard::Locker bufferLock(mBufferLock);
161
162	try {
163
164		if (!IsFormatSupported(format))
165           throw ((OSStatus) AL_INVALID_VALUE);   // this is not a valid buffer token or is an invalid format
166
167		// don't allow if the buffer is in a queue
168        if (mAttachedSourceList->Size() > 0)
169        {
170            DebugMessage("AddAudioDataStatic ATTACHMENT > 0");
171            throw ((OSStatus) AL_INVALID_OPERATION);
172        }
173
174        mPreConvertedDataSize = (UInt32) inAudioDataSize;
175        OSStatus    result = noErr;
176
177		// if this buffer was using memory created by the library, free it now and initialize mData
178		if (!mAppOwnsBufferMemory && (mData != NULL))
179		{
180			free (mData);
181			mData = NULL;
182		}
183
184		mData = (UInt8*) inAudioData;
185		mDataSize = (UInt32) inAudioDataSize;
186
187		result = FillInASBD(mDataFormat, format, freq);
188			THROW_RESULT
189
190		mPreConvertedDataFormat.SetFrom(mDataFormat); //  make sure they are the same so original format info can be returned to caller
191    }
192    catch (OSStatus     result) {
193		mData = NULL;
194		mAppOwnsBufferMemory = false;
195		DebugMessageN1("AddAudioDataStatic Failed - err = %ld\n", (long int) result);
196        alSetError(result);
197    }
198    catch (...) {
199		mData = NULL;
200		mAppOwnsBufferMemory = false;
201		DebugMessage("AddAudioDataStatic Failed");
202        alSetError(AL_INVALID_OPERATION);
203	}
204
205	mAppOwnsBufferMemory = true;
206	return noErr;
207}
208
209
210// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
211OSStatus	OALBuffer::AddAudioData(char*	inAudioData, UInt32	inAudioDataSize, ALenum format, ALsizei freq, bool	inPreConvertToHalFormat)
212{
213#if LOG_VERBOSE
214	DebugMessageN6("OALBuffer::AddAudioData() - OALBuffer:inAudioData:inAudioDataSize:format:freq:inPreConvertToHalFormat = %ld:%p:%ld:%d:%d:%d", (long int) mSelfToken, inAudioData, (long int) inAudioDataSize, format, freq, inPreConvertToHalFormat);
215#endif
216	// creates memory if needed
217	// reallocs if needed
218	// returns an error if buffer is in use
219
220	CAGuard::Locker bufferLock(mBufferLock);
221
222	try {
223        if (!IsFormatSupported(format))
224           throw ((OSStatus) AL_INVALID_VALUE);   // this is not a valid buffer token or is an invalid format
225
226#if USE_SOURCE_LIST_MUTEX
227	bool wasLocked = mSourceListGuard.Lock();
228#endif
229		// don't allow if the buffer is in a queue
230		UInt32	attachedCount = mAttachedSourceList->Size();
231
232#if USE_SOURCE_LIST_MUTEX
233		if (wasLocked) mSourceListGuard.Unlock();
234#endif
235
236        if (attachedCount > 0)
237        {
238			DebugMessage("WAITING: AddAudioData ---> WaitOneRenderCycle");
239			// Let a render cycle go by and try again
240
241			WaitOneRenderCycle();
242
243#if USE_SOURCE_LIST_MUTEX
244			wasLocked = mSourceListGuard.Lock();
245#endif
246			attachedCount = mAttachedSourceList->Size();
247
248#if USE_SOURCE_LIST_MUTEX
249			if (wasLocked) mSourceListGuard.Unlock();
250#endif
251
252			if (attachedCount > 0){
253				DebugMessageN2("OALBuffer::AddAudioData: buffer ATTACHMENT > 0 - mSelfToken:mAttachedSourceList->Size() = %ld:%ld", (long int) mSelfToken, (long int) mAttachedSourceList->Size());
254				throw ((OSStatus) AL_INVALID_OPERATION);
255			}
256        }
257
258		if (mAppOwnsBufferMemory)
259		{
260			mData = NULL;	// we were using the apps memory before so just initialize mData incase we fail
261			mAppOwnsBufferMemory = false;
262		}
263
264        mPreConvertedDataSize = (UInt32) inAudioDataSize;
265        // do not pre-convert stereo sounds, let the AC do the deinterleaving
266        OSStatus    result = noErr;
267        if (!inPreConvertToHalFormat || ((format == AL_FORMAT_STEREO16) || (format == AL_FORMAT_STEREO8)))
268        {
269            if (mData != NULL)
270            {
271                if (mDataSize != (UInt32) inAudioDataSize)
272                {
273                    mDataSize = (UInt32) inAudioDataSize;
274                    void *newDataPtr = realloc(mData, mDataSize);
275                    mData = (UInt8 *) newDataPtr;
276                }
277            }
278            else
279            {
280                mDataSize = (UInt32) inAudioDataSize;
281                mData = (UInt8 *) malloc (mDataSize);
282            }
283
284            if (mData)
285            {
286                result = FillInASBD(mDataFormat, format, freq);
287                    THROW_RESULT
288
289                mPreConvertedDataFormat.SetFrom(mDataFormat); //  make sure they are the same so original format info can be returned to caller
290                memcpy (mData, inAudioData, mDataSize);
291            }
292        }
293        else
294        {
295    #if LOG_EXTRAS
296        DebugMessage("alBufferData called: Converting Data Now");
297    #endif
298
299            result = ConvertDataForBuffer(inAudioData, inAudioDataSize, format, freq);	// convert the data to the mixer's format and copy to the buffer
300                THROW_RESULT
301        }
302    }
303    catch (OSStatus     result) {
304		DebugMessageN1("OALBuffer::AddAudioData Failed - err = %ld\n", (long int) result);
305        alSetError(result);
306		throw result;
307    }
308    catch (...) {
309		DebugMessage("OALBuffer::AddAudioData Failed");
310        alSetError(AL_INVALID_OPERATION);
311		throw -1;
312	}
313
314	return noErr;
315}
316
317// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
318// This currently will only work for cbr formats
319OSStatus OALBuffer::ConvertDataForBuffer(void *inData, UInt32 inDataSize, UInt32	inDataFormat, UInt32 inDataSampleRate)
320{
321#if LOG_VERBOSE
322	DebugMessageN5("OALBuffer::ConvertDataForBuffer() - OALBuffer:inData:inDataSize:inDataFormat:inDataSampleRate = %ld:%p:%ld:%ld:%ld", (long int) mSelfToken, inData, (long int) inDataSize, (long int) inDataFormat, (long int) inDataSampleRate);
323#endif
324	OSStatus					result = noErr;
325
326    try {
327
328        AudioConverterRef			converter;
329        CAStreamBasicDescription	destFormat;
330        UInt32						framesOfSource = 0;
331
332        if (inData == NULL)
333            throw ((OSStatus) AL_INVALID_OPERATION);
334
335        result = FillInASBD(mPreConvertedDataFormat, inDataFormat, inDataSampleRate);
336            THROW_RESULT
337
338        if (mPreConvertedDataFormat.NumberChannels() == 1)
339            mPreConvertedDataFormat.mFormatFlags |= kAudioFormatFlagIsNonInterleaved;
340
341        destFormat.mChannelsPerFrame = mPreConvertedDataFormat.NumberChannels();
342        destFormat.mSampleRate = mPreConvertedDataFormat.mSampleRate;
343        destFormat.mFormatID = kAudioFormatLinearPCM;
344
345        if (mPreConvertedDataFormat.NumberChannels() == 1)
346            destFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
347        else
348            destFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; // leave stereo data interleaved, and an AC will be used for deinterleaving later on
349
350        destFormat.mFramesPerPacket = 1;
351        destFormat.mBitsPerChannel = sizeof (Float32) * 8;
352        destFormat.mBytesPerPacket = sizeof (Float32) * destFormat.NumberChannels();
353        destFormat.mBytesPerFrame = sizeof (Float32) * destFormat.NumberChannels();
354
355        result = FillInASBD(mDataFormat, inDataFormat, UInt32(destFormat.mSampleRate));
356            THROW_RESULT
357
358        result = AudioConverterNew(&mPreConvertedDataFormat, &destFormat, &converter);
359            THROW_RESULT
360
361		framesOfSource = inDataSize / mPreConvertedDataFormat.mBytesPerFrame; // THIS ONLY WORKS FOR CBR FORMATS
362
363       	UInt32 dataSize = framesOfSource * sizeof(Float32) * destFormat.NumberChannels();
364
365		mDataSize = (UInt32) dataSize;
366
367        if (mData != NULL)
368        {
369            if (mDataSize != dataSize)
370            {
371                mDataSize = dataSize;
372                void *newDataPtr = realloc(mData, mDataSize);
373                if (newDataPtr == NULL)
374                    throw ((OSStatus) AL_INVALID_OPERATION);
375
376                mData = (UInt8 *) newDataPtr;
377            }
378        }
379        else
380        {
381            mDataSize = dataSize;
382            mData = (UInt8 *) malloc (mDataSize);
383            if (mData == NULL)
384                throw ((OSStatus) AL_INVALID_OPERATION);
385        }
386
387        if (mData != NULL)
388        {
389			result = AudioConverterConvertBuffer(converter, inDataSize, inData, &mDataSize, mData);
390			if (result == noErr)
391            {
392                mDataFormat.SetFrom(destFormat);
393				if (mPreConvertedDataFormat.NumberChannels() == 1)
394					mDataHasBeenConverted = true;
395				else
396					mDataHasBeenConverted = false;
397			}
398        }
399
400        AudioConverterDispose(converter);
401    }
402    catch (OSStatus     result) {
403        return (result);
404    }
405    catch (...) {
406        result = (OSStatus) AL_INVALID_OPERATION;
407    }
408
409	return (result);
410}
411
412// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
413// Methods called from OALSource objects
414// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
415UInt32 	OALBuffer::GetFramesToBytes(UInt32	inOffsetInFrames)
416{
417#if LOG_VERBOSE
418	DebugMessageN2("OALBuffer::GetFramesToBytes() - OALBuffer:inOffsetInFrames = %ld:%ld", (long int) mSelfToken, (long int) inOffsetInFrames);
419#endif
420	UInt32		returnValue = 0;
421
422	if (GetFramesPerPacket() == 1)
423	{
424		// pcm - it's easy
425		returnValue = inOffsetInFrames * GetBytesPerPacket();
426	}
427	else
428	{
429		// discover which packet conatins the frame we want to offset to
430		// CURRENTLY UNNECESSARY BECAUSE WE ARE ONLY STORING PCM DATA
431	}
432
433	return returnValue;
434}
435
436// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
437UInt32		OALBuffer::GetFrameCount()
438{
439#if LOG_VERBOSE
440	DebugMessageN1("OALBuffer::GetFrameCount() - OALBuffer = %ld", (long int) mSelfToken);
441#endif
442	UInt32	totalPackets =  (mDataFormat.mBytesPerPacket == 0) ? 0 : mDataSize/mDataFormat.mBytesPerPacket;
443
444	return totalPackets * mDataFormat.mFramesPerPacket;
445}
446
447// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
448bool	OALBuffer::UseThisBuffer(OALSource*	inSource)
449{
450#if LOG_VERBOSE
451	DebugMessageN1("OALBuffer::UseThisBuffer() - OALBuffer = %ld", (long int) mSelfToken);
452#endif
453#if USE_SOURCE_LIST_MUTEX
454	bool wasLocked = mSourceListGuard.Lock();
455#endif
456
457	// see if this source is in list already
458	if (mAttachedSourceList->SourceExists(inSource) == true)
459		mAttachedSourceList->IncreaseAttachmentCount(inSource);
460	else
461	{
462		SourceAttachedInfo		nuSourceInfo;
463
464		nuSourceInfo.mSource = inSource;
465		nuSourceInfo.mAttachedCount = 1;
466
467		mAttachedSourceList->Add(nuSourceInfo);
468	}
469
470#if USE_SOURCE_LIST_MUTEX
471	if (wasLocked) mSourceListGuard.Unlock();
472#endif
473
474#if LOG_EXTRAS
475	DebugMessageN2("OALBuffer::UseThisBuffer - BufferToken:SourceToken = %ld:%ld", (long int)mSelfToken, (long int)inSource->GetToken());
476#endif
477
478	return noErr;
479}
480
481// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
482// Called by OALSource's BufferQueue::RemoveQueueEntryByIndex() class
483bool	OALBuffer::ReleaseBuffer(OALSource*	inSource)
484{
485#if LOG_VERBOSE
486	DebugMessageN2("OALBuffer::ReleaseBuffer() - OALBuffer:inSource = %ld:%ld", (long int) mSelfToken, (long int) inSource->GetToken());
487#endif
488	bool returnValue = false;
489#if LOG_EXTRAS
490	DebugMessageN2("OALBuffer::ReleaseBuffer - BufferToken:SourceToken = %ld:%ld", mSelfToken, inSource->GetToken());
491#endif
492
493#if USE_SOURCE_LIST_MUTEX
494	bool wasLocked = mSourceListGuard.Lock();
495#endif
496
497	// see if this source is in list already
498	if (mAttachedSourceList->SourceExists(inSource) == true)
499	{
500		UInt32		attachmentCount = mAttachedSourceList->DecreaseAttachmentCount(inSource);
501		if (attachmentCount == 0)
502		{
503			//DebugMessageN2("OALBuffer::ReleaseBuffer - BufferToken:SourceToken = %ld:%ld", (long int) mSelfToken, (long int) inSource->GetToken());
504			mAttachedSourceList->Remove(inSource);
505			returnValue = true;
506		}
507	}
508
509#if USE_SOURCE_LIST_MUTEX
510	if (wasLocked) mSourceListGuard.Unlock();
511#endif
512
513	return returnValue;
514}