BackgroundMusic.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. /*
  2. Copyright (C) 2009-2011 id Software LLC, a ZeniMax Media company.
  3. This program is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU General Public License
  5. as published by the Free Software Foundation; either version 2
  6. of the License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  14. */
  15. //==================================================================================================
  16. // Includes
  17. //==================================================================================================
  18. // System Includes
  19. #include <AudioToolbox/AudioToolbox.h>
  20. #include <CoreFoundation/CFURL.h>
  21. #include <OpenAL/al.h>
  22. #include <OpenAL/alc.h>
  23. #include <map>
  24. #include <vector>
  25. #include <pthread.h>
  26. #include <mach/mach.h>
  27. #include <string>
  28. extern "C" {
  29. #include "doomiphone.h"
  30. }
  31. enum {
  32. kSoundEngineErrUnitialized = 1,
  33. kSoundEngineErrInvalidID = 2,
  34. kSoundEngineErrFileNotFound = 3,
  35. kSoundEngineErrInvalidFileFormat = 4,
  36. kSoundEngineErrDeviceNotFound = 5
  37. };
  38. #define AssertNoError(inMessage, inHandler) \
  39. if(result != noErr) \
  40. { \
  41. printf("%s: %d\n", inMessage, (int)result); \
  42. goto inHandler; \
  43. }
  44. #define kNumberBuffers 3
  45. static Float32 gMasterVolumeGain = 0.5f;
  46. //==================================================================================================
  47. // Helper functions
  48. //==================================================================================================
  49. OSStatus LoadFileDataInfo(const char *inFilePath, AudioFileID &outAFID, AudioStreamBasicDescription &outFormat, UInt64 &outDataSize)
  50. {
  51. UInt32 thePropSize;
  52. CFURLRef theURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8*)inFilePath, strlen(inFilePath), false);
  53. if (theURL == NULL)
  54. return kSoundEngineErrFileNotFound;
  55. OSStatus result = AudioFileOpenURL(theURL, kAudioFileReadPermission, 0, &outAFID);
  56. CFRelease(theURL);
  57. AssertNoError("Error opening file", end);
  58. thePropSize = sizeof(outFormat);
  59. result = AudioFileGetProperty(outAFID, kAudioFilePropertyDataFormat, &thePropSize, &outFormat);
  60. AssertNoError("Error getting file format", end);
  61. thePropSize = sizeof(UInt64);
  62. result = AudioFileGetProperty(outAFID, kAudioFilePropertyAudioDataByteCount, &thePropSize, &outDataSize);
  63. AssertNoError("Error getting file data size", end);
  64. end:
  65. return result;
  66. }
  67. void CalculateBytesForTime (AudioStreamBasicDescription & inDesc, UInt32 inMaxPacketSize, Float64 inSeconds, UInt32 *outBufferSize, UInt32 *outNumPackets)
  68. {
  69. static const UInt32 maxBufferSize = 0x10000; // limit size to 64K
  70. static const UInt32 minBufferSize = 0x4000; // limit size to 16K
  71. if (inDesc.mFramesPerPacket) {
  72. Float64 numPacketsForTime = inDesc.mSampleRate / inDesc.mFramesPerPacket * inSeconds;
  73. *outBufferSize = (long unsigned int)numPacketsForTime * inMaxPacketSize;
  74. } else {
  75. // if frames per packet is zero, then the codec has no predictable packet == time
  76. // so we can't tailor this (we don't know how many Packets represent a time period
  77. // we'll just return a default buffer size
  78. *outBufferSize = maxBufferSize > inMaxPacketSize ? maxBufferSize : inMaxPacketSize;
  79. }
  80. // we're going to limit our size to our default
  81. if (*outBufferSize > maxBufferSize && *outBufferSize > inMaxPacketSize)
  82. *outBufferSize = maxBufferSize;
  83. else {
  84. // also make sure we're not too small - we don't want to go the disk for too small chunks
  85. if (*outBufferSize < minBufferSize)
  86. *outBufferSize = minBufferSize;
  87. }
  88. *outNumPackets = *outBufferSize / inMaxPacketSize;
  89. }
  90. static Boolean MatchFormatFlags(const AudioStreamBasicDescription& x, const AudioStreamBasicDescription& y)
  91. {
  92. UInt32 xFlags = x.mFormatFlags;
  93. UInt32 yFlags = y.mFormatFlags;
  94. // match wildcards
  95. if (x.mFormatID == 0 || y.mFormatID == 0 || xFlags == 0 || yFlags == 0)
  96. return true;
  97. if (x.mFormatID == kAudioFormatLinearPCM)
  98. {
  99. // knock off the all clear flag
  100. xFlags = xFlags & ~kAudioFormatFlagsAreAllClear;
  101. yFlags = yFlags & ~kAudioFormatFlagsAreAllClear;
  102. // if both kAudioFormatFlagIsPacked bits are set, then we don't care about the kAudioFormatFlagIsAlignedHigh bit.
  103. if (xFlags & yFlags & kAudioFormatFlagIsPacked) {
  104. xFlags = xFlags & ~kAudioFormatFlagIsAlignedHigh;
  105. yFlags = yFlags & ~kAudioFormatFlagIsAlignedHigh;
  106. }
  107. // if both kAudioFormatFlagIsFloat bits are set, then we don't care about the kAudioFormatFlagIsSignedInteger bit.
  108. if (xFlags & yFlags & kAudioFormatFlagIsFloat) {
  109. xFlags = xFlags & ~kAudioFormatFlagIsSignedInteger;
  110. yFlags = yFlags & ~kAudioFormatFlagIsSignedInteger;
  111. }
  112. // if the bit depth is 8 bits or less and the format is packed, we don't care about endianness
  113. if((x.mBitsPerChannel <= 8) && ((xFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked))
  114. {
  115. xFlags = xFlags & ~kAudioFormatFlagIsBigEndian;
  116. }
  117. if((y.mBitsPerChannel <= 8) && ((yFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked))
  118. {
  119. yFlags = yFlags & ~kAudioFormatFlagIsBigEndian;
  120. }
  121. // if the number of channels is 0 or 1, we don't care about non-interleavedness
  122. if (x.mChannelsPerFrame <= 1 && y.mChannelsPerFrame <= 1) {
  123. xFlags &= ~kLinearPCMFormatFlagIsNonInterleaved;
  124. yFlags &= ~kLinearPCMFormatFlagIsNonInterleaved;
  125. }
  126. }
  127. return xFlags == yFlags;
  128. }
  129. Boolean FormatIsEqual(AudioStreamBasicDescription x, AudioStreamBasicDescription y)
  130. {
  131. // the semantics for equality are:
  132. // 1) Values must match exactly
  133. // 2) wildcard's are ignored in the comparison
  134. #define MATCH(name) ((x.name) == 0 || (y.name) == 0 || (x.name) == (y.name))
  135. return
  136. ((x.mSampleRate==0.) || (y.mSampleRate==0.) || (x.mSampleRate==y.mSampleRate))
  137. && MATCH(mFormatID)
  138. && MatchFormatFlags(x, y)
  139. && MATCH(mBytesPerPacket)
  140. && MATCH(mFramesPerPacket)
  141. && MATCH(mBytesPerFrame)
  142. && MATCH(mChannelsPerFrame)
  143. && MATCH(mBitsPerChannel) ;
  144. }
  145. #pragma mark ***** BackgroundTrackMgr *****
  146. //==================================================================================================
  147. // BackgroundTrackMgr class
  148. //==================================================================================================
  149. typedef struct BG_FileInfo {
  150. std::string mFilePath;
  151. AudioFileID mAFID;
  152. AudioStreamBasicDescription mFileFormat;
  153. UInt64 mFileDataSize;
  154. //UInt64 mFileNumPackets; // this is only used if loading file to memory
  155. Boolean mLoadAtOnce;
  156. Boolean mFileDataInQueue;
  157. } BackgroundMusicFileInfo;
  158. class BackgroundTrackMgr
  159. {
  160. public:
  161. BackgroundTrackMgr();
  162. ~BackgroundTrackMgr();
  163. void Teardown();
  164. static void QueueCallback( void * inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inCompleteAQBuffer);
  165. OSStatus SetupQueue(BG_FileInfo *inFileInfo);
  166. OSStatus SetupBuffers(BG_FileInfo *inFileInfo);
  167. OSStatus LoadTrack(const char* inFilePath, Boolean inAddToQueue, Boolean inLoadAtOnce);
  168. OSStatus SetVolume(Float32 inVolume);
  169. Float32 GetVolume() const;
  170. OSStatus Start();
  171. OSStatus Stop(Boolean inStopAtEnd);
  172. AudioQueueRef mQueue;
  173. AudioQueueBufferRef mBuffers[kNumberBuffers];
  174. UInt32 mBufferByteSize;
  175. SInt64 mCurrentPacket;
  176. UInt32 mNumPacketsToRead;
  177. Float32 mVolume;
  178. AudioStreamPacketDescription * mPacketDescs;
  179. static BG_FileInfo * CurFileInfo;
  180. Boolean mStopAtEnd;
  181. };
  182. BG_FileInfo *BackgroundTrackMgr::CurFileInfo;
  183. BackgroundTrackMgr::BackgroundTrackMgr()
  184. : mQueue(0),
  185. mBufferByteSize(0),
  186. mCurrentPacket(0),
  187. mNumPacketsToRead(0),
  188. mVolume(1.0f),
  189. mPacketDescs(NULL),
  190. mStopAtEnd(false)
  191. { }
  192. BackgroundTrackMgr::~BackgroundTrackMgr() {
  193. Teardown();
  194. }
  195. void BackgroundTrackMgr::Teardown() {
  196. if (mQueue) {
  197. AudioQueueDispose(mQueue, true);
  198. mQueue = NULL;
  199. }
  200. if ( CurFileInfo ) {
  201. AudioFileClose( CurFileInfo->mAFID);
  202. delete CurFileInfo;
  203. CurFileInfo = NULL;
  204. }
  205. if (mPacketDescs) {
  206. delete[] mPacketDescs;
  207. mPacketDescs = NULL;
  208. }
  209. }
  210. void BackgroundTrackMgr::QueueCallback( void * inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inCompleteAQBuffer ) {
  211. // dispose of the buffer if no longer in use
  212. OSStatus result = noErr;
  213. BackgroundTrackMgr *THIS = (BackgroundTrackMgr*)inUserData;
  214. UInt32 nPackets = 0;
  215. // loop the current buffer if the following:
  216. // 1. file was loaded into the buffer previously
  217. // 2. only one file in the queue
  218. // 3. we have not been told to stop at playlist completion
  219. if ((CurFileInfo->mFileDataInQueue) && (!THIS->mStopAtEnd)) {
  220. nPackets = THIS->mNumPacketsToRead;
  221. } else {
  222. UInt32 numBytes;
  223. while (nPackets == 0) {
  224. // if loadAtOnce, get all packets in the file, otherwise ~.5 seconds of data
  225. nPackets = THIS->mNumPacketsToRead;
  226. result = AudioFileReadPackets(CurFileInfo->mAFID, false, &numBytes, THIS->mPacketDescs, THIS->mCurrentPacket, &nPackets,
  227. inCompleteAQBuffer->mAudioData);
  228. AssertNoError("Error reading file data", end);
  229. inCompleteAQBuffer->mAudioDataByteSize = numBytes;
  230. if (nPackets == 0) { // no packets were read, this file has ended.
  231. if (CurFileInfo->mLoadAtOnce) {
  232. CurFileInfo->mFileDataInQueue = true;
  233. }
  234. THIS->mCurrentPacket = 0;
  235. // we have gone through the playlist. if mStopAtEnd, stop the queue here
  236. if ( THIS->mStopAtEnd ) {
  237. result = AudioQueueStop(inAQ, false);
  238. AssertNoError("Error stopping queue", end);
  239. return;
  240. }
  241. }
  242. }
  243. }
  244. result = AudioQueueEnqueueBuffer(inAQ, inCompleteAQBuffer, (THIS->mPacketDescs ? nPackets : 0), THIS->mPacketDescs);
  245. if(result != noErr) {
  246. result = AudioQueueFreeBuffer(inAQ, inCompleteAQBuffer);
  247. AssertNoError("Error freeing buffers that didn't enqueue", end);
  248. }
  249. AssertNoError("Error enqueuing new buffer", end);
  250. if (CurFileInfo->mLoadAtOnce) {
  251. CurFileInfo->mFileDataInQueue = true;
  252. }
  253. THIS->mCurrentPacket += nPackets;
  254. end:
  255. return;
  256. }
  257. OSStatus BackgroundTrackMgr::SetupQueue(BG_FileInfo *inFileInfo) {
  258. UInt32 size = 0;
  259. OSStatus err;
  260. OSStatus result = AudioQueueNewOutput(&inFileInfo->mFileFormat, QueueCallback, this,
  261. CFRunLoopGetMain() /* CFRunLoopGetCurrent() */, kCFRunLoopCommonModes, 0, &mQueue);
  262. AssertNoError("Error creating queue", end);
  263. #if 0
  264. // (2) If the file has a cookie, we should get it and set it on the AQ
  265. size = sizeof(UInt32);
  266. result = AudioFileGetPropertyInfo (inFileInfo->mAFID, kAudioFilePropertyMagicCookieData, &size, NULL);
  267. if (!result && size) {
  268. char* cookie = new char [size];
  269. result = AudioFileGetProperty (inFileInfo->mAFID, kAudioFilePropertyMagicCookieData, &size, cookie);
  270. AssertNoError("Error getting magic cookie", end);
  271. result = AudioQueueSetProperty(mQueue, kAudioQueueProperty_MagicCookie, cookie, size);
  272. delete [] cookie;
  273. AssertNoError("Error setting magic cookie", end);
  274. }
  275. #endif
  276. // channel layout
  277. err = AudioFileGetPropertyInfo(inFileInfo->mAFID, kAudioFilePropertyChannelLayout, &size, NULL);
  278. if (err == noErr && size > 0) {
  279. AudioChannelLayout *acl = (AudioChannelLayout *)malloc(size);
  280. result = AudioFileGetProperty(inFileInfo->mAFID, kAudioFilePropertyChannelLayout, &size, acl);
  281. AssertNoError("Error getting channel layout from file", end);
  282. result = AudioQueueSetProperty(mQueue, kAudioQueueProperty_ChannelLayout, acl, size);
  283. free(acl);
  284. AssertNoError("Error setting channel layout on queue", end);
  285. }
  286. // volume
  287. result = SetVolume(mVolume);
  288. end:
  289. return result;
  290. }
  291. OSStatus BackgroundTrackMgr::SetupBuffers(BG_FileInfo *inFileInfo) {
  292. int numBuffersToQueue = kNumberBuffers;
  293. UInt32 maxPacketSize;
  294. UInt32 size = sizeof(maxPacketSize);
  295. bool isFormatVBR;
  296. // we need to calculate how many packets we read at a time, and how big a buffer we need
  297. // we base this on the size of the packets in the file and an approximate duration for each buffer
  298. // first check to see what the max size of a packet is - if it is bigger
  299. // than our allocation default size, that needs to become larger
  300. OSStatus result = AudioFileGetProperty(inFileInfo->mAFID, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize);
  301. AssertNoError("Error getting packet upper bound size", end);
  302. isFormatVBR = (inFileInfo->mFileFormat.mBytesPerPacket == 0 || inFileInfo->mFileFormat.mFramesPerPacket == 0);
  303. CalculateBytesForTime(inFileInfo->mFileFormat, maxPacketSize, 0.5/*seconds*/, &mBufferByteSize, &mNumPacketsToRead);
  304. // if the file is smaller than the capacity of all the buffer queues, always load it at once
  305. if ((mBufferByteSize * numBuffersToQueue) > inFileInfo->mFileDataSize) {
  306. inFileInfo->mLoadAtOnce = true;
  307. }
  308. if (inFileInfo->mLoadAtOnce) {
  309. UInt64 theFileNumPackets;
  310. size = sizeof(UInt64);
  311. result = AudioFileGetProperty(inFileInfo->mAFID, kAudioFilePropertyAudioDataPacketCount, &size, &theFileNumPackets);
  312. AssertNoError("Error getting packet count for file", end);
  313. mNumPacketsToRead = (UInt32)theFileNumPackets;
  314. mBufferByteSize = (UInt32)inFileInfo->mFileDataSize;
  315. numBuffersToQueue = 1;
  316. } else {
  317. mNumPacketsToRead = mBufferByteSize / maxPacketSize;
  318. }
  319. if (isFormatVBR) {
  320. mPacketDescs = new AudioStreamPacketDescription [mNumPacketsToRead];
  321. } else {
  322. mPacketDescs = NULL; // we don't provide packet descriptions for constant bit rate formats (like linear PCM)
  323. }
  324. // allocate the queue's buffers
  325. for (int i = 0; i < numBuffersToQueue; ++i) {
  326. result = AudioQueueAllocateBuffer(mQueue, mBufferByteSize, &mBuffers[i]);
  327. AssertNoError("Error allocating buffer for queue", end);
  328. QueueCallback (this, mQueue, mBuffers[i]);
  329. if (inFileInfo->mLoadAtOnce) {
  330. inFileInfo->mFileDataInQueue = true;
  331. }
  332. }
  333. end:
  334. return result;
  335. }
  336. OSStatus BackgroundTrackMgr::LoadTrack(const char* inFilePath, Boolean inAddToQueue, Boolean inLoadAtOnce) {
  337. // OSStatus result = LoadFileDataInfo(CurFileInfo->mFilePath.c_str(), CurFileInfo->mAFID, CurFileInfo->mFileFormat, CurFileInfo->mFileDataSize);
  338. // AssertNoError("Error getting file data info", fail);
  339. OSStatus result;
  340. UInt32 thePropSize;
  341. CFURLRef theURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8*)inFilePath, strlen(inFilePath), false);
  342. if (theURL == NULL)
  343. result = kSoundEngineErrFileNotFound;
  344. else
  345. result = 0;
  346. AssertNoError("Error opening URL", fail);
  347. CurFileInfo = new BG_FileInfo;
  348. CurFileInfo->mFilePath = inFilePath;
  349. result = AudioFileOpenURL(theURL, kAudioFileReadPermission, 0, &CurFileInfo->mAFID);
  350. CFRelease(theURL);
  351. AssertNoError("Error opening file", fail);
  352. thePropSize = sizeof(CurFileInfo->mFileFormat);
  353. result = AudioFileGetProperty(CurFileInfo->mAFID, kAudioFilePropertyDataFormat, &thePropSize, &CurFileInfo->mFileFormat);
  354. AssertNoError("Error getting file format", fail);
  355. thePropSize = sizeof(UInt64);
  356. result = AudioFileGetProperty(CurFileInfo->mAFID, kAudioFilePropertyAudioDataByteCount, &thePropSize, &CurFileInfo->mFileDataSize);
  357. AssertNoError("Error getting file data size", fail);
  358. CurFileInfo->mLoadAtOnce = inLoadAtOnce;
  359. CurFileInfo->mFileDataInQueue = false;
  360. result = SetupQueue(CurFileInfo);
  361. AssertNoError("Error setting up queue", fail);
  362. result = SetupBuffers(CurFileInfo);
  363. AssertNoError("Error setting up queue buffers", fail);
  364. return result;
  365. fail:
  366. if (CurFileInfo) {
  367. delete CurFileInfo;
  368. CurFileInfo = NULL;
  369. }
  370. return result;
  371. }
  372. OSStatus BackgroundTrackMgr::SetVolume(Float32 inVolume) {
  373. mVolume = inVolume;
  374. return AudioQueueSetParameter(mQueue, kAudioQueueParam_Volume, mVolume * gMasterVolumeGain);
  375. }
  376. Float32 BackgroundTrackMgr::GetVolume() const {
  377. return mVolume;
  378. }
  379. OSStatus BackgroundTrackMgr::Start() {
  380. OSStatus result = AudioQueuePrime(mQueue, 1, NULL);
  381. if (result) {
  382. printf("BackgroundTrackMgr: Error priming queue: %d\n", (int)result);
  383. return result;
  384. }
  385. return AudioQueueStart(mQueue, NULL);
  386. }
  387. OSStatus BackgroundTrackMgr::Stop(Boolean inStopAtEnd) {
  388. if (inStopAtEnd) {
  389. mStopAtEnd = true;
  390. return noErr;
  391. } else {
  392. return AudioQueueStop(mQueue, true);
  393. }
  394. }
  395. static BackgroundTrackMgr sBackgroundTrackMgr;
  396. static char currentMusicName[1024];
  397. void iphonePauseMusic() {
  398. if( music ) {
  399. if ( music->value == 0 ) {
  400. // music is disabled
  401. return;
  402. }
  403. AudioQueuePause(sBackgroundTrackMgr.mQueue);
  404. }
  405. }
  406. void iphoneResumeMusic() {
  407. if ( music->value == 0 ) {
  408. // music is disabled
  409. return;
  410. }
  411. AudioQueueStart(sBackgroundTrackMgr.mQueue,NULL);
  412. }
  413. void iphoneStopMusic() {
  414. sBackgroundTrackMgr.Teardown();
  415. }
  416. void iphoneStartMusic() {
  417. if ( music->value == 0 ) {
  418. // music is disabled
  419. return;
  420. }
  421. char fullName[1024];
  422. sprintf( fullName, "%s/base/music/d_%s.mp3", SysIphoneGetAppDir(), currentMusicName );
  423. printf( "Starting music '%s'\n", fullName );
  424. iphoneStopMusic();
  425. sBackgroundTrackMgr.LoadTrack( fullName, false, true);
  426. sBackgroundTrackMgr.Start();
  427. if ( !strcmp( currentMusicName, "intro" ) ) {
  428. // stop the intro music at end, don't loop
  429. sBackgroundTrackMgr.mStopAtEnd = true;
  430. } else {
  431. sBackgroundTrackMgr.mStopAtEnd = false;
  432. }
  433. }
  434. void iphonePlayMusic( const char *name ) {
  435. strcpy( currentMusicName, name );
  436. iphoneStartMusic();
  437. }