hostile-takeover/game/iphone/iphonesounddev.mm
Scott Ludwig f2d7002bc3 Restore game audio after OS events (dictation or siri) use audio
- Dictation reliable raises the handleAudioInterrupt notification, so that
  is used for disabling / enabling audio
- Siri sometimes doesn't raise the handleAudioInterrupt notification, so
  rely on applicationDidBecomeActive / applicationWillResignActive.
- Trial and error showed it is best to destroy and re-create the OS audio
  objects when audio is disabled / enabled.
2016-01-21 21:45:51 -08:00

265 lines
6.3 KiB
Plaintext

#import <AVFoundation/AVFoundation.h>
#include <AudioToolbox/AudioQueue.h>
#include <AudioToolbox/AudioServices.h>
#include "../sounddevice.h"
#include "../mixer.h"
#include "criticalsection.h"
#include "iphone.h"
namespace wi {
#define kcBuffers 4
#define kcbBuffer 256
#define kcChannels 4
extern long HostGetTickCount();
extern void SetSoundServiceDevice(SoundDevice *psndd);
void AudioCallback(void *pvUser, AudioQueueRef haq, AudioQueueBuffer *paqb);
class IPhoneSoundDevice : public SoundDevice // sndd
{
public:
IPhoneSoundDevice();
virtual ~IPhoneSoundDevice();
bool Init();
virtual void Enable(bool fEnable);
virtual bool IsEnabled();
virtual void PlayAdpcm(int ichnl, byte *pb, word cb);
virtual int GetChannelCount();
virtual bool IsChannelFree(int ichnl);
virtual void ServiceProc();
virtual bool IsSilent();
virtual void SetVolume(int nVolume);
virtual int GetVolume();
private:
void InitAudioBuffer(AudioQueueBuffer *paqb);
bool m_fEnable;
long m_tSilence;
AudioQueueRef m_haq;
AudioQueueBuffer *m_apaqb[kcBuffers];
Channel m_achnl[kcChannels];
base::CriticalSection m_crit;
friend void AudioCallback(void *pvUser, AudioQueueRef haq,
AudioQueueBuffer *paqb);
};
SoundDevice *CreateIPhoneSoundDevice()
{
IPhoneSoundDevice *psndd = new IPhoneSoundDevice;
if (psndd == NULL)
return NULL;
if (!psndd->Init()) {
delete psndd;
return NULL;
}
return (SoundDevice *)psndd;
}
IPhoneSoundDevice::IPhoneSoundDevice()
{
m_haq = NULL;
for (int i = 0; i < ARRAYSIZE(m_apaqb); i++) {
m_apaqb[i] = NULL;
}
m_fEnable = false;
m_tSilence = 0;
}
IPhoneSoundDevice::~IPhoneSoundDevice()
{
// This api deletes buffers too
if (m_haq != NULL) {
AudioQueueDispose(m_haq, true);
}
SetSoundServiceDevice(NULL);
}
bool IPhoneSoundDevice::Init()
{
// Initialize the default audio session object to tell it
// to allow background music, and to tell us when audio
// gets resumed (like if a phone call comes in, iphone takes
// over audio. If the user then ignores the phone call, the
// audio needs to be turned on again.
AVAudioSession *session = [AVAudioSession sharedInstance];
if (session == nil) {
return false;
}
// This category will let background music continue to play, will mute
// when ring/silent is set to "silent", and will mute when the screen locks.
NSError *sessionError = nil;
BOOL success = [session setCategory:AVAudioSessionCategoryAmbient error:&sessionError];
if (!success) {
NSLog(@"AVAudioSession setCategory error %@", sessionError);
return false;
}
success = [session setActive:YES error:nil];
if (!success) {
return false;
}
// Set up streaming
AudioStreamBasicDescription desc;
desc.mSampleRate = 8000;
desc.mFormatID = kAudioFormatLinearPCM;
desc.mFormatFlags = kAudioFormatFlagIsPacked;
desc.mBytesPerPacket = 1;
desc.mFramesPerPacket = 1;
desc.mBytesPerFrame = 1;
desc.mChannelsPerFrame = 1;
desc.mBitsPerChannel = 8;
OSStatus err = AudioQueueNewOutput(&desc, AudioCallback, this,
NULL,
kCFRunLoopCommonModes,
0, &m_haq);
if (err != 0) {
return false;
}
for (int i = 0; i < kcBuffers; i++) {
err = AudioQueueAllocateBuffer(m_haq, kcbBuffer, &m_apaqb[i]);
if (err != 0) {
return false;
}
}
return true;
}
bool IPhoneSoundDevice::IsEnabled()
{
return m_fEnable;
}
void IPhoneSoundDevice::Enable(bool fEnable)
{
base::CritScope cs(&m_crit);
if (fEnable) {
if (!m_fEnable) {
memset(m_achnl, 0, sizeof(m_achnl));
m_tSilence = 0;
m_fEnable = true;
for (int i = 0; i < kcBuffers; i++) {
InitAudioBuffer(m_apaqb[i]);
}
AudioQueuePrime(m_haq, 0, NULL);
AudioQueueStart(m_haq, NULL);
SetSoundServiceDevice(this);
}
} else {
if (m_fEnable) {
m_fEnable = false;
memset(m_achnl, 0, sizeof(m_achnl));
m_tSilence = 0;
AudioQueueStop(m_haq, false);
SetSoundServiceDevice(NULL);
}
}
}
int IPhoneSoundDevice::GetChannelCount()
{
return kcChannels;
}
bool IPhoneSoundDevice::IsChannelFree(int ichnl)
{
if (ichnl < 0 || ichnl >= kcChannels)
return false;
Channel *pchnl = &m_achnl[ichnl];
return (pchnl->pb >= pchnl->pbEnd);
}
bool IPhoneSoundDevice::IsSilent()
{
if (!m_fEnable)
return true;
if (m_tSilence == 0)
return true;
long tCurrent = HostGetTickCount();
if (tCurrent < 0 && m_tSilence >= 0)
return true;
return tCurrent >= m_tSilence;
}
void IPhoneSoundDevice::ServiceProc()
{
}
void IPhoneSoundDevice::PlayAdpcm(int ichnl, byte *pb, word cb)
{
if (ichnl < 0 || ichnl >= kcChannels)
return;
if (!m_fEnable)
return;
// Start it up
{
base::CritScope cs(&m_crit);
Channel *pchnl = &m_achnl[ichnl];
pchnl->pb = pb;
pchnl->pbEnd = pb + cb;
pchnl->nStepIndex = 0;
pchnl->bSampleLast = 128;
m_tSilence = HostGetTickCount() +
(cb + kcBuffers * kcbBuffer + 39) / 40 + 1;
}
}
void IPhoneSoundDevice::SetVolume(int nVolume)
{
if (nVolume < 0)
nVolume = 0;
if (nVolume > 255)
nVolume = 255;
AudioQueueSetParameter(m_haq, kAudioQueueParam_Volume,
(Float32)nVolume / (Float32)255.0);
}
int IPhoneSoundDevice::GetVolume()
{
AudioQueueParameterValue value;
AudioQueueGetParameter(m_haq, kAudioQueueParam_Volume,
&value);
return (int)(value * 255.0);
}
void AudioCallback(void *pvUser, AudioQueueRef haq, AudioQueueBuffer *paqb)
{
IPhoneSoundDevice *psndd = (IPhoneSoundDevice *)pvUser;
psndd->InitAudioBuffer(paqb);
}
void IPhoneSoundDevice::InitAudioBuffer(AudioQueueBuffer *paqb)
{
base::CritScope cs(&m_crit);
// Don't send buffers to the device if not enabled
if (!m_fEnable)
return;
// Fill the buffer and queue it for playing
paqb->mAudioDataByteSize = kcbBuffer;
MixChannels(m_achnl, ARRAYSIZE(m_achnl), (byte *)paqb->mAudioData,
kcbBuffer);
AudioQueueEnqueueBuffer(m_haq, paqb, 0, NULL);
}
} // namespace wi