1 /**
2  * Copyright (c) 2006-2019 LOVE Development Team
3  *
4  * This software is provided 'as-is', without any express or implied
5  * warranty.  In no event will the authors be held liable for any damages
6  * arising from the use of this software.
7  *
8  * Permission is granted to anyone to use this software for any purpose,
9  * including commercial applications, and to alter it and redistribute it
10  * freely, subject to the following restrictions:
11  *
12  * 1. The origin of this software must not be misrepresented; you must not
13  *    claim that you wrote the original software. If you use this software
14  *    in a product, an acknowledgment in the product documentation would be
15  *    appreciated but is not required.
16  * 2. Altered source versions must be plainly marked as such, and must not be
17  *    misrepresented as being the original software.
18  * 3. This notice may not be removed or altered from any source distribution.
19  **/
20 
21 #ifdef LOVE_SUPPORT_COREAUDIO
22 
23 // LOVE
24 #include "CoreAudioDecoder.h"
25 
26 // C++
27 #include <vector>
28 
29 namespace love
30 {
31 namespace sound
32 {
33 namespace lullaby
34 {
35 
36 // Callbacks
37 namespace
38 {
readFunc(void * inClientData,SInt64 inPosition,UInt32 requestCount,void * buffer,UInt32 * actualCount)39 OSStatus readFunc(void *inClientData, SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount)
40 {
41 	Data *data = (Data *) inClientData;
42 	SInt64 bytesLeft = data->getSize() - inPosition;
43 
44 	if (bytesLeft > 0)
45 	{
46 		UInt32 actualSize = bytesLeft >= requestCount ? requestCount : (UInt32) bytesLeft;
47 		memcpy(buffer, (char *) data->getData() + inPosition, actualSize);
48 		*actualCount = actualSize;
49 	}
50 	else
51 	{
52 		*actualCount = 0;
53 		return kAudioFilePositionError;
54 	}
55 
56 	return noErr;
57 }
58 
getSizeFunc(void * inClientData)59 SInt64 getSizeFunc(void *inClientData)
60 {
61 	Data *data = (Data *) inClientData;
62 	return data->getSize();
63 }
64 } // anonymous namespace
65 
CoreAudioDecoder(Data * data,int bufferSize)66 CoreAudioDecoder::CoreAudioDecoder(Data *data, int bufferSize)
67 	: Decoder(data, bufferSize)
68 	, audioFile(nullptr)
69 	, extAudioFile(nullptr)
70 	, inputInfo()
71 	, outputInfo()
72 	, duration(-2.0)
73 {
74 	try
75 	{
76 		OSStatus err = noErr;
77 
78 		// Open the file represented by the Data.
79 		err = AudioFileOpenWithCallbacks(data, readFunc, nullptr, getSizeFunc, nullptr, kAudioFileMP3Type, &audioFile);
80 		if (err != noErr)
81 			throw love::Exception("Could open audio file for decoding.");
82 
83 		// We want to use the Extended AudioFile API.
84 		err = ExtAudioFileWrapAudioFileID(audioFile, false, &extAudioFile);
85 
86 		if (err != noErr)
87 			throw love::Exception("Could open audio file for decoding.");
88 
89 		// Get the format of the audio data.
90 		UInt32 propertySize = sizeof(inputInfo);
91 		err = ExtAudioFileGetProperty(extAudioFile, kExtAudioFileProperty_FileDataFormat, &propertySize, &inputInfo);
92 
93 		if (err != noErr)
94 			throw love::Exception("Could not determine file format.");
95 
96 		// Set the output format to 16 bit signed integer (native-endian) data.
97 		// Keep the channel count and sample rate of the source format.
98 		outputInfo.mSampleRate = inputInfo.mSampleRate;
99 		outputInfo.mChannelsPerFrame = inputInfo.mChannelsPerFrame;
100 
101 		int bytes = (inputInfo.mBitsPerChannel == 8) ? 1 : 2;
102 
103 		outputInfo.mFormatID = kAudioFormatLinearPCM;
104 		outputInfo.mBitsPerChannel = bytes * 8;
105 		outputInfo.mBytesPerFrame = bytes * outputInfo.mChannelsPerFrame;
106 		outputInfo.mFramesPerPacket = 1;
107 		outputInfo.mBytesPerPacket = bytes * outputInfo.mChannelsPerFrame;
108 		outputInfo.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
109 
110 		// unsigned 8-bit or signed 16-bit integer PCM data.
111 		if (outputInfo.mBitsPerChannel == 16)
112 			outputInfo.mFormatFlags |= kAudioFormatFlagIsSignedInteger;
113 
114 		// Set the desired output format.
115 		propertySize = sizeof(outputInfo);
116 		err = ExtAudioFileSetProperty(extAudioFile, kExtAudioFileProperty_ClientDataFormat, propertySize, &outputInfo);
117 
118 		if (err != noErr)
119 			throw love::Exception("Could not set decoder properties.");
120 	}
121 	catch (love::Exception &)
122 	{
123 		closeAudioFile();
124 		throw;
125 	}
126 
127 	sampleRate = (int) outputInfo.mSampleRate;
128 }
129 
~CoreAudioDecoder()130 CoreAudioDecoder::~CoreAudioDecoder()
131 {
132 	closeAudioFile();
133 }
134 
closeAudioFile()135 void CoreAudioDecoder::closeAudioFile()
136 {
137 	if (extAudioFile != nullptr)
138 		ExtAudioFileDispose(extAudioFile);
139 	else if (audioFile != nullptr)
140 		AudioFileClose(audioFile);
141 
142 	extAudioFile = nullptr;
143 	audioFile = nullptr;
144 }
145 
accepts(const std::string & ext)146 bool CoreAudioDecoder::accepts(const std::string &ext)
147 {
148 	UInt32 size = 0;
149 	std::vector<UInt32> types;
150 
151 	// Get the size in bytes of the type array we're about to get.
152 	OSStatus err = AudioFileGetGlobalInfoSize(kAudioFileGlobalInfo_ReadableTypes, sizeof(UInt32), nullptr, &size);
153 	if (err != noErr)
154 		return false;
155 
156 	types.resize(size / sizeof(UInt32));
157 
158 	// Get an array of supported types.
159 	err = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_ReadableTypes, 0, nullptr, &size, &types[0]);
160 	if (err != noErr)
161 		return false;
162 
163 	// Turn the extension string into a CFStringRef.
164 	CFStringRef extstr = CFStringCreateWithCString(nullptr, ext.c_str(), kCFStringEncodingUTF8);
165 
166 	CFArrayRef exts = nullptr;
167 	size = sizeof(CFArrayRef);
168 
169 	for (UInt32 type : types)
170 	{
171 		// Get the extension strings for the type.
172 		err = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_ExtensionsForType, sizeof(UInt32), &type, &size, &exts);
173 		if (err != noErr)
174 			continue;
175 
176 		// A type can have more than one extension string.
177 		for (CFIndex i = 0; i < CFArrayGetCount(exts); i++)
178 		{
179 			CFStringRef value = (CFStringRef) CFArrayGetValueAtIndex(exts, i);
180 
181 			if (CFStringCompare(extstr, value, 0) == kCFCompareEqualTo)
182 			{
183 				CFRelease(extstr);
184 				CFRelease(exts);
185 				return true;
186 			}
187 		}
188 
189 		CFRelease(exts);
190 	}
191 
192 	CFRelease(extstr);
193 	return false;
194 }
195 
clone()196 love::sound::Decoder *CoreAudioDecoder::clone()
197 {
198 	return new CoreAudioDecoder(data.get(), bufferSize);
199 }
200 
decode()201 int CoreAudioDecoder::decode()
202 {
203 	int size = 0;
204 
205 	while (size < bufferSize)
206 	{
207 		AudioBufferList dataBuffer;
208 		dataBuffer.mNumberBuffers = 1;
209 		dataBuffer.mBuffers[0].mDataByteSize = bufferSize - size;
210 		dataBuffer.mBuffers[0].mData = (char *) buffer + size;
211 		dataBuffer.mBuffers[0].mNumberChannels = outputInfo.mChannelsPerFrame;
212 
213 		UInt32 frames = (bufferSize - size) / outputInfo.mBytesPerFrame;
214 
215 		if (ExtAudioFileRead(extAudioFile, &frames, &dataBuffer) != noErr)
216 			return size;
217 
218 		if (frames == 0)
219 		{
220 			eof = true;
221 			break;
222 		}
223 
224 		size += frames * outputInfo.mBytesPerFrame;
225 	}
226 
227 	return size;
228 }
229 
seek(double s)230 bool CoreAudioDecoder::seek(double s)
231 {
232 	OSStatus err = ExtAudioFileSeek(extAudioFile, (SInt64) (s * inputInfo.mSampleRate));
233 
234 	if (err == noErr)
235 	{
236 		eof = false;
237 		return true;
238 	}
239 
240 	return false;
241 }
242 
rewind()243 bool CoreAudioDecoder::rewind()
244 {
245 	OSStatus err = ExtAudioFileSeek(extAudioFile, 0);
246 
247 	if (err == noErr)
248 	{
249 		eof = false;
250 		return true;
251 	}
252 
253 	return false;
254 }
255 
isSeekable()256 bool CoreAudioDecoder::isSeekable()
257 {
258 	return true;
259 }
260 
getChannelCount() const261 int CoreAudioDecoder::getChannelCount() const
262 {
263 	return outputInfo.mChannelsPerFrame;
264 }
265 
getBitDepth() const266 int CoreAudioDecoder::getBitDepth() const
267 {
268 	return outputInfo.mBitsPerChannel;
269 }
270 
getDuration()271 double CoreAudioDecoder::getDuration()
272 {
273 	// Only calculate the duration if we haven't done so already.
274 	if (duration == -2.0)
275 	{
276 		SInt64 samples = 0;
277 		UInt32 psize = (UInt32) sizeof(samples);
278 
279 		OSStatus err = ExtAudioFileGetProperty(extAudioFile, kExtAudioFileProperty_FileLengthFrames, &psize, &samples);
280 
281 		if (err == noErr)
282 			duration = (double) samples / (double) sampleRate;
283 		else
284 			duration = -1.0;
285 	}
286 
287 	return duration;
288 }
289 
290 } // lullaby
291 } // sound
292 } // love
293 
294 #endif // LOVE_SUPPORT_COREAUDIO
295