Skip to content

Commit 28e2f13

Browse files
committed
test: replace SDL with system audio engine to support audio playing when screen is dimmed out
1 parent 4a9e109 commit 28e2f13

3 files changed

Lines changed: 89 additions & 18 deletions

File tree

VoidLink/Input/MicHandler.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,11 @@ public class MicHandler: NSObject {
185185

186186
private func configureSession() throws {
187187
let session = AVAudioSession.sharedInstance()
188-
try session.setCategory(.playAndRecord, mode: .videoRecording, options: [.mixWithOthers, .defaultToSpeaker, .allowBluetooth])
188+
let bluetoothAudioOption = self.useBuiltinMic ? AVAudioSession.CategoryOptions.allowBluetoothA2DP : AVAudioSession.CategoryOptions.allowBluetooth
189+
try session.setCategory(.playAndRecord, mode: .videoRecording, options: [.mixWithOthers, .defaultToSpeaker, bluetoothAudioOption])
189190
if #available(iOS 13.0, *) {
190191
try session.setAllowHapticsAndSystemSoundsDuringRecording(true)
191192
}
192-
try session.setActive(true)
193193

194194
// 列出所有可用输入
195195
if self.useBuiltinMic, let inputs = session.availableInputs {
@@ -201,6 +201,7 @@ public class MicHandler: NSObject {
201201
}
202202
}
203203
}
204+
try session.setActive(true)
204205
}
205206

