2006-01-10 17:51:06 +00:00
|
|
|
/***
|
2006-06-19 21:53:48 +00:00
|
|
|
This file is part of PulseAudio.
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-02-13 15:35:19 +00:00
|
|
|
Copyright 2006 Lennart Poettering
|
|
|
|
|
Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
|
|
|
|
|
|
2006-06-19 21:53:48 +00:00
|
|
|
PulseAudio is free software; you can redistribute it and/or modify
|
2006-01-10 17:51:06 +00:00
|
|
|
it under the terms of the GNU Lesser General Public License as published
|
2009-03-03 20:23:02 +00:00
|
|
|
by the Free Software Foundation; either version 2.1 of the License,
|
2006-01-10 17:51:06 +00:00
|
|
|
or (at your option) any later version.
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2006-06-19 21:53:48 +00:00
|
|
|
PulseAudio is distributed in the hope that it will be useful, but
|
2006-01-10 17:51:06 +00:00
|
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
General Public License for more details.
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
2014-11-26 14:14:51 +01:00
|
|
|
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
|
2006-01-10 17:51:06 +00:00
|
|
|
***/
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#include <mmsystem.h>
|
2017-03-01 19:29:52 +01:00
|
|
|
#include <string.h>
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2006-06-19 21:53:48 +00:00
|
|
|
#include <pulse/xmalloc.h>
|
2006-08-22 11:38:46 +00:00
|
|
|
#include <pulse/timeval.h>
|
2006-05-17 16:34:18 +00:00
|
|
|
|
2006-06-19 21:53:48 +00:00
|
|
|
#include <pulsecore/sink.h>
|
|
|
|
|
#include <pulsecore/source.h>
|
|
|
|
|
#include <pulsecore/module.h>
|
|
|
|
|
#include <pulsecore/modargs.h>
|
|
|
|
|
#include <pulsecore/sample-util.h>
|
|
|
|
|
#include <pulsecore/core-util.h>
|
|
|
|
|
#include <pulsecore/log.h>
|
2011-01-12 15:02:18 +01:00
|
|
|
#include <pulsecore/thread.h>
|
|
|
|
|
#include <pulsecore/thread-mq.h>
|
2006-02-16 19:19:58 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
PA_MODULE_AUTHOR("Pierre Ossman");
|
|
|
|
|
PA_MODULE_DESCRIPTION("Windows waveOut Sink/Source");
|
|
|
|
|
PA_MODULE_VERSION(PACKAGE_VERSION);
|
2006-04-27 05:39:11 +00:00
|
|
|
PA_MODULE_USAGE(
|
|
|
|
|
"sink_name=<name for the sink> "
|
2007-02-12 09:37:34 +00:00
|
|
|
"source_name=<name for the source> "
|
2017-03-01 19:29:52 +01:00
|
|
|
"output_device=<device number for the sink> "
|
|
|
|
|
"output_device_name=<name of the output device> "
|
|
|
|
|
"input_device=<device number for the source> "
|
|
|
|
|
"input_device_name=<name of the input device> "
|
2006-04-27 05:39:11 +00:00
|
|
|
"record=<enable source?> "
|
|
|
|
|
"playback=<enable sink?> "
|
|
|
|
|
"format=<sample format> "
|
|
|
|
|
"rate=<sample rate> "
|
2011-03-18 11:52:30 +01:00
|
|
|
"channels=<number of channels> "
|
|
|
|
|
"channel_map=<channel map> "
|
2006-04-27 05:39:11 +00:00
|
|
|
"fragments=<number of fragments> "
|
2017-03-01 19:29:53 +01:00
|
|
|
"fragment_size=<fragment size>"
|
|
|
|
|
"device=<device number - deprecated>"
|
|
|
|
|
"device_name=<name of the device - deprecated>");
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
#define DEFAULT_SINK_NAME "wave_output"
|
|
|
|
|
#define DEFAULT_SOURCE_NAME "wave_input"
|
|
|
|
|
|
2006-02-21 16:35:12 +00:00
|
|
|
#define WAVEOUT_MAX_VOLUME 0xFFFF
|
|
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
struct userdata {
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_sink *sink;
|
|
|
|
|
pa_source *source;
|
|
|
|
|
pa_core *core;
|
2006-01-10 17:51:06 +00:00
|
|
|
pa_usec_t poll_timeout;
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_thread *thread;
|
|
|
|
|
pa_thread_mq thread_mq;
|
|
|
|
|
pa_rtpoll *rtpoll;
|
|
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
uint32_t fragments, fragment_size;
|
|
|
|
|
|
|
|
|
|
uint32_t free_ofrags, free_ifrags;
|
|
|
|
|
|
|
|
|
|
DWORD written_bytes;
|
2006-08-22 16:15:47 +00:00
|
|
|
int sink_underflow;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
int cur_ohdr, cur_ihdr;
|
|
|
|
|
WAVEHDR *ohdrs, *ihdrs;
|
|
|
|
|
|
|
|
|
|
HWAVEOUT hwo;
|
|
|
|
|
HWAVEIN hwi;
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_module *module;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
CRITICAL_SECTION crit;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const char* const valid_modargs[] = {
|
|
|
|
|
"sink_name",
|
|
|
|
|
"source_name",
|
2017-03-01 19:29:52 +01:00
|
|
|
"output_device",
|
|
|
|
|
"output_device_name",
|
|
|
|
|
"input_device",
|
|
|
|
|
"input_device_name",
|
2006-01-10 17:51:06 +00:00
|
|
|
"record",
|
|
|
|
|
"playback",
|
|
|
|
|
"fragments",
|
|
|
|
|
"fragment_size",
|
|
|
|
|
"format",
|
|
|
|
|
"rate",
|
|
|
|
|
"channels",
|
2006-04-27 05:39:11 +00:00
|
|
|
"channel_map",
|
2017-03-01 19:29:53 +01:00
|
|
|
"device",
|
|
|
|
|
"device_name",
|
2006-01-10 17:51:06 +00:00
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
static void do_write(struct userdata *u) {
|
2006-08-22 16:15:47 +00:00
|
|
|
uint32_t free_frags;
|
|
|
|
|
pa_memchunk memchunk;
|
2006-01-10 17:51:06 +00:00
|
|
|
WAVEHDR *hdr;
|
|
|
|
|
MMRESULT res;
|
2011-01-12 15:02:18 +01:00
|
|
|
void *p;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
if (!u->sink)
|
|
|
|
|
return;
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
if (!PA_SINK_IS_LINKED(u->sink->state))
|
|
|
|
|
return;
|
|
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
EnterCriticalSection(&u->crit);
|
|
|
|
|
free_frags = u->free_ofrags;
|
|
|
|
|
LeaveCriticalSection(&u->crit);
|
|
|
|
|
|
2006-08-22 16:15:47 +00:00
|
|
|
if (!u->sink_underflow && (free_frags == u->fragments))
|
2006-08-18 21:38:40 +00:00
|
|
|
pa_log_debug("WaveOut underflow!");
|
2006-04-19 11:55:46 +00:00
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
while (free_frags) {
|
|
|
|
|
hdr = &u->ohdrs[u->cur_ohdr];
|
|
|
|
|
if (hdr->dwFlags & WHDR_PREPARED)
|
|
|
|
|
waveOutUnprepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
|
|
|
|
|
|
2006-08-22 16:15:47 +00:00
|
|
|
hdr->dwBufferLength = 0;
|
|
|
|
|
while (hdr->dwBufferLength < u->fragment_size) {
|
|
|
|
|
size_t len;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2006-08-22 16:15:47 +00:00
|
|
|
len = u->fragment_size - hdr->dwBufferLength;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_sink_render(u->sink, len, &memchunk);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(memchunk.memblock);
|
|
|
|
|
pa_assert(memchunk.length);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2006-08-22 16:15:47 +00:00
|
|
|
if (memchunk.length < len)
|
|
|
|
|
len = memchunk.length;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
p = pa_memblock_acquire(memchunk.memblock);
|
|
|
|
|
memcpy(hdr->lpData + hdr->dwBufferLength, (char*) p + memchunk.index, len);
|
|
|
|
|
pa_memblock_release(memchunk.memblock);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2006-08-22 16:15:47 +00:00
|
|
|
hdr->dwBufferLength += len;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2006-08-22 16:15:47 +00:00
|
|
|
pa_memblock_unref(memchunk.memblock);
|
|
|
|
|
memchunk.memblock = NULL;
|
|
|
|
|
}
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-03-12 19:41:07 +01:00
|
|
|
/* Underflow detection */
|
2006-08-22 16:15:47 +00:00
|
|
|
if (hdr->dwBufferLength == 0) {
|
|
|
|
|
u->sink_underflow = 1;
|
|
|
|
|
break;
|
2006-01-10 17:51:06 +00:00
|
|
|
}
|
2006-08-22 16:15:47 +00:00
|
|
|
u->sink_underflow = 0;
|
|
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
res = waveOutPrepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
|
2011-01-12 15:02:18 +01:00
|
|
|
if (res != MMSYSERR_NOERROR)
|
|
|
|
|
pa_log_error("Unable to prepare waveOut block: %d", res);
|
|
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
res = waveOutWrite(u->hwo, hdr, sizeof(WAVEHDR));
|
2011-01-12 15:02:18 +01:00
|
|
|
if (res != MMSYSERR_NOERROR)
|
|
|
|
|
pa_log_error("Unable to write waveOut block: %d", res);
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2006-08-22 16:15:47 +00:00
|
|
|
u->written_bytes += hdr->dwBufferLength;
|
|
|
|
|
|
|
|
|
|
EnterCriticalSection(&u->crit);
|
|
|
|
|
u->free_ofrags--;
|
|
|
|
|
LeaveCriticalSection(&u->crit);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
free_frags--;
|
|
|
|
|
u->cur_ohdr++;
|
|
|
|
|
u->cur_ohdr %= u->fragments;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
static void do_read(struct userdata *u) {
|
2006-01-10 17:51:06 +00:00
|
|
|
uint32_t free_frags;
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_memchunk memchunk;
|
2006-01-10 17:51:06 +00:00
|
|
|
WAVEHDR *hdr;
|
|
|
|
|
MMRESULT res;
|
2011-01-12 15:02:18 +01:00
|
|
|
void *p;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
if (!u->source)
|
|
|
|
|
return;
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
if (!PA_SOURCE_IS_LINKED(u->source->state))
|
|
|
|
|
return;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
EnterCriticalSection(&u->crit);
|
2006-01-10 17:51:06 +00:00
|
|
|
free_frags = u->free_ifrags;
|
|
|
|
|
u->free_ifrags = 0;
|
|
|
|
|
LeaveCriticalSection(&u->crit);
|
|
|
|
|
|
2006-04-19 11:55:46 +00:00
|
|
|
if (free_frags == u->fragments)
|
2006-08-18 21:38:40 +00:00
|
|
|
pa_log_debug("WaveIn overflow!");
|
2006-04-19 11:55:46 +00:00
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
while (free_frags) {
|
|
|
|
|
hdr = &u->ihdrs[u->cur_ihdr];
|
|
|
|
|
if (hdr->dwFlags & WHDR_PREPARED)
|
|
|
|
|
waveInUnprepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
|
|
|
|
|
|
|
|
|
|
if (hdr->dwBytesRecorded) {
|
2006-08-22 11:37:53 +00:00
|
|
|
memchunk.memblock = pa_memblock_new(u->core->mempool, hdr->dwBytesRecorded);
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(memchunk.memblock);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
p = pa_memblock_acquire(memchunk.memblock);
|
|
|
|
|
memcpy((char*) p, hdr->lpData, hdr->dwBytesRecorded);
|
|
|
|
|
pa_memblock_release(memchunk.memblock);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
memchunk.length = hdr->dwBytesRecorded;
|
2006-01-10 17:51:06 +00:00
|
|
|
memchunk.index = 0;
|
|
|
|
|
|
|
|
|
|
pa_source_post(u->source, &memchunk);
|
|
|
|
|
pa_memblock_unref(memchunk.memblock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = waveInPrepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
|
2011-01-12 15:02:18 +01:00
|
|
|
if (res != MMSYSERR_NOERROR)
|
|
|
|
|
pa_log_error("Unable to prepare waveIn block: %d", res);
|
|
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
res = waveInAddBuffer(u->hwi, hdr, sizeof(WAVEHDR));
|
2011-01-12 15:02:18 +01:00
|
|
|
if (res != MMSYSERR_NOERROR)
|
|
|
|
|
pa_log_error("Unable to add waveIn block: %d", res);
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
free_frags--;
|
|
|
|
|
u->cur_ihdr++;
|
|
|
|
|
u->cur_ihdr %= u->fragments;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
static void thread_func(void *userdata) {
|
2006-01-10 17:51:06 +00:00
|
|
|
struct userdata *u = userdata;
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(u->sink || u->source);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_log_debug("Thread starting up");
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
if (u->core->realtime_scheduling)
|
|
|
|
|
pa_make_realtime(u->core->realtime_priority);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_thread_mq_install(&u->thread_mq);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
for (;;) {
|
|
|
|
|
int ret;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool need_timer = false;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2012-08-30 16:50:13 +03:00
|
|
|
if (u->sink) {
|
2012-11-16 23:09:15 +05:30
|
|
|
if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_sink_process_rewind(u->sink, 0);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2012-08-30 16:50:13 +03:00
|
|
|
if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
|
|
|
|
|
do_write(u);
|
2013-06-27 19:28:09 +02:00
|
|
|
need_timer = true;
|
2012-08-30 16:50:13 +03:00
|
|
|
}
|
2011-03-14 10:51:11 +01:00
|
|
|
}
|
2012-08-30 16:50:13 +03:00
|
|
|
|
2011-03-14 10:51:11 +01:00
|
|
|
if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
|
|
|
|
|
do_read(u);
|
2013-06-27 19:28:09 +02:00
|
|
|
need_timer = true;
|
2011-03-14 10:51:11 +01:00
|
|
|
}
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-03-14 10:51:11 +01:00
|
|
|
if (need_timer)
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_rtpoll_set_timer_relative(u->rtpoll, u->poll_timeout);
|
2011-03-14 10:51:11 +01:00
|
|
|
else
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_rtpoll_set_timer_disabled(u->rtpoll);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
/* Hmm, nothing to do. Let's sleep */
|
2014-11-10 14:15:39 +01:00
|
|
|
if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
|
2011-01-12 15:02:18 +01:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
|
if (ret == 0)
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
/* If this was no regular exit from the loop we have to continue
|
|
|
|
|
* processing messages until we received PA_MESSAGE_SHUTDOWN */
|
|
|
|
|
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
|
|
|
|
|
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
|
|
|
|
|
|
|
|
|
|
finish:
|
|
|
|
|
pa_log_debug("Thread shutting down");
|
2006-01-10 17:51:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void CALLBACK chunk_done_cb(HWAVEOUT hwo, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
|
2011-03-12 19:41:07 +01:00
|
|
|
struct userdata *u = (struct userdata*) inst;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-03-12 19:41:07 +01:00
|
|
|
if (msg == WOM_OPEN)
|
|
|
|
|
pa_log_debug("WaveOut subsystem opened.");
|
|
|
|
|
if (msg == WOM_CLOSE)
|
|
|
|
|
pa_log_debug("WaveOut subsystem closed.");
|
2006-01-10 17:51:06 +00:00
|
|
|
if (msg != WOM_DONE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
EnterCriticalSection(&u->crit);
|
|
|
|
|
u->free_ofrags++;
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(u->free_ofrags <= u->fragments);
|
2006-01-10 17:51:06 +00:00
|
|
|
LeaveCriticalSection(&u->crit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void CALLBACK chunk_ready_cb(HWAVEIN hwi, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
|
2011-03-12 19:41:07 +01:00
|
|
|
struct userdata *u = (struct userdata*) inst;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-03-12 19:41:07 +01:00
|
|
|
if (msg == WIM_OPEN)
|
|
|
|
|
pa_log_debug("WaveIn subsystem opened.");
|
|
|
|
|
if (msg == WIM_CLOSE)
|
|
|
|
|
pa_log_debug("WaveIn subsystem closed.");
|
2006-01-10 17:51:06 +00:00
|
|
|
if (msg != WIM_DATA)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
EnterCriticalSection(&u->crit);
|
|
|
|
|
u->free_ifrags++;
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(u->free_ifrags <= u->fragments);
|
2006-01-10 17:51:06 +00:00
|
|
|
LeaveCriticalSection(&u->crit);
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
static pa_usec_t sink_get_latency(struct userdata *u) {
|
2006-01-10 17:51:06 +00:00
|
|
|
uint32_t free_frags;
|
|
|
|
|
MMTIME mmt;
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(u->sink);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
memset(&mmt, 0, sizeof(mmt));
|
|
|
|
|
mmt.wType = TIME_BYTES;
|
|
|
|
|
if (waveOutGetPosition(u->hwo, &mmt, sizeof(mmt)) == MMSYSERR_NOERROR)
|
2011-01-12 15:02:18 +01:00
|
|
|
return pa_bytes_to_usec(u->written_bytes - mmt.u.cb, &u->sink->sample_spec);
|
2006-01-10 17:51:06 +00:00
|
|
|
else {
|
|
|
|
|
EnterCriticalSection(&u->crit);
|
|
|
|
|
free_frags = u->free_ofrags;
|
|
|
|
|
LeaveCriticalSection(&u->crit);
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
return pa_bytes_to_usec((u->fragments - free_frags) * u->fragment_size, &u->sink->sample_spec);
|
2006-01-10 17:51:06 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
static pa_usec_t source_get_latency(struct userdata *u) {
|
2006-01-10 17:51:06 +00:00
|
|
|
pa_usec_t r = 0;
|
|
|
|
|
uint32_t free_frags;
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(u->source);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
EnterCriticalSection(&u->crit);
|
|
|
|
|
free_frags = u->free_ifrags;
|
|
|
|
|
LeaveCriticalSection(&u->crit);
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
r += pa_bytes_to_usec((free_frags + 1) * u->fragment_size, &u->source->sample_spec);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
static int process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
|
|
|
|
struct userdata *u;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
if (pa_sink_isinstance(o)) {
|
|
|
|
|
u = PA_SINK(o)->userdata;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
switch (code) {
|
|
|
|
|
|
|
|
|
|
case PA_SINK_MESSAGE_GET_LATENCY: {
|
|
|
|
|
pa_usec_t r = 0;
|
|
|
|
|
if (u->hwo)
|
|
|
|
|
r = sink_get_latency(u);
|
source/sink: Allow pa_{source, sink}_get_latency_within_thread() to return negative values
The reported latency of source or sink is based on measured initial conditions.
If the conditions contain an error, the estimated latency values may become negative.
This does not indicate that the latency is indeed negative but can be considered
merely an offset error. The current get_latency_in_thread() calls and the
implementations of the PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY messages truncate negative
latencies because they do not make sense from a physical point of view. In fact,
the values are truncated twice, once in the message handler and a second time in
the pa_{source,sink}_get_latency_within_thread() call itself.
This leads to two problems for the latency controller within module-loopback:
- Truncating leads to discontinuities in the latency reports which then trigger
unwanted end to end latency corrections.
- If a large negative port latency offsets is set, the reported latency is always 0,
making it impossible to control the end to end latency at all.
This patch is a pre-condition for solving these problems.
It adds a new flag to pa_{sink,source}_get_latency_within_thread() to allow
negative return values. Truncating is also removed in all implementations of the
PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY message handlers. The allow_negative flag
is set to false for all calls of pa_{sink,source}_get_latency_within_thread()
except when used within PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY. This means that the
original behavior is not altered in most cases. Only if a positive latency offset
is set and the message returns a negative value, the reported latency is smaller
because the values are not truncated twice.
Additionally let PA_SOURCE_MESSAGE_GET_LATENCY return -pa_sink_get_latency_within_thread()
for monitor sources because the source gets the data before it is played.
2017-04-17 19:50:10 +02:00
|
|
|
*((int64_t*) data) = (int64_t)r;
|
2011-01-12 15:02:18 +01:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pa_sink_process_msg(o, code, data, offset, chunk);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pa_source_isinstance(o)) {
|
|
|
|
|
u = PA_SOURCE(o)->userdata;
|
|
|
|
|
|
|
|
|
|
switch (code) {
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
case PA_SOURCE_MESSAGE_GET_LATENCY: {
|
|
|
|
|
pa_usec_t r = 0;
|
|
|
|
|
if (u->hwi)
|
|
|
|
|
r = source_get_latency(u);
|
source/sink: Allow pa_{source, sink}_get_latency_within_thread() to return negative values
The reported latency of source or sink is based on measured initial conditions.
If the conditions contain an error, the estimated latency values may become negative.
This does not indicate that the latency is indeed negative but can be considered
merely an offset error. The current get_latency_in_thread() calls and the
implementations of the PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY messages truncate negative
latencies because they do not make sense from a physical point of view. In fact,
the values are truncated twice, once in the message handler and a second time in
the pa_{source,sink}_get_latency_within_thread() call itself.
This leads to two problems for the latency controller within module-loopback:
- Truncating leads to discontinuities in the latency reports which then trigger
unwanted end to end latency corrections.
- If a large negative port latency offsets is set, the reported latency is always 0,
making it impossible to control the end to end latency at all.
This patch is a pre-condition for solving these problems.
It adds a new flag to pa_{sink,source}_get_latency_within_thread() to allow
negative return values. Truncating is also removed in all implementations of the
PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY message handlers. The allow_negative flag
is set to false for all calls of pa_{sink,source}_get_latency_within_thread()
except when used within PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY. This means that the
original behavior is not altered in most cases. Only if a positive latency offset
is set and the message returns a negative value, the reported latency is smaller
because the values are not truncated twice.
Additionally let PA_SOURCE_MESSAGE_GET_LATENCY return -pa_sink_get_latency_within_thread()
for monitor sources because the source gets the data before it is played.
2017-04-17 19:50:10 +02:00
|
|
|
*((int64_t*) data) = (int64_t)r;
|
2011-01-12 15:02:18 +01:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pa_source_process_msg(o, code, data, offset, chunk);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
2006-01-10 17:51:06 +00:00
|
|
|
}
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
static void sink_get_volume_cb(pa_sink *s) {
|
2006-02-21 16:35:12 +00:00
|
|
|
struct userdata *u = s->userdata;
|
2011-07-02 12:12:43 +02:00
|
|
|
WAVEOUTCAPS caps;
|
2006-02-21 16:35:12 +00:00
|
|
|
DWORD vol;
|
|
|
|
|
pa_volume_t left, right;
|
|
|
|
|
|
2011-07-02 12:12:43 +02:00
|
|
|
if (waveOutGetDevCaps(u->hwo, &caps, sizeof(caps)) != MMSYSERR_NOERROR)
|
|
|
|
|
return;
|
|
|
|
|
if (!(caps.dwSupport & WAVECAPS_VOLUME))
|
|
|
|
|
return;
|
|
|
|
|
|
2006-02-21 16:35:12 +00:00
|
|
|
if (waveOutGetVolume(u->hwo, &vol) != MMSYSERR_NOERROR)
|
2011-01-12 15:02:18 +01:00
|
|
|
return;
|
2006-02-21 16:35:12 +00:00
|
|
|
|
2010-10-09 15:38:43 +05:30
|
|
|
left = PA_CLAMP_VOLUME((vol & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME);
|
2011-07-02 12:12:43 +02:00
|
|
|
if (caps.dwSupport & WAVECAPS_LRVOLUME)
|
|
|
|
|
right = PA_CLAMP_VOLUME(((vol >> 16) & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME);
|
|
|
|
|
else
|
|
|
|
|
right = left;
|
2006-02-21 16:35:12 +00:00
|
|
|
|
|
|
|
|
/* Windows supports > 2 channels, except for volume control */
|
2011-01-12 15:02:18 +01:00
|
|
|
if (s->real_volume.channels > 2)
|
|
|
|
|
pa_cvolume_set(&s->real_volume, s->real_volume.channels, (left + right)/2);
|
2006-02-21 16:35:12 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
s->real_volume.values[0] = left;
|
|
|
|
|
if (s->real_volume.channels > 1)
|
|
|
|
|
s->real_volume.values[1] = right;
|
2006-02-21 16:35:12 +00:00
|
|
|
}
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
static void sink_set_volume_cb(pa_sink *s) {
|
2006-02-21 16:35:12 +00:00
|
|
|
struct userdata *u = s->userdata;
|
2011-07-02 12:12:43 +02:00
|
|
|
WAVEOUTCAPS caps;
|
2006-02-21 16:35:12 +00:00
|
|
|
DWORD vol;
|
|
|
|
|
|
2011-07-02 12:12:43 +02:00
|
|
|
if (waveOutGetDevCaps(u->hwo, &caps, sizeof(caps)) != MMSYSERR_NOERROR)
|
|
|
|
|
return;
|
|
|
|
|
if (!(caps.dwSupport & WAVECAPS_VOLUME))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (s->real_volume.channels == 2 && caps.dwSupport & WAVECAPS_LRVOLUME) {
|
|
|
|
|
vol = (s->real_volume.values[0] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM)
|
|
|
|
|
| (s->real_volume.values[1] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM) << 16;
|
|
|
|
|
} else {
|
|
|
|
|
vol = (pa_cvolume_avg(&(s->real_volume)) * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM)
|
|
|
|
|
| (pa_cvolume_avg(&(s->real_volume)) * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM) << 16;
|
|
|
|
|
}
|
2006-02-21 16:35:12 +00:00
|
|
|
|
|
|
|
|
if (waveOutSetVolume(u->hwo, vol) != MMSYSERR_NOERROR)
|
2011-01-12 15:02:18 +01:00
|
|
|
return;
|
2006-02-21 16:35:12 +00:00
|
|
|
}
|
|
|
|
|
|
2006-01-11 01:17:39 +00:00
|
|
|
static int ss_to_waveformat(pa_sample_spec *ss, LPWAVEFORMATEX wf) {
|
2006-01-10 17:51:06 +00:00
|
|
|
wf->wFormatTag = WAVE_FORMAT_PCM;
|
|
|
|
|
|
|
|
|
|
if (ss->channels > 2) {
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_log_error("More than two channels not supported.");
|
2006-01-10 17:51:06 +00:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wf->nChannels = ss->channels;
|
|
|
|
|
|
|
|
|
|
wf->nSamplesPerSec = ss->rate;
|
|
|
|
|
|
|
|
|
|
if (ss->format == PA_SAMPLE_U8)
|
|
|
|
|
wf->wBitsPerSample = 8;
|
|
|
|
|
else if (ss->format == PA_SAMPLE_S16NE)
|
|
|
|
|
wf->wBitsPerSample = 16;
|
|
|
|
|
else {
|
2011-03-12 19:41:07 +01:00
|
|
|
pa_log_error("Unsupported sample format, only u8 and s16 are supported.");
|
2006-01-10 17:51:06 +00:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wf->nBlockAlign = wf->nChannels * wf->wBitsPerSample/8;
|
|
|
|
|
wf->nAvgBytesPerSec = wf->nSamplesPerSec * wf->nBlockAlign;
|
|
|
|
|
|
|
|
|
|
wf->cbSize = 0;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
int pa__get_n_used(pa_module *m) {
|
|
|
|
|
struct userdata *u;
|
|
|
|
|
pa_assert(m);
|
|
|
|
|
pa_assert(m->userdata);
|
2011-03-12 19:41:07 +01:00
|
|
|
u = (struct userdata*) m->userdata;
|
2011-01-12 15:02:18 +01:00
|
|
|
|
|
|
|
|
return (u->sink ? pa_sink_used_by(u->sink) : 0) +
|
|
|
|
|
(u->source ? pa_source_used_by(u->source) : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int pa__init(pa_module *m) {
|
2006-01-10 17:51:06 +00:00
|
|
|
struct userdata *u = NULL;
|
|
|
|
|
HWAVEOUT hwo = INVALID_HANDLE_VALUE;
|
|
|
|
|
HWAVEIN hwi = INVALID_HANDLE_VALUE;
|
|
|
|
|
WAVEFORMATEX wf;
|
2011-02-27 09:53:59 +01:00
|
|
|
WAVEOUTCAPS pwoc;
|
2017-03-01 19:29:52 +01:00
|
|
|
WAVEINCAPS pwic;
|
2011-03-12 19:41:07 +01:00
|
|
|
MMRESULT result;
|
2006-01-10 17:51:06 +00:00
|
|
|
int nfrags, frag_size;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool record = true, playback = true;
|
2017-03-01 19:29:52 +01:00
|
|
|
unsigned int input_device;
|
|
|
|
|
unsigned int output_device;
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_sample_spec ss;
|
2006-04-27 05:39:11 +00:00
|
|
|
pa_channel_map map;
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_modargs *ma = NULL;
|
2017-03-01 19:29:52 +01:00
|
|
|
const char *input_device_name = NULL;
|
|
|
|
|
const char *output_device_name = NULL;
|
2006-01-10 17:51:06 +00:00
|
|
|
unsigned int i;
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(m);
|
|
|
|
|
pa_assert(m->core);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
2006-08-18 21:38:40 +00:00
|
|
|
pa_log("failed to parse module arguments.");
|
2006-01-10 17:51:06 +00:00
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-01 19:29:53 +01:00
|
|
|
/* Check whether deprecated arguments have been used. */
|
|
|
|
|
if (pa_modargs_get_value(ma, "device", NULL) != NULL || pa_modargs_get_value(ma, "device_name", NULL) != NULL) {
|
|
|
|
|
pa_log("device and device_name are no longer supported. Please use input_device, input_device_name, output_device and output_device_name.");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
|
2006-08-18 21:38:40 +00:00
|
|
|
pa_log("record= and playback= expect boolean argument.");
|
2006-01-10 17:51:06 +00:00
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!playback && !record) {
|
2006-08-18 21:38:40 +00:00
|
|
|
pa_log("neither playback nor record enabled for device.");
|
2006-01-10 17:51:06 +00:00
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-01 19:29:52 +01:00
|
|
|
/* Set the output_device to be opened. If set output_device_name is used,
|
|
|
|
|
* else output_device if set and lastly WAVE_MAPPER is the default */
|
|
|
|
|
output_device = WAVE_MAPPER;
|
|
|
|
|
if (pa_modargs_get_value_u32(ma, "output_device", &output_device) < 0) {
|
|
|
|
|
pa_log("failed to parse output_device argument");
|
2007-02-12 09:37:34 +00:00
|
|
|
goto fail;
|
|
|
|
|
}
|
2017-03-01 19:29:52 +01:00
|
|
|
if ((output_device_name = pa_modargs_get_value(ma, "output_device_name", NULL)) != NULL) {
|
|
|
|
|
unsigned int num_output_devices = waveOutGetNumDevs();
|
|
|
|
|
for (i = 0; i < num_output_devices; i++) {
|
2011-02-27 09:53:59 +01:00
|
|
|
if (waveOutGetDevCaps(i, &pwoc, sizeof(pwoc)) == MMSYSERR_NOERROR)
|
2017-03-01 19:29:52 +01:00
|
|
|
if (strcmp(output_device_name, pwoc.szPname) == 0)
|
2011-02-27 09:53:59 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2017-03-01 19:29:52 +01:00
|
|
|
if (i < num_output_devices)
|
|
|
|
|
output_device = i;
|
2011-02-27 09:53:59 +01:00
|
|
|
else {
|
2017-03-01 19:29:52 +01:00
|
|
|
pa_log("output_device not found: %s", output_device_name);
|
2011-02-27 09:53:59 +01:00
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-01 19:29:52 +01:00
|
|
|
if (waveOutGetDevCaps(output_device, &pwoc, sizeof(pwoc)) == MMSYSERR_NOERROR)
|
|
|
|
|
output_device_name = pwoc.szPname;
|
2011-02-27 09:53:59 +01:00
|
|
|
else
|
2017-03-01 19:29:52 +01:00
|
|
|
output_device_name = "unknown";
|
|
|
|
|
|
|
|
|
|
/* Set the input_device to be opened. If set input_device_name is used,
|
|
|
|
|
* else input_device if set and lastly WAVE_MAPPER is the default */
|
|
|
|
|
input_device = WAVE_MAPPER;
|
|
|
|
|
if (pa_modargs_get_value_u32(ma, "input_device", &input_device) < 0) {
|
|
|
|
|
pa_log("failed to parse input_device argument");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
if ((input_device_name = pa_modargs_get_value(ma, "input_device_name", NULL)) != NULL) {
|
|
|
|
|
unsigned int num_input_devices = waveInGetNumDevs();
|
|
|
|
|
for (i = 0; i < num_input_devices; i++) {
|
|
|
|
|
if (waveInGetDevCaps(i, &pwic, sizeof(pwic)) == MMSYSERR_NOERROR)
|
|
|
|
|
if (strcmp(input_device_name, pwic.szPname) == 0)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (i < num_input_devices)
|
|
|
|
|
input_device = i;
|
|
|
|
|
else {
|
|
|
|
|
pa_log("input_device not found: %s", input_device_name);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (waveInGetDevCaps(input_device, &pwic, sizeof(pwic)) == MMSYSERR_NOERROR)
|
|
|
|
|
input_device_name = pwic.szPname;
|
|
|
|
|
else
|
|
|
|
|
input_device_name = "unknown";
|
|
|
|
|
|
2007-02-12 09:37:34 +00:00
|
|
|
|
2006-04-19 11:55:46 +00:00
|
|
|
nfrags = 5;
|
|
|
|
|
frag_size = 8192;
|
2006-01-10 17:51:06 +00:00
|
|
|
if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
|
2006-08-18 21:38:40 +00:00
|
|
|
pa_log("failed to parse fragments arguments");
|
2006-01-10 17:51:06 +00:00
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
ss = m->core->default_sample_spec;
|
2006-05-17 06:58:58 +00:00
|
|
|
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_WAVEEX) < 0) {
|
2006-08-18 21:38:40 +00:00
|
|
|
pa_log("failed to parse sample specification");
|
2006-01-10 17:51:06 +00:00
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ss_to_waveformat(&ss, &wf) < 0)
|
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
|
u = pa_xmalloc(sizeof(struct userdata));
|
|
|
|
|
|
|
|
|
|
if (record) {
|
2017-03-01 19:29:52 +01:00
|
|
|
result = waveInOpen(&hwi, input_device, &wf, 0, 0, WAVE_FORMAT_DIRECT | WAVE_FORMAT_QUERY);
|
2011-03-12 19:41:07 +01:00
|
|
|
if (result != MMSYSERR_NOERROR) {
|
|
|
|
|
pa_log_warn("Sample spec not supported by WaveIn, falling back to default sample rate.");
|
|
|
|
|
ss.rate = wf.nSamplesPerSec = m->core->default_sample_spec.rate;
|
|
|
|
|
}
|
2017-03-01 19:29:52 +01:00
|
|
|
result = waveInOpen(&hwi, input_device, &wf, (DWORD_PTR) chunk_ready_cb, (DWORD_PTR) u, CALLBACK_FUNCTION);
|
2011-03-12 19:41:07 +01:00
|
|
|
if (result != MMSYSERR_NOERROR) {
|
|
|
|
|
char errortext[MAXERRORLENGTH];
|
|
|
|
|
pa_log("Failed to open WaveIn.");
|
|
|
|
|
if (waveInGetErrorText(result, errortext, sizeof(errortext)) == MMSYSERR_NOERROR)
|
|
|
|
|
pa_log("Error: %s", errortext);
|
2006-01-10 17:51:06 +00:00
|
|
|
goto fail;
|
2006-08-22 15:20:57 +00:00
|
|
|
}
|
|
|
|
|
if (waveInStart(hwi) != MMSYSERR_NOERROR) {
|
|
|
|
|
pa_log("failed to start waveIn");
|
2006-01-10 17:51:06 +00:00
|
|
|
goto fail;
|
2006-08-22 15:20:57 +00:00
|
|
|
}
|
2006-01-10 17:51:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (playback) {
|
2017-03-01 19:29:52 +01:00
|
|
|
result = waveOutOpen(&hwo, output_device, &wf, 0, 0, WAVE_FORMAT_DIRECT | WAVE_FORMAT_QUERY);
|
2011-03-12 19:41:07 +01:00
|
|
|
if (result != MMSYSERR_NOERROR) {
|
|
|
|
|
pa_log_warn("Sample spec not supported by WaveOut, falling back to default sample rate.");
|
|
|
|
|
ss.rate = wf.nSamplesPerSec = m->core->default_sample_spec.rate;
|
|
|
|
|
}
|
2017-03-01 19:29:52 +01:00
|
|
|
result = waveOutOpen(&hwo, output_device, &wf, (DWORD_PTR) chunk_done_cb, (DWORD_PTR) u, CALLBACK_FUNCTION);
|
2011-03-12 19:41:07 +01:00
|
|
|
if (result != MMSYSERR_NOERROR) {
|
|
|
|
|
char errortext[MAXERRORLENGTH];
|
|
|
|
|
pa_log("Failed to open WaveOut.");
|
|
|
|
|
if (waveOutGetErrorText(result, errortext, sizeof(errortext)) == MMSYSERR_NOERROR)
|
|
|
|
|
pa_log("Error: %s", errortext);
|
2006-01-10 17:51:06 +00:00
|
|
|
goto fail;
|
2006-08-22 15:20:57 +00:00
|
|
|
}
|
2006-01-10 17:51:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InitializeCriticalSection(&u->crit);
|
|
|
|
|
|
|
|
|
|
if (hwi != INVALID_HANDLE_VALUE) {
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_source_new_data data;
|
|
|
|
|
pa_source_new_data_init(&data);
|
|
|
|
|
data.driver = __FILE__;
|
|
|
|
|
data.module = m;
|
|
|
|
|
pa_source_new_data_set_sample_spec(&data, &ss);
|
|
|
|
|
pa_source_new_data_set_channel_map(&data, &map);
|
|
|
|
|
pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME));
|
2017-03-01 19:29:52 +01:00
|
|
|
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "WaveIn on %s", input_device_name);
|
2011-01-12 15:02:18 +01:00
|
|
|
u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
|
|
|
|
|
pa_source_new_data_done(&data);
|
|
|
|
|
|
|
|
|
|
pa_assert(u->source);
|
2006-01-10 17:51:06 +00:00
|
|
|
u->source->userdata = u;
|
2011-01-12 15:02:18 +01:00
|
|
|
u->source->parent.process_msg = process_msg;
|
2006-01-10 17:51:06 +00:00
|
|
|
} else
|
|
|
|
|
u->source = NULL;
|
|
|
|
|
|
|
|
|
|
if (hwo != INVALID_HANDLE_VALUE) {
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_sink_new_data data;
|
|
|
|
|
pa_sink_new_data_init(&data);
|
|
|
|
|
data.driver = __FILE__;
|
|
|
|
|
data.module = m;
|
|
|
|
|
pa_sink_new_data_set_sample_spec(&data, &ss);
|
|
|
|
|
pa_sink_new_data_set_channel_map(&data, &map);
|
|
|
|
|
pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME));
|
2017-03-01 19:29:52 +01:00
|
|
|
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "WaveOut on %s", output_device_name);
|
2011-01-12 15:02:18 +01:00
|
|
|
u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
|
|
|
|
|
pa_sink_new_data_done(&data);
|
|
|
|
|
|
|
|
|
|
pa_assert(u->sink);
|
2011-07-17 15:29:29 +01:00
|
|
|
pa_sink_set_get_volume_callback(u->sink, sink_get_volume_cb);
|
|
|
|
|
pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
|
2006-01-10 17:51:06 +00:00
|
|
|
u->sink->userdata = u;
|
2011-01-12 15:02:18 +01:00
|
|
|
u->sink->parent.process_msg = process_msg;
|
2006-01-10 17:51:06 +00:00
|
|
|
} else
|
|
|
|
|
u->sink = NULL;
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(u->source || u->sink);
|
|
|
|
|
pa_modargs_free(ma);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
u->core = m->core;
|
2006-01-10 17:51:06 +00:00
|
|
|
u->hwi = hwi;
|
|
|
|
|
u->hwo = hwo;
|
|
|
|
|
|
|
|
|
|
u->fragments = nfrags;
|
|
|
|
|
u->free_ifrags = u->fragments;
|
|
|
|
|
u->free_ofrags = u->fragments;
|
|
|
|
|
u->fragment_size = frag_size - (frag_size % pa_frame_size(&ss));
|
|
|
|
|
|
|
|
|
|
u->written_bytes = 0;
|
2006-08-22 16:15:47 +00:00
|
|
|
u->sink_underflow = 1;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2006-04-19 11:55:46 +00:00
|
|
|
u->poll_timeout = pa_bytes_to_usec(u->fragments * u->fragment_size / 10, &ss);
|
2011-03-14 10:51:11 +01:00
|
|
|
pa_log_debug("Poll timeout = %.1f ms", (double) u->poll_timeout / PA_USEC_PER_MSEC);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
u->cur_ihdr = 0;
|
|
|
|
|
u->cur_ohdr = 0;
|
|
|
|
|
u->ihdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(u->ihdrs);
|
2006-01-10 17:51:06 +00:00
|
|
|
u->ohdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(u->ohdrs);
|
2011-03-11 15:41:29 +01:00
|
|
|
for (i = 0; i < u->fragments; i++) {
|
2006-01-10 17:51:06 +00:00
|
|
|
u->ihdrs[i].dwBufferLength = u->fragment_size;
|
|
|
|
|
u->ohdrs[i].dwBufferLength = u->fragment_size;
|
|
|
|
|
u->ihdrs[i].lpData = pa_xmalloc(u->fragment_size);
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(u->ihdrs);
|
2006-01-10 17:51:06 +00:00
|
|
|
u->ohdrs[i].lpData = pa_xmalloc(u->fragment_size);
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(u->ohdrs);
|
2006-01-10 17:51:06 +00:00
|
|
|
}
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
u->module = m;
|
|
|
|
|
m->userdata = u;
|
|
|
|
|
|
2006-02-23 09:28:39 +00:00
|
|
|
/* Read mixer settings */
|
|
|
|
|
if (u->sink)
|
2011-01-12 15:02:18 +01:00
|
|
|
sink_get_volume_cb(u->sink);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
u->rtpoll = pa_rtpoll_new();
|
2016-09-13 18:43:38 +03:00
|
|
|
|
|
|
|
|
if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) {
|
|
|
|
|
pa_log("pa_thread_mq_init() failed.");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
if (u->sink) {
|
|
|
|
|
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
|
|
|
|
|
pa_sink_set_rtpoll(u->sink, u->rtpoll);
|
|
|
|
|
}
|
|
|
|
|
if (u->source) {
|
|
|
|
|
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
|
|
|
|
|
pa_source_set_rtpoll(u->source, u->rtpoll);
|
|
|
|
|
}
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-03-11 15:41:29 +01:00
|
|
|
if (!(u->thread = pa_thread_new("waveout", thread_func, u))) {
|
|
|
|
|
pa_log("Failed to create thread.");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (u->sink)
|
|
|
|
|
pa_sink_put(u->sink);
|
|
|
|
|
if (u->source)
|
|
|
|
|
pa_source_put(u->source);
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
return 0;
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
fail:
|
2006-01-10 17:51:06 +00:00
|
|
|
if (ma)
|
|
|
|
|
pa_modargs_free(ma);
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
pa__done(m);
|
|
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
void pa__done(pa_module *m) {
|
2006-01-10 17:51:06 +00:00
|
|
|
struct userdata *u;
|
|
|
|
|
unsigned int i;
|
|
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_assert(m);
|
|
|
|
|
pa_assert(m->core);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
|
|
|
|
if (!(u = m->userdata))
|
|
|
|
|
return;
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
if (u->sink)
|
|
|
|
|
pa_sink_unlink(u->sink);
|
|
|
|
|
if (u->source)
|
|
|
|
|
pa_source_unlink(u->source);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
|
|
|
|
|
if (u->thread)
|
2011-03-11 15:41:29 +01:00
|
|
|
pa_thread_free(u->thread);
|
2011-01-12 15:02:18 +01:00
|
|
|
pa_thread_mq_done(&u->thread_mq);
|
2006-01-10 17:51:06 +00:00
|
|
|
|
2011-01-12 15:02:18 +01:00
|
|
|
if (u->sink)
|
2006-01-10 17:51:06 +00:00
|
|
|
pa_sink_unref(u->sink);
|
2011-01-12 15:02:18 +01:00
|
|
|
if (u->source)
|
2006-01-10 17:51:06 +00:00
|
|
|
pa_source_unref(u->source);
|
2011-01-12 15:02:18 +01:00
|
|
|
|
|
|
|
|
if (u->rtpoll)
|
|
|
|
|
pa_rtpoll_free(u->rtpoll);
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
if (u->hwi != INVALID_HANDLE_VALUE) {
|
|
|
|
|
waveInReset(u->hwi);
|
|
|
|
|
waveInClose(u->hwi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (u->hwo != INVALID_HANDLE_VALUE) {
|
|
|
|
|
waveOutReset(u->hwo);
|
|
|
|
|
waveOutClose(u->hwo);
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-11 15:41:29 +01:00
|
|
|
for (i = 0; i < u->fragments; i++) {
|
2006-01-10 17:51:06 +00:00
|
|
|
pa_xfree(u->ihdrs[i].lpData);
|
|
|
|
|
pa_xfree(u->ohdrs[i].lpData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_xfree(u->ihdrs);
|
|
|
|
|
pa_xfree(u->ohdrs);
|
|
|
|
|
|
|
|
|
|
DeleteCriticalSection(&u->crit);
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2006-01-10 17:51:06 +00:00
|
|
|
pa_xfree(u);
|
|
|
|
|
}
|