Coverage Report

Created: 2026-10-28 06:54

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
  BOOL connected;
103
} AUDIN_PLUGIN;
104
105
static BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, const ADDIN_ARGV* args);
106
107
static UINT audin_channel_write_and_free(AUDIN_CHANNEL_CALLBACK* callback, wStream* out,
108
                                         BOOL freeStream)
109
0
{
110
0
  if (!callback || !out)
111
0
    return ERROR_INVALID_PARAMETER;
112
113
0
  if (!callback->channel || !callback->channel->Write)
114
0
    return ERROR_INTERNAL_ERROR;
115
116
0
  Stream_SealLength(out);
117
118
0
  const ULONG len = WINPR_ASSERTING_INT_CAST(ULONG, Stream_Length(out));
119
0
  const UINT error =
120
0
      callback->channel->Write(callback->channel, len, Stream_Buffer(out), nullptr);
121
122
0
  if (freeStream)
123
0
    Stream_Free(out, TRUE);
124
125
0
  return error;
126
0
}
127
128
/**
129
 * Function description
130
 *
131
 * @return 0 on success, otherwise a Win32 error code
132
 */
133
static UINT audin_process_version(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
134
0
{
135
0
  const UINT32 ClientVersion = SNDIN_VERSION;
136
0
  UINT32 ServerVersion = 0;
137
138
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
139
0
    return ERROR_INVALID_DATA;
140
141
0
  Stream_Read_UINT32(s, ServerVersion);
142
0
  WLog_Print(audin->log, WLOG_DEBUG, "ServerVersion=%" PRIu32 ", ClientVersion=%" PRIu32,
143
0
             ServerVersion, ClientVersion);
144
145
  /* Do not answer server packet, we do not support the channel version. */
146
0
  if (ServerVersion > ClientVersion)
147
0
  {
148
0
    WLog_Print(audin->log, WLOG_WARN,
149
0
               "Incompatible channel version server=%" PRIu32
150
0
               ", client supports version=%" PRIu32,
151
0
               ServerVersion, ClientVersion);
152
0
    return CHANNEL_RC_OK;
153
0
  }
154
0
  audin->version = ServerVersion;
155
156
0
  wStream* out = Stream_New(nullptr, 5);
157
158
0
  if (!out)
159
0
  {
160
0
    WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
161
0
    return ERROR_OUTOFMEMORY;
162
0
  }
163
164
0
  Stream_Write_UINT8(out, MSG_SNDIN_VERSION);
165
0
  Stream_Write_UINT32(out, ClientVersion);
166
0
  return audin_channel_write_and_free(callback, out, TRUE);
167
0
}
168
169
/**
170
 * Function description
171
 *
172
 * @return 0 on success, otherwise a Win32 error code
173
 */
174
static UINT audin_send_incoming_data_pdu(AUDIN_CHANNEL_CALLBACK* callback)
175
0
{
176
0
  BYTE out_data[1] = { MSG_SNDIN_DATA_INCOMING };
177
178
0
  if (!callback || !callback->channel || !callback->channel->Write)
179
0
    return ERROR_INTERNAL_ERROR;
180
181
0
  return callback->channel->Write(callback->channel, 1, out_data, nullptr);
182
0
}
183
184
/**
185
 * Function description
186
 *
187
 * @return 0 on success, otherwise a Win32 error code
188
 */
189
static UINT audin_process_formats(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
190
0
{
191
0
  UINT error = ERROR_INTERNAL_ERROR;
192
0
  UINT32 NumFormats = 0;
193
0
  UINT32 cbSizeFormatsPacket = 0;
194
195
0
  WINPR_ASSERT(audin);
196
0
  WINPR_ASSERT(callback);
197
198
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
199
0
    return ERROR_INVALID_DATA;
200
201
0
  Stream_Read_UINT32(s, NumFormats);
202
0
  WLog_Print(audin->log, WLOG_DEBUG, "NumFormats %" PRIu32 "", NumFormats);
203
204
0
  if ((NumFormats < 1) || (NumFormats > 1000))
205
0
  {
206
0
    WLog_Print(audin->log, WLOG_ERROR, "bad NumFormats %" PRIu32 "", NumFormats);
207
0
    return ERROR_INVALID_DATA;
208
0
  }
209
210
0
  Stream_Seek_UINT32(s); /* cbSizeFormatsPacket */
211
212
0
  audin->format = nullptr;
213
0
  audio_formats_free(callback->formats, callback->formats_count);
214
0
  callback->formats_count = 0;
215
216
0
  callback->formats = audio_formats_new(NumFormats);
217
218
0
  if (!callback->formats)
219
0
  {
220
0
    WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
221
0
    return ERROR_INVALID_DATA;
222
0
  }
223
224
0
  wStream* out = Stream_New(nullptr, 9);
225
226
0
  if (!out)
227
0
  {
228
0
    error = CHANNEL_RC_NO_MEMORY;
229
0
    WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
230
0
    goto out;
231
0
  }
232
233
0
  Stream_Seek(out, 9);
234
235
  /* SoundFormats (variable) */
236
0
  for (UINT32 i = 0; i < NumFormats; i++)
237
0
  {
238
0
    AUDIO_FORMAT format = WINPR_C_ARRAY_INIT;
239
240
0
    if (!audio_format_read(s, &format))
241
0
    {
242
0
      error = ERROR_INVALID_DATA;
243
0
      goto out;
244
0
    }
245
246
0
    audio_format_print(audin->log, WLOG_DEBUG, &format);
247
248
0
    if (!audio_format_compatible(audin->fixed_format, &format))
249
0
    {
250
0
      audio_format_free(&format);
251
0
      continue;
252
0
    }
253
254
0
    if (freerdp_dsp_supports_format(&format, TRUE) ||
255
0
        audin->device->FormatSupported(audin->device, &format))
256
0
    {
257
      /* Store the agreed format in the corresponding index */
258
0
      callback->formats[callback->formats_count++] = format;
259
260
0
      if (!audio_format_write(out, &format))
261
0
      {
262
0
        error = CHANNEL_RC_NO_MEMORY;
263
0
        WLog_Print(audin->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
264
0
        goto out;
265
0
      }
266
0
    }
267
0
    else
268
0
    {
269
0
      audio_format_free(&format);
270
0
    }
271
0
  }
272
273
0
  if ((error = audin_send_incoming_data_pdu(callback)))
274
0
  {
275
0
    WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!");
276
0
    goto out;
277
0
  }
278
279
0
  cbSizeFormatsPacket = (UINT32)Stream_GetPosition(out);
280
0
  Stream_ResetPosition(out);
281
0
  Stream_Write_UINT8(out, MSG_SNDIN_FORMATS);        /* Header (1 byte) */
282
0
  Stream_Write_UINT32(out, callback->formats_count); /* NumFormats (4 bytes) */
283
0
  Stream_Write_UINT32(out, cbSizeFormatsPacket);     /* cbSizeFormatsPacket (4 bytes) */
284
0
  if (!Stream_SetPosition(out, cbSizeFormatsPacket))
285
0
    goto out;
286
0
  error = audin_channel_write_and_free(callback, out, FALSE);
287
0
out:
288
289
0
  if (error != CHANNEL_RC_OK)
290
0
  {
291
0
    audin->format = nullptr;
292
0
    audio_formats_free(callback->formats, NumFormats);
293
0
    callback->formats = nullptr;
294
0
  }
295
296
0
  Stream_Free(out, TRUE);
297
0
  return error;
298
0
}
299
300
/**
301
 * Function description
302
 *
303
 * @return 0 on success, otherwise a Win32 error code
304
 */
305
static UINT audin_send_format_change_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
306
                                         UINT32 NewFormat)
307
0
{
308
0
  WINPR_ASSERT(audin);
309
0
  WINPR_ASSERT(callback);
310
311
0
  wStream* out = Stream_New(nullptr, 5);
312
313
0
  if (!out)
314
0
  {
315
0
    WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
316
0
    return CHANNEL_RC_OK;
317
0
  }
318
319
0
  Stream_Write_UINT8(out, MSG_SNDIN_FORMATCHANGE);
320
0
  Stream_Write_UINT32(out, NewFormat);
321
0
  return audin_channel_write_and_free(callback, out, TRUE);
322
0
}
323
324
/**
325
 * Function description
326
 *
327
 * @return 0 on success, otherwise a Win32 error code
328
 */
329
static UINT audin_send_open_reply_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
330
                                      UINT32 Result)
331
0
{
332
0
  WINPR_ASSERT(audin);
333
0
  WINPR_ASSERT(callback);
334
335
0
  wStream* out = Stream_New(nullptr, 5);
336
337
0
  if (!out)
338
0
  {
339
0
    WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
340
0
    return CHANNEL_RC_NO_MEMORY;
341
0
  }
342
343
0
  Stream_Write_UINT8(out, MSG_SNDIN_OPEN_REPLY);
344
0
  Stream_Write_UINT32(out, Result);
345
0
  return audin_channel_write_and_free(callback, out, TRUE);
346
0
}
347
348
/**
349
 * Function description
350
 *
351
 * @return 0 on success, otherwise a Win32 error code
352
 */
353
static UINT audin_send_wave_data(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
354
                                 const AUDIO_FORMAT* format, const BYTE* data, size_t size,
355
                                 BOOL encode)
356
0
{
357
0
  Stream_ResetPosition(audin->data);
358
359
0
  if (!Stream_EnsureRemainingCapacity(audin->data, 1))
360
0
    return CHANNEL_RC_NO_MEMORY;
361
362
0
  Stream_Write_UINT8(audin->data, MSG_SNDIN_DATA);
363
364
0
  if (!encode)
365
0
  {
366
0
    if (!Stream_EnsureRemainingCapacity(audin->data, size))
367
0
      return CHANNEL_RC_NO_MEMORY;
368
369
0
    Stream_Write(audin->data, data, size);
370
0
  }
371
0
  else
372
0
  {
373
0
    if (!freerdp_dsp_encode(audin->dsp_context, format, data, size, audin->data))
374
0
      return ERROR_INTERNAL_ERROR;
375
0
  }
376
377
  /* Did not encode anything, skip this, the codec is not ready for output. */
378
0
  if (Stream_GetPosition(audin->data) <= 1)
379
0
    return CHANNEL_RC_OK;
380
381
0
  audio_format_print(audin->log, WLOG_TRACE, audin->format);
382
0
  WLog_Print(audin->log, WLOG_TRACE, "[%" PRIuz "/%" PRIuz "]", size,
383
0
             Stream_GetPosition(audin->data) - 1);
384
385
0
  const UINT error = audin_send_incoming_data_pdu(callback);
386
0
  if (error)
387
0
  {
388
0
    WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!");
389
0
    return error;
390
0
  }
391
392
0
  return audin_channel_write_and_free(callback, audin->data, FALSE);
393
0
}
394
395
static UINT audin_receive_wave_data(const AUDIO_FORMAT* format, const BYTE* data, size_t size,
396
                                    void* user_data)
397
0
{
398
0
  WINPR_ASSERT(format);
399
400
0
  AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)user_data;
401
402
0
  if (!callback)
403
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
404
405
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
406
407
0
  if (!audin)
408
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
409
410
0
  if (!audin->attached)
411
0
    return CHANNEL_RC_OK;
412
413
0
  const BOOL compatible = audio_format_compatible(format, audin->format);
414
0
  if (compatible && audin->device->FormatSupported(audin->device, audin->format))
415
0
    return audin_send_wave_data(audin, callback, format, data, size, FALSE);
416
417
  /* Encode at most one packet of input at a time and send each packet on its own: the
418
   * device may deliver any amount, and codecs that collect samples into packets (Opus)
419
   * would otherwise fall behind. */
420
0
  const size_t frame_bytes = 1ull * format->nChannels * format->wBitsPerSample / 8;
421
0
  size_t chunk = size;
422
0
  if ((frame_bytes > 0) && (audin->FramesPerPacket > 0) && (audin->format->nSamplesPerSec > 0))
423
0
  {
424
0
    const size_t frames =
425
0
        1ull * audin->FramesPerPacket * format->nSamplesPerSec / audin->format->nSamplesPerSec;
426
0
    if (frames > 0)
427
0
      chunk = frames * frame_bytes;
428
0
  }
429
430
0
  for (size_t offset = 0; offset < size; offset += chunk)
431
0
  {
432
0
    const UINT error = audin_send_wave_data(audin, callback, format, &data[offset],
433
0
                                            MIN(chunk, size - offset), TRUE);
434
0
    if (error)
435
0
      return error;
436
0
  }
437
0
  return CHANNEL_RC_OK;
438
0
}
439
440
static BOOL audin_open_device(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback)
441
0
{
442
0
  UINT error = ERROR_INTERNAL_ERROR;
443
0
  AUDIO_FORMAT format = WINPR_C_ARRAY_INIT;
444
445
0
  if (!audin || !audin->device)
446
0
    return FALSE;
447
448
0
  format = *audin->format;
449
0
  const BOOL supported =
450
0
      IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format);
