~ubuntu-branches/ubuntu/wily/sflphone/wily

« back to all changes in this revision

Viewing changes to daemon/libs/pjproject-2.1.0/pjmedia/src/pjmedia-audiodev/wmme_dev.c

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2015-01-07 14:51:16 UTC
  • mfrom: (4.3.5 sid)
  • Revision ID: package-import@ubuntu.com-20150107145116-yxnafinf4lrdvrmx
Tags: 1.4.1-0.1ubuntu1
* Merge with Debian, remaining changes:
 - Drop soprano, nepomuk build-dep
* Drop ubuntu patches, now upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* $Id: wmme_dev.c 3664 2011-07-19 03:42:28Z nanang $ */
2
 
/* 
3
 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4
 
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5
 
 *
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.
10
 
 *
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.
15
 
 *
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 
19
 
 */
20
 
#include <pjmedia-audiodev/audiodev_imp.h>
21
 
#include <pj/assert.h>
22
 
#include <pj/log.h>
23
 
#include <pj/os.h>
24
 
#include <pj/string.h>
25
 
#include <pj/unicode.h>
26
 
 
27
 
#if PJMEDIA_AUDIO_DEV_HAS_WMME
28
 
 
29
 
#ifdef _MSC_VER
30
 
#   pragma warning(push, 3)
31
 
#endif
32
 
 
33
 
#include <windows.h>
34
 
#include <mmsystem.h>
35
 
#include <mmreg.h>
36
 
 
37
 
#ifdef _MSC_VER
38
 
#   pragma warning(pop)
39
 
#endif
40
 
 
41
 
#ifndef PJMEDIA_WMME_DEV_USE_MMDEVICE_API
42
 
#   define PJMEDIA_WMME_DEV_USE_MMDEVICE_API \
43
 
           (defined(_WIN32_WINNT) && (_WIN32_WINNT>=0x0600))
44
 
#endif
45
 
 
46
 
#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0
47
 
#   define DRV_QUERYFUNCTIONINSTANCEID     (DRV_RESERVED + 17)
48
 
#   define DRV_QUERYFUNCTIONINSTANCEIDSIZE (DRV_RESERVED + 18)
49
 
#endif
50
 
 
51
 
/* mingw lacks WAVE_FORMAT_ALAW/MULAW */
52
 
#ifndef WAVE_FORMAT_ALAW
53
 
#   define  WAVE_FORMAT_ALAW       0x0006
54
 
#endif
55
 
#ifndef WAVE_FORMAT_MULAW
56
 
#   define  WAVE_FORMAT_MULAW      0x0007
57
 
#endif
58
 
 
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")
63
 
#endif
64
 
 
65
 
 
66
 
#define THIS_FILE                       "wmme_dev.c"
67
 
 
68
 
/* WMME device info */
69
 
struct wmme_dev_info
70
 
{
71
 
    pjmedia_aud_dev_info         info;
72
 
    unsigned                     deviceId;
73
 
    const wchar_t               *endpointId;
74
 
};
75
 
 
76
 
/* WMME factory */
77
 
struct wmme_factory
78
 
{
79
 
    pjmedia_aud_dev_factory      base;
80
 
    pj_pool_t                   *base_pool;
81
 
    pj_pool_t                   *pool;
82
 
    pj_pool_factory             *pf;
83
 
 
84
 
    unsigned                     dev_count;
85
 
    struct wmme_dev_info        *dev_info;
86
 
};
87
 
 
88
 
 
89
 
/* Individual WMME capture/playback stream descriptor */
90
 
struct wmme_channel
91
 
{
92
 
    union
93
 
    {
94
 
        HWAVEIN   In;
95
 
        HWAVEOUT  Out;
96
 
    } hWave;
97
 
 
98
 
    WAVEHDR      *WaveHdr;
99
 
    HANDLE        hEvent;
100
 
    DWORD         dwBufIdx;
101
 
    DWORD         dwMaxBufIdx;
102
 
    pj_timestamp  timestamp;
103
 
};
104
 
 
105
 
 
106
 
/* Sound stream. */
107
 
struct wmme_stream
108
 
{
109
 
    pjmedia_aud_stream   base;              /**< Base stream           */
110
 
    pjmedia_aud_param    param;             /**< Settings              */
111
 
    pj_pool_t           *pool;              /**< Memory pool.          */
112
 
 
113
 
    pjmedia_aud_rec_cb   rec_cb;            /**< Capture callback.     */
114
 
    pjmedia_aud_play_cb  play_cb;           /**< Playback callback.    */
115
 
    void                *user_data;         /**< Application data.     */
116
 
 
117
 
    struct wmme_channel  play_strm;         /**< Playback stream.      */
118
 
    struct wmme_channel  rec_strm;          /**< Capture stream.       */
119
 
 
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       */
124
 
 
125
 
    pjmedia_frame_ext   *xfrm;              /**< Extended frame buffer */
126
 
    unsigned             xfrm_size;         /**< Total ext frm size    */
127
 
 
128
 
    pj_thread_t         *thread;            /**< Thread handle.        */
129
 
    HANDLE               thread_quit_event; /**< Quit signal to thread */
130
 
};
131
 
 
132
 
 
133
 
/* Prototypes */
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, 
139
 
                                        unsigned index,
140
 
                                        pjmedia_aud_dev_info *info);
141
 
static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
142
 
                                         unsigned index,
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,
148
 
                                         void *user_data,
149
 
                                         pjmedia_aud_stream **p_aud_strm);
150
 
 
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,
155
 
                                  void *value);
156
 
static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
157
 
                                  pjmedia_aud_dev_cap cap,
158
 
                                  const void *value);
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);
162
 
 
163
 
 
164
 
/* Operations */
165
 
static pjmedia_aud_dev_factory_op factory_op =
166
 
{
167
 
    &factory_init,
168
 
    &factory_destroy,
169
 
    &factory_get_dev_count,
170
 
    &factory_get_dev_info,
171
 
    &factory_default_param,
172
 
    &factory_create_stream,
173
 
    &factory_refresh
174
 
};
175
 
 
176
 
