mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-22 05:27:20 +00:00
[client] audio: use block comments
This commit is contained in:
parent
7c2d493bb5
commit
7efc274e81
@ -108,9 +108,9 @@ typedef struct
|
|||||||
RingBuffer timings;
|
RingBuffer timings;
|
||||||
GraphHandle graph;
|
GraphHandle graph;
|
||||||
|
|
||||||
// These two structs contain data specifically for use in the device and
|
/* These two structs contain data specifically for use in the device and
|
||||||
// Spice data threads respectively. Keep them on separate cache lines to
|
* Spice data threads respectively. Keep them on separate cache lines to
|
||||||
// avoid false sharing
|
* avoid false sharing. */
|
||||||
alignas(64) PlaybackDeviceData deviceData;
|
alignas(64) PlaybackDeviceData deviceData;
|
||||||
alignas(64) PlaybackSpiceData spiceData;
|
alignas(64) PlaybackSpiceData spiceData;
|
||||||
}
|
}
|
||||||
@ -222,9 +222,9 @@ static int playbackPullFrames(uint8_t * dst, int frames)
|
|||||||
{
|
{
|
||||||
if (audio.playback.state == STREAM_STATE_SETUP_DEVICE)
|
if (audio.playback.state == STREAM_STATE_SETUP_DEVICE)
|
||||||
{
|
{
|
||||||
// If necessary, slew backwards to play silence until we reach the target
|
/* If necessary, slew backwards to play silence until we reach the target
|
||||||
// startup latency. This avoids underrunning the buffer if the audio
|
* startup latency. This avoids underrunning the buffer if the audio
|
||||||
// device starts earlier than required
|
* device starts earlier than required. */
|
||||||
int offset = ringbuffer_getCount(audio.playback.buffer) -
|
int offset = ringbuffer_getCount(audio.playback.buffer) -
|
||||||
audio.playback.targetStartFrames;
|
audio.playback.targetStartFrames;
|
||||||
if (offset < 0)
|
if (offset < 0)
|
||||||
@ -245,14 +245,14 @@ static int playbackPullFrames(uint8_t * dst, int frames)
|
|||||||
if (init)
|
if (init)
|
||||||
data->nextTime = now + llrint(newPeriodSec * 1.0e9);
|
data->nextTime = now + llrint(newPeriodSec * 1.0e9);
|
||||||
else
|
else
|
||||||
// Due to the double-buffered nature of audio playback, we are filling
|
/* Due to the double-buffered nature of audio playback, we are filling
|
||||||
// in the next buffer while the device is playing the previous buffer.
|
* in the next buffer while the device is playing the previous buffer.
|
||||||
// This results in slightly unintuitive behaviour when the period size
|
* This results in slightly unintuitive behaviour when the period size
|
||||||
// changes. The device will request enough samples for the new period
|
* changes. The device will request enough samples for the new period
|
||||||
// size, but won't call us again until the previous buffer at the old
|
* size, but won't call us again until the previous buffer at the old
|
||||||
// size has finished playing. So, to avoid a blip in the timing
|
* size has finished playing. So, to avoid a blip in the timing
|
||||||
// calculations, we must set the estimated next wakeup time based upon
|
* calculations, we must set the estimated next wakeup time based upon
|
||||||
// the previous period size, not the new one
|
* the previous period size, not the new one. */
|
||||||
data->nextTime += llrint(data->periodSec * 1.0e9);
|
data->nextTime += llrint(data->periodSec * 1.0e9);
|
||||||
|
|
||||||
data->periodFrames = frames;
|
data->periodFrames = frames;
|
||||||
@ -517,59 +517,59 @@ void audio_playbackData(uint8_t * data, size_t size)
|
|||||||
spiceData->devNextPosition = deviceTick.nextPosition;
|
spiceData->devNextPosition = deviceTick.nextPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the target latency. This is made up of three components:
|
/* Determine the target latency. This is made up of three components:
|
||||||
// 1. Half the Spice period. This is necessary due to the way qemu handles
|
* 1. Half the Spice period. This is necessary due to the way qemu handles
|
||||||
// audio. Data is not sent as soon as it is produced by the virtual sound
|
* audio. Data is not sent as soon as it is produced by the virtual sound
|
||||||
// card; instead, qemu polls for new data every ~10ms. This results in a
|
* card; instead, qemu polls for new data every ~10ms. This results in a
|
||||||
// sawtooth pattern in the packet timing as it drifts in and out of phase
|
* sawtooth pattern in the packet timing as it drifts in and out of phase
|
||||||
// with the virtual device. LG measures the average progression of the
|
* with the virtual device. LG measures the average progression of the
|
||||||
// Spice clock, so sees the packet timing error drift by half a period
|
* Spice clock, so sees the packet timing error drift by half a period
|
||||||
// above and below the measured clock. We need to account for this in the
|
* above and below the measured clock. We need to account for this in the
|
||||||
// target latency to avoid underrunning.
|
* target latency to avoid underrunning.
|
||||||
// 2. The maximum audio device period, plus a little extra to absorb timing
|
* 2. The maximum audio device period, plus a little extra to absorb timing
|
||||||
// jitter.
|
* jitter.
|
||||||
// 3. A configurable additional buffer period. The default value is set high
|
* 3. A configurable additional buffer period. The default value is set high
|
||||||
// enough to absorb typical timing jitter from Spice, which can be quite
|
* enough to absorb typical timing jitter from Spice, which can be quite
|
||||||
// significant. Users may reduce this if they care more about latency than
|
* significant. Users may reduce this if they care more about latency than
|
||||||
// audio quality.
|
* audio quality. */
|
||||||
int configLatencyMs = max(g_params.audioBufferLatency, 0);
|
int configLatencyMs = max(g_params.audioBufferLatency, 0);
|
||||||
double targetLatencyFrames =
|
double targetLatencyFrames =
|
||||||
spiceData->periodFrames / 2.0 +
|
spiceData->periodFrames / 2.0 +
|
||||||
audio.playback.deviceMaxPeriodFrames * 1.1 +
|
audio.playback.deviceMaxPeriodFrames * 1.1 +
|
||||||
configLatencyMs * audio.playback.sampleRate / 1000.0;
|
configLatencyMs * audio.playback.sampleRate / 1000.0;
|
||||||
|
|
||||||
// If the device is currently at a lower period size than its maximum (which
|
/* If the device is currently at a lower period size than its maximum (which
|
||||||
// can happen, for example, if another application has requested a lower
|
* can happen, for example, if another application has requested a lower
|
||||||
// latency) then we need to take that into account in our target latency.
|
* latency) then we need to take that into account in our target latency.
|
||||||
//
|
*
|
||||||
// The reason to do this is not necessarily obvious, since we already set the
|
* The reason to do this is not necessarily obvious, since we already set the
|
||||||
// target latency based upon the maximum period size. The problem stems from
|
* target latency based upon the maximum period size. The problem stems from
|
||||||
// the way the device changes the period size. When the period size is
|
* the way the device changes the period size. When the period size is
|
||||||
// reduced, there will be a transitional period where `playbackPullFrames` is
|
* reduced, there will be a transitional period where `playbackPullFrames` is
|
||||||
// invoked with the new smaller period size, but the time until the next
|
* invoked with the new smaller period size, but the time until the next
|
||||||
// invocation is based upon the previous size. This happens because the device
|
* invocation is based upon the previous size. This happens because the device
|
||||||
// is preparing the next small buffer while still playing back the previous
|
* is preparing the next small buffer while still playing back the previous
|
||||||
// large buffer. The result of this is that we end up with a surplus of data
|
* large buffer. The result of this is that we end up with a surplus of data
|
||||||
// in the ring buffer. The overall latency is unchanged, but the balance has
|
* in the ring buffer. The overall latency is unchanged, but the balance has
|
||||||
// shifted: there is more data in our ring buffer and less in the device
|
* shifted: there is more data in our ring buffer and less in the device
|
||||||
// buffer.
|
* buffer.
|
||||||
//
|
*
|
||||||
// Unaccounted for, this would be detected as an offset error and playback
|
* Unaccounted for, this would be detected as an offset error and playback
|
||||||
// would be sped up to bring things back in line. In isolation, this is not
|
* would be sped up to bring things back in line. In isolation, this is not
|
||||||
// inherently problematic, and may even be desirable because it would reduce
|
* inherently problematic, and may even be desirable because it would reduce
|
||||||
// the overall latency. The real problem occurs when the period size goes back
|
* the overall latency. The real problem occurs when the period size goes back
|
||||||
// up.
|
* up.
|
||||||
//
|
*
|
||||||
// When the period size increases, the exact opposite happens. The device will
|
* When the period size increases, the exact opposite happens. The device will
|
||||||
// suddenly request data at the new period size, but the timing interval will
|
* suddenly request data at the new period size, but the timing interval will
|
||||||
// be based upon the previous period size during the transition. If there is
|
* be based upon the previous period size during the transition. If there is
|
||||||
// not enough data to satisfy this then playback will start severely
|
* not enough data to satisfy this then playback will start severely
|
||||||
// underrunning until the timing loop can correct for the error.
|
* underrunning until the timing loop can correct for the error.
|
||||||
//
|
*
|
||||||
// To counteract this issue, if the current period size is smaller than the
|
* To counteract this issue, if the current period size is smaller than the
|
||||||
// maximum period size then we increase the target latency by the difference.
|
* maximum period size then we increase the target latency by the difference.
|
||||||
// This keeps the offset error stable and ensures we have enough data in the
|
* This keeps the offset error stable and ensures we have enough data in the
|
||||||
// buffer to absorb rate increases.
|
* buffer to absorb rate increases. */
|
||||||
if (spiceData->devPeriodFrames != 0 &&
|
if (spiceData->devPeriodFrames != 0 &&
|
||||||
spiceData->devPeriodFrames < audio.playback.deviceMaxPeriodFrames)
|
spiceData->devPeriodFrames < audio.playback.deviceMaxPeriodFrames)
|
||||||
targetLatencyFrames +=
|
targetLatencyFrames +=
|
||||||
@ -600,10 +600,10 @@ void audio_playbackData(uint8_t * data, size_t size)
|
|||||||
double error = (now - spiceData->nextTime) * 1.0e-9;
|
double error = (now - spiceData->nextTime) * 1.0e-9;
|
||||||
if (fabs(error) >= 0.2 || audio.playback.state == STREAM_STATE_KEEP_ALIVE)
|
if (fabs(error) >= 0.2 || audio.playback.state == STREAM_STATE_KEEP_ALIVE)
|
||||||
{
|
{
|
||||||
// Clock error is too high or we are starting a new playback; slew the
|
/* Clock error is too high or we are starting a new playback; slew the
|
||||||
// write pointer and reset the timing parameters to get back in sync. If
|
* write pointer and reset the timing parameters to get back in sync. If
|
||||||
// we know the device playback position then we can slew directly to the
|
* we know the device playback position then we can slew directly to the
|
||||||
// target latency, otherwise just slew based upon the error amount
|
* target latency, otherwise just slew based upon the error amount */
|
||||||
int slewFrames;
|
int slewFrames;
|
||||||
if (spiceData->devLastTime != INT64_MIN)
|
if (spiceData->devLastTime != INT64_MIN)
|
||||||
{
|
{
|
||||||
@ -649,11 +649,11 @@ void audio_playbackData(uint8_t * data, size_t size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measure the offset between the Spice position and the device position,
|
/* Measure the offset between the Spice position and the device position,
|
||||||
// and how far away this is from the target latency. We use this to adjust
|
* and how far away this is from the target latency. We use this to adjust
|
||||||
// the playback speed to bring them back in line. This value can change
|
* the playback speed to bring them back in line. This value can change
|
||||||
// quite rapidly, particularly at the start of playback, so filter it to
|
* quite rapidly, particularly at the start of playback, so filter it to
|
||||||
// avoid sudden pitch shifts which will be noticeable to the user.
|
* avoid sudden pitch shifts which will be noticeable to the user. */
|
||||||
double actualOffset = 0.0;
|
double actualOffset = 0.0;
|
||||||
double offsetError = spiceData->offsetError;
|
double offsetError = spiceData->offsetError;
|
||||||
if (spiceData->devLastTime != INT64_MIN)
|
if (spiceData->devLastTime != INT64_MIN)
|
||||||
@ -712,22 +712,22 @@ void audio_playbackData(uint8_t * data, size_t size)
|
|||||||
|
|
||||||
if (audio.playback.state == STREAM_STATE_SETUP_SPICE)
|
if (audio.playback.state == STREAM_STATE_SETUP_SPICE)
|
||||||
{
|
{
|
||||||
// Latency corrections at startup can be quite significant due to poor
|
/* Latency corrections at startup can be quite significant due to poor
|
||||||
// packet pacing from Spice, so require at least two full Spice periods'
|
* packet pacing from Spice, so require at least two full Spice periods'
|
||||||
// worth of data in addition to the startup delay requested by the device
|
* worth of data in addition to the startup delay requested by the device
|
||||||
// before starting playback to minimise the chances of underrunning
|
* before starting playback to minimise the chances of underrunning. */
|
||||||
int startFrames =
|
int startFrames =
|
||||||
spiceData->periodFrames * 2 + audio.playback.deviceStartFrames;
|
spiceData->periodFrames * 2 + audio.playback.deviceStartFrames;
|
||||||
audio.playback.targetStartFrames = startFrames;
|
audio.playback.targetStartFrames = startFrames;
|
||||||
|
|
||||||
// The actual time between opening the device and the device starting to
|
/* The actual time between opening the device and the device starting to
|
||||||
// pull data can range anywhere between nearly instant and hundreds of
|
* pull data can range anywhere between nearly instant and hundreds of
|
||||||
// milliseconds. To minimise startup latency, we open the device
|
* milliseconds. To minimise startup latency, we open the device
|
||||||
// immediately. If the device starts earlier than required (as per the
|
* immediately. If the device starts earlier than required (as per the
|
||||||
// `startFrames` value we just calculated), then a period of silence will be
|
* `startFrames` value we just calculated), then a period of silence will be
|
||||||
// inserted at the beginning of playback to avoid underrunning. If it starts
|
* inserted at the beginning of playback to avoid underrunning. If it starts
|
||||||
// later, then we just accept the higher latency and let the adaptive
|
* later, then we just accept the higher latency and let the adaptive
|
||||||
// resampling deal with it
|
* resampling deal with it. */
|
||||||
audio.playback.state = STREAM_STATE_SETUP_DEVICE;
|
audio.playback.state = STREAM_STATE_SETUP_DEVICE;
|
||||||
audio.audioDev->playback.start();
|
audio.audioDev->playback.start();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user