| | |
| | | /* |
| | | Simple DirectMedia Layer |
| | | Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org> |
| | | Copyright (C) 1997-2018 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 |
| | |
| | | /* !!! FIXME: clean out some of the macro salsa in here. */ |
| | | |
| | | #include "SDL_audio.h" |
| | | #include "SDL_hints.h" |
| | | #include "../SDL_audio_c.h" |
| | | #include "../SDL_sysaudio.h" |
| | | #include "SDL_coreaudio.h" |
| | |
| | | static void interruption_end(_THIS) |
| | | { |
| | | if (this != NULL && this->hidden != NULL && this->hidden->audioQueue != NULL |
| | | && this->hidden->interrupted) { |
| | | && this->hidden->interrupted |
| | | && AudioQueueStart(this->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) { |
| | | this->hidden->interrupted = SDL_FALSE; |
| | | AudioQueueStart(this->hidden->audioQueue, NULL); |
| | | } |
| | | } |
| | | |
| | |
| | | @autoreleasepool { |
| | | AVAudioSession *session = [AVAudioSession sharedInstance]; |
| | | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; |
| | | NSString *category; |
| | | /* Set category to ambient by default so that other music continues playing. */ |
| | | NSString *category = AVAudioSessionCategoryAmbient; |
| | | NSError *err = nil; |
| | | |
| | | if (open_playback_devices && open_capture_devices) { |
| | |
| | | } else if (open_capture_devices) { |
| | | category = AVAudioSessionCategoryRecord; |
| | | } else { |
| | | /* Set category to ambient so that other music continues playing. |
| | | You can change this at runtime in your own code if you need different |
| | | behavior. If this is common, we can add an SDL hint for this. */ |
| | | category = AVAudioSessionCategoryAmbient; |
| | | const char *hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY); |
| | | if (hint) { |
| | | if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) { |
| | | category = AVAudioSessionCategoryAmbient; |
| | | } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) { |
| | | category = AVAudioSessionCategorySoloAmbient; |
| | | } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 || |
| | | SDL_strcasecmp(hint, "playback") == 0) { |
| | | category = AVAudioSessionCategoryPlayback; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (![session setCategory:category error:&err]) { |
| | |
| | | outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) |
| | | { |
| | | SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; |
| | | if (SDL_AtomicGet(&this->hidden->shutdown)) { |
| | | return; /* don't do anything. */ |
| | | } |
| | | |
| | | if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) { |
| | | /* Supply silence if audio is enabled and not paused */ |
| | | /* Supply silence if audio is not enabled or paused */ |
| | | SDL_memset(inBuffer->mAudioData, this->spec.silence, inBuffer->mAudioDataBytesCapacity); |
| | | } else { |
| | | UInt32 remaining = inBuffer->mAudioDataBytesCapacity; |
| | |
| | | if (this->hidden->bufferOffset >= this->hidden->bufferSize) { |
| | | /* Generate the data */ |
| | | SDL_LockMutex(this->mixer_lock); |
| | | (*this->spec.callback)(this->spec.userdata, |
| | | (*this->callbackspec.callback)(this->callbackspec.userdata, |
| | | this->hidden->buffer, this->hidden->bufferSize); |
| | | SDL_UnlockMutex(this->mixer_lock); |
| | | this->hidden->bufferOffset = 0; |
| | |
| | | } |
| | | } |
| | | |
| | | if (!SDL_AtomicGet(&this->hidden->shutdown)) { |
| | | AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); |
| | | } |
| | | AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); |
| | | |
| | | inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity; |
| | | } |
| | |
| | | const AudioStreamPacketDescription *inPacketDescs ) |
| | | { |
| | | SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; |
| | | if (SDL_AtomicGet(&this->enabled) && !SDL_AtomicGet(&this->paused)) { /* ignore unless we're active. */ |
| | | |
| | | if (SDL_AtomicGet(&this->shutdown)) { |
| | | return; /* don't do anything. */ |
| | | } |
| | | |
| | | /* ignore unless we're active. */ |
| | | if (!SDL_AtomicGet(&this->paused) && SDL_AtomicGet(&this->enabled) && !SDL_AtomicGet(&this->paused)) { |
| | | const Uint8 *ptr = (const Uint8 *) inBuffer->mAudioData; |
| | | UInt32 remaining = inBuffer->mAudioDataByteSize; |
| | | while (remaining > 0) { |
| | |
| | | |
| | | if (this->hidden->bufferOffset >= this->hidden->bufferSize) { |
| | | SDL_LockMutex(this->mixer_lock); |
| | | (*this->spec.callback)(this->spec.userdata, this->hidden->buffer, this->hidden->bufferSize); |
| | | (*this->callbackspec.callback)(this->callbackspec.userdata, this->hidden->buffer, this->hidden->bufferSize); |
| | | SDL_UnlockMutex(this->mixer_lock); |
| | | this->hidden->bufferOffset = 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (!SDL_AtomicGet(&this->hidden->shutdown)) { |
| | | AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); |
| | | } |
| | | AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); |
| | | } |
| | | |
| | | |
| | |
| | | COREAUDIO_CloseDevice(_THIS) |
| | | { |
| | | const SDL_bool iscapture = this->iscapture; |
| | | int i; |
| | | |
| | | /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ |
| | | /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ |
| | |
| | | update_audio_session(this, SDL_FALSE); |
| | | #endif |
| | | |
| | | /* if callback fires again, feed silence; don't call into the app. */ |
| | | SDL_AtomicSet(&this->paused, 1); |
| | | |
| | | if (this->hidden->audioQueue) { |
| | | AudioQueueDispose(this->hidden->audioQueue, 1); |
| | | } |
| | | |
| | | if (this->hidden->thread) { |
| | | SDL_AtomicSet(&this->hidden->shutdown, 1); |
| | | SDL_WaitThread(this->hidden->thread, NULL); |
| | | } |
| | | |
| | | if (this->hidden->audioQueue) { |
| | | for (i = 0; i < SDL_arraysize(this->hidden->audioBuffer); i++) { |
| | | if (this->hidden->audioBuffer[i]) { |
| | | AudioQueueFreeBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i]); |
| | | } |
| | | } |
| | | AudioQueueDispose(this->hidden->audioQueue, 1); |
| | | } |
| | | |
| | | if (this->hidden->ready_semaphore) { |
| | | SDL_DestroySemaphore(this->hidden->ready_semaphore); |
| | | } |
| | | |
| | | /* AudioQueueDispose() frees the actual buffer objects. */ |
| | | SDL_free(this->hidden->audioBuffer); |
| | | SDL_free(this->hidden->thread_error); |
| | | SDL_free(this->hidden->buffer); |
| | | SDL_free(this->hidden); |
| | |
| | | return 0; |
| | | } |
| | | |
| | | for (i = 0; i < SDL_arraysize(this->hidden->audioBuffer); i++) { |
| | | /* Make sure we can feed the device a minimum amount of time */ |
| | | double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0; |
| | | #if defined(__IPHONEOS__) |
| | | if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) { |
| | | /* Older iOS hardware, use 40 ms as a minimum time */ |
| | | MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0; |
| | | } |
| | | #endif |
| | | const double msecs = (this->spec.samples / ((double) this->spec.freq)) * 1000.0; |
| | | int numAudioBuffers = 2; |
| | | if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { /* use more buffers if we have a VERY small sample set. */ |
| | | numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2); |
| | | } |
| | | |
| | | this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers); |
| | | if (this->hidden->audioBuffer == NULL) { |
| | | SDL_OutOfMemory(); |
| | | return 0; |
| | | } |
| | | |
| | | #if DEBUG_COREAUDIO |
| | | printf("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers); |
| | | #endif |
| | | |
| | | for (i = 0; i < numAudioBuffers; i++) { |
| | | result = AudioQueueAllocateBuffer(this->hidden->audioQueue, this->spec.size, &this->hidden->audioBuffer[i]); |
| | | CHECK_RESULT("AudioQueueAllocateBuffer"); |
| | | SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity); |
| | |
| | | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); |
| | | } |
| | | |
| | | if (this->iscapture) { /* just stop immediately for capture devices. */ |
| | | AudioQueueStop(this->hidden->audioQueue, 1); |
| | | } else { /* Drain off any pending playback. */ |
| | | AudioQueueStop(this->hidden->audioQueue, 0); |
| | | if (!this->iscapture) { /* Drain off any pending playback. */ |
| | | const CFTimeInterval secs = (((this->spec.size / (SDL_AUDIO_BITSIZE(this->spec.format) / 8)) / this->spec.channels) / ((CFTimeInterval) this->spec.freq)) * 2.0; |
| | | CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0); |
| | | } |
| | |
| | | if (!update_audio_session(this, SDL_TRUE)) { |
| | | return -1; |
| | | } |
| | | |
| | | /* Stop CoreAudio from doing expensive audio rate conversion */ |
| | | @autoreleasepool { |
| | | AVAudioSession* session = [AVAudioSession sharedInstance]; |
| | | [session setPreferredSampleRate:this->spec.freq error:nil]; |
| | | this->spec.freq = (int)session.sampleRate; |
| | | } |
| | | #endif |
| | | |
| | | /* Setup a AudioStreamBasicDescription with the requested format */ |