diff --git a/channels/drdynvc/CMakeLists.txt b/channels/drdynvc/CMakeLists.txt index ce32efd1b..1e3cc13f3 100644 --- a/channels/drdynvc/CMakeLists.txt +++ b/channels/drdynvc/CMakeLists.txt @@ -33,4 +33,5 @@ target_link_libraries(drdynvc freerdp-utils) install(TARGETS drdynvc DESTINATION ${FREERDP_PLUGIN_PATH}) add_subdirectory(tsmf) +add_subdirectory(audin) diff --git a/channels/drdynvc/audin/CMakeLists.txt b/channels/drdynvc/audin/CMakeLists.txt new file mode 100644 index 000000000..14a48252c --- /dev/null +++ b/channels/drdynvc/audin/CMakeLists.txt @@ -0,0 +1,41 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(AUDIN_SRCS + audin_main.c + audin_main.h +) + +include_directories(..) + +add_library(audin ${AUDIN_SRCS}) +set_target_properties(audin PROPERTIES PREFIX "") + +target_link_libraries(audin freerdp-utils) + +install(TARGETS audin DESTINATION ${FREERDP_PLUGIN_PATH}) + +if(ALSA_FOUND) + add_subdirectory(alsa) +endif() + +if(PULSE_FOUND) + add_subdirectory(pulse) +endif() + diff --git a/channels/drdynvc/audin/alsa/CMakeLists.txt b/channels/drdynvc/audin/alsa/CMakeLists.txt new file mode 100644 index 000000000..786f9ce2c --- /dev/null +++ b/channels/drdynvc/audin/alsa/CMakeLists.txt @@ -0,0 +1,33 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(AUDIN_ALSA_SRCS + audin_alsa.c +) + +include_directories(..) +include_directories(${ALSA_INCLUDE_DIRS}) + +add_library(audin_alsa ${AUDIN_ALSA_SRCS}) +set_target_properties(audin_alsa PROPERTIES PREFIX "") + +target_link_libraries(audin_alsa freerdp-utils) +target_link_libraries(audin_alsa ${ALSA_LIBRARIES}) + +install(TARGETS audin_alsa DESTINATION ${FREERDP_PLUGIN_PATH}) diff --git a/channels/drdynvc/audin/alsa/audin_alsa.c b/channels/drdynvc/audin/alsa/audin_alsa.c new file mode 100644 index 000000000..26da82929 --- /dev/null +++ b/channels/drdynvc/audin/alsa/audin_alsa.c @@ -0,0 +1,356 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Audio Input Redirection Virtual Channel - ALSA implementation + * + * Copyright 2010-2011 Vic Lee + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "audin_main.h" + +typedef struct _AudinALSADevice +{ + IAudinDevice iface; + + char device_name[32]; + uint32 frames_per_packet; + uint32 target_rate; + uint32 actual_rate; + snd_pcm_format_t format; + uint32 target_channels; + uint32 actual_channels; + int bytes_per_channel; + int wformat; + int block_size; + ADPCM adpcm; + + freerdp_thread* thread; + + uint8* buffer; + int buffer_frames; + + AudinReceive receive; + void* user_data; +} AudinALSADevice; + +static boolean audin_alsa_set_params(AudinALSADevice* alsa, snd_pcm_t* capture_handle) +{ + int error; + snd_pcm_hw_params_t* hw_params; + + if ((error = snd_pcm_hw_params_malloc(&hw_params)) < 0) + { + DEBUG_WARN("snd_pcm_hw_params_malloc (%s)", + snd_strerror(error)); + return False; + } + snd_pcm_hw_params_any(capture_handle, hw_params); + snd_pcm_hw_params_set_access(capture_handle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(capture_handle, hw_params, + alsa->format); + snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, + &alsa->actual_rate, NULL); + snd_pcm_hw_params_set_channels_near(capture_handle, hw_params, + &alsa->actual_channels); + snd_pcm_hw_params(capture_handle, hw_params); + snd_pcm_hw_params_free(hw_params); + snd_pcm_prepare(capture_handle); + + if ((alsa->actual_rate != alsa->target_rate) || + (alsa->actual_channels != alsa->target_channels)) + { + DEBUG_DVC("actual rate %d / channel %d is " + "different from target rate %d / channel %d, resampling required.", + alsa->actual_rate, alsa->actual_channels, + alsa->target_rate, alsa->target_channels); + } + return True; +} + +static boolean audin_alsa_thread_receive(AudinALSADevice* alsa, uint8* src, int size) +{ + int frames; + int cframes; + int ret = 0; + int encoded_size; + uint8* encoded_data; + int rbytes_per_frame; + int tbytes_per_frame; + uint8* resampled_data; + + rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_channel; + tbytes_per_frame = alsa->target_channels * alsa->bytes_per_channel; + + if ((alsa->target_rate == alsa->actual_rate) && + (alsa->target_channels == alsa->actual_channels)) + { + resampled_data = NULL; + frames = size / rbytes_per_frame; + } + else + { + resampled_data = dsp_resample(src, alsa->bytes_per_channel, + alsa->actual_channels, alsa->actual_rate, size / rbytes_per_frame, + alsa->target_channels, alsa->target_rate, &frames); + DEBUG_DVC("resampled %d frames at %d to %d frames at %d", + size / rbytes_per_frame, alsa->actual_rate, frames, alsa->target_rate); + size = frames * tbytes_per_frame; + src = resampled_data; + } + + while (frames > 0) + { + cframes = alsa->frames_per_packet - alsa->buffer_frames; + if (cframes > frames) + cframes = frames; + memcpy(alsa->buffer + alsa->buffer_frames * tbytes_per_frame, + src, cframes * tbytes_per_frame); + alsa->buffer_frames += cframes; + if (alsa->buffer_frames >= alsa->frames_per_packet) + { + if (alsa->wformat == 0x11) + { + encoded_data = dsp_encode_ima_adpcm(&alsa->adpcm, + alsa->buffer, alsa->buffer_frames * tbytes_per_frame, + alsa->target_channels, alsa->block_size, &encoded_size); + DEBUG_DVC("encoded %d to %d", + alsa->buffer_frames * tbytes_per_frame, encoded_size); + } + else + { + encoded_data = alsa->buffer; + encoded_size = alsa->buffer_frames * tbytes_per_frame; + } + + ret = alsa->receive(encoded_data, encoded_size, alsa->user_data); + alsa->buffer_frames = 0; + if (encoded_data != alsa->buffer) + xfree(encoded_data); + if (!ret) + break; + } + src += cframes * tbytes_per_frame; + frames -= cframes; + } + + if (resampled_data) + xfree(resampled_data); + + return ret; +} + +static void* audin_alsa_thread_func(void* arg) +{ + int error; + uint8* buffer; + int rbytes_per_frame; + int tbytes_per_frame; + snd_pcm_t* capture_handle = NULL; + AudinALSADevice* alsa = (AudinALSADevice*) arg; + + DEBUG_DVC("in"); + + rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_channel; + tbytes_per_frame = alsa->target_channels * alsa->bytes_per_channel; + alsa->buffer = (uint8*) xzalloc(tbytes_per_frame * alsa->frames_per_packet); + alsa->buffer_frames = 0; + buffer = (uint8*) xzalloc(rbytes_per_frame * alsa->frames_per_packet); + memset(&alsa->adpcm, 0, sizeof(ADPCM)); + do + { + if ((error = snd_pcm_open(&capture_handle, alsa->device_name, SND_PCM_STREAM_CAPTURE, 0)) < 0) + { + DEBUG_WARN("snd_pcm_open (%s)", snd_strerror(error)); + break; + } + if (!audin_alsa_set_params(alsa, capture_handle)) + { + break; + } + + while (!freerdp_thread_is_stopped(alsa->thread)) + { + error = snd_pcm_readi(capture_handle, buffer, alsa->frames_per_packet); + if (error == -EPIPE) + { + snd_pcm_recover(capture_handle, error, 0); + continue; + } + else if (error < 0) + { + DEBUG_WARN("snd_pcm_readi (%s)", snd_strerror(error)); + break; + } + if (!audin_alsa_thread_receive(alsa, buffer, error * rbytes_per_frame)) + break; + } + } while (0); + + xfree(buffer); + xfree(alsa->buffer); + alsa->buffer = NULL; + if (capture_handle) + snd_pcm_close(capture_handle); + + freerdp_thread_quit(alsa->thread); + + DEBUG_DVC("out"); + + return NULL; +} + +static void audin_alsa_free(IAudinDevice* device) +{ + AudinALSADevice* alsa = (AudinALSADevice*) device; + + freerdp_thread_free(alsa->thread); + xfree(alsa); +} + +static boolean audin_alsa_format_supported(IAudinDevice* device, audinFormat* format) +{ + switch (format->wFormatTag) + { + case 1: /* PCM */ + if (format->cbSize == 0 && + (format->nSamplesPerSec <= 48000) && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels == 1 || format->nChannels == 2)) + { + return True; + } + break; + + case 0x11: /* IMA ADPCM */ + if ((format->nSamplesPerSec <= 48000) && + (format->wBitsPerSample == 4) && + (format->nChannels == 1 || format->nChannels == 2)) + { + return True; + } + break; + } + return False; +} + +static void audin_alsa_set_format(IAudinDevice* device, audinFormat* format, uint32 FramesPerPacket) +{ + int bs; + AudinALSADevice* alsa = (AudinALSADevice*) device; + + alsa->target_rate = format->nSamplesPerSec; + alsa->actual_rate = format->nSamplesPerSec; + alsa->target_channels = format->nChannels; + alsa->actual_channels = format->nChannels; + switch (format->wFormatTag) + { + case 1: /* PCM */ + switch (format->wBitsPerSample) + { + case 8: + alsa->format = SND_PCM_FORMAT_S8; + alsa->bytes_per_channel = 1; + break; + case 16: + alsa->format = SND_PCM_FORMAT_S16_LE; + alsa->bytes_per_channel = 2; + break; + } + break; + + case 0x11: /* IMA ADPCM */ + alsa->format = SND_PCM_FORMAT_S16_LE; + alsa->bytes_per_channel = 2; + bs = (format->nBlockAlign - 4 * format->nChannels) * 4; + alsa->frames_per_packet = (alsa->frames_per_packet * format->nChannels * 2 / + bs + 1) * bs / (format->nChannels * 2); + DEBUG_DVC("aligned FramesPerPacket=%d", + alsa->frames_per_packet); + break; + } + alsa->wformat = format->wFormatTag; + alsa->block_size = format->nBlockAlign; +} + +static void audin_alsa_open(IAudinDevice* device, AudinReceive receive, void* user_data) +{ + AudinALSADevice* alsa = (AudinALSADevice*) device; + + DEBUG_DVC(""); + + alsa->receive = receive; + alsa->user_data = user_data; + + freerdp_thread_start(alsa->thread, audin_alsa_thread_func, alsa); +} + +static void audin_alsa_close(IAudinDevice* device) +{ + AudinALSADevice* alsa = (AudinALSADevice*) device; + + DEBUG_DVC(""); + + freerdp_thread_stop(alsa->thread); + + alsa->receive = NULL; + alsa->user_data = NULL; +} + +int FreeRDPAudinDeviceEntry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints) +{ + AudinALSADevice* alsa; + RDP_PLUGIN_DATA* data; + + alsa = xnew(AudinALSADevice); + + alsa->iface.Open = audin_alsa_open; + alsa->iface.FormatSupported = audin_alsa_format_supported; + alsa->iface.SetFormat = audin_alsa_set_format; + alsa->iface.Close = audin_alsa_close; + alsa->iface.Free = audin_alsa_free; + + data = pEntryPoints->plugin_data; + if (data && data->data[0] && strcmp(data->data[0], "audin") == 0 && + data->data[1] && strcmp(data->data[1], "alsa") == 0) + { + strncpy(alsa->device_name, (char*)data->data[2], sizeof(alsa->device_name)); + } + if (alsa->device_name[0] == '\0') + { + strcpy(alsa->device_name, "default"); + } + + alsa->frames_per_packet = 128; + alsa->target_rate = 22050; + alsa->actual_rate = 22050; + alsa->format = SND_PCM_FORMAT_S16_LE; + alsa->target_channels = 2; + alsa->actual_channels = 2; + alsa->bytes_per_channel = 2; + alsa->thread = freerdp_thread_new(); + + pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*) alsa); + + return 0; +} + diff --git a/channels/drdynvc/audin/audin_main.c b/channels/drdynvc/audin/audin_main.c new file mode 100644 index 000000000..2c8617cf3 --- /dev/null +++ b/channels/drdynvc/audin/audin_main.c @@ -0,0 +1,542 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Audio Input Reirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * + * 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 +#include +#include +#include +#include +#include + +#include "audin_main.h" + +#define MSG_SNDIN_VERSION 0x01 +#define MSG_SNDIN_FORMATS 0x02 +#define MSG_SNDIN_OPEN 0x03 +#define MSG_SNDIN_OPEN_REPLY 0x04 +#define MSG_SNDIN_DATA_INCOMING 0x05 +#define MSG_SNDIN_DATA 0x06 +#define MSG_SNDIN_FORMATCHANGE 0x07 + +typedef struct _AUDIN_LISTENER_CALLBACK AUDIN_LISTENER_CALLBACK; +struct _AUDIN_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; +}; + +typedef struct _AUDIN_CHANNEL_CALLBACK AUDIN_CHANNEL_CALLBACK; +struct _AUDIN_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; + + /** + * The supported format list sent back to the server, which needs to + * be stored as reference when the server sends the format index in + * Open PDU and Format Change PDU + */ + audinFormat* formats; + int formats_count; +}; + +typedef struct _AUDIN_PLUGIN AUDIN_PLUGIN; +struct _AUDIN_PLUGIN +{ + IWTSPlugin iface; + + AUDIN_LISTENER_CALLBACK* listener_callback; + + /* Parsed plugin data */ + uint16 fixed_format; + uint16 fixed_channel; + uint32 fixed_rate; + + /* Device interface */ + IAudinDevice* device; +}; + +static int audin_process_version(IWTSVirtualChannelCallback* pChannelCallback, STREAM* s) +{ + int error; + STREAM* out; + uint32 Version; + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) pChannelCallback; + + stream_read_uint32(s, Version); + + DEBUG_DVC("Version=%d", Version); + + out = stream_new(5); + stream_write_uint8(out, MSG_SNDIN_VERSION); + stream_write_uint32(out, Version); + error = callback->channel->Write(callback->channel, stream_get_length(s), stream_get_head(s), NULL); + stream_free(out); + + return error; +} + +static int audin_send_incoming_data_pdu(IWTSVirtualChannelCallback* pChannelCallback) +{ + uint8 out_data[1]; + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) pChannelCallback; + + out_data[0] = MSG_SNDIN_DATA_INCOMING; + return callback->channel->Write(callback->channel, 1, out_data, NULL); +} + +static int audin_process_formats(IWTSVirtualChannelCallback* pChannelCallback, STREAM* s) +{ + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) pChannelCallback; + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) callback->plugin; + uint32 i; + uint8* fm; + int error; + STREAM* out; + uint32 NumFormats; + audinFormat format; + uint32 cbSizeFormatsPacket; + + stream_read_uint32(s, NumFormats); + DEBUG_DVC("NumFormats %d", NumFormats); + if ((NumFormats < 1) || (NumFormats > 1000)) + { + DEBUG_WARN("bad NumFormats %d", NumFormats); + return 1; + } + stream_seek_uint32(s); /* cbSizeFormatsPacket */ + + callback->formats = (audinFormat*) xzalloc(NumFormats * sizeof(audinFormat)); + + out = stream_new(9); + stream_seek(out, 9); + + /* SoundFormats (variable) */ + for (i = 0; i < NumFormats; i++) + { + stream_get_mark(s, fm); + stream_read_uint16(s, format.wFormatTag); + stream_read_uint16(s, format.nChannels); + stream_read_uint32(s, format.nSamplesPerSec); + stream_seek_uint32(s); /* nAvgBytesPerSec */ + stream_read_uint16(s, format.nBlockAlign); + stream_read_uint16(s, format.wBitsPerSample); + stream_read_uint16(s, format.cbSize); + format.data = stream_get_tail(s); + stream_seek(s, format.cbSize); + + DEBUG_DVC("wFormatTag=%d nChannels=%d nSamplesPerSec=%d " + "nBlockAlign=%d wBitsPerSample=%d cbSize=%d", + format.wFormatTag, format.nChannels, format.nSamplesPerSec, + format.nBlockAlign, format.wBitsPerSample, format.cbSize); + + if (audin->fixed_format > 0 && audin->fixed_format != format.wFormatTag) + continue; + if (audin->fixed_channel > 0 && audin->fixed_channel != format.nChannels) + continue; + if (audin->fixed_rate > 0 && audin->fixed_rate != format.nSamplesPerSec) + continue; + if (audin->device && audin->device->FormatSupported(audin->device, &format)) + { + DEBUG_DVC("format ok"); + + /* Store the agreed format in the corresponding index */ + callback->formats[callback->formats_count++] = format; + /* Put the format to output buffer */ + stream_check_size(out, 18 + format.cbSize); + stream_write(out, fm, 18 + format.cbSize); + } + } + + audin_send_incoming_data_pdu(pChannelCallback); + + cbSizeFormatsPacket = stream_get_pos(out); + stream_set_pos(out, 0); + + stream_write_uint8(out, MSG_SNDIN_FORMATS); /* Header (1 byte) */ + stream_write_uint32(out, callback->formats_count); /* NumFormats (4 bytes) */ + stream_write_uint32(out, cbSizeFormatsPacket); /* cbSizeFormatsPacket (4 bytes) */ + + error = callback->channel->Write(callback->channel, cbSizeFormatsPacket, stream_get_head(out), NULL); + stream_free(out); + + return error; +} + +static int audin_send_format_change_pdu(IWTSVirtualChannelCallback* pChannelCallback, uint32 NewFormat) +{ + int error; + STREAM* out; + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) pChannelCallback; + + out = stream_new(5); + stream_write_uint8(out, MSG_SNDIN_FORMATCHANGE); + stream_write_uint32(out, NewFormat); + error = callback->channel->Write(callback->channel, 5, stream_get_head(out), NULL); + stream_free(out); + + return error; +} + +static int audin_send_open_reply_pdu(IWTSVirtualChannelCallback* pChannelCallback, uint32 Result) +{ + int error; + STREAM* out; + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) pChannelCallback; + + out = stream_new(5); + stream_write_uint8(out, MSG_SNDIN_OPEN_REPLY); + stream_write_uint32(out, Result); + error = callback->channel->Write(callback->channel, 5, stream_get_head(out), NULL); + stream_free(out); + + return error; +} + +static boolean audin_receive_wave_data(uint8* data, int size, void* user_data) +{ + int error; + STREAM* out; + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) user_data; + + error = audin_send_incoming_data_pdu((IWTSVirtualChannelCallback*) callback); + if (error != 0) + return False; + + out = stream_new(size + 1); + stream_write_uint8(out, MSG_SNDIN_DATA); + stream_write(out, data, size); + error = callback->channel->Write(callback->channel, stream_get_length(out), stream_get_head(out), NULL); + stream_free(out); + + return (error == 0 ? True : False); +} + +static int audin_process_open(IWTSVirtualChannelCallback* pChannelCallback, STREAM* s) +{ + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) pChannelCallback; + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) callback->plugin; + audinFormat* format; + uint32 initialFormat; + uint32 FramesPerPacket; + + stream_read_uint32(s, FramesPerPacket); + stream_read_uint32(s, initialFormat); + + DEBUG_DVC("FramesPerPacket=%d initialFormat=%d", + FramesPerPacket, initialFormat); + + if (initialFormat >= callback->formats_count) + { + DEBUG_WARN("invalid format index %d (total %d)", + initialFormat, callback->formats_count); + return 1; + } + + format = &callback->formats[initialFormat]; + if (audin->device) + { + IFCALL(audin->device->SetFormat, audin->device, format, FramesPerPacket); + IFCALL(audin->device->Open, audin->device, audin_receive_wave_data, callback); + } + + audin_send_format_change_pdu(pChannelCallback, initialFormat); + audin_send_open_reply_pdu(pChannelCallback, 0); + + return 0; +} + +static int audin_process_format_change(IWTSVirtualChannelCallback* pChannelCallback, STREAM* s) +{ + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) pChannelCallback; + AUDIN_PLUGIN * audin = (AUDIN_PLUGIN *) callback->plugin; + uint32 NewFormat; + audinFormat* format; + + stream_read_uint32(s, NewFormat); + + DEBUG_DVC("NewFormat=%d", NewFormat); + + if (NewFormat >= callback->formats_count) + { + DEBUG_WARN("invalid format index %d (total %d)", + NewFormat, callback->formats_count); + return 1; + } + + format = &callback->formats[NewFormat]; + + if (audin->device) + { + IFCALL(audin->device->Close, audin->device); + IFCALL(audin->device->SetFormat, audin->device, format, 0); + IFCALL(audin->device->Open, audin->device, audin_receive_wave_data, callback); + } + + audin_send_format_change_pdu(pChannelCallback, NewFormat); + + return 0; +} + +static int audin_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, + uint32 cbSize, + uint8* pBuffer) +{ + int error; + STREAM* s; + uint8 MessageId; + + s = stream_new(0); + stream_attach(s, pBuffer, cbSize); + + stream_read_uint8(s, MessageId); + + DEBUG_DVC("MessageId=0x%x", MessageId); + + switch (MessageId) + { + case MSG_SNDIN_VERSION: + error = audin_process_version(pChannelCallback, s); + break; + + case MSG_SNDIN_FORMATS: + error = audin_process_formats(pChannelCallback, s); + break; + + case MSG_SNDIN_OPEN: + error = audin_process_open(pChannelCallback, s); + break; + + case MSG_SNDIN_FORMATCHANGE: + error = audin_process_format_change(pChannelCallback, s); + break; + + default: + DEBUG_WARN("unknown MessageId=0x%x", MessageId); + error = 1; + break; + } + + stream_detach(s); + stream_free(s); + + return error; +} + +static int audin_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) pChannelCallback; + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) callback->plugin; + + DEBUG_DVC(""); + + if (audin->device) + IFCALL(audin->device->Close, audin->device); + + xfree(callback->formats); + xfree(callback); + + return 0; +} + +static int audin_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, + uint8* Data, + int* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + AUDIN_CHANNEL_CALLBACK* callback; + AUDIN_LISTENER_CALLBACK* listener_callback = (AUDIN_LISTENER_CALLBACK*) pListenerCallback; + + DEBUG_DVC(""); + + callback = xnew(AUDIN_CHANNEL_CALLBACK); + + callback->iface.OnDataReceived = audin_on_data_received; + callback->iface.OnClose = audin_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + + *ppCallback = (IWTSVirtualChannelCallback*) callback; + + return 0; +} + +static int audin_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) pPlugin; + + DEBUG_DVC(""); + + audin->listener_callback = xnew(AUDIN_LISTENER_CALLBACK); + + audin->listener_callback->iface.OnNewChannelConnection = audin_on_new_channel_connection; + audin->listener_callback->plugin = pPlugin; + audin->listener_callback->channel_mgr = pChannelMgr; + return pChannelMgr->CreateListener(pChannelMgr, "AUDIO_INPUT", 0, + (IWTSListenerCallback*) audin->listener_callback, NULL); +} + +static int audin_plugin_terminated(IWTSPlugin* pPlugin) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) pPlugin; + + DEBUG_DVC(""); + + if (audin->device) + { + IFCALL(audin->device->Close, audin->device); + IFCALL(audin->device->Free, audin->device); + audin->device = NULL; + } + xfree(audin->listener_callback); + xfree(audin); + + return 0; +} + +static void audin_register_device_plugin(IWTSPlugin* pPlugin, IAudinDevice* device) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) pPlugin; + + if (audin->device) + { + DEBUG_WARN("existing device, abort."); + return; + } + + DEBUG_DVC("device registered."); + + audin->device = device; +} + +static boolean audin_load_device_plugin(IWTSPlugin* pPlugin, const char* name, RDP_PLUGIN_DATA* data) +{ + char* fullname; + PFREERDP_AUDIN_DEVICE_ENTRY entry; + FREERDP_AUDIN_DEVICE_ENTRY_POINTS entryPoints; + + if (strrchr(name, '.') != NULL) + entry = (PFREERDP_AUDIN_DEVICE_ENTRY)freerdp_load_plugin(name, AUDIN_DEVICE_EXPORT_FUNC_NAME); + else + { + fullname = xzalloc(strlen(name) + 8); + strcpy(fullname, "audin_"); + strcat(fullname, name); + entry = (PFREERDP_AUDIN_DEVICE_ENTRY)freerdp_load_plugin(fullname, AUDIN_DEVICE_EXPORT_FUNC_NAME); + xfree(fullname); + } + if (entry == NULL) + { + return False; + } + + entryPoints.plugin = pPlugin; + entryPoints.pRegisterAudinDevice = audin_register_device_plugin; + entryPoints.plugin_data = data; + if (entry(&entryPoints) != 0) + { + DEBUG_WARN("%s entry returns error.", name); + return False; + } + + return True; +} + +static boolean audin_process_plugin_data(IWTSPlugin* pPlugin, RDP_PLUGIN_DATA* data) +{ + boolean ret; + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) pPlugin; + RDP_PLUGIN_DATA default_data[2] = { { 0 }, { 0 } }; + + if (data->data[0] && strcmp((char*)data->data[0], "audin") == 0) + { + if (data->data[1] && strcmp((char*)data->data[1], "format") == 0) + { + audin->fixed_format = atoi(data->data[2]); + return True; + } + else if (data->data[1] && strcmp((char*)data->data[1], "rate") == 0) + { + audin->fixed_rate = atoi(data->data[2]); + return True; + } + else if (data->data[1] && strcmp((char*)data->data[1], "channel") == 0) + { + audin->fixed_channel = atoi(data->data[2]); + return True; + } + else if (data->data[1] && ((char*)data->data[1])[0]) + { + return audin_load_device_plugin(pPlugin, (char*)data->data[1], data); + } + else + { + default_data[0].size = sizeof(RDP_PLUGIN_DATA); + default_data[0].data[0] = "audin"; + default_data[0].data[1] = "pulse"; + default_data[0].data[2] = ""; + ret = audin_load_device_plugin(pPlugin, "pulse", default_data); + if (!ret) + { + default_data[0].size = sizeof(RDP_PLUGIN_DATA); + default_data[0].data[0] = "audin"; + default_data[0].data[1] = "alsa"; + default_data[0].data[2] = "default"; + ret = audin_load_device_plugin(pPlugin, "alsa", default_data); + } + return ret; + } + } + + return True; +} + +int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + int error = 0; + AUDIN_PLUGIN* audin; + + audin = (AUDIN_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "audin"); + if (audin == NULL) + { + audin = xnew(AUDIN_PLUGIN); + + audin->iface.Initialize = audin_plugin_initialize; + audin->iface.Connected = NULL; + audin->iface.Disconnected = NULL; + audin->iface.Terminated = audin_plugin_terminated; + error = pEntryPoints->RegisterPlugin(pEntryPoints, "audin", (IWTSPlugin*) audin); + } + + if (error == 0) + { + audin_process_plugin_data((IWTSPlugin*) audin, + pEntryPoints->GetPluginData(pEntryPoints)); + } + + return error; +} + diff --git a/channels/drdynvc/audin/audin_main.h b/channels/drdynvc/audin/audin_main.h new file mode 100644 index 000000000..42fa8e4cf --- /dev/null +++ b/channels/drdynvc/audin/audin_main.h @@ -0,0 +1,65 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Audio Input Reirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * + * 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. + */ + +#ifndef __AUDIN_MAIN_H +#define __AUDIN_MAIN_H + +#include "drdynvc_types.h" + +typedef boolean (*AudinReceive) (uint8* data, int size, void* user_data); + +typedef struct audin_format audinFormat; +struct audin_format +{ + uint16 wFormatTag; + uint16 nChannels; + uint32 nSamplesPerSec; + uint16 nBlockAlign; + uint16 wBitsPerSample; + uint16 cbSize; + uint8* data; +}; + +typedef struct _IAudinDevice IAudinDevice; +struct _IAudinDevice +{ + void (*Open) (IAudinDevice* devplugin, AudinReceive receive, void* user_data); + boolean (*FormatSupported) (IAudinDevice* devplugin, audinFormat* format); + void (*SetFormat) (IAudinDevice* devplugin, audinFormat* format, uint32 FramesPerPacket); + void (*Close) (IAudinDevice* devplugin); + void (*Free) (IAudinDevice* devplugin); +}; + +#define AUDIN_DEVICE_EXPORT_FUNC_NAME "FreeRDPAudinDeviceEntry" + +typedef void (*PREGISTERAUDINDEVICE)(IWTSPlugin* plugin, IAudinDevice* device); + +struct _FREERDP_AUDIN_DEVICE_ENTRY_POINTS +{ + IWTSPlugin* plugin; + PREGISTERAUDINDEVICE pRegisterAudinDevice; + RDP_PLUGIN_DATA* plugin_data; +}; +typedef struct _FREERDP_AUDIN_DEVICE_ENTRY_POINTS FREERDP_AUDIN_DEVICE_ENTRY_POINTS; +typedef FREERDP_AUDIN_DEVICE_ENTRY_POINTS* PFREERDP_AUDIN_DEVICE_ENTRY_POINTS; + +typedef int (*PFREERDP_AUDIN_DEVICE_ENTRY)(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints); + +#endif /* __AUDIN_MAIN_H */ + diff --git a/channels/drdynvc/audin/pulse/CMakeLists.txt b/channels/drdynvc/audin/pulse/CMakeLists.txt new file mode 100644 index 000000000..10d6afe39 --- /dev/null +++ b/channels/drdynvc/audin/pulse/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(AUDIN_PULSE_SRCS + audin_pulse.c +) + +include_directories(..) +include_directories(${PULSE_INCLUDE_DIRS}) + +add_library(audin_pulse ${AUDIN_PULSE_SRCS}) +set_target_properties(audin_pulse PROPERTIES PREFIX "") + +target_link_libraries(audin_pulse freerdp-utils) +target_link_libraries(audin_pulse ${PULSE_LIBRARIES}) + +install(TARGETS audin_pulse DESTINATION ${FREERDP_PLUGIN_PATH}) + diff --git a/channels/drdynvc/audin/pulse/audin_pulse.c b/channels/drdynvc/audin/pulse/audin_pulse.c new file mode 100644 index 000000000..62a10c174 --- /dev/null +++ b/channels/drdynvc/audin/pulse/audin_pulse.c @@ -0,0 +1,471 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Audio Input Redirection Virtual Channel - PulseAudio implementation + * + * Copyright 2010-2011 Vic Lee + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "audin_main.h" + +typedef struct _AudinPulseDevice +{ + IAudinDevice iface; + + char device_name[32]; + uint32 frames_per_packet; + pa_threaded_mainloop* mainloop; + pa_context* context; + pa_sample_spec sample_spec; + pa_stream* stream; + int format; + int block_size; + ADPCM adpcm; + + int bytes_per_frame; + uint8* buffer; + int buffer_frames; + + AudinReceive receive; + void* user_data; +} AudinPulseDevice; + +static void audin_pulse_context_state_callback(pa_context* context, void* userdata) +{ + pa_context_state_t state; + AudinPulseDevice* pulse = (AudinPulseDevice*) userdata; + + state = pa_context_get_state(context); + switch (state) + { + case PA_CONTEXT_READY: + DEBUG_DVC("PA_CONTEXT_READY"); + pa_threaded_mainloop_signal (pulse->mainloop, 0); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + DEBUG_DVC("state %d", (int)state); + pa_threaded_mainloop_signal (pulse->mainloop, 0); + break; + + default: + DEBUG_DVC("state %d", (int)state); + break; + } +} + +static boolean audin_pulse_connect(IAudinDevice* device) +{ + pa_context_state_t state; + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + if (!pulse->context) + return False; + + if (pa_context_connect(pulse->context, NULL, 0, NULL)) + { + DEBUG_WARN("pa_context_connect failed (%d)", + pa_context_errno(pulse->context)); + return False; + } + pa_threaded_mainloop_lock(pulse->mainloop); + if (pa_threaded_mainloop_start(pulse->mainloop) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + DEBUG_WARN("pa_threaded_mainloop_start failed (%d)", + pa_context_errno(pulse->context)); + return False; + } + for (;;) + { + state = pa_context_get_state(pulse->context); + if (state == PA_CONTEXT_READY) + break; + if (!PA_CONTEXT_IS_GOOD(state)) + { + DEBUG_WARN("bad context state (%d)", + pa_context_errno(pulse->context)); + break; + } + pa_threaded_mainloop_wait(pulse->mainloop); + } + pa_threaded_mainloop_unlock(pulse->mainloop); + if (state == PA_CONTEXT_READY) + { + DEBUG_DVC("connected"); + return True; + } + else + { + pa_context_disconnect(pulse->context); + return False; + } +} + +static void audin_pulse_free(IAudinDevice* device) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + DEBUG_DVC(""); + + if (!pulse) + return; + if (pulse->mainloop) + { + pa_threaded_mainloop_stop(pulse->mainloop); + } + if (pulse->context) + { + pa_context_disconnect(pulse->context); + pa_context_unref(pulse->context); + pulse->context = NULL; + } + if (pulse->mainloop) + { + pa_threaded_mainloop_free(pulse->mainloop); + pulse->mainloop = NULL; + } + xfree(pulse); +} + +static boolean audin_pulse_format_supported(IAudinDevice* device, audinFormat* format) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + if (!pulse->context) + return 0; + + switch (format->wFormatTag) + { + case 1: /* PCM */ + if (format->cbSize == 0 && + (format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX)) + { + return True; + } + break; + + case 6: /* A-LAW */ + case 7: /* U-LAW */ + if (format->cbSize == 0 && + (format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 8) && + (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX)) + { + return True; + } + break; + + case 0x11: /* IMA ADPCM */ + if ((format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 4) && + (format->nChannels == 1 || format->nChannels == 2)) + { + return True; + } + break; + } + return False; +} + +static void audin_pulse_set_format(IAudinDevice* device, audinFormat* format, uint32 FramesPerPacket) +{ + int bs; + pa_sample_spec sample_spec = { 0 }; + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + if (!pulse->context) + return; + + if (FramesPerPacket > 0) + { + pulse->frames_per_packet = FramesPerPacket; + } + + sample_spec.rate = format->nSamplesPerSec; + sample_spec.channels = format->nChannels; + switch (format->wFormatTag) + { + case 1: /* PCM */ + switch (format->wBitsPerSample) + { + case 8: + sample_spec.format = PA_SAMPLE_U8; + break; + case 16: + sample_spec.format = PA_SAMPLE_S16LE; + break; + } + break; + + case 6: /* A-LAW */ + sample_spec.format = PA_SAMPLE_ALAW; + break; + + case 7: /* U-LAW */ + sample_spec.format = PA_SAMPLE_ULAW; + break; + + case 0x11: /* IMA ADPCM */ + sample_spec.format = PA_SAMPLE_S16LE; + bs = (format->nBlockAlign - 4 * format->nChannels) * 4; + pulse->frames_per_packet = (pulse->frames_per_packet * format->nChannels * 2 / + bs + 1) * bs / (format->nChannels * 2); + DEBUG_DVC("aligned FramesPerPacket=%d", + pulse->frames_per_packet); + break; + } + + pulse->sample_spec = sample_spec; + pulse->format = format->wFormatTag; + pulse->block_size = format->nBlockAlign; +} + +static void audin_pulse_stream_state_callback(pa_stream* stream, void* userdata) +{ + pa_stream_state_t state; + AudinPulseDevice* pulse = (AudinPulseDevice*) userdata; + + state = pa_stream_get_state(stream); + switch (state) + { + case PA_STREAM_READY: + DEBUG_DVC("PA_STREAM_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + DEBUG_DVC("state %d", (int)state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + DEBUG_DVC("state %d", (int)state); + break; + } +} + +static void audin_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata) +{ + int frames; + int cframes; + boolean ret; + const void* data; + const uint8* src; + int encoded_size; + uint8* encoded_data; + AudinPulseDevice* pulse = (AudinPulseDevice*) userdata; + + pa_stream_peek(stream, &data, &length); + frames = length / pulse->bytes_per_frame; + + DEBUG_DVC("length %d frames %d", (int) length, frames); + + src = (const uint8*) data; + while (frames > 0) + { + cframes = pulse->frames_per_packet - pulse->buffer_frames; + if (cframes > frames) + cframes = frames; + memcpy(pulse->buffer + pulse->buffer_frames * pulse->bytes_per_frame, + src, cframes * pulse->bytes_per_frame); + pulse->buffer_frames += cframes; + if (pulse->buffer_frames >= pulse->frames_per_packet) + { + if (pulse->format == 0x11) + { + encoded_data = dsp_encode_ima_adpcm(&pulse->adpcm, + pulse->buffer, pulse->buffer_frames * pulse->bytes_per_frame, + pulse->sample_spec.channels, pulse->block_size, &encoded_size); + DEBUG_DVC("encoded %d to %d", + pulse->buffer_frames * pulse->bytes_per_frame, encoded_size); + } + else + { + encoded_data = pulse->buffer; + encoded_size = pulse->buffer_frames * pulse->bytes_per_frame; + } + + ret = pulse->receive(encoded_data, encoded_size, pulse->user_data); + pulse->buffer_frames = 0; + if (encoded_data != pulse->buffer) + xfree(encoded_data); + if (!ret) + break; + } + src += cframes * pulse->bytes_per_frame; + frames -= cframes; + } + + pa_stream_drop(stream); +} + + +static void audin_pulse_close(IAudinDevice* device) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + if (!pulse->context || !pulse->stream) + return; + + DEBUG_DVC(""); + + pa_threaded_mainloop_lock(pulse->mainloop); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = NULL; + pa_threaded_mainloop_unlock(pulse->mainloop); + + pulse->receive = NULL; + pulse->user_data = NULL; + if (pulse->buffer) + { + xfree(pulse->buffer); + pulse->buffer = NULL; + pulse->buffer_frames = 0; + } +} + +static void audin_pulse_open(IAudinDevice* device, AudinReceive receive, void* user_data) +{ + pa_stream_state_t state; + pa_buffer_attr buffer_attr = { 0 }; + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + if (!pulse->context) + return; + if (!pulse->sample_spec.rate || pulse->stream) + return; + + DEBUG_DVC(""); + + pulse->receive = receive; + pulse->user_data = user_data; + + pa_threaded_mainloop_lock(pulse->mainloop); + pulse->stream = pa_stream_new(pulse->context, "freerdp_audin", + &pulse->sample_spec, NULL); + if (!pulse->stream) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + DEBUG_DVC("pa_stream_new failed (%d)", + pa_context_errno(pulse->context)); + return; + } + pulse->bytes_per_frame = pa_frame_size(&pulse->sample_spec); + pa_stream_set_state_callback(pulse->stream, + audin_pulse_stream_state_callback, pulse); + pa_stream_set_read_callback(pulse->stream, + audin_pulse_stream_request_callback, pulse); + buffer_attr.maxlength = (uint32_t) -1; + buffer_attr.tlength = (uint32_t) -1; + buffer_attr.prebuf = (uint32_t) -1; + buffer_attr.minreq = (uint32_t) -1; + /* 500ms latency */ + buffer_attr.fragsize = pa_usec_to_bytes(500000, &pulse->sample_spec); + if (pa_stream_connect_record(pulse->stream, + pulse->device_name[0] ? pulse->device_name : NULL, + &buffer_attr, PA_STREAM_ADJUST_LATENCY) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + DEBUG_WARN("pa_stream_connect_playback failed (%d)", + pa_context_errno(pulse->context)); + return; + } + + for (;;) + { + state = pa_stream_get_state(pulse->stream); + if (state == PA_STREAM_READY) + break; + if (!PA_STREAM_IS_GOOD(state)) + { + DEBUG_WARN("bad stream state (%d)", + pa_context_errno(pulse->context)); + break; + } + pa_threaded_mainloop_wait(pulse->mainloop); + } + pa_threaded_mainloop_unlock(pulse->mainloop); + if (state == PA_STREAM_READY) + { + memset(&pulse->adpcm, 0, sizeof(ADPCM)); + pulse->buffer = xzalloc(pulse->bytes_per_frame * pulse->frames_per_packet); + pulse->buffer_frames = 0; + DEBUG_DVC("connected"); + } + else + { + audin_pulse_close(device); + } +} + +int FreeRDPAudinDeviceEntry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints) +{ + AudinPulseDevice* pulse; + RDP_PLUGIN_DATA * data; + + pulse = xnew(AudinPulseDevice); + + pulse->iface.Open = audin_pulse_open; + pulse->iface.FormatSupported = audin_pulse_format_supported; + pulse->iface.SetFormat = audin_pulse_set_format; + pulse->iface.Close = audin_pulse_close; + pulse->iface.Free = audin_pulse_free; + + data = pEntryPoints->plugin_data; + if (data && data->data[0] && strcmp(data->data[0], "audin") == 0 && + data->data[1] && strcmp(data->data[1], "pulse") == 0) + { + strncpy(pulse->device_name, (char*)data->data[2], sizeof(pulse->device_name)); + } + + pulse->mainloop = pa_threaded_mainloop_new(); + if (!pulse->mainloop) + { + DEBUG_WARN("pa_threaded_mainloop_new failed"); + audin_pulse_free((IAudinDevice*) pulse); + return 1; + } + pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp"); + if (!pulse->context) + { + DEBUG_WARN("pa_context_new failed"); + audin_pulse_free((IAudinDevice*) pulse); + return 1; + } + pa_context_set_state_callback(pulse->context, audin_pulse_context_state_callback, pulse); + if (!audin_pulse_connect((IAudinDevice*) pulse)) + { + audin_pulse_free((IAudinDevice*) pulse); + return 1; + } + + pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*) pulse); + + return 0; +} +