Coverage Report

Created: 2026-01-16 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/channels/audin/client/audin_main.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Audio Input Redirection Virtual Channel
4
 *
5
 * Copyright 2010-2011 Vic Lee
6
 * Copyright 2015 Thincast Technologies GmbH
7
 * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
8
 * Copyright 2015 Armin Novak <armin.novak@thincast.com>
9
 *
10
 * Licensed under the Apache License, Version 2.0 (the "License");
11
 * you may not use this file except in compliance with the License.
12
 * You may obtain a copy of the License at
13
 *
14
 *     http://www.apache.org/licenses/LICENSE-2.0
15
 *
16
 * Unless required by applicable law or agreed to in writing, software
17
 * distributed under the License is distributed on an "AS IS" BASIS,
18
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
 * See the License for the specific language governing permissions and
20
 * limitations under the License.
21
 */
22
23
#include <freerdp/config.h>
24
25
#include <errno.h>
26
#include <winpr/assert.h>
27
#include <stdio.h>
28
#include <stdlib.h>
29
#include <string.h>
30
31
#include <winpr/crt.h>
32
#include <winpr/cmdline.h>
33
#include <winpr/wlog.h>
34
35
#include <freerdp/addin.h>
36
37
#include <winpr/stream.h>
38
#include <freerdp/freerdp.h>
39
#include <freerdp/codec/dsp.h>
40
#include <freerdp/client/channels.h>
41
#include <freerdp/channels/audin.h>
42
43
#include "audin_main.h"
44
45
0
#define SNDIN_VERSION 0x02
46
47
typedef enum
48
{
49
  MSG_SNDIN_VERSION = 0x01,
50
  MSG_SNDIN_FORMATS = 0x02,
51
  MSG_SNDIN_OPEN = 0x03,
52
  MSG_SNDIN_OPEN_REPLY = 0x04,
53
  MSG_SNDIN_DATA_INCOMING = 0x05,
54
  MSG_SNDIN_DATA = 0x06,
55
  MSG_SNDIN_FORMATCHANGE = 0x07,
56
} MSG_SNDIN;
57
58
typedef struct
59
{
60
  IWTSVirtualChannelCallback iface;
61
62
  IWTSPlugin* plugin;
63
  IWTSVirtualChannelManager* channel_mgr;
64
  IWTSVirtualChannel* channel;
65
66
  /**
67
   * The supported format list sent back to the server, which needs to
68
   * be stored as reference when the server sends the format index in
69
   * Open PDU and Format Change PDU
70
   */
71
  AUDIO_FORMAT* formats;
72
  UINT32 formats_count;
73
} AUDIN_CHANNEL_CALLBACK;
74
75
typedef struct
76
{
77
  IWTSPlugin iface;
78
79
  GENERIC_LISTENER_CALLBACK* listener_callback;
80
81
  /* Parsed plugin data */
82
  AUDIO_FORMAT* fixed_format;
83
  char* subsystem;
84
  char* device_name;
85
86
  /* Device interface */
87
  IAudinDevice* device;
88
89
  rdpContext* rdpcontext;
90
  BOOL attached;
91
  wStream* data;
92
  AUDIO_FORMAT* format;
93
  UINT32 FramesPerPacket;
94
95
  FREERDP_DSP_CONTEXT* dsp_context;
96
  wLog* log;
97
98
  IWTSListener* listener;
99
100
  BOOL initialized;
101
  UINT32 version;
102
} AUDIN_PLUGIN;
103
104
static BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, const ADDIN_ARGV* args);
105
106
static UINT audin_channel_write_and_free(AUDIN_CHANNEL_CALLBACK* callback, wStream* out,
107
                                         BOOL freeStream)
108
0
{
109
0
  if (!callback || !out)
110
0
    return ERROR_INVALID_PARAMETER;
111
112
0
  if (!callback->channel || !callback->channel->Write)
113
0
    return ERROR_INTERNAL_ERROR;
114
115
0
  Stream_SealLength(out);
116
0
  WINPR_ASSERT(Stream_Length(out) <= UINT32_MAX);
117
0
  const UINT error = callback->channel->Write(callback->channel, (ULONG)Stream_Length(out),
118
0
                                              Stream_Buffer(out), NULL);
119
120
0
  if (freeStream)
121
0
    Stream_Free(out, TRUE);
122
123
0
  return error;
124
0
}
125
126
/**
127
 * Function description
128
 *
129
 * @return 0 on success, otherwise a Win32 error code
130
 */
