PulseAudio: Hold mainloop lock during GetPosition() call

Mainloop lock needs to be held to access PulseAudio object from outside the
event loop thread, in particular in GetPosition() calls. Otherwise we might hit
a race condition and have the call fail. There's also a possibility that the
call might randomly fail if it's called before first timing data is received
for the stream, so handle that case as well.

Also fix the stream write callback to call Mix() with correct parameters, as
well as do the work in a loop in case pa_stream_begin_write() returns smaller
memory buffer than was requested.
This commit is contained in:
phantom10111
2024-05-11 17:27:29 +02:00
committed by teejusb
parent bbf556450d
commit b365dcd65d
2 changed files with 39 additions and 17 deletions
+36 -16
View File
@@ -307,11 +307,25 @@ void RageSoundDriver_PulseAudio::StreamStateCb(pa_stream *s)
}
std::int64_t RageSoundDriver_PulseAudio::GetPosition() const
{
pa_threaded_mainloop_lock(m_PulseMainLoop);
std::int64_t position = GetPositionUnlocked();
pa_threaded_mainloop_unlock(m_PulseMainLoop);
return position;
}
std::int64_t RageSoundDriver_PulseAudio::GetPositionUnlocked() const
{
pa_usec_t usec;
if(pa_stream_get_time(m_PulseStream, &usec) < 0)
{
RageException::Throw("Pulse: pa_stream_get_time()");
int paErrno = pa_context_errno(m_PulseCtx);
// We might get no data error if the stream has just been started and hasn't received any timing data yet
if(paErrno == PA_ERR_NODATA)
return 0;
else
RageException::Throw("Pulse: pa_stream_get_time() failed: %s", pa_strerror(paErrno));
}
std::size_t length = pa_usec_to_bytes(usec, &m_ss);
@@ -320,23 +334,29 @@ std::int64_t RageSoundDriver_PulseAudio::GetPosition() const
void RageSoundDriver_PulseAudio::StreamWriteCb(pa_stream *s, std::size_t length)
{
void* buf;
if(pa_stream_begin_write(m_PulseStream, &buf, &length) < 0)
std::int64_t curPos = GetPositionUnlocked();
while(length > 0)
{
RageException::Throw("Pulse: pa_stream_begin_write()");
void* buf;
std::size_t bufsize = length;
if(pa_stream_begin_write(m_PulseStream, &buf, &bufsize) < 0)
{
RageException::Throw("Pulse: pa_stream_begin_write() failed: %s", pa_strerror(pa_context_errno(m_PulseCtx)));
}
const std::size_t nbframes = bufsize / sizeof(std::int16_t); /* we use 16-bit frames */
std::int64_t pos1 = m_LastPosition;
std::int64_t pos2 = pos1 + nbframes/2; /* Mix() position in stereo frames */
this->Mix( reinterpret_cast<std::int16_t*>(buf), pos2-pos1, pos1, curPos);
if(pa_stream_write(m_PulseStream, buf, bufsize, nullptr, 0, PA_SEEK_RELATIVE) < 0)
{
RageException::Throw("Pulse: pa_stream_write() failed: %s", pa_strerror(pa_context_errno(m_PulseCtx)));
}
m_LastPosition = pos2;
length -= bufsize;
}
const std::size_t nbframes = length / sizeof(std::int16_t); /* we use 16-bit frames */
std::int64_t pos1 = m_LastPosition;
std::int64_t pos2 = pos1 + nbframes/2; /* Mix() position in stereo frames */
this->Mix( reinterpret_cast<std::int16_t*>(buf), pos2-pos1, pos1, pos2);
if(pa_stream_write(m_PulseStream, buf, length, nullptr, 0, PA_SEEK_RELATIVE) < 0)
{
RageException::Throw("Pulse: pa_stream_write()");
}
m_LastPosition = pos2;
}
/* Static wrappers, because pulseaudio is a C API, it uses callbacks.
+3 -1
View File
@@ -18,10 +18,12 @@ public:
RString Init();
inline std::int64_t GetPosition() const;
std::int64_t GetPosition() const;
inline int GetSampleRate() const { return m_ss.rate; };
protected:
std::int64_t GetPositionUnlocked() const;
std::int64_t m_LastPosition;
pa_sample_spec m_ss;
char *m_Error;