summaryrefslogtreecommitdiffstats
path: root/channels/rdpsnd/client/ios
diff options
context:
space:
mode:
Diffstat (limited to 'channels/rdpsnd/client/ios')
-rw-r--r--channels/rdpsnd/client/ios/CMakeLists.txt40
-rw-r--r--channels/rdpsnd/client/ios/TPCircularBuffer.c153
-rw-r--r--channels/rdpsnd/client/ios/TPCircularBuffer.h217
-rw-r--r--channels/rdpsnd/client/ios/rdpsnd_ios.c282
4 files changed, 692 insertions, 0 deletions
diff --git a/channels/rdpsnd/client/ios/CMakeLists.txt b/channels/rdpsnd/client/ios/CMakeLists.txt
new file mode 100644
index 0000000..bfb3903
--- /dev/null
+++ b/channels/rdpsnd/client/ios/CMakeLists.txt
@@ -0,0 +1,40 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Laxmikant Rashinkar <LK.Rashinkar@gmail.com>
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+# Copyright 2013 Corey Clayton <can.of.tuna@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("rdpsnd" "ios" "")
+
+FIND_LIBRARY(CORE_AUDIO CoreAudio)
+FIND_LIBRARY(AUDIO_TOOL AudioToolbox)
+FIND_LIBRARY(CORE_FOUNDATION CoreFoundation)
+
+set(${MODULE_PREFIX}_SRCS
+ rdpsnd_ios.c
+ TPCircularBuffer.c)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+ freerdp
+ ${AUDIO_TOOL}
+ ${CORE_AUDIO}
+ ${CORE_FOUNDATION}
+)
+
+include_directories(..)
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/rdpsnd/client/ios/TPCircularBuffer.c b/channels/rdpsnd/client/ios/TPCircularBuffer.c
new file mode 100644
index 0000000..b29f611
--- /dev/null
+++ b/channels/rdpsnd/client/ios/TPCircularBuffer.c
@@ -0,0 +1,153 @@
+//
+// TPCircularBuffer.c
+// Circular/Ring buffer implementation
+//
+// https://github.com/michaeltyson/TPCircularBuffer
+//
+// Created by Michael Tyson on 10/12/2011.
+//
+// Copyright (C) 2012-2013 A Tasty Pixel
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#include <winpr/wlog.h>
+
+#include "TPCircularBuffer.h"
+#include "rdpsnd_main.h"
+
+#include <mach/mach.h>
+#include <stdio.h>
+
+#define reportResult(result, operation) (_reportResult((result), (operation), __FILE__, __LINE__))
+static inline bool _reportResult(kern_return_t result, const char* operation, const char* file,
+ int line)
+{
+ if (result != ERR_SUCCESS)
+ {
+ WLog_DBG(TAG, "%s:%d: %s: %s\n", file, line, operation, mach_error_string(result));
+ return false;
+ }
+ return true;
+}
+
+bool TPCircularBufferInit(TPCircularBuffer* buffer, int length)
+{
+
+ // Keep trying until we get our buffer, needed to handle race conditions
+ int retries = 3;
+ while (true)
+ {
+
+ buffer->length = round_page(length); // We need whole page sizes
+
+ // Temporarily allocate twice the length, so we have the contiguous address space to
+ // support a second instance of the buffer directly after
+ vm_address_t bufferAddress;
+ kern_return_t result = vm_allocate(mach_task_self(), &bufferAddress, buffer->length * 2,
+ VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit
+ if (result != ERR_SUCCESS)
+ {
+ if (retries-- == 0)
+ {
+ reportResult(result, "Buffer allocation");
+ return false;
+ }
+ // Try again if we fail
+ continue;
+ }
+
+ // Now replace the second half of the allocation with a virtual copy of the first half.
+ // Deallocate the second half...
+ result = vm_deallocate(mach_task_self(), bufferAddress + buffer->length, buffer->length);
+ if (result != ERR_SUCCESS)
+ {
+ if (retries-- == 0)
+ {
+ reportResult(result, "Buffer deallocation");
+ return false;
+ }
+ // If this fails somehow, deallocate the whole region and try again
+ vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
+ continue;
+ }
+
+ // Re-map the buffer to the address space immediately after the buffer
+ vm_address_t virtualAddress = bufferAddress + buffer->length;
+ vm_prot_t cur_prot, max_prot;
+ result = vm_remap(mach_task_self(),
+ &virtualAddress, // mirror target
+ buffer->length, // size of mirror
+ 0, // auto alignment
+ 0, // force remapping to virtualAddress
+ mach_task_self(), // same task
+ bufferAddress, // mirror source
+ 0, // MAP READ-WRITE, NOT COPY
+ &cur_prot, // unused protection struct
+ &max_prot, // unused protection struct
+ VM_INHERIT_DEFAULT);
+ if (result != ERR_SUCCESS)
+ {
+ if (retries-- == 0)
+ {
+ reportResult(result, "Remap buffer memory");
+ return false;
+ }
+ // If this remap failed, we hit a race condition, so deallocate and try again
+ vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
+ continue;
+ }
+
+ if (virtualAddress != bufferAddress + buffer->length)
+ {
+ // If the memory is not contiguous, clean up both allocated buffers and try again
+ if (retries-- == 0)
+ {
+ WLog_DBG(TAG, "Couldn't map buffer memory to end of buffer");
+ return false;
+ }
+
+ vm_deallocate(mach_task_self(), virtualAddress, buffer->length);
+ vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
+ continue;
+ }
+
+ buffer->buffer = (void*)bufferAddress;
+ buffer->fillCount = 0;
+ buffer->head = buffer->tail = 0;
+
+ return true;
+ }
+ return false;
+}
+
+void TPCircularBufferCleanup(TPCircularBuffer* buffer)
+{
+ vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2);
+ memset(buffer, 0, sizeof(TPCircularBuffer));
+}
+
+void TPCircularBufferClear(TPCircularBuffer* buffer)
+{
+ int32_t fillCount;
+ if (TPCircularBufferTail(buffer, &fillCount))
+ {
+ TPCircularBufferConsume(buffer, fillCount);
+ }
+}
diff --git a/channels/rdpsnd/client/ios/TPCircularBuffer.h b/channels/rdpsnd/client/ios/TPCircularBuffer.h
new file mode 100644
index 0000000..97e1095
--- /dev/null
+++ b/channels/rdpsnd/client/ios/TPCircularBuffer.h
@@ -0,0 +1,217 @@
+//
+// TPCircularBuffer.h
+// Circular/Ring buffer implementation
+//
+// https://github.com/michaeltyson/TPCircularBuffer
+//
+// Created by Michael Tyson on 10/12/2011.
+//
+//
+// This implementation makes use of a virtual memory mapping technique that inserts a virtual copy
+// of the buffer memory directly after the buffer's end, negating the need for any buffer
+// wrap-around logic. Clients can simply use the returned memory address as if it were contiguous
+// space.
+//
+// The implementation is thread-safe in the case of a single producer and single consumer.
+//
+// Virtual memory technique originally proposed by Philip Howard (http://vrb.slashusr.org/), and
+// adapted to Darwin by Kurt Revis (http://www.snoize.com,
+// http://www.snoize.com/Code/PlayBufferedSoundFile.tar.gz)
+//
+//
+// Copyright (C) 2012-2013 A Tasty Pixel
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#ifndef TPCircularBuffer_h
+#define TPCircularBuffer_h
+
+#include <libkern/OSAtomic.h>
+#include <string.h>
+#include <winpr/assert.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ void* buffer;
+ int32_t length;
+ int32_t tail;
+ int32_t head;
+ volatile int32_t fillCount;
+ } TPCircularBuffer;
+
+ /*!
+ * Initialise buffer
+ *
+ * Note that the length is advisory only: Because of the way the
+ * memory mirroring technique works, the true buffer length will
+ * be multiples of the device page size (e.g. 4096 bytes)
+ *
+ * @param buffer Circular buffer
+ * @param length Length of buffer
+ */
+ bool TPCircularBufferInit(TPCircularBuffer* buffer, int32_t length);
+
+ /*!
+ * Cleanup buffer
+ *
+ * Releases buffer resources.
+ */
+ void TPCircularBufferCleanup(TPCircularBuffer* buffer);
+
+ /*!
+ * Clear buffer
+ *
+ * Resets buffer to original, empty state.
+ *
+ * This is safe for use by consumer while producer is accessing
+ * buffer.
+ */
+ void TPCircularBufferClear(TPCircularBuffer* buffer);
+
+ // Reading (consuming)
+
+ /*!
+ * Access end of buffer
+ *
+ * This gives you a pointer to the end of the buffer, ready
+ * for reading, and the number of available bytes to read.
+ *
+ * @param buffer Circular buffer
+ * @param availableBytes On output, the number of bytes ready for reading
+ * @return Pointer to the first bytes ready for reading, or NULL if buffer is empty
+ */
+ static __inline__ __attribute__((always_inline)) void*
+ TPCircularBufferTail(TPCircularBuffer* buffer, int32_t* availableBytes)
+ {
+ *availableBytes = buffer->fillCount;
+ if (*availableBytes == 0)
+ return NULL;
+ return (void*)((char*)buffer->buffer + buffer->tail);
+ }
+
+ /*!
+ * Consume bytes in buffer
+ *
+ * This frees up the just-read bytes, ready for writing again.
+ *
+ * @param buffer Circular buffer
+ * @param amount Number of bytes to consume
+ */
+ static __inline__ __attribute__((always_inline)) void
+ TPCircularBufferConsume(TPCircularBuffer* buffer, int32_t amount)
+ {
+ buffer->tail = (buffer->tail + amount) % buffer->length;
+ OSAtomicAdd32Barrier(-amount, &buffer->fillCount);
+ WINPR_ASSERT(buffer->fillCount >= 0);
+ }
+
+ /*!
+ * Version of TPCircularBufferConsume without the memory barrier, for more optimal use in
+ * single-threaded contexts
+ */
+ static __inline__ __attribute__((always_inline)) void
+ TPCircularBufferConsumeNoBarrier(TPCircularBuffer* buffer, int32_t amount)
+ {
+ buffer->tail = (buffer->tail + amount) % buffer->length;
+ buffer->fillCount -= amount;
+ WINPR_ASSERT(buffer->fillCount >= 0);
+ }
+
+ /*!
+ * Access front of buffer
+ *
+ * This gives you a pointer to the front of the buffer, ready
+ * for writing, and the number of available bytes to write.
+ *
+ * @param buffer Circular buffer
+ * @param availableBytes On output, the number of bytes ready for writing
+ * @return Pointer to the first bytes ready for writing, or NULL if buffer is full
+ */
+ static __inline__ __attribute__((always_inline)) void*
+ TPCircularBufferHead(TPCircularBuffer* buffer, int32_t* availableBytes)
+ {
+ *availableBytes = (buffer->length - buffer->fillCount);
+ if (*availableBytes == 0)
+ return NULL;
+ return (void*)((char*)buffer->buffer + buffer->head);
+ }
+
+ // Writing (producing)
+
+ /*!
+ * Produce bytes in buffer
+ *
+ * This marks the given section of the buffer ready for reading.
+ *
+ * @param buffer Circular buffer
+ * @param amount Number of bytes to produce
+ */
+ static __inline__ __attribute__((always_inline)) void
+ TPCircularBufferProduce(TPCircularBuffer* buffer, int amount)
+ {
+ buffer->head = (buffer->head + amount) % buffer->length;
+ OSAtomicAdd32Barrier(amount, &buffer->fillCount);
+ WINPR_ASSERT(buffer->fillCount <= buffer->length);
+ }
+
+ /*!
+ * Version of TPCircularBufferProduce without the memory barrier, for more optimal use in
+ * single-threaded contexts
+ */
+ static __inline__ __attribute__((always_inline)) void
+ TPCircularBufferProduceNoBarrier(TPCircularBuffer* buffer, int amount)
+ {
+ buffer->head = (buffer->head + amount) % buffer->length;
+ buffer->fillCount += amount;
+ WINPR_ASSERT(buffer->fillCount <= buffer->length);
+ }
+
+ /*!
+ * Helper routine to copy bytes to buffer
+ *
+ * This copies the given bytes to the buffer, and marks them ready for writing.
+ *
+ * @param buffer Circular buffer
+ * @param src Source buffer
+ * @param len Number of bytes in source buffer
+ * @return true if bytes copied, false if there was insufficient space
+ */
+ static __inline__ __attribute__((always_inline)) bool
+ TPCircularBufferProduceBytes(TPCircularBuffer* buffer, const void* src, int32_t len)
+ {
+ int32_t space;
+ void* ptr = TPCircularBufferHead(buffer, &space);
+ if (space < len)
+ return false;
+ memcpy(ptr, src, len);
+ TPCircularBufferProduce(buffer, len);
+ return true;
+ }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/channels/rdpsnd/client/ios/rdpsnd_ios.c b/channels/rdpsnd/client/ios/rdpsnd_ios.c
new file mode 100644
index 0000000..b8f004f
--- /dev/null
+++ b/channels/rdpsnd/client/ios/rdpsnd_ios.c
@@ -0,0 +1,282 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2013 Dell Software <Mike.McDonald@software.dell.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/wtypes.h>
+
+#include <freerdp/types.h>
+#include <freerdp/codec/dsp.h>
+
+#import <AudioToolbox/AudioToolbox.h>
+
+#include "rdpsnd_main.h"
+#include "TPCircularBuffer.h"
+
+#define INPUT_BUFFER_SIZE 32768
+#define CIRCULAR_BUFFER_SIZE (INPUT_BUFFER_SIZE * 4)
+
+typedef struct
+{
+ rdpsndDevicePlugin device;
+ AudioComponentInstance audio_unit;
+ TPCircularBuffer buffer;
+ BOOL is_opened;
+ BOOL is_playing;
+} rdpsndIOSPlugin;
+
+#define THIS(__ptr) ((rdpsndIOSPlugin*)__ptr)
+
+static OSStatus rdpsnd_ios_render_cb(void* inRefCon,
+ AudioUnitRenderActionFlags __unused* ioActionFlags,
+ const AudioTimeStamp __unused* inTimeStamp, UInt32 inBusNumber,
+ UInt32 __unused inNumberFrames, AudioBufferList* ioData)
+{
+ if (inBusNumber != 0)
+ {
+ return noErr;
+ }
+
+ rdpsndIOSPlugin* p = THIS(inRefCon);
+
+ for (unsigned int i = 0; i < ioData->mNumberBuffers; i++)
+ {
+ AudioBuffer* target_buffer = &ioData->mBuffers[i];
+ int32_t available_bytes = 0;
+ const void* buffer = TPCircularBufferTail(&p->buffer, &available_bytes);
+
+ if (buffer != NULL && available_bytes > 0)
+ {
+ const int bytes_to_copy = MIN((int32_t)target_buffer->mDataByteSize, available_bytes);
+ memcpy(target_buffer->mData, buffer, bytes_to_copy);
+ target_buffer->mDataByteSize = bytes_to_copy;
+ TPCircularBufferConsume(&p->buffer, bytes_to_copy);
+ }
+ else
+ {
+ target_buffer->mDataByteSize = 0;
+ AudioOutputUnitStop(p->audio_unit);
+ p->is_playing = 0;
+ }
+ }
+
+ return noErr;
+}
+
+static BOOL rdpsnd_ios_format_supported(rdpsndDevicePlugin* __unused device,
+ const AUDIO_FORMAT* format)
+{
+ if (format->wFormatTag == WAVE_FORMAT_PCM)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+static BOOL rdpsnd_ios_set_volume(rdpsndDevicePlugin* __unused device, UINT32 __unused value)
+{
+ return TRUE;
+}
+
+static void rdpsnd_ios_start(rdpsndDevicePlugin* device)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+
+ /* If this device is not playing... */
+ if (!p->is_playing)
+ {
+ /* Start the device. */
+ int32_t available_bytes = 0;
+ TPCircularBufferTail(&p->buffer, &available_bytes);
+
+ if (available_bytes > 0)
+ {
+ p->is_playing = 1;
+ AudioOutputUnitStart(p->audio_unit);
+ }
+ }
+}
+
+static void rdpsnd_ios_stop(rdpsndDevicePlugin* __unused device)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+
+ /* If the device is playing... */
+ if (p->is_playing)
+ {
+ /* Stop the device. */
+ AudioOutputUnitStop(p->audio_unit);
+ p->is_playing = 0;
+ /* Free all buffers. */
+ TPCircularBufferClear(&p->buffer);
+ }
+}
+
+static UINT rdpsnd_ios_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+ const BOOL ok = TPCircularBufferProduceBytes(&p->buffer, data, size);
+
+ if (!ok)
+ return 0;
+
+ rdpsnd_ios_start(device);
+ return 10; /* TODO: Get real latencry in [ms] */
+}
+
+static BOOL rdpsnd_ios_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
+ int __unused latency)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+
+ if (p->is_opened)
+ return TRUE;
+
+ /* Find the output audio unit. */
+ AudioComponentDescription desc;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = kAudioUnitSubType_RemoteIO;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+ AudioComponent audioComponent = AudioComponentFindNext(NULL, &desc);
+
+ if (audioComponent == NULL)
+ return FALSE;
+
+ /* Open the audio unit. */
+ OSStatus status = AudioComponentInstanceNew(audioComponent, &p->audio_unit);
+
+ if (status != 0)
+ return FALSE;
+
+ /* Set the format for the AudioUnit. */
+ AudioStreamBasicDescription audioFormat = { 0 };
+ audioFormat.mSampleRate = format->nSamplesPerSec;
+ audioFormat.mFormatID = kAudioFormatLinearPCM;
+ audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
+ audioFormat.mFramesPerPacket = 1; /* imminent property of the Linear PCM */
+ audioFormat.mChannelsPerFrame = format->nChannels;
+ audioFormat.mBitsPerChannel = format->wBitsPerSample;
+ audioFormat.mBytesPerFrame = (format->wBitsPerSample * format->nChannels) / 8;
+ audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
+ status = AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, 0, &audioFormat, sizeof(audioFormat));
+
+ if (status != 0)
+ {
+ AudioComponentInstanceDispose(p->audio_unit);
+ p->audio_unit = NULL;
+ return FALSE;
+ }
+
+ /* Set up the AudioUnit callback. */
+ AURenderCallbackStruct callbackStruct = { 0 };
+ callbackStruct.inputProc = rdpsnd_ios_render_cb;
+ callbackStruct.inputProcRefCon = p;
+ status =
+ AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct));
+
+ if (status != 0)
+ {
+ AudioComponentInstanceDispose(p->audio_unit);
+ p->audio_unit = NULL;
+ return FALSE;
+ }
+
+ /* Initialize the AudioUnit. */
+ status = AudioUnitInitialize(p->audio_unit);
+
+ if (status != 0)
+ {
+ AudioComponentInstanceDispose(p->audio_unit);
+ p->audio_unit = NULL;
+ return FALSE;
+ }
+
+ /* Allocate the circular buffer. */
+ const BOOL ok = TPCircularBufferInit(&p->buffer, CIRCULAR_BUFFER_SIZE);
+
+ if (!ok)
+ {
+ AudioUnitUninitialize(p->audio_unit);
+ AudioComponentInstanceDispose(p->audio_unit);
+ p->audio_unit = NULL;
+ return FALSE;
+ }
+
+ p->is_opened = 1;
+ return TRUE;
+}
+
+static void rdpsnd_ios_close(rdpsndDevicePlugin* device)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+ /* Make sure the device is stopped. */
+ rdpsnd_ios_stop(device);
+
+ /* If the device is open... */
+ if (p->is_opened)
+ {
+ /* Close the device. */
+ AudioUnitUninitialize(p->audio_unit);
+ AudioComponentInstanceDispose(p->audio_unit);
+ p->audio_unit = NULL;
+ p->is_opened = 0;
+ /* Destroy the circular buffer. */
+ TPCircularBufferCleanup(&p->buffer);
+ }
+}
+
+static void rdpsnd_ios_free(rdpsndDevicePlugin* device)
+{
+ rdpsndIOSPlugin* p = THIS(device);
+ /* Ensure the device is closed. */
+ rdpsnd_ios_close(device);
+ /* Free memory associated with the device. */
+ free(p);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT ios_freerdp_rdpsnd_client_subsystem_entry(
+ PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
+{
+ rdpsndIOSPlugin* p = (rdpsndIOSPlugin*)calloc(1, sizeof(rdpsndIOSPlugin));
+
+ if (!p)
+ return CHANNEL_RC_NO_MEMORY;
+
+ p->device.Open = rdpsnd_ios_open;
+ p->device.FormatSupported = rdpsnd_ios_format_supported;
+ p->device.SetVolume = rdpsnd_ios_set_volume;
+ p->device.Play = rdpsnd_ios_play;
+ p->device.Start = rdpsnd_ios_start;
+ p->device.Close = rdpsnd_ios_close;
+ p->device.Free = rdpsnd_ios_free;
+ pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)p);
+ return CHANNEL_RC_OK;
+}