FreeRDP
Loading...
Searching...
No Matches
rdpsnd_ios.c
1
22#include <freerdp/config.h>
23
24#include <winpr/wtypes.h>
25
26#include <freerdp/types.h>
27#include <freerdp/codec/dsp.h>
28
29#import <AudioToolbox/AudioToolbox.h>
30
31#include "rdpsnd_main.h"
32#include "TPCircularBuffer.h"
33
34#define INPUT_BUFFER_SIZE 32768
35#define CIRCULAR_BUFFER_SIZE (INPUT_BUFFER_SIZE * 4)
36
37typedef struct
38{
39 rdpsndDevicePlugin device;
40 AudioComponentInstance audio_unit;
41 TPCircularBuffer buffer;
42 BOOL is_opened;
43 BOOL is_playing;
44} rdpsndIOSPlugin;
45
46#define THIS(__ptr) ((rdpsndIOSPlugin*)__ptr)
47
48static OSStatus rdpsnd_ios_render_cb(void* inRefCon,
49 AudioUnitRenderActionFlags __unused* ioActionFlags,
50 const AudioTimeStamp __unused* inTimeStamp, UInt32 inBusNumber,
51 UInt32 __unused inNumberFrames, AudioBufferList* ioData)
52{
53 if (inBusNumber != 0)
54 {
55 return noErr;
56 }
57
58 rdpsndIOSPlugin* p = THIS(inRefCon);
59
60 for (unsigned int i = 0; i < ioData->mNumberBuffers; i++)
61 {
62 AudioBuffer* target_buffer = &ioData->mBuffers[i];
63 int32_t available_bytes = 0;
64 const void* buffer = TPCircularBufferTail(&p->buffer, &available_bytes);
65
66 if (buffer != NULL && available_bytes > 0)
67 {
68 const int bytes_to_copy = MIN((int32_t)target_buffer->mDataByteSize, available_bytes);
69 memcpy(target_buffer->mData, buffer, bytes_to_copy);
70 target_buffer->mDataByteSize = bytes_to_copy;
71 TPCircularBufferConsume(&p->buffer, bytes_to_copy);
72 }
73 else
74 {
75 target_buffer->mDataByteSize = 0;
76 AudioOutputUnitStop(p->audio_unit);
77 p->is_playing = 0;
78 }
79 }
80
81 return noErr;
82}
83
84static BOOL rdpsnd_ios_format_supported(rdpsndDevicePlugin* __unused device,
85 const AUDIO_FORMAT* format)
86{
87 if (format->wFormatTag == WAVE_FORMAT_PCM)
88 {
89 return 1;
90 }
91
92 return 0;
93}
94
95static BOOL rdpsnd_ios_set_volume(rdpsndDevicePlugin* __unused device, UINT32 __unused value)
96{
97 return TRUE;
98}
99
100static void rdpsnd_ios_start(rdpsndDevicePlugin* device)
101{
102 rdpsndIOSPlugin* p = THIS(device);
103
104 /* If this device is not playing... */
105 if (!p->is_playing)
106 {
107 /* Start the device. */
108 int32_t available_bytes = 0;
109 TPCircularBufferTail(&p->buffer, &available_bytes);
110
111 if (available_bytes > 0)
112 {
113 p->is_playing = 1;
114 AudioOutputUnitStart(p->audio_unit);
115 }
116 }
117}
118
119static void rdpsnd_ios_stop(rdpsndDevicePlugin* __unused device)
120{
121 rdpsndIOSPlugin* p = THIS(device);
122
123 /* If the device is playing... */
124 if (p->is_playing)
125 {
126 /* Stop the device. */
127 AudioOutputUnitStop(p->audio_unit);
128 p->is_playing = 0;
129 /* Free all buffers. */
130 TPCircularBufferClear(&p->buffer);
131 }
132}
133
134static UINT rdpsnd_ios_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
135{
136 rdpsndIOSPlugin* p = THIS(device);
137 const BOOL ok = TPCircularBufferProduceBytes(&p->buffer, data, size);
138
139 if (!ok)
140 return 0;
141
142 rdpsnd_ios_start(device);
143 return 10; /* TODO: Get real latencry in [ms] */
144}
145
146static BOOL rdpsnd_ios_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
147 UINT32 __unused latency)
148{
149 rdpsndIOSPlugin* p = THIS(device);
150
151 if (p->is_opened)
152 return TRUE;
153
154 /* Find the output audio unit. */
155 AudioComponentDescription desc;
156 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
157 desc.componentType = kAudioUnitType_Output;
158 desc.componentSubType = kAudioUnitSubType_RemoteIO;
159 desc.componentFlags = 0;
160 desc.componentFlagsMask = 0;
161 AudioComponent audioComponent = AudioComponentFindNext(NULL, &desc);
162
163 if (audioComponent == NULL)
164 return FALSE;
165
166 /* Open the audio unit. */
167 OSStatus status = AudioComponentInstanceNew(audioComponent, &p->audio_unit);
168
169 if (status != 0)
170 return FALSE;
171
172 /* Set the format for the AudioUnit. */
173 AudioStreamBasicDescription audioFormat = { 0 };
174 audioFormat.mSampleRate = format->nSamplesPerSec;
175 audioFormat.mFormatID = kAudioFormatLinearPCM;
176 audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
177 audioFormat.mFramesPerPacket = 1; /* imminent property of the Linear PCM */
178 audioFormat.mChannelsPerFrame = format->nChannels;
179 audioFormat.mBitsPerChannel = format->wBitsPerSample;
180 audioFormat.mBytesPerFrame = (format->wBitsPerSample * format->nChannels) / 8;
181 audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
182 status = AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_StreamFormat,
183 kAudioUnitScope_Input, 0, &audioFormat, sizeof(audioFormat));
184
185 if (status != 0)
186 {
187 AudioComponentInstanceDispose(p->audio_unit);
188 p->audio_unit = NULL;
189 return FALSE;
190 }
191
192 /* Set up the AudioUnit callback. */
193 AURenderCallbackStruct callbackStruct = { 0 };
194 callbackStruct.inputProc = rdpsnd_ios_render_cb;
195 callbackStruct.inputProcRefCon = p;
196 status =
197 AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_SetRenderCallback,
198 kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct));
199
200 if (status != 0)
201 {
202 AudioComponentInstanceDispose(p->audio_unit);
203 p->audio_unit = NULL;
204 return FALSE;
205 }
206
207 /* Initialize the AudioUnit. */
208 status = AudioUnitInitialize(p->audio_unit);
209
210 if (status != 0)
211 {
212 AudioComponentInstanceDispose(p->audio_unit);
213 p->audio_unit = NULL;
214 return FALSE;
215 }
216
217 /* Allocate the circular buffer. */
218 const BOOL ok = TPCircularBufferInit(&p->buffer, CIRCULAR_BUFFER_SIZE);
219
220 if (!ok)
221 {
222 AudioUnitUninitialize(p->audio_unit);
223 AudioComponentInstanceDispose(p->audio_unit);
224 p->audio_unit = NULL;
225 return FALSE;
226 }
227
228 p->is_opened = 1;
229 return TRUE;
230}
231
232static void rdpsnd_ios_close(rdpsndDevicePlugin* device)
233{
234 rdpsndIOSPlugin* p = THIS(device);
235 /* Make sure the device is stopped. */
236 rdpsnd_ios_stop(device);
237
238 /* If the device is open... */
239 if (p->is_opened)
240 {
241 /* Close the device. */
242 AudioUnitUninitialize(p->audio_unit);
243 AudioComponentInstanceDispose(p->audio_unit);
244 p->audio_unit = NULL;
245 p->is_opened = 0;
246 /* Destroy the circular buffer. */
247 TPCircularBufferCleanup(&p->buffer);
248 }
249}
250
251static void rdpsnd_ios_free(rdpsndDevicePlugin* device)
252{
253 rdpsndIOSPlugin* p = THIS(device);
254 /* Ensure the device is closed. */
255 rdpsnd_ios_close(device);
256 /* Free memory associated with the device. */
257 free(p);
258}
259
265FREERDP_ENTRY_POINT(UINT VCAPITYPE ios_freerdp_rdpsnd_client_subsystem_entry(
267{
268 rdpsndIOSPlugin* p = (rdpsndIOSPlugin*)calloc(1, sizeof(rdpsndIOSPlugin));
269
270 if (!p)
271 return CHANNEL_RC_NO_MEMORY;
272
273 p->device.Open = rdpsnd_ios_open;
274 p->device.FormatSupported = rdpsnd_ios_format_supported;
275 p->device.SetVolume = rdpsnd_ios_set_volume;
276 p->device.Play = rdpsnd_ios_play;
277 p->device.Start = rdpsnd_ios_start;
278 p->device.Close = rdpsnd_ios_close;
279 p->device.Free = rdpsnd_ios_free;
280 pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)p);
281 return CHANNEL_RC_OK;
282}