diff --git a/Jamulus.pro b/Jamulus.pro
index 9778b3daf2..89072d5bd6 100644
--- a/Jamulus.pro
+++ b/Jamulus.pro
@@ -196,12 +196,8 @@ win32 {
OBJECTIVE_SOURCES += ios/sound.mm
QMAKE_TARGET_BUNDLE_PREFIX = io.jamulus
QMAKE_APPLICATION_BUNDLE_NAME. = $$TARGET
- LIBS += -framework CoreFoundation \
- -framework CoreServices \
- -framework AVFoundation \
- -framework CoreMIDI \
- -framework AudioToolbox \
- -framework Foundation
+ LIBS += -framework AVFoundation \
+ -framework AudioToolbox
} else:android {
# we want to compile with C++14
CONFIG += c++14
diff --git a/ios/Info.plist b/ios/Info.plist
index dfc2d2937c..aa4a9559b8 100644
--- a/ios/Info.plist
+++ b/ios/Info.plist
@@ -29,6 +29,10 @@
UIApplicationSupportsIndirectInputEvents
+ UIBackgroundModes
+
+ audio
+
UILaunchScreen
UILaunchStoryboardName
diff --git a/ios/ios_app_delegate.mm b/ios/ios_app_delegate.mm
index 54b78a2651..42a1dbee3b 100644
--- a/ios/ios_app_delegate.mm
+++ b/ios/ios_app_delegate.mm
@@ -1,4 +1,3 @@
-
#import "ios_app_delegate.h"
@interface QIOSApplicationDelegate ()
@@ -7,10 +6,8 @@ @interface QIOSApplicationDelegate ()
@implementation QIOSApplicationDelegate
-
-
-- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
-
+- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
+{
return YES;
}
@end
diff --git a/ios/sound.h b/ios/sound.h
index 7f08179867..f6bdee07f3 100644
--- a/ios/sound.h
+++ b/ios/sound.h
@@ -1,8 +1,8 @@
/******************************************************************************\
- * Copyright (c) 2004-2020
+ * Copyright (c) 2004-2021
*
* Author(s):
- * ann0see based on code from Volker Fischer
+ * ann0see and ngocdh based on code from Volker Fischer
*
******************************************************************************
*
@@ -23,13 +23,11 @@
\******************************************************************************/
#pragma once
-#include
-#include
-#include
#include "soundbase.h"
#include "global.h"
-/* Classes ********************************************************************/
+#import
+
class CSound : public CSoundBase
{
Q_OBJECT
@@ -40,68 +38,36 @@ class CSound : public CSoundBase
const QString& strMIDISetup,
const bool,
const QString& );
+ ~CSound();
virtual int Init ( const int iNewPrefMonoBufferSize );
virtual void Start();
virtual void Stop();
+ virtual void processBufferList ( AudioBufferList*, CSound* );
- // channel selection
- virtual int GetNumInputChannels() { return iNumInChanPlusAddChan; }
- virtual QString GetInputChannelName ( const int iDiD ) { return sChannelNamesInput[iDiD]; }
- virtual void SetLeftInputChannel ( const int iNewChan );
- virtual void SetRightInputChannel ( const int iNewChan );
- virtual int GetLeftInputChannel() { return iSelInputLeftChannel; }
- virtual int GetRightInputChannel() { return iSelInputRightChannel; }
-
- virtual int GetNumOutputChannels() { return iNumOutChan; }
- virtual QString GetOutputChannelName ( const int iDiD ) { return sChannelNamesOutput[iDiD]; }
- virtual void SetLeftOutputChannel ( const int iNewChan );
- virtual void SetRightOutputChannel ( const int iNewChan );
- virtual int GetLeftOutputChannel() { return iSelOutputLeftChannel; }
- virtual int GetRightOutputChannel() { return iSelOutputRightChannel; }
+ AudioUnit audioUnit;
// these variables/functions should be protected but cannot since we want
// to access them from the callback function
CVector vecsTmpAudioSndCrdStereo;
int iCoreAudioBufferSizeMono;
int iCoreAudioBufferSizeStereo;
- long lCurDev;
- int iNumInChan;
- int iNumInChanPlusAddChan; // includes additional "added" channels
- int iNumOutChan;
- int iSelInputLeftChannel;
- int iSelInputRightChannel;
- int iSelOutputLeftChannel;
- int iSelOutputRightChannel;
- int iSelInBufferLeft;
- int iSelInBufferRight;
- int iSelInInterlChLeft;
- int iSelInInterlChRight;
- int iSelAddInBufferLeft;
- int iSelAddInBufferRight;
- int iSelAddInInterlChLeft;
- int iSelAddInInterlChRight;
- int iSelOutBufferLeft;
- int iSelOutBufferRight;
- int iSelOutInterlChLeft;
- int iSelOutInterlChRight;
- CVector vecNumInBufChan;
- CVector vecNumOutBufChan;
+ bool isInitialized;
protected:
virtual QString LoadAndInitializeDriver ( QString strDriverName, bool );
+ void GetAvailableInOutDevices();
+ void SwitchDevice ( QString strDriverName );
- QString CheckDeviceCapabilities ( const int iDriverIdx );
- void GetAvailableInOutDevices();
-
- static void callbackMIDI ( const MIDIPacketList* pktlist, void* refCon, void* );
-
- // AVAudioSession audioSession;
-
- MIDIPortRef midiInPortRef;
-
- QString sChannelNamesInput[MAX_NUM_IN_OUT_CHANNELS];
- QString sChannelNamesOutput[MAX_NUM_IN_OUT_CHANNELS];
+ AudioBuffer buffer;
+ AudioBufferList bufferList;
+ void checkStatus ( int status );
+ static OSStatus recordingCallback ( void* inRefCon,
+ AudioUnitRenderActionFlags* ioActionFlags,
+ const AudioTimeStamp* inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList* ioData );
QMutex Mutex;
};
diff --git a/ios/sound.mm b/ios/sound.mm
index a768b14876..b2366b51f6 100644
--- a/ios/sound.mm
+++ b/ios/sound.mm
@@ -1,8 +1,8 @@
/******************************************************************************\
- * Copyright (c) 2004-2020
+ * Copyright (c) 2004-2021
*
* Author(s):
- * ann0see based on code from Volker Fischer
+ * ann0see and ngocdh based on code from Volker Fischer
*
******************************************************************************
*
@@ -24,209 +24,324 @@
#include "sound.h"
#include
+#define kOutputBus 0
+#define kInputBus 1
/* Implementation *************************************************************/
-CSound::CSound ( void (*fpNewProcessCallback) ( CVector& psData, void* arg ),
+CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ),
void* arg,
const QString& strMIDISetup,
- const bool ,
+ const bool,
const QString& ) :
CSoundBase ( "CoreAudio iOS", fpNewProcessCallback, arg, strMIDISetup ),
- midiInPortRef ( static_cast ( NULL ) )
+ isInitialized ( false )
{
- NSError *audioSessionError = nil;
-
- [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&audioSessionError];
- [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
- if (granted) {
- // ok
- }
- }];
- [[AVAudioSession sharedInstance] setMode:AVAudioSessionModeMeasurement error:&audioSessionError];
+ try
+ {
+ NSError* audioSessionError = nil;
+
+ [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&audioSessionError];
+ [[AVAudioSession sharedInstance] requestRecordPermission:^( BOOL granted ) {
+ if ( granted )
+ {
+ // ok
+ }
+ else
+ {
+ // TODO - alert user
+ }
+ }];
+ [[AVAudioSession sharedInstance] setMode:AVAudioSessionModeMeasurement error:&audioSessionError];
+ }
+ catch ( const CGenErr& generr )
+ {
+ QMessageBox::warning ( nullptr, "Sound exception", generr.GetErrorText() );
+ }
- //GetAvailableInOutDevices();
+ buffer.mNumberChannels = 2;
+ buffer.mData = malloc ( 256 * sizeof ( Float32 ) * buffer.mNumberChannels ); // max size
+ bufferList.mNumberBuffers = 1;
+ bufferList.mBuffers[0] = buffer;
}
-int CSound::Init ( const int /*iNewPrefMonoBufferSize*/ )
+CSound::~CSound() { free ( buffer.mData ); }
+
+/**
+ This callback is called when sound card needs output data to play.
+ And because Jamulus uses the same buffer to store input and output data (input is sent to server, then output is fetched from server), we actually
+ use the output callback to read inputdata first, process it, and then copy the output fetched from server to ioData, which will then be played.
+ */
+OSStatus CSound::recordingCallback ( void* inRefCon,
+ AudioUnitRenderActionFlags* ioActionFlags,
+ const AudioTimeStamp* inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList* ioData )
{
- // store buffer size
- //iCoreAudioBufferSizeMono = audioSession.IOBufferDuration;
+ CSound* pSound = static_cast ( inRefCon );
- // init base class
- //CSoundBase::Init ( iCoreAudioBufferSizeMono );
+ // setting up temp buffer
+ pSound->buffer.mDataByteSize = pSound->iCoreAudioBufferSizeMono * sizeof ( Float32 ) * pSound->buffer.mNumberChannels;
+ pSound->bufferList.mBuffers[0] = pSound->buffer;
- // set internal buffer size value and calculate stereo buffer size
- //iCoreAudioBufferSizeStereo = 2 * iCoreAudioBufferSizeMono;
+ // Obtain recorded samples
- // create memory for intermediate audio buffer
- //vecsTmpAudioSndCrdStereo.Init ( iCoreAudioBufferSizeStereo );
+ // Calling Unit Render to store input data to bufferList
+ AudioUnitRender ( pSound->audioUnit, ioActionFlags, inTimeStamp, 1, inNumberFrames, &pSound->bufferList );
- return iCoreAudioBufferSizeMono;
-}
+ // Now, we have the samples we just read sitting in buffers in bufferList
+ // Process the new data
+ pSound->processBufferList ( &pSound->bufferList, pSound ); // THIS IS WHERE vecsStereo is filled with data from bufferList
-void CSound::Start()
-{
- // ??
+ Float32* pData = (Float32*) ( ioData->mBuffers[0].mData );
- // call base class
- //CSoundBase::Start();
-}
+ // copy output data
+ for ( int i = 0; i < pSound->iCoreAudioBufferSizeMono; i++ )
+ {
+ pData[2 * i] = (Float32) pSound->vecsTmpAudioSndCrdStereo[2 * i] / _MAXSHORT; // left
+ pData[2 * i + 1] = (Float32) pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] / _MAXSHORT; // right
+ }
-void CSound::Stop()
-{
- // ??
- // call base class
- //CSoundBase::Stop();
+ return noErr;
}
-void CSound::GetAvailableInOutDevices()
+void CSound::processBufferList ( AudioBufferList* inInputData, CSound* pSound ) // got stereo input data
{
- // https://developer.apple.com/documentation/avfoundation/avaudiosession/1616557-availableinputs?language=objc?
+ QMutexLocker locker ( &pSound->MutexAudioProcessCallback );
+ Float32* pData = static_cast ( inInputData->mBuffers[0].mData );
+ // copy input data
+ for ( int i = 0; i < pSound->iCoreAudioBufferSizeMono; i++ )
+ {
+ // copy left and right channels separately
+ pSound->vecsTmpAudioSndCrdStereo[2 * i] = (short) ( pData[2 * i] * _MAXSHORT ); // left
+ pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] = (short) ( pData[2 * i + 1] * _MAXSHORT ); // right
+ }
+ pSound->ProcessCallback ( pSound->vecsTmpAudioSndCrdStereo );
}
-QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool )
-{
- // get the driver: check if devices are capable
- // reload the driver list of available sound devices
- //GetAvailableInOutDevices();
-
- // find driver index from given driver name
- //int iDriverIdx = INVALID_INDEX; // initialize with an invalid index
-
- /*for ( int i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ )
- {
- if ( strDriverName.compare ( strDriverNames[i] ) == 0 )
- {
- iDriverIdx = i;
- }
- }
-
- // if the selected driver was not found, return an error message
- if ( iDriverIdx == INVALID_INDEX )
- {
- return tr ( "The current selected audio device is no longer present in the system." );
- }
-
- // check device capabilities if it fulfills our requirements
- const QString strStat = CheckDeviceCapabilities ( iDriverIdx );
-
- // check if device is capable and if not the same device is used
- if ( strStat.isEmpty() && ( strCurDevName.compare ( strDriverNames[iDriverIdx] ) != 0 ) )
- {
- }
- // set the left/right in/output channels
-
- return strStat;
- */
- return "";
-}
-
-QString CSound::CheckDeviceCapabilities ( const int iDriverIdx )
+// TODO - CSound::Init is called multiple times at launch to verify device capabilities.
+// For iOS though, Init takes long, so should better reduce those inits for iOS (Now: 9 times/launch).
+int CSound::Init ( const int iCoreAudioBufferSizeMono )
{
- /*// This function checks if our required input/output channel
- // properties are supported by the selected device. If the return
- // string is empty, the device can be used, otherwise the error
- // message is returned.
-
- // check sample rate of input
- audioSession.setPreferredSampleRate( SYSTEM_SAMPLE_RATE_HZ );
- // if false, try to set it
- if ( audioSession.sampleRate != SYSTEM_SAMPLE_RATE_HZ )
- {
- throw CGenErr ( tr ("Could not set sample rate. Please close other applications playing audio.") );
- }
-
- // special case with 4 input channels: support adding channels
- if ( ( lNumInChan == 4 ) && bInputChMixingSupported )
+ try
{
- // add four mixed channels (i.e. 4 normal, 4 mixed channels)
- lNumInChanPlusAddChan = 8;
-
- for ( int iCh = 0; iCh < lNumInChanPlusAddChan; iCh++ )
+ this->iCoreAudioBufferSizeMono = iCoreAudioBufferSizeMono;
+
+ // set internal buffer size value and calculate stereo buffer size
+ iCoreAudioBufferSizeStereo = 2 * iCoreAudioBufferSizeMono;
+
+ // create memory for intermediate audio buffer
+ vecsTmpAudioSndCrdStereo.Init ( iCoreAudioBufferSizeStereo );
+
+ AVAudioSession* sessionInstance = [AVAudioSession sharedInstance];
+
+ // we are going to play and record so we pick that category
+ NSError* error = nil;
+ [sessionInstance setCategory:AVAudioSessionCategoryPlayAndRecord
+ withOptions:AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionDefaultToSpeaker |
+ AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionAllowBluetoothA2DP |
+ AVAudioSessionCategoryOptionAllowAirPlay
+ error:&error];
+
+ // using values from jamulus settings 64 = 2.67ms/2
+ NSTimeInterval bufferDuration = iCoreAudioBufferSizeMono / Float32 ( SYSTEM_SAMPLE_RATE_HZ ); // yeah it's math
+ [sessionInstance setPreferredIOBufferDuration:bufferDuration error:&error];
+
+ // set the session's sample rate 48000 - the only supported by Jamulus
+ [sessionInstance setPreferredSampleRate:SYSTEM_SAMPLE_RATE_HZ error:&error];
+ [[AVAudioSession sharedInstance] setActive:YES error:&error];
+
+ OSStatus status;
+
+ // Describe audio component
+ AudioComponentDescription desc;
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = kAudioUnitSubType_RemoteIO;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+
+ // Get component
+ AudioComponent inputComponent = AudioComponentFindNext ( NULL, &desc );
+
+ // Get audio units
+ status = AudioComponentInstanceNew ( inputComponent, &audioUnit );
+ checkStatus ( status );
+
+ // Enable IO for recording
+ UInt32 flag = 1;
+ status = AudioUnitSetProperty ( audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof ( flag ) );
+ checkStatus ( status );
+
+ // Enable IO for playback
+ status = AudioUnitSetProperty ( audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof ( flag ) );
+ checkStatus ( status );
+
+ // Describe format
+ AudioStreamBasicDescription audioFormat;
+ audioFormat.mSampleRate = SYSTEM_SAMPLE_RATE_HZ;
+ audioFormat.mFormatID = kAudioFormatLinearPCM;
+ audioFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
+ audioFormat.mFramesPerPacket = 1;
+ audioFormat.mChannelsPerFrame = 2; // stereo, so 2 interleaved channels
+ audioFormat.mBitsPerChannel = 32; // sizeof float32
+ audioFormat.mBytesPerPacket = 8; // (sizeof float32) * 2 channels
+ audioFormat.mBytesPerFrame = 8; //(sizeof float32) * 2 channels
+
+ // Apply format
+ status = AudioUnitSetProperty ( audioUnit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output,
+ kInputBus,
+ &audioFormat,
+ sizeof ( audioFormat ) );
+ checkStatus ( status );
+ status = AudioUnitSetProperty ( audioUnit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ kOutputBus,
+ &audioFormat,
+ sizeof ( audioFormat ) );
+ checkStatus ( status );
+
+ // Set callback
+ AURenderCallbackStruct callbackStruct;
+ callbackStruct.inputProc = recordingCallback; // this is actually the playback callback
+ callbackStruct.inputProcRefCon = this;
+ status = AudioUnitSetProperty ( audioUnit,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Global,
+ kOutputBus,
+ &callbackStruct,
+ sizeof ( callbackStruct ) );
+ checkStatus ( status );
+
+ // Initialise
+ status = AudioUnitInitialize ( audioUnit );
+ checkStatus ( status );
+
+ SwitchDevice ( strCurDevName );
+
+ if ( !isInitialized )
{
- int iSelCH, iSelAddCH;
-
- GetSelCHAndAddCH ( iCh, lNumInChan, iSelCH, iSelAddCH );
-
- if ( iSelAddCH >= 0 )
- {
- // for mixed channels, show both audio channel names to be mixed
- channelInputName[iCh] =
- channelInputName[iSelCH] + " + " + channelInputName[iSelAddCH];
- }
+ [[NSNotificationCenter defaultCenter] addObserverForName:AVAudioSessionRouteChangeNotification
+ object:nil
+ queue:nil
+ usingBlock:^( NSNotification* notification ) {
+ UInt8 reason =
+ [[notification.userInfo valueForKey:AVAudioSessionRouteChangeReasonKey] intValue];
+ if ( reason == AVAudioSessionRouteChangeReasonNewDeviceAvailable or
+ reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable )
+ {
+ emit ReinitRequest ( RS_RELOAD_RESTART_AND_INIT ); // reload the available devices frame
+ }
+ }];
}
+
+ isInitialized = true;
}
- else
+ catch ( const CGenErr& generr )
{
- // regular case: no mixing input channels used
- lNumInChanPlusAddChan = lNumInChan;
+ QMessageBox::warning ( nullptr, "Sound init exception", generr.GetErrorText() );
}
-*/
- // everything is ok, return empty string for "no error" case
- return "";
+
+ return iCoreAudioBufferSizeMono;
}
-void CSound::SetLeftInputChannel ( const int iNewChan )
+void CSound::Start()
{
- /*// apply parameter after input parameter check
- if ( ( iNewChan >= 0 ) && ( iNewChan < iNumInChanPlusAddChan ) )
+ // call base class
+ CSoundBase::Start();
+ try
{
- iSelInputLeftChannel = iNewChan;
+ OSStatus err = AudioOutputUnitStart ( audioUnit );
+ checkStatus ( err );
+ }
+ catch ( const CGenErr& generr )
+ {
+ QMessageBox::warning ( nullptr, "Sound start exception", generr.GetErrorText() );
}
- */
}
-void CSound::SetRightInputChannel ( const int iNewChan )
+void CSound::Stop()
{
- /* // apply parameter after input parameter check
- if ( ( iNewChan >= 0 ) && ( iNewChan < lNumInChanPlusAddChan ) )
- {
- vSelectedInputChannels[1] = iNewChan;
- }*/
+ try
+ {
+ OSStatus err = AudioOutputUnitStop ( audioUnit );
+ checkStatus ( err );
+ }
+ catch ( const CGenErr& generr )
+ {
+ QMessageBox::warning ( nullptr, "Sound stop exception", generr.GetErrorText() );
+ }
+ // call base class
+ CSoundBase::Stop();
}
-void CSound::SetLeftOutputChannel ( const int iNewChan )
+void CSound::checkStatus ( int status )
{
- /*// apply parameter after input parameter check
- if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) )
- {
- vSelectedOutputChannels[0] = iNewChan;
- }*/
+ if ( status )
+ {
+ printf ( "Status not 0! %d\n", status );
+ }
}
-void CSound::SetRightOutputChannel ( const int iNewChan )
+QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool )
{
- /*// apply parameter after input parameter check
- if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) )
- {
- vSelectedOutputChannels[1] = iNewChan;
- }*/
-}
+ // secure lNumDevs/strDriverNames access
+ QMutexLocker locker ( &Mutex );
+ // reload the driver list of available sound devices
+ GetAvailableInOutDevices();
+
+ // store the current name of the driver
+ strCurDevName = strDriverName;
+
+ return "";
+}
-void CSound::callbackMIDI ( const MIDIPacketList* pktlist,
- void* refCon,
- void* )
+void CSound::GetAvailableInOutDevices()
{
- /* CSound* pSound = static_cast ( refCon );
+ // always add system default devices for input and output as first entry
+ lNumDevs = 1;
+ strDriverNames[0] = "System Default In/Out Devices";
+
+ AVAudioSession* sessionInstance = [AVAudioSession sharedInstance];
- if ( pSound->midiInPortRef != static_cast ( NULL ) )
+ if ( sessionInstance.availableInputs.count > 1 )
{
- MIDIPacket* midiPacket = const_cast ( pktlist->packet );
+ lNumDevs = 2;
+ strDriverNames[1] = "in: Built-in Mic/out: System Default";
+ }
+}
- for ( unsigned int j = 0; j < pktlist->numPackets; j++ )
+void CSound::SwitchDevice ( QString strDriverName )
+{
+ // find driver index from given driver name
+ int iDriverIdx = INVALID_INDEX; // initialize with an invalid index
+
+ for ( int i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ )
+ {
+ if ( strDriverName.compare ( strDriverNames[i] ) == 0 )
{
- // copy packet and send it to the MIDI parser
- CVector vMIDIPaketBytes ( midiPacket->length );
- for ( int i = 0; i < midiPacket->length; i++ )
- {
- vMIDIPaketBytes[i] = static_cast ( midiPacket->data[i] );
- }
- pSound->ParseMIDIMessage ( vMIDIPaketBytes );
-
- midiPacket = MIDIPacketNext ( midiPacket );
+ iDriverIdx = i;
+ break;
}
- }*/
+ }
+
+ NSError* error = nil;
+
+ AVAudioSession* sessionInstance = [AVAudioSession sharedInstance];
+
+ if ( iDriverIdx == 0 ) // system default device
+ {
+ unsigned long lastInput = sessionInstance.availableInputs.count - 1;
+ [sessionInstance setPreferredInput:sessionInstance.availableInputs[lastInput] error:&error];
+ }
+ else // built-in mic
+ {
+ [sessionInstance setPreferredInput:sessionInstance.availableInputs[0] error:&error];
+ }
}
diff --git a/src/client.cpp b/src/client.cpp
index 240a2e122b..decc43a768 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -769,14 +769,28 @@ void CClient::Init()
const int iFraSizeDefault = SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_DEFAULT;
const int iFraSizeSafe = SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_SAFE;
+#if defined( Q_OS_IOS )
+ bFraSiFactPrefSupported = true; // to reduce sound init time, because we know it's supported in iOS
+ bFraSiFactDefSupported = true;
+ bFraSiFactSafeSupported = true;
+#else
bFraSiFactPrefSupported = ( Sound.Init ( iFraSizePreffered ) == iFraSizePreffered );
bFraSiFactDefSupported = ( Sound.Init ( iFraSizeDefault ) == iFraSizeDefault );
bFraSiFactSafeSupported = ( Sound.Init ( iFraSizeSafe ) == iFraSizeSafe );
+#endif
// translate block size index in actual block size
const int iPrefMonoFrameSize = iSndCrdPrefFrameSizeFactor * SYSTEM_FRAME_SIZE_SAMPLES;
// get actual sound card buffer size using preferred size
+ // TODO - iOS needs 1 init only, now: 9 inits at launch <- slow
+ // Initially, I tried to fix this as follows (inside #ifdef ios tag):
+ // if ( Sound.isInitialized )
+ // iMonoBlockSizeSam = iPrefMonoFrameSize;
+ // else
+ // iMonoBlockSizeSam = Sound.Init ( iPrefMonoFrameSize );
+ // Problem is legitimate setting changes (buffer size for example).
+ // so the condition should be something like "if ( Sound.isInitialized and APP_IS_INIALIZING)"
iMonoBlockSizeSam = Sound.Init ( iPrefMonoFrameSize );
// Calculate the current sound card frame size factor. In case
diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp
index f0bea55c83..ebf8fb3755 100644
--- a/src/clientsettingsdlg.cpp
+++ b/src/clientsettingsdlg.cpp
@@ -33,7 +33,7 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet
setupUi ( this );
#if defined( Q_OS_IOS )
- // IOS needs menu to close
+ // iOS needs menu to close
QMenuBar* pMenu = new QMenuBar ( this );
QAction* action = pMenu->addAction ( tr ( "&Close" ) );
connect ( action, SIGNAL ( triggered() ), this, SLOT ( close() ) );
diff --git a/src/main.cpp b/src/main.cpp
index 1205aa44b0..94ee8de2c2 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -738,6 +738,7 @@ int main ( int argc, char** argv )
#else
# if defined( Q_OS_IOS )
bUseGUI = true;
+ bIsClient = true; // Client only - TODO: maybe a switch in interface to change to server?
// bUseMultithreading = true;
QApplication* pApp = new QApplication ( argc, argv );
diff --git a/src/socket.cpp b/src/socket.cpp
index d7cdb9a699..3a6622f91a 100644
--- a/src/socket.cpp
+++ b/src/socket.cpp
@@ -33,13 +33,59 @@
#endif
/* Implementation *************************************************************/
-void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP )
+
+// Connections -------------------------------------------------------------
+// it is important to do the following connections in this class since we
+// have a thread transition
+
+// we have different connections for client and server, created after Init in corresponding constructor
+
+CSocket::CSocket ( CChannel* pNewChannel, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP, bool bEnableIPv6 ) :
+ pChannel ( pNewChannel ),
+ bIsClient ( true ),
+ bJitterBufferOK ( true ),
+ bEnableIPv6 ( bEnableIPv6 )
+{
+ Init ( iPortNumber, iQosNumber, strServerBindIP );
+
+ // client connections:
+ QObject::connect ( this, &CSocket::ProtcolMessageReceived, pChannel, &CChannel::OnProtcolMessageReceived );
+
+ QObject::connect ( this, &CSocket::ProtcolCLMessageReceived, pChannel, &CChannel::OnProtcolCLMessageReceived );
+
+ QObject::connect ( this, static_cast ( &CSocket::NewConnection ), pChannel, &CChannel::OnNewConnection );
+}
+
+CSocket::CSocket ( CServer* pNServP, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP, bool bEnableIPv6 ) :
+ pServer ( pNServP ),
+ bIsClient ( false ),
+ bJitterBufferOK ( true ),
+ bEnableIPv6 ( bEnableIPv6 )
+{
+ Init ( iPortNumber, iQosNumber, strServerBindIP );
+
+ // server connections:
+ QObject::connect ( this, &CSocket::ProtcolMessageReceived, pServer, &CServer::OnProtcolMessageReceived );
+
+ QObject::connect ( this, &CSocket::ProtcolCLMessageReceived, pServer, &CServer::OnProtcolCLMessageReceived );
+
+ QObject::connect ( this, static_cast ( &CSocket::NewConnection ), pServer, &CServer::OnNewConnection );
+
+ QObject::connect ( this, &CSocket::ServerFull, pServer, &CServer::OnServerFull );
+}
+
+void CSocket::Init ( const quint16 iNewPortNumber, const quint16 iNewQosNumber, const QString& strNewServerBindIP )
{
uSockAddr UdpSocketAddr;
int UdpSocketAddrLen;
uint16_t* UdpPort;
+ // first store parameters, in case reinit is required (mostly for iOS)
+ iPortNumber = iNewPortNumber;
+ iQosNumber = iNewQosNumber;
+ strServerBindIP = strNewServerBindIP;
+
#ifdef _WIN32
// for the Windows socket usage we have to start it up first
@@ -114,6 +160,12 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const
}
}
+#ifdef Q_OS_IOS
+ // ignore the broken pipe signal to avoid crash (iOS)
+ int valueone = 1;
+ setsockopt ( UdpSocket, SOL_SOCKET, SO_NOSIGPIPE, &valueone, sizeof ( valueone ) );
+#endif
+
// allocate memory for network receive and send buffer in samples
vecbyRecBuf.Init ( MAX_SIZE_BYTES_NETW_BUF );
@@ -164,38 +216,9 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const
if ( !bSuccess )
{
// we cannot bind socket, throw error
- throw CGenErr ( "Cannot bind the socket (maybe the software is already running).", "Network Error" );
- }
-
- // Connections -------------------------------------------------------------
- // it is important to do the following connections in this class since we
- // have a thread transition
-
- // we have different connections for client and server
- if ( bIsClient )
- {
- // client connections:
-
- QObject::connect ( this, &CSocket::ProtcolMessageReceived, pChannel, &CChannel::OnProtcolMessageReceived );
-
- QObject::connect ( this, &CSocket::ProtcolCLMessageReceived, pChannel, &CChannel::OnProtcolCLMessageReceived );
-
- QObject::connect ( this, static_cast ( &CSocket::NewConnection ), pChannel, &CChannel::OnNewConnection );
- }
- else
- {
- // server connections:
-
- QObject::connect ( this, &CSocket::ProtcolMessageReceived, pServer, &CServer::OnProtcolMessageReceived );
-
- QObject::connect ( this, &CSocket::ProtcolCLMessageReceived, pServer, &CServer::OnProtcolCLMessageReceived );
-
- QObject::connect ( this,
- static_cast ( &CSocket::NewConnection ),
- pServer,
- &CServer::OnNewConnection );
-
- QObject::connect ( this, &CSocket::ServerFull, pServer, &CServer::OnServerFull );
+ throw CGenErr ( "Cannot bind the socket (maybe "
+ "the software is already running).",
+ "Network Error" );
}
}
@@ -227,6 +250,8 @@ CSocket::~CSocket()
void CSocket::SendPacket ( const CVector& vecbySendBuf, const CHostAddress& HostAddr )
{
+ int status;
+
uSockAddr UdpSocketAddr;
memset ( &UdpSocketAddr, 0, sizeof ( UdpSocketAddr ) );
@@ -241,57 +266,73 @@ void CSocket::SendPacket ( const CVector& vecbySendBuf, const CHostAddr
// char vector in "const char*", for this we first convert the const
// uint8_t vector in a read/write uint8_t vector and then do the cast to
// const char *)
- if ( HostAddr.InetAddr.protocol() == QAbstractSocket::IPv4Protocol )
+
+ for ( int tries = 0; tries < 2; tries++ ) // retry loop in case send fails on iOS
{
- if ( bEnableIPv6 )
+ if ( HostAddr.InetAddr.protocol() == QAbstractSocket::IPv4Protocol )
+ {
+ if ( bEnableIPv6 )
+ {
+ // Linux and Mac allow to pass an AF_INET address to a dual-stack socket,
+ // but Windows does not. So use a V4MAPPED address in an AF_INET6 sockaddr,
+ // which works on all platforms.
+
+ UdpSocketAddr.sa6.sin6_family = AF_INET6;
+ UdpSocketAddr.sa6.sin6_port = htons ( HostAddr.iPort );
+
+ uint32_t* addr = (uint32_t*) &UdpSocketAddr.sa6.sin6_addr;
+
+ addr[0] = 0;
+ addr[1] = 0;
+ addr[2] = htonl ( 0xFFFF );
+ addr[3] = htonl ( HostAddr.InetAddr.toIPv4Address() );
+
+ status = sendto ( UdpSocket,
+ (const char*) &( (CVector) vecbySendBuf )[0],
+ iVecSizeOut,
+ 0,
+ &UdpSocketAddr.sa,
+ sizeof ( UdpSocketAddr.sa6 ) );
+ }
+ else
+ {
+ UdpSocketAddr.sa4.sin_family = AF_INET;
+ UdpSocketAddr.sa4.sin_port = htons ( HostAddr.iPort );
+ UdpSocketAddr.sa4.sin_addr.s_addr = htonl ( HostAddr.InetAddr.toIPv4Address() );
+
+ status = sendto ( UdpSocket,
+ (const char*) &( (CVector) vecbySendBuf )[0],
+ iVecSizeOut,
+ 0,
+ &UdpSocketAddr.sa,
+ sizeof ( UdpSocketAddr.sa4 ) );
+ }
+ }
+ else if ( bEnableIPv6 )
{
- // Linux and Mac allow to pass an AF_INET address to a dual-stack socket,
- // but Windows does not. So use a V4MAPPED address in an AF_INET6 sockaddr,
- // which works on all platforms.
-
UdpSocketAddr.sa6.sin6_family = AF_INET6;
UdpSocketAddr.sa6.sin6_port = htons ( HostAddr.iPort );
-
- uint32_t* addr = (uint32_t*) &UdpSocketAddr.sa6.sin6_addr;
-
- addr[0] = 0;
- addr[1] = 0;
- addr[2] = htonl ( 0xFFFF );
- addr[3] = htonl ( HostAddr.InetAddr.toIPv4Address() );
-
- sendto ( UdpSocket,
- (const char*) &( (CVector) vecbySendBuf )[0],
- iVecSizeOut,
- 0,
- &UdpSocketAddr.sa,
- sizeof ( UdpSocketAddr.sa6 ) );
+ inet_pton ( AF_INET6, HostAddr.InetAddr.toString().toLocal8Bit().constData(), &UdpSocketAddr.sa6.sin6_addr );
+
+ status = sendto ( UdpSocket,
+ (const char*) &( (CVector) vecbySendBuf )[0],
+ iVecSizeOut,
+ 0,
+ &UdpSocketAddr.sa,
+ sizeof ( UdpSocketAddr.sa6 ) );
}
- else
+
+ if ( status >= 0 )
{
- UdpSocketAddr.sa4.sin_family = AF_INET;
- UdpSocketAddr.sa4.sin_port = htons ( HostAddr.iPort );
- UdpSocketAddr.sa4.sin_addr.s_addr = htonl ( HostAddr.InetAddr.toIPv4Address() );
-
- sendto ( UdpSocket,
- (const char*) &( (CVector) vecbySendBuf )[0],
- iVecSizeOut,
- 0,
- &UdpSocketAddr.sa,
- sizeof ( UdpSocketAddr.sa4 ) );
+ break; // do not retry if success
}
- }
- else if ( bEnableIPv6 )
- {
- UdpSocketAddr.sa6.sin6_family = AF_INET6;
- UdpSocketAddr.sa6.sin6_port = htons ( HostAddr.iPort );
- inet_pton ( AF_INET6, HostAddr.InetAddr.toString().toLocal8Bit().constData(), &UdpSocketAddr.sa6.sin6_addr );
-
- sendto ( UdpSocket,
- (const char*) &( (CVector) vecbySendBuf )[0],
- iVecSizeOut,
- 0,
- &UdpSocketAddr.sa,
- sizeof ( UdpSocketAddr.sa6 ) );
+
+#ifdef Q_OS_IOS
+ // qDebug("Socket send exception - mostly happens in iOS when returning from idle");
+ Init ( iPortNumber, iQosNumber, strServerBindIP ); // reinit
+
+ // loop back to retry
+#endif
}
}
}
diff --git a/src/socket.h b/src/socket.h
index d41a343881..c29df14d9d 100644
--- a/src/socket.h
+++ b/src/socket.h
@@ -53,23 +53,8 @@ class CSocket : public QObject
Q_OBJECT
public:
- CSocket ( CChannel* pNewChannel, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP, bool bEnableIPv6 ) :
- pChannel ( pNewChannel ),
- bIsClient ( true ),
- bJitterBufferOK ( true ),
- bEnableIPv6 ( bEnableIPv6 )
- {
- Init ( iPortNumber, iQosNumber, strServerBindIP );
- }
-
- CSocket ( CServer* pNServP, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP, bool bEnableIPv6 ) :
- pServer ( pNServP ),
- bIsClient ( false ),
- bJitterBufferOK ( true ),
- bEnableIPv6 ( bEnableIPv6 )
- {
- Init ( iPortNumber, iQosNumber, strServerBindIP );
- }
+ CSocket ( CChannel* pNewChannel, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP, bool bEnableIPv6 );
+ CSocket ( CServer* pNServP, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP, bool bEnableIPv6 );
virtual ~CSocket();
@@ -79,7 +64,10 @@ class CSocket : public QObject
void Close();
protected:
- void Init ( const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP );
+ void Init ( const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP );
+ quint16 iPortNumber;
+ quint16 iQosNumber;
+ QString strServerBindIP;
#ifdef _WIN32
SOCKET UdpSocket;