FreeRDP
Loading...
Searching...
No Matches
rdpsnd_mac.m
1
24#include <freerdp/config.h>
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29
30#include <winpr/crt.h>
31#include <winpr/sysinfo.h>
32
33#include <freerdp/types.h>
34
35#include <AVFoundation/AVAudioBuffer.h>
36#include <AVFoundation/AVFoundation.h>
37
38#include "rdpsnd_main.h"
39
40typedef struct
41{
42 rdpsndDevicePlugin device;
43
44 BOOL isOpen;
45 BOOL isPlaying;
46
47 UINT32 latency;
48 AUDIO_FORMAT format;
49
50 AVAudioEngine *engine;
51 AVAudioPlayerNode *player;
52 UINT64 diff;
53} rdpsndMacPlugin;
54
55static BOOL rdpsnd_mac_set_format(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format,
56 UINT32 latency)
57{
58 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
59 if (!mac || !format)
60 return FALSE;
61
62 mac->latency = latency;
63 mac->format = *format;
64
65 audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format);
66 return TRUE;
67}
68
69static char *FormatError(OSStatus st)
70{
71 switch (st)
72 {
73 case kAudioFileUnspecifiedError:
74 return "kAudioFileUnspecifiedError";
75
76 case kAudioFileUnsupportedFileTypeError:
77 return "kAudioFileUnsupportedFileTypeError";
78
79 case kAudioFileUnsupportedDataFormatError:
80 return "kAudioFileUnsupportedDataFormatError";
81
82 case kAudioFileUnsupportedPropertyError:
83 return "kAudioFileUnsupportedPropertyError";
84
85 case kAudioFileBadPropertySizeError:
86 return "kAudioFileBadPropertySizeError";
87
88 case kAudioFilePermissionsError:
89 return "kAudioFilePermissionsError";
90
91 case kAudioFileNotOptimizedError:
92 return "kAudioFileNotOptimizedError";
93
94 case kAudioFileInvalidChunkError:
95 return "kAudioFileInvalidChunkError";
96
97 case kAudioFileDoesNotAllow64BitDataSizeError:
98 return "kAudioFileDoesNotAllow64BitDataSizeError";
99
100 case kAudioFileInvalidPacketOffsetError:
101 return "kAudioFileInvalidPacketOffsetError";
102
103 case kAudioFileInvalidFileError:
104 return "kAudioFileInvalidFileError";
105
106 case kAudioFileOperationNotSupportedError:
107 return "kAudioFileOperationNotSupportedError";
108
109 case kAudioFileNotOpenError:
110 return "kAudioFileNotOpenError";
111
112 case kAudioFileEndOfFileError:
113 return "kAudioFileEndOfFileError";
114
115 case kAudioFilePositionError:
116 return "kAudioFilePositionError";
117
118 case kAudioFileFileNotFoundError:
119 return "kAudioFileFileNotFoundError";
120
121 default:
122 return "unknown error";
123 }
124}
125
126static void rdpsnd_mac_release(rdpsndMacPlugin *mac)
127{
128 if (mac->player)
129 [mac->player release];
130 mac->player = nullptr;
131
132 if (mac->engine)
133 [mac->engine release];
134 mac->engine = nullptr;
135}
136
137static BOOL rdpsnd_mac_open(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format, UINT32 latency)
138{
139 @autoreleasepool
140 {
141 AudioDeviceID outputDeviceID;
142 UInt32 propertySize;
143 OSStatus err;
144 NSError *error;
145 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
146 AudioObjectPropertyAddress propertyAddress = {
147 kAudioHardwarePropertyDefaultOutputDevice,
148 kAudioObjectPropertyScopeGlobal,
149#if defined(MAC_OS_VERSION_12_0)
150 kAudioObjectPropertyElementMain
151#else
152 kAudioObjectPropertyElementMaster
153#endif
154 };
155
156 if (mac->isOpen)
157 return TRUE;
158
159 if (!rdpsnd_mac_set_format(device, format, latency))
160 return FALSE;
161
162 propertySize = sizeof(outputDeviceID);
163 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
164 &propertySize, &outputDeviceID);
165 if (err)
166 {
167 WLog_ERR(TAG, "AudioHardwareGetProperty: %s", FormatError(err));
168 return FALSE;
169 }
170
171 mac->engine = [[AVAudioEngine alloc] init];
172 if (!mac->engine)
173 return FALSE;
174
175 err = AudioUnitSetProperty(mac->engine.outputNode.audioUnit,
176 kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global,
177 0, &outputDeviceID, sizeof(outputDeviceID));
178 if (err)
179 {
180 rdpsnd_mac_release(mac);
181 WLog_ERR(TAG, "AudioUnitSetProperty: %s", FormatError(err));
182 return FALSE;
183 }
184
185 mac->player = [[AVAudioPlayerNode alloc] init];
186 if (!mac->player)
187 {
188 rdpsnd_mac_release(mac);
189 WLog_ERR(TAG, "AVAudioPlayerNode::init() failed");
190 return FALSE;
191 }
192
193 [mac->engine attachNode:mac->player];
194
195 [mac->engine connect:mac->player to:mac->engine.mainMixerNode format:nil];
196
197 [mac->engine prepare];
198
199 if (![mac->engine startAndReturnError:&error])
200 {
201 device->Close(device);
202 WLog_ERR(TAG, "Failed to start audio player %s",
203 [error.localizedDescription UTF8String]);
204 return FALSE;
205 }
206
207 mac->isOpen = TRUE;
208 return TRUE;
209 }
210}
211
212static void rdpsnd_mac_close(rdpsndDevicePlugin *device)
213{
214 @autoreleasepool
215 {
216 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
217
218 if (mac->isPlaying)
219 {
220 [mac->player stop];
221 mac->isPlaying = FALSE;
222 }
223
224 if (mac->isOpen)
225 {
226 [mac->engine stop];
227 mac->isOpen = FALSE;
228 }
229
230 rdpsnd_mac_release(mac);
231 }
232}
233
234static void rdpsnd_mac_free(rdpsndDevicePlugin *device)
235{
236 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
237 device->Close(device);
238 free(mac);
239}
240
241static BOOL rdpsnd_mac_format_supported(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format)
242{
243 WINPR_UNUSED(device);
244
245 switch (format->wFormatTag)
246 {
247 case WAVE_FORMAT_PCM:
248 if (format->wBitsPerSample != 16)
249 return FALSE;
250
251 if (format->nChannels != 2)
252 return FALSE;
253 return TRUE;
254
255 default:
256 return FALSE;
257 }
258}
259
260static BOOL rdpsnd_mac_set_volume(rdpsndDevicePlugin *device, UINT32 value)
261{
262 @autoreleasepool
263 {
264 Float32 fVolume;
265 UINT16 volumeLeft;
266 UINT16 volumeRight;
267 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
268
269 if (!mac->player)
270 return FALSE;
271
272 volumeLeft = (value & 0xFFFF);
273 volumeRight = ((value >> 16) & 0xFFFF);
274 fVolume = ((float)volumeLeft) / 65535.0f;
275
276 mac->player.volume = fVolume;
277
278 return TRUE;
279 }
280}
281
282static void rdpsnd_mac_start(rdpsndDevicePlugin *device)
283{
284 @autoreleasepool
285 {
286 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
287
288 if (!mac->engine.isRunning)
289 {
290 NSError *error;
291 [mac->engine connect:mac->player to:mac->engine.mainMixerNode format:nil];
292 [mac->engine prepare];
293 if (![mac->engine startAndReturnError:&error])
294 {
295 device->Close(device);
296 WLog_ERR(TAG, "Failed to start audio player %s",
297 [error.localizedDescription UTF8String]);
298 return;
299 }
300 mac->isPlaying = FALSE; /* force [player play] below */
301 }
302
303 if (!mac->isPlaying)
304 {
305 [mac->player play];
306
307 mac->isPlaying = TRUE;
308 mac->diff = 100; /* Initial latency, corrected after first sample is played. */
309 }
310 }
311}
312
313static UINT rdpsnd_mac_play(rdpsndDevicePlugin *device, const BYTE *data, size_t size)
314{
315 @autoreleasepool
316 {
317 rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
318 AVAudioPCMBuffer *buffer;
319 AVAudioFormat *format;
320 float *const *db;
321 size_t step;
322 AVAudioFrameCount count;
323 UINT64 start = GetTickCount64();
324
325 if (!mac->isOpen)
326 return 0;
327
328 step = 2 * mac->format.nChannels;
329
330 count = size / step;
331 format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
332 sampleRate:mac->format.nSamplesPerSec
333 channels:mac->format.nChannels
334 interleaved:NO];
335
336 if (!format)
337 {
338 WLog_WARN(TAG, "AVAudioFormat::init() failed");
339 return 0;
340 }
341
342 buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format frameCapacity:count];
343 [format release];
344
345 if (!buffer)
346 {
347 WLog_WARN(TAG, "AVAudioPCMBuffer::init() failed");
348 return 0;
349 }
350
351 buffer.frameLength = buffer.frameCapacity;
352 db = buffer.floatChannelData;
353
354 for (size_t pos = 0; pos < count; pos++)
355 {
356 const BYTE *d = &data[pos * step];
357 for (size_t x = 0; x < mac->format.nChannels; x++)
358 {
359 const float val = (int16_t)((uint16_t)d[0] | ((uint16_t)d[1] << 8)) / 32768.0f;
360 db[x][pos] = val;
361 d += sizeof(int16_t);
362 }
363 }
364
365 rdpsnd_mac_start(device);
366
367 [mac->player scheduleBuffer:buffer
368 completionHandler:^{
369 UINT64 stop = GetTickCount64();
370 if (start > stop)
371 mac->diff = 0;
372 else
373 mac->diff = stop - start;
374 }];
375
376 [buffer release];
377
378 return mac->diff > UINT_MAX ? UINT_MAX : mac->diff;
379 }
380}
381
387FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_rdpsnd_client_subsystem_entry(
389{
390 rdpsndMacPlugin *mac;
391 mac = (rdpsndMacPlugin *)calloc(1, sizeof(rdpsndMacPlugin));
392
393 if (!mac)
394 return CHANNEL_RC_NO_MEMORY;
395
396 mac->device.Open = rdpsnd_mac_open;
397 mac->device.FormatSupported = rdpsnd_mac_format_supported;
398 mac->device.SetVolume = rdpsnd_mac_set_volume;
399 mac->device.Play = rdpsnd_mac_play;
400 mac->device.Close = rdpsnd_mac_close;
401 mac->device.Free = rdpsnd_mac_free;
402 pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin *)mac);
403 return CHANNEL_RC_OK;
404}