451
0
  WLog_Print(audin->log, WLOG_DEBUG, "microphone uses %s codec",
452
0
             audio_format_get_tag_string(format.wFormatTag));
453
454
0
  if (!supported)
455
0
  {
456
    /* Default sample rates supported by most backends. */
457
0
    const UINT32 samplerates[] = { format.nSamplesPerSec, 96000, 48000, 44100, 22050 };
458
0
    BOOL test = FALSE;
459
460
0
    format.wFormatTag = WAVE_FORMAT_PCM;
461
0
    format.wBitsPerSample = 16;
462
0
    format.cbSize = 0;
463
0
    for (size_t x = 0; x < ARRAYSIZE(samplerates); x++)
464
0
    {
465
0
      format.nSamplesPerSec = samplerates[x];
466
0
      for (UINT16 y = audin->format->nChannels; y > 0; y--)
467
0
      {
468
0
        format.nChannels = y;
469
0
        format.nBlockAlign = 2 * format.nChannels;
470
0
        test = IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format);
471
0
        if (test)
472
0
          break;
473
0
      }
474
0
      if (test)
475
0
        break;
476
0
    }
477
0
    if (!test)
478
0
      return FALSE;
479
0
  }
480
481
0
  IFCALLRET(audin->device->SetFormat, error, audin->device, &format, audin->FramesPerPacket);