static pjmedia_aud_stream_op stream_op = 
177
 
{
178
 
    &stream_get_param,
179
 
    &stream_get_cap,
180
 
    &stream_set_cap,
181
 
    &stream_start,
182
 
    &stream_stop,
183
 
    &stream_destroy
184
 
};
185
 
 
186
 
 
187
 
/****************************************************************************
188
 
 * Factory operations
189
 
 */
190
 
/*
191
 
 * Init WMME audio driver.
192
 
 */
193
 
pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf)
194
 
{
195
 
    struct wmme_factory *f;
196
 
    pj_pool_t *pool;
197
 
 
198
 
    pool = pj_pool_create(pf, "WMME base", 1000, 1000, NULL);
199
 
    f = PJ_POOL_ZALLOC_T(pool, struct wmme_factory);
200
 
    f->pf = pf;
201
 
    f->base_pool = pool;
202
 
    f->base.op = &factory_op;
203
 
 
204
 
    return &f->base;
205
 
}
206
 
 
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
210
 
 */
211
 
#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0
212
 
 
213
 
#define COBJMACROS
214
 
#include <mmdeviceapi.h>
215
 
#define INITGUID
216
 
#include <Guiddef.h>
217
 
#include <FunctionDiscoveryKeys_devpkey.h>
218
 
 
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);
223
 
 
224
 
static void get_dev_names(pjmedia_aud_dev_factory *f)
225
 
{
226
 
    struct wmme_factory *wf = (struct wmme_factory*)f;
227
 
    HRESULT              coinit = S_OK;
228
 
    HRESULT              hr = S_OK;
229
 
    IMMDeviceEnumerator *pEnumerator = NULL;
230
 
    IMMDeviceCollection *pDevices = NULL;
231
 
    UINT                 cDevices = 0;
232
 
    UINT                 nDevice = 0;
233
 
 
234
 
    coinit = CoInitializeEx(NULL, COINIT_MULTITHREADED);
235
 
    if (coinit == RPC_E_CHANGED_MODE)
236
 
        coinit = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
237
 
    if (FAILED(coinit))
238
 
        goto on_error;
239
 
 
240
 
    hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
241
 
                          CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator,
242
 
                          (void**)&pEnumerator);
243
 
    if (FAILED(hr))
244
 
        goto on_error;
245
 
    hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eAll,
246
 
                                                DEVICE_STATE_ACTIVE,
247
 
                                                &pDevices);
248
 
    if (FAILED(hr))
249
 
        goto on_error;
250
 
    hr = IMMDeviceCollection_GetCount(pDevices, &cDevices);
251
 
    if (FAILED(hr))
252
 
        goto on_error;
253
 
 
254
 
    for (nDevice = 0; nDevice < cDevices; ++nDevice) {
255
 
        IMMDevice      *pDevice = NULL;
256
 
        IPropertyStore *pProps = NULL;
257
 
        LPWSTR          pwszID = NULL;
258
 
        PROPVARIANT     varName;
259
 
        unsigned        i;
260
 
 
261
 
        PropVariantInit(&varName);
262
 
 
263
 
        hr = IMMDeviceCollection_Item(pDevices, nDevice, &pDevice);
264
 
        if (FAILED(hr))
265
 
            goto cleanup;
266
 
        hr = IMMDevice_GetId(pDevice, &pwszID);
267
 
        if (FAILED(hr))
268
 
            goto cleanup;
269
 
        hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProps);
270
 
        if (FAILED(hr))
271
 
            goto cleanup;
272
 
        hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName,
273
 
                                     &varName);
274
 
        if (FAILED(hr))
275
 
            goto cleanup;
276
 
 
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));
281
 
                break;
282
 
            }
283
 
        }
284
 
 
285
 
        PropVariantClear(&varName);
286
 
 
287
 
    cleanup:
288
 
        if (pProps)
289
 
            IPropertyStore_Release(pProps);
290
 
        if (pwszID)
291
 
            CoTaskMemFree(pwszID);
292
 
        if (pDevice)
293
 
            hr = IMMDevice_Release(pDevice);
294
 
    }
295
 
 
296
 
on_error:
297
 
    if (pDevices)
298
 
        hr = IMMDeviceCollection_Release(pDevices);
299
 
 
300
 
    if (pEnumerator)
301
 
        hr = IMMDeviceEnumerator_Release(pEnumerator);
302
 
 
303
 
    if (SUCCEEDED(coinit))
304
 
        CoUninitialize();
305
 
}
306
 
 
307
 
#else
308
 
 
309
 
static void get_dev_names(pjmedia_aud_dev_factory *f)
310
 
{
311
 
    PJ_UNUSED_ARG(f);
312
 
}
313
 
 
314
 
#endif
315
 
 
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)
319
 
{
320
 
#define WIC_WOC(wic,woc,field)  (wic? wic->field : woc->field)
321
 
 
322
 
    pj_bzero(wdi, sizeof(*wdi));
323
 
    wdi->deviceId = deviceId;
324
 
 
325
 
    /* Device Name */
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';
329
 
    } else {
330
 
        const pj_char_t *szPname = WIC_WOC(wic, woc, szPname);
331
 
        PJ_DECL_ANSI_TEMP_BUF(wTmp, sizeof(wdi->info.name));
332
 
        
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';
337
 
    }
338
 
 
339
 
    wdi->info.default_samples_per_sec = 16000;
340
 
    strcpy(wdi->info.driver, "WMME");
341
 
 
342
 
    if (wic) {
343
 
        wdi->info.input_count = wic->wChannels;
344
 
        wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
345
 
 
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!
349
 
         */
350
 
        if (wdi->info.input_count<1 || wdi->info.input_count>256) {
351
 
            wdi->info.input_count = 2;
352
 
        }
353
 
    }
354
 
 
355
 
    if (woc) {
356
 
        wdi->info.output_count = woc->wChannels;
357
 
        wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
358
 
        
359
 
        if (woc->dwSupport & WAVECAPS_VOLUME) {
360
 
            wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
361
 
        }
362
 
 
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!
366
 
         */
367
 
        if (wdi->info.output_count<1 || wdi->info.output_count>256) {
368
 
            wdi->info.output_count = 2;
369
 
        }
370
 
    }