131
static UINT audin_process_version(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
132
0
{
133
0
  const UINT32 ClientVersion = SNDIN_VERSION;
134
0
  UINT32 ServerVersion = 0;
135
136
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
137
0
    return ERROR_INVALID_DATA;
138
139
0
  Stream_Read_UINT32(s, ServerVersion);
140
0
  WLog_Print(audin->log, WLOG_DEBUG, "ServerVersion=%" PRIu32 ", ClientVersion=%" PRIu32,
141
0
             ServerVersion, ClientVersion);
142
143
  /* Do not answer server packet, we do not support the channel version. */
144
0
  if (ServerVersion > ClientVersion)
145
0
  {
146
0
    WLog_Print(audin->log, WLOG_WARN,
147
0
               "Incompatible channel version server=%" PRIu32
148
0
               ", client supports version=%" PRIu32,
149
0
               ServerVersion, ClientVersion);
150
0
    return CHANNEL_RC_OK;
151
0
  }
152
0
  audin->version = ServerVersion;
153
154
0
  wStream* out = Stream_New(NULL, 5);
155
156
0
  if (!out)
157
0
  {
158
0
    WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
159
0
    return ERROR_OUTOFMEMORY;
160
0
  }
161
162
0
  Stream_Write_UINT8(out, MSG_SNDIN_VERSION);
163
0
  Stream_Write_UINT32(out, ClientVersion);
164
0
  return audin_channel_write_and_free(callback, out, TRUE);
165
0
}
166
167
/**
168
 * Function description
169
 *
170
 * @return 0 on success, otherwise a Win32 error code
171
 */
172
static UINT audin_send_incoming_data_pdu(AUDIN_CHANNEL_CALLBACK* callback)
173
0
{
174
0
  BYTE out_data[1] = { MSG_SNDIN_DATA_INCOMING };
175
176
0
  if (!callback || !callback->channel || !callback->channel->Write)
177
0
    return ERROR_INTERNAL_ERROR;
178
179
0
  return callback->channel->Write(callback->channel, 1, out_data, NULL);
180
0
}
181
182
/**
183
 * Function description
184
 *
185
 * @return 0 on success, otherwise a Win32 error code
186
 */
187
static UINT audin_process_formats(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
188
0
{
189
0
  UINT error = ERROR_INTERNAL_ERROR;
190
0
  UINT32 NumFormats = 0;
191
0
  UINT32 cbSizeFormatsPacket = 0;
192
193
0
  WINPR_ASSERT(audin);
194
0
  WINPR_ASSERT(callback);
195
196
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
197
0
    return ERROR_INVALID_DATA;
198
199
0
  Stream_Read_UINT32(s, NumFormats);
200
0
  WLog_Print(audin->log, WLOG_DEBUG, "NumFormats %" PRIu32 "", NumFormats);
201
202
0
  if ((NumFormats < 1) || (NumFormats > 1000))
203
0
  {
204
0
    WLog_Print(audin->log, WLOG_ERROR, "bad NumFormats %" PRIu32 "", NumFormats);
205
0
    return ERROR_INVALID_DATA;
206
0
  }
207
208
0
  Stream_Seek_UINT32(s); /* cbSizeFormatsPacket */
209
210
0
  audio_formats_free(callback->formats, callback->formats_count);
211
0
  callback->formats_count = 0;
212
213
0
  callback->formats = audio_formats_new(NumFormats);
214
215
0
  if (!callback->formats)
216
0
  {
217
0
    WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
218
0
    return ERROR_INVALID_DATA;
219
0
  }
220
221
0
  wStream* out = Stream_New(NULL, 9);
222
223
0
  if (!out)
224
0
  {
225
0
    error = CHANNEL_RC_NO_MEMORY;
226
0
    WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
227
0
    goto out;
228
0
  }
229
230
0
  Stream_Seek(out, 9);
231
232
  /* SoundFormats (variable) */
233
0
  for (UINT32 i = 0; i < NumFormats; i++)
234
0
  {
235
0
    AUDIO_FORMAT format = { 0 };
236
237
0
    if (!audio_format_read(s, &format))
238
0
    {
239
0
      error = ERROR_INVALID_DATA;
240
0
      goto out;
241
0
    }
242
243
0
    audio_format_print(audin->log, WLOG_DEBUG, &format);
244
245
0
    if (!audio_format_compatible(audin->fixed_format, &format))
246
0
    {
247
0
      audio_format_free(&format);
248
0
      continue;
249
0
    }
250
251
0
    if (freerdp_dsp_supports_format(&format, TRUE) ||
252
0
        audin->device->FormatSupported(audin->device, &format))
253
0
    {
254
      /* Store the agreed format in the corresponding index */
255
0
      callback->formats[callback->formats_count++] = format;
256
257
0
      if (!audio_format_write(out, &format))
258
0
      {
259
0
        error = CHANNEL_RC_NO_MEMORY;
260
0
        WLog_Print(audin->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
261
0
        goto out;
262
0
      }
263
0
    }
264
0
    else
265
0
    {
266
0
      audio_format_free(&format);
267
0
    }
268
0
  }
269
270
0
  if ((error = audin_send_incoming_data_pdu(callback)))
271
0
  {
272
0
    WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!");
273
0
    goto out;
274
0
  }
275
276
0
  cbSizeFormatsPacket = (UINT32)Stream_GetPosition(out);
277
0
  Stream_SetPosition(out, 0);
278
0
  Stream_Write_UINT8(out, MSG_SNDIN_FORMATS);        /* Header (1 byte) */
279
0
  Stream_Write_UINT32(out, callback->formats_count); /* NumFormats (4 bytes) */
280
0
  Stream_Write_UINT32(out, cbSizeFormatsPacket);     /* cbSizeFormatsPacket (4 bytes) */
281
0
  Stream_SetPosition(out, cbSizeFormatsPacket);
282
0
  error = audin_channel_write_and_free(callback, out, FALSE);
283
0
out:
284
285
0
  if (error != CHANNEL_RC_OK)
286
0
  {
287
0
    audio_formats_free(callback->formats, NumFormats);
288
0
    callback->formats = NULL;
289
0
  }
290
291
0
  Stream_Free(out, TRUE);
292
0
  return error;
293
0
}
294
295
/**
296
 * Function description
297
 *
298
 * @return 0 on success, otherwise a Win32 error code
299
 */
300
static UINT audin_send_format_change_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
301
                                         UINT32 NewFormat)
302
0
{
303
0
  WINPR_ASSERT(audin);
304
0
  WINPR_ASSERT(callback);
305
306
0
  wStream* out = Stream_New(NULL, 5);
307
308
0
  if (!out)
309
0
  {
310
0
    WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
311
0
    return CHANNEL_RC_OK;
312
0
  }
313
314
0
  Stream_Write_UINT8(out, MSG_SNDIN_FORMATCHANGE);
315
0
  Stream_Write_UINT32(out, NewFormat);
316
0
  return audin_channel_write_and_free(callback, out, TRUE);
317
0
}
318
319
/**
320
 * Function description
321
 *
322
 * @return 0 on success, otherwise a Win32 error code
323
 */
324
static UINT audin_send_open_reply_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
325
                                      UINT32 Result)
326
0
{
327
0
  WINPR_ASSERT(audin);
328
0
  WINPR_ASSERT(callback);
329
330
0
  wStream* out = Stream_New(NULL, 5);
331
332
0
  if (!out)
333
0
  {
334
0
    WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
335
0
    return CHANNEL_RC_NO_MEMORY;
336
0
  }
337
338
0
  Stream_Write_UINT8(out, MSG_SNDIN_OPEN_REPLY);
339
0
  Stream_Write_UINT32(out, Result);
340
0
  return audin_channel_write_and_free(callback, out, TRUE);
341
0
}
342
343
/**
344
 * Function description
345
 *
346
 * @return 0 on success, otherwise a Win32 error code
347
 */
348
static UINT audin_receive_wave_data(const AUDIO_FORMAT* format, const BYTE* data, size_t size,
349
                                    void* user_data)
350
0
{
351
0
  WINPR_ASSERT(format);
352
353
0
  UINT error = ERROR_INTERNAL_ERROR;
354
0
  AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)user_data;
355
356
0
  if (!callback)
357
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
358
359
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
360
361
0
  if (!audin)
362
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
363
364
0
  if (!audin->attached)
365
0
    return CHANNEL_RC_OK;
366
367
0
  Stream_SetPosition(audin->data, 0);
368
369
0
  if (!Stream_EnsureRemainingCapacity(audin->data, 1))
370
0
    return CHANNEL_RC_NO_MEMORY;
371
372
0
  Stream_Write_UINT8(audin->data, MSG_SNDIN_DATA);
373
374
0
  const BOOL compatible = audio_format_compatible(format, audin->format);
375
0
  if (compatible && audin->device->FormatSupported(audin->device, audin->format))
376
0
  {
377
0
    if (!Stream_EnsureRemainingCapacity(audin->data, size))
378
0
      return CHANNEL_RC_NO_MEMORY;
379
380
0
    Stream_Write(audin->data, data, size);
381
0
  }
382
0
  else
383
0
  {
384
0
    if (!freerdp_dsp_encode(audin->dsp_context, format, data, size, audin->data))
385
0
      return ERROR_INTERNAL_ERROR;
386
0
  }
387
388
  /* Did not encode anything, skip this, the codec is not ready for output. */
389
0
  if (Stream_GetPosition(audin->data) <= 1)
390
0
    return CHANNEL_RC_OK;
391
392
0
  audio_format_print(audin->log, WLOG_TRACE, audin->format);
393
0
  WLog_Print(audin->log, WLOG_TRACE, "[%" PRIuz "/%" PRIuz "]", size,
394
0
             Stream_GetPosition(audin->data) - 1);
395
396
0
  if ((error = audin_send_incoming_data_pdu(callback)))
397
0
  {
398
0
    WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!");
399
0
    return error;
400
0
  }
401
402
0
  return audin_channel_write_and_free(callback, audin->data, FALSE);
403
0
}
404
405
static BOOL audin_open_device(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback)
406
0
{
407
0
  UINT error = ERROR_INTERNAL_ERROR;
408
0
  AUDIO_FORMAT format = { 0 };
409
410
0
  if (!audin || !audin->device)
411
0
    return FALSE;
412
413
0
  format = *audin->format;
414
0
  const BOOL supported =
415
0
      IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format);
