1
/* $Id: wmme_dev.c 3664 2011-07-19 03:42:28Z nanang $ */
3
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
#include <pjmedia-audiodev/audiodev_imp.h>
21
#include <pj/assert.h>
24
#include <pj/string.h>
25
#include <pj/unicode.h>
27
#if PJMEDIA_AUDIO_DEV_HAS_WMME
30
# pragma warning(push, 3)
41
#ifndef PJMEDIA_WMME_DEV_USE_MMDEVICE_API
42
# define PJMEDIA_WMME_DEV_USE_MMDEVICE_API \
43
(defined(_WIN32_WINNT) && (_WIN32_WINNT>=0x0600))
46
#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0
47
# define DRV_QUERYFUNCTIONINSTANCEID (DRV_RESERVED + 17)
48
# define DRV_QUERYFUNCTIONINSTANCEIDSIZE (DRV_RESERVED + 18)
51
/* mingw lacks WAVE_FORMAT_ALAW/MULAW */
52
#ifndef WAVE_FORMAT_ALAW
53
# define WAVE_FORMAT_ALAW 0x0006
55
#ifndef WAVE_FORMAT_MULAW
56
# define WAVE_FORMAT_MULAW 0x0007
59
#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0
60
# pragma comment(lib, "Coredll.lib")
61
#elif defined(_MSC_VER)
62
# pragma comment(lib, "winmm.lib")
66
#define THIS_FILE "wmme_dev.c"
68
/* WMME device info */
71
pjmedia_aud_dev_info info;
73
const wchar_t *endpointId;
79
pjmedia_aud_dev_factory base;
85
struct wmme_dev_info *dev_info;
89
/* Individual WMME capture/playback stream descriptor */
102
pj_timestamp timestamp;
109
pjmedia_aud_stream base; /**< Base stream */
110
pjmedia_aud_param param; /**< Settings */
111
pj_pool_t *pool; /**< Memory pool. */
113
pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */
114
pjmedia_aud_play_cb play_cb; /**< Playback callback. */
115
void *user_data; /**< Application data. */
117
struct wmme_channel play_strm; /**< Playback stream. */
118
struct wmme_channel rec_strm; /**< Capture stream. */
120
void *buffer; /**< Temp. frame buffer. */
121
pjmedia_format_id fmt_id; /**< Frame format */
122
pj_uint8_t silence_char; /**< Silence pattern */
123
unsigned bytes_per_frame; /**< Bytes per frame */
125
pjmedia_frame_ext *xfrm; /**< Extended frame buffer */
126
unsigned xfrm_size; /**< Total ext frm size */
128
pj_thread_t *thread; /**< Thread handle. */
129
HANDLE thread_quit_event; /**< Quit signal to thread */
134
static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
135
static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
136
static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f);
137
static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
138
static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
140
pjmedia_aud_dev_info *info);
141
static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
143
pjmedia_aud_param *param);
144
static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
145
const pjmedia_aud_param *param,
146
pjmedia_aud_rec_cb rec_cb,
147
pjmedia_aud_play_cb play_cb,
149
pjmedia_aud_stream **p_aud_strm);
151
static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
152
pjmedia_aud_param *param);
153
static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
154
pjmedia_aud_dev_cap cap,
156
static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
157
pjmedia_aud_dev_cap cap,
159
static pj_status_t stream_start(pjmedia_aud_stream *strm);
160
static pj_status_t stream_stop(pjmedia_aud_stream *strm);
161
static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
165
static pjmedia_aud_dev_factory_op factory_op =
169
&factory_get_dev_count,
170
&factory_get_dev_info,
171
&factory_default_param,
172
&factory_create_stream,
176
static pjmedia_aud_stream_op stream_op =
187
/****************************************************************************
191
* Init WMME audio driver.
193
pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf)
195
struct wmme_factory *f;
198
pool = pj_pool_create(pf, "WMME base", 1000, 1000, NULL);
199
f = PJ_POOL_ZALLOC_T(pool, struct wmme_factory);
202
f->base.op = &factory_op;
207
/* Internal: Windows Vista and Windows 7 have their device
208
* names truncated when using the waveXXX api. The names
209
* should be acquired from the MMDevice APIs
211
#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0
214
#include <mmdeviceapi.h>
217
#include <FunctionDiscoveryKeys_devpkey.h>
219
DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C,
220
0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
221
DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35,
222
0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6);
224
static void get_dev_names(pjmedia_aud_dev_factory *f)
226
struct wmme_factory *wf = (struct wmme_factory*)f;
227
HRESULT coinit = S_OK;
229
IMMDeviceEnumerator *pEnumerator = NULL;
230
IMMDeviceCollection *pDevices = NULL;
234
coinit = CoInitializeEx(NULL, COINIT_MULTITHREADED);
235
if (coinit == RPC_E_CHANGED_MODE)
236
coinit = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
240
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
241
CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator,
242
(void**)&pEnumerator);
245
hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eAll,
250
hr = IMMDeviceCollection_GetCount(pDevices, &cDevices);
254
for (nDevice = 0; nDevice < cDevices; ++nDevice) {
255
IMMDevice *pDevice = NULL;
256
IPropertyStore *pProps = NULL;
257
LPWSTR pwszID = NULL;
261
PropVariantInit(&varName);
263
hr = IMMDeviceCollection_Item(pDevices, nDevice, &pDevice);
266
hr = IMMDevice_GetId(pDevice, &pwszID);
269
hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProps);
272
hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName,
277
for (i = 0; i < wf->dev_count; ++i) {
278
if (0 == wcscmp(wf->dev_info[i].endpointId, pwszID)) {
279
wcstombs(wf->dev_info[i].info.name, varName.pwszVal,
280
sizeof(wf->dev_info[i].info.name));
285
PropVariantClear(&varName);
289
IPropertyStore_Release(pProps);
291
CoTaskMemFree(pwszID);
293
hr = IMMDevice_Release(pDevice);
298
hr = IMMDeviceCollection_Release(pDevices);
301
hr = IMMDeviceEnumerator_Release(pEnumerator);
303
if (SUCCEEDED(coinit))
309
static void get_dev_names(pjmedia_aud_dev_factory *f)
316
/* Internal: build device info from WAVEINCAPS/WAVEOUTCAPS */
317
static void build_dev_info(UINT deviceId, struct wmme_dev_info *wdi,
318
const WAVEINCAPS *wic, const WAVEOUTCAPS *woc)
320
#define WIC_WOC(wic,woc,field) (wic? wic->field : woc->field)
322
pj_bzero(wdi, sizeof(*wdi));
323
wdi->deviceId = deviceId;
326
if (deviceId==WAVE_MAPPER) {
327
strncpy(wdi->info.name, "Wave mapper", sizeof(wdi->info.name));
328
wdi->info.name[sizeof(wdi->info.name)-1] = '\0';
330
const pj_char_t *szPname = WIC_WOC(wic, woc, szPname);
331
PJ_DECL_ANSI_TEMP_BUF(wTmp, sizeof(wdi->info.name));
333
strncpy(wdi->info.name,
334
PJ_NATIVE_TO_STRING(szPname, wTmp, PJ_ARRAY_SIZE(wTmp)),
335
sizeof(wdi->info.name));
336
wdi->info.name[sizeof(wdi->info.name)-1] = '\0';
339
wdi->info.default_samples_per_sec = 16000;
340
strcpy(wdi->info.driver, "WMME");
343
wdi->info.input_count = wic->wChannels;
344
wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
346
/* Sometimes a device can return a rediculously large number of
347
* channels. This happened with an SBLive card on a Windows ME box.
348
* It also happens on Win XP!
350
if (wdi->info.input_count<1 || wdi->info.input_count>256) {
351
wdi->info.input_count = 2;
356
wdi->info.output_count = woc->wChannels;
357
wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
359
if (woc->dwSupport & WAVECAPS_VOLUME) {
360
wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
363
/* Sometimes a device can return a rediculously large number of
364
* channels. This happened with an SBLive card on a Windows ME box.
365
* It also happens on Win XP!
367
if (wdi->info.output_count<1 || wdi->info.output_count>256) {
368
wdi->info.output_count = 2;
372
/* Extended formats */
373
wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
374
wdi->info.ext_fmt_cnt = 2;
375
pjmedia_format_init_audio(&wdi->info.ext_fmt[0],
376
PJMEDIA_FORMAT_PCMU, 8000, 1, 8,
377
20000, 64000, 64000);
378
pjmedia_format_init_audio(&wdi->info.ext_fmt[0],
379
PJMEDIA_FORMAT_PCMA, 8000, 1, 8,
380
20000, 64000, 64000);
383
/* API: init factory */
384
static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
386
pj_status_t ret = factory_refresh(f);
387
if (ret != PJ_SUCCESS)
390
PJ_LOG(4, (THIS_FILE, "WMME initialized"));
394
/* API: refresh the device list */
395
static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f)
397
struct wmme_factory *wf = (struct wmme_factory*)f;
400
int inputDeviceCount, outputDeviceCount, devCount=0;
401
pj_bool_t waveMapperAdded = PJ_FALSE;
403
if (wf->pool != NULL) {
404
pj_pool_release(wf->pool);
408
/* Enumerate sound devices */
410
wf->pool = pj_pool_create(wf->pf, "WMME", 1000, 1000, NULL);
412
inputDeviceCount = waveInGetNumDevs();
413
devCount += inputDeviceCount;
415
outputDeviceCount = waveOutGetNumDevs();
416
devCount += outputDeviceCount;
419
/* Assume there is WAVE_MAPPER */
424
PJ_LOG(4,(THIS_FILE, "WMME found no sound devices"));
425
/* Enabling this will cause pjsua-lib initialization to fail when there
426
* is no sound device installed in the system, even when pjsua has been
427
* run with --null-audio. Moreover, it might be better to think that
428
* the WMME backend initialization is successfull, regardless there is
429
* no audio device installed, as later application can check it using
431
return PJMEDIA_EAUD_NODEV;
436
wf->dev_info = (struct wmme_dev_info*)
437
pj_pool_calloc(wf->pool, devCount,
438
sizeof(struct wmme_dev_info));
440
if (inputDeviceCount && outputDeviceCount) {
441
/* Attempt to add WAVE_MAPPER as input and output device */
445
pj_bzero(&wic, sizeof(WAVEINCAPS));
446
mr = waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(WAVEINCAPS));
448
if (mr == MMSYSERR_NOERROR) {
451
pj_bzero(&woc, sizeof(WAVEOUTCAPS));
452
mr = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS));
453
if (mr == MMSYSERR_NOERROR) {
454
build_dev_info(WAVE_MAPPER, &wf->dev_info[wf->dev_count],
456
wf->dev_info[wf->dev_count].endpointId = L"";
458
waveMapperAdded = PJ_TRUE;
464
if (inputDeviceCount > 0) {
465
/* -1 is the WAVE_MAPPER */
466
for (i = (waveMapperAdded? 0 : -1); i < inputDeviceCount; ++i) {
467
UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
472
pj_bzero(&wic, sizeof(WAVEINCAPS));
474
mr = waveInGetDevCaps(uDeviceID, &wic, sizeof(WAVEINCAPS));
476
if (mr == MMSYSERR_NOMEM)
479
if (mr != MMSYSERR_NOERROR)
482
build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count],
485
#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0
486
/* Try to get the endpoint id of the audio device */
487
wf->dev_info[wf->dev_count].endpointId = L"";
489
mr = waveInMessage((HWAVEIN)IntToPtr(uDeviceID),
490
DRV_QUERYFUNCTIONINSTANCEIDSIZE,
491
(DWORD_PTR)&cbEndpointId, (DWORD_PTR)NULL);
492
if (mr == MMSYSERR_NOERROR) {
493
const wchar_t **epid = &wf->dev_info[wf->dev_count].endpointId;
494
*epid = (const wchar_t*) pj_pool_calloc(wf->pool,
496
mr = waveInMessage((HWAVEIN)IntToPtr(uDeviceID),
497
DRV_QUERYFUNCTIONINSTANCEID,
502
PJ_UNUSED_ARG(cbEndpointId);
509
if( outputDeviceCount > 0 )
511
/* -1 is the WAVE_MAPPER */
512
for (i = (waveMapperAdded? 0 : -1); i < outputDeviceCount; ++i) {
513
UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
518
pj_bzero(&woc, sizeof(WAVEOUTCAPS));
520
mr = waveOutGetDevCaps(uDeviceID, &woc, sizeof(WAVEOUTCAPS));
522
if (mr == MMSYSERR_NOMEM)
525
if (mr != MMSYSERR_NOERROR)
528
build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count],
531
#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0
532
/* Try to get the endpoint id of the audio device */
533
wf->dev_info[wf->dev_count].endpointId = L"";
535
mr = waveOutMessage((HWAVEOUT)IntToPtr(uDeviceID),
536
DRV_QUERYFUNCTIONINSTANCEIDSIZE,
537
(DWORD_PTR)&cbEndpointId, (DWORD_PTR)NULL);
538
if (mr == MMSYSERR_NOERROR) {
539
const wchar_t **epid = &wf->dev_info[wf->dev_count].endpointId;
540
*epid = (const wchar_t*)pj_pool_calloc(wf->pool,
542
mr = waveOutMessage((HWAVEOUT)IntToPtr(uDeviceID),
543
DRV_QUERYFUNCTIONINSTANCEID,
544
(DWORD_PTR)*epid, cbEndpointId);
547
PJ_UNUSED_ARG(cbEndpointId);
554
/* On Windows Vista and Windows 7 get the full device names */
557
PJ_LOG(4, (THIS_FILE, "WMME found %d devices:",
559
for (c = 0; c < wf->dev_count; ++c) {
560
PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d)",
562
wf->dev_info[c].info.name,
563
wf->dev_info[c].info.input_count,
564
wf->dev_info[c].info.output_count));
570
/* API: destroy factory */
571
static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
573
struct wmme_factory *wf = (struct wmme_factory*)f;
574
pj_pool_t *pool = wf->base_pool;
576
pj_pool_release(wf->pool);
577
wf->base_pool = NULL;
578
pj_pool_release(pool);
583
/* API: get number of devices */
584
static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
586
struct wmme_factory *wf = (struct wmme_factory*)f;
587
return wf->dev_count;
590
/* API: get device info */
591
static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
593
pjmedia_aud_dev_info *info)
595
struct wmme_factory *wf = (struct wmme_factory*)f;
597
PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
599
pj_memcpy(info, &wf->dev_info[index].info, sizeof(*info));
604
/* API: create default device parameter */
605
static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
607
pjmedia_aud_param *param)
609
struct wmme_factory *wf = (struct wmme_factory*)f;
610
struct wmme_dev_info *di = &wf->dev_info[index];
612
PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
614
pj_bzero(param, sizeof(*param));
615
if (di->info.input_count && di->info.output_count) {
616
param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
617
param->rec_id = index;
618
param->play_id = index;
619
} else if (di->info.input_count) {
620
param->dir = PJMEDIA_DIR_CAPTURE;
621
param->rec_id = index;
622
param->play_id = PJMEDIA_AUD_INVALID_DEV;
623
} else if (di->info.output_count) {
624
param->dir = PJMEDIA_DIR_PLAYBACK;
625
param->play_id = index;
626
param->rec_id = PJMEDIA_AUD_INVALID_DEV;
628
return PJMEDIA_EAUD_INVDEV;
631
param->clock_rate = di->info.default_samples_per_sec;
632
param->channel_count = 1;
633
param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000;
634
param->bits_per_sample = 16;
635
param->flags = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
636
PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
637
param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
638
param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
643
/* Internal: init WAVEFORMATEX */
644
static pj_status_t init_waveformatex(LPWAVEFORMATEX wfx,
645
const pjmedia_aud_param *prm)
648
pj_bzero(wfx, sizeof(WAVEFORMATEX));
649
if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16) {
650
enum { BYTES_PER_SAMPLE = 2 };
651
wfx->wFormatTag = WAVE_FORMAT_PCM;
652
wfx->nChannels = (pj_uint16_t)prm->channel_count;
653
wfx->nSamplesPerSec = prm->clock_rate;
654
wfx->nBlockAlign = (pj_uint16_t)(prm->channel_count *
656
wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count *
658
wfx->wBitsPerSample = 16;
662
} else if ((prm->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) &&
663
(prm->ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
664
prm->ext_fmt.id == PJMEDIA_FORMAT_PCMU))
668
ptime = prm->samples_per_frame * 1000 /
669
(prm->clock_rate * prm->channel_count);
670
wfx->wFormatTag = (pj_uint16_t)
671
((prm->ext_fmt.id==PJMEDIA_FORMAT_PCMA) ?
672
WAVE_FORMAT_ALAW : WAVE_FORMAT_MULAW);
673
wfx->nChannels = (pj_uint16_t)prm->channel_count;
674
wfx->nSamplesPerSec = prm->clock_rate;
675
wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count;
676
wfx->nBlockAlign = (pj_uint16_t)(wfx->nAvgBytesPerSec * ptime /
678
wfx->wBitsPerSample = 8;
685
return PJMEDIA_EAUD_BADFORMAT;
690
/* Get format name */
691
static const char *get_fmt_name(pj_uint32_t id)
695
if (id == PJMEDIA_FORMAT_L16)
697
pj_memcpy(name, &id, 4);
702
/* Internal: create WMME player device. */
703
static pj_status_t init_player_stream( struct wmme_factory *wf,
705
struct wmme_stream *parent,
706
struct wmme_channel *wmme_strm,
707
const pjmedia_aud_param *prm,
708
unsigned buffer_count)
716
PJ_ASSERT_RETURN(prm->play_id < (int)wf->dev_count, PJ_EINVAL);
719
* Create a wait event.
721
wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
722
if (NULL == wmme_strm->hEvent)
723
return pj_get_os_error();
726
* Set up wave format structure for opening the device.
728
status = init_waveformatex(&wfx, prm);
729
if (status != PJ_SUCCESS)
732
ptime = prm->samples_per_frame * 1000 /
733
(prm->clock_rate * prm->channel_count);
734
parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000;
736
flag = CALLBACK_EVENT;
737
if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16)
738
flag |= WAVE_FORMAT_DIRECT;
743
mr = waveOutOpen(&wmme_strm->hWave.Out,
744
wf->dev_info[prm->play_id].deviceId,
745
&wfx, (DWORD)wmme_strm->hEvent, 0, flag);
746
if (mr != MMSYSERR_NOERROR) {
747
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
750
/* Pause the wave out device */
751
mr = waveOutPause(wmme_strm->hWave.Out);
752
if (mr != MMSYSERR_NOERROR) {
753
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
757
* Create the buffers.
759
wmme_strm->WaveHdr = (WAVEHDR*)
760
pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
761
for (i = 0; i < buffer_count; ++i) {
762
wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool,
763
parent->bytes_per_frame);
764
wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame;
765
mr = waveOutPrepareHeader(wmme_strm->hWave.Out,
766
&(wmme_strm->WaveHdr[i]),
768
if (mr != MMSYSERR_NOERROR) {
769
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
771
mr = waveOutWrite(wmme_strm->hWave.Out, &(wmme_strm->WaveHdr[i]),
773
if (mr != MMSYSERR_NOERROR) {
774
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
778
wmme_strm->dwBufIdx = 0;
779
wmme_strm->dwMaxBufIdx = buffer_count;
780
wmme_strm->timestamp.u64 = 0;
782
/* Done setting up play device. */
783
PJ_LOG(4, (THIS_FILE,
784
" WaveAPI Sound player \"%s\" initialized ("
785
"format=%s, clock_rate=%d, "
786
"channel_count=%d, samples_per_frame=%d (%dms))",
787
wf->dev_info[prm->play_id].info.name,
788
get_fmt_name(prm->ext_fmt.id),
789
prm->clock_rate, prm->channel_count, prm->samples_per_frame,
790
prm->samples_per_frame * 1000 / prm->clock_rate));
796
/* Internal: create Windows Multimedia recorder device */
797
static pj_status_t init_capture_stream( struct wmme_factory *wf,
799
struct wmme_stream *parent,
800
struct wmme_channel *wmme_strm,
801
const pjmedia_aud_param *prm,
802
unsigned buffer_count)
809
PJ_ASSERT_RETURN(prm->rec_id < (int)wf->dev_count, PJ_EINVAL);
812
* Create a wait event.
814
wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
815
if (NULL == wmme_strm->hEvent) {
816
return pj_get_os_error();
820
* Set up wave format structure for opening the device.
822
init_waveformatex(&wfx, prm);
823
ptime = prm->samples_per_frame * 1000 /
824
(prm->clock_rate * prm->channel_count);
825
parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000;
827
flag = CALLBACK_EVENT;
828
if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16)
829
flag |= WAVE_FORMAT_DIRECT;
834
mr = waveInOpen(&wmme_strm->hWave.In,
835
wf->dev_info[prm->rec_id].deviceId,
836
&wfx, (DWORD)wmme_strm->hEvent, 0, flag);
837
if (mr != MMSYSERR_NOERROR) {
838
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
842
* Create the buffers.
844
wmme_strm->WaveHdr = (WAVEHDR*)
845
pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
846
for (i = 0; i < buffer_count; ++i) {
847
wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool,
848
parent->bytes_per_frame);
849
wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame;
850
mr = waveInPrepareHeader(wmme_strm->hWave.In,
851
&(wmme_strm->WaveHdr[i]),
853
if (mr != MMSYSERR_NOERROR) {
854
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
856
mr = waveInAddBuffer(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]),
858
if (mr != MMSYSERR_NOERROR) {
859
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
863
wmme_strm->dwBufIdx = 0;
864
wmme_strm->dwMaxBufIdx = buffer_count;
865
wmme_strm->timestamp.u64 = 0;
867
/* Done setting up play device. */
869
" WaveAPI Sound recorder \"%s\" initialized "
870
"(format=%s, clock_rate=%d, "
871
"channel_count=%d, samples_per_frame=%d (%dms))",
872
wf->dev_info[prm->rec_id].info.name,
873
get_fmt_name(prm->ext_fmt.id),
874
prm->clock_rate, prm->channel_count, prm->samples_per_frame,
875
prm->samples_per_frame * 1000 / prm->clock_rate));
881
/* WMME capture and playback thread. */
882
static int PJ_THREAD_FUNC wmme_dev_thread(void *arg)
884
struct wmme_stream *strm = (struct wmme_stream*)arg;
887
pj_status_t status = PJ_SUCCESS;
888
static unsigned rec_cnt, play_cnt;
889
enum { MAX_BURST = 1000 };
891
rec_cnt = play_cnt = 0;
894
events[eventCount++] = strm->thread_quit_event;
895
if (strm->param.dir & PJMEDIA_DIR_PLAYBACK)
896
events[eventCount++] = strm->play_strm.hEvent;
897
if (strm->param.dir & PJMEDIA_DIR_CAPTURE)
898
events[eventCount++] = strm->rec_strm.hEvent;
901
/* Raise self priority. We don't want the audio to be distorted by
904
#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0
905
if (strm->param.dir & PJMEDIA_DIR_PLAYBACK)
906
CeSetThreadPriority(GetCurrentThread(), 153);
908
CeSetThreadPriority(GetCurrentThread(), 247);
910
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
914
* Loop while not signalled to quit, wait for event objects to be
915
* signalled by WMME capture and play buffer.
917
while (status == PJ_SUCCESS)
921
pjmedia_dir signalled_dir;
923
/* Swap hWaveIn and hWaveOut to get equal opportunity for both */
925
HANDLE hTemp = events[2];
926
events[2] = events[1];
930
rc = WaitForMultipleObjects(eventCount, events, FALSE, INFINITE);
931
if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount)
934
if (rc == WAIT_OBJECT_0)
937
if (rc == (WAIT_OBJECT_0 + 1))
939
if (events[1] == strm->play_strm.hEvent)
940
signalled_dir = PJMEDIA_DIR_PLAYBACK;
942
signalled_dir = PJMEDIA_DIR_CAPTURE;
946
if (events[2] == strm->play_strm.hEvent)
947
signalled_dir = PJMEDIA_DIR_PLAYBACK;
949
signalled_dir = PJMEDIA_DIR_CAPTURE;
953
if (signalled_dir == PJMEDIA_DIR_PLAYBACK)
955
struct wmme_channel *wmme_strm = &strm->play_strm;
961
* Windows Multimedia has requested us to feed some frames to
965
for (burst=0; burst<MAX_BURST &&
966
(wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE);
969
void *buffer = wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
970
pjmedia_frame pcm_frame, *frame;
971
MMRESULT mr = MMSYSERR_NOERROR;
973
//PJ_LOG(5,(THIS_FILE, "Finished writing buffer %d",
974
// wmme_strm->dwBufIdx));
976
if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
980
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
981
frame->size = strm->bytes_per_frame;
983
frame->timestamp.u64 = wmme_strm->timestamp.u64;
987
frame = &strm->xfrm->base;
989
strm->xfrm->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
990
strm->xfrm->base.size = strm->bytes_per_frame;
991
strm->xfrm->base.buf = NULL;
992
strm->xfrm->base.timestamp.u64 = wmme_strm->timestamp.u64;
993
strm->xfrm->base.bit_info = 0;
996
/* Get frame from application. */
997
//PJ_LOG(5,(THIS_FILE, "xxx %u play_cb", play_cnt++));
998
status = (*strm->play_cb)(strm->user_data, frame);
1000
if (status != PJ_SUCCESS)
1003
if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
1005
if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
1006
pj_bzero(buffer, strm->bytes_per_frame);
1007
} else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
1008
pj_assert(!"Frame type not supported");
1009
} else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
1012
pj_assert(!"Frame type not supported");
1016
if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
1017
pj_memset(buffer, strm->silence_char,
1018
strm->bytes_per_frame);
1019
} else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
1021
sz = pjmedia_frame_ext_copy_payload(strm->xfrm,
1023
strm->bytes_per_frame);
1024
if (sz < strm->bytes_per_frame) {
1025
pj_memset((char*)buffer+sz,
1027
strm->bytes_per_frame - sz);
1030
pj_assert(!"Frame type not supported");
1034
/* Write to the device. */
1035
mr = waveOutWrite(wmme_strm->hWave.Out,
1036
&(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
1038
if (mr != MMSYSERR_NOERROR) {
1039
status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
1043
/* Increment position. */
1044
if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
1045
wmme_strm->dwBufIdx = 0;
1046
wmme_strm->timestamp.u64 += strm->param.samples_per_frame /
1047
strm->param.channel_count;
1052
struct wmme_channel *wmme_strm = &strm->rec_strm;
1054
MMRESULT mr = MMSYSERR_NOERROR;
1055
status = PJ_SUCCESS;
1058
* Windows Multimedia has indicated that it has some frames ready
1059
* in the capture buffer. Get as much frames as possible to
1060
* prevent overflows.
1064
static DWORD tc = 0;
1065
DWORD now = GetTickCount();
1069
if (tc == 0) tc = now;
1071
for (i = 0; i < wmme_strm->dwMaxBufIdx; ++i)
1074
bits |= wmme_strm->WaveHdr[i].dwFlags & WHDR_DONE;
1076
PJ_LOG(5,(THIS_FILE, "Record Signal> Index: %d, Delta: %4.4d, "
1078
wmme_strm->dwBufIdx,
1085
for (burst=0; burst<MAX_BURST &&
1086
(wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE);
1089
char* buffer = (char*)
1090
wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
1092
wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwBytesRecorded;
1093
pjmedia_frame pcm_frame, *frame;
1096
PJ_LOG(5,(THIS_FILE, "Read %d bytes from buffer %d", cap_len,
1097
wmme_strm->dwBufIdx));
1100
if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
1102
if (cap_len < strm->bytes_per_frame)
1103
pj_bzero(buffer + cap_len,
1104
strm->bytes_per_frame - cap_len);
1106
/* Copy the audio data out of the wave buffer. */
1107
pj_memcpy(strm->buffer, buffer, strm->bytes_per_frame);
1111
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
1112
frame->buf = strm->buffer;
1113
frame->size = strm->bytes_per_frame;
1114
frame->timestamp.u64 = wmme_strm->timestamp.u64;
1115
frame->bit_info = 0;
1119
frame = &strm->xfrm->base;
1121
frame->type = PJMEDIA_FRAME_TYPE_EXTENDED;
1123
frame->size = strm->bytes_per_frame;
1124
frame->timestamp.u64 = wmme_strm->timestamp.u64;
1125
frame->bit_info = 0;
1127
strm->xfrm->samples_cnt = 0;
1128
strm->xfrm->subframe_cnt = 0;
1129
pjmedia_frame_ext_append_subframe(
1131
strm->bytes_per_frame *8,
1132
strm->param.samples_per_frame
1136
/* Re-add the buffer to the device. */
1137
mr = waveInAddBuffer(wmme_strm->hWave.In,
1138
&(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
1140
if (mr != MMSYSERR_NOERROR) {
1141
status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
1147
//PJ_LOG(5,(THIS_FILE, "xxx %u rec_cb", rec_cnt++));
1148
status = (*strm->rec_cb)(strm->user_data, frame);
1149
if (status != PJ_SUCCESS)
1152
/* Increment position. */
1153
if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
1154
wmme_strm->dwBufIdx = 0;
1155
wmme_strm->timestamp.u64 += strm->param.samples_per_frame /
1156
strm->param.channel_count;
1161
PJ_LOG(5,(THIS_FILE, "WMME: thread stopping.."));
1166
/* API: create stream */
1167
static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
1168
const pjmedia_aud_param *param,
1169
pjmedia_aud_rec_cb rec_cb,
1170
pjmedia_aud_play_cb play_cb,
1172
pjmedia_aud_stream **p_aud_strm)
1174
struct wmme_factory *wf = (struct wmme_factory*)f;
1176
struct wmme_stream *strm;
1177
pj_uint8_t silence_char;
1180
switch (param->ext_fmt.id) {
1181
case PJMEDIA_FORMAT_L16:
1182
silence_char = '\0';
1184
case PJMEDIA_FORMAT_ALAW:
1185
silence_char = (pj_uint8_t)'\xd5';
1187
case PJMEDIA_FORMAT_ULAW:
1188
silence_char = (pj_uint8_t)'\xff';
1191
return PJMEDIA_EAUD_BADFORMAT;
1194
/* Create and Initialize stream descriptor */
1195
pool = pj_pool_create(wf->pf, "wmme-dev", 1000, 1000, NULL);
1196
PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
1198
strm = PJ_POOL_ZALLOC_T(pool, struct wmme_stream);
1199
pj_memcpy(&strm->param, param, sizeof(*param));
1201
strm->rec_cb = rec_cb;
1202
strm->play_cb = play_cb;
1203
strm->user_data = user_data;
1204
strm->fmt_id = param->ext_fmt.id;
1205
strm->silence_char = silence_char;
1207
/* Create player stream */
1208
if (param->dir & PJMEDIA_DIR_PLAYBACK) {
1211
if ((param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)==0) {
1212
strm->param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
1213
strm->param.output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
1216
buf_count = strm->param.output_latency_ms * param->clock_rate *
1217
param->channel_count / param->samples_per_frame / 1000;
1219
status = init_player_stream(wf, strm->pool,
1225
if (status != PJ_SUCCESS) {
1226
stream_destroy(&strm->base);
1231
/* Create capture stream */
1232
if (param->dir & PJMEDIA_DIR_CAPTURE) {
1235
if ((param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)==0) {
1236
strm->param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
1237
strm->param.input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
1240
buf_count = strm->param.input_latency_ms * param->clock_rate *
1241
param->channel_count / param->samples_per_frame / 1000;
1243
status = init_capture_stream(wf, strm->pool,
1249
if (status != PJ_SUCCESS) {
1250
stream_destroy(&strm->base);
1255
strm->buffer = pj_pool_alloc(pool, strm->bytes_per_frame);
1256
if (!strm->buffer) {
1257
pj_pool_release(pool);
1261
/* If format is extended, must create buffer for the extended frame. */
1262
if (strm->fmt_id != PJMEDIA_FORMAT_L16) {
1263
strm->xfrm_size = sizeof(pjmedia_frame_ext) +
1264
32 * sizeof(pjmedia_frame_ext_subframe) +
1265
strm->bytes_per_frame + 4;
1266
strm->xfrm = (pjmedia_frame_ext*)
1267
pj_pool_alloc(pool, strm->xfrm_size);
1270
/* Create the stop event */
1271
strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL);
1272
if (strm->thread_quit_event == NULL) {
1273
status = pj_get_os_error();
1274
stream_destroy(&strm->base);
1278
/* Create and start the thread */
1279
status = pj_thread_create(pool, "wmme", &wmme_dev_thread, strm, 0, 0,
1281
if (status != PJ_SUCCESS) {
1282
stream_destroy(&strm->base);
1286
/* Apply the remaining settings */
1287
if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
1288
stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1289
¶m->output_vol);
1294
strm->base.op = &stream_op;
1295
*p_aud_strm = &strm->base;
1300
/* API: Get stream info. */
1301
static pj_status_t stream_get_param(pjmedia_aud_stream *s,
1302
pjmedia_aud_param *pi)
1304
struct wmme_stream *strm = (struct wmme_stream*)s;
1306
PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1308
pj_memcpy(pi, &strm->param, sizeof(*pi));
1310
/* Update the volume setting */
1311
if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1312
&pi->output_vol) == PJ_SUCCESS)
1314
pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
1320
/* API: get capability */
1321
static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
1322
pjmedia_aud_dev_cap cap,
1325
struct wmme_stream *strm = (struct wmme_stream*)s;
1327
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1329
if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
1330
(strm->param.dir & PJMEDIA_DIR_CAPTURE))
1332
/* Recording latency */
1333
*(unsigned*)pval = strm->param.input_latency_ms;
1335
} else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
1336
(strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1338
/* Playback latency */
1339
*(unsigned*)pval = strm->param.output_latency_ms;
1341
} else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
1342
strm->play_strm.hWave.Out)
1344
/* Output volume setting */
1348
mr = waveOutGetVolume(strm->play_strm.hWave.Out, &waveVol);
1349
if (mr != MMSYSERR_NOERROR) {
1350
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
1354
*(unsigned*)pval = (waveVol * 100) / 0xFFFF;
1357
return PJMEDIA_EAUD_INVCAP;
1361
/* API: set capability */
1362
static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
1363
pjmedia_aud_dev_cap cap,
1366
struct wmme_stream *strm = (struct wmme_stream*)s;
1368
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1370
if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
1371
strm->play_strm.hWave.Out)
1373
/* Output volume setting */
1374
unsigned vol = *(unsigned*)pval;
1382
waveVol = (vol * 0xFFFF) / 100;
1383
waveVol |= (waveVol << 16);
1385
mr = waveOutSetVolume(strm->play_strm.hWave.Out, waveVol);
1386
status = (mr==MMSYSERR_NOERROR)? PJ_SUCCESS :
1387
PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
1388
if (status == PJ_SUCCESS) {
1389
strm->param.output_vol = *(unsigned*)pval;
1394
return PJMEDIA_EAUD_INVCAP;
1397
/* API: Start stream. */
1398
static pj_status_t stream_start(pjmedia_aud_stream *strm)
1400
struct wmme_stream *stream = (struct wmme_stream*)strm;
1403
if (stream->play_strm.hWave.Out != NULL)
1405
mr = waveOutRestart(stream->play_strm.hWave.Out);
1406
if (mr != MMSYSERR_NOERROR) {
1407
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
1409
PJ_LOG(4,(THIS_FILE, "WMME playback stream started"));
1412
if (stream->rec_strm.hWave.In != NULL)
1414
mr = waveInStart(stream->rec_strm.hWave.In);
1415
if (mr != MMSYSERR_NOERROR) {
1416
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
1418
PJ_LOG(4,(THIS_FILE, "WMME capture stream started"));
1424
/* API: Stop stream. */
1425
static pj_status_t stream_stop(pjmedia_aud_stream *strm)
1427
struct wmme_stream *stream = (struct wmme_stream*)strm;
1430
PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
1432
if (stream->play_strm.hWave.Out != NULL)
1434
mr = waveOutPause(stream->play_strm.hWave.Out);
1435
if (mr != MMSYSERR_NOERROR) {
1436
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
1438
PJ_LOG(4,(THIS_FILE, "Stopped WMME playback stream"));
1441
if (stream->rec_strm.hWave.In != NULL)
1443
mr = waveInStop(stream->rec_strm.hWave.In);
1444
if (mr != MMSYSERR_NOERROR) {
1445
return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
1447
PJ_LOG(4,(THIS_FILE, "Stopped WMME capture stream"));
1454
/* API: Destroy stream. */
1455
static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
1457
struct wmme_stream *stream = (struct wmme_stream*)strm;
1460
PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
1464
/* Stop the stream thread */
1467
SetEvent(stream->thread_quit_event);
1468
pj_thread_join(stream->thread);
1469
pj_thread_destroy(stream->thread);
1470
stream->thread = NULL;
1473
/* Close the thread quit event */
1474
if (stream->thread_quit_event)
1476
CloseHandle(stream->thread_quit_event);
1477
stream->thread_quit_event = NULL;
1480
/* Unprepare the headers and close the play device */
1481
if (stream->play_strm.hWave.Out)
1483
waveOutReset(stream->play_strm.hWave.Out);
1484
for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
1485
waveOutUnprepareHeader(stream->play_strm.hWave.Out,
1486
&(stream->play_strm.WaveHdr[i]),
1488
waveOutClose(stream->play_strm.hWave.Out);
1489
stream->play_strm.hWave.Out = NULL;
1492
/* Close the play event */
1493
if (stream->play_strm.hEvent)
1495
CloseHandle(stream->play_strm.hEvent);
1496
stream->play_strm.hEvent = NULL;
1499
/* Unprepare the headers and close the record device */
1500
if (stream->rec_strm.hWave.In)
1502
waveInReset(stream->rec_strm.hWave.In);
1503
for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
1504
waveInUnprepareHeader(stream->rec_strm.hWave.In,
1505
&(stream->rec_strm.WaveHdr[i]),
1507
waveInClose(stream->rec_strm.hWave.In);
1508
stream->rec_strm.hWave.In = NULL;
1511
/* Close the record event */
1512
if (stream->rec_strm.hEvent)
1514
CloseHandle(stream->rec_strm.hEvent);
1515
stream->rec_strm.hEvent = NULL;
1518
pj_pool_release(stream->pool);
1523
#endif /* PJMEDIA_AUDIO_DEV_HAS_WMME */