371
 
 
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);
381
 
}
382
 
 
383
 
/* API: init factory */
384
 
static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
385
 
{
386
 
    pj_status_t ret = factory_refresh(f);
387
 
    if (ret != PJ_SUCCESS)
388
 
        return ret;
389
 
 
390
 
    PJ_LOG(4, (THIS_FILE, "WMME initialized"));
391
 
    return PJ_SUCCESS;
392
 
}
393
 
 
394
 
/* API: refresh the device list */
395
 
static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f)
396
 
{
397
 
    struct wmme_factory *wf = (struct wmme_factory*)f;
398
 
    unsigned c;
399
 
    int i;
400
 
    int inputDeviceCount, outputDeviceCount, devCount=0;
401
 
    pj_bool_t waveMapperAdded = PJ_FALSE;
402
 
 
403
 
    if (wf->pool != NULL) {
404
 
        pj_pool_release(wf->pool);
405
 
        wf->pool = NULL;
406
 
    }
407
 
 
408
 
    /* Enumerate sound devices */
409
 
    wf->dev_count = 0;
410
 
    wf->pool = pj_pool_create(wf->pf, "WMME", 1000, 1000, NULL);
411
 
 
412
 
    inputDeviceCount = waveInGetNumDevs();
413
 
    devCount += inputDeviceCount;
414
 
 
415
 
    outputDeviceCount = waveOutGetNumDevs();
416
 
    devCount += outputDeviceCount;
417
 
 
418
 
    if (devCount) {
419
 
        /* Assume there is WAVE_MAPPER */
420
 
        devCount += 2;
421
 
    }
422
 
 
423
 
    if (devCount==0) {
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 
430
 
         * get_dev_count().
431
 
        return PJMEDIA_EAUD_NODEV;
432
 
         */
433
 
        return PJ_SUCCESS;
434
 
    }
435
 
 
436
 
    wf->dev_info = (struct wmme_dev_info*)
437
 
                   pj_pool_calloc(wf->pool, devCount, 
438
 
                                  sizeof(struct wmme_dev_info));
439
 
 
440
 
    if (inputDeviceCount && outputDeviceCount) {
441
 
        /* Attempt to add WAVE_MAPPER as input and output device */
442
 
        WAVEINCAPS wic;
443
 
        MMRESULT mr;
444
 
 
445
 
        pj_bzero(&wic, sizeof(WAVEINCAPS));
446
 
        mr = waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(WAVEINCAPS));
447
 
 
448
 
        if (mr == MMSYSERR_NOERROR) {
449
 
            WAVEOUTCAPS woc;
450
 
 
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], 
455
 
                               &wic, &woc);
456
 
                wf->dev_info[wf->dev_count].endpointId = L"";
457
 
                ++wf->dev_count;
458
 
                waveMapperAdded = PJ_TRUE;
459
 
            }
460
 
        }
461
 
 
462
 
    }
463
 
 
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);
468
 
            WAVEINCAPS wic;
469
 
            MMRESULT mr;
470
 
            DWORD cbEndpointId;
471
 
 
472
 
            pj_bzero(&wic, sizeof(WAVEINCAPS));
473
 
 
474
 
            mr = waveInGetDevCaps(uDeviceID, &wic, sizeof(WAVEINCAPS));
475
 
 
476
 
            if (mr == MMSYSERR_NOMEM)
477
 
                return PJ_ENOMEM;
478
 
 
479
 
            if (mr != MMSYSERR_NOERROR)
480
 
                continue;
481
 
 
482
 
            build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count], 
483
 
                           &wic, NULL);
484
 
 
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"";
488
 
 
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,
495
 
                                                        cbEndpointId, 1);
496
 
                mr = waveInMessage((HWAVEIN)IntToPtr(uDeviceID),
497
 
                                   DRV_QUERYFUNCTIONINSTANCEID,
498
 
                                   (DWORD_PTR)*epid,
499
 
                                   cbEndpointId);
500
 
            }
501
 
#else
502
 
            PJ_UNUSED_ARG(cbEndpointId);
503
 
#endif
504
 
 
505
 
            ++wf->dev_count;
506
 
        }
507
 
    }
508
 
 
509
 
    if( outputDeviceCount > 0 )
510
 
    {
511
 
        /* -1 is the WAVE_MAPPER */
512
 
        for (i = (waveMapperAdded? 0 : -1); i < outputDeviceCount; ++i) {
513
 
            UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
514
 
            WAVEOUTCAPS woc;
515
 
            MMRESULT mr;
516
 
            DWORD cbEndpointId;
517
 
 
518
 
            pj_bzero(&woc, sizeof(WAVEOUTCAPS));
519
 
 
520
 
            mr = waveOutGetDevCaps(uDeviceID, &woc, sizeof(WAVEOUTCAPS));
521
 
 
522
 
            if (mr == MMSYSERR_NOMEM)
523
 
                return PJ_ENOMEM;
524
 
 
525
 
            if (mr != MMSYSERR_NOERROR)
526
 
                continue;
527
 
 
528
 
            build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count], 
529
 
                           NULL, &woc);
530
 
 
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"";
534
 
 
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,
541
 
                                                       cbEndpointId, 1);
542
 
                mr = waveOutMessage((HWAVEOUT)IntToPtr(uDeviceID),
543
 
                                    DRV_QUERYFUNCTIONINSTANCEID,
544
 
                                    (DWORD_PTR)*epid, cbEndpointId);
545
 
            }
546
 
#else
547
 
            PJ_UNUSED_ARG(cbEndpointId);
548
 
#endif
549
 
 
550
 
            ++wf->dev_count;
551
 
        }
552
 
    }
553
 
 
554
 
    /* On Windows Vista and Windows 7 get the full device names */
555
 
    get_dev_names(f);
556
 
 
557
 
    PJ_LOG(4, (THIS_FILE, "WMME found %d devices:",
558
 
               wf->dev_count));
