SDL_ngageaudio.cpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
  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. Permission is granted to anyone to use this software for any purpose,
  8. including commercial applications, and to alter it and redistribute it
  9. freely, subject to the following restrictions:
  10. 1. The origin of this software must not be misrepresented; you must not
  11. claim that you wrote the original software. If you use this software
  12. in a product, an acknowledgment in the product documentation would be
  13. appreciated but is not required.
  14. 2. Altered source versions must be plainly marked as such, and must not be
  15. misrepresented as being the original software.
  16. 3. This notice may not be removed or altered from any source distribution.
  17. */
  18. #ifdef __cplusplus
  19. extern "C" {
  20. #endif
  21. #include "SDL_ngageaudio.h"
  22. #include "../SDL_sysaudio.h"
  23. #include "SDL_internal.h"
  24. #ifdef __cplusplus
  25. }
  26. #endif
  27. #ifdef SDL_AUDIO_DRIVER_NGAGE
  28. #include "SDL_ngageaudio.hpp"
  29. CAudio::CAudio() : CActive(EPriorityStandard), iBufDes(NULL, 0) {}
  30. CAudio *CAudio::NewL(TInt aLatency)
  31. {
  32. CAudio *self = new (ELeave) CAudio();
  33. CleanupStack::PushL(self);
  34. self->ConstructL(aLatency);
  35. CleanupStack::Pop(self);
  36. return self;
  37. }
  38. void CAudio::ConstructL(TInt aLatency)
  39. {
  40. CActiveScheduler::Add(this);
  41. User::LeaveIfError(iTimer.CreateLocal());
  42. iTimerCreated = ETrue;
  43. iStream = CMdaAudioOutputStream::NewL(*this);
  44. if (!iStream) {
  45. SDL_Log("Error: Failed to create audio stream");
  46. User::Leave(KErrNoMemory);
  47. }
  48. iLatency = aLatency;
  49. iLatencySamples = aLatency * 8; // 8kHz.
  50. // Determine minimum and maximum number of samples to write with one
  51. // WriteL request.
  52. iMinWrite = iLatencySamples / 8;
  53. iMaxWrite = iLatencySamples / 2;
  54. // Set defaults.
  55. iState = EStateNone;
  56. iTimerCreated = EFalse;
  57. iTimerActive = EFalse;
  58. }
  59. CAudio::~CAudio()
  60. {
  61. if (iStream) {
  62. iStream->Stop();
  63. while (iState != EStateDone) {
  64. User::After(100000); // 100ms.
  65. }
  66. delete iStream;
  67. }
  68. }
  69. void CAudio::Start()
  70. {
  71. if (iStream) {
  72. // Set to 8kHz mono audio.
  73. iStreamSettings.iChannels = TMdaAudioDataSettings::EChannelsMono;
  74. iStreamSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate8000Hz;
  75. iStream->Open(&iStreamSettings);
  76. iState = EStateOpening;
  77. } else {
  78. SDL_Log("Error: Failed to open audio stream");
  79. }
  80. }
  81. // Feeds more processed data to the audio stream.
  82. void CAudio::Feed()
  83. {
  84. // If a WriteL is already in progress, or we aren't even playing;
  85. // do nothing!
  86. if ((iState != EStateWriting) && (iState != EStatePlaying)) {
  87. return;
  88. }
  89. // Figure out the number of samples that really have been played
  90. // through the output.
  91. TTimeIntervalMicroSeconds pos = iStream->Position();
  92. TInt played = 8 * (pos.Int64() / TInt64(1000)).GetTInt(); // 8kHz.
  93. played += iBaseSamplesPlayed;
  94. // Determine the difference between the number of samples written to
  95. // CMdaAudioOutputStream and the number of samples it has played.
  96. // The difference is the amount of data in the buffers.
  97. if (played < 0) {
  98. played = 0;
  99. }
  100. TInt buffered = iSamplesWritten - played;
  101. if (buffered < 0) {
  102. buffered = 0;
  103. }
  104. if (iState == EStateWriting) {
  105. return;
  106. }
  107. // The trick for low latency: Do not let the buffers fill up beyond the
  108. // latency desired! We write as many samples as the difference between
  109. // the latency target (in samples) and the amount of data buffered.
  110. TInt samplesToWrite = iLatencySamples - buffered;
  111. // Do not write very small blocks. This should improve efficiency, since
  112. // writes to the streaming API are likely to be expensive.
  113. if (samplesToWrite < iMinWrite) {
  114. // Not enough data to write, set up a timer to fire after a while.
  115. // Try againwhen it expired.
  116. if (iTimerActive) {
  117. return;
  118. }
  119. iTimerActive = ETrue;
  120. SetActive();
  121. iTimer.After(iStatus, (1000 * iLatency) / 8);
  122. return;
  123. }
  124. // Do not write more than the set number of samples at once.
  125. int numSamples = samplesToWrite;
  126. if (numSamples > iMaxWrite) {
  127. numSamples = iMaxWrite;
  128. }
  129. SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
  130. if (device) {
  131. SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
  132. iBufDes.Set(phdata->buffer, 2 * numSamples, 2 * numSamples);
  133. iStream->WriteL(iBufDes);
  134. iState = EStateWriting;
  135. // Keep track of the number of samples written (for latency calculations).
  136. iSamplesWritten += numSamples;
  137. } else {
  138. // Output device not ready yet. Let's go for another round.
  139. if (iTimerActive) {
  140. return;
  141. }
  142. iTimerActive = ETrue;
  143. SetActive();
  144. iTimer.After(iStatus, (1000 * iLatency) / 8);
  145. }
  146. }
  147. void CAudio::RunL()
  148. {
  149. iTimerActive = EFalse;
  150. Feed();
  151. }
  152. void CAudio::DoCancel()
  153. {
  154. iTimerActive = EFalse;
  155. iTimer.Cancel();
  156. }
  157. void CAudio::StartThread()
  158. {
  159. TInt heapMinSize = 8192; // 8 KB initial heap size.
  160. TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size.
  161. TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this);
  162. if (err == KErrNone) {
  163. iProcess.SetPriority(EPriorityLess);
  164. iProcess.Resume();
  165. } else {
  166. SDL_Log("Error: Failed to create audio processing thread: %d", err);
  167. }
  168. }
  169. void CAudio::StopThread()
  170. {
  171. if (iStreamStarted) {
  172. iProcess.Kill(KErrNone);
  173. iProcess.Close();
  174. iStreamStarted = EFalse;
  175. }
  176. }
  177. TInt CAudio::ProcessThreadCB(TAny *aPtr)
  178. {
  179. CAudio *self = static_cast<CAudio *>(aPtr);
  180. SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
  181. while (self->iStreamStarted) {
  182. if (device) {
  183. SDL_PlaybackAudioThreadIterate(device);
  184. } else {
  185. device = NGAGE_GetAudioDeviceAddr();
  186. }
  187. User::After(100000); // 100ms.
  188. }
  189. return KErrNone;
  190. }
  191. void CAudio::MaoscOpenComplete(TInt aError)
  192. {
  193. if (aError == KErrNone) {
  194. iStream->SetVolume(1);
  195. iStreamStarted = ETrue;
  196. StartThread();
  197. } else {
  198. SDL_Log("Error: Failed to open audio stream: %d", aError);
  199. }
  200. }
  201. void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/)
  202. {
  203. if (aError == KErrNone) {
  204. iState = EStatePlaying;
  205. Feed();
  206. } else if (aError == KErrAbort) {
  207. // The stream has been stopped.
  208. iState = EStateDone;
  209. } else {
  210. SDL_Log("Error: Failed to copy audio buffer: %d", aError);
  211. }
  212. }
  213. void CAudio::MaoscPlayComplete(TInt aError)
  214. {
  215. // If we finish due to an underflow, we'll need to restart playback.
  216. // Normally KErrUnderlow is raised at stream end, but in our case the API
  217. // should never see the stream end -- we are continuously feeding it more
  218. // data! Many underflow errors mean that the latency target is too low.
  219. if (aError == KErrUnderflow) {
  220. // The number of samples played gets resetted to zero when we restart
  221. // playback after underflow.
  222. iBaseSamplesPlayed = iSamplesWritten;
  223. iStream->Stop();
  224. Cancel();
  225. iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono);
  226. iState = EStatePlaying;
  227. Feed();
  228. return;
  229. } else if (aError != KErrNone) {
  230. // Handle error.
  231. }
  232. // We shouldn't get here.
  233. SDL_Log("%s: %d", __FUNCTION__, aError);
  234. }
  235. static TBool gAudioRunning;
  236. TBool AudioIsReady()
  237. {
  238. return gAudioRunning;
  239. }
  240. TInt AudioThreadCB(TAny *aParams)
  241. {
  242. CTrapCleanup *cleanup = CTrapCleanup::New();
  243. if (!cleanup) {
  244. return KErrNoMemory;
  245. }
  246. CActiveScheduler *scheduler = new CActiveScheduler();
  247. if (!scheduler) {
  248. delete cleanup;
  249. return KErrNoMemory;
  250. }
  251. CActiveScheduler::Install(scheduler);
  252. TRAPD(err,
  253. {
  254. TInt latency = *(TInt *)aParams;
  255. CAudio *audio = CAudio::NewL(latency);
  256. CleanupStack::PushL(audio);
  257. gAudioRunning = ETrue;
  258. audio->Start();
  259. TBool once = EFalse;
  260. while (gAudioRunning) {
  261. // Allow active scheduler to process any events.
  262. TInt error;
  263. CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
  264. if (!once) {
  265. SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
  266. if (device) {
  267. // Stream ready; start feeding audio data.
  268. // After feeding it once, the callbacks will take over.
  269. audio->iState = CAudio::EStatePlaying;
  270. audio->Feed();
  271. once = ETrue;
  272. }
  273. }
  274. User::After(100000); // 100ms.
  275. }
  276. CleanupStack::PopAndDestroy(audio);
  277. });
  278. delete scheduler;
  279. delete cleanup;
  280. return err;
  281. }
  282. RThread audioThread;
  283. void InitAudio(TInt *aLatency)
  284. {
  285. _LIT(KAudioThreadName, "AudioThread");
  286. TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency);
  287. if (err != KErrNone) {
  288. User::Leave(err);
  289. }
  290. audioThread.Resume();
  291. }
  292. void DeinitAudio()
  293. {
  294. gAudioRunning = EFalse;
  295. TRequestStatus status;
  296. audioThread.Logon(status);
  297. User::WaitForRequest(status);
  298. audioThread.Close();
  299. }
  300. #endif // SDL_AUDIO_DRIVER_NGAGE