winepulse: Don't rely on pulseaudio callbacks for timing.
Contains contributions from Mark Harmstone and Zhiyi Zhang. Some devices, especially USB devices, have very irregular timing or very long periods. Some Windows applications (The Witcher 3) are built to expect regular, short period times, and so would underrun when the device timing didn't match their expectations. This patch decouples the Windows-side timing from the OS-side timing so we can meet those applications' requirements. Signed-off-by: Andrew Eikum <aeikum@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
parent
30596feb03
commit
a10824a03c
|
@ -172,13 +172,16 @@ struct ACImpl {
|
||||||
EDataFlow dataflow;
|
EDataFlow dataflow;
|
||||||
DWORD flags;
|
DWORD flags;
|
||||||
AUDCLNT_SHAREMODE share;
|
AUDCLNT_SHAREMODE share;
|
||||||
HANDLE event;
|
HANDLE event, timer;
|
||||||
|
|
||||||
INT32 locked;
|
INT32 locked;
|
||||||
UINT32 bufsize_frames, bufsize_bytes, capture_period, pad, started, peek_ofs, wri_offs_bytes, lcl_offs_bytes;
|
UINT32 bufsize_frames, real_bufsize_bytes, period_bytes;
|
||||||
UINT32 tmp_buffer_bytes, held_bytes, peek_len, peek_buffer_len;
|
UINT32 started, peek_ofs, read_offs_bytes, lcl_offs_bytes, pa_offs_bytes;
|
||||||
|
UINT32 tmp_buffer_bytes, held_bytes, peek_len, peek_buffer_len, pa_held_bytes;
|
||||||
BYTE *local_buffer, *tmp_buffer, *peek_buffer;
|
BYTE *local_buffer, *tmp_buffer, *peek_buffer;
|
||||||
void *locked_ptr;
|
void *locked_ptr;
|
||||||
|
BOOL please_quit, just_started, just_underran;
|
||||||
|
pa_usec_t last_time, mmdev_period_usec;
|
||||||
|
|
||||||
pa_stream *stream;
|
pa_stream *stream;
|
||||||
pa_sample_spec ss;
|
pa_sample_spec ss;
|
||||||
|
@ -709,18 +712,7 @@ static void silence_buffer(pa_sample_format_t format, BYTE *buffer, UINT32 bytes
|
||||||
memset(buffer, format == PA_SAMPLE_U8 ? 0x80 : 0, bytes);
|
memset(buffer, format == PA_SAMPLE_U8 ? 0x80 : 0, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pulse_free_noop(void *buf)
|
static int write_buffer(const ACImpl *This, BYTE *buffer, UINT32 bytes)
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
enum write_buffer_flags
|
|
||||||
{
|
|
||||||
WINEPULSE_WRITE_NOFREE = 0x01,
|
|
||||||
WINEPULSE_WRITE_SILENT = 0x02
|
|
||||||
};
|
|
||||||
|
|
||||||
static int write_buffer(const ACImpl *This, BYTE *buffer, UINT32 bytes,
|
|
||||||
enum write_buffer_flags flags)
|
|
||||||
{
|
{
|
||||||
float vol[PA_CHANNELS_MAX];
|
float vol[PA_CHANNELS_MAX];
|
||||||
BOOL adjust = FALSE;
|
BOOL adjust = FALSE;
|
||||||
|
@ -728,7 +720,7 @@ static int write_buffer(const ACImpl *This, BYTE *buffer, UINT32 bytes,
|
||||||
BYTE *end;
|
BYTE *end;
|
||||||
|
|
||||||
if (!bytes) return 0;
|
if (!bytes) return 0;
|
||||||
if (This->session->mute || (flags & WINEPULSE_WRITE_SILENT))
|
if (This->session->mute)
|
||||||
{
|
{
|
||||||
silence_buffer(This->ss.format, buffer, bytes);
|
silence_buffer(This->ss.format, buffer, bytes);
|
||||||
goto write;
|
goto write;
|
||||||
|
@ -855,9 +847,7 @@ static int write_buffer(const ACImpl *This, BYTE *buffer, UINT32 bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
write:
|
write:
|
||||||
return pa_stream_write(This->stream, buffer, bytes,
|
return pa_stream_write(This->stream, buffer, bytes, NULL, 0, PA_SEEK_RELATIVE);
|
||||||
(flags & WINEPULSE_WRITE_NOFREE) ? pulse_free_noop : NULL,
|
|
||||||
0, PA_SEEK_RELATIVE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dump_attr(const pa_buffer_attr *attr) {
|
static void dump_attr(const pa_buffer_attr *attr) {
|
||||||
|
@ -880,147 +870,129 @@ static void pulse_attr_update(pa_stream *s, void *user) {
|
||||||
dump_attr(attr);
|
dump_attr(attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Here's the buffer setup:
|
static void pulse_write(ACImpl *This)
|
||||||
*
|
|
||||||
* vvvvvvvv sent to HW already
|
|
||||||
* vvvvvvvv in Pulse buffer but rewindable
|
|
||||||
* [dddddddddddddddd] Pulse buffer
|
|
||||||
* [dddddddddddddddd--------] mmdevapi buffer
|
|
||||||
* ^^^^^^^^^^^^^^^^ pad
|
|
||||||
* ^ lcl_offs_bytes
|
|
||||||
* ^^^^^^^^^ held_bytes
|
|
||||||
* ^ wri_offs_bytes
|
|
||||||
*
|
|
||||||
* GetCurrentPadding is pad
|
|
||||||
*
|
|
||||||
* During pulse_wr_callback, we decrement pad, fill Pulse buffer, and move
|
|
||||||
* lcl_offs forward
|
|
||||||
*
|
|
||||||
* During Stop, we flush the Pulse buffer
|
|
||||||
*/
|
|
||||||
static void pulse_wr_callback(pa_stream *s, size_t bytes, void *userdata)
|
|
||||||
{
|
{
|
||||||
ACImpl *This = userdata;
|
/* write as much data to PA as we can */
|
||||||
UINT32 oldpad = This->pad;
|
UINT32 to_write;
|
||||||
|
BYTE *buf = This->local_buffer + This->pa_offs_bytes;
|
||||||
|
UINT32 bytes = pa_stream_writable_size(This->stream);
|
||||||
|
|
||||||
if(This->local_buffer){
|
if(This->just_underran){
|
||||||
UINT32 to_write;
|
/* prebuffer with silence if needed */
|
||||||
BYTE *buf = This->local_buffer + This->lcl_offs_bytes;
|
if(This->pa_held_bytes < bytes){
|
||||||
|
to_write = bytes - This->pa_held_bytes;
|
||||||
if(This->pad > bytes){
|
TRACE("prebuffering %u frames of silence\n",
|
||||||
This->clock_written += bytes;
|
(int)(to_write / pa_frame_size(&This->ss)));
|
||||||
This->pad -= bytes;
|
buf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, to_write);
|
||||||
}else{
|
pa_stream_write(This->stream, buf, to_write, NULL, 0, PA_SEEK_RELATIVE);
|
||||||
This->clock_written += This->pad;
|
HeapFree(GetProcessHeap(), 0, buf);
|
||||||
This->pad = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes = min(bytes, This->held_bytes);
|
This->just_underran = FALSE;
|
||||||
|
|
||||||
if(This->lcl_offs_bytes + bytes > This->bufsize_bytes){
|
|
||||||
to_write = This->bufsize_bytes - This->lcl_offs_bytes;
|
|
||||||
TRACE("writing small chunk of %u bytes\n", to_write);
|
|
||||||
write_buffer(This, buf, to_write, 0);
|
|
||||||
This->held_bytes -= to_write;
|
|
||||||
to_write = bytes - to_write;
|
|
||||||
This->lcl_offs_bytes = 0;
|
|
||||||
buf = This->local_buffer;
|
|
||||||
}else
|
|
||||||
to_write = bytes;
|
|
||||||
|
|
||||||
TRACE("writing main chunk of %u bytes\n", to_write);
|
|
||||||
write_buffer(This, buf, to_write, 0);
|
|
||||||
This->lcl_offs_bytes += to_write;
|
|
||||||
This->lcl_offs_bytes %= This->bufsize_bytes;
|
|
||||||
This->held_bytes -= to_write;
|
|
||||||
}else{
|
|
||||||
if (bytes < This->bufsize_bytes)
|
|
||||||
This->pad = This->bufsize_bytes - bytes;
|
|
||||||
else
|
|
||||||
This->pad = 0;
|
|
||||||
|
|
||||||
if (oldpad == This->pad)
|
|
||||||
return;
|
|
||||||
|
|
||||||
assert(oldpad > This->pad);
|
|
||||||
|
|
||||||
This->clock_written += oldpad - This->pad;
|
|
||||||
TRACE("New pad: %zu (-%zu)\n", This->pad / pa_frame_size(&This->ss), (oldpad - This->pad) / pa_frame_size(&This->ss));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (This->event)
|
buf = This->local_buffer + This->pa_offs_bytes;
|
||||||
SetEvent(This->event);
|
TRACE("held: %u, avail: %u\n",
|
||||||
|
This->pa_held_bytes, bytes);
|
||||||
|
bytes = min(This->pa_held_bytes, bytes);
|
||||||
|
|
||||||
|
if(This->pa_offs_bytes + bytes > This->real_bufsize_bytes){
|
||||||
|
to_write = This->real_bufsize_bytes - This->pa_offs_bytes;
|
||||||
|
TRACE("writing small chunk of %u bytes\n", to_write);
|
||||||
|
write_buffer(This, buf, to_write);
|
||||||
|
This->pa_held_bytes -= to_write;
|
||||||
|
to_write = bytes - to_write;
|
||||||
|
This->pa_offs_bytes = 0;
|
||||||
|
buf = This->local_buffer;
|
||||||
|
}else
|
||||||
|
to_write = bytes;
|
||||||
|
|
||||||
|
TRACE("writing main chunk of %u bytes\n", to_write);
|
||||||
|
write_buffer(This, buf, to_write);
|
||||||
|
This->pa_offs_bytes += to_write;
|
||||||
|
This->pa_offs_bytes %= This->real_bufsize_bytes;
|
||||||
|
This->pa_held_bytes -= to_write;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pulse_underflow_callback(pa_stream *s, void *userdata)
|
static void pulse_underflow_callback(pa_stream *s, void *userdata)
|
||||||
{
|
|
||||||
WARN("Underflow\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Latency is periodically updated even when nothing is played,
|
|
||||||
* because of PA_STREAM_AUTO_TIMING_UPDATE so use it as timer
|
|
||||||
*
|
|
||||||
* Perfect for passing all tests :)
|
|
||||||
*/
|
|
||||||
static void pulse_latency_callback(pa_stream *s, void *userdata)
|
|
||||||
{
|
{
|
||||||
ACImpl *This = userdata;
|
ACImpl *This = userdata;
|
||||||
if (!This->pad && This->event)
|
WARN("%p: Underflow\n", userdata);
|
||||||
SetEvent(This->event);
|
This->just_underran = TRUE;
|
||||||
|
/* re-sync */
|
||||||
|
This->pa_offs_bytes = This->lcl_offs_bytes;
|
||||||
|
This->pa_held_bytes = This->held_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pulse_started_callback(pa_stream *s, void *userdata)
|
static void pulse_started_callback(pa_stream *s, void *userdata)
|
||||||
{
|
{
|
||||||
TRACE("(Re)started playing\n");
|
TRACE("%p: (Re)started playing\n", userdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pulse_rd_loop(ACImpl *This, size_t bytes)
|
static void pulse_read(ACImpl *This)
|
||||||
{
|
{
|
||||||
while (bytes >= This->capture_period) {
|
size_t bytes = pa_stream_readable_size(This->stream);
|
||||||
ACPacket *p, *next;
|
|
||||||
LARGE_INTEGER stamp, freq;
|
TRACE("Readable total: %zu, fragsize: %u\n", bytes, pa_stream_get_buffer_attr(This->stream)->fragsize);
|
||||||
BYTE *dst, *src;
|
|
||||||
size_t src_len, copy, rem = This->capture_period;
|
bytes += This->peek_len - This->peek_ofs;
|
||||||
if (!(p = (ACPacket*)list_head(&This->packet_free_head))) {
|
|
||||||
p = (ACPacket*)list_head(&This->packet_filled_head);
|
while (bytes >= This->period_bytes) {
|
||||||
if (!p->discont) {
|
BYTE *dst = NULL, *src;
|
||||||
next = (ACPacket*)p->entry.next;
|
size_t src_len, copy, rem = This->period_bytes;
|
||||||
next->discont = 1;
|
|
||||||
} else
|
if (This->started) {
|
||||||
p = (ACPacket*)list_tail(&This->packet_filled_head);
|
LARGE_INTEGER stamp, freq;
|
||||||
assert(This->pad == This->bufsize_bytes);
|
ACPacket *p, *next;
|
||||||
} else {
|
|
||||||
assert(This->pad < This->bufsize_bytes);
|
if (!(p = (ACPacket*)list_head(&This->packet_free_head))) {
|
||||||
This->pad += This->capture_period;
|
p = (ACPacket*)list_head(&This->packet_filled_head);
|
||||||
assert(This->pad <= This->bufsize_bytes);
|
if (!p) return;
|
||||||
}
|
if (!p->discont) {
|
||||||
QueryPerformanceCounter(&stamp);
|
next = (ACPacket*)p->entry.next;
|
||||||
QueryPerformanceFrequency(&freq);
|
next->discont = 1;
|
||||||
p->qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart;
|
} else
|
||||||
p->discont = 0;
|
p = (ACPacket*)list_tail(&This->packet_filled_head);
|
||||||
list_remove(&p->entry);
|
} else {
|
||||||
list_add_tail(&This->packet_filled_head, &p->entry);
|
This->held_bytes += This->period_bytes;
|
||||||
|
}
|
||||||
|
QueryPerformanceCounter(&stamp);
|
||||||
|
QueryPerformanceFrequency(&freq);
|
||||||
|
p->qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart;
|
||||||
|
p->discont = 0;
|
||||||
|
list_remove(&p->entry);
|
||||||
|
list_add_tail(&This->packet_filled_head, &p->entry);
|
||||||
|
|
||||||
|
dst = p->data;
|
||||||
|
}
|
||||||
|
|
||||||
dst = p->data;
|
|
||||||
while (rem) {
|
while (rem) {
|
||||||
if (This->peek_len) {
|
if (This->peek_len) {
|
||||||
copy = min(rem, This->peek_len - This->peek_ofs);
|
copy = min(rem, This->peek_len - This->peek_ofs);
|
||||||
|
|
||||||
memcpy(dst, This->peek_buffer + This->peek_ofs, copy);
|
if (dst) {
|
||||||
|
memcpy(dst, This->peek_buffer + This->peek_ofs, copy);
|
||||||
|
dst += copy;
|
||||||
|
}
|
||||||
|
|
||||||
rem -= copy;
|
rem -= copy;
|
||||||
dst += copy;
|
|
||||||
This->peek_ofs += copy;
|
This->peek_ofs += copy;
|
||||||
if(This->peek_len == This->peek_ofs)
|
if(This->peek_len == This->peek_ofs)
|
||||||
This->peek_len = 0;
|
This->peek_len = This->peek_ofs = 0;
|
||||||
} else {
|
|
||||||
pa_stream_peek(This->stream, (const void**)&src, &src_len);
|
} else if (pa_stream_peek(This->stream, (const void**)&src, &src_len) == 0 && src_len) {
|
||||||
|
|
||||||
copy = min(rem, src_len);
|
copy = min(rem, src_len);
|
||||||
|
|
||||||
memcpy(dst, src, rem);
|
if (dst) {
|
||||||
|
if(src)
|
||||||
|
memcpy(dst, src, copy);
|
||||||
|
else
|
||||||
|
silence_buffer(This->ss.format, dst, copy);
|
||||||
|
|
||||||
|
dst += copy;
|
||||||
|
}
|
||||||
|
|
||||||
dst += copy;
|
|
||||||
rem -= copy;
|
rem -= copy;
|
||||||
|
|
||||||
if (copy < src_len) {
|
if (copy < src_len) {
|
||||||
|
@ -1030,7 +1002,11 @@ static void pulse_rd_loop(ACImpl *This, size_t bytes)
|
||||||
This->peek_buffer_len = src_len;
|
This->peek_buffer_len = src_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(This->peek_buffer, src + copy, src_len - copy);
|
if(src)
|
||||||
|
memcpy(This->peek_buffer, src + copy, src_len - copy);
|
||||||
|
else
|
||||||
|
silence_buffer(This->ss.format, This->peek_buffer, src_len - copy);
|
||||||
|
|
||||||
This->peek_len = src_len - copy;
|
This->peek_len = src_len - copy;
|
||||||
This->peek_ofs = 0;
|
This->peek_ofs = 0;
|
||||||
}
|
}
|
||||||
|
@ -1039,56 +1015,100 @@ static void pulse_rd_loop(ACImpl *This, size_t bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes -= This->capture_period;
|
bytes -= This->period_bytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pulse_rd_drop(ACImpl *This, size_t bytes)
|
static DWORD WINAPI pulse_timer_cb(void *user)
|
||||||
{
|
{
|
||||||
while (bytes >= This->capture_period) {
|
DWORD delay;
|
||||||
size_t src_len, copy, rem = This->capture_period;
|
UINT32 adv_bytes;
|
||||||
while (rem) {
|
ACImpl *This = user;
|
||||||
const void *src;
|
int success;
|
||||||
pa_stream_peek(This->stream, &src, &src_len);
|
pa_operation *o;
|
||||||
assert(src_len);
|
|
||||||
assert(This->peek_ofs < src_len);
|
|
||||||
src_len -= This->peek_ofs;
|
|
||||||
assert(src_len <= bytes);
|
|
||||||
|
|
||||||
copy = rem;
|
pthread_mutex_lock(&pulse_lock);
|
||||||
if (copy > src_len)
|
delay = This->mmdev_period_usec / 1000;
|
||||||
copy = src_len;
|
pa_stream_get_time(This->stream, &This->last_time);
|
||||||
|
pthread_mutex_unlock(&pulse_lock);
|
||||||
|
|
||||||
src_len -= copy;
|
while(!This->please_quit){
|
||||||
rem -= copy;
|
pa_usec_t now, adv_usec = 0;
|
||||||
|
int err;
|
||||||
|
|
||||||
if (!src_len) {
|
Sleep(delay);
|
||||||
This->peek_ofs = 0;
|
|
||||||
pa_stream_drop(This->stream);
|
pthread_mutex_lock(&pulse_lock);
|
||||||
} else
|
|
||||||
This->peek_ofs += copy;
|
delay = This->mmdev_period_usec / 1000;
|
||||||
bytes -= copy;
|
|
||||||
|
o = pa_stream_update_timing_info(This->stream, pulse_op_cb, &success);
|
||||||
|
if (o)
|
||||||
|
{
|
||||||
|
while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
|
||||||
|
pthread_cond_wait(&pulse_cond, &pulse_lock);
|
||||||
|
pa_operation_unref(o);
|
||||||
}
|
}
|
||||||
|
err = pa_stream_get_time(This->stream, &now);
|
||||||
|
if(err == 0){
|
||||||
|
TRACE("got now: %s, last time: %s\n", wine_dbgstr_longlong(now), wine_dbgstr_longlong(This->last_time));
|
||||||
|
if(This->started && (This->dataflow == eCapture || This->held_bytes)){
|
||||||
|
if(This->just_underran){
|
||||||
|
This->last_time = now;
|
||||||
|
This->just_started = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(This->just_started){
|
||||||
|
/* let it play out a period to absorb some latency and get accurate timing */
|
||||||
|
pa_usec_t diff = now - This->last_time;
|
||||||
|
|
||||||
|
if(diff > This->mmdev_period_usec){
|
||||||
|
This->just_started = FALSE;
|
||||||
|
This->last_time = now;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
INT32 adjust = This->last_time + This->mmdev_period_usec - now;
|
||||||
|
|
||||||
|
adv_usec = now - This->last_time;
|
||||||
|
|
||||||
|
if(adjust > ((INT32)(This->mmdev_period_usec / 2)))
|
||||||
|
adjust = This->mmdev_period_usec / 2;
|
||||||
|
else if(adjust < -((INT32)(This->mmdev_period_usec / 2)))
|
||||||
|
adjust = -1 * This->mmdev_period_usec / 2;
|
||||||
|
|
||||||
|
delay = (This->mmdev_period_usec + adjust) / 1000;
|
||||||
|
|
||||||
|
This->last_time += This->mmdev_period_usec;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(This->dataflow == eRender){
|
||||||
|
pulse_write(This);
|
||||||
|
|
||||||
|
/* regardless of what PA does, advance one period */
|
||||||
|
adv_bytes = min(This->period_bytes, This->held_bytes);
|
||||||
|
This->lcl_offs_bytes += adv_bytes;
|
||||||
|
This->lcl_offs_bytes %= This->real_bufsize_bytes;
|
||||||
|
This->held_bytes -= adv_bytes;
|
||||||
|
}else if(This->dataflow == eCapture){
|
||||||
|
pulse_read(This);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
This->last_time = now;
|
||||||
|
delay = This->mmdev_period_usec / 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (This->event)
|
||||||
|
SetEvent(This->event);
|
||||||
|
|
||||||
|
TRACE("%p after update, adv usec: %d, held: %u, delay: %u\n",
|
||||||
|
This, (int)adv_usec,
|
||||||
|
(int)(This->held_bytes/ pa_frame_size(&This->ss)), delay);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&pulse_lock);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void pulse_rd_callback(pa_stream *s, size_t bytes, void *userdata)
|
return 0;
|
||||||
{
|
|
||||||
ACImpl *This = userdata;
|
|
||||||
|
|
||||||
TRACE("Readable total: %zu, fragsize: %u\n", bytes, pa_stream_get_buffer_attr(s)->fragsize);
|
|
||||||
assert(bytes >= This->peek_ofs);
|
|
||||||
bytes -= This->peek_ofs;
|
|
||||||
if (bytes < This->capture_period)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (This->started)
|
|
||||||
pulse_rd_loop(This, bytes);
|
|
||||||
else
|
|
||||||
pulse_rd_drop(This, bytes);
|
|
||||||
|
|
||||||
if (This->event)
|
|
||||||
SetEvent(This->event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
|
static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
|
||||||
|
@ -1117,15 +1137,16 @@ static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
|
||||||
|
|
||||||
/* PulseAudio will fill in correct values */
|
/* PulseAudio will fill in correct values */
|
||||||
attr.minreq = attr.fragsize = period_bytes;
|
attr.minreq = attr.fragsize = period_bytes;
|
||||||
attr.maxlength = attr.tlength = This->bufsize_bytes;
|
attr.tlength = period_bytes * 3;
|
||||||
|
attr.maxlength = This->bufsize_frames * pa_frame_size(&This->ss);
|
||||||
attr.prebuf = pa_frame_size(&This->ss);
|
attr.prebuf = pa_frame_size(&This->ss);
|
||||||
dump_attr(&attr);
|
dump_attr(&attr);
|
||||||
if (This->dataflow == eRender)
|
if (This->dataflow == eRender)
|
||||||
ret = pa_stream_connect_playback(This->stream, NULL, &attr,
|
ret = pa_stream_connect_playback(This->stream, NULL, &attr,
|
||||||
PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS, NULL, NULL);
|
PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY, NULL, NULL);
|
||||||
else
|
else
|
||||||
ret = pa_stream_connect_record(This->stream, NULL, &attr,
|
ret = pa_stream_connect_record(This->stream, NULL, &attr,
|
||||||
PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS);
|
PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
WARN("Returns %i\n", ret);
|
WARN("Returns %i\n", ret);
|
||||||
return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
|
return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
|
||||||
|
@ -1136,11 +1157,9 @@ static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
|
||||||
return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
|
return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
|
||||||
|
|
||||||
if (This->dataflow == eRender) {
|
if (This->dataflow == eRender) {
|
||||||
pa_stream_set_write_callback(This->stream, pulse_wr_callback, This);
|
|
||||||
pa_stream_set_underflow_callback(This->stream, pulse_underflow_callback, This);
|
pa_stream_set_underflow_callback(This->stream, pulse_underflow_callback, This);
|
||||||
pa_stream_set_started_callback(This->stream, pulse_started_callback, This);
|
pa_stream_set_started_callback(This->stream, pulse_started_callback, This);
|
||||||
} else
|
}
|
||||||
pa_stream_set_read_callback(This->stream, pulse_rd_callback, This);
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1275,6 +1294,11 @@ static ULONG WINAPI AudioClient_Release(IAudioClient *iface)
|
||||||
TRACE("(%p) Refcount now %u\n", This, ref);
|
TRACE("(%p) Refcount now %u\n", This, ref);
|
||||||
if (!ref) {
|
if (!ref) {
|
||||||
if (This->stream) {
|
if (This->stream) {
|
||||||
|
if(This->timer){
|
||||||
|
This->please_quit = TRUE;
|
||||||
|
WaitForSingleObject(This->timer, INFINITE);
|
||||||
|
CloseHandle(This->timer);
|
||||||
|
}
|
||||||
pthread_mutex_lock(&pulse_lock);
|
pthread_mutex_lock(&pulse_lock);
|
||||||
if (PA_STREAM_IS_GOOD(pa_stream_get_state(This->stream))) {
|
if (PA_STREAM_IS_GOOD(pa_stream_get_state(This->stream))) {
|
||||||
pa_stream_disconnect(This->stream);
|
pa_stream_disconnect(This->stream);
|
||||||
|
@ -1570,7 +1594,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface,
|
||||||
{
|
{
|
||||||
ACImpl *This = impl_from_IAudioClient(iface);
|
ACImpl *This = impl_from_IAudioClient(iface);
|
||||||
HRESULT hr = S_OK;
|
HRESULT hr = S_OK;
|
||||||
UINT period_bytes;
|
UINT32 bufsize_bytes;
|
||||||
|
|
||||||
TRACE("(%p)->(%x, %x, %s, %s, %p, %s)\n", This, mode, flags,
|
TRACE("(%p)->(%x, %x, %s, %s, %p, %s)\n", This, mode, flags,
|
||||||
wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid));
|
wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid));
|
||||||
|
@ -1617,38 +1641,19 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface,
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
if (mode == AUDCLNT_SHAREMODE_SHARED) {
|
period = pulse_def_period[This->dataflow == eCapture];
|
||||||
REFERENCE_TIME def = pulse_def_period[This->dataflow == eCapture];
|
if (duration < 3 * period)
|
||||||
REFERENCE_TIME min = pulse_min_period[This->dataflow == eCapture];
|
duration = 3 * period;
|
||||||
|
|
||||||
/* Switch to low latency mode if below 2 default periods,
|
This->period_bytes = pa_frame_size(&This->ss) * MulDiv(period, This->ss.rate, 10000000);
|
||||||
* which is 20 ms by default, this will increase the amount
|
|
||||||
* of interrupts but allows very low latency. In dsound I
|
|
||||||
* managed to get a total latency of ~8ms, which is well below
|
|
||||||
* default
|
|
||||||
*/
|
|
||||||
if (duration < 2 * def)
|
|
||||||
period = min;
|
|
||||||
else
|
|
||||||
period = def;
|
|
||||||
if (duration < 2 * period)
|
|
||||||
duration = 2 * period;
|
|
||||||
|
|
||||||
/* Uh oh, really low latency requested.. */
|
This->bufsize_frames = ceil((duration / 10000000.) * fmt->nSamplesPerSec);
|
||||||
if (duration <= 2 * period)
|
bufsize_bytes = This->bufsize_frames * pa_frame_size(&This->ss);
|
||||||
period /= 2;
|
This->mmdev_period_usec = period / 10;
|
||||||
}
|
|
||||||
period_bytes = pa_frame_size(&This->ss) * MulDiv(period, This->ss.rate, 10000000);
|
|
||||||
|
|
||||||
if (duration < 20000000)
|
|
||||||
This->bufsize_frames = ceil((duration / 10000000.) * fmt->nSamplesPerSec);
|
|
||||||
else
|
|
||||||
This->bufsize_frames = 2 * fmt->nSamplesPerSec;
|
|
||||||
This->bufsize_bytes = This->bufsize_frames * pa_frame_size(&This->ss);
|
|
||||||
|
|
||||||
This->share = mode;
|
This->share = mode;
|
||||||
This->flags = flags;
|
This->flags = flags;
|
||||||
hr = pulse_stream_connect(This, period_bytes);
|
hr = pulse_stream_connect(This, This->period_bytes);
|
||||||
if (SUCCEEDED(hr)) {
|
if (SUCCEEDED(hr)) {
|
||||||
UINT32 unalign;
|
UINT32 unalign;
|
||||||
const pa_buffer_attr *attr = pa_stream_get_buffer_attr(This->stream);
|
const pa_buffer_attr *attr = pa_stream_get_buffer_attr(This->stream);
|
||||||
|
@ -1656,39 +1661,34 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface,
|
||||||
/* Update frames according to new size */
|
/* Update frames according to new size */
|
||||||
dump_attr(attr);
|
dump_attr(attr);
|
||||||
if (This->dataflow == eRender) {
|
if (This->dataflow == eRender) {
|
||||||
if (attr->tlength < This->bufsize_bytes) {
|
This->real_bufsize_bytes = This->bufsize_frames * 2 * pa_frame_size(&This->ss);
|
||||||
TRACE("PulseAudio buffer too small (%u < %u), using tmp buffer\n", attr->tlength, This->bufsize_bytes);
|
This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->real_bufsize_bytes);
|
||||||
|
if(!This->local_buffer)
|
||||||
This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->bufsize_bytes);
|
hr = E_OUTOFMEMORY;
|
||||||
if(!This->local_buffer)
|
|
||||||
hr = E_OUTOFMEMORY;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
UINT32 i, capture_packets;
|
UINT32 i, capture_packets;
|
||||||
|
|
||||||
This->capture_period = period_bytes = attr->fragsize;
|
if ((unalign = bufsize_bytes % This->period_bytes))
|
||||||
if ((unalign = This->bufsize_bytes % period_bytes))
|
bufsize_bytes += This->period_bytes - unalign;
|
||||||
This->bufsize_bytes += period_bytes - unalign;
|
This->bufsize_frames = bufsize_bytes / pa_frame_size(&This->ss);
|
||||||
This->bufsize_frames = This->bufsize_bytes / pa_frame_size(&This->ss);
|
This->real_bufsize_bytes = bufsize_bytes;
|
||||||
|
|
||||||
capture_packets = This->bufsize_bytes / This->capture_period;
|
capture_packets = This->real_bufsize_bytes / This->period_bytes;
|
||||||
|
|
||||||
This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->bufsize_bytes + capture_packets * sizeof(ACPacket));
|
This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->real_bufsize_bytes + capture_packets * sizeof(ACPacket));
|
||||||
if (!This->local_buffer)
|
if (!This->local_buffer)
|
||||||
hr = E_OUTOFMEMORY;
|
hr = E_OUTOFMEMORY;
|
||||||
else {
|
else {
|
||||||
ACPacket *cur_packet = (ACPacket*)((char*)This->local_buffer + This->bufsize_bytes);
|
ACPacket *cur_packet = (ACPacket*)((char*)This->local_buffer + This->real_bufsize_bytes);
|
||||||
BYTE *data = This->local_buffer;
|
BYTE *data = This->local_buffer;
|
||||||
silence_buffer(This->ss.format, This->local_buffer, This->bufsize_bytes);
|
silence_buffer(This->ss.format, This->local_buffer, This->real_bufsize_bytes);
|
||||||
list_init(&This->packet_free_head);
|
list_init(&This->packet_free_head);
|
||||||
list_init(&This->packet_filled_head);
|
list_init(&This->packet_filled_head);
|
||||||
for (i = 0; i < capture_packets; ++i, ++cur_packet) {
|
for (i = 0; i < capture_packets; ++i, ++cur_packet) {
|
||||||
list_add_tail(&This->packet_free_head, &cur_packet->entry);
|
list_add_tail(&This->packet_free_head, &cur_packet->entry);
|
||||||
cur_packet->data = data;
|
cur_packet->data = data;
|
||||||
data += This->capture_period;
|
data += This->period_bytes;
|
||||||
}
|
}
|
||||||
assert(!This->capture_period || This->bufsize_bytes == This->capture_period * capture_packets);
|
|
||||||
assert(!capture_packets || data - This->bufsize_bytes == This->local_buffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1753,12 +1753,12 @@ static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient *iface,
|
||||||
attr = pa_stream_get_buffer_attr(This->stream);
|
attr = pa_stream_get_buffer_attr(This->stream);
|
||||||
if (This->dataflow == eRender){
|
if (This->dataflow == eRender){
|
||||||
lat = attr->minreq / pa_frame_size(&This->ss);
|
lat = attr->minreq / pa_frame_size(&This->ss);
|
||||||
lat += pulse_def_period[0];
|
|
||||||
}else
|
}else
|
||||||
lat = attr->fragsize / pa_frame_size(&This->ss);
|
lat = attr->fragsize / pa_frame_size(&This->ss);
|
||||||
*latency = 10000000;
|
*latency = 10000000;
|
||||||
*latency *= lat;
|
*latency *= lat;
|
||||||
*latency /= This->ss.rate;
|
*latency /= This->ss.rate;
|
||||||
|
*latency += pulse_def_period[0];
|
||||||
pthread_mutex_unlock(&pulse_lock);
|
pthread_mutex_unlock(&pulse_lock);
|
||||||
TRACE("Latency: %u ms\n", (DWORD)(*latency / 10000));
|
TRACE("Latency: %u ms\n", (DWORD)(*latency / 10000));
|
||||||
return S_OK;
|
return S_OK;
|
||||||
|
@ -1766,7 +1766,7 @@ static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient *iface,
|
||||||
|
|
||||||
static void ACImpl_GetRenderPad(ACImpl *This, UINT32 *out)
|
static void ACImpl_GetRenderPad(ACImpl *This, UINT32 *out)
|
||||||
{
|
{
|
||||||
*out = This->pad / pa_frame_size(&This->ss);
|
*out = This->held_bytes / pa_frame_size(&This->ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ACImpl_GetCapturePad(ACImpl *This, UINT32 *out)
|
static void ACImpl_GetCapturePad(ACImpl *This, UINT32 *out)
|
||||||
|
@ -1778,7 +1778,7 @@ static void ACImpl_GetCapturePad(ACImpl *This, UINT32 *out)
|
||||||
list_remove(&packet->entry);
|
list_remove(&packet->entry);
|
||||||
}
|
}
|
||||||
if (out)
|
if (out)
|
||||||
*out = This->pad / pa_frame_size(&This->ss);
|
*out = This->held_bytes / pa_frame_size(&This->ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
static HRESULT WINAPI AudioClient_GetCurrentPadding(IAudioClient *iface,
|
static HRESULT WINAPI AudioClient_GetCurrentPadding(IAudioClient *iface,
|
||||||
|
@ -2024,6 +2024,8 @@ static HRESULT WINAPI AudioClient_Start(IAudioClient *iface)
|
||||||
return AUDCLNT_E_NOT_STOPPED;
|
return AUDCLNT_E_NOT_STOPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pulse_write(This);
|
||||||
|
|
||||||
if (pa_stream_is_corked(This->stream)) {
|
if (pa_stream_is_corked(This->stream)) {
|
||||||
o = pa_stream_cork(This->stream, 0, pulse_op_cb, &success);
|
o = pa_stream_cork(This->stream, 0, pulse_op_cb, &success);
|
||||||
if (o) {
|
if (o) {
|
||||||
|
@ -2038,8 +2040,10 @@ static HRESULT WINAPI AudioClient_Start(IAudioClient *iface)
|
||||||
|
|
||||||
if (SUCCEEDED(hr)) {
|
if (SUCCEEDED(hr)) {
|
||||||
This->started = TRUE;
|
This->started = TRUE;
|
||||||
if (This->dataflow == eRender && This->event)
|
This->just_started = TRUE;
|
||||||
pa_stream_set_latency_update_callback(This->stream, pulse_latency_callback, This);
|
|
||||||
|
if(!This->timer)
|
||||||
|
This->timer = CreateThread(NULL, 0, pulse_timer_cb, This, 0, NULL);
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&pulse_lock);
|
pthread_mutex_unlock(&pulse_lock);
|
||||||
return hr;
|
return hr;
|
||||||
|
@ -2111,7 +2115,7 @@ static HRESULT WINAPI AudioClient_Reset(IAudioClient *iface)
|
||||||
if (This->dataflow == eRender) {
|
if (This->dataflow == eRender) {
|
||||||
/* If there is still data in the render buffer it needs to be removed from the server */
|
/* If there is still data in the render buffer it needs to be removed from the server */
|
||||||
int success = 0;
|
int success = 0;
|
||||||
if (This->pad) {
|
if (This->held_bytes) {
|
||||||
pa_operation *o = pa_stream_flush(This->stream, pulse_op_cb, &success);
|
pa_operation *o = pa_stream_flush(This->stream, pulse_op_cb, &success);
|
||||||
if (o) {
|
if (o) {
|
||||||
while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
|
while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
|
||||||
|
@ -2119,14 +2123,14 @@ static HRESULT WINAPI AudioClient_Reset(IAudioClient *iface)
|
||||||
pa_operation_unref(o);
|
pa_operation_unref(o);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (success || !This->pad){
|
if (success || !This->held_bytes){
|
||||||
This->clock_lastpos = This->clock_written = This->pad = 0;
|
This->clock_lastpos = This->clock_written = 0;
|
||||||
This->wri_offs_bytes = This->lcl_offs_bytes = This->held_bytes = 0;
|
This->pa_offs_bytes = This->lcl_offs_bytes = This->held_bytes = This->pa_held_bytes = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ACPacket *p;
|
ACPacket *p;
|
||||||
This->clock_written += This->pad;
|
This->clock_written += This->held_bytes;
|
||||||
This->pad = 0;
|
This->held_bytes = 0;
|
||||||
|
|
||||||
if ((p = This->locked_ptr)) {
|
if ((p = This->locked_ptr)) {
|
||||||
This->locked_ptr = NULL;
|
This->locked_ptr = NULL;
|
||||||
|
@ -2292,10 +2296,9 @@ static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface,
|
||||||
UINT32 frames, BYTE **data)
|
UINT32 frames, BYTE **data)
|
||||||
{
|
{
|
||||||
ACImpl *This = impl_from_IAudioRenderClient(iface);
|
ACImpl *This = impl_from_IAudioRenderClient(iface);
|
||||||
size_t avail, req, bytes = frames * pa_frame_size(&This->ss);
|
size_t bytes = frames * pa_frame_size(&This->ss);
|
||||||
UINT32 pad;
|
|
||||||
HRESULT hr = S_OK;
|
HRESULT hr = S_OK;
|
||||||
int ret = -1;
|
UINT32 wri_offs_bytes;
|
||||||
|
|
||||||
TRACE("(%p)->(%u, %p)\n", This, frames, data);
|
TRACE("(%p)->(%u, %p)\n", This, frames, data);
|
||||||
|
|
||||||
|
@ -2314,37 +2317,19 @@ static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface,
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
ACImpl_GetRenderPad(This, &pad);
|
if(This->held_bytes / pa_frame_size(&This->ss) + frames > This->bufsize_frames){
|
||||||
avail = This->bufsize_frames - pad;
|
|
||||||
if (avail < frames || bytes > This->bufsize_bytes) {
|
|
||||||
pthread_mutex_unlock(&pulse_lock);
|
pthread_mutex_unlock(&pulse_lock);
|
||||||
WARN("Wanted to write %u, but only %zu available\n", frames, avail);
|
|
||||||
return AUDCLNT_E_BUFFER_TOO_LARGE;
|
return AUDCLNT_E_BUFFER_TOO_LARGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(This->local_buffer){
|
wri_offs_bytes = (This->lcl_offs_bytes + This->held_bytes) % This->real_bufsize_bytes;
|
||||||
if(This->wri_offs_bytes + bytes > This->bufsize_bytes){
|
if(wri_offs_bytes + bytes > This->real_bufsize_bytes){
|
||||||
alloc_tmp_buffer(This, bytes);
|
alloc_tmp_buffer(This, bytes);
|
||||||
*data = This->tmp_buffer;
|
*data = This->tmp_buffer;
|
||||||
This->locked = -frames;
|
This->locked = -bytes;
|
||||||
}else{
|
|
||||||
*data = This->local_buffer + This->wri_offs_bytes;
|
|
||||||
This->locked = frames;
|
|
||||||
}
|
|
||||||
}else{
|
}else{
|
||||||
req = bytes;
|
*data = This->local_buffer + wri_offs_bytes;
|
||||||
ret = pa_stream_begin_write(This->stream, &This->locked_ptr, &req);
|
This->locked = bytes;
|
||||||
if (ret < 0 || req < bytes) {
|
|
||||||
FIXME("%p Not using pulse locked data: %i %zu/%u %u/%u\n", This, ret, req/pa_frame_size(&This->ss), frames, pad, This->bufsize_frames);
|
|
||||||
if (ret >= 0)
|
|
||||||
pa_stream_cancel_write(This->stream);
|
|
||||||
alloc_tmp_buffer(This, bytes);
|
|
||||||
*data = This->tmp_buffer;
|
|
||||||
This->locked_ptr = NULL;
|
|
||||||
} else
|
|
||||||
*data = This->locked_ptr;
|
|
||||||
|
|
||||||
This->locked = frames;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
silence_buffer(This->ss.format, *data, bytes);
|
silence_buffer(This->ss.format, *data, bytes);
|
||||||
|
@ -2356,12 +2341,13 @@ static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface,
|
||||||
|
|
||||||
static void pulse_wrap_buffer(ACImpl *This, BYTE *buffer, UINT32 written_bytes)
|
static void pulse_wrap_buffer(ACImpl *This, BYTE *buffer, UINT32 written_bytes)
|
||||||
{
|
{
|
||||||
UINT32 chunk_bytes = This->bufsize_bytes - This->wri_offs_bytes;
|
UINT32 wri_offs_bytes = (This->lcl_offs_bytes + This->held_bytes) % This->real_bufsize_bytes;
|
||||||
|
UINT32 chunk_bytes = This->real_bufsize_bytes - wri_offs_bytes;
|
||||||
|
|
||||||
if(written_bytes <= chunk_bytes){
|
if(written_bytes <= chunk_bytes){
|
||||||
memcpy(This->local_buffer + This->wri_offs_bytes, buffer, written_bytes);
|
memcpy(This->local_buffer + wri_offs_bytes, buffer, written_bytes);
|
||||||
}else{
|
}else{
|
||||||
memcpy(This->local_buffer + This->wri_offs_bytes, buffer, chunk_bytes);
|
memcpy(This->local_buffer + wri_offs_bytes, buffer, chunk_bytes);
|
||||||
memcpy(This->local_buffer, buffer + chunk_bytes,
|
memcpy(This->local_buffer, buffer + chunk_bytes,
|
||||||
written_bytes - chunk_bytes);
|
written_bytes - chunk_bytes);
|
||||||
}
|
}
|
||||||
|
@ -2372,88 +2358,47 @@ static HRESULT WINAPI AudioRenderClient_ReleaseBuffer(
|
||||||
{
|
{
|
||||||
ACImpl *This = impl_from_IAudioRenderClient(iface);
|
ACImpl *This = impl_from_IAudioRenderClient(iface);
|
||||||
UINT32 written_bytes = written_frames * pa_frame_size(&This->ss);
|
UINT32 written_bytes = written_frames * pa_frame_size(&This->ss);
|
||||||
|
BYTE *buffer;
|
||||||
|
|
||||||
TRACE("(%p)->(%u, %x)\n", This, written_frames, flags);
|
TRACE("(%p)->(%u, %x)\n", This, written_frames, flags);
|
||||||
|
|
||||||
pthread_mutex_lock(&pulse_lock);
|
pthread_mutex_lock(&pulse_lock);
|
||||||
if (!This->locked || !written_frames) {
|
if (!This->locked || !written_frames) {
|
||||||
if (This->locked_ptr)
|
|
||||||
pa_stream_cancel_write(This->stream);
|
|
||||||
This->locked = 0;
|
This->locked = 0;
|
||||||
This->locked_ptr = NULL;
|
|
||||||
pthread_mutex_unlock(&pulse_lock);
|
pthread_mutex_unlock(&pulse_lock);
|
||||||
return written_frames ? AUDCLNT_E_OUT_OF_ORDER : S_OK;
|
return written_frames ? AUDCLNT_E_OUT_OF_ORDER : S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (This->locked < written_frames) {
|
if(written_frames * pa_frame_size(&This->ss) > (This->locked >= 0 ? This->locked : -This->locked)){
|
||||||
pthread_mutex_unlock(&pulse_lock);
|
pthread_mutex_unlock(&pulse_lock);
|
||||||
return AUDCLNT_E_INVALID_SIZE;
|
return AUDCLNT_E_INVALID_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(This->local_buffer){
|
if(This->locked >= 0)
|
||||||
BYTE *buffer;
|
buffer = This->local_buffer + (This->lcl_offs_bytes + This->held_bytes) % This->real_bufsize_bytes;
|
||||||
|
else
|
||||||
|
buffer = This->tmp_buffer;
|
||||||
|
|
||||||
if(This->locked >= 0)
|
if(flags & AUDCLNT_BUFFERFLAGS_SILENT)
|
||||||
buffer = This->local_buffer + This->wri_offs_bytes;
|
silence_buffer(This->ss.format, buffer, written_bytes);
|
||||||
else
|
|
||||||
buffer = This->tmp_buffer;
|
|
||||||
|
|
||||||
if(flags & AUDCLNT_BUFFERFLAGS_SILENT)
|
if(This->locked < 0)
|
||||||
silence_buffer(This->ss.format, buffer, written_bytes);
|
pulse_wrap_buffer(This, buffer, written_bytes);
|
||||||
|
|
||||||
if(This->locked < 0)
|
This->held_bytes += written_bytes;
|
||||||
pulse_wrap_buffer(This, buffer, written_bytes);
|
This->pa_held_bytes += written_bytes;
|
||||||
|
if(This->pa_held_bytes > This->real_bufsize_bytes){
|
||||||
This->wri_offs_bytes += written_bytes;
|
This->pa_offs_bytes += This->pa_held_bytes - This->real_bufsize_bytes;
|
||||||
This->wri_offs_bytes %= This->bufsize_bytes;
|
This->pa_offs_bytes %= This->real_bufsize_bytes;
|
||||||
|
This->pa_held_bytes = This->real_bufsize_bytes;
|
||||||
This->pad += written_bytes;
|
|
||||||
This->held_bytes += written_bytes;
|
|
||||||
|
|
||||||
if(This->held_bytes == This->pad){
|
|
||||||
int e;
|
|
||||||
UINT32 to_write = min(This->attr.tlength, written_bytes);
|
|
||||||
|
|
||||||
/* nothing in PA, so send data immediately */
|
|
||||||
|
|
||||||
TRACE("pre-writing %u bytes\n", to_write);
|
|
||||||
|
|
||||||
e = write_buffer(This, buffer, to_write, 0);
|
|
||||||
if(e)
|
|
||||||
ERR("pa_stream_write failed: 0x%x\n", e);
|
|
||||||
|
|
||||||
This->lcl_offs_bytes += to_write;
|
|
||||||
This->lcl_offs_bytes %= This->bufsize_bytes;
|
|
||||||
This->held_bytes -= to_write;
|
|
||||||
}
|
|
||||||
|
|
||||||
}else{
|
|
||||||
enum write_buffer_flags wr_flags = 0;
|
|
||||||
|
|
||||||
if (flags & AUDCLNT_BUFFERFLAGS_SILENT) wr_flags |= WINEPULSE_WRITE_SILENT;
|
|
||||||
if (!This->locked_ptr) wr_flags |= WINEPULSE_WRITE_NOFREE;
|
|
||||||
|
|
||||||
write_buffer(This, This->locked_ptr ? This->locked_ptr : This->tmp_buffer, written_bytes, wr_flags);
|
|
||||||
This->pad += written_bytes;
|
|
||||||
}
|
}
|
||||||
|
This->clock_written += written_bytes;
|
||||||
if (!pa_stream_is_corked(This->stream)) {
|
|
||||||
int success;
|
|
||||||
pa_operation *o;
|
|
||||||
o = pa_stream_trigger(This->stream, pulse_op_cb, &success);
|
|
||||||
if (o) {
|
|
||||||
while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
|
|
||||||
pthread_cond_wait(&pulse_cond, &pulse_lock);
|
|
||||||
pa_operation_unref(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
This->locked = 0;
|
This->locked = 0;
|
||||||
This->locked_ptr = NULL;
|
|
||||||
TRACE("Released %u, pad %zu\n", written_frames, This->pad / pa_frame_size(&This->ss));
|
TRACE("Released %u, held %zu\n", written_frames, This->held_bytes / pa_frame_size(&This->ss));
|
||||||
assert(This->pad <= This->bufsize_bytes);
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&pulse_lock);
|
pthread_mutex_unlock(&pulse_lock);
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2530,13 +2475,13 @@ static HRESULT WINAPI AudioCaptureClient_GetBuffer(IAudioCaptureClient *iface,
|
||||||
|
|
||||||
ACImpl_GetCapturePad(This, NULL);
|
ACImpl_GetCapturePad(This, NULL);
|
||||||
if ((packet = This->locked_ptr)) {
|
if ((packet = This->locked_ptr)) {
|
||||||
*frames = This->capture_period / pa_frame_size(&This->ss);
|
*frames = This->period_bytes / pa_frame_size(&This->ss);
|
||||||
*flags = 0;
|
*flags = 0;
|
||||||
if (packet->discont)
|
if (packet->discont)
|
||||||
*flags |= AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY;
|
*flags |= AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY;
|
||||||
if (devpos) {
|
if (devpos) {
|
||||||
if (packet->discont)
|
if (packet->discont)
|
||||||
*devpos = (This->clock_written + This->capture_period) / pa_frame_size(&This->ss);
|
*devpos = (This->clock_written + This->period_bytes) / pa_frame_size(&This->ss);
|
||||||
else
|
else
|
||||||
*devpos = This->clock_written / pa_frame_size(&This->ss);
|
*devpos = This->clock_written / pa_frame_size(&This->ss);
|
||||||
}
|
}
|
||||||
|
@ -2570,11 +2515,11 @@ static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer(
|
||||||
if (done) {
|
if (done) {
|
||||||
ACPacket *packet = This->locked_ptr;
|
ACPacket *packet = This->locked_ptr;
|
||||||
This->locked_ptr = NULL;
|
This->locked_ptr = NULL;
|
||||||
This->pad -= This->capture_period;
|
This->held_bytes -= This->period_bytes;
|
||||||
if (packet->discont)
|
if (packet->discont)
|
||||||
This->clock_written += 2 * This->capture_period;
|
This->clock_written += 2 * This->period_bytes;
|
||||||
else
|
else
|
||||||
This->clock_written += This->capture_period;
|
This->clock_written += This->period_bytes;
|
||||||
list_add_tail(&This->packet_free_head, &packet->entry);
|
list_add_tail(&This->packet_free_head, &packet->entry);
|
||||||
}
|
}
|
||||||
This->locked = 0;
|
This->locked = 0;
|
||||||
|
@ -2594,7 +2539,7 @@ static HRESULT WINAPI AudioCaptureClient_GetNextPacketSize(
|
||||||
pthread_mutex_lock(&pulse_lock);
|
pthread_mutex_lock(&pulse_lock);
|
||||||
ACImpl_GetCapturePad(This, NULL);
|
ACImpl_GetCapturePad(This, NULL);
|
||||||
if (This->locked_ptr)
|
if (This->locked_ptr)
|
||||||
*frames = This->capture_period / pa_frame_size(&This->ss);
|
*frames = This->period_bytes / pa_frame_size(&This->ss);
|
||||||
else
|
else
|
||||||
*frames = 0;
|
*frames = 0;
|
||||||
pthread_mutex_unlock(&pulse_lock);
|
pthread_mutex_unlock(&pulse_lock);
|
||||||
|
@ -2686,7 +2631,7 @@ static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos,
|
||||||
return hr;
|
return hr;
|
||||||
}
|
}
|
||||||
|
|
||||||
*pos = This->clock_written;
|
*pos = This->clock_written - This->held_bytes;
|
||||||
|
|
||||||
if (This->share == AUDCLNT_SHAREMODE_EXCLUSIVE)
|
if (This->share == AUDCLNT_SHAREMODE_EXCLUSIVE)
|
||||||
*pos /= pa_frame_size(&This->ss);
|
*pos /= pa_frame_size(&This->ss);
|
||||||
|
|
Loading…
Reference in New Issue