559
 
    for (c = 0; c < wf->dev_count; ++c) {
560
 
        PJ_LOG(4, (THIS_FILE, " dev_id %d: %s  (in=%d, out=%d)", 
561
 
            c,
562
 
            wf->dev_info[c].info.name,
563
 
            wf->dev_info[c].info.input_count,
564
 
            wf->dev_info[c].info.output_count));
565
 
    }
566
 
 
567
 
    return PJ_SUCCESS;
568
 
}
569
 
 
570
 
/* API: destroy factory */
571
 
static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
572
 
{
573
 
    struct wmme_factory *wf = (struct wmme_factory*)f;
574
 
    pj_pool_t *pool = wf->base_pool;
575
 
 
576
 
    pj_pool_release(wf->pool);
577
 
    wf->base_pool = NULL;
578
 
    pj_pool_release(pool);
579
 
 
580
 
    return PJ_SUCCESS;
581
 
}
582
 
 
583
 
/* API: get number of devices */
584
 
static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
585
 
{
586
 
    struct wmme_factory *wf = (struct wmme_factory*)f;
587
 
    return wf->dev_count;
588
 
}
589
 
 
590
 
/* API: get device info */
591
 
static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
592
 
                                        unsigned index,
593
 
                                        pjmedia_aud_dev_info *info)
594
 
{
595
 
    struct wmme_factory *wf = (struct wmme_factory*)f;
596
 
 
597
 
    PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
598
 
 
599
 
    pj_memcpy(info, &wf->dev_info[index].info, sizeof(*info));
600
 
 
601
 
    return PJ_SUCCESS;
602
 
}
603
 
 
604
 
/* API: create default device parameter */
605
 
static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
606
 
                                         unsigned index,
607
 
                                         pjmedia_aud_param *param)
608
 
{
609
 
    struct wmme_factory *wf = (struct wmme_factory*)f;
610
 
    struct wmme_dev_info *di = &wf->dev_info[index];
611
 
 
612
 
    PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
613
 
 
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;
627
 
    } else {
628
 
        return PJMEDIA_EAUD_INVDEV;
629
 
    }
630
 
 
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;
639
 
 
640
 
    return PJ_SUCCESS;
641
 
}
642
 
 
643
 
/* Internal: init WAVEFORMATEX */
644
 
static pj_status_t init_waveformatex(LPWAVEFORMATEX wfx, 
645
 
                                     const pjmedia_aud_param *prm)
646
 
{
647
 
 
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 * 
655
 
                                         BYTES_PER_SAMPLE);
656
 
        wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count * 
657
 
                               BYTES_PER_SAMPLE;
658
 
        wfx->wBitsPerSample = 16;
659
 
 
660
 
        return PJ_SUCCESS;
661
 
 
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))
665
 
    {
666
 
        unsigned ptime;
667
 
 
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 /
677
 
                                         1000);
678
 
        wfx->wBitsPerSample = 8;
679
 
        wfx->cbSize = 0;
680
 
 
681
 
        return PJ_SUCCESS;
682
 
 
683
 
    } else {
684
 
 
685
 
        return PJMEDIA_EAUD_BADFORMAT;
686
 
 
687
 
    }
688
 
}
689
 
 
690
 
/* Get format name */
691
 
static const char *get_fmt_name(pj_uint32_t id)
692
 
{
693
 
    static char name[8];
694
 
 
695
 
    if (id == PJMEDIA_FORMAT_L16)
696
 
        return "PCM";
697
 
    pj_memcpy(name, &id, 4);
698
 
    name[4] = '\0';
699
 
    return name;
700
 
}
701
 
 
702
 
/* Internal: create WMME player device. */
703
 
static pj_status_t init_player_stream(  struct wmme_factory *wf,
704
 
                                        pj_pool_t *pool,
705
 
                                        struct wmme_stream *parent,
706
 
                                        struct wmme_channel *wmme_strm,
707
 
                                        const pjmedia_aud_param *prm,
708
 
                                        unsigned buffer_count)
709
 
{
710
 
    MMRESULT mr;
711
 
    WAVEFORMATEX wfx; 
712
 
    unsigned i, ptime;
713
 
    DWORD flag;
714
 
    pj_status_t status;
715
 
 
716
 
    PJ_ASSERT_RETURN(prm->play_id < (int)wf->dev_count, PJ_EINVAL);
717
 
 
718
 
    /*
719
 
     * Create a wait event.
720
 
     */
721
 
    wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
722
 
    if (NULL == wmme_strm->hEvent)
723
 
        return pj_get_os_error();
724
 
 
725
 
    /*
726
 
     * Set up wave format structure for opening the device.
727
 
     */
728
 
    status = init_waveformatex(&wfx, prm);
729
 
    if (status != PJ_SUCCESS)
730
 
        return status;
731
 
 
732
 
    ptime = prm->samples_per_frame * 1000 / 
733
 
            (prm->clock_rate * prm->channel_count);
734
 
    parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000;
735
 
 
736
 
    flag = CALLBACK_EVENT;
737
 
    if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16)
738
 
        flag |= WAVE_FORMAT_DIRECT;
739
 
 
740
 
    /*
741
 
     * Open wave device.
742
 
     */
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);
748
 
    }
749
 
 
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);
754
 
    }
755
 
 
756
 
    /*
757
 
     * Create the buffers. 
758
 
     */
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]),
767
 
                                  sizeof(WAVEHDR));
768
 
        if (mr != MMSYSERR_NOERROR) {
769
 
            return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); 
770
 
        }
771
 
        mr = waveOutWrite(wmme_strm->hWave.Out, &(wmme_strm->WaveHdr[i]), 
772
 
                          sizeof(WAVEHDR));
773
 
        if (mr != MMSYSERR_NOERROR) {
774
 
            return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
775
 
        }
776
 
    }
777
 
 
778
 
    wmme_strm->dwBufIdx = 0;
779
 
    wmme_strm->dwMaxBufIdx = buffer_count;
780
 
    wmme_strm->timestamp.u64 = 0;
781
 
 
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));
791
 
 
792
 
    return PJ_SUCCESS;
793
 
}
794
 
 
795
 
 
796
 