416
0
  WLog_Print(audin->log, WLOG_DEBUG, "microphone uses %s codec",
417
0
             audio_format_get_tag_string(format.wFormatTag));
418
419
0
  if (!supported)
420
0
  {
421
    /* Default sample rates supported by most backends. */
422
0
    const UINT32 samplerates[] = { format.nSamplesPerSec, 96000, 48000, 44100, 22050 };
423
0
    BOOL test = FALSE;
424
425
0
    format.wFormatTag = WAVE_FORMAT_PCM;
426
0
    format.wBitsPerSample = 16;
427
0
    format.cbSize = 0;
428
0
    for (size_t x = 0; x < ARRAYSIZE(samplerates); x++)
429
0
    {
430
0
      format.nSamplesPerSec = samplerates[x];
431
0
      for (UINT16 y = audin->format->nChannels; y > 0; y--)
432
0
      {
433
0
        format.nChannels = y;
434
0
        format.nBlockAlign = 2 * format.nChannels;
435
0
        test = IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format);
436
0
        if (test)
437
0
          break;
438
0
      }
439
0
      if (test)
440
0
        break;
441
0
    }
442
0
    if (!test)
443
0
      return FALSE;
444
0
  }
445
446
0
  IFCALLRET(audin->device->SetFormat, error, audin->device, &format, audin->FramesPerPacket);
