Added RDPSND device plugin for iOS
authorMike McDonald <mikem@nogginware.com>
Thu, 29 Aug 2013 01:02:48 +0000 (21:02 -0400)
committerMike McDonald <mikem@nogginware.com>
Thu, 29 Aug 2013 01:02:48 +0000 (21:02 -0400)
channels/rdpsnd/client/ios/CMakeLists.txt [new file with mode: 0644]
channels/rdpsnd/client/ios/TPCircularBuffer.c [new file with mode: 0644]
channels/rdpsnd/client/ios/TPCircularBuffer.h [new file with mode: 0644]
channels/rdpsnd/client/ios/rdpsnd_ios.c [new file with mode: 0644]

diff --git a/channels/rdpsnd/client/ios/CMakeLists.txt b/channels/rdpsnd/client/ios/CMakeLists.txt
new file mode 100644 (file)
index 0000000..2db67b2
--- /dev/null
@@ -0,0 +1,52 @@
+# 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)
+
+include_directories(..)
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
+
+set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "")
+
+set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
+       MONOLITHIC ${MONOLITHIC_BUILD}
+       MODULE freerdp
+       MODULES freerdp-utils)
+       
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} 
+    ${AUDIO_TOOL}
+    ${CORE_AUDIO}
+    ${CORE_FOUNDATION})
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+if(NOT STATIC_CHANNELS)
+       install(TARGETS ${MODULE_NAME} DESTINATION ${FREERDP_ADDIN_PATH})
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client/ios")
diff --git a/channels/rdpsnd/client/ios/TPCircularBuffer.c b/channels/rdpsnd/client/ios/TPCircularBuffer.c
new file mode 100644 (file)
index 0000000..d9e049a
--- /dev/null
@@ -0,0 +1,136 @@
+//
+//  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 "TPCircularBuffer.h"
+#include <mach/mach.h>
+#include <stdio.h>
+
+#define reportResult(result,operation) (_reportResult((result),(operation),strrchr(__FILE__, '/')+1,__LINE__))
+static inline bool _reportResult(kern_return_t result, const char *operation, const char* file, int line) {
+    if ( result != ERR_SUCCESS ) {
+        printf("%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 ) {
+                printf("Couldn't map buffer memory to end of buffer\n");
+                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 (file)
index 0000000..cd2a4d8
--- /dev/null
@@ -0,0 +1,195 @@
+//
+//  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 <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);
+    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;
+    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);
+    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;
+    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 (file)
index 0000000..60d6b96
--- /dev/null
@@ -0,0 +1,300 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Output Virtual Channel
+ *
+ * Copyright 2013 Dell Software <Mike.McDonald@software.dell.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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <winpr/wtypes.h>
+
+#include <freerdp/types.h>
+#include <freerdp/codec/dsp.h>
+#include <freerdp/utils/svc_plugin.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 rdpsnd_ios_plugin
+{
+    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
+)
+{
+       unsigned int i;
+       
+    if (inBusNumber != 0)
+    {
+        return noErr;
+    };
+    
+    rdpsndIOSPlugin *p = THIS(inRefCon);
+       
+    for (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, AUDIO_FORMAT* format)
+{
+       if (format->wFormatTag == WAVE_FORMAT_PCM)
+    {
+               return 1;
+    }
+    return 0;
+}
+
+static void rdpsnd_ios_set_format(rdpsndDevicePlugin* __unused device, AUDIO_FORMAT* __unused format, int __unused latency)
+{
+}
+
+static void rdpsnd_ios_set_volume(rdpsndDevicePlugin* __unused device, UINT32 __unused value)
+{
+}
+
+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 void rdpsnd_ios_play(rdpsndDevicePlugin* device, BYTE* data, int size)
+{
+    rdpsndIOSPlugin *p = THIS(device);
+       
+    const BOOL ok = TPCircularBufferProduceBytes(&p->buffer, data, size);
+    if (!ok)
+    {
+        return;
+    }
+       
+       rdpsnd_ios_start(device);
+       
+}
+
+static void rdpsnd_ios_open(rdpsndDevicePlugin* device, AUDIO_FORMAT* format, int __unused latency)
+{
+    rdpsndIOSPlugin *p = THIS(device);
+       
+    if (p->is_opened)
+    {
+        return;
+    };
+
+       /* 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;
+
+       /* Open the audio unit. */
+    OSStatus status = AudioComponentInstanceNew(audioComponent, &p->audio_unit);
+       if (status != 0) return;
+
+       /* 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;
+       }
+       
+       /* 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;
+       }
+
+       /* Initialize the AudioUnit. */
+    status = AudioUnitInitialize(p->audio_unit);
+       if (status != 0)
+       {
+               AudioComponentInstanceDispose(p->audio_unit);
+               p->audio_unit = NULL;
+               return;
+       }
+       
+       /* 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;
+       }
+
+    p->is_opened = 1;
+}
+
+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);
+}
+
+#ifdef STATIC_CHANNELS
+#define freerdp_rdpsnd_client_subsystem_entry  ios_freerdp_rdpsnd_client_subsystem_entry
+#endif
+
+int freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)
+{
+    rdpsndIOSPlugin *p = (rdpsndIOSPlugin*)malloc(sizeof(rdpsndIOSPlugin));
+    memset(p, 0, sizeof(rdpsndIOSPlugin));
+    
+    p->device.Open = rdpsnd_ios_open;
+    p->device.FormatSupported = rdpsnd_ios_format_supported;
+    p->device.SetFormat = rdpsnd_ios_set_format;
+    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 0;
+}
\ No newline at end of file