/* Internal: create Windows Multimedia recorder device */
797
 
static pj_status_t init_capture_stream( struct wmme_factory *wf,
798
 
                                        pj_pool_t *pool,
799
 
                                        struct wmme_stream *parent,
800
 
                                        struct wmme_channel *wmme_strm,
801
 
                                        const pjmedia_aud_param *prm,
802
 
                                        unsigned buffer_count)
803
 
{
804
 
    MMRESULT mr;
805
 
    WAVEFORMATEX wfx; 
806
 
    DWORD flag;
807
 
    unsigned i, ptime;
808
 
 
809
 
    PJ_ASSERT_RETURN(prm->rec_id < (int)wf->dev_count, PJ_EINVAL);
810
 
 
811
 
    /*
812
 
    * Create a wait event.
813
 
    */
814
 
    wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
815
 
    if (NULL == wmme_strm->hEvent) {
816
 
        return pj_get_os_error();
817
 
    }
818
 
 
819
 
    /*
820
 
     * Set up wave format structure for opening the device.
821
 
     */
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;
826
 
 
827
 
    flag = CALLBACK_EVENT;
828
 
    if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16)
829
 
        flag |= WAVE_FORMAT_DIRECT;
830
 
 
831
 
    /*
832
 
     * Open wave device.
833
 
     */
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);
839
 
    }
840
 
 
841
 
    /*
842
 
     * Create the buffers. 
843
 
     */
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]),
852
 
                                 sizeof(WAVEHDR));
853
 
        if (mr != MMSYSERR_NOERROR) {
854
 
            return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
855
 
        }
856
 
        mr = waveInAddBuffer(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]), 
857
 
                             sizeof(WAVEHDR));
858
 
        if (mr != MMSYSERR_NOERROR) {
859
 
            return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
860
 
        }
861
 
    }
862
 
 
863
 
    wmme_strm->dwBufIdx = 0;
864
 
    wmme_strm->dwMaxBufIdx = buffer_count;
865
 
    wmme_strm->timestamp.u64 = 0;
866
 
 
867
 
    /* Done setting up play device. */
868
 
    PJ_LOG(4,(THIS_FILE, 
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));
876
 
 
877
 
    return PJ_SUCCESS;
878
 
}
879
 
 
880
 
 
881
 
/* WMME capture and playback thread. */
882
 
static int PJ_THREAD_FUNC wmme_dev_thread(void *arg)
883
 
{
884
 
    struct wmme_stream *strm = (struct wmme_stream*)arg;
885
 
    HANDLE events[3];
886
 
    unsigned eventCount;
887
 
    pj_status_t status = PJ_SUCCESS;
888
 
    static unsigned rec_cnt, play_cnt;
889
 
    enum { MAX_BURST = 1000 };
890
 
 
891
 
    rec_cnt = play_cnt = 0;
892
 
 
893
 
    eventCount = 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;
899
 
 
900
 
 
901
 
    /* Raise self priority. We don't want the audio to be distorted by
902
 
     * system activity.
903
 
     */
904
 
#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0
905
 
    if (strm->param.dir & PJMEDIA_DIR_PLAYBACK)
906
 
        CeSetThreadPriority(GetCurrentThread(), 153);
907
 
    else
908
 
        CeSetThreadPriority(GetCurrentThread(), 247);
909
 
#else
910
 
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
911
 
#endif
912
 
 
913
 
    /*
914
 
     * Loop while not signalled to quit, wait for event objects to be 
915
 
     * signalled by WMME capture and play buffer.
916
 
     */
917
 
    while (status == PJ_SUCCESS)
918
 
    {
919
 
 
920
 
        DWORD rc;
921
 
        pjmedia_dir signalled_dir;
922
 
 
923
 
        /* Swap hWaveIn and hWaveOut to get equal opportunity for both */
924
 
        if (eventCount==3) {
925
 
            HANDLE hTemp = events[2];
926
 
            events[2] = events[1];
927
 
            events[1] = hTemp;
928
 
        }
929
 
 
930
 
        rc = WaitForMultipleObjects(eventCount, events, FALSE, INFINITE);
931
 
        if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount)
932
 
            continue;
933
 
 
934
 
        if (rc == WAIT_OBJECT_0)
935
 
            break;
936
 
 
937
 
        if (rc == (WAIT_OBJECT_0 + 1))
938
 
        {
939
 
            if (events[1] == strm->play_strm.hEvent)
940
 
                signalled_dir = PJMEDIA_DIR_PLAYBACK;
941
 
            else
942
 
                signalled_dir = PJMEDIA_DIR_CAPTURE;
943
 
        }
944
 
        else
945
 
        {
946
 
            if (events[2] == strm->play_strm.hEvent)
947
 
                signalled_dir = PJMEDIA_DIR_PLAYBACK;
948
 
            else
949
 
                signalled_dir = PJMEDIA_DIR_CAPTURE;
950
 
        }
951
 
 
952
 
 
953
 
        if (signalled_dir == PJMEDIA_DIR_PLAYBACK)
954
 
        {
955
 
            struct wmme_channel *wmme_strm = &strm->play_strm;
956
 
            unsigned burst;
957
 
 
958
 
            status = PJ_SUCCESS;
959
 
 
960
 
            /*
961
 
             * Windows Multimedia has requested us to feed some frames to
962
 
             * playback buffer.
963
 
             */
964
 
 
965
 
            for (burst=0; burst<MAX_BURST &&
966
 
                 (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE);
967
 
                 ++burst)
968
 
            {
969
 
                void *buffer = wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
970
 
                pjmedia_frame pcm_frame, *frame;
971
 
                MMRESULT mr = MMSYSERR_NOERROR;
972
 
 
973
 
                //PJ_LOG(5,(THIS_FILE, "Finished writing buffer %d", 
974
 
                //        wmme_strm->dwBufIdx));
975
 
 
976
 
                if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
977
 
                    /* PCM mode */
978
 
                    frame = &pcm_frame;
979
 
 
980
 
                    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
981
 
                    frame->size = strm->bytes_per_frame;
982
 
                    frame->buf = buffer;
983
 
                    frame->timestamp.u64 = wmme_strm->timestamp.u64;
984
 
                    frame->bit_info = 0;
985
 
                } else {
986
 
                    /* Codec mode */
987
 
                    frame = &strm->xfrm->base;
988
 
 
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;
994
 
                }
995
 
 
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);
999
 
 
1000
 
                if (status != PJ_SUCCESS)
1001
 
                    break;
1002
 
 
1003
 
                if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
1004
 
                    /* PCM mode */
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) {
1010
 
                        /* Nothing to do */
1011
 
                    } else {
1012
 
                        pj_assert(!"Frame type not supported");
1013
 
                    }