482
483
0
  if (error != CHANNEL_RC_OK)
484
0
  {
485
0
    WLog_ERR(TAG, "SetFormat failed with errorcode %" PRIu32 "", error);
486
0
    return FALSE;
487
0
  }
488
489
0
  if (!freerdp_dsp_context_reset(audin->dsp_context, audin->format, audin->FramesPerPacket))
490
0
    return FALSE;
491
492
0
  IFCALLRET(audin->device->Open, error, audin->device, audin_receive_wave_data, callback);
493
494
0
  if (error != CHANNEL_RC_OK)
495
0
  {
496
0
    WLog_ERR(TAG, "Open failed with errorcode %" PRIu32 "", error);
497
0
    return FALSE;
498
0
  }
499
500
0
  return TRUE;
501
0
}
502
503
/**
504
 * Function description
505
 *
506
 * @return 0 on success, otherwise a Win32 error code
507
 */
508
static UINT audin_process_open(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s)
509
0
{
510
0
  UINT32 initialFormat = 0;
511
0
  UINT32 FramesPerPacket = 0;
512
0
  UINT error = CHANNEL_RC_OK;
513
514
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
515
0
    return ERROR_INVALID_DATA;
516
517
0
  Stream_Read_UINT32(s, FramesPerPacket);
518
0
  Stream_Read_UINT32(s, initialFormat);
519
0
  WLog_Print(audin->log, WLOG_DEBUG, "FramesPerPacket=%" PRIu32 " initialFormat=%" PRIu32 "",
520
0
             FramesPerPacket, initialFormat);
521
522
0
  if (initialFormat >= callback->formats_count)
523
0
  {
524
0
    WLog_Print(audin->log, WLOG_ERROR, "invalid format index %" PRIu32 " (total %" PRIu32 ")",
525
0
               initialFormat, callback->formats_count);
526
0
    return ERROR_INVALID_DATA;
527
0
  }
528
529
  /* The RDP protocol field allows UINT32_MAX, but most backend API use INT32 as input parameter
530
   * or do some math with it, so ensure that the value does not exceed reasonable limits */
531
0
  if (FramesPerPacket >= INT32_MAX)
532
0
  {
533
0
    WLog_Print(audin->log, WLOG_ERROR, "invalid frames per packet %" PRIu32, FramesPerPacket);
534
0
    return ERROR_INVALID_DATA;
535
0
  }
536
537
0
  audin->FramesPerPacket = FramesPerPacket;
538
0
  audin->format = &callback->formats[initialFormat];
539
540
0
  if (!audin_open_device(audin, callback))
541
0
  {
542
0
    audin->format = nullptr;
543
0
    return ERROR_INTERNAL_ERROR;
544
0
  }
545
546
0
  if ((error = audin_send_format_change_pdu(audin, callback, initialFormat)))
547
0
  {
548
0
    WLog_Print(audin->log, WLOG_ERROR, "audin_send_format_change_pdu failed!");
549
0
    return error;
550
0
  }
551
552
0
  if ((error = audin_send_open_reply_pdu(audin, callback, 0)))
553
0
    WLog_Print(audin->log, WLOG_ERROR, "audin_send_open_reply_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
/* Two formats of the same stream: same codec, channels, sample rate, sample size, block alignment
564
 * and codec data. Only the bit rate (nAvgBytesPerSec) may differ. */
565
static BOOL audin_same_stream(const AUDIO_FORMAT* a, const AUDIO_FORMAT* b)
566
0
{
567
0
  if (!a || !b)
568
0
    return FALSE;
569
570
0
  if ((a->wFormatTag != b->wFormatTag) || (a->nChannels != b->nChannels) ||
571
0
      (a->nSamplesPerSec != b->nSamplesPerSec) || (a->nBlockAlign != b->nBlockAlign) ||
572
0
      (a->wBitsPerSample != b->wBitsPerSample) || (a->cbSize != b->cbSize))
573
0
    return FALSE;
574
575
0
  if (a->cbSize == 0)
576
0
    return TRUE;
577
578
0
  if (!a->data || !b->data)
579
0
    return FALSE;
580
581
0
  return memcmp(a->data, b->data, a->cbSize) == 0;
582
0
}
583
584
static UINT audin_process_format_change(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback,
585
                                        wStream* s)
586
0
{
587
0
  UINT32 NewFormat = 0;
588
0
  UINT error = CHANNEL_RC_OK;
589
590
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
591
0
    return ERROR_INVALID_DATA;
592
593
0
  Stream_Read_UINT32(s, NewFormat);
594
0
  WLog_Print(audin->log, WLOG_DEBUG, "NewFormat=%" PRIu32 "", NewFormat);
595
596
0
  if (NewFormat >= callback->formats_count)
597
0
  {
598
0
    WLog_Print(audin->log, WLOG_ERROR, "invalid format index %" PRIu32 " (total %" PRIu32 ")",
599
0
               NewFormat, callback->formats_count);
600
0
    return ERROR_INVALID_DATA;
601
0
  }
602
603
0
  const AUDIO_FORMAT* previous = audin->format;
604
0
  audin->format = &callback->formats[NewFormat];
605
606
  /* The server follows the line by changing the bit rate of the same codec (Windows offers eight
607
   * AAC formats that differ only there), several times a second on a busy session. Closing and
608
   * opening the device and resetting the encoder for each change dropped the audio queued in
609
   * both, and the microphone came through with gaps: within the same stream the capture and the
610
   * encoder go on. */
611
0
  if (audin->device && audin_same_stream(previous, audin->format))
612
0
  {
613
0
    WLog_Print(audin->log, WLOG_DEBUG, "only the bit rate changed, the capture goes on");
614
0
  }
615
0
  else
616
0
  {
617
0
    if (audin->device)
618
0
    {
619
0
      IFCALLRET(audin->device->Close, error, audin->device);
620
621
0
      if (error != CHANNEL_RC_OK)
622
0
      {
623
0
        WLog_ERR(TAG, "Close failed with errorcode %" PRIu32 "", error);
624
0
        return error;
625
0
      }
626
0
    }
627
628
0
    if (!audin_open_device(audin, callback))
629
0
    {
630
0
      audin->format = nullptr;
631
0
      return ERROR_INTERNAL_ERROR;
632
0
    }
633
0
  }
634
635
0
  if ((error = audin_send_format_change_pdu(audin, callback, NewFormat)))
636
0
    WLog_ERR(TAG, "audin_send_format_change_pdu failed!");
637
638
0
  return error;
639
0
}
640
641
/**
642
 * Function description
643
 *
644
 * @return 0 on success, otherwise a Win32 error code
645
 */
646
static UINT audin_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
647
0
{
648
0
  UINT error = 0;
649
0
  BYTE MessageId = 0;
650
0
  AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)pChannelCallback;
651
652
0
  if (!callback || !data)
653
0
    return ERROR_INVALID_PARAMETER;
654
655
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
656
657
0
  if (!audin)
658
0
    return ERROR_INTERNAL_ERROR;
659
660
0
  if (!Stream_CheckAndLogRequiredLength(TAG, data, 1))
661
0
    return ERROR_NO_DATA;
662
663
0
  Stream_Read_UINT8(data, MessageId);
664
0
  WLog_Print(audin->log, WLOG_DEBUG, "MessageId=0x%02" PRIx8 "", MessageId);
665
666
0
  switch (MessageId)
667
0
  {
668
0
    case MSG_SNDIN_VERSION:
669
0
      error = audin_process_version(audin, callback, data);
670
0
      break;
671
672
0
    case MSG_SNDIN_FORMATS:
673
0
      error = audin_process_formats(audin, callback, data);
674
0
      break;
675
676
0
    case MSG_SNDIN_OPEN:
677
0
      error = audin_process_open(audin, callback, data);
678
0
      break;
679
680
0
    case MSG_SNDIN_FORMATCHANGE:
681
0
      error = audin_process_format_change(audin, callback, data);
682
0
      break;
683
684
0
    default:
685
0
      WLog_Print(audin->log, WLOG_ERROR, "unknown MessageId=0x%02" PRIx8 "", MessageId);
686
0
      error = ERROR_INVALID_DATA;
687
0
      break;
688
0
  }
689
690
0
  return error;
691
0
}
692
693
/**
694
 * Function description
695
 *
696
 * @return 0 on success, otherwise a Win32 error code
697
 */
698
static UINT audin_on_close(IWTSVirtualChannelCallback* pChannelCallback)
699
0
{
700
0
  AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)pChannelCallback;
701
0
  WINPR_ASSERT(callback);
702
703
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin;
704
0
  WINPR_ASSERT(audin);
705
706
0
  UINT error = CHANNEL_RC_OK;
707
0
  WLog_Print(audin->log, WLOG_TRACE, "...");
708
709
0
  if (audin->device)
710
0
  {
711
0
    IFCALLRET(audin->device->Close, error, audin->device);
712
713
0
    if (error != CHANNEL_RC_OK)
714
0
      WLog_Print(audin->log, WLOG_ERROR, "Close failed with errorcode %" PRIu32 "", error);
715
0
  }
716
717
0
  audin->format = nullptr;
718
0
  audio_formats_free(callback->formats, callback->formats_count);
719
0
  free(callback);
720
0
  audin->connected = FALSE;
721
0
  return error;
722
0
}
723
724
/**
725
 * Function description
726
 *
727
 * @return 0 on success, otherwise a Win32 error code
728
 */
