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 {
4750static float volume = 1.0 ;
4851static int audioFrameSize;
4952
53+ static bool useSystemAudioEngine;
54+ static AVAudioEngine *audioEngine;
55+ static AVAudioPlayerNode *audioPlayerNode;
56+ static AVAudioPCMBuffer *pcmBuffer;
57+ static AVAudioFormat *audioFormat;
58+
5059static VideoDecoderRenderer* renderer;
5160
5261static 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+
313356void 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