Added RDPSND device plugin for iOS
This commit is contained in:
parent
de8650b118
commit
772f738c47
52
channels/rdpsnd/client/ios/CMakeLists.txt
Normal file
52
channels/rdpsnd/client/ios/CMakeLists.txt
Normal file
@ -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")
|
136
channels/rdpsnd/client/ios/TPCircularBuffer.c
Normal file
136
channels/rdpsnd/client/ios/TPCircularBuffer.c
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
195
channels/rdpsnd/client/ios/TPCircularBuffer.h
Normal file
195
channels/rdpsnd/client/ios/TPCircularBuffer.h
Normal file
@ -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
|
300
channels/rdpsnd/client/ios/rdpsnd_ios.c
Normal file
300
channels/rdpsnd/client/ios/rdpsnd_ios.c
Normal file
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user