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
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;
/**
* 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;
/**
* The return value of {@link #WaitForSingleObject(long, long)} which
* indicates that the specified object is signaled.
*/
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 char WAVE_FORMAT_PCM = 1;
......@@ -302,6 +324,23 @@ public static native long PSPropertyKeyFromString(String pszString)
public static native void ResetEvent(long hEvent)
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(
long hHandle,
long dwMilliseconds)
......
......@@ -816,6 +816,11 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
}
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;
logger.error("WaitForSingleObject", hre);
}
......@@ -823,7 +828,7 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
* If the function WaitForSingleObject fails once, it will very
* likely fail forever. Bail out of a possible busy wait.
*/
if (wfso == WAIT_FAILED)
if ((wfso == WAIT_FAILED) || (wfso == WAIT_ABANDONED))
break;
}
while (true);
......
......@@ -60,12 +60,6 @@ public class PortAudioRenderer
*/
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.
*/
......@@ -311,7 +305,7 @@ public void willPaUpdateAvailableDeviceList()
* <tt>paTimedOut</tt> and/or Windows Multimedia reporting
* <tt>MMSYSERR_NODRIVER</tt> (may) indicate abnormal functioning.
*/
private long writeIsMalfunctioningSince = -1;
private long writeIsMalfunctioningSince = DiagnosticsControl.NEVER;
/**
* Initializes a new <tt>PortAudioRenderer</tt> instance.
......@@ -366,7 +360,7 @@ public synchronized void close()
started = false;
flags &= ~(FLAG_OPEN | FLAG_STARTED);
if (writeIsMalfunctioningSince != NEVER)
if (writeIsMalfunctioningSince != DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false);
}
catch (PortAudioException paex)
......@@ -668,7 +662,7 @@ private void doOpen()
* framesPerBuffer;
// Pa_WriteStream has not been invoked yet.
if (writeIsMalfunctioningSince != NEVER)
if (writeIsMalfunctioningSince != DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false);
}
}
......@@ -743,7 +737,7 @@ public int process(Buffer buffer)
* The execution is somewhat abnormal but it is not because of a
* malfunction in Pa_WriteStream.
*/
if (writeIsMalfunctioningSince != NEVER)
if (writeIsMalfunctioningSince != DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false);
return BUFFER_PROCESSED_OK;
......@@ -788,14 +782,14 @@ public int process(Buffer buffer)
if (errorCode == Pa.paNoError)
{
// Pa_WriteStream appears to function normally.
if (writeIsMalfunctioningSince != NEVER)
if (writeIsMalfunctioningSince != DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false);
}
else if ((Pa.paTimedOut == errorCode)
|| (Pa.HostApiTypeId.paMME.equals(hostApiType)
&& (Pa.MMSYSERR_NODRIVER == errorCode)))
{
if (writeIsMalfunctioningSince == NEVER)
if (writeIsMalfunctioningSince == DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(true);
yield = true;
}
......@@ -899,21 +893,21 @@ public void setLocator(MediaLocator locator)
/**
* 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>
*/
private void setWriteIsMalfunctioning(boolean malfunctioning)
private void setWriteIsMalfunctioning(boolean writeIsMalfunctioning)
{
if (malfunctioning)
if (writeIsMalfunctioning)
{
if (writeIsMalfunctioningSince == NEVER)
if (writeIsMalfunctioningSince == DiagnosticsControl.NEVER)
{
writeIsMalfunctioningSince = System.currentTimeMillis();
PortAudioSystem.monitorFunctionalHealth(diagnosticsControl);
}
}
else
writeIsMalfunctioningSince = NEVER;
writeIsMalfunctioningSince = DiagnosticsControl.NEVER;
}
/**
......@@ -953,7 +947,7 @@ public synchronized void stop()
flags &= ~FLAG_STARTED;
bufferLeft = null;
if (writeIsMalfunctioningSince != NEVER)
if (writeIsMalfunctioningSince != DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false);
}
catch (PortAudioException paex)
......
......@@ -16,6 +16,7 @@
import javax.media.*;
import javax.media.format.*;
import org.jitsi.impl.neomedia.control.*;
import org.jitsi.impl.neomedia.device.*;
import org.jitsi.impl.neomedia.jmfext.media.protocol.wasapi.*;
import org.jitsi.service.neomedia.*;
......@@ -50,6 +51,11 @@ public class WASAPIRenderer
private static final String PLUGIN_NAME
= "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
* this instance, {@link #iAudioClient} and {@link #iAudioRenderClient} is
......@@ -162,6 +168,23 @@ public class WASAPIRenderer
*/
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
* playback (as opposed to sound a notification).
......@@ -352,6 +375,7 @@ public synchronized void open()
int sampleRate = (int) format.getSampleRate();
bufferDuration = numBufferFrames * 1000L / sampleRate;
/*
* We will very likely be inefficient if we fail to
* synchronize with the scheduling period of the audio
......@@ -359,9 +383,6 @@ public synchronized void open()
*/
if (devicePeriod <= 1)
{
long bufferDuration
= numBufferFrames * 1000L / sampleRate;
devicePeriod = bufferDuration / 2;
if ((devicePeriod
> WASAPISystem.DEFAULT_DEVICE_PERIOD)
......@@ -388,6 +409,10 @@ public synchronized void open()
*/
remainderLength = remainder.length;
writeIsMalfunctioningSince = DiagnosticsControl.NEVER;
writeIsMalfunctioningTimeout
= 2 * Math.max(bufferDuration, devicePeriod);
this.eventHandle = eventHandle;
eventHandle = 0;
this.iAudioClient = iAudioClient;
......@@ -572,6 +597,14 @@ public int process(Buffer buffer)
*/
ret |= INPUT_BUFFER_NOT_CONSUMED;
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
{
......@@ -598,11 +631,28 @@ public int process(Buffer buffer)
buffer.setOffset(offset + toCopy);
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
{
ret |= INPUT_BUFFER_NOT_CONSUMED;
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)
// We have consumed frames from remainder.
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)
written = 0;
logger.error("IAudioRenderClient_Write", hre);
}
popFromRemainder(written);
if (written != 0)
{
popFromRemainder(written);
if (writeIsMalfunctioningSince
!= DiagnosticsControl.NEVER)
setWriteIsMalfunctioning(false);
}
}
}
finally
......@@ -919,6 +1011,11 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
}
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;
logger.error("WaitForSingleObject", hre);
}
......@@ -926,7 +1023,7 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
* If the function WaitForSingleObject fails once, it will very
* likely fail forever. Bail out of a possible busy wait.
*/
if (wfso == WAIT_FAILED)
if ((wfso == WAIT_FAILED) || (wfso == WAIT_ABANDONED))
break;
}
while (true);
......@@ -963,6 +1060,26 @@ public Format setInputFormat(Format format)
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}
*/
......@@ -1076,6 +1193,8 @@ public synchronized void stop()
started = false;
waitWhileEventHandleCmd();
writeIsMalfunctioningSince = DiagnosticsControl.NEVER;
}
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