FreeRDP
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 
37 typedef 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 
48 static 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 
84 static 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 
95 static BOOL rdpsnd_ios_set_volume(rdpsndDevicePlugin* __unused device, UINT32 __unused value)
96 {
97  return TRUE;
98 }
99 
100 static 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 
119 static 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 
134 static 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 
146 static 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 
232 static 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 
251 static 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 
265 FREERDP_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 }
Definition: client/rdpsnd.h:76