1014
 
                } else {
1015
 
                    /* Codec mode */
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) {
1020
 
                        unsigned sz;
1021
 
                        sz = pjmedia_frame_ext_copy_payload(strm->xfrm,
1022
 
                                                            buffer,
1023
 
                                                            strm->bytes_per_frame);
1024
 
                        if (sz < strm->bytes_per_frame) {
1025
 
                            pj_memset((char*)buffer+sz,
1026
 
                                      strm->silence_char,
1027
 
                                      strm->bytes_per_frame - sz);
1028
 
                        }
1029
 
                    } else {
1030
 
                        pj_assert(!"Frame type not supported");
1031
 
                    }
1032
 
                }
1033
 
 
1034
 
                /* Write to the device. */
1035
 
                mr = waveOutWrite(wmme_strm->hWave.Out, 
1036
 
                                  &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
1037
 
                                  sizeof(WAVEHDR));
1038
 
                if (mr != MMSYSERR_NOERROR) {
1039
 
                    status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
1040
 
                    break;
1041
 
                }
1042
 
 
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;
1048
 
            } /* for */
1049
 
        }
1050
 
        else
1051
 
        {
1052
 
            struct wmme_channel *wmme_strm = &strm->rec_strm;
1053
 
            unsigned burst;
1054
 
            MMRESULT mr = MMSYSERR_NOERROR;
1055
 
            status = PJ_SUCCESS;
1056
 
 
1057
 
            /*
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.
1061
 
            */
1062
 
#if 0
1063
 
            {
1064
 
                static DWORD tc = 0;
1065
 
                DWORD now = GetTickCount();
1066
 
                DWORD i = 0;
1067
 
                DWORD bits = 0;
1068
 
 
1069
 
                if (tc == 0) tc = now;
1070
 
 
1071
 
                for (i = 0; i < wmme_strm->dwMaxBufIdx; ++i)
1072
 
                {
1073
 
                    bits = bits << 4;
1074
 
                    bits |= wmme_strm->WaveHdr[i].dwFlags & WHDR_DONE;
1075
 
                }
1076
 
                PJ_LOG(5,(THIS_FILE, "Record Signal> Index: %d, Delta: %4.4d, "
1077
 
                          "Flags: %6.6x\n",
1078
 
                          wmme_strm->dwBufIdx,
1079
 
                          now - tc,
1080
 
                          bits));
1081
 
                tc = now;
1082
 
            }
1083
 
#endif
1084
 
 
1085
 
            for (burst=0; burst<MAX_BURST &&
1086
 
                 (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE);
1087
 
                 ++burst)
1088
 
            {
1089
 
                char* buffer = (char*)
1090
 
                               wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
1091
 
                unsigned cap_len = 
1092
 
                        wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwBytesRecorded;
1093
 
                pjmedia_frame pcm_frame, *frame;
1094
 
 
1095
 
                /*
1096
 
                PJ_LOG(5,(THIS_FILE, "Read %d bytes from buffer %d", cap_len, 
1097
 
                          wmme_strm->dwBufIdx));
1098
 
                */
1099
 
            
1100
 
                if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
1101
 
                    /* PCM mode */
1102
 
                    if (cap_len < strm->bytes_per_frame)
1103
 
                        pj_bzero(buffer + cap_len, 
1104
 
                                 strm->bytes_per_frame - cap_len);
1105
 
 
1106
 
                    /* Copy the audio data out of the wave buffer. */
1107
 
                    pj_memcpy(strm->buffer, buffer, strm->bytes_per_frame);
1108
 
 
1109
 
                    /* Prepare frame */
1110
 
                    frame = &pcm_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;
1116
 
 
1117
 
                } else {
1118
 
                    /* Codec mode */
1119
 
                    frame = &strm->xfrm->base;
1120
 
 
1121
 
                    frame->type = PJMEDIA_FRAME_TYPE_EXTENDED;
1122
 
                    frame->buf = NULL;
1123
 
                    frame->size = strm->bytes_per_frame;
1124
 
                    frame->timestamp.u64 = wmme_strm->timestamp.u64;
1125
 
                    frame->bit_info = 0;
1126
 
 
1127
 
                    strm->xfrm->samples_cnt = 0;
1128
 
                    strm->xfrm->subframe_cnt = 0;
1129
 
                    pjmedia_frame_ext_append_subframe(
1130
 
                        strm->xfrm, buffer,
1131
 
                        strm->bytes_per_frame *8,
1132
 
                        strm->param.samples_per_frame
1133
 
                    );
1134
 
                }
1135
 
 
1136
 
                /* Re-add the buffer to the device. */
1137
 
                mr = waveInAddBuffer(wmme_strm->hWave.In, 
1138
 
                                     &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]), 
1139
 
                                     sizeof(WAVEHDR));
1140
 
                if (mr != MMSYSERR_NOERROR) {
1141
 
                    status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
1142
 
                    break;
1143
 
                }
1144
 
 
1145
 
 
1146
 
                /* Call callback */
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)
1150
 
                    break;
1151
 
 
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;
1157
 
            } /* for */
1158
 
        }
1159
 
    }
1160
 
 
1161
 
    PJ_LOG(5,(THIS_FILE, "WMME: thread stopping.."));
1162
 
    return 0;
1163
 
}
1164
 
 
1165
 
 
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,
1171
 
                                         void *user_data,
1172
 
                                         pjmedia_aud_stream **p_aud_strm)
1173
 
