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