diff --git a/Jamulus.pro b/Jamulus.pro index 3394cfe7ab..b0ac5a3998 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -186,12 +186,8 @@ win32 { OBJECTIVE_SOURCES += ios/sound.mm QMAKE_TARGET_BUNDLE_PREFIX = com.jamulussoftware.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/android/sound.cpp b/android/sound.cpp index 65e45b108c..f92103c44f 100644 --- a/android/sound.cpp +++ b/android/sound.cpp @@ -345,3 +345,36 @@ void CSound::Stats::log() const << "frames_in: " << frames_in << ",frames_out: " << frames_out << ",frames_filled_out: " << frames_filled_out << ",in_callback_calls: " << in_callback_calls << ",out_callback_calls: " << out_callback_calls << ",ring_overrun: " << ring_overrun; } + +void CSound::SetInputDeviceId ( int deviceid ) // 0 for external device (auto to be exact) +{ + closeStream ( mRecordingStream ); + + oboe::AudioStreamBuilder inBuilder; + + // Setup input stream + inBuilder.setDirection ( oboe::Direction::Input ); + + // Only set callback for the input direction + // the output will be handled writing directly on the stream + inBuilder.setCallback ( this ); + setupCommonStreamParams ( &inBuilder ); + + if ( inBuilder.isAAudioSupported() ) + inBuilder.setAudioApi ( oboe::AudioApi::AAudio ); + + inBuilder.setDeviceId ( deviceid ); + oboe::Result result = inBuilder.openManagedStream ( mRecordingStream ); + if ( result != oboe::Result::OK ) + { + inBuilder.setDeviceId ( oboe::kUnspecified ); + result = inBuilder.openManagedStream ( mRecordingStream ); + } + + mRecordingStream->setBufferSizeInFrames ( iOboeBufferSizeStereo ); + + warnIfNotLowLatency ( mRecordingStream, "RecordStream" ); + printStreamDetails ( mRecordingStream ); + + mRecordingStream->requestStart(); +} diff --git a/android/sound.h b/android/sound.h index f75c0ab881..b500a1f821 100644 --- a/android/sound.h +++ b/android/sound.h @@ -49,6 +49,7 @@ class CSound : public CSoundBase, public oboe::AudioStreamCallback virtual int Init ( const int iNewPrefMonoBufferSize ); virtual void Start(); virtual void Stop(); + virtual void SetInputDeviceId ( int deviceid ); // Call backs for Oboe virtual oboe::DataCallbackResult onAudioReady ( oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames ); diff --git a/distributions/raspijamulus.sh b/distributions/raspijamulus.sh old mode 100755 new mode 100644 diff --git a/ios/Info.plist b/ios/Info.plist index dfc2d2937c..1631f2ca2d 100644 --- a/ios/Info.plist +++ b/ios/Info.plist @@ -6,6 +6,10 @@ $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundleIcons~ipad + CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -29,19 +33,25 @@ UIApplicationSupportsIndirectInputEvents + UIBackgroundModes + + audio + UILaunchScreen UILaunchStoryboardName LaunchScreen + UIMainStoryboardFile + LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations - UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad diff --git a/ios/ios_app_delegate.mm b/ios/ios_app_delegate.mm index 54b78a2651..dc84bab14c 100644 --- a/ios/ios_app_delegate.mm +++ b/ios/ios_app_delegate.mm @@ -7,10 +7,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..aeeec03f79 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,12 @@ \******************************************************************************/ #pragma once -#include #include -#include #include "soundbase.h" #include "global.h" -/* Classes ********************************************************************/ +#import + class CSound : public CSoundBase { Q_OBJECT @@ -44,64 +43,20 @@ class CSound : public CSoundBase virtual int Init ( const int iNewPrefMonoBufferSize ); virtual void Start(); virtual void Stop(); + virtual void processBufferList ( AudioBufferList*, CSound* ); + virtual void SetInputDeviceId ( int deviceid ); - // 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 ); - - 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]; - QMutex Mutex; }; diff --git a/ios/sound.mm b/ios/sound.mm index a768b14876..e827f29256 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,289 @@ #include "sound.h" #include +#define kOutputBus 0 +#define kInputBus 1 -/* Implementation *************************************************************/ -CSound::CSound ( void (*fpNewProcessCallback) ( CVector& psData, void* arg ), - void* arg, - const QString& strMIDISetup, - const bool , - const QString& ) : - CSoundBase ( "CoreAudio iOS", fpNewProcessCallback, arg, strMIDISetup ), - midiInPortRef ( static_cast ( NULL ) ) +void checkStatus ( int status ) { - NSError *audioSessionError = nil; - - [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&audioSessionError]; - [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) { - if (granted) { - // ok - } - }]; - [[AVAudioSession sharedInstance] setMode:AVAudioSessionModeMeasurement error:&audioSessionError]; + if ( status ) + { + printf ( "Status not 0! %d\n", status ); + } +} - //GetAvailableInOutDevices(); +void checkStatus ( int status, char* s ) +{ + if ( status ) + { + printf ( "Status not 0! %d - %s \n", status, s ); + } } -int CSound::Init ( const int /*iNewPrefMonoBufferSize*/ ) +/** + This callback is called when sound card needs output data to play. And because Jamulus use 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. + */ +static OSStatus 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 ); + AudioBuffer buffer; - // set internal buffer size value and calculate stereo buffer size - //iCoreAudioBufferSizeStereo = 2 * iCoreAudioBufferSizeMono; + buffer.mNumberChannels = 2; + buffer.mDataByteSize = pSound->iCoreAudioBufferSizeMono * sizeof ( Float32 ) * buffer.mNumberChannels; + buffer.mData = malloc ( buffer.mDataByteSize ); - // create memory for intermediate audio buffer - //vecsTmpAudioSndCrdStereo.Init ( iCoreAudioBufferSizeStereo ); + // Put buffer in a AudioBufferList + AudioBufferList bufferList; + bufferList.mNumberBuffers = 1; + bufferList.mBuffers[0] = buffer; - return iCoreAudioBufferSizeMono; -} + // Then: + // Obtain recorded samples -void CSound::Start() -{ - // ?? + OSStatus status; - // call base class - //CSoundBase::Start(); -} + // Calling Unit Render to store input data to bufferList + status = AudioUnitRender ( pSound->audioUnit, ioActionFlags, inTimeStamp, 1, inNumberFrames, &bufferList ); + // checkStatus(status, (char *)" Just called AudioUnitRender "); -void CSound::Stop() -{ - // ?? - // call base class - //CSoundBase::Stop(); -} + // Now, we have the samples we just read sitting in buffers in bufferList + // Process the new data + pSound->processBufferList ( &bufferList, pSound ); // THIS IS WHERE vecsStereo is filled with data from bufferList -void CSound::GetAvailableInOutDevices() -{ - // https://developer.apple.com/documentation/avfoundation/avaudiosession/1616557-availableinputs?language=objc? + // release the malloc'ed data in the buffer we created earlier + free ( bufferList.mBuffers[0].mData ); + + Float32* pData = (Float32*) ( ioData->mBuffers[0].mData ); + + // 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 + } + return noErr; } -QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool ) +void CSound::processBufferList ( AudioBufferList* inInputData, CSound* pSound ) // got stereo input data { - // 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 ""; + 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::CheckDeviceCapabilities ( const int iDriverIdx ) +/* Implementation *************************************************************/ +CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), + void* arg, + const QString& strMIDISetup, + const bool, + const QString& ) : + CSoundBase ( "CoreAudio iOS", fpNewProcessCallback, arg, strMIDISetup ), + midiInPortRef ( static_cast ( NULL ) ) { - /*// 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++ ) - { - 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]; - } - } + NSError* audioSessionError = nil; + + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&audioSessionError]; + [[AVAudioSession sharedInstance] requestRecordPermission:^( BOOL granted ) { + if ( granted ) + { + // ok + } + }]; + [[AVAudioSession sharedInstance] setMode:AVAudioSessionModeMeasurement error:&audioSessionError]; } - else + catch ( const CGenErr& generr ) { - // regular case: no mixing input channels used - lNumInChanPlusAddChan = lNumInChan; + qDebug ( "Sound exception ...." ); // This try-catch seems to fix Connect button crash } -*/ - // everything is ok, return empty string for "no error" case - return ""; + isInitialized = false; } -void CSound::SetLeftInputChannel ( const int iNewChan ) +int CSound::Init ( const int iCoreAudioBufferSizeMono ) { - /*// apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < iNumInChanPlusAddChan ) ) + try { - iSelInputLeftChannel = iNewChan; + // printf("Init sound ..."); <= to check the number of Sound inits at launch + // init base class + // CSoundBase::Init ( iCoreAudioBufferSizeMono ); this does nothing + 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]; + + // NGOCDH - using values from jamulus settings 64 = 2.67ms/2 + NSTimeInterval bufferDuration = iCoreAudioBufferSizeMono / 48000.0; // yeah it's math + [sessionInstance setPreferredIOBufferDuration:bufferDuration error:&error]; + + // set the session's sample rate 48000 - the only supported by Jamulus ? + [sessionInstance setPreferredSampleRate:48000 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 = 48000.00; + audioFormat.mFormatID = kAudioFormatLinearPCM; + audioFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; + audioFormat.mFramesPerPacket = 1; + audioFormat.mChannelsPerFrame = 2; // steoreo, 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 ); + + isInitialized = true; + } + catch ( const CGenErr& generr ) + { + qDebug ( "Sound Init exception ...." ); // This try-catch seems to fix Connect button crash } - */ -} -void CSound::SetRightInputChannel ( const int iNewChan ) -{ - /* // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < lNumInChanPlusAddChan ) ) - { - vSelectedInputChannels[1] = iNewChan; - }*/ + return iCoreAudioBufferSizeMono; } -void CSound::SetLeftOutputChannel ( const int iNewChan ) +void CSound::Start() { - /*// apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) ) - { - vSelectedOutputChannels[0] = iNewChan; - }*/ + // call base class + CSoundBase::Start(); + try + { + OSStatus err = AudioOutputUnitStart ( audioUnit ); + checkStatus ( err ); + } + catch ( const CGenErr& generr ) + { + qDebug ( "Sound Start exception ...." ); // This try-catch seems to fix Connect button crash + } } -void CSound::SetRightOutputChannel ( const int iNewChan ) +void CSound::Stop() { - /*// apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) ) - { - vSelectedOutputChannels[1] = iNewChan; - }*/ + try + { + OSStatus err = AudioOutputUnitStop ( audioUnit ); + checkStatus ( err ); + } + catch ( const CGenErr& generr ) + { + qDebug ( "Sound Stop exception ...." ); // This try-catch seems to fix Connect button crash + } + // call base class + CSoundBase::Stop(); } - -void CSound::callbackMIDI ( const MIDIPacketList* pktlist, - void* refCon, - void* ) +void CSound::SetInputDeviceId ( int deviceid ) { - /* CSound* pSound = static_cast ( refCon ); - - if ( pSound->midiInPortRef != static_cast ( NULL ) ) + try { - MIDIPacket* midiPacket = const_cast ( pktlist->packet ); + NSError* error = nil; + bool builtinmic = true; - for ( unsigned int j = 0; j < pktlist->numPackets; j++ ) + if ( deviceid == 0 ) + builtinmic = false; // try external device + + AVAudioSession* sessionInstance = [AVAudioSession sharedInstance]; + // assumming iOS only has max 2 inputs: 0 for builtin mic and 1 for external device + if ( builtinmic ) + { + [sessionInstance setPreferredInput:sessionInstance.availableInputs[0] error:&error]; + } + else { - // 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 ); + unsigned long lastInput = sessionInstance.availableInputs.count - 1; + [sessionInstance setPreferredInput:sessionInstance.availableInputs[lastInput] error:&error]; } - }*/ + } + catch ( const CGenErr& generr ) + { + qDebug ( "Sound dev change exception ...." ); // This try-catch seems to fix Connect button crash + } } diff --git a/src/chatdlg.cpp b/src/chatdlg.cpp index e248c5c8e5..bb8bc98c61 100644 --- a/src/chatdlg.cpp +++ b/src/chatdlg.cpp @@ -65,6 +65,9 @@ CChatDlg::CChatDlg ( QWidget* parent ) : CBaseDlg ( parent, Qt::Window ) // use connect ( action, SIGNAL ( triggered() ), this, SLOT ( close() ) ); #endif +#if defined( ANDROID ) || defined( Q_OS_ANDROID ) + pEditMenu->addAction ( tr ( "&Close" ), this, SLOT ( close() ), QKeySequence ( Qt::CTRL + Qt::Key_C ) ); +#endif // Now tell the layout about the menu layout()->setMenuBar ( pMenu ); diff --git a/src/client.cpp b/src/client.cpp index 367972abdb..4e53e2015e 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -52,6 +52,7 @@ CClient::CClient ( const quint16 iPortNumber, bReverbOnLeftChan ( false ), iReverbLevel ( 0 ), iInputBoost ( 1 ), + iBuiltInMicId ( 0 ), iSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_DEFAULT ), iSndCrdFrameSizeFactor ( FRAME_SIZE_FACTOR_DEFAULT ), bSndCrdConversionBufferRequired ( false ), @@ -767,14 +768,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 @@ -1236,3 +1251,11 @@ int CClient::EstimatedOverallDelay ( const int iPingTimeMs ) return MathUtils::round ( fTotalBufferDelayMs + iPingTimeMs ); } + +void CClient::SetInputDeviceId ( const int deviceid ) +{ +#if defined( Q_OS_IOS ) or defined( Q_OS_ANDROID ) or defined( ANDROID ) + // iOS ok, Android experimental + Sound.SetInputDeviceId ( deviceid ); +#endif +} diff --git a/src/client.h b/src/client.h index 929da28853..dc5d816920 100644 --- a/src/client.h +++ b/src/client.h @@ -240,6 +240,9 @@ class CClient : public QObject void SetInputBoost ( const int iNewBoost ) { iInputBoost = iNewBoost; } + void SetBuiltInMicId ( const int iNewMicId ) { iBuiltInMicId = iNewMicId; } + int GetBuiltInMicId() { return iBuiltInMicId; } + void SetRemoteInfo() { Channel.SetRemoteInfo ( ChannelInfo ); } void CreateChatTextMes ( const QString& strChatText ) { Channel.CreateChatTextMes ( strChatText ); } @@ -264,6 +267,8 @@ class CClient : public QObject Channel.GetBufErrorRates ( vecErrRates, dLimit, dMaxUpLimit ); } + void SetInputDeviceId ( const int deviceid ); // for mobile devices - 0 for external devices + // settings CChannelCoreInfo ChannelInfo; QString strClientName; @@ -324,6 +329,7 @@ class CClient : public QObject int iReverbLevel; CAudioReverb AudioReverb; int iInputBoost; + int iBuiltInMicId; int iSndCrdPrefFrameSizeFactor; int iSndCrdFrameSizeFactor; diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp index b34d9e741c..619e17763a 100644 --- a/src/clientdlg.cpp +++ b/src/clientdlg.cpp @@ -375,6 +375,17 @@ CClientDlg::CClientDlg ( CClient* pNCliP, pMenu->addMenu ( pEditMenu ); pMenu->addMenu ( new CHelpMenu ( true, this ) ); +#if defined( Q_OS_IOS ) or defined( Q_OS_ANDROID ) or defined( ANDROID ) + // Mobile input -------------------------------------------------------------- + QMenu* pMobileMenu = new QMenu ( tr ( "&Mobile input" ), this ); + + pMobileMenu->addAction ( tr ( "Builtin Mic" ), this, SLOT ( setBuiltinMic() ) ); + + pMobileMenu->addAction ( tr ( "Auto" ), this, SLOT ( unsetBuiltinMic() ) ); + + pMenu->addMenu ( pMobileMenu ); +#endif + // Now tell the layout about the menu layout()->setMenuBar ( pMenu ); diff --git a/src/clientdlg.h b/src/clientdlg.h index 4d2ce7e182..d10774673d 100644 --- a/src/clientdlg.h +++ b/src/clientdlg.h @@ -231,6 +231,23 @@ public slots: void accept() { close(); } // introduced by pljones + void setBuiltinMic() + { +#if defined( Q_OS_IOS ) + pClient->SetInputDeviceId ( 1 ); +#endif +#if defined( Q_OS_ANDROID ) or defined( ANDROID ) + pClient->SetInputDeviceId ( pClient->GetBuiltInMicId() ); +#endif + } + + void unsetBuiltinMic() + { +#if defined( Q_OS_IOS ) or defined( Q_OS_ANDROID ) or defined( ANDROID ) + pClient->SetInputDeviceId ( 0 ); +#endif + } + signals: void SendTabChange ( int iTabIdx ); }; diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index 4faf0b1884..20ac92d7a9 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() ) ); @@ -42,6 +42,16 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet layout()->setMenuBar ( pMenu ); #endif +#if defined( Q_OS_ANDROID ) or defined( ANDROID ) + // Android too + QMenuBar* pMenu = new QMenuBar ( this ); + QMenu* pCloseMenu = new QMenu ( tr ( "Close" ), this ); + pCloseMenu->addAction ( tr ( "Close" ), this, SLOT ( close() ) ); + pMenu->addMenu ( pCloseMenu ); + + // Now tell the layout about the menu + layout()->setMenuBar ( pMenu ); +#endif // Add help text to controls ----------------------------------------------- // local audio input fader QString strAudFader = "" + tr ( "Local Audio Input Fader" ) + ": " + @@ -325,6 +335,21 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet cbxInputBoost->setWhatsThis ( strInputBoost ); cbxInputBoost->setAccessibleName ( tr ( "Input Boost combo box" ) ); +#if defined( Q_OS_ANDROID ) or defined( ANDROID ) // Built-Mic for Android - experimental feature - requires User inputting correct deviceId + // Built-in Mic + QString strBuiltInMic = "" + tr ( "Built-in Mic Device Id" ) + ": " + tr ( "Choose Input device Id. Use LiveEffect for references." ); + lblBuiltInMicId->setWhatsThis ( strBuiltInMic ); + edtBuiltInMicId->setWhatsThis ( strBuiltInMic ); + edtBuiltInMicId->setAccessibleName ( tr ( "Built-in Mic Device Id edit box" ) ); +#else + if ( horizontalLayout_14 ) + horizontalLayout_14->deleteLater(); + if ( lblBuiltInMicId ) + lblBuiltInMicId->deleteLater(); + if ( edtBuiltInMicId ) + edtBuiltInMicId->deleteLater(); +#endif + // custom directory server address QString strCentrServAddr = "" + tr ( "Custom Directory Server Address" ) + ": " + tr ( "Leave this blank unless you need to enter the address of a directory " @@ -373,6 +398,7 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet lblUpstreamValue->setText ( "---" ); lblUpstreamUnit->setText ( "" ); edtNewClientLevel->setValidator ( new QIntValidator ( 0, 100, this ) ); // % range from 0-100 + edtBuiltInMicId->setValidator ( new QIntValidator ( 0, 1000, this ) ); // input device - from 0 to 1000 // init slider controls --- // network buffer sliders @@ -583,6 +609,9 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet // line edits QObject::connect ( edtNewClientLevel, &QLineEdit::editingFinished, this, &CClientSettingsDlg::OnNewClientLevelEditingFinished ); + // line edits + QObject::connect ( edtBuiltInMicId, &QLineEdit::editingFinished, this, &CClientSettingsDlg::OnBuiltInMicIdChanged ); + // combo boxes QObject::connect ( cbxSoundcard, static_cast ( &QComboBox::activated ), @@ -1118,3 +1147,9 @@ void CClientSettingsDlg::OnAudioPanValueChanged ( int value ) pClient->SetAudioInFader ( value ); UpdateAudioFaderSlider(); } + +void CClientSettingsDlg::OnBuiltInMicIdChanged() +{ + pSettings->iBuiltInMicId = edtBuiltInMicId->text().toInt(); + pClient->SetBuiltInMicId ( pSettings->iBuiltInMicId ); // change value in variable only, not yet effective +} diff --git a/src/clientsettingsdlg.h b/src/clientsettingsdlg.h index 53e05fd1bb..26c6ceac8f 100644 --- a/src/clientsettingsdlg.h +++ b/src/clientsettingsdlg.h @@ -105,6 +105,7 @@ public slots: void OnTabChanged(); void OnMakeTabChange ( int iTabIdx ); void OnAudioPanValueChanged ( int value ); + void OnBuiltInMicIdChanged(); signals: void GUIDesignChanged(); diff --git a/src/clientsettingsdlgbase.ui b/src/clientsettingsdlgbase.ui index 70e091f195..4e42dda285 100644 --- a/src/clientsettingsdlgbase.ui +++ b/src/clientsettingsdlgbase.ui @@ -1027,6 +1027,20 @@ + + + + Built-in Mic DeviceId (Android only) + + + + + + + + + + diff --git a/src/main.cpp b/src/main.cpp index fb601e74bd..41f7fdfecd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -538,6 +538,7 @@ int main ( int argc, char** argv ) QCoreApplication* pApp = new QCoreApplication ( argc, argv ); #else # if defined( Q_OS_IOS ) + bIsClient = true; // Ngocdh - client only now - TODO - maybe a switch in interface to change to Server? bUseGUI = true; // bUseMultithreading = true; diff --git a/src/settings.h b/src/settings.h index 765b6ee115..57e28b76fc 100644 --- a/src/settings.h +++ b/src/settings.h @@ -120,6 +120,7 @@ class CClientSettings : public CSettings vstrIPAddress ( MAX_NUM_SERVER_ADDR_ITEMS, "" ), iNewClientFaderLevel ( 100 ), iInputBoost ( 1 ), + iBuiltInMicId ( 0 ), iSettingsTab ( SETTING_TAB_AUDIONET ), bConnectDlgShowAllMusicians ( true ), eChannelSortType ( ST_NO_SORT ), @@ -152,6 +153,7 @@ class CClientSettings : public CSettings int iNewClientFaderLevel; int iInputBoost; int iSettingsTab; + int iBuiltInMicId; // 0 for external bool bConnectDlgShowAllMusicians; EChSortType eChannelSortType; int iNumMixerPanelRows; diff --git a/src/socket.cpp b/src/socket.cpp index 94d254920f..d8d41645f2 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -28,6 +28,11 @@ /* Implementation *************************************************************/ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP ) { + // first store parameters, in case reinit is required (mostly for iOS) + iPortNumber_ = iPortNumber; + iQosNumber_ = iQosNumber; + strServerBindIP_ = strServerBindIP; + #ifdef _WIN32 // for the Windows socket usage we have to start it up first @@ -45,6 +50,12 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const const char tos = (char) iQosNumber; // Quality of Service setsockopt ( UdpSocket, IPPROTO_IP, IP_TOS, &tos, sizeof ( tos ) ); +#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 ); @@ -115,6 +126,8 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const // Connections ------------------------------------------------------------- // it is important to do the following connections in this class since we // have a thread transition + if ( bIsInitRan ) + return; // we have different connections for client and server if ( bIsClient ) @@ -142,6 +155,8 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const QObject::connect ( this, &CSocket::ServerFull, pServer, &CServer::OnServerFull ); } + + bIsInitRan = true; // QObject::connect once only } void CSocket::Close() @@ -188,12 +203,22 @@ void CSocket::SendPacket ( const CVector& vecbySendBuf, const CHostAddr UdpSocketOutAddr.sin_port = htons ( HostAddr.iPort ); UdpSocketOutAddr.sin_addr.s_addr = htonl ( HostAddr.InetAddr.toIPv4Address() ); - sendto ( UdpSocket, - (const char*) &( (CVector) vecbySendBuf )[0], - iVecSizeOut, - 0, - (sockaddr*) &UdpSocketOutAddr, - sizeof ( sockaddr_in ) ); + if ( sendto ( UdpSocket, + (const char*) &( (CVector) vecbySendBuf )[0], + iVecSizeOut, + 0, + (sockaddr*) &UdpSocketOutAddr, + sizeof ( sockaddr_in ) ) < 0 ) + { + // qDebug("Socket send exception - mostly happens in iOS when returning from idle"); + Init ( iPortNumber_, iQosNumber_, strServerBindIP_ ); // reinit + sendto ( UdpSocket, + (const char*) &( (CVector) vecbySendBuf )[0], + iVecSizeOut, + 0, + (sockaddr*) &UdpSocketOutAddr, + sizeof ( sockaddr_in ) ); + } } } diff --git a/src/socket.h b/src/socket.h index bbf6c49880..2a248835bd 100644 --- a/src/socket.h +++ b/src/socket.h @@ -77,7 +77,11 @@ 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_; + bool bIsInitRan; #ifdef _WIN32 SOCKET UdpSocket; diff --git a/src/util.cpp b/src/util.cpp index 3ca37589d0..3502a64920 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -486,6 +486,7 @@ CAboutDlg::CAboutDlg ( QWidget* parent ) : CBaseDlg ( parent ) "

Jeroen van Veldhuizen (jeroenvv)

" "

Reinhard (reinhardwh)

" "

Stefan Menzel (menzels)

" + "

Dau Huy Ngoc (ngocdh)

" "
" + tr ( "For details on the contributions check out the " ) + "" + tr ( "Github Contributors list" ) +