{
1174
 
    struct wmme_factory *wf = (struct wmme_factory*)f;
1175
 
    pj_pool_t *pool;
1176
 
    struct wmme_stream *strm;
1177
 
    pj_uint8_t silence_char;
1178
 
    pj_status_t status;
1179
 
 
1180
 
    switch (param->ext_fmt.id) {
1181
 
    case PJMEDIA_FORMAT_L16:
1182
 
        silence_char = '\0';
1183
 
        break;
1184
 
    case PJMEDIA_FORMAT_ALAW:
1185
 
        silence_char = (pj_uint8_t)'\xd5';
1186
 
        break;
1187
 
    case PJMEDIA_FORMAT_ULAW:
1188
 
        silence_char = (pj_uint8_t)'\xff';
1189
 
        break;
1190
 
    default:
1191
 
        return PJMEDIA_EAUD_BADFORMAT;
1192
 
    }
1193
 
 
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);
1197
 
 
1198
 
    strm = PJ_POOL_ZALLOC_T(pool, struct wmme_stream);
1199
 
    pj_memcpy(&strm->param, param, sizeof(*param));
1200
 
    strm->pool = pool;
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;
1206
 
 
1207
 
    /* Create player stream */
1208
 
    if (param->dir & PJMEDIA_DIR_PLAYBACK) {
1209
 
        unsigned buf_count;
1210
 
 
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;
1214
 
        }
1215
 
 
1216
 
        buf_count = strm->param.output_latency_ms * param->clock_rate * 
1217
 
                    param->channel_count / param->samples_per_frame / 1000;
1218
 
 
1219
 
        status = init_player_stream(wf, strm->pool,
1220
 
                                    strm,
1221
 
                                    &strm->play_strm,
1222
 
                                    param,
1223
 
                                    buf_count);
1224
 
 
1225
 
        if (status != PJ_SUCCESS) {
1226
 
            stream_destroy(&strm->base);
1227
 
            return status;
1228
 
        }
1229
 
    }
1230
 
 
1231
 
    /* Create capture stream */
1232
 
    if (param->dir & PJMEDIA_DIR_CAPTURE) {
1233
 
        unsigned buf_count;
1234
 
 
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;
1238
 
        }
1239
 
 
1240
 
        buf_count = strm->param.input_latency_ms * param->clock_rate * 
1241
 
                    param->channel_count / param->samples_per_frame / 1000;
1242
 
 
1243
 
        status = init_capture_stream(wf, strm->pool,
1244
 
                                     strm,
1245
 
                                     &strm->rec_strm,
1246
 
                                     param,
1247
 
                                     buf_count);
1248
 
 
1249
 
        if (status != PJ_SUCCESS) {
1250
 
            stream_destroy(&strm->base);
1251
 
            return status;
1252
 
        }
1253
 
    }
1254
 
 
1255
 
    strm->buffer = pj_pool_alloc(pool, strm->bytes_per_frame);
1256
 
    if (!strm->buffer) {
1257
 
        pj_pool_release(pool);
1258
 
        return PJ_ENOMEM;
1259
 
    }
1260
 
 
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);
1268
 
    }
1269
 
 
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);
1275
 
        return status;
1276
 
    }
1277
 
 
1278
 
    /* Create and start the thread */
1279
 
    status = pj_thread_create(pool, "wmme", &wmme_dev_thread, strm, 0, 0, 
1280
 
                              &strm->thread);
1281
 
    if (status != PJ_SUCCESS) {
1282
 
        stream_destroy(&strm->base);
1283
 
        return status;
1284
 
    }
1285
 
 
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
 
                       &param->output_vol);
1290
 
    }
1291
 
 
1292
 
 
1293
 
    /* Done */
1294
 
    strm->base.op = &stream_op;
1295
 
    *p_aud_strm = &strm->base;
1296
 
 
1297
 
    return PJ_SUCCESS;
1298
 
}
1299
 
 
1300
 
/* API: Get stream info. */
1301
 
static pj_status_t stream_get_param(pjmedia_aud_stream *s,
1302
 
                                    pjmedia_aud_param *pi)
1303
 
{
1304
 
    struct wmme_stream *strm = (struct wmme_stream*)s;
1305
 
 
1306
 
    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1307
 
 
1308
 
    pj_memcpy(pi, &strm->param, sizeof(*pi));
1309
 
    
1310
 
    /* Update the volume setting */
1311
 
    if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1312
 
                      &pi->output_vol) == PJ_SUCCESS)
1313
 
    {
1314
 
        pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
1315
 
    }
1316
 
 
1317
 
    return PJ_SUCCESS;
1318
 
}
1319
 
 
1320
 
/* API: get capability */
1321
 
static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
1322
 
                                  pjmedia_aud_dev_cap cap,
1323
 
                                  void *pval)
1324
 
{
1325
 
    struct wmme_stream *strm = (struct wmme_stream*)s;
1326
 
 
1327
 
    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1328
 
 
1329
 
    if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && 
1330
 
        (strm->param.dir & PJMEDIA_DIR_CAPTURE)) 
1331
 
    {
1332
 
        /* Recording latency */
1333
 
        *(unsigned*)pval = strm->param.input_latency_ms;
1334
 
        return PJ_SUCCESS;
1335
 
    } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY  && 
1336
 
               (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
1337
 
    {
1338
 
        /* Playback latency */
1339
 
        *(unsigned*)pval = strm->param.output_latency_ms;
1340
 
        return PJ_SUCCESS;
1341
 
    } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
1342
 
               strm->play_strm.hWave.Out)
1343
 
    {
1344
 
        /* Output volume setting */
1345
 
        DWORD waveVol;
1346
 
        MMRESULT mr;
1347
 
 
1348
 
        mr = waveOutGetVolume(strm->play_strm.hWave.Out, &waveVol);
1349
 
        if (mr != MMSYSERR_NOERROR) {
1350
 
            return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
1351
 
        }
1352
 
 
1353
 
        waveVol &= 0xFFFF;
1354
 
        *(unsigned*)pval = (waveVol * 100) / 0xFFFF;
1355
 
        return PJ_SUCCESS;
1356
 
    } else {
1357
 
        return PJMEDIA_EAUD_INVCAP;
1358
 
    }
