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;