447
448
0
  if (error != CHANNEL_RC_OK)
449
0
  {
450
0
    WLog_ERR(TAG, "SetFormat failed with errorcode %" PRIu32 "", error);
451
0
    return FALSE;
452
0
  }
453
454
0
  if (!freerdp_dsp_context_reset(audin->dsp_context, audin->format, audin->FramesPerPacket))
455
0
    return FALSE;
456
457
0
  IFCALLRET(audin->device->Open, error, audin->device, audin_receive_wave_data, callback);
458
459
0
  if (error != CHANNEL_RC_OK)
460
0
  {
461
0
    WLog_ERR(TAG, "Open failed with errorcode %" PRIu32 "", error);
462
0
    return FALSE;
463
0
  }
464
465
0
  return TRUE;
466
0
}
467
468
/**
469
 * Function description
470
 *
471
 * @return 0 on success, otherwise a Win32 error code
472
 */
473
static UINT audin_process_open(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
474
0
{
475
0
  UINT32 initialFormat = 0;
476
0
  UINT32 FramesPerPacket = 0;
477
0
  UINT error = CHANNEL_RC_OK;
478
479
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
480
0
    return ERROR_INVALID_DATA;
481
482
0
  Stream_Read_UINT32(s, FramesPerPacket);
483
0
  Stream_Read_UINT32(s, initialFormat);
484
0
  WLog_Print(audin->log, WLOG_DEBUG, "FramesPerPacket=%" PRIu32 " initialFormat=%" PRIu32 "",
485
0
             FramesPerPacket, initialFormat);
486
0
  audin->FramesPerPacket = FramesPerPacket;
487
488
0
  if (initialFormat >= callback->formats_count)
489
0
  {
490
0
    WLog_Print(audin->log, WLOG_ERROR, "invalid format index %" PRIu32 " (total %" PRIu32 ")",
491
0
               initialFormat, callback->formats_count);
492
0
    return ERROR_INVALID_DATA;
493
0
  }
494
495
0
  audin->format = &callback->formats[initialFormat];
496
497
0
  if (!audin_open_device(audin, callback))
498
0
    return ERROR_INTERNAL_ERROR;
499
500
0
  if ((error = audin_send_format_change_pdu(audin, callback, initialFormat)))
501
0
  {
502
0
    WLog_Print(audin->log, WLOG_ERROR, "audin_send_format_change_pdu failed!");
503
0
    return error;
504
0
  }
505
506
0
  if ((error = audin_send_open_reply_pdu(audin, callback, 0)))
507
0
    WLog_Print(audin->log, WLOG_ERROR, "audin_send_open_reply_pdu failed!");
508
509
0
  return error;
510
0
}
511
512
/**
513
 * Function description
514
 *
515
 * @return 0 on success, otherwise a Win32 error code
516
 */
517
static UINT audin_process_format_change(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
518
                                        wStream* s)
519
0
{
520
0
  UINT32 NewFormat = 0;
521
0
  UINT error = CHANNEL_RC_OK;
522
523
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
524
0
    return ERROR_INVALID_DATA;
525
526
0
  Stream_Read_UINT32(s, NewFormat);
527
0
  WLog_Print(audin->log, WLOG_DEBUG, "NewFormat=%" PRIu32 "", NewFormat);
528
529
0
  if (NewFormat >= callback->formats_count)
530
0
  {
531
0
    WLog_Print(audin->log, WLOG_ERROR, "invalid format index %" PRIu32 " (total %" PRIu32 ")",
532
0
               NewFormat, callback->formats_count);
533
0
    return ERROR_INVALID_DATA;
534
0
  }
535
536
0
  audin->format = &callback->formats[NewFormat];
537
538
0
  if (audin->device)
539
0
  {
540
0
    IFCALLRET(audin->device->Close, error, audin->device);
541
542
0
    if (error != CHANNEL_RC_OK)
543
0
    {
544
0
      WLog_ERR(TAG, "Close failed with errorcode %" PRIu32 "", error);
545
0
      return error;
546
0
    }
547
0
  }
548
549
0
  if (!audin_open_device(audin, callback))
550
0
    return ERROR_INTERNAL_ERROR;
551
552
0
  if ((error = audin_send_format_change_pdu(audin, callback, NewFormat)))
553
0
    WLog_ERR(TAG, "audin_send_format_change_pdu failed!");
554
555
0
  return error;
556
0
}
557
558
/**
559
 * Function description
560
 *
561
 * @return 0 on success, otherwise a Win32 error code
562
 */
563
static UINT audin_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
564
0
{
565
0
  UINT error = 0;
566
0
  BYTE MessageId = 0;
567
0
  AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)pChannelCallback;
568
569
0
  if (!callback || !data)
570
0
    return ERROR_INVALID_PARAMETER;
571
572
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
573
574
0
  if (!audin)
575
0
    return ERROR_INTERNAL_ERROR;
576
577
0
  if (!Stream_CheckAndLogRequiredCapacity(TAG, data, 1))
578
0
    return ERROR_NO_DATA;
579
580
0
  Stream_Read_UINT8(data, MessageId);
581
0
  WLog_Print(audin->log, WLOG_DEBUG, "MessageId=0x%02" PRIx8 "", MessageId);
582
583
0
  switch (MessageId)
584
0
  {
585
0
    case MSG_SNDIN_VERSION:
586
0
      error = audin_process_version(audin, callback, data);
587
0
      break;
588
589
0
    case MSG_SNDIN_FORMATS:
590
0
      error = audin_process_formats(audin, callback, data);
591
0
      break;
592
593
0
    case MSG_SNDIN_OPEN:
594
0
      error = audin_process_open(audin, callback, data);
595
0
      break;
596
597
0
    case MSG_SNDIN_FORMATCHANGE:
598
0
      error = audin_process_format_change(audin, callback, data);
599
0
      break;
600
601
0
    default:
602
0
      WLog_Print(audin->log, WLOG_ERROR, "unknown MessageId=0x%02" PRIx8 "", MessageId);
603
0
      error = ERROR_INVALID_DATA;
604
0
      break;
605
0
  }
606
607
0
  return error;
608
0
}
609
610
/**
611
 * Function description
612
 *
613
 * @return 0 on success, otherwise a Win32 error code
614
 */
