mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 12:07:31 +01:00
Merge pull request #22727 from hrydgard/ios-airplay
iOS: AirPlay audio fixes, stuck touch and exit crash
This commit is contained in:
7 files changed
+59
-10
No files matched your search
@@ -82,6 +82,14 @@ __attribute__((used)) static Class _forceLinkSceneDelegate = [SceneDelegate clas
|
||||
iOSCoreAudioInit();
|
||||
}
|
||||
|
||||
// for AVAudioSessionRouteChangeNotification
|
||||
-(void) handleAudioRouteChange:(NSNotification *)notification {
|
||||
NSNumber *reason = notification.userInfo[AVAudioSessionRouteChangeReasonKey];
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "change, reason %d", reason.intValue);
|
||||
iOSCoreAudioLogRoute(buf);
|
||||
}
|
||||
|
||||
-(BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
self.launchOptions = launchOptions;
|
||||
|
||||
@@ -91,6 +99,7 @@ __attribute__((used)) static Class _forceLinkSceneDelegate = [SceneDelegate clas
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioRouteChange:) name:AVAudioSessionRouteChangeNotification object:nil];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ public:
|
||||
void Moved(NSSet *touches, UIView *view);
|
||||
void Ended(NSSet *touches, UIView *view);
|
||||
void Cancelled(NSSet *touches, UIView *view);
|
||||
// Lifts every touch still down. UIKit doesn't always end the touches that pull down Control Center.
|
||||
void CancelAll();
|
||||
private:
|
||||
void SendTouchEvent(CGPoint point, UIView *view, int code, int pointerId);
|
||||
int ToTouchID(UITouch *uiTouch, bool allowAllocate);
|
||||
|
||||
@@ -381,6 +381,18 @@ void TouchTracker::Cancelled(NSSet *touches, UIView *view) {
|
||||
}
|
||||
}
|
||||
|
||||
void TouchTracker::CancelAll() {
|
||||
for (int touchId = 0; touchId < (int)ARRAY_SIZE(touches_); ++touchId) {
|
||||
if (touches_[touchId]) {
|
||||
TouchInput input{};
|
||||
input.flags = TouchInputFlags::UP | TouchInputFlags::CANCEL;
|
||||
input.id = touchId;
|
||||
NativeTouch(input);
|
||||
touches_[touchId] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
void ProcessAccelerometerData(CMAccelerometerData *accData) {
|
||||
CMAcceleration acc = accData.acceleration;
|
||||
|
||||
@@ -337,6 +337,10 @@ static int GetPickerRequestId(id picker) {
|
||||
- (void)willResignActive {
|
||||
[self requestExitVulkanRenderLoop];
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
g_touchTracker.CancelAll();
|
||||
#endif
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
// Stop accelerometer updates
|
||||
if (self.motionManager.accelerometerActive) {
|
||||
|
||||
@@ -25,6 +25,9 @@ void iOSCoreAudioShutdown();
|
||||
// Applies the audio settings (mix with others, respect silent mode) to the session.
|
||||
void iOSCoreAudioUpdateSession();
|
||||
|
||||
// Logs where the session's output currently goes (speaker, headphones, AirPlay...).
|
||||
void iOSCoreAudioLogRoute(const char *reason);
|
||||
|
||||
// Microphone input, for sceUsbMic. These are called on the CPU thread. Input is buffered, and
|
||||
// iOSCoreAudioPollRecording hands it over to Microphone::addAudioData.
|
||||
void iOSCoreAudioStartRecording(int sampleRate);
|
||||
|
||||
+23
-4
@@ -65,13 +65,17 @@ void iOSCoreAudioUpdateSession() {
|
||||
#else
|
||||
// Hacky hack to force iOS to re-evaluate.
|
||||
// Switching from CatogoryPlayback to CategoryPlayback with an option otherwise does nothing.
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAudioProcessing error:&error];
|
||||
NSError *hackError = nil;
|
||||
if (![[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAudioProcessing error:&hackError]) {
|
||||
NSLog(@"Switching to AudioProcessing failed: %@", hackError);
|
||||
}
|
||||
|
||||
// Here, we apply the settings.
|
||||
if (g_micEnabled) {
|
||||
// Recording needs PlayAndRecord, which ignores the silent switch. A2DP rather than the
|
||||
// hands-free profile keeps Bluetooth headphones at full quality, with the built-in mic as input.
|
||||
AVAudioSessionCategoryOptions options = AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetoothA2DP;
|
||||
// Unlike the playback categories, PlayAndRecord needs to be told to allow AirPlay.
|
||||
AVAudioSessionCategoryOptions options = AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetoothA2DP | AVAudioSessionCategoryOptionAllowAirPlay;
|
||||
if (g_Config.bAudioMixWithOthers) {
|
||||
options |= AVAudioSessionCategoryOptionMixWithOthers;
|
||||
}
|
||||
@@ -93,7 +97,17 @@ void iOSCoreAudioUpdateSession() {
|
||||
#endif
|
||||
|
||||
if (error) {
|
||||
NSLog(@"%@", error);
|
||||
NSLog(@"Setting the audio session category failed: %@", error);
|
||||
}
|
||||
}
|
||||
|
||||
void iOSCoreAudioLogRoute(const char *reason) {
|
||||
AVAudioSession *session = [AVAudioSession sharedInstance];
|
||||
for (AVAudioSessionPortDescription *port in session.currentRoute.outputs) {
|
||||
NSLog(@"Audio route (%s): %@ '%@' (category %@, options %d)", reason, port.portType, port.portName, session.category, (int)session.categoryOptions);
|
||||
}
|
||||
for (AVAudioSessionPortDescription *port in session.currentRoute.inputs) {
|
||||
NSLog(@"Audio input (%s): %@ '%@'", reason, port.portType, port.portName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +218,8 @@ void iOSCoreAudioInit() {
|
||||
}
|
||||
}
|
||||
|
||||
iOSCoreAudioLogRoute("init");
|
||||
|
||||
if (audioInstance) {
|
||||
// Already running
|
||||
return;
|
||||
@@ -307,14 +323,15 @@ void iOSCoreAudioShutdown()
|
||||
|
||||
// Brings the unit in line with g_micEnabled. Main thread.
|
||||
static void UpdateMicrophone() {
|
||||
iOSCoreAudioUpdateSession();
|
||||
if (!audioInstance) {
|
||||
// Sets up the session too.
|
||||
iOSCoreAudioInit();
|
||||
return;
|
||||
}
|
||||
if (!g_Config.bEnableSound && !g_micEnabled) {
|
||||
// The unit was only running for the microphone.
|
||||
iOSCoreAudioShutdown();
|
||||
iOSCoreAudioUpdateSession();
|
||||
return;
|
||||
}
|
||||
if (g_micEnabled == g_micInputOn && (!g_micEnabled || g_micSampleRate == g_micInputRate)) {
|
||||
@@ -323,6 +340,8 @@ static void UpdateMicrophone() {
|
||||
|
||||
AudioOutputUnitStop(audioInstance);
|
||||
AudioUnitUninitialize(audioInstance);
|
||||
// Only with the unit stopped: iOS refuses (with '!pri') to leave PlayAndRecord while the input runs.
|
||||
iOSCoreAudioUpdateSession();
|
||||
ConfigureInput(audioInstance, g_micEnabled);
|
||||
OSStatus err = AudioUnitInitialize(audioInstance);
|
||||
if (err == noErr) {
|
||||
|
||||
+6
-6
@@ -506,12 +506,12 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
|
||||
}
|
||||
case SystemRequestType::EXIT_APP:
|
||||
// NOTE: on iOS, this is considered a crash and not a valid way to exit.
|
||||
exit(0);
|
||||
// The below seems right, but causes hangs. See #12140.
|
||||
// dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// [sharedViewController shutdown];
|
||||
// exit(0);
|
||||
// });
|
||||
// We're on the render thread. Calling exit() here would destroy its still joinable std::thread,
|
||||
// which aborts, so let the main thread join it first.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[sharedViewController shutdown];
|
||||
exit(0);
|
||||
});
|
||||
break;
|
||||
case SystemRequestType::BROWSE_FOR_FILE:
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user