1359
 
}
1360
 
 
1361
 
/* API: set capability */
1362
 
static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
1363
 
                                  pjmedia_aud_dev_cap cap,
1364
 
                                  const void *pval)
1365
 
{
1366
 
    struct wmme_stream *strm = (struct wmme_stream*)s;
1367
 
 
1368
 
    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1369
 
 
1370
 
    if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
1371
 
        strm->play_strm.hWave.Out)
1372
 
    {
1373
 
        /* Output volume setting */
1374
 
        unsigned vol = *(unsigned*)pval;
1375
 
        DWORD waveVol;
1376
 
        MMRESULT mr;
1377
 
        pj_status_t status;
1378
 
 
1379
 
        if (vol > 100)
1380
 
            vol = 100;
1381
 
 
1382
 
        waveVol = (vol * 0xFFFF) / 100;
1383
 
        waveVol |= (waveVol << 16);
1384
 
 
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;
1390
 
        }
1391
 
        return status;
1392
 
    }
1393
 
 
1394
 
    return PJMEDIA_EAUD_INVCAP;
1395
 
}
1396
 
 
1397
 
/* API: Start stream. */
1398
 
static pj_status_t stream_start(pjmedia_aud_stream *strm)
1399
 
{
1400
 
    struct wmme_stream *stream = (struct wmme_stream*)strm;
1401
 
    MMRESULT mr;
1402
 
 
1403
 
    if (stream->play_strm.hWave.Out != NULL)
1404
 
    {
1405
 
        mr = waveOutRestart(stream->play_strm.hWave.Out);
1406
 
        if (mr != MMSYSERR_NOERROR) {
1407
 
            return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
1408
 
        }
1409
 
        PJ_LOG(4,(THIS_FILE, "WMME playback stream started"));
1410
 
    }
1411
 
 
1412
 
    if (stream->rec_strm.hWave.In != NULL)
1413
 
    {
1414
 
        mr = waveInStart(stream->rec_strm.hWave.In);
1415
 
        if (mr != MMSYSERR_NOERROR) {
1416
 
            return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
1417
 
        }
1418
 
        PJ_LOG(4,(THIS_FILE, "WMME capture stream started"));
1419
 
    }
1420
 
 
1421
 
    return PJ_SUCCESS;
1422
 
}
1423
 
 
1424
 
/* API: Stop stream. */
1425
 
static pj_status_t stream_stop(pjmedia_aud_stream *strm)
1426
 
{
1427
 
    struct wmme_stream *stream = (struct wmme_stream*)strm;
1428
 
    MMRESULT mr;
1429
 
 
1430
 
    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
1431
 
 
1432
 
    if (stream->play_strm.hWave.Out != NULL)
1433
 
    {
1434
 
        mr = waveOutPause(stream->play_strm.hWave.Out);
1435
 
        if (mr != MMSYSERR_NOERROR) {
1436
 
            return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
1437
 
        }
1438
 
        PJ_LOG(4,(THIS_FILE, "Stopped WMME playback stream"));
1439
 
    }
1440
 
 
1441
 
    if (stream->rec_strm.hWave.In != NULL)
1442
 
    {
1443
 
        mr = waveInStop(stream->rec_strm.hWave.In);
1444
 
        if (mr != MMSYSERR_NOERROR) {
1445
 
            return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
1446
 
        }
1447
 
        PJ_LOG(4,(THIS_FILE, "Stopped WMME capture stream"));
1448
 
    }
1449
 
 
1450
 
    return PJ_SUCCESS;
1451
 
}
1452
 
 
1453
 
 
1454
 
/* API: Destroy stream. */
1455
 
static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
1456
 
{
1457
 
    struct wmme_stream *stream = (struct wmme_stream*)strm;
1458
 
    unsigned i;
1459
 
 
1460
 
    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
1461
 
 
1462
 
    stream_stop(strm);
1463
 
 
1464
 
    /* Stop the stream thread */
1465
 
    if (stream->thread)
1466
 
    {
1467
 
        SetEvent(stream->thread_quit_event);
1468
 
        pj_thread_join(stream->thread);
1469
 
        pj_thread_destroy(stream->thread);
1470
 
        stream->thread = NULL;
1471
 
    }
1472
 
 
1473
 
    /* Close the thread quit event */
1474
 
    if (stream->thread_quit_event)
1475
 
    {
1476
 
        CloseHandle(stream->thread_quit_event);
1477
 
        stream->thread_quit_event = NULL;
1478
 
    }
1479
 
 
1480
 
    /* Unprepare the headers and close the play device */
1481
 
    if (stream->play_strm.hWave.Out)
1482
 
    {
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]),
1487
 
                                   sizeof(WAVEHDR));
1488
 
        waveOutClose(stream->play_strm.hWave.Out);
1489
 
        stream->play_strm.hWave.Out = NULL;
1490
 
    }
1491
 
 
1492
 
    /* Close the play event */
1493
 
    if (stream->play_strm.hEvent)
1494
 
    {
1495
 
        CloseHandle(stream->play_strm.hEvent);
1496
 
        stream->play_strm.hEvent = NULL;
1497
 
    }
1498
 
 
1499
 
    /* Unprepare the headers and close the record device */
1500
 
    if (stream->rec_strm.hWave.In)
1501
 
    {
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]),
1506
 
                                  sizeof(WAVEHDR));
1507
 
        waveInClose(stream->rec_strm.hWave.In);
1508
 
        stream->rec_strm.hWave.In = NULL;
1509
 
    }
1510
 
 
1511
 
    /* Close the record event */
1512
 
    if (stream->rec_strm.hEvent)
1513
 
    {
1514
 
        CloseHandle(stream->rec_strm.hEvent);
1515
 
        stream->rec_strm.hEvent = NULL;
1516
 
    }
1517
 
 
1518
 
    pj_pool_release(stream->pool);
1519
 
 
1520
 
    return PJ_SUCCESS;
1521
 
}
1522
 
 
1523
 
#endif  /* PJMEDIA_AUDIO_DEV_HAS_WMME */
1524