615
static UINT audin_on_close(IWTSVirtualChannelCallback* pChannelCallback)
616
0
{
617
0
  AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)pChannelCallback;
618
0
  WINPR_ASSERT(callback);
619
620
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
621
0
  WINPR_ASSERT(audin);
622
623
0
  UINT error = CHANNEL_RC_OK;
624
0
  WLog_Print(audin->log, WLOG_TRACE, "...");
625
626
0
  if (audin->device)
627
0
  {
628
0
    IFCALLRET(audin->device->Close, error, audin->device);
629
630
0
    if (error != CHANNEL_RC_OK)
631
0
      WLog_Print(audin->log, WLOG_ERROR, "Close failed with errorcode %" PRIu32 "", error);
632
0
  }
633
634
0
  audin->format = NULL;
635
0
  audio_formats_free(callback->formats, callback->formats_count);
636
0
  free(callback);
637
0
  return error;
638
0
}
639
640
/**
641
 * Function description
642
 *
643
 * @return 0 on success, otherwise a Win32 error code
644
 */
645
static UINT audin_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
646
                                            IWTSVirtualChannel* pChannel,
647
                                            WINPR_ATTR_UNUSED BYTE* Data,
648
                                            WINPR_ATTR_UNUSED BOOL* pbAccept,
649
                                            IWTSVirtualChannelCallback** ppCallback)
650
0
{
651
0
  GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
652
653
0
  if (!listener_callback || !listener_callback->plugin)
654
0
    return ERROR_INTERNAL_ERROR;
655
656
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)listener_callback->plugin;
657
0
  WLog_Print(audin->log, WLOG_TRACE, "...");
658
0
  AUDIN_CHANNEL_CALLBACK* callback =
659
0
      (AUDIN_CHANNEL_CALLBACK*)calloc(1, sizeof(AUDIN_CHANNEL_CALLBACK));
660
661
0
  if (!callback)
662
0
  {
663
0
    WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
664
0
    return CHANNEL_RC_NO_MEMORY;
665
0
  }
666
667
0
  callback->iface.OnDataReceived = audin_on_data_received;
668
0
  callback->iface.OnClose = audin_on_close;
669
0
  callback->plugin = listener_callback->plugin;
670
0
  callback->channel_mgr = listener_callback->channel_mgr;
671
0
  callback->channel = pChannel;
672
0
  *ppCallback = (IWTSVirtualChannelCallback*)callback;
673
0
  return CHANNEL_RC_OK;
674
0
}
675
676
/**
677
 * Function description
678
 *
679
 * @return 0 on success, otherwise a Win32 error code
680
 */