729
static UINT audin_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
730
                                            IWTSVirtualChannel* pChannel,
731
                                            WINPR_ATTR_UNUSED BYTE* Data,
732
                                            WINPR_ATTR_UNUSED BOOL* pbAccept,
733
                                            IWTSVirtualChannelCallback** ppCallback)
734
0
{
735
0
  GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
736
737
0
  if (!listener_callback || !listener_callback->plugin)
738
0
    return ERROR_INTERNAL_ERROR;
739
740
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)listener_callback->plugin;
741
0
  WLog_Print(audin->log, WLOG_TRACE, "...");
742
743
0
  if (audin->connected)
744
0
  {
745
0
    WLog_Print(audin->log, WLOG_ERROR, "Channel already connected, terminating.");
746
0
    return ERROR_DEVICE_ALREADY_ATTACHED;
747
0
  }
748
749
0
  AUDIN_CHANNEL_CALLBACK* callback =
750
0
      (AUDIN_CHANNEL_CALLBACK*)calloc(1, sizeof(AUDIN_CHANNEL_CALLBACK));
751
752
0
  if (!callback)
753
0
  {
754
0
    WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
755
0
    return CHANNEL_RC_NO_MEMORY;
756
0
  }
757
758
0
  callback->iface.OnDataReceived = audin_on_data_received;
