aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/alffplay.cpp98
1 files changed, 86 insertions, 12 deletions
diff --git a/examples/alffplay.cpp b/examples/alffplay.cpp
index 3b5de1b9..9cbf7694 100644
--- a/examples/alffplay.cpp
+++ b/examples/alffplay.cpp
@@ -37,6 +37,20 @@ extern "C" {
#include "AL/al.h"
#include "AL/alext.h"
+extern "C" {
+#ifndef ALC_SOFT_device_clock
+#define ALC_SOFT_device_clock 1
+typedef int64_t ALCint64SOFT;
+typedef uint64_t ALCuint64SOFT;
+#define ALC_DEVICE_CLOCK_SOFT 0x1600
+#define ALC_DEVICE_LATENCY_SOFT 0x1601
+#define ALC_DEVICE_CLOCK_LATENCY_SOFT 0x1602
+#define AL_SAMPLE_OFFSET_CLOCK_SOFT 0x1202
+#define AL_SEC_OFFSET_CLOCK_SOFT 0x1203
+typedef void (ALC_APIENTRY*LPALCGETINTEGER64VSOFT)(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values);
+#endif
+} // extern "C"
+
namespace {
using nanoseconds = std::chrono::nanoseconds;
@@ -49,6 +63,7 @@ const std::string AppName("alffplay");
bool EnableDirectOut = false;
LPALGETSOURCEI64VSOFT alGetSourcei64vSOFT;
+LPALCGETINTEGER64VSOFT alcGetInteger64vSOFT;
const seconds AVNoSyncThreshold(10);
@@ -174,6 +189,9 @@ struct AudioState {
/* Time of the next sample to be buffered */
nanoseconds mCurrentPts{0};
+ /* Device clock time that the stream started at. */
+ nanoseconds mDeviceStartTime{nanoseconds::min()};
+
/* Decompressed sample frame, and swresample context for conversion */
AVFramePtr mDecodedFrame;
SwrContextPtr mSwresCtx;
@@ -328,9 +346,29 @@ struct MovieState {
nanoseconds AudioState::getClock()
{
+ auto device = alcGetContextsDevice(alcGetCurrentContext());
std::unique_lock<std::recursive_mutex> lock(mSrcMutex);
- /* The audio clock is the timestamp of the sample currently being heard.
- * It's based on 4 components:
+
+ // The audio clock is the timestamp of the sample currently being heard.
+ if(alcGetInteger64vSOFT)
+ {
+ // If device start time = min, we aren't playing yet.
+ if(mDeviceStartTime == nanoseconds::min())
+ return nanoseconds::zero();
+
+ // Get the current device clock time and latency.
+ ALCint64SOFT devtimes[2]={0,0};
+ alcGetInteger64vSOFT(device, ALC_DEVICE_CLOCK_LATENCY_SOFT, 2, devtimes);
+ auto latency = nanoseconds(devtimes[1]);
+ auto device_time = nanoseconds(devtimes[0]);
+
+ // The clock is simply the current device time relative to the recorded
+ // start time. We can also subtract the latency to get more a accurate
+ // position of where the audio device actually is in the output stream.
+ return device_time - mDeviceStartTime - latency;
+ }
+
+ /* The source-based clock is based on 4 components:
* 1 - The timestamp of the next sample to buffer (mCurrentPts)
* 2 - The length of the source's buffer queue
* (AudioBufferTime*AL_BUFFERS_QUEUED)
@@ -340,10 +378,10 @@ nanoseconds AudioState::getClock()
* AL_SAMPLE_OFFSET_LATENCY_SOFT)
*
* Subtracting the length of the source queue from the next sample's
- * timestamp gives the timestamp of the sample at start of the source
- * queue. Adding the source offset to that results in the timestamp for
- * OpenAL's current position, and subtracting the source latency from that
- * gives the timestamp of the sample currently at the DAC.
+ * timestamp gives the timestamp of the sample at the start of the source
+ * queue. Adding the source offset to that results in the timestamp for the
+ * sample at OpenAL's current position, and subtracting the source latency
+ * from that gives the timestamp of the sample currently at the DAC.
*/
nanoseconds pts = mCurrentPts;
if(mSource)
@@ -380,6 +418,7 @@ nanoseconds AudioState::getClock()
fixed32(offset[0] / mCodecCtx->sample_rate)
);
}
+ /* Don't offset by the latency if the source isn't playing. */
if(status == AL_PLAYING)
pts -= nanoseconds(offset[1]);
}
@@ -400,7 +439,27 @@ bool AudioState::isBufferFilled()
void AudioState::startPlayback()
{
- return alSourcePlay(mSource);
+ alSourcePlay(mSource);
+ if(alcGetInteger64vSOFT)
+ {
+ using fixed32 = std::chrono::duration<int64_t,std::ratio<1,(1ll<<32)>>;
+
+ // Subtract the total buffer queue time from the current pts to get the
+ // pts of the start of the queue.
+ nanoseconds startpts = mCurrentPts - AudioBufferTotalTime;
+ int64_t srctimes[2]={0,0};
+ alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_CLOCK_SOFT, srctimes);
+ auto device_time = nanoseconds(srctimes[1]);
+ auto src_offset = std::chrono::duration_cast<nanoseconds>(fixed32(srctimes[0])) /
+ mCodecCtx->sample_rate;
+
+ // The mixer may have ticked and incremented the device time and sample
+ // offset, so subtract the source offset from the device time to get
+ // the device time the source started at. Also subtract startpts to get
+ // the device time the stream would have started at to reach where it
+ // is now.
+ mDeviceStartTime = device_time - src_offset - startpts;
+ }
}
int AudioState::getSync()
@@ -537,7 +596,12 @@ int AudioState::readAudio(uint8_t *samples, int length)
mSamplesPos = std::min(mSamplesLen, sample_skip);
sample_skip -= mSamplesPos;
- mCurrentPts += nanoseconds(seconds(mSamplesPos)) / mCodecCtx->sample_rate;
+ // Adjust the device start time and current pts by the amount we're
+ // skipping/duplicating, so that the clock remains correct for the
+ // current stream position.
+ auto skip = nanoseconds(seconds(mSamplesPos)) / mCodecCtx->sample_rate;
+ mDeviceStartTime -= skip;
+ mCurrentPts += skip;
continue;
}
@@ -777,14 +841,13 @@ int AudioState::handler()
continue;
}
- lock.unlock();
-
/* (re)start the source if needed, and wait for a buffer to finish */
if(state != AL_PLAYING && state != AL_PAUSED &&
mMovie.mPlaying.load(std::memory_order_relaxed))
- alSourcePlay(mSource);
- SDL_Delay((AudioBufferTime/3).count());
+ startPlayback();
+ lock.unlock();
+ SDL_Delay((AudioBufferTime/3).count());
lock.lock();
}
@@ -1525,6 +1588,17 @@ int main(int argc, char *argv[])
name = alcGetString(device, ALC_DEVICE_SPECIFIER);
std::cout<< "Opened \""<<name<<"\"" <<std::endl;
+ /* WARNING: ALC_SOFT_device_clock is still subject to change. It's fine to
+ * play around with and test out, but avoid in production code.
+ */
+ if(alcIsExtensionPresent(device, "ALC_SOFTX_device_clock"))
+ {
+ std::cout<< "Found ALC_SOFT_device_clock" <<std::endl;
+ alcGetInteger64vSOFT = reinterpret_cast<LPALCGETINTEGER64VSOFT>(
+ alcGetProcAddress(device, "alcGetInteger64vSOFT")
+ );
+ }
+
if(alIsExtensionPresent("AL_SOFT_source_latency"))
{
std::cout<< "Found AL_SOFT_source_latency" <<std::endl;