Skip to content
Snippets Groups Projects
Commit ff777a04 authored by Lyubomir Marinov's avatar Lyubomir Marinov
Browse files

Attempts to unblock the media flow/codec chain when a Windows Audio Session...

Attempts to unblock the media flow/codec chain when a Windows Audio Session API (WASAPI) render device appears to be malfunctioning/blocked.
parent c69cd21e
No related branches found
No related tags found
No related merge requests found
...@@ -76,12 +76,34 @@ public class WASAPI ...@@ -76,12 +76,34 @@ public class WASAPI
public static final int STGM_READ = 0x0; public static final int STGM_READ = 0x0;
/**
* The return value of {@link #WaitForSingleObject(long, long)} which
* indicates that the specified object is a mutex that was not released by
* the thread that owned the mutex before the owning thread terminated.
* Ownership of the mutex is granted to the calling thread and the mutex
* state is set to non-signaled.
*/
public static final int WAIT_ABANDONED = 0x00000080; public static final int WAIT_ABANDONED = 0x00000080;
/**
* The return value of {@link #WaitForSingleObject(long, long)} which
* indicates that the function has failed. Normally, the function will throw
* an {@link HResultException} in the case and
* {@link HResultException#getHResult()} will return <tt>WAIT_FAILED</tt>.
*/
public static final int WAIT_FAILED = 0xffffffff; public static final int WAIT_FAILED = 0xffffffff;
/**
* The return value of {@link #WaitForSingleObject(long, long)} which
* indicates that the specified object is signaled.
*/
public static final int WAIT_OBJECT_0 = 0x00000000; public static final int WAIT_OBJECT_0 = 0x00000000;
/**
* The return value of {@link #WaitForSingleObject(long, long)} which
* indicates that the specified time-out interval has elapsed and the state
* of the specified object is non-signaled.
*/
public static final int WAIT_TIMEOUT = 0x00000102; public static final int WAIT_TIMEOUT = 0x00000102;
public static final char WAVE_FORMAT_PCM = 1; public static final char WAVE_FORMAT_PCM = 1;
...@@ -302,6 +324,23 @@ public static native long PSPropertyKeyFromString(String pszString) ...@@ -302,6 +324,23 @@ public static native long PSPropertyKeyFromString(String pszString)
public static native void ResetEvent(long hEvent) public static native void ResetEvent(long hEvent)
throws HResultException; throws HResultException;
/**
* Waits until the specified object is in the signaled state or the
* specified time-out interval elapses.
*
* @param hHandle a <tt>HANDLE</tt> to the object to wait for
* @param dwMilliseconds the time-out interval in milliseconds to wait. If a
* nonzero value is specified, the function waits until the specified object
* is signaled or the specified time-out interval elapses. If
* <tt>dwMilliseconds</tt> is zero, the function does not enter a wait state
* if the specified object is not signaled; it always returns immediately.
* If <tt>dwMilliseconds</tt> is <tt>INFINITE</tt>, the function will return
* only when the specified object is signaled.
* @return one of the <tt>WAIT_XXX</tt> constant values defined by the
* <tt>WASAPI</tt> class to indicate the event that caused the function to
* return
* @throws HResultException if the return value is {@link #WAIT_FAILED}
*/
public static native int WaitForSingleObject( public static native int WaitForSingleObject(
long hHandle, long hHandle,
long dwMilliseconds) long dwMilliseconds)
......
...@@ -816,6 +816,11 @@ private void runInEventHandleCmd(Runnable eventHandleCmd) ...@@ -816,6 +816,11 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
} }
catch (HResultException hre) catch (HResultException hre)
{ {
/*
* WaitForSingleObject will throw HResultException only in
* the case of WAIT_FAILED. Event if it didn't, it would
* still be a failure from our point of view.
*/
wfso = WAIT_FAILED; wfso = WAIT_FAILED;
logger.error("WaitForSingleObject", hre); logger.error("WaitForSingleObject", hre);
} }
...@@ -823,7 +828,7 @@ private void runInEventHandleCmd(Runnable eventHandleCmd) ...@@ -823,7 +828,7 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
* If the function WaitForSingleObject fails once, it will very * If the function WaitForSingleObject fails once, it will very
* likely fail forever. Bail out of a possible busy wait. * likely fail forever. Bail out of a possible busy wait.
*/ */
if (wfso == WAIT_FAILED) if ((wfso == WAIT_FAILED) || (wfso == WAIT_ABANDONED))
break; break;
} }
while (true); while (true);
......
...@@ -60,12 +60,6 @@ public class PortAudioRenderer ...@@ -60,12 +60,6 @@ public class PortAudioRenderer
*/ */
private static final byte FLAG_STARTED = 2; private static final byte FLAG_STARTED = 2;
/**
* The constant which expresses a non-existent time in milliseconds for the
* purposes of {@link #writeIsMalfunctioningSince}.
*/
private static final long NEVER = DiagnosticsControl.NEVER;
/** /**
* The human-readable name of the <tt>PortAudioRenderer</tt> JMF plug-in. * The human-readable name of the <tt>PortAudioRenderer</tt> JMF plug-in.
*/ */
...@@ -311,7 +305,7 @@ public void willPaUpdateAvailableDeviceList() ...@@ -311,7 +305,7 @@ public void willPaUpdateAvailableDeviceList()
* <tt>paTimedOut</tt> and/or Windows Multimedia reporting * <tt>paTimedOut</tt> and/or Windows Multimedia reporting
* <tt>MMSYSERR_NODRIVER</tt> (may) indicate abnormal functioning. * <tt>MMSYSERR_NODRIVER</tt> (may) indicate abnormal functioning.
*/ */
private long writeIsMalfunctioningSince = -1; private long writeIsMalfunctioningSince = DiagnosticsControl.NEVER;
/** /**
* Initializes a new <tt>PortAudioRenderer</tt> instance. * Initializes a new <tt>PortAudioRenderer</tt> instance.
...@@ -366,7 +360,7 @@ public synchronized void close() ...@@ -366,7 +360,7 @@ public synchronized void close()
started = false; started = false;
flags &= ~(FLAG_OPEN | FLAG_STARTED); flags &= ~(FLAG_OPEN | FLAG_STARTED);
if (writeIsMalfunctioningSince != NEVER) if (writeIsMalfunctioningSince != DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false); setWriteIsMalfunctioning(false);
} }
catch (PortAudioException paex) catch (PortAudioException paex)
...@@ -668,7 +662,7 @@ private void doOpen() ...@@ -668,7 +662,7 @@ private void doOpen()
* framesPerBuffer; * framesPerBuffer;
// Pa_WriteStream has not been invoked yet. // Pa_WriteStream has not been invoked yet.
if (writeIsMalfunctioningSince != NEVER) if (writeIsMalfunctioningSince != DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false); setWriteIsMalfunctioning(false);
} }
} }
...@@ -743,7 +737,7 @@ public int process(Buffer buffer) ...@@ -743,7 +737,7 @@ public int process(Buffer buffer)
* The execution is somewhat abnormal but it is not because of a * The execution is somewhat abnormal but it is not because of a
* malfunction in Pa_WriteStream. * malfunction in Pa_WriteStream.
*/ */
if (writeIsMalfunctioningSince != NEVER) if (writeIsMalfunctioningSince != DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false); setWriteIsMalfunctioning(false);
return BUFFER_PROCESSED_OK; return BUFFER_PROCESSED_OK;
...@@ -788,14 +782,14 @@ public int process(Buffer buffer) ...@@ -788,14 +782,14 @@ public int process(Buffer buffer)
if (errorCode == Pa.paNoError) if (errorCode == Pa.paNoError)
{ {
// Pa_WriteStream appears to function normally. // Pa_WriteStream appears to function normally.
if (writeIsMalfunctioningSince != NEVER) if (writeIsMalfunctioningSince != DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false); setWriteIsMalfunctioning(false);
} }
else if ((Pa.paTimedOut == errorCode) else if ((Pa.paTimedOut == errorCode)
|| (Pa.HostApiTypeId.paMME.equals(hostApiType) || (Pa.HostApiTypeId.paMME.equals(hostApiType)
&& (Pa.MMSYSERR_NODRIVER == errorCode))) && (Pa.MMSYSERR_NODRIVER == errorCode)))
{ {
if (writeIsMalfunctioningSince == NEVER) if (writeIsMalfunctioningSince == DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(true); setWriteIsMalfunctioning(true);
yield = true; yield = true;
} }
...@@ -899,21 +893,21 @@ public void setLocator(MediaLocator locator) ...@@ -899,21 +893,21 @@ public void setLocator(MediaLocator locator)
/** /**
* Indicates whether <tt>Pa_WriteStream</tt> is malfunctioning. * Indicates whether <tt>Pa_WriteStream</tt> is malfunctioning.
* *
* @param malfunctioning <tt>true</tt> if <tt>Pa_WriteStream</tt> is * @param writeIsMalfunctioning <tt>true</tt> if <tt>Pa_WriteStream</tt> is
* malfunctioning; otherwise, <tt>false</tt> * malfunctioning; otherwise, <tt>false</tt>
*/ */
private void setWriteIsMalfunctioning(boolean malfunctioning) private void setWriteIsMalfunctioning(boolean writeIsMalfunctioning)
{ {
if (malfunctioning) if (writeIsMalfunctioning)
{ {
if (writeIsMalfunctioningSince == NEVER) if (writeIsMalfunctioningSince == DiagnosticsControl.NEVER)
{ {
writeIsMalfunctioningSince = System.currentTimeMillis(); writeIsMalfunctioningSince = System.currentTimeMillis();
PortAudioSystem.monitorFunctionalHealth(diagnosticsControl); PortAudioSystem.monitorFunctionalHealth(diagnosticsControl);
} }
} }
else else
writeIsMalfunctioningSince = NEVER; writeIsMalfunctioningSince = DiagnosticsControl.NEVER;
} }
/** /**
...@@ -953,7 +947,7 @@ public synchronized void stop() ...@@ -953,7 +947,7 @@ public synchronized void stop()
flags &= ~FLAG_STARTED; flags &= ~FLAG_STARTED;
bufferLeft = null; bufferLeft = null;
if (writeIsMalfunctioningSince != NEVER) if (writeIsMalfunctioningSince != DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false); setWriteIsMalfunctioning(false);
} }
catch (PortAudioException paex) catch (PortAudioException paex)
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
import javax.media.*; import javax.media.*;
import javax.media.format.*; import javax.media.format.*;
import org.jitsi.impl.neomedia.control.*;
import org.jitsi.impl.neomedia.device.*; import org.jitsi.impl.neomedia.device.*;
import org.jitsi.impl.neomedia.jmfext.media.protocol.wasapi.*; import org.jitsi.impl.neomedia.jmfext.media.protocol.wasapi.*;
import org.jitsi.service.neomedia.*; import org.jitsi.service.neomedia.*;
...@@ -50,6 +51,11 @@ public class WASAPIRenderer ...@@ -50,6 +51,11 @@ public class WASAPIRenderer
private static final String PLUGIN_NAME private static final String PLUGIN_NAME
= "Windows Audio Session API (WASAPI) Renderer"; = "Windows Audio Session API (WASAPI) Renderer";
/**
* The duration in milliseconds of the endpoint buffer.
*/
private long bufferDuration;
/** /**
* The indicator which determines whether the audio stream represented by * The indicator which determines whether the audio stream represented by
* this instance, {@link #iAudioClient} and {@link #iAudioRenderClient} is * this instance, {@link #iAudioClient} and {@link #iAudioRenderClient} is
...@@ -162,6 +168,23 @@ public class WASAPIRenderer ...@@ -162,6 +168,23 @@ public class WASAPIRenderer
*/ */
private boolean started; private boolean started;
/**
* The time in milliseconds at which the writing to the render endpoint
* buffer has started malfunctioning. For example, {@link #remainder} being
* full from the point of view of {@link #process(Buffer)} for an extended
* period of time may indicate abnormal functioning.
*/
private long writeIsMalfunctioningSince = DiagnosticsControl.NEVER;
/**
* The maximum interval of time in milliseconds that the writing to the
* render endpoint buffer is allowed to be under suspicion that it is
* malfunctioning. If it remains under suspicion after the maximum interval
* of time has elapsed, the writing to the render endpoing buffer is to be
* considered malfunctioning for real.
*/
private long writeIsMalfunctioningTimeout;
/** /**
* Initializes a new <tt>WASAPIRenderer</tt> instance which is to perform * Initializes a new <tt>WASAPIRenderer</tt> instance which is to perform
* playback (as opposed to sound a notification). * playback (as opposed to sound a notification).
...@@ -352,6 +375,7 @@ public synchronized void open() ...@@ -352,6 +375,7 @@ public synchronized void open()
int sampleRate = (int) format.getSampleRate(); int sampleRate = (int) format.getSampleRate();
bufferDuration = numBufferFrames * 1000L / sampleRate;
/* /*
* We will very likely be inefficient if we fail to * We will very likely be inefficient if we fail to
* synchronize with the scheduling period of the audio * synchronize with the scheduling period of the audio
...@@ -359,9 +383,6 @@ public synchronized void open() ...@@ -359,9 +383,6 @@ public synchronized void open()
*/ */
if (devicePeriod <= 1) if (devicePeriod <= 1)
{ {
long bufferDuration
= numBufferFrames * 1000L / sampleRate;
devicePeriod = bufferDuration / 2; devicePeriod = bufferDuration / 2;
if ((devicePeriod if ((devicePeriod
> WASAPISystem.DEFAULT_DEVICE_PERIOD) > WASAPISystem.DEFAULT_DEVICE_PERIOD)
...@@ -388,6 +409,10 @@ public synchronized void open() ...@@ -388,6 +409,10 @@ public synchronized void open()
*/ */
remainderLength = remainder.length; remainderLength = remainder.length;
writeIsMalfunctioningSince = DiagnosticsControl.NEVER;
writeIsMalfunctioningTimeout
= 2 * Math.max(bufferDuration, devicePeriod);
this.eventHandle = eventHandle; this.eventHandle = eventHandle;
eventHandle = 0; eventHandle = 0;
this.iAudioClient = iAudioClient; this.iAudioClient = iAudioClient;
...@@ -572,6 +597,14 @@ public int process(Buffer buffer) ...@@ -572,6 +597,14 @@ public int process(Buffer buffer)
*/ */
ret |= INPUT_BUFFER_NOT_CONSUMED; ret |= INPUT_BUFFER_NOT_CONSUMED;
sleep = devicePeriod; sleep = devicePeriod;
/*
* The writing to the render endpoint buffer may or may
* not be malfunctioning, it depends on the interval of
* time that the state remains unchanged.
*/
if (writeIsMalfunctioningSince
== DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(true);
} }
else else
{ {
...@@ -598,11 +631,28 @@ public int process(Buffer buffer) ...@@ -598,11 +631,28 @@ public int process(Buffer buffer)
buffer.setOffset(offset + toCopy); buffer.setOffset(offset + toCopy);
ret |= INPUT_BUFFER_NOT_CONSUMED; ret |= INPUT_BUFFER_NOT_CONSUMED;
} }
/*
* Writing from the input Buffer into remainder has
* occurred so it does not look like the writing to
* the render endpoint buffer is malfunctioning.
*/
if (writeIsMalfunctioningSince
!= DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false);
} }
else else
{ {
ret |= INPUT_BUFFER_NOT_CONSUMED; ret |= INPUT_BUFFER_NOT_CONSUMED;
sleep = devicePeriod; sleep = devicePeriod;
/*
* No writing from the input Buffer into remainder
* has occurred so it is possible that the writing
* to the render endpoint buffer is malfunctioning.
*/
if (writeIsMalfunctioningSince
== DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(true);
} }
} }
} }
...@@ -733,6 +783,41 @@ else if (written > 0) ...@@ -733,6 +783,41 @@ else if (written > 0)
// We have consumed frames from remainder. // We have consumed frames from remainder.
popFromRemainder(written); popFromRemainder(written);
} }
if (writeIsMalfunctioningSince
!= DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false);
}
}
/*
* If the writing to the render endpoint buffer is
* malfunctioning, fail the processing of the input Buffer in
* order to avoid blocking of the Codec chain.
*/
if (((ret & INPUT_BUFFER_NOT_CONSUMED)
== INPUT_BUFFER_NOT_CONSUMED)
&& (writeIsMalfunctioningSince
!= DiagnosticsControl.NEVER))
{
long writeIsMalfunctioningDuration
= System.currentTimeMillis()
- writeIsMalfunctioningSince;
if (writeIsMalfunctioningDuration
> writeIsMalfunctioningTimeout)
{
/*
* The writing to the render endpoint buffer has taken
* too long so whatever is in remainder is surely
* out-of-date.
*/
remainderLength = 0;
ret = BUFFER_PROCESSED_FAILED;
logger.warn(
"Audio endpoint device appears to be"
+ " malfunctioning: "
+ getLocator());
} }
} }
} }
...@@ -899,7 +984,14 @@ private void runInEventHandleCmd(Runnable eventHandleCmd) ...@@ -899,7 +984,14 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
written = 0; written = 0;
logger.error("IAudioRenderClient_Write", hre); logger.error("IAudioRenderClient_Write", hre);
} }
popFromRemainder(written); if (written != 0)
{
popFromRemainder(written);
if (writeIsMalfunctioningSince
!= DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false);
}
} }
} }
finally finally
...@@ -919,6 +1011,11 @@ private void runInEventHandleCmd(Runnable eventHandleCmd) ...@@ -919,6 +1011,11 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
} }
catch (HResultException hre) catch (HResultException hre)
{ {
/*
* WaitForSingleObject will throw HResultException only in
* the case of WAIT_FAILED. Event if it didn't, it would
* still be a failure from our point of view.
*/
wfso = WAIT_FAILED; wfso = WAIT_FAILED;
logger.error("WaitForSingleObject", hre); logger.error("WaitForSingleObject", hre);
} }
...@@ -926,7 +1023,7 @@ private void runInEventHandleCmd(Runnable eventHandleCmd) ...@@ -926,7 +1023,7 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
* If the function WaitForSingleObject fails once, it will very * If the function WaitForSingleObject fails once, it will very
* likely fail forever. Bail out of a possible busy wait. * likely fail forever. Bail out of a possible busy wait.
*/ */
if (wfso == WAIT_FAILED) if ((wfso == WAIT_FAILED) || (wfso == WAIT_ABANDONED))
break; break;
} }
while (true); while (true);
...@@ -963,6 +1060,26 @@ public Format setInputFormat(Format format) ...@@ -963,6 +1060,26 @@ public Format setInputFormat(Format format)
return setInputFormat; return setInputFormat;
} }
/**
* Indicates whether the writing to the render endpoint buffer is
* malfunctioning. Keeps track of the time at which the malfunction has
* started.
*
* @param writeIsMalfunctioning <tt>true</tt> if the writing to the render
* endpoint buffer is (believed to be) malfunctioning; otherwise,
* <tt>false</tt>
*/
private void setWriteIsMalfunctioning(boolean writeIsMalfunctioning)
{
if (writeIsMalfunctioning)
{
if (writeIsMalfunctioningSince == DiagnosticsControl.NEVER)
writeIsMalfunctioningSince = System.currentTimeMillis();
}
else
writeIsMalfunctioningSince = DiagnosticsControl.NEVER;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
...@@ -1076,6 +1193,8 @@ public synchronized void stop() ...@@ -1076,6 +1193,8 @@ public synchronized void stop()
started = false; started = false;
waitWhileEventHandleCmd(); waitWhileEventHandleCmd();
writeIsMalfunctioningSince = DiagnosticsControl.NEVER;
} }
catch (HResultException hre) catch (HResultException hre)
{ {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment