| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
- #ifdef __cplusplus
- extern "C" {
- #endif
- #include "SDL_ngageaudio.h"
- #include "../SDL_sysaudio.h"
- #include "SDL_internal.h"
- #ifdef __cplusplus
- }
- #endif
- #ifdef SDL_AUDIO_DRIVER_NGAGE
- #include "SDL_ngageaudio.hpp"
- CAudio::CAudio() : CActive(EPriorityStandard), iBufDes(NULL, 0) {}
- CAudio *CAudio::NewL(TInt aLatency)
- {
- CAudio *self = new (ELeave) CAudio();
- CleanupStack::PushL(self);
- self->ConstructL(aLatency);
- CleanupStack::Pop(self);
- return self;
- }
- void CAudio::ConstructL(TInt aLatency)
- {
- CActiveScheduler::Add(this);
- User::LeaveIfError(iTimer.CreateLocal());
- iTimerCreated = ETrue;
- iStream = CMdaAudioOutputStream::NewL(*this);
- if (!iStream) {
- SDL_Log("Error: Failed to create audio stream");
- User::Leave(KErrNoMemory);
- }
- iLatency = aLatency;
- iLatencySamples = aLatency * 8; // 8kHz.
- // Determine minimum and maximum number of samples to write with one
- // WriteL request.
- iMinWrite = iLatencySamples / 8;
- iMaxWrite = iLatencySamples / 2;
- // Set defaults.
- iState = EStateNone;
- iTimerCreated = EFalse;
- iTimerActive = EFalse;
- }
- CAudio::~CAudio()
- {
- if (iStream) {
- iStream->Stop();
- while (iState != EStateDone) {
- User::After(100000); // 100ms.
- }
- delete iStream;
- }
- }
- void CAudio::Start()
- {
- if (iStream) {
- // Set to 8kHz mono audio.
- iStreamSettings.iChannels = TMdaAudioDataSettings::EChannelsMono;
- iStreamSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate8000Hz;
- iStream->Open(&iStreamSettings);
- iState = EStateOpening;
- } else {
- SDL_Log("Error: Failed to open audio stream");
- }
- }
- // Feeds more processed data to the audio stream.
- void CAudio::Feed()
- {
- // If a WriteL is already in progress, or we aren't even playing;
- // do nothing!
- if ((iState != EStateWriting) && (iState != EStatePlaying)) {
- return;
- }
- // Figure out the number of samples that really have been played
- // through the output.
- TTimeIntervalMicroSeconds pos = iStream->Position();
- TInt played = 8 * (pos.Int64() / TInt64(1000)).GetTInt(); // 8kHz.
- played += iBaseSamplesPlayed;
- // Determine the difference between the number of samples written to
- // CMdaAudioOutputStream and the number of samples it has played.
- // The difference is the amount of data in the buffers.
- if (played < 0) {
- played = 0;
- }
- TInt buffered = iSamplesWritten - played;
- if (buffered < 0) {
- buffered = 0;
- }
- if (iState == EStateWriting) {
- return;
- }
- // The trick for low latency: Do not let the buffers fill up beyond the
- // latency desired! We write as many samples as the difference between
- // the latency target (in samples) and the amount of data buffered.
- TInt samplesToWrite = iLatencySamples - buffered;
- // Do not write very small blocks. This should improve efficiency, since
- // writes to the streaming API are likely to be expensive.
- if (samplesToWrite < iMinWrite) {
- // Not enough data to write, set up a timer to fire after a while.
- // Try againwhen it expired.
- if (iTimerActive) {
- return;
- }
- iTimerActive = ETrue;
- SetActive();
- iTimer.After(iStatus, (1000 * iLatency) / 8);
- return;
- }
- // Do not write more than the set number of samples at once.
- int numSamples = samplesToWrite;
- if (numSamples > iMaxWrite) {
- numSamples = iMaxWrite;
- }
- SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
- if (device) {
- SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
- iBufDes.Set(phdata->buffer, 2 * numSamples, 2 * numSamples);
- iStream->WriteL(iBufDes);
- iState = EStateWriting;
- // Keep track of the number of samples written (for latency calculations).
- iSamplesWritten += numSamples;
- } else {
- // Output device not ready yet. Let's go for another round.
- if (iTimerActive) {
- return;
- }
- iTimerActive = ETrue;
- SetActive();
- iTimer.After(iStatus, (1000 * iLatency) / 8);
- }
- }
- void CAudio::RunL()
- {
- iTimerActive = EFalse;
- Feed();
- }
- void CAudio::DoCancel()
- {
- iTimerActive = EFalse;
- iTimer.Cancel();
- }
- void CAudio::StartThread()
- {
- TInt heapMinSize = 8192; // 8 KB initial heap size.
- TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size.
- TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this);
- if (err == KErrNone) {
- iProcess.SetPriority(EPriorityLess);
- iProcess.Resume();
- } else {
- SDL_Log("Error: Failed to create audio processing thread: %d", err);
- }
- }
- void CAudio::StopThread()
- {
- if (iStreamStarted) {
- iProcess.Kill(KErrNone);
- iProcess.Close();
- iStreamStarted = EFalse;
- }
- }
- TInt CAudio::ProcessThreadCB(TAny *aPtr)
- {
- CAudio *self = static_cast<CAudio *>(aPtr);
- SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
- while (self->iStreamStarted) {
- if (device) {
- SDL_PlaybackAudioThreadIterate(device);
- } else {
- device = NGAGE_GetAudioDeviceAddr();
- }
- User::After(100000); // 100ms.
- }
- return KErrNone;
- }
- void CAudio::MaoscOpenComplete(TInt aError)
- {
- if (aError == KErrNone) {
- iStream->SetVolume(1);
- iStreamStarted = ETrue;
- StartThread();
- } else {
- SDL_Log("Error: Failed to open audio stream: %d", aError);
- }
- }
- void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/)
- {
- if (aError == KErrNone) {
- iState = EStatePlaying;
- Feed();
- } else if (aError == KErrAbort) {
- // The stream has been stopped.
- iState = EStateDone;
- } else {
- SDL_Log("Error: Failed to copy audio buffer: %d", aError);
- }
- }
- void CAudio::MaoscPlayComplete(TInt aError)
- {
- // If we finish due to an underflow, we'll need to restart playback.
- // Normally KErrUnderlow is raised at stream end, but in our case the API
- // should never see the stream end -- we are continuously feeding it more
- // data! Many underflow errors mean that the latency target is too low.
- if (aError == KErrUnderflow) {
- // The number of samples played gets resetted to zero when we restart
- // playback after underflow.
- iBaseSamplesPlayed = iSamplesWritten;
- iStream->Stop();
- Cancel();
- iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono);
- iState = EStatePlaying;
- Feed();
- return;
- } else if (aError != KErrNone) {
- // Handle error.
- }
- // We shouldn't get here.
- SDL_Log("%s: %d", __FUNCTION__, aError);
- }
- static TBool gAudioRunning;
- TBool AudioIsReady()
- {
- return gAudioRunning;
- }
- TInt AudioThreadCB(TAny *aParams)
- {
- CTrapCleanup *cleanup = CTrapCleanup::New();
- if (!cleanup) {
- return KErrNoMemory;
- }
- CActiveScheduler *scheduler = new CActiveScheduler();
- if (!scheduler) {
- delete cleanup;
- return KErrNoMemory;
- }
- CActiveScheduler::Install(scheduler);
- TRAPD(err,
- {
- TInt latency = *(TInt *)aParams;
- CAudio *audio = CAudio::NewL(latency);
- CleanupStack::PushL(audio);
- gAudioRunning = ETrue;
- audio->Start();
- TBool once = EFalse;
- while (gAudioRunning) {
- // Allow active scheduler to process any events.
- TInt error;
- CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
- if (!once) {
- SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
- if (device) {
- // Stream ready; start feeding audio data.
- // After feeding it once, the callbacks will take over.
- audio->iState = CAudio::EStatePlaying;
- audio->Feed();
- once = ETrue;
- }
- }
- User::After(100000); // 100ms.
- }
- CleanupStack::PopAndDestroy(audio);
- });
- delete scheduler;
- delete cleanup;
- return err;
- }
- RThread audioThread;
- void InitAudio(TInt *aLatency)
- {
- _LIT(KAudioThreadName, "AudioThread");
- TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency);
- if (err != KErrNone) {
- User::Leave(err);
- }
- audioThread.Resume();
- }
- void DeinitAudio()
- {
- gAudioRunning = EFalse;
- TRequestStatus status;
- audioThread.Logon(status);
- User::WaitForRequest(status);
- audioThread.Close();
- }
- #endif // SDL_AUDIO_DRIVER_NGAGE
|