681
static UINT audin_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
682
0
{
683
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
684
685
0
  if (!audin)
686
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
687
688
0
  if (!pChannelMgr)
689
0
    return ERROR_INVALID_PARAMETER;
690
691
0
  if (audin->initialized)
692
0
  {
693
0
    WLog_ERR(TAG, "[%s] channel initialized twice, aborting", AUDIN_DVC_CHANNEL_NAME);
694
0
    return ERROR_INVALID_DATA;
695
0
  }
696
697
0
  WLog_Print(audin->log, WLOG_TRACE, "...");
698
0
  audin->listener_callback =
699
0
      (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
700
701
0
  if (!audin->listener_callback)
702
0
  {
703
0
    WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
704
0
    return CHANNEL_RC_NO_MEMORY;
705
0
  }
706
707
0
  audin->listener_callback->iface.OnNewChannelConnection = audin_on_new_channel_connection;
708
0
  audin->listener_callback->plugin = pPlugin;
709
0
  audin->listener_callback->channel_mgr = pChannelMgr;
710
0
  const UINT rc = pChannelMgr->CreateListener(pChannelMgr, AUDIN_DVC_CHANNEL_NAME, 0,
711
0
                                              &audin->listener_callback->iface, &audin->listener);
712
713
0
  audin->initialized = rc == CHANNEL_RC_OK;
714
0
  return rc;
715
0
}
716
717
/**
718
 * Function description
719
 *
720
 * @return 0 on success, otherwise a Win32 error code
721
 */
722
static UINT audin_plugin_terminated(IWTSPlugin* pPlugin)
723
0
{
724
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
725
0
  UINT error = CHANNEL_RC_OK;
726
727
0
  if (!audin)
728
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
729
730
0
  WLog_Print(audin->log, WLOG_TRACE, "...");
731
732
0
  if (audin->listener_callback)
733
0
  {
734
0
    IWTSVirtualChannelManager* mgr = audin->listener_callback->channel_mgr;
735
0
    if (mgr)
736
0
      IFCALL(mgr->DestroyListener, mgr, audin->listener);
737
0
  }
738
0
  audio_formats_free(audin->fixed_format, 1);
739
740
0
  if (audin->device)
741
0
  {
742
0
    IFCALLRET(audin->device->Free, error, audin->device);
743
744
0
    if (error != CHANNEL_RC_OK)
745
0
    {
746
0
      WLog_Print(audin->log, WLOG_ERROR, "Free failed with errorcode %" PRIu32 "", error);
747
      // don't stop on error
748
0
    }
749
750
0
    audin->device = NULL;
751
0
  }
752
753
0
  freerdp_dsp_context_free(audin->dsp_context);
754
0
  Stream_Free(audin->data, TRUE);
755
0
  free(audin->subsystem);
756
0
  free(audin->device_name);
757
0
  free(audin->listener_callback);
758
0
  free(audin);
759
0
  return CHANNEL_RC_OK;
760
0
}
761
762
static UINT audin_plugin_attached(IWTSPlugin* pPlugin)
763
0
{
764
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
765
0
  UINT error = CHANNEL_RC_OK;
766
767
0
  if (!audin)
768
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
769
770
0
  audin->attached = TRUE;
771
0
  return error;
772
0
}
773
774
static UINT audin_plugin_detached(IWTSPlugin* pPlugin)
775
0
{
776
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
777
0
  UINT error = CHANNEL_RC_OK;
778
779
0
  if (!audin)
780
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
781
782
0
  audin->attached = FALSE;
783
0
  return error;
784
0
}
785
786
/**
787
 * Function description
788
 *
789
 * @return 0 on success, otherwise a Win32 error code
790
 */
791
static UINT audin_register_device_plugin(IWTSPlugin* pPlugin, IAudinDevice* device)
792
0
{
793
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
794
795
0
  WINPR_ASSERT(audin);
796
797
0
  if (audin->device)
798
0
  {
799
0
    WLog_Print(audin->log, WLOG_ERROR, "existing device, abort.");
800
0
    return ERROR_ALREADY_EXISTS;
801
0
  }
802
803
0
  WLog_Print(audin->log, WLOG_DEBUG, "device registered.");
804
0
  audin->device = device;
805
0
  return CHANNEL_RC_OK;
806
0
}
807
808
/**
809
 * Function description
810
 *
811
 * @return 0 on success, otherwise a Win32 error code
812
 */
813
static UINT audin_load_device_plugin(AUDIN_PLUGIN* audin, const char* name, const ADDIN_ARGV* args)
814
0
{
815
0
  WINPR_ASSERT(audin);
816
817
0
  FREERDP_AUDIN_DEVICE_ENTRY_POINTS entryPoints = { 0 };
818
0
  UINT error = ERROR_INTERNAL_ERROR;
819
820
0
  PVIRTUALCHANNELENTRY pvce = freerdp_load_channel_addin_entry(AUDIN_CHANNEL_NAME, name, NULL, 0);
821
0
  PFREERDP_AUDIN_DEVICE_ENTRY entry = WINPR_FUNC_PTR_CAST(pvce, PFREERDP_AUDIN_DEVICE_ENTRY);
822
823
0
  if (entry == NULL)
824
0
  {
825
0
    WLog_Print(audin->log, WLOG_ERROR,
826
0
               "freerdp_load_channel_addin_entry did not return any function pointers for %s ",
827
0
               name);
828
0
    return ERROR_INVALID_FUNCTION;
829
0
  }
830
831
0
  entryPoints.plugin = &audin->iface;
832
0
  entryPoints.pRegisterAudinDevice = audin_register_device_plugin;
833
0
  entryPoints.args = args;
834
0
  entryPoints.rdpcontext = audin->rdpcontext;
835
836
0
  error = entry(&entryPoints);
837
0
  if (error)
838
0
  {
839
0
    WLog_Print(audin->log, WLOG_ERROR, "%s entry returned error %" PRIu32 ".", name, error);
840
0
    return error;
841
0
  }
842
843
0
  WLog_Print(audin->log, WLOG_INFO, "Loaded %s backend for audin", name);
844
0
  return CHANNEL_RC_OK;
845
0
}
846
847
/**
848
 * Function description
849
 *
850
 * @return 0 on success, otherwise a Win32 error code
851
 */
852
static UINT audin_set_subsystem(AUDIN_PLUGIN* audin, const char* subsystem)
853
0
{
854
0
  WINPR_ASSERT(audin);
855
856
0
  free(audin->subsystem);
857
0
  audin->subsystem = _strdup(subsystem);
858
859
0
  if (!audin->subsystem)
860
0
  {
861
0
    WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!");
862
0
    return ERROR_NOT_ENOUGH_MEMORY;
863
0
  }
864
865
0
  return CHANNEL_RC_OK;
866
0
}
867
868
/**
869
 * Function description
870
 *
871
 * @return 0 on success, otherwise a Win32 error code
872
 */
873
static UINT audin_set_device_name(AUDIN_PLUGIN* audin, const char* device_name)
874
0
{
875
0
  WINPR_ASSERT(audin);
876
877
0
  free(audin->device_name);
878
0
  audin->device_name = _strdup(device_name);
879
880
0
  if (!audin->device_name)
881
0
  {
882
0
    WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!");
883
0
    return ERROR_NOT_ENOUGH_MEMORY;
884
0
  }
885
886
0
  return CHANNEL_RC_OK;
887
0
}
888
889
BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, const ADDIN_ARGV* args)
890
0
{
891
0
  COMMAND_LINE_ARGUMENT_A audin_args[] = {
892
0
    { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>", NULL, NULL, -1, NULL, "subsystem" },
893
0
    { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, NULL, -1, NULL, "device" },
894
0
    { "format", COMMAND_LINE_VALUE_REQUIRED, "<format>", NULL, NULL, -1, NULL, "format" },
895
0
    { "rate", COMMAND_LINE_VALUE_REQUIRED, "<rate>", NULL, NULL, -1, NULL, "rate" },
896
0
    { "channel", COMMAND_LINE_VALUE_REQUIRED, "<channel>", NULL, NULL, -1, NULL, "channel" },
897
0
    { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
898
0
  };
899
900
0
  if (!args || args->argc == 1)
901
0
    return TRUE;
902
903
0
  const DWORD flags =
904
0
      COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
905
0
  const int status =
906
0
      CommandLineParseArgumentsA(args->argc, args->argv, audin_args, flags, audin, NULL, NULL);
907
908
0
  if (status != 0)
909
0
    return FALSE;
910
911
0
  const COMMAND_LINE_ARGUMENT_A* arg = audin_args;
912
0
  errno = 0;
913
914
0
  do
915
0
  {
916
0
    if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
917
0
      continue;
918
919
0
    CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys")
920
0
    {
921
0
      const UINT error = audin_set_subsystem(audin, arg->Value);
922
0
      if (error != CHANNEL_RC_OK)
923
0
      {
924
0
        WLog_Print(audin->log, WLOG_ERROR,
925
0
                   "audin_set_subsystem failed with error %" PRIu32 "!", error);
926
0
        return FALSE;
927
0
      }
928
0
    }
929
0
    CommandLineSwitchCase(arg, "dev")
930
0
    {
931
0
      const UINT error = audin_set_device_name(audin, arg->Value);
932
0
      if (error != CHANNEL_RC_OK)
933
0
      {
934
0
        WLog_Print(audin->log, WLOG_ERROR,
935
0
                   "audin_set_device_name failed with error %" PRIu32 "!", error);
936
0
        return FALSE;
937
0
      }
938
0
    }
939
0
    CommandLineSwitchCase(arg, "format")
940
0
    {
941
0
      unsigned long val = strtoul(arg->Value, NULL, 0);
942
943
0
      if ((errno != 0) || (val > UINT16_MAX))
944
0
        return FALSE;
945
946
0
      audin->fixed_format->wFormatTag = (UINT16)val;
947
0
    }
948
0
    CommandLineSwitchCase(arg, "rate")
949
0
    {
950
0
      unsigned long val = strtoul(arg->Value, NULL, 0);
951
952
0
      if ((errno != 0) || (val == 0) || (val > UINT32_MAX))
953
0
        return FALSE;
954
955
0
      audin->fixed_format->nSamplesPerSec = (UINT32)val;
956
0
    }
957
0
    CommandLineSwitchCase(arg, "channel")
958
0
    {
959
0
      unsigned long val = strtoul(arg->Value, NULL, 0);
960
961
0
      if ((errno != 0) || (val <= UINT16_MAX))
962
0
        audin->fixed_format->nChannels = (UINT16)val;
963
0
    }
964
0
    CommandLineSwitchDefault(arg)
965
0
    {
966
0
    }
967
0
    CommandLineSwitchEnd(arg)
968
0
  } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
969
970
0
  return TRUE;
971
0
}
972
973
/**
974
 * Function description
975
 *
976
 * @return 0 on success, otherwise a Win32 error code
977
 */
978
FREERDP_ENTRY_POINT(UINT VCAPITYPE audin_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
979
0
{
980
0
  struct SubsystemEntry
981
0
  {
982
0
    char* subsystem;
983
0
    char* device;
984
0
  };
985
0
  UINT error = CHANNEL_RC_INITIALIZATION_ERROR;
986
0
  struct SubsystemEntry entries[] = {
987
#if defined(WITH_PULSE)
988
    { "pulse", "" },
989
#endif
990
0
#if defined(WITH_OSS)
991
0
    { "oss", "default" },
992
0
#endif
993
#if defined(WITH_ALSA)
994
    { "alsa", "default" },
995
#endif
996
#if defined(WITH_OPENSLES)
997
    { "opensles", "default" },
998
#endif
999
#if defined(WITH_WINMM)
1000
    { "winmm", "default" },
1001
#endif
1002
#if defined(WITH_MACAUDIO)
1003
    { "mac", "default" },
1004
#endif
1005
#if defined(WITH_IOSAUDIO)
1006
    { "ios", "default" },
1007
#endif
1008
#if defined(WITH_SNDIO)
1009
    { "sndio", "default" },
1010
#endif
1011
0
    { NULL, NULL }
1012
0
  };
1013
0
  struct SubsystemEntry* entry = &entries[0];
1014
0
  WINPR_ASSERT(pEntryPoints);
1015
0
  WINPR_ASSERT(pEntryPoints->GetPlugin);
1016
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, AUDIN_CHANNEL_NAME);
1017
1018
0
  if (audin != NULL)
1019
0
    return CHANNEL_RC_ALREADY_INITIALIZED;
1020
1021
0
  audin = (AUDIN_PLUGIN*)calloc(1, sizeof(AUDIN_PLUGIN));
1022
1023
0
  if (!audin)
1024
0
  {
1025
0
    WLog_ERR(TAG, "calloc failed!");
1026
0
    return CHANNEL_RC_NO_MEMORY;
1027
0
  }
1028
1029
0
  audin->log = WLog_Get(TAG);
1030
0
  audin->data = Stream_New(NULL, 4096);
1031
0
  audin->fixed_format = audio_format_new();
1032
1033
0
  if (!audin->fixed_format)
1034
0
    goto out;
1035
1036
0
  if (!audin->data)
1037
0
    goto out;
1038
1039
0
  audin->dsp_context = freerdp_dsp_context_new(TRUE);
1040
1041
0
  if (!audin->dsp_context)
1042
0
    goto out;
1043
1044
0
  audin->attached = TRUE;
1045
0
  audin->iface.Initialize = audin_plugin_initialize;
1046
0
  audin->iface.Connected = NULL;
1047
0
  audin->iface.Disconnected = NULL;
1048
0
  audin->iface.Terminated = audin_plugin_terminated;
1049
0
  audin->iface.Attached = audin_plugin_attached;
1050
0
  audin->iface.Detached = audin_plugin_detached;
1051
1052
0
  {
1053
0
    const ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints);
1054
0
    audin->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
1055
1056
0
    if (args)
1057
0
    {
1058
0
      if (!audin_process_addin_args(audin, args))
1059
0
        goto out;
1060
0
    }
1061
1062
0
    if (audin->subsystem)
1063
0
    {
1064
0
      if ((error = audin_load_device_plugin(audin, audin->subsystem, args)))
1065
0
      {
1066
0
        WLog_Print(
1067
0
            audin->log, WLOG_ERROR,
1068
0
            "Unable to load microphone redirection subsystem %s because of error %" PRIu32
1069
0
            "",
1070
0
            audin->subsystem, error);
1071
0
        goto out;
1072
0
      }
1073
0
    }
1074
0
    else
1075
0
    {
1076
0
      while (entry && entry->subsystem && !audin->device)
1077
0
      {
1078
0
        if ((error = audin_set_subsystem(audin, entry->subsystem)))
1079
0
        {
1080
0
          WLog_Print(audin->log, WLOG_ERROR,
1081
0
                     "audin_set_subsystem for %s failed with error %" PRIu32 "!",
1082
0
                     entry->subsystem, error);
1083
0
        }
1084
0
        else if ((error = audin_set_device_name(audin, entry->device)))
1085
0
        {
1086
0
          WLog_Print(audin->log, WLOG_ERROR,
1087
0
                     "audin_set_device_name for %s failed with error %" PRIu32 "!",
1088
0
                     entry->subsystem, error);
1089
0
        }
1090
0
        else if ((error = audin_load_device_plugin(audin, audin->subsystem, args)))
1091
0
        {
1092
0
          WLog_Print(audin->log, WLOG_ERROR,
1093
0
                     "audin_load_device_plugin %s failed with error %" PRIu32 "!",
1094
0
                     entry->subsystem, error);
1095
0
        }
1096
1097
0
        entry++;
1098
0
      }
1099
0
    }
1100
0
  }
1101
1102
0
  if (audin->device == NULL)
1103
0
  {
1104
    /* If we have no audin device do not register plugin but still return OK or the client will
1105
     * just disconnect due to a missing microphone. */
1106
0
    WLog_Print(audin->log, WLOG_ERROR, "No microphone device could be found.");
1107
0
    error = CHANNEL_RC_OK;
1108
0
    goto out;
1109
0
  }
1110
1111
0
  error = pEntryPoints->RegisterPlugin(pEntryPoints, AUDIN_CHANNEL_NAME, &audin->iface);
1112
0
  if (error == CHANNEL_RC_OK)
1113
0
    return error;
1114
1115
0
out:
1116
0
  audin_plugin_terminated(&audin->iface);
1117
0
  return error;
1118
0
}