206207
private func sendOpusFrameFromDequeBuffer() {

VoidLink/Stream/Connection.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
// Created by Diego Waxemberg on 1/19/14.
66
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
77
//
8+
// Modified by True砖家 since 2025.9
9+
// Copyright © 2025 True砖家 on Bilibili. All rights reserved.
10+
//
11+
812

913
#import "ConnectionCallbacks.h"
1014
#import "VideoDecoderRenderer.h"
@@ -23,5 +27,6 @@
2327
-(BOOL) getVideoStats:(video_stats_t*)stats;
2428
-(NSString*) getActiveCodecName;
2529
+ (void)setVolume:(float)newVolume;
30+
+ (void)setUseSystemAudioEngine:(bool)useSysAudioEngine;
2631

2732
@end

VoidLink/Stream/Connection.m

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
// Created by Diego Waxemberg on 1/19/14.
66
// Copyright (c) 2015 Moonlight Stream. All rights reserved.
77
//
8+
// Modified by True砖家 since 2025.9
9+
// Copyright © 2025 True砖家 on Bilibili. All rights reserved.
10+
//
811

912
#import "Connection.h"
1013
#import "Plot.h"
@@ -47,6 +50,12 @@ @implementation Connection {
4750
static float volume = 1.0;
4851
static int audioFrameSize;
4952

53+
static bool useSystemAudioEngine;
54+
static AVAudioEngine *audioEngine;
55+
static AVAudioPlayerNode *audioPlayerNode;
56+
static AVAudioPCMBuffer *pcmBuffer;
57+
static AVAudioFormat *audioFormat;
58+
5059
static VideoDecoderRenderer* renderer;
5160

5261
static BandwidthTracker *bwTracker;
@@ -276,10 +285,20 @@ int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, v
276285
SDL_PauseAudioDevice(audioDevice, 0);
277286

278287
// Disable lowering volume of other audio streams (SDL sets AVAudioSessionCategoryOptionDuckOthers by default)
279-
280-
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
281-
288+
// AVAudioSessionCategoryOptionAllowBluetoothA2DP
289+
// AVAudioSessionCategoryOptionAllowBluetooth
290+
DataManager* dataMan = [[DataManager alloc] init];
291+
TemporarySettings* tempSettings = [dataMan getSettings];
292+
bool useBluetoothD2P = tempSettings.useBuiltinMic || !tempSettings.redirectMic;
293+
AVAudioSessionCategoryOptions bluetoothAudioOption = useBluetoothD2P ? AVAudioSessionCategoryOptionAllowBluetoothA2DP : AVAudioSessionCategoryOptionAllowBluetooth;
294+
AVAudioSession *session = [AVAudioSession sharedInstance];
295+
[session setCategory:AVAudioSessionCategoryPlayAndRecord
296+
withOptions:AVAudioSessionCategoryOptionMixWithOthers|AVAudioSessionCategoryOptionDefaultToSpeaker|bluetoothAudioOption
297+
error:nil];
298+
[session setActive:YES error:nil];
282299

300+
AudioEngineInit(audioConfig.sampleRate, audioConfig.channelCount);
301+
283302
return 0;
284303
}
285304

@@ -310,6 +329,30 @@ + (void)setVolume:(float)linearVolume{
310329
volume = powf(linearVolume, exponent);
311330
}
312331

332+
+ (void)setUseSystemAudioEngine:(bool)useSysAudioEngine{
333+
useSystemAudioEngine = useSysAudioEngine;
334+
}
335+
336+
void AudioEngineInit(int sampleRate, int channelCount) {
337+
audioEngine = [[AVAudioEngine alloc] init];
338+
audioPlayerNode = [[AVAudioPlayerNode alloc] init];
339+
340+
[audioEngine attachNode:audioPlayerNode];
341+
342+
audioFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
343+
sampleRate:sampleRate
344+
channels:channelCount
345+
interleaved:NO];
346+
347+
[audioEngine connect:audioPlayerNode to:audioEngine.mainMixerNode format:audioFormat];
348+
349+
NSError *err = nil;
350+
if (![audioEngine startAndReturnError:&err]) {
351+
NSLog(@"AudioEngine start error: %@", err);
352+
}
353+
[audioPlayerNode play];
354+
}
355+
313356
void ArDecodeAndPlaySample(char* sampleData, int sampleLength)
314357
{
315358
int decodeLen;
@@ -333,21 +376,41 @@ void ArDecodeAndPlaySample(char* sampleData, int sampleLength)
333376

334377
float* fbuf = (float*)audioBuffer;
335378

336-
if(volume != 1.0){
337-
int totalSamples = decodeLen * audioConfig.channelCount;
338-
for (int i = 0; i < totalSamples; i++) {
339-
fbuf[i] *= volume;
379+
// if(useSystemAudioEngine && false) {
380+
if(true){
381+
// 创建 AVAudioPCMBuffer
382+
AVAudioFrameCount frameCount = decodeLen;
383+
AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:audioFormat frameCapacity:frameCount];
384+
buffer.frameLength = frameCount;
385+
386+
// 拷贝数据到 buffer
387+
for (int ch = 0; ch < audioConfig.channelCount; ch++) {
388+
float *dst = buffer.floatChannelData[ch];
389+
for (int i = 0; i < decodeLen; i++) {
390+
dst[i] = fbuf[i * audioConfig.channelCount + ch] * volume; // 非交错数据
391+
}
340392
}
393+
// 播放
394+
[audioPlayerNode scheduleBuffer:buffer completionHandler:nil];
341395
}
342396

343-
while (SDL_GetQueuedAudioSize(audioDevice) / audioFrameSize > 10) {
344-
[NSThread sleepForTimeInterval:0.001f];
345-
}
346-
347-
if (SDL_QueueAudio(audioDevice,
348-
audioBuffer,
349-
sizeof(float) * decodeLen * audioConfig.channelCount) < 0) {
350-
Log(LOG_E, @"Failed to queue audio sample: %s\n", SDL_GetError());
397+
else{
398+
if(volume != 1.0){
399+
int totalSamples = decodeLen * audioConfig.channelCount;
400+
for (int i = 0; i < totalSamples; i++) {
401+
fbuf[i] *= volume;
402+
}
403+
}
404+
405+
while (SDL_GetQueuedAudioSize(audioDevice) / audioFrameSize > 10) {
406+
[NSThread sleepForTimeInterval:0.001f];
407+
}
408+
409+
if (SDL_QueueAudio(audioDevice,
410+
audioBuffer,
411+
sizeof(float) * decodeLen * audioConfig.channelCount) < 0) {
412+
Log(LOG_E, @"Failed to queue audio sample: %s\n", SDL_GetError());
413+
}
351414
}
352415
}
353416
}
@@ -423,7 +486,9 @@ -(void) terminate
423486
// won't be able to acquire it if LiStartConnection is in
424487
// progress.
425488
LiInterruptConnection();
426-
489+
[audioPlayerNode stop];
490+
[audioEngine stop];
491+
427492
// We dispatch this async to get out because this can be invoked
428493
// on a thread inside common and we don't want to deadlock. It also avoids
429494
// blocking on the caller's thread waiting to acquire initLock.

0 commit comments

Comments
 (0)