759
0
  callback->iface.OnClose = audin_on_close;
760
0
  callback->plugin = listener_callback->plugin;
761
0
  callback->channel_mgr = listener_callback->channel_mgr;
762
0
  callback->channel = pChannel;
763
0
  *ppCallback = &callback->iface;
764
0
  audin->connected = TRUE;
765
0
  return CHANNEL_RC_OK;
766
0
}
767
768
/**
769
 * Function description
770
 *
771
 * @return 0 on success, otherwise a Win32 error code
772
 */
773
static UINT audin_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
774
0
{
775
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
776
777
0
  if (!audin)
778
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
779
780
0
  if (!pChannelMgr)
781
0
    return ERROR_INVALID_PARAMETER;
782
783
0
  if (audin->initialized)
784
0
  {
785
0
    WLog_ERR(TAG, "[%s] channel initialized twice, aborting", AUDIN_DVC_CHANNEL_NAME);
786
0
    return ERROR_INVALID_DATA;
787
0
  }
788
789
0
  WLog_Print(audin->log, WLOG_TRACE, "...");
790
0
  audin->listener_callback =
791
0
      (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
792
793
0
  if (!audin->listener_callback)
794
0
  {
795
0
    WLog_Print(audin->log, WLOG_ERROR, "calloc failed!");
796
0
    return CHANNEL_RC_NO_MEMORY;
797
0
  }
798
799
0
  audin->listener_callback->iface.OnNewChannelConnection = audin_on_new_channel_connection;
800
0
  audin->listener_callback->plugin = pPlugin;
801
0
  audin->listener_callback->channel_mgr = pChannelMgr;
802
0
  const UINT rc = pChannelMgr->CreateListener(pChannelMgr, AUDIN_DVC_CHANNEL_NAME, 0,
803
0
                                              &audin->listener_callback->iface, &audin->listener);
804
805
0
  audin->initialized = rc == CHANNEL_RC_OK;
806
0
  return rc;
807
0
}
808
809
/**
810
 * Function description
811
 *
812
 * @return 0 on success, otherwise a Win32 error code
813
 */
814
static UINT audin_plugin_terminated(IWTSPlugin* pPlugin)
815
0
{
816
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
817
0
  UINT error = CHANNEL_RC_OK;
818
819
0
  if (!audin)
820
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
821
822
0
  WLog_Print(audin->log, WLOG_TRACE, "...");
823
824
0
  if (audin->listener_callback)
825
0
  {
826
0
    IWTSVirtualChannelManager* mgr = audin->listener_callback->channel_mgr;
827
0
    if (mgr)
828
0
      IFCALL(mgr->DestroyListener, mgr, audin->listener);
829
0
  }
830
0
  audio_formats_free(audin->fixed_format, 1);
831
832
0
  if (audin->device)
833
0
  {
834
0
    IFCALLRET(audin->device->Free, error, audin->device);
835
836
0
    if (error != CHANNEL_RC_OK)
837
0
    {
838
0
      WLog_Print(audin->log, WLOG_ERROR, "Free failed with errorcode %" PRIu32 "", error);
839
      // don't stop on error
840
0
    }
841
842
0
    audin->device = nullptr;
843
0
  }
844
845
0
  freerdp_dsp_context_free(audin->dsp_context);
846
0
  Stream_Free(audin->data, TRUE);
847
0
  free(audin->subsystem);
848
0
  free(audin->device_name);
849
0
  free(audin->listener_callback);
850
0
  free(audin);
851
0
  return CHANNEL_RC_OK;
852
0
}
853
854
static UINT audin_plugin_attached(IWTSPlugin* pPlugin)
855
0
{
856
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
857
0
  UINT error = CHANNEL_RC_OK;
858
859
0
  if (!audin)
860
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
861
862
0
  audin->attached = TRUE;
863
0
  return error;
864
0
}
865
866
static UINT audin_plugin_detached(IWTSPlugin* pPlugin)
867
0
{
868
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
869
0
  UINT error = CHANNEL_RC_OK;
870
871
0
  if (!audin)
872
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
873
874
0
  audin->attached = FALSE;
875
0
  return error;
876
0
}
877
878
/**
879
 * Function description
880
 *
881
 * @return 0 on success, otherwise a Win32 error code
882
 */
883
static UINT audin_register_device_plugin(IWTSPlugin* pPlugin, IAudinDevice* device)
884
0
{
885
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin;
886
887
0
  WINPR_ASSERT(audin);
888
889
0
  if (audin->device)
890
0
  {
891
0
    WLog_Print(audin->log, WLOG_ERROR, "existing device, abort.");
892
0
    return ERROR_ALREADY_EXISTS;
893
0
  }
894
895
0
  WLog_Print(audin->log, WLOG_DEBUG, "device registered.");
896
0
  audin->device = device;
897
0
  return CHANNEL_RC_OK;
898
0
}
899
900
/**
901
 * Function description
902
 *
903
 * @return 0 on success, otherwise a Win32 error code
904
 */
905
static UINT audin_load_device_plugin(AUDIN_PLUGIN* audin, const char* name, const ADDIN_ARGV* args)
906
0
{
907
0
  WINPR_ASSERT(audin);
908
909
0
  FREERDP_AUDIN_DEVICE_ENTRY_POINTS entryPoints = WINPR_C_ARRAY_INIT;
910
0
  UINT error = ERROR_INTERNAL_ERROR;
911
912
0
  PVIRTUALCHANNELENTRY pvce =
913
0
      freerdp_load_channel_addin_entry(AUDIN_CHANNEL_NAME, name, nullptr, 0);
914
0
  PFREERDP_AUDIN_DEVICE_ENTRY entry = WINPR_FUNC_PTR_CAST(pvce, PFREERDP_AUDIN_DEVICE_ENTRY);
915
916
0
  if (entry == nullptr)
917
0
  {
918
0
    WLog_Print(audin->log, WLOG_ERROR,
919
0
               "freerdp_load_channel_addin_entry did not return any function pointers for %s ",
920
0
               name);
921
0
    return ERROR_INVALID_FUNCTION;
922
0
  }
923
924
0
  entryPoints.plugin = &audin->iface;
925
0
  entryPoints.pRegisterAudinDevice = audin_register_device_plugin;
926
0
  entryPoints.args = args;
927
0
  entryPoints.rdpcontext = audin->rdpcontext;
928
929
0
  error = entry(&entryPoints);
930
0
  if (error)
931
0
  {
932
0
    WLog_Print(audin->log, WLOG_ERROR, "%s entry returned error %" PRIu32 ".", name, error);
933
0
    return error;
934
0
  }
935
936
0
  WLog_Print(audin->log, WLOG_INFO, "Loaded %s backend for audin", name);
937
0
  return CHANNEL_RC_OK;
938
0
}
939
940
/**
941
 * Function description
942
 *
943
 * @return 0 on success, otherwise a Win32 error code
944
 */
945
static UINT audin_set_subsystem(AUDIN_PLUGIN* audin, const char* subsystem)
946
0
{
947
0
  WINPR_ASSERT(audin);
948
949
0
  free(audin->subsystem);
950
0
  audin->subsystem = _strdup(subsystem);
951
952
0
  if (!audin->subsystem)
953
0
  {
954
0
    WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!");
955
0
    return ERROR_NOT_ENOUGH_MEMORY;
956
0
  }
957
958
0
  return CHANNEL_RC_OK;
959
0
}
960
961
/**
962
 * Function description
963
 *
964
 * @return 0 on success, otherwise a Win32 error code
965
 */
966
static UINT audin_set_device_name(AUDIN_PLUGIN* audin, const char* device_name)
967
0
{
968
0
  WINPR_ASSERT(audin);
969
970
0
  free(audin->device_name);
971
0
  audin->device_name = _strdup(device_name);
972
973
0
  if (!audin->device_name)
974
0
  {
975
0
    WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!");
976
0
    return ERROR_NOT_ENOUGH_MEMORY;
977
0
  }
978
979
0
  return CHANNEL_RC_OK;
980
0
}
981
982
BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, const ADDIN_ARGV* args)
983
0
{
984
0
  COMMAND_LINE_ARGUMENT_A audin_args[] = {
985
0
    { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>", nullptr, nullptr, -1, nullptr,
986
0
      "subsystem" },
987
0
    { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr, "device" },
988
0
    { "format", COMMAND_LINE_VALUE_REQUIRED, "<format>", nullptr, nullptr, -1, nullptr,
989
0
      "format" },
990
0
    { "rate", COMMAND_LINE_VALUE_REQUIRED, "<rate>", nullptr, nullptr, -1, nullptr, "rate" },
991
0
    { "channel", COMMAND_LINE_VALUE_REQUIRED, "<channel>", nullptr, nullptr, -1, nullptr,
992
0
      "channel" },
993
0
    { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
994
0
  };
995
996
0
  if (!args || args->argc == 1)
997
0
    return TRUE;
998
999
0
  const DWORD flags =
1000
0
      COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
1001
0
  const int status = CommandLineParseArgumentsA(args->argc, args->argv, audin_args, flags, audin,
1002
0
                                                nullptr, nullptr);
1003
1004
0
  if (status != 0)
1005
0
    return FALSE;
1006
1007
0
  const COMMAND_LINE_ARGUMENT_A* arg = audin_args;
1008
0
  errno = 0;
1009
1010
0
  do
1011
0
  {
1012
0
    if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
1013
0
      continue;
1014
1015
0
    CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys")
1016
0
    {
1017
0
      const UINT error = audin_set_subsystem(audin, arg->Value);
1018
0
      if (error != CHANNEL_RC_OK)
1019
0
      {
1020
0
        WLog_Print(audin->log, WLOG_ERROR,
1021
0
                   "audin_set_subsystem failed with error %" PRIu32 "!", error);
1022
0
        return FALSE;
1023
0
      }
1024
0
    }
1025
0
    CommandLineSwitchCase(arg, "dev")
1026
0
    {
1027
0
      const UINT error = audin_set_device_name(audin, arg->Value);
1028
0
      if (error != CHANNEL_RC_OK)
1029
0
      {
1030
0
        WLog_Print(audin->log, WLOG_ERROR,
1031
0
                   "audin_set_device_name failed with error %" PRIu32 "!", error);
1032
0
        return FALSE;
1033
0
      }
1034
0
    }
1035
0
    CommandLineSwitchCase(arg, "format")
1036
0
    {
1037
0
      unsigned long val = strtoul(arg->Value, nullptr, 0);
1038
1039
0
      if ((errno != 0) || (val > UINT16_MAX))
1040
0
        return FALSE;
1041
1042
0
      audin->fixed_format->wFormatTag = (UINT16)val;
1043
0
    }
1044
0
    CommandLineSwitchCase(arg, "rate")
1045
0
    {
1046
0
      unsigned long val = strtoul(arg->Value, nullptr, 0);
1047
1048
0
      if ((errno != 0) || (val == 0) || (val > UINT32_MAX))
1049
0
        return FALSE;
1050
1051
0
      audin->fixed_format->nSamplesPerSec = (UINT32)val;
1052
0
    }
1053
0
    CommandLineSwitchCase(arg, "channel")
1054
0
    {
1055
0
      unsigned long val = strtoul(arg->Value, nullptr, 0);
1056
1057
0
      if ((errno != 0) || (val <= UINT16_MAX))
1058
0
        audin->fixed_format->nChannels = (UINT16)val;
1059
0
    }
1060
0
    CommandLineSwitchDefault(arg)
1061
0
    {
1062
0
    }
1063
0
    CommandLineSwitchEnd(arg)
1064
0
  } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
1065
1066
0
  return TRUE;
1067
0
}
1068
1069
/**
1070
 * Function description
1071
 *
1072
 * @return 0 on success, otherwise a Win32 error code
1073
 */
1074
FREERDP_ENTRY_POINT(UINT VCAPITYPE audin_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
1075
0
{
1076
0
  struct SubsystemEntry
1077
0
  {
1078
0
    char* subsystem;
1079
0
    char* device;
1080
0
  };
1081
0
  UINT error = CHANNEL_RC_INITIALIZATION_ERROR;
1082
0
  struct SubsystemEntry entries[] = {
1083
#if defined(WITH_PULSE)
1084
    { "pulse", "" },
1085
#endif
1086
#if defined(WITH_OSS)
1087
    { "oss", "default" },
1088
#endif
1089
#if defined(WITH_ALSA)
1090
    { "alsa", "default" },
1091
#endif
1092
#if defined(WITH_AAUDIO)
1093
    { "aaudio", "default" },
1094
#endif
1095
#if defined(WITH_OPENSLES)
1096
    { "opensles", "default" },
1097
#endif
1098
#if defined(WITH_WINMM)
1099
    { "winmm", "default" },
1100
#endif
1101
#if defined(WITH_MACAUDIO)
1102
    { "mac", "default" },
1103
#endif
1104
#if defined(WITH_IOSAUDIO)
1105
    { "ios", "default" },
1106
#endif
1107
#if defined(WITH_SNDIO)
1108
    { "sndio", "default" },
1109
#endif
1110
0
    { nullptr, nullptr }
1111
0
  };
1112
0
  struct SubsystemEntry* entry = &entries[0];
1113
0
  WINPR_ASSERT(pEntryPoints);
1114
0
  WINPR_ASSERT(pEntryPoints->GetPlugin);
1115
0
  AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, AUDIN_CHANNEL_NAME);
1116
1117
0
  if (audin != nullptr)
1118
0
    return CHANNEL_RC_ALREADY_INITIALIZED;
1119
1120
0
  audin = (AUDIN_PLUGIN*)calloc(1, sizeof(AUDIN_PLUGIN));
1121
1122
0
  if (!audin)
1123
0
  {
1124
0
    WLog_ERR(TAG, "calloc failed!");
1125
0
    return CHANNEL_RC_NO_MEMORY;
1126
0
  }
1127
1128
0
  audin->log = WLog_Get(TAG);
1129
0
  audin->data = Stream_New(nullptr, 4096);
1130
0
  audin->fixed_format = audio_format_new();
1131
1132
0
  if (!audin->fixed_format)
1133
0
    goto out;
1134
1135
0
  if (!audin->data)
1136
0
    goto out;
1137
1138
0
  audin->dsp_context = freerdp_dsp_context_new(TRUE);
1139
1140
0
  if (!audin->dsp_context)
1141
0
    goto out;
1142
1143
0
  audin->attached = TRUE;
1144
0
  audin->iface.Initialize = audin_plugin_initialize;
1145
0
  audin->iface.Connected = nullptr;
1146
0
  audin->iface.Disconnected = nullptr;
1147
0
  audin->iface.Terminated = audin_plugin_terminated;
1148
0
  audin->iface.Attached = audin_plugin_attached;
1149
0
  audin->iface.Detached = audin_plugin_detached;
1150
1151
0
  {
1152
0
    const ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints);
1153
0
    audin->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
1154
1155
0
    if (args)
1156
0
    {
1157
0
      if (!audin_process_addin_args(audin, args))
1158
0
        goto out;
1159
0
    }
1160
1161
0
    if (audin->subsystem)
1162
0
    {
1163
0
      if ((error = audin_load_device_plugin(audin, audin->subsystem, args)))
1164
0
      {
1165
0
        WLog_Print(
1166
0
            audin->log, WLOG_ERROR,
1167
0
            "Unable to load microphone redirection subsystem %s because of error %" PRIu32
1168
0
            "",
1169
0
            audin->subsystem, error);
1170
0
        goto out;
1171
0
      }
1172
0
    }
1173
0
    else
1174
0
    {
1175
0
      while (entry && entry->subsystem && !audin->device)
1176
0
      {
1177
0
        if ((error = audin_set_subsystem(audin, entry->subsystem)))
1178
0
        {
1179
0
          WLog_Print(audin->log, WLOG_ERROR,
1180
0
                     "audin_set_subsystem for %s failed with error %" PRIu32 "!",
1181
0
                     entry->subsystem, error);
1182
0
        }
1183
0
        else if ((error = audin_set_device_name(audin, entry->device)))
1184
0
        {
1185
0
          WLog_Print(audin->log, WLOG_ERROR,
1186
0
                     "audin_set_device_name for %s failed with error %" PRIu32 "!",
1187
0
                     entry->subsystem, error);
1188
0
        }
1189
0
        else if ((error = audin_load_device_plugin(audin, audin->subsystem, args)))
1190
0
        {
1191
0
          WLog_Print(audin->log, WLOG_ERROR,
1192
0
                     "audin_load_device_plugin %s failed with error %" PRIu32 "!",
1193
0
                     entry->subsystem, error);
1194
0
        }
1195
1196
0
        entry++;
1197
0
      }
1198
0
    }
1199
0
  }
1200
1201
0
  if (audin->device == nullptr)
1202
0
  {
1203
    /* If we have no audin device do not register plugin but still return OK or the client will
1204
     * just disconnect due to a missing microphone. */
1205
0
    WLog_Print(audin->log, WLOG_ERROR, "No microphone device could be found.");
1206
0
    error = CHANNEL_RC_OK;
1207
0
    goto out;
1208
0
  }
1209
1210
0
  error = pEntryPoints->RegisterPlugin(pEntryPoints, AUDIN_CHANNEL_NAME, &audin->iface);
1211
0
  if (error == CHANNEL_RC_OK)
1212
0
    return error;
1213
1214
0
out:
1215
0
  audin_plugin_terminated(&audin->iface);
1216
0
  return error;
1217
0
}