blob: 436c8507f6203ebe725da87d69814e93651f3518 [file] [log] [blame]
Benny Prijono598b01d2009-02-18 13:55:03 +00001/* $Id$ */
2/*
3 * Copyright (C) 2008-2009 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>
Benny Prijono598b01d2009-02-18 13:55:03 +000021#include <pj/assert.h>
22#include <pj/log.h>
23#include <pj/os.h>
24#include <pj/string.h>
25#include <pj/unicode.h>
Benny Prijono8eeab0b2009-03-04 19:00:28 +000026
27#if PJMEDIA_AUDIO_DEV_HAS_WMME
28
Benny Prijono598b01d2009-02-18 13:55:03 +000029#ifdef _MSC_VER
30# pragma warning(push, 3)
31#endif
32
33#include <windows.h>
34#include <mmsystem.h>
Benny Prijono2058f472009-02-22 17:15:34 +000035#include <mmreg.h>
Benny Prijono598b01d2009-02-18 13:55:03 +000036
37#ifdef _MSC_VER
38# pragma warning(pop)
39#endif
40
41#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0
42# pragma comment(lib, "Coredll.lib")
43#elif defined(_MSC_VER)
44# pragma comment(lib, "winmm.lib")
45#endif
46
47
48#define THIS_FILE "wmme_dev.c"
Benny Prijono598b01d2009-02-18 13:55:03 +000049
Benny Prijono598b01d2009-02-18 13:55:03 +000050/* WMME device info */
51struct wmme_dev_info
52{
53 pjmedia_aud_dev_info info;
54 unsigned deviceId;
55};
56
57/* WMME factory */
58struct wmme_factory
59{
60 pjmedia_aud_dev_factory base;
61 pj_pool_t *pool;
62 pj_pool_factory *pf;
63
64 unsigned dev_count;
65 struct wmme_dev_info *dev_info;
66};
67
68
69/* Individual WMME capture/playback stream descriptor */
70struct wmme_channel
71{
72 union
73 {
74 HWAVEIN In;
75 HWAVEOUT Out;
76 } hWave;
77
78 WAVEHDR *WaveHdr;
79 HANDLE hEvent;
80 DWORD dwBufIdx;
81 DWORD dwMaxBufIdx;
Benny Prijono598b01d2009-02-18 13:55:03 +000082 pj_timestamp timestamp;
83};
84
85
86/* Sound stream. */
87struct wmme_stream
88{
Benny Prijono7a380002009-03-09 12:55:29 +000089 pjmedia_aud_stream base; /**< Base stream */
90 pjmedia_aud_param param; /**< Settings */
Benny Prijono598b01d2009-02-18 13:55:03 +000091 pj_pool_t *pool; /**< Memory pool. */
92
93 pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */
94 pjmedia_aud_play_cb play_cb; /**< Playback callback. */
95 void *user_data; /**< Application data. */
96
97 struct wmme_channel play_strm; /**< Playback stream. */
98 struct wmme_channel rec_strm; /**< Capture stream. */
99
100 void *buffer; /**< Temp. frame buffer. */
Benny Prijono5fad7be2009-02-22 21:33:20 +0000101 pjmedia_format_id fmt_id; /**< Frame format */
102 pj_uint8_t silence_char; /**< Silence pattern */
Benny Prijono2058f472009-02-22 17:15:34 +0000103 unsigned bytes_per_frame; /**< Bytes per frame */
Benny Prijono598b01d2009-02-18 13:55:03 +0000104
Benny Prijono2058f472009-02-22 17:15:34 +0000105 pjmedia_frame_ext *xfrm; /**< Extended frame buffer */
106 unsigned xfrm_size; /**< Total ext frm size */
107
Benny Prijono598b01d2009-02-18 13:55:03 +0000108 pj_thread_t *thread; /**< Thread handle. */
109 HANDLE thread_quit_event; /**< Quit signal to thread */
110};
111
112
113/* Prototypes */
114static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
115static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
116static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
117static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
118 unsigned index,
119 pjmedia_aud_dev_info *info);
120static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
121 unsigned index,
Benny Prijono10454dc2009-02-21 14:21:59 +0000122 pjmedia_aud_param *param);
Benny Prijono598b01d2009-02-18 13:55:03 +0000123static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
Benny Prijono10454dc2009-02-21 14:21:59 +0000124 const pjmedia_aud_param *param,
Benny Prijono598b01d2009-02-18 13:55:03 +0000125 pjmedia_aud_rec_cb rec_cb,
126 pjmedia_aud_play_cb play_cb,
127 void *user_data,
128 pjmedia_aud_stream **p_aud_strm);
129
130static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
Benny Prijono10454dc2009-02-21 14:21:59 +0000131 pjmedia_aud_param *param);
Benny Prijono598b01d2009-02-18 13:55:03 +0000132static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
133 pjmedia_aud_dev_cap cap,
134 void *value);
135static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
136 pjmedia_aud_dev_cap cap,
137 const void *value);
138static pj_status_t stream_start(pjmedia_aud_stream *strm);
139static pj_status_t stream_stop(pjmedia_aud_stream *strm);
140static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
141
142
143/* Operations */
144static pjmedia_aud_dev_factory_op factory_op =
145{
146 &factory_init,
147 &factory_destroy,
148 &factory_get_dev_count,
149 &factory_get_dev_info,
150 &factory_default_param,
151 &factory_create_stream
152};
153
154static pjmedia_aud_stream_op stream_op =
155{
156 &stream_get_param,
157 &stream_get_cap,
158 &stream_set_cap,
159 &stream_start,
160 &stream_stop,
161 &stream_destroy
162};
163
Benny Prijono598b01d2009-02-18 13:55:03 +0000164
165/****************************************************************************
166 * Factory operations
167 */
168/*
169 * Init WMME audio driver.
170 */
171pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf)
172{
173 struct wmme_factory *f;
174 pj_pool_t *pool;
175
176 pool = pj_pool_create(pf, "WMME", 1000, 1000, NULL);
177 f = PJ_POOL_ZALLOC_T(pool, struct wmme_factory);
178 f->pf = pf;
179 f->pool = pool;
180 f->base.op = &factory_op;
181
182 return &f->base;
183}
184
185
186/* Internal: build device info from WAVEINCAPS/WAVEOUTCAPS */
187static void build_dev_info(UINT deviceId, struct wmme_dev_info *wdi,
Benny Prijono2058f472009-02-22 17:15:34 +0000188 const WAVEINCAPS *wic, const WAVEOUTCAPS *woc)
Benny Prijono598b01d2009-02-18 13:55:03 +0000189{
190#define WIC_WOC(wic,woc,field) (wic? wic->field : woc->field)
191
192 pj_bzero(wdi, sizeof(*wdi));
193 wdi->deviceId = deviceId;
194
195 /* Device Name */
196 if (deviceId==WAVE_MAPPER) {
197 strncpy(wdi->info.name, "Wave mapper", sizeof(wdi->info.name));
198 wdi->info.name[sizeof(wdi->info.name)-1] = '\0';
199 } else {
Benny Prijono2058f472009-02-22 17:15:34 +0000200 const pj_char_t *szPname = WIC_WOC(wic, woc, szPname);
Benny Prijono598b01d2009-02-18 13:55:03 +0000201 PJ_DECL_ANSI_TEMP_BUF(wTmp, sizeof(wdi->info.name));
202
203 strncpy(wdi->info.name,
204 PJ_NATIVE_TO_STRING(szPname, wTmp, PJ_ARRAY_SIZE(wTmp)),
205 sizeof(wdi->info.name));
206 wdi->info.name[sizeof(wdi->info.name)-1] = '\0';
207 }
208
209 wdi->info.default_samples_per_sec = 16000;
210 strcpy(wdi->info.driver, "WMME");
211
212 if (wic) {
213 wdi->info.input_count = wic->wChannels;
214 wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
215
216 /* Sometimes a device can return a rediculously large number of
217 * channels. This happened with an SBLive card on a Windows ME box.
218 * It also happens on Win XP!
219 */
220 if (wdi->info.input_count<1 || wdi->info.input_count>256) {
221 wdi->info.input_count = 2;
222 }
223 }
224
225 if (woc) {
226 wdi->info.output_count = woc->wChannels;
227 wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
228
229 if (woc->dwSupport & WAVECAPS_VOLUME) {
230 wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
231 }
232
233 /* Sometimes a device can return a rediculously large number of
234 * channels. This happened with an SBLive card on a Windows ME box.
235 * It also happens on Win XP!
236 */
237 if (wdi->info.output_count<1 || wdi->info.output_count>256) {
238 wdi->info.output_count = 2;
239 }
240 }
Benny Prijono2058f472009-02-22 17:15:34 +0000241
242 /* Extended formats */
243 wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
244 wdi->info.ext_fmt_cnt = 2;
245 wdi->info.ext_fmt[0].id = PJMEDIA_FORMAT_PCMU;
246 wdi->info.ext_fmt[0].bitrate = 64000;
247 wdi->info.ext_fmt[0].vad = 0;
248 wdi->info.ext_fmt[1].id = PJMEDIA_FORMAT_PCMA;
249 wdi->info.ext_fmt[1].bitrate = 64000;
250 wdi->info.ext_fmt[1].vad = 0;
Benny Prijono598b01d2009-02-18 13:55:03 +0000251}
252
253/* API: init factory */
254static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
255{
256 struct wmme_factory *wf = (struct wmme_factory*)f;
257 unsigned c;
258 int i;
259 int inputDeviceCount, outputDeviceCount, devCount=0;
260 pj_bool_t waveMapperAdded = PJ_FALSE;
261
262 /* Enumerate sound devices */
263 wf->dev_count = 0;
264
265 inputDeviceCount = waveInGetNumDevs();
266 devCount += inputDeviceCount;
267
268 outputDeviceCount = waveOutGetNumDevs();
269 devCount += outputDeviceCount;
270
271 if (devCount) {
272 /* Assume there is WAVE_MAPPER */
273 devCount += 2;
274 }
275
276 if (devCount==0) {
277 PJ_LOG(4,(THIS_FILE, "WMME found no sound devices"));
278 return PJMEDIA_EAUD_NODEV;
279 }
280
281 wf->dev_info = (struct wmme_dev_info*)
282 pj_pool_calloc(wf->pool, devCount,
283 sizeof(struct wmme_dev_info));
284
Benny Prijono2058f472009-02-22 17:15:34 +0000285 if (inputDeviceCount && outputDeviceCount) {
Benny Prijono598b01d2009-02-18 13:55:03 +0000286 /* Attempt to add WAVE_MAPPER as input and output device */
287 WAVEINCAPS wic;
288 MMRESULT mr;
289
290 pj_bzero(&wic, sizeof(WAVEINCAPS));
291 mr = waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(WAVEINCAPS));
292
293 if (mr == MMSYSERR_NOERROR) {
294 WAVEOUTCAPS woc;
295
296 pj_bzero(&woc, sizeof(WAVEOUTCAPS));
297 mr = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS));
298 if (mr == MMSYSERR_NOERROR) {
299 build_dev_info(WAVE_MAPPER, &wf->dev_info[wf->dev_count],
300 &wic, &woc);
301 ++wf->dev_count;
302 waveMapperAdded = PJ_TRUE;
303 }
304 }
305
306 }
307
308 if (inputDeviceCount > 0) {
309 /* -1 is the WAVE_MAPPER */
310 for (i = (waveMapperAdded? 0 : -1); i < inputDeviceCount; ++i) {
311 UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
312 WAVEINCAPS wic;
313 MMRESULT mr;
314
315 pj_bzero(&wic, sizeof(WAVEINCAPS));
316
317 mr = waveInGetDevCaps(uDeviceID, &wic, sizeof(WAVEINCAPS));
318
319 if (mr == MMSYSERR_NOMEM)
320 return PJ_ENOMEM;
321
322 if (mr != MMSYSERR_NOERROR)
323 continue;
324
325 build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count],
326 &wic, NULL);
327 ++wf->dev_count;
328 }
329 }
330
331 if( outputDeviceCount > 0 )
332 {
333 /* -1 is the WAVE_MAPPER */
334 for (i = (waveMapperAdded? 0 : -1); i < outputDeviceCount; ++i) {
335 UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
336 WAVEOUTCAPS woc;
337 MMRESULT mr;
338
339 pj_bzero(&woc, sizeof(WAVEOUTCAPS));
340
341 mr = waveOutGetDevCaps(uDeviceID, &woc, sizeof(WAVEOUTCAPS));
342
343 if (mr == MMSYSERR_NOMEM)
344 return PJ_ENOMEM;
345
346 if (mr != MMSYSERR_NOERROR)
347 continue;
348
349 build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count],
350 NULL, &woc);
351 ++wf->dev_count;
352 }
353 }
354
355 PJ_LOG(4, (THIS_FILE, "WMME initialized, found %d devices:",
356 wf->dev_count));
357 for (c = 0; c < wf->dev_count; ++c) {
358 PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d)",
359 c,
360 wf->dev_info[c].info.name,
361 wf->dev_info[c].info.input_count,
362 wf->dev_info[c].info.output_count));
363 }
364
365 return PJ_SUCCESS;
366}
367
368/* API: destroy factory */
369static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
370{
371 struct wmme_factory *wf = (struct wmme_factory*)f;
372 pj_pool_t *pool = wf->pool;
373
374 wf->pool = NULL;
375 pj_pool_release(pool);
376
377 return PJ_SUCCESS;
378}
379
380/* API: get number of devices */
381static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
382{
383 struct wmme_factory *wf = (struct wmme_factory*)f;
384 return wf->dev_count;
385}
386
387/* API: get device info */
388static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
389 unsigned index,
390 pjmedia_aud_dev_info *info)
391{
392 struct wmme_factory *wf = (struct wmme_factory*)f;
393
394 PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
395
396 pj_memcpy(info, &wf->dev_info[index].info, sizeof(*info));
397
398 return PJ_SUCCESS;
399}
400
401/* API: create default device parameter */
402static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
403 unsigned index,
Benny Prijono10454dc2009-02-21 14:21:59 +0000404 pjmedia_aud_param *param)
Benny Prijono598b01d2009-02-18 13:55:03 +0000405{
406 struct wmme_factory *wf = (struct wmme_factory*)f;
407 struct wmme_dev_info *di = &wf->dev_info[index];
408
409 PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
410
411 pj_bzero(param, sizeof(*param));
412 if (di->info.input_count && di->info.output_count) {
413 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
414 param->rec_id = index;
415 param->play_id = index;
416 } else if (di->info.input_count) {
417 param->dir = PJMEDIA_DIR_CAPTURE;
418 param->rec_id = index;
Benny Prijono96e74f32009-02-22 12:00:12 +0000419 param->play_id = PJMEDIA_AUD_INVALID_DEV;
Benny Prijono598b01d2009-02-18 13:55:03 +0000420 } else if (di->info.output_count) {
421 param->dir = PJMEDIA_DIR_PLAYBACK;
422 param->play_id = index;
Benny Prijono96e74f32009-02-22 12:00:12 +0000423 param->rec_id = PJMEDIA_AUD_INVALID_DEV;
Benny Prijono598b01d2009-02-18 13:55:03 +0000424 } else {
425 return PJMEDIA_EAUD_INVDEV;
426 }
427
428 param->clock_rate = di->info.default_samples_per_sec;
429 param->channel_count = 1;
430 param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000;
431 param->bits_per_sample = 16;
Benny Prijono7a380002009-03-09 12:55:29 +0000432 param->flags = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
433 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
Benny Prijono598b01d2009-02-18 13:55:03 +0000434 param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
435 param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
436
437 return PJ_SUCCESS;
438}
439
440/* Internal: init WAVEFORMATEX */
Benny Prijono2058f472009-02-22 17:15:34 +0000441static pj_status_t init_waveformatex(LPWAVEFORMATEX wfx,
442 const pjmedia_aud_param *prm)
Benny Prijono598b01d2009-02-18 13:55:03 +0000443{
Benny Prijono2058f472009-02-22 17:15:34 +0000444
445 pj_bzero(wfx, sizeof(PCMWAVEFORMAT));
446 if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16) {
Benny Prijono5fad7be2009-02-22 21:33:20 +0000447 enum { BYTES_PER_SAMPLE = 2 };
Benny Prijono2058f472009-02-22 17:15:34 +0000448 wfx->wFormatTag = WAVE_FORMAT_PCM;
449 wfx->nChannels = (pj_uint16_t)prm->channel_count;
450 wfx->nSamplesPerSec = prm->clock_rate;
451 wfx->nBlockAlign = (pj_uint16_t)(prm->channel_count *
452 BYTES_PER_SAMPLE);
453 wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count *
454 BYTES_PER_SAMPLE;
Benny Prijono5fad7be2009-02-22 21:33:20 +0000455 wfx->wBitsPerSample = 16;
Benny Prijono2058f472009-02-22 17:15:34 +0000456
457 return PJ_SUCCESS;
458
459 } else if ((prm->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) &&
460 (prm->ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
461 prm->ext_fmt.id == PJMEDIA_FORMAT_PCMU))
462 {
463 unsigned ptime;
464
465 ptime = prm->samples_per_frame * 1000 /
466 (prm->clock_rate * prm->channel_count);
467 wfx->wFormatTag = (pj_uint16_t)
468 ((prm->ext_fmt.id==PJMEDIA_FORMAT_PCMA) ?
469 WAVE_FORMAT_ALAW : WAVE_FORMAT_MULAW);
470 wfx->nChannels = (pj_uint16_t)prm->channel_count;
471 wfx->nSamplesPerSec = prm->clock_rate;
472 wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count;
473 wfx->nBlockAlign = (pj_uint16_t)(wfx->nAvgBytesPerSec * ptime /
474 1000);
475 wfx->wBitsPerSample = 8;
476 wfx->cbSize = 0;
477
478 return PJ_SUCCESS;
479
480 } else {
481
482 return PJMEDIA_EAUD_BADFORMAT;
483
484 }
Benny Prijono598b01d2009-02-18 13:55:03 +0000485}
486
Benny Prijono5fad7be2009-02-22 21:33:20 +0000487/* Get format name */
488static const char *get_fmt_name(pj_uint32_t id)
489{
490 static char name[8];
491
492 if (id == PJMEDIA_FORMAT_L16)
Benny Prijono7a380002009-03-09 12:55:29 +0000493 return "PCM";
Benny Prijono5fad7be2009-02-22 21:33:20 +0000494 pj_memcpy(name, &id, 4);
495 name[4] = '\0';
496 return name;
497}
Benny Prijono598b01d2009-02-18 13:55:03 +0000498
499/* Internal: create WMME player device. */
500static pj_status_t init_player_stream( struct wmme_factory *wf,
501 pj_pool_t *pool,
Benny Prijono2058f472009-02-22 17:15:34 +0000502 struct wmme_stream *parent,
Benny Prijono598b01d2009-02-18 13:55:03 +0000503 struct wmme_channel *wmme_strm,
Benny Prijono2058f472009-02-22 17:15:34 +0000504 const pjmedia_aud_param *prm,
Benny Prijono598b01d2009-02-18 13:55:03 +0000505 unsigned buffer_count)
506{
507 MMRESULT mr;
Benny Prijono2058f472009-02-22 17:15:34 +0000508 WAVEFORMATEX wfx;
509 unsigned i, ptime;
510 pj_status_t status;
Benny Prijono598b01d2009-02-18 13:55:03 +0000511
Benny Prijono2058f472009-02-22 17:15:34 +0000512 PJ_ASSERT_RETURN(prm->play_id < (int)wf->dev_count, PJ_EINVAL);
Benny Prijono598b01d2009-02-18 13:55:03 +0000513
514 /*
515 * Create a wait event.
516 */
517 wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
518 if (NULL == wmme_strm->hEvent)
519 return pj_get_os_error();
520
521 /*
522 * Set up wave format structure for opening the device.
523 */
Benny Prijono2058f472009-02-22 17:15:34 +0000524 status = init_waveformatex(&wfx, prm);
525 if (status != PJ_SUCCESS)
526 return status;
527
528 ptime = prm->samples_per_frame * 1000 /
529 (prm->clock_rate * prm->channel_count);
Benny Prijono5fad7be2009-02-22 21:33:20 +0000530 parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000;
Benny Prijono598b01d2009-02-18 13:55:03 +0000531
532 /*
533 * Open wave device.
534 */
Benny Prijono2058f472009-02-22 17:15:34 +0000535 mr = waveOutOpen(&wmme_strm->hWave.Out,
536 wf->dev_info[prm->play_id].deviceId,
537 &wfx, (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
Benny Prijono598b01d2009-02-18 13:55:03 +0000538 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000539 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +0000540 }
541
542 /* Pause the wave out device */
543 mr = waveOutPause(wmme_strm->hWave.Out);
544 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000545 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +0000546 }
547
548 /*
549 * Create the buffers.
550 */
551 wmme_strm->WaveHdr = (WAVEHDR*)
552 pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
553 for (i = 0; i < buffer_count; ++i) {
Benny Prijono2058f472009-02-22 17:15:34 +0000554 wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool,
555 parent->bytes_per_frame);
556 wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame;
Benny Prijono598b01d2009-02-18 13:55:03 +0000557 mr = waveOutPrepareHeader(wmme_strm->hWave.Out,
558 &(wmme_strm->WaveHdr[i]),
559 sizeof(WAVEHDR));
560 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000561 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +0000562 }
563 mr = waveOutWrite(wmme_strm->hWave.Out, &(wmme_strm->WaveHdr[i]),
564 sizeof(WAVEHDR));
565 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000566 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +0000567 }
568 }
569
570 wmme_strm->dwBufIdx = 0;
571 wmme_strm->dwMaxBufIdx = buffer_count;
572 wmme_strm->timestamp.u64 = 0;
573
574 /* Done setting up play device. */
Benny Prijono2e988792009-02-23 10:21:33 +0000575 PJ_LOG(4, (THIS_FILE,
Benny Prijono5fad7be2009-02-22 21:33:20 +0000576 " WaveAPI Sound player \"%s\" initialized ("
577 "format=%s, clock_rate=%d, "
Benny Prijono598b01d2009-02-18 13:55:03 +0000578 "channel_count=%d, samples_per_frame=%d (%dms))",
Benny Prijono2058f472009-02-22 17:15:34 +0000579 wf->dev_info[prm->play_id].info.name,
Benny Prijono5fad7be2009-02-22 21:33:20 +0000580 get_fmt_name(prm->ext_fmt.id),
Benny Prijono2058f472009-02-22 17:15:34 +0000581 prm->clock_rate, prm->channel_count, prm->samples_per_frame,
582 prm->samples_per_frame * 1000 / prm->clock_rate));
Benny Prijono598b01d2009-02-18 13:55:03 +0000583
584 return PJ_SUCCESS;
585}
586
587
588/* Internal: create Windows Multimedia recorder device */
589static pj_status_t init_capture_stream( struct wmme_factory *wf,
590 pj_pool_t *pool,
Benny Prijono2058f472009-02-22 17:15:34 +0000591 struct wmme_stream *parent,
Benny Prijono598b01d2009-02-18 13:55:03 +0000592 struct wmme_channel *wmme_strm,
Benny Prijono2058f472009-02-22 17:15:34 +0000593 const pjmedia_aud_param *prm,
Benny Prijono598b01d2009-02-18 13:55:03 +0000594 unsigned buffer_count)
595{
596 MMRESULT mr;
Benny Prijono2058f472009-02-22 17:15:34 +0000597 WAVEFORMATEX wfx;
598 unsigned i, ptime;
Benny Prijono598b01d2009-02-18 13:55:03 +0000599
Benny Prijono2058f472009-02-22 17:15:34 +0000600 PJ_ASSERT_RETURN(prm->rec_id < (int)wf->dev_count, PJ_EINVAL);
Benny Prijono598b01d2009-02-18 13:55:03 +0000601
602 /*
603 * Create a wait event.
604 */
605 wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
606 if (NULL == wmme_strm->hEvent) {
607 return pj_get_os_error();
608 }
609
610 /*
611 * Set up wave format structure for opening the device.
612 */
Benny Prijono2058f472009-02-22 17:15:34 +0000613 init_waveformatex(&wfx, prm);
614 ptime = prm->samples_per_frame * 1000 /
615 (prm->clock_rate * prm->channel_count);
Benny Prijono5fad7be2009-02-22 21:33:20 +0000616 parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000;
Benny Prijono598b01d2009-02-18 13:55:03 +0000617
618 /*
619 * Open wave device.
620 */
Benny Prijono2058f472009-02-22 17:15:34 +0000621 mr = waveInOpen(&wmme_strm->hWave.In,
622 wf->dev_info[prm->rec_id].deviceId,
623 &wfx, (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
Benny Prijono598b01d2009-02-18 13:55:03 +0000624 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000625 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +0000626 }
627
628 /*
629 * Create the buffers.
630 */
631 wmme_strm->WaveHdr = (WAVEHDR*)
632 pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
633 for (i = 0; i < buffer_count; ++i) {
Benny Prijono2058f472009-02-22 17:15:34 +0000634 wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool,
635 parent->bytes_per_frame);
636 wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame;
Benny Prijono598b01d2009-02-18 13:55:03 +0000637 mr = waveInPrepareHeader(wmme_strm->hWave.In,
638 &(wmme_strm->WaveHdr[i]),
639 sizeof(WAVEHDR));
640 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000641 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +0000642 }
643 mr = waveInAddBuffer(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]),
644 sizeof(WAVEHDR));
645 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000646 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +0000647 }
648 }
649
650 wmme_strm->dwBufIdx = 0;
651 wmme_strm->dwMaxBufIdx = buffer_count;
652 wmme_strm->timestamp.u64 = 0;
653
654 /* Done setting up play device. */
Benny Prijono2e988792009-02-23 10:21:33 +0000655 PJ_LOG(4,(THIS_FILE,
Benny Prijono5fad7be2009-02-22 21:33:20 +0000656 " WaveAPI Sound recorder \"%s\" initialized "
657 "(format=%s, clock_rate=%d, "
Benny Prijono598b01d2009-02-18 13:55:03 +0000658 "channel_count=%d, samples_per_frame=%d (%dms))",
Benny Prijono2058f472009-02-22 17:15:34 +0000659 wf->dev_info[prm->rec_id].info.name,
Benny Prijono5fad7be2009-02-22 21:33:20 +0000660 get_fmt_name(prm->ext_fmt.id),
Benny Prijono2058f472009-02-22 17:15:34 +0000661 prm->clock_rate, prm->channel_count, prm->samples_per_frame,
662 prm->samples_per_frame * 1000 / prm->clock_rate));
Benny Prijono598b01d2009-02-18 13:55:03 +0000663
664 return PJ_SUCCESS;
665}
666
667
668/* WMME capture and playback thread. */
669static int PJ_THREAD_FUNC wmme_dev_thread(void *arg)
670{
671 struct wmme_stream *strm = (struct wmme_stream*)arg;
672 HANDLE events[3];
673 unsigned eventCount;
Benny Prijono598b01d2009-02-18 13:55:03 +0000674 pj_status_t status = PJ_SUCCESS;
Benny Prijono5fad7be2009-02-22 21:33:20 +0000675 static unsigned rec_cnt, play_cnt;
676
677 rec_cnt = play_cnt = 0;
Benny Prijono598b01d2009-02-18 13:55:03 +0000678
Benny Prijono598b01d2009-02-18 13:55:03 +0000679 eventCount = 0;
680 events[eventCount++] = strm->thread_quit_event;
Benny Prijono7a380002009-03-09 12:55:29 +0000681 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK)
Benny Prijono598b01d2009-02-18 13:55:03 +0000682 events[eventCount++] = strm->play_strm.hEvent;
Benny Prijono7a380002009-03-09 12:55:29 +0000683 if (strm->param.dir & PJMEDIA_DIR_CAPTURE)
Benny Prijono598b01d2009-02-18 13:55:03 +0000684 events[eventCount++] = strm->rec_strm.hEvent;
685
686
687 /* Raise self priority. We don't want the audio to be distorted by
688 * system activity.
689 */
690#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0
Benny Prijono7a380002009-03-09 12:55:29 +0000691 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK)
Benny Prijono598b01d2009-02-18 13:55:03 +0000692 CeSetThreadPriority(GetCurrentThread(), 153);
693 else
694 CeSetThreadPriority(GetCurrentThread(), 247);
695#else
696 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
697#endif
698
Benny Prijono598b01d2009-02-18 13:55:03 +0000699 /*
700 * Loop while not signalled to quit, wait for event objects to be
701 * signalled by WMME capture and play buffer.
702 */
703 while (status == PJ_SUCCESS)
704 {
705
706 DWORD rc;
707 pjmedia_dir signalled_dir;
708
Benny Prijono5fad7be2009-02-22 21:33:20 +0000709 /* Swap */
710 if (eventCount==3) {
711 HANDLE hTemp = events[2];
712 events[2] = events[1];
713 events[1] = hTemp;
714 }
715
Benny Prijono598b01d2009-02-18 13:55:03 +0000716 rc = WaitForMultipleObjects(eventCount, events, FALSE, INFINITE);
717 if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount)
718 continue;
719
720 if (rc == WAIT_OBJECT_0)
721 break;
722
723 if (rc == (WAIT_OBJECT_0 + 1))
724 {
725 if (events[1] == strm->play_strm.hEvent)
726 signalled_dir = PJMEDIA_DIR_PLAYBACK;
727 else
728 signalled_dir = PJMEDIA_DIR_CAPTURE;
729 }
730 else
731 {
732 if (events[2] == strm->play_strm.hEvent)
733 signalled_dir = PJMEDIA_DIR_PLAYBACK;
734 else
735 signalled_dir = PJMEDIA_DIR_CAPTURE;
736 }
737
738
739 if (signalled_dir == PJMEDIA_DIR_PLAYBACK)
740 {
741 struct wmme_channel *wmme_strm = &strm->play_strm;
Benny Prijono2058f472009-02-22 17:15:34 +0000742
Benny Prijono598b01d2009-02-18 13:55:03 +0000743 status = PJ_SUCCESS;
744
745 /*
746 * Windows Multimedia has requested us to feed some frames to
747 * playback buffer.
748 */
749
750 while (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE)
751 {
752 void *buffer = wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
Benny Prijono2058f472009-02-22 17:15:34 +0000753 pjmedia_frame pcm_frame, *frame;
Benny Prijono5fad7be2009-02-22 21:33:20 +0000754 MMRESULT mr = MMSYSERR_NOERROR;
Benny Prijono598b01d2009-02-18 13:55:03 +0000755
756 //PJ_LOG(5,(THIS_FILE, "Finished writing buffer %d",
757 // wmme_strm->dwBufIdx));
758
Benny Prijono5fad7be2009-02-22 21:33:20 +0000759 if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
Benny Prijono2058f472009-02-22 17:15:34 +0000760 /* PCM mode */
761 frame = &pcm_frame;
762
763 frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
764 frame->size = strm->bytes_per_frame;
765 frame->buf = buffer;
766 frame->timestamp.u64 = wmme_strm->timestamp.u64;
767 frame->bit_info = 0;
768 } else {
769 /* Codec mode */
770 frame = &strm->xfrm->base;
771
772 strm->xfrm->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
773 strm->xfrm->base.size = strm->bytes_per_frame;
774 strm->xfrm->base.buf = NULL;
775 strm->xfrm->base.timestamp.u64 = wmme_strm->timestamp.u64;
776 strm->xfrm->base.bit_info = 0;
777 }
Benny Prijono598b01d2009-02-18 13:55:03 +0000778
779 /* Get frame from application. */
Benny Prijono2e988792009-02-23 10:21:33 +0000780 //PJ_LOG(5,(THIS_FILE, "xxx %u play_cb", play_cnt++));
Benny Prijono2058f472009-02-22 17:15:34 +0000781 status = (*strm->play_cb)(strm->user_data, frame);
Benny Prijono598b01d2009-02-18 13:55:03 +0000782
783 if (status != PJ_SUCCESS)
784 break;
785
Benny Prijono5fad7be2009-02-22 21:33:20 +0000786 if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
Benny Prijono2058f472009-02-22 17:15:34 +0000787 /* PCM mode */
788 if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
789 pj_bzero(buffer, strm->bytes_per_frame);
Benny Prijono2058f472009-02-22 17:15:34 +0000790 } else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
791 pj_assert(!"Frame type not supported");
792 } else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
Benny Prijono5fad7be2009-02-22 21:33:20 +0000793 /* Nothing to do */
Benny Prijono2058f472009-02-22 17:15:34 +0000794 } else {
795 pj_assert(!"Frame type not supported");
796 }
797 } else {
798 /* Codec mode */
799 if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
Benny Prijono5fad7be2009-02-22 21:33:20 +0000800 pj_memset(buffer, strm->silence_char,
801 strm->bytes_per_frame);
Benny Prijono2058f472009-02-22 17:15:34 +0000802 } else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
803 unsigned sz;
804 sz = pjmedia_frame_ext_copy_payload(strm->xfrm,
805 buffer,
806 strm->bytes_per_frame);
Benny Prijono5fad7be2009-02-22 21:33:20 +0000807 if (sz < strm->bytes_per_frame) {
808 pj_memset((char*)buffer+sz,
809 strm->silence_char,
810 strm->bytes_per_frame - sz);
811 }
Benny Prijono2058f472009-02-22 17:15:34 +0000812 } else {
813 pj_assert(!"Frame type not supported");
814 }
Benny Prijono598b01d2009-02-18 13:55:03 +0000815 }
816
817 /* Write to the device. */
Benny Prijono5fad7be2009-02-22 21:33:20 +0000818 mr = waveOutWrite(wmme_strm->hWave.Out,
819 &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
820 sizeof(WAVEHDR));
821 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000822 status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
Benny Prijono5fad7be2009-02-22 21:33:20 +0000823 break;
Benny Prijono598b01d2009-02-18 13:55:03 +0000824 }
825
826 /* Increment position. */
827 if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
828 wmme_strm->dwBufIdx = 0;
Benny Prijono7a380002009-03-09 12:55:29 +0000829 wmme_strm->timestamp.u64 += strm->param.samples_per_frame /
830 strm->param.channel_count;
Benny Prijono598b01d2009-02-18 13:55:03 +0000831 }
832 }
833 else
834 {
835 struct wmme_channel *wmme_strm = &strm->rec_strm;
836 MMRESULT mr = MMSYSERR_NOERROR;
837 status = PJ_SUCCESS;
838
839 /*
840 * Windows Multimedia has indicated that it has some frames ready
841 * in the capture buffer. Get as much frames as possible to
842 * prevent overflows.
843 */
844#if 0
845 {
846 static DWORD tc = 0;
847 DWORD now = GetTickCount();
848 DWORD i = 0;
849 DWORD bits = 0;
850
851 if (tc == 0) tc = now;
852
853 for (i = 0; i < wmme_strm->dwMaxBufIdx; ++i)
854 {
855 bits = bits << 4;
856 bits |= wmme_strm->WaveHdr[i].dwFlags & WHDR_DONE;
857 }
858 PJ_LOG(5,(THIS_FILE, "Record Signal> Index: %d, Delta: %4.4d, "
859 "Flags: %6.6x\n",
860 wmme_strm->dwBufIdx,
861 now - tc,
862 bits));
863 tc = now;
864 }
865#endif
866
867 while (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE)
868 {
869 char* buffer = (char*)
870 wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
871 unsigned cap_len =
872 wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwBytesRecorded;
Benny Prijono2058f472009-02-22 17:15:34 +0000873 pjmedia_frame pcm_frame, *frame;
Benny Prijono598b01d2009-02-18 13:55:03 +0000874
875 /*
876 PJ_LOG(5,(THIS_FILE, "Read %d bytes from buffer %d", cap_len,
877 wmme_strm->dwBufIdx));
878 */
Benny Prijono2058f472009-02-22 17:15:34 +0000879
Benny Prijono5fad7be2009-02-22 21:33:20 +0000880 if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
Benny Prijono2058f472009-02-22 17:15:34 +0000881 /* PCM mode */
882 if (cap_len < strm->bytes_per_frame)
883 pj_bzero(buffer + cap_len,
884 strm->bytes_per_frame - cap_len);
Benny Prijono598b01d2009-02-18 13:55:03 +0000885
Benny Prijono2058f472009-02-22 17:15:34 +0000886 /* Copy the audio data out of the wave buffer. */
887 pj_memcpy(strm->buffer, buffer, strm->bytes_per_frame);
Benny Prijono598b01d2009-02-18 13:55:03 +0000888
Benny Prijono2058f472009-02-22 17:15:34 +0000889 /* Prepare frame */
890 frame = &pcm_frame;
891 frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
892 frame->buf = strm->buffer;
893 frame->size = strm->bytes_per_frame;
894 frame->timestamp.u64 = wmme_strm->timestamp.u64;
895 frame->bit_info = 0;
896
897 } else {
898 /* Codec mode */
899 frame = &strm->xfrm->base;
900
901 frame->type = PJMEDIA_FRAME_TYPE_EXTENDED;
902 frame->buf = NULL;
903 frame->size = strm->bytes_per_frame;
904 frame->timestamp.u64 = wmme_strm->timestamp.u64;
905 frame->bit_info = 0;
906
907 strm->xfrm->samples_cnt = 0;
908 strm->xfrm->subframe_cnt = 0;
Benny Prijono7a380002009-03-09 12:55:29 +0000909 pjmedia_frame_ext_append_subframe(
910 strm->xfrm, buffer,
911 strm->bytes_per_frame *8,
912 strm->param.samples_per_frame
913 );
Benny Prijono2058f472009-02-22 17:15:34 +0000914 }
Benny Prijono598b01d2009-02-18 13:55:03 +0000915
916 /* Re-add the buffer to the device. */
917 mr = waveInAddBuffer(wmme_strm->hWave.In,
918 &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
919 sizeof(WAVEHDR));
920 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000921 status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +0000922 break;
923 }
924
Benny Prijono598b01d2009-02-18 13:55:03 +0000925
926 /* Call callback */
Benny Prijono2e988792009-02-23 10:21:33 +0000927 //PJ_LOG(5,(THIS_FILE, "xxx %u rec_cb", rec_cnt++));
Benny Prijono2058f472009-02-22 17:15:34 +0000928 status = (*strm->rec_cb)(strm->user_data, frame);
Benny Prijono598b01d2009-02-18 13:55:03 +0000929 if (status != PJ_SUCCESS)
930 break;
931
932 /* Increment position. */
933 if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
934 wmme_strm->dwBufIdx = 0;
Benny Prijono7a380002009-03-09 12:55:29 +0000935 wmme_strm->timestamp.u64 += strm->param.samples_per_frame /
936 strm->param.channel_count;
Benny Prijono598b01d2009-02-18 13:55:03 +0000937 }
938 }
939 }
940
941 PJ_LOG(5,(THIS_FILE, "WMME: thread stopping.."));
942 return 0;
943}
944
945
946/* API: create stream */
947static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
Benny Prijono10454dc2009-02-21 14:21:59 +0000948 const pjmedia_aud_param *param,
Benny Prijono598b01d2009-02-18 13:55:03 +0000949 pjmedia_aud_rec_cb rec_cb,
950 pjmedia_aud_play_cb play_cb,
951 void *user_data,
952 pjmedia_aud_stream **p_aud_strm)
953{
954 struct wmme_factory *wf = (struct wmme_factory*)f;
955 pj_pool_t *pool;
956 struct wmme_stream *strm;
Benny Prijono5fad7be2009-02-22 21:33:20 +0000957 pj_uint8_t silence_char;
Benny Prijono598b01d2009-02-18 13:55:03 +0000958 pj_status_t status;
959
Benny Prijono5fad7be2009-02-22 21:33:20 +0000960 switch (param->ext_fmt.id) {
961 case PJMEDIA_FORMAT_L16:
962 silence_char = '\0';
963 break;
964 case PJMEDIA_FORMAT_ALAW:
965 silence_char = (pj_uint8_t)'\xd5';
966 break;
967 case PJMEDIA_FORMAT_ULAW:
968 silence_char = (pj_uint8_t)'\xff';
969 break;
970 default:
971 return PJMEDIA_EAUD_BADFORMAT;
972 }
Benny Prijono598b01d2009-02-18 13:55:03 +0000973
974 /* Create and Initialize stream descriptor */
975 pool = pj_pool_create(wf->pf, "wmme-dev", 1000, 1000, NULL);
976 PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
977
978 strm = PJ_POOL_ZALLOC_T(pool, struct wmme_stream);
Benny Prijono7a380002009-03-09 12:55:29 +0000979 pj_memcpy(&strm->param, param, sizeof(*param));
Benny Prijono598b01d2009-02-18 13:55:03 +0000980 strm->pool = pool;
981 strm->rec_cb = rec_cb;
982 strm->play_cb = play_cb;
983 strm->user_data = user_data;
Benny Prijono5fad7be2009-02-22 21:33:20 +0000984 strm->fmt_id = param->ext_fmt.id;
985 strm->silence_char = silence_char;
Benny Prijono598b01d2009-02-18 13:55:03 +0000986
987 /* Create player stream */
988 if (param->dir & PJMEDIA_DIR_PLAYBACK) {
989 unsigned buf_count;
990
Benny Prijono7a380002009-03-09 12:55:29 +0000991 if ((param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)==0) {
992 strm->param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
993 strm->param.output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
994 }
Benny Prijono598b01d2009-02-18 13:55:03 +0000995
Benny Prijono7a380002009-03-09 12:55:29 +0000996 buf_count = strm->param.output_latency_ms * param->clock_rate *
Benny Prijono598b01d2009-02-18 13:55:03 +0000997 param->channel_count / param->samples_per_frame / 1000;
998
999 status = init_player_stream(wf, strm->pool,
Benny Prijono2058f472009-02-22 17:15:34 +00001000 strm,
Benny Prijono598b01d2009-02-18 13:55:03 +00001001 &strm->play_strm,
Benny Prijono2058f472009-02-22 17:15:34 +00001002 param,
Benny Prijono598b01d2009-02-18 13:55:03 +00001003 buf_count);
1004
1005 if (status != PJ_SUCCESS) {
1006 stream_destroy(&strm->base);
1007 return status;
1008 }
1009 }
1010
1011 /* Create capture stream */
1012 if (param->dir & PJMEDIA_DIR_CAPTURE) {
1013 unsigned buf_count;
1014
Benny Prijono7a380002009-03-09 12:55:29 +00001015 if ((param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)==0) {
1016 strm->param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
1017 strm->param.input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
1018 }
Benny Prijono598b01d2009-02-18 13:55:03 +00001019
Benny Prijono7a380002009-03-09 12:55:29 +00001020 buf_count = strm->param.input_latency_ms * param->clock_rate *
Benny Prijono598b01d2009-02-18 13:55:03 +00001021 param->channel_count / param->samples_per_frame / 1000;
1022
1023 status = init_capture_stream(wf, strm->pool,
Benny Prijono2058f472009-02-22 17:15:34 +00001024 strm,
Benny Prijono598b01d2009-02-18 13:55:03 +00001025 &strm->rec_strm,
Benny Prijono2058f472009-02-22 17:15:34 +00001026 param,
Benny Prijono598b01d2009-02-18 13:55:03 +00001027 buf_count);
1028
1029 if (status != PJ_SUCCESS) {
1030 stream_destroy(&strm->base);
1031 return status;
1032 }
1033 }
1034
Benny Prijono5fad7be2009-02-22 21:33:20 +00001035 strm->buffer = pj_pool_alloc(pool, strm->bytes_per_frame);
1036 if (!strm->buffer) {
1037 pj_pool_release(pool);
1038 return PJ_ENOMEM;
1039 }
1040
Benny Prijono2058f472009-02-22 17:15:34 +00001041 /* If format is extended, must create buffer for the extended frame. */
Benny Prijono5fad7be2009-02-22 21:33:20 +00001042 if (strm->fmt_id != PJMEDIA_FORMAT_L16) {
Benny Prijono2058f472009-02-22 17:15:34 +00001043 strm->xfrm_size = sizeof(pjmedia_frame_ext) +
1044 32 * sizeof(pjmedia_frame_ext_subframe) +
Benny Prijono5fad7be2009-02-22 21:33:20 +00001045 strm->bytes_per_frame + 4;
Benny Prijono2058f472009-02-22 17:15:34 +00001046 strm->xfrm = (pjmedia_frame_ext*)
1047 pj_pool_alloc(pool, strm->xfrm_size);
1048 }
1049
Benny Prijono598b01d2009-02-18 13:55:03 +00001050 /* Create the stop event */
1051 strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL);
1052 if (strm->thread_quit_event == NULL) {
1053 status = pj_get_os_error();
1054 stream_destroy(&strm->base);
1055 return status;
1056 }
1057
1058 /* Create and start the thread */
1059 status = pj_thread_create(pool, "wmme", &wmme_dev_thread, strm, 0, 0,
1060 &strm->thread);
1061 if (status != PJ_SUCCESS) {
1062 stream_destroy(&strm->base);
1063 return status;
1064 }
1065
Benny Prijono7a380002009-03-09 12:55:29 +00001066 /* Apply the remaining settings */
1067 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
1068 stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1069 &param->output_vol);
1070 }
1071
1072
Benny Prijono598b01d2009-02-18 13:55:03 +00001073 /* Done */
1074 strm->base.op = &stream_op;
1075 *p_aud_strm = &strm->base;
1076
1077 return PJ_SUCCESS;
1078}
1079
1080/* API: Get stream info. */
1081static pj_status_t stream_get_param(pjmedia_aud_stream *s,
Benny Prijono10454dc2009-02-21 14:21:59 +00001082 pjmedia_aud_param *pi)
Benny Prijono598b01d2009-02-18 13:55:03 +00001083{
1084 struct wmme_stream *strm = (struct wmme_stream*)s;
1085
1086 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1087
Benny Prijono7a380002009-03-09 12:55:29 +00001088 pj_memcpy(pi, &strm->param, sizeof(*pi));
Benny Prijono598b01d2009-02-18 13:55:03 +00001089
Benny Prijono85bdaa82009-03-09 13:22:01 +00001090 /* Update the volume setting */
1091 if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1092 &pi->output_vol) == PJ_SUCCESS)
1093 {
1094 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
1095 }
1096
Benny Prijono598b01d2009-02-18 13:55:03 +00001097 return PJ_SUCCESS;
1098}
1099
1100/* API: get capability */
1101static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
1102 pjmedia_aud_dev_cap cap,
1103 void *pval)
1104{
1105 struct wmme_stream *strm = (struct wmme_stream*)s;
1106
1107 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1108
1109 if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
Benny Prijono7a380002009-03-09 12:55:29 +00001110 (strm->param.dir & PJMEDIA_DIR_CAPTURE))
Benny Prijono598b01d2009-02-18 13:55:03 +00001111 {
1112 /* Recording latency */
Benny Prijono7a380002009-03-09 12:55:29 +00001113 *(unsigned*)pval = strm->param.input_latency_ms;
Benny Prijono598b01d2009-02-18 13:55:03 +00001114 return PJ_SUCCESS;
1115 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
Benny Prijono7a380002009-03-09 12:55:29 +00001116 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
Benny Prijono598b01d2009-02-18 13:55:03 +00001117 {
1118 /* Playback latency */
Benny Prijono7a380002009-03-09 12:55:29 +00001119 *(unsigned*)pval = strm->param.output_latency_ms;
Benny Prijono598b01d2009-02-18 13:55:03 +00001120 return PJ_SUCCESS;
1121 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
1122 strm->play_strm.hWave.Out)
1123 {
1124 /* Output volume setting */
Benny Prijono7a380002009-03-09 12:55:29 +00001125 DWORD waveVol;
Benny Prijono598b01d2009-02-18 13:55:03 +00001126 MMRESULT mr;
1127
Benny Prijono7a380002009-03-09 12:55:29 +00001128 mr = waveOutGetVolume(strm->play_strm.hWave.Out, &waveVol);
Benny Prijono598b01d2009-02-18 13:55:03 +00001129 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001130 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +00001131 }
1132
Benny Prijono7a380002009-03-09 12:55:29 +00001133 waveVol &= 0xFFFF;
1134 *(unsigned*)pval = (waveVol * 100) / 0xFFFF;
Benny Prijono598b01d2009-02-18 13:55:03 +00001135 return PJ_SUCCESS;
1136 } else {
Benny Prijono64f91382009-03-05 18:02:28 +00001137 return PJMEDIA_EAUD_INVCAP;
Benny Prijono598b01d2009-02-18 13:55:03 +00001138 }
1139}
1140
1141/* API: set capability */
1142static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
1143 pjmedia_aud_dev_cap cap,
1144 const void *pval)
1145{
1146 struct wmme_stream *strm = (struct wmme_stream*)s;
1147
1148 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1149
1150 if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
1151 strm->play_strm.hWave.Out)
1152 {
1153 /* Output volume setting */
Benny Prijono7a380002009-03-09 12:55:29 +00001154 unsigned vol = *(unsigned*)pval;
1155 DWORD waveVol;
Benny Prijono598b01d2009-02-18 13:55:03 +00001156 MMRESULT mr;
Benny Prijono7a380002009-03-09 12:55:29 +00001157 pj_status_t status;
Benny Prijono598b01d2009-02-18 13:55:03 +00001158
Benny Prijono7a380002009-03-09 12:55:29 +00001159 if (vol > 100)
1160 vol = 100;
Benny Prijono598b01d2009-02-18 13:55:03 +00001161
Benny Prijono7a380002009-03-09 12:55:29 +00001162 waveVol = (vol * 0xFFFF) / 100;
1163 waveVol |= (waveVol << 16);
1164
1165 mr = waveOutSetVolume(strm->play_strm.hWave.Out, waveVol);
1166 status = (mr==MMSYSERR_NOERROR)? PJ_SUCCESS :
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001167 PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
Benny Prijono7a380002009-03-09 12:55:29 +00001168 if (status == PJ_SUCCESS) {
1169 strm->param.output_vol = *(unsigned*)pval;
1170 }
1171 return status;
Benny Prijono598b01d2009-02-18 13:55:03 +00001172 }
1173
Benny Prijono64f91382009-03-05 18:02:28 +00001174 return PJMEDIA_EAUD_INVCAP;
Benny Prijono598b01d2009-02-18 13:55:03 +00001175}
1176
1177/* API: Start stream. */
1178static pj_status_t stream_start(pjmedia_aud_stream *strm)
1179{
1180 struct wmme_stream *stream = (struct wmme_stream*)strm;
1181 MMRESULT mr;
1182
1183 if (stream->play_strm.hWave.Out != NULL)
1184 {
1185 mr = waveOutRestart(stream->play_strm.hWave.Out);
1186 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001187 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +00001188 }
Benny Prijono2e988792009-02-23 10:21:33 +00001189 PJ_LOG(4,(THIS_FILE, "WMME playback stream started"));
Benny Prijono598b01d2009-02-18 13:55:03 +00001190 }
1191
1192 if (stream->rec_strm.hWave.In != NULL)
1193 {
1194 mr = waveInStart(stream->rec_strm.hWave.In);
1195 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001196 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +00001197 }
Benny Prijono2e988792009-02-23 10:21:33 +00001198 PJ_LOG(4,(THIS_FILE, "WMME capture stream started"));
Benny Prijono598b01d2009-02-18 13:55:03 +00001199 }
1200
1201 return PJ_SUCCESS;
1202}
1203
1204/* API: Stop stream. */
1205static pj_status_t stream_stop(pjmedia_aud_stream *strm)
1206{
1207 struct wmme_stream *stream = (struct wmme_stream*)strm;
1208 MMRESULT mr;
1209
1210 PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
1211
1212 if (stream->play_strm.hWave.Out != NULL)
1213 {
1214 mr = waveOutPause(stream->play_strm.hWave.Out);
1215 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001216 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +00001217 }
Benny Prijono2e988792009-02-23 10:21:33 +00001218 PJ_LOG(4,(THIS_FILE, "Stopped WMME playback stream"));
Benny Prijono598b01d2009-02-18 13:55:03 +00001219 }
1220
1221 if (stream->rec_strm.hWave.In != NULL)
1222 {
1223 mr = waveInStop(stream->rec_strm.hWave.In);
1224 if (mr != MMSYSERR_NOERROR) {
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001225 return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
Benny Prijono598b01d2009-02-18 13:55:03 +00001226 }
Benny Prijono2e988792009-02-23 10:21:33 +00001227 PJ_LOG(4,(THIS_FILE, "Stopped WMME capture stream"));
Benny Prijono598b01d2009-02-18 13:55:03 +00001228 }
1229
1230 return PJ_SUCCESS;
1231}
1232
1233
1234/* API: Destroy stream. */
1235static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
1236{
1237 struct wmme_stream *stream = (struct wmme_stream*)strm;
1238 unsigned i;
1239
1240 PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
1241
1242 stream_stop(strm);
1243
1244 if (stream->thread)
1245 {
1246 SetEvent(stream->thread_quit_event);
1247 pj_thread_join(stream->thread);
1248 pj_thread_destroy(stream->thread);
1249 stream->thread = NULL;
1250 }
1251
1252 /* Unprepare the headers and close the play device */
1253 if (stream->play_strm.hWave.Out)
1254 {
1255 waveOutReset(stream->play_strm.hWave.Out);
1256 for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
1257 waveOutUnprepareHeader(stream->play_strm.hWave.Out,
1258 &(stream->play_strm.WaveHdr[i]),
1259 sizeof(WAVEHDR));
1260 waveOutClose(stream->play_strm.hWave.Out);
1261 stream->play_strm.hWave.Out = NULL;
1262 }
1263
1264 /* Close the play event */
1265 if (stream->play_strm.hEvent)
1266 {
1267 CloseHandle(stream->play_strm.hEvent);
1268 stream->play_strm.hEvent = NULL;
1269 }
1270
1271 /* Unprepare the headers and close the record device */
1272 if (stream->rec_strm.hWave.In)
1273 {
1274 waveInReset(stream->rec_strm.hWave.In);
1275 for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
1276 waveInUnprepareHeader(stream->rec_strm.hWave.In,
1277 &(stream->rec_strm.WaveHdr[i]),
1278 sizeof(WAVEHDR));
1279 waveInClose(stream->rec_strm.hWave.In);
1280 stream->rec_strm.hWave.In = NULL;
1281 }
1282
1283 /* Close the record event */
1284 if (stream->rec_strm.hEvent)
1285 {
1286 CloseHandle(stream->rec_strm.hEvent);
1287 stream->rec_strm.hEvent = NULL;
1288 }
1289
1290 pj_pool_release(stream->pool);
1291
1292 return PJ_SUCCESS;
1293}
1294
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001295#endif /* PJMEDIA_AUDIO_DEV_HAS_WMME */
1296