diff --git a/src/internal_modules/roc_pipeline/receiver_loop.cpp b/src/internal_modules/roc_pipeline/receiver_loop.cpp index 7e4c4b28c..d1418f971 100644 --- a/src/internal_modules/roc_pipeline/receiver_loop.cpp +++ b/src/internal_modules/roc_pipeline/receiver_loop.cpp @@ -176,6 +176,14 @@ core::nanoseconds_t ReceiverLoop::latency() const { return source_.latency(); } +bool ReceiverLoop::has_latency() const { + roc_panic_if(!is_valid()); + + core::Mutex::Lock lock(source_mutex_); + + return source_.has_latency(); +} + bool ReceiverLoop::has_clock() const { roc_panic_if(!is_valid()); diff --git a/src/internal_modules/roc_pipeline/receiver_loop.h b/src/internal_modules/roc_pipeline/receiver_loop.h index c35a1a2c8..34c8da19f 100644 --- a/src/internal_modules/roc_pipeline/receiver_loop.h +++ b/src/internal_modules/roc_pipeline/receiver_loop.h @@ -143,6 +143,7 @@ class ReceiverLoop : public PipelineLoop, private sndio::ISource { virtual bool restart(); virtual audio::SampleSpec sample_spec() const; virtual core::nanoseconds_t latency() const; + virtual bool has_latency() const; virtual bool has_clock() const; virtual void reclock(core::nanoseconds_t timestamp); virtual bool read(audio::Frame&); diff --git a/src/internal_modules/roc_pipeline/receiver_source.cpp b/src/internal_modules/roc_pipeline/receiver_source.cpp index b845162ad..5e80d6462 100644 --- a/src/internal_modules/roc_pipeline/receiver_source.cpp +++ b/src/internal_modules/roc_pipeline/receiver_source.cpp @@ -128,6 +128,10 @@ core::nanoseconds_t ReceiverSource::latency() const { return 0; } +bool ReceiverSource::has_latency() const { + return false; +} + bool ReceiverSource::has_clock() const { return config_.common.enable_timing; } diff --git a/src/internal_modules/roc_pipeline/receiver_source.h b/src/internal_modules/roc_pipeline/receiver_source.h index be9f04af9..d5dbdf707 100644 --- a/src/internal_modules/roc_pipeline/receiver_source.h +++ b/src/internal_modules/roc_pipeline/receiver_source.h @@ -86,6 +86,9 @@ class ReceiverSource : public sndio::ISource, public core::NonCopyable<> { //! Get latency of the source. virtual core::nanoseconds_t latency() const; + //! Check if the source supports latency reports. + virtual bool has_latency() const; + //! Check if the source has own clock. virtual bool has_clock() const; diff --git a/src/internal_modules/roc_pipeline/sender_loop.cpp b/src/internal_modules/roc_pipeline/sender_loop.cpp index d0c649eab..70699fba2 100644 --- a/src/internal_modules/roc_pipeline/sender_loop.cpp +++ b/src/internal_modules/roc_pipeline/sender_loop.cpp @@ -177,6 +177,14 @@ core::nanoseconds_t SenderLoop::latency() const { return sink_.latency(); } +bool SenderLoop::has_latency() const { + roc_panic_if_not(is_valid()); + + core::Mutex::Lock lock(sink_mutex_); + + return sink_.has_latency(); +} + bool SenderLoop::has_clock() const { roc_panic_if_not(is_valid()); diff --git a/src/internal_modules/roc_pipeline/sender_loop.h b/src/internal_modules/roc_pipeline/sender_loop.h index c1878e414..756d64453 100644 --- a/src/internal_modules/roc_pipeline/sender_loop.h +++ b/src/internal_modules/roc_pipeline/sender_loop.h @@ -143,6 +143,7 @@ class SenderLoop : public PipelineLoop, private sndio::ISink { virtual bool restart(); virtual audio::SampleSpec sample_spec() const; virtual core::nanoseconds_t latency() const; + virtual bool has_latency() const; virtual bool has_clock() const; virtual void write(audio::Frame& frame); diff --git a/src/internal_modules/roc_pipeline/sender_sink.cpp b/src/internal_modules/roc_pipeline/sender_sink.cpp index d85a1d16e..b251c777b 100644 --- a/src/internal_modules/roc_pipeline/sender_sink.cpp +++ b/src/internal_modules/roc_pipeline/sender_sink.cpp @@ -126,6 +126,10 @@ core::nanoseconds_t SenderSink::latency() const { return 0; } +bool SenderSink::has_latency() const { + return false; +} + bool SenderSink::has_clock() const { return config_.enable_timing; } diff --git a/src/internal_modules/roc_pipeline/sender_sink.h b/src/internal_modules/roc_pipeline/sender_sink.h index 2a5385e9b..fbd0bf80c 100644 --- a/src/internal_modules/roc_pipeline/sender_sink.h +++ b/src/internal_modules/roc_pipeline/sender_sink.h @@ -90,6 +90,9 @@ class SenderSink : public sndio::ISink, public core::NonCopyable<> { //! Get latency of the sink. virtual core::nanoseconds_t latency() const; + //! Check if the sink supports latency reports. + virtual bool has_latency() const; + //! Check if the sink has own clock. virtual bool has_clock() const; diff --git a/src/internal_modules/roc_pipeline/transcoder_sink.cpp b/src/internal_modules/roc_pipeline/transcoder_sink.cpp index 7a9e39989..a3a6ddec9 100644 --- a/src/internal_modules/roc_pipeline/transcoder_sink.cpp +++ b/src/internal_modules/roc_pipeline/transcoder_sink.cpp @@ -106,6 +106,10 @@ core::nanoseconds_t TranscoderSink::latency() const { return 0; } +bool TranscoderSink::has_latency() const { + return false; +} + bool TranscoderSink::has_clock() const { return false; } diff --git a/src/internal_modules/roc_pipeline/transcoder_sink.h b/src/internal_modules/roc_pipeline/transcoder_sink.h index 982453d91..ebde4310d 100644 --- a/src/internal_modules/roc_pipeline/transcoder_sink.h +++ b/src/internal_modules/roc_pipeline/transcoder_sink.h @@ -64,6 +64,9 @@ class TranscoderSink : public sndio::ISink, public core::NonCopyable<> { //! Get latency of the sink. virtual core::nanoseconds_t latency() const; + //! Check if the sink supports latency reports. + virtual bool has_latency() const; + //! Check if the sink has own clock. virtual bool has_clock() const; diff --git a/src/internal_modules/roc_pipeline/transcoder_source.cpp b/src/internal_modules/roc_pipeline/transcoder_source.cpp index a31899c90..16c3993d9 100644 --- a/src/internal_modules/roc_pipeline/transcoder_source.cpp +++ b/src/internal_modules/roc_pipeline/transcoder_source.cpp @@ -119,6 +119,10 @@ core::nanoseconds_t TranscoderSource::latency() const { return 0; } +bool TranscoderSource::has_latency() const { + return false; +} + bool TranscoderSource::has_clock() const { return input_source_.has_clock(); } diff --git a/src/internal_modules/roc_pipeline/transcoder_source.h b/src/internal_modules/roc_pipeline/transcoder_source.h index 3b2a10284..670fff9a2 100644 --- a/src/internal_modules/roc_pipeline/transcoder_source.h +++ b/src/internal_modules/roc_pipeline/transcoder_source.h @@ -64,6 +64,9 @@ class TranscoderSource : public sndio::ISource, public core::NonCopyable<> { //! Get latency of the source. virtual core::nanoseconds_t latency() const; + //! Check if the source supports latency reports. + virtual bool has_latency() const; + //! Check if the sink has own clock. virtual bool has_clock() const; diff --git a/src/internal_modules/roc_sndio/idevice.h b/src/internal_modules/roc_sndio/idevice.h index 603f5dc01..81dd41c3a 100644 --- a/src/internal_modules/roc_sndio/idevice.h +++ b/src/internal_modules/roc_sndio/idevice.h @@ -53,6 +53,9 @@ class IDevice { //! Get latency of the device. virtual core::nanoseconds_t latency() const = 0; + //! Check if the device supports latency reports. + virtual bool has_latency() const = 0; + //! Check if the device has own clock. virtual bool has_clock() const = 0; }; diff --git a/src/internal_modules/roc_sndio/pump.cpp b/src/internal_modules/roc_sndio/pump.cpp index e3a7cc101..2f76e5b20 100644 --- a/src/internal_modules/roc_sndio/pump.cpp +++ b/src/internal_modules/roc_sndio/pump.cpp @@ -57,6 +57,7 @@ bool Pump::run() { ISource* current_source = &main_source_; while (!stop_) { + // switch between main and backup sources when necessary if (main_source_.state() == DeviceState_Active) { if (current_source == backup_source_) { roc_log(LogInfo, "pump: switching to main source"); @@ -86,6 +87,7 @@ bool Pump::run() { } } + // read frame audio::Frame frame(frame_buffer_.data(), frame_buffer_.size()); // if source has clock, here we block on it @@ -106,21 +108,35 @@ bool Pump::run() { // where this frame spent some time before we read it // we subtract frame size because we already read the whole frame from // recording buffer, and should take it into account too - frame.set_capture_timestamp( - core::timestamp(core::ClockUnix) - current_source->latency() - - sample_spec_.samples_overall_2_ns(frame.num_samples())); + core::nanoseconds_t capture_latency = 0; + + if (current_source->has_latency()) { + capture_latency = current_source->latency() + + sample_spec_.samples_overall_2_ns(frame.num_samples()); + } + + frame.set_capture_timestamp(core::timestamp(core::ClockUnix) + - capture_latency); } // if sink has clock, here we block on it - // note that either source or sink may have clock, but not both + // note that either source or sink has clock, but not both sink_.write(frame); - // tell source what is playback time of first sample of last read frame - // we add sink latency to take into account playback buffer size - // we subtract frame size because we already wrote the whole frame into - // playback buffer, and should take it into account too - current_source->reclock(core::timestamp(core::ClockUnix) + sink_.latency() - - sample_spec_.samples_overall_2_ns(frame.num_samples())); + { + // tell source what is playback time of first sample of last read frame + // we add sink latency to take into account playback buffer size + // we subtract frame size because we already wrote the whole frame into + // playback buffer, and should take it into account too + core::nanoseconds_t playback_latency = 0; + + if (sink_.has_latency()) { + playback_latency = sink_.latency() + - sample_spec_.samples_overall_2_ns(frame.num_samples()); + } + + current_source->reclock(core::timestamp(core::ClockUnix) + playback_latency); + } if (current_source == &main_source_) { n_bufs_++; diff --git a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_device.cpp b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_device.cpp index 362b3584a..196877539 100644 --- a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_device.cpp +++ b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_device.cpp @@ -161,10 +161,6 @@ core::nanoseconds_t PulseaudioDevice::latency() const { return latency; } -bool PulseaudioDevice::has_clock() const { - return true; -} - bool PulseaudioDevice::request(audio::Frame& frame) { want_mainloop_(); diff --git a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_device.h b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_device.h index 100684d63..962e4bc0a 100644 --- a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_device.h +++ b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_device.h @@ -57,9 +57,6 @@ class PulseaudioDevice : public core::NonCopyable<> { //! Get latency of the sink. core::nanoseconds_t latency() const; - //! Check if the sink has own clock. - bool has_clock() const; - //! Process audio frame. bool request(audio::Frame& frame); diff --git a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_sink.cpp b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_sink.cpp index 7574a043d..9c2561563 100644 --- a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_sink.cpp +++ b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_sink.cpp @@ -46,8 +46,12 @@ core::nanoseconds_t PulseaudioSink::latency() const { return PulseaudioDevice::latency(); } +bool PulseaudioSink::has_latency() const { + return true; +} + bool PulseaudioSink::has_clock() const { - return PulseaudioDevice::has_clock(); + return true; } void PulseaudioSink::write(audio::Frame& frame) { diff --git a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_sink.h b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_sink.h index c02f161bc..7c0555e62 100644 --- a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_sink.h +++ b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_sink.h @@ -47,6 +47,9 @@ class PulseaudioSink : public ISink, public PulseaudioDevice { //! Get latency of the sink. virtual core::nanoseconds_t latency() const; + //! Check if the sink supports latency reports. + virtual bool has_latency() const; + //! Check if the sink has own clock. virtual bool has_clock() const; diff --git a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_source.cpp b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_source.cpp index 9e4c92ab6..d60db6a27 100644 --- a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_source.cpp +++ b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_source.cpp @@ -48,8 +48,12 @@ core::nanoseconds_t PulseaudioSource::latency() const { return PulseaudioDevice::latency(); } +bool PulseaudioSource::has_latency() const { + return true; +} + bool PulseaudioSource::has_clock() const { - return PulseaudioDevice::has_clock(); + return true; } void PulseaudioSource::reclock(core::nanoseconds_t) { diff --git a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_source.h b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_source.h index a5ad89500..f1f7ffd4f 100644 --- a/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_source.h +++ b/src/internal_modules/roc_sndio/target_pulseaudio/roc_sndio/pulseaudio_source.h @@ -47,6 +47,9 @@ class PulseaudioSource : public ISource, public PulseaudioDevice { //! Get latency of the source. virtual core::nanoseconds_t latency() const; + //! Check if the source supports latency reports. + virtual bool has_latency() const; + //! Check if the source has own clock. virtual bool has_clock() const; diff --git a/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_sink.cpp b/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_sink.cpp index 801a7c00d..2aaf17d69 100644 --- a/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_sink.cpp +++ b/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_sink.cpp @@ -127,6 +127,16 @@ core::nanoseconds_t SoxSink::latency() const { return 0; } +bool SoxSink::has_latency() const { + roc_panic_if(!valid_); + + if (!output_) { + roc_panic("sox sink: has_latency(): non-open output file or device"); + } + + return false; +} + bool SoxSink::has_clock() const { roc_panic_if(!valid_); diff --git a/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_sink.h b/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_sink.h index 5d5a72d6a..9ec0b5a28 100644 --- a/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_sink.h +++ b/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_sink.h @@ -71,6 +71,9 @@ class SoxSink : public ISink, public core::NonCopyable<> { //! Get latency of the sink. virtual core::nanoseconds_t latency() const; + //! Check if the sink supports latency reports. + virtual bool has_latency() const; + //! Check if the sink has own clock. virtual bool has_clock() const; diff --git a/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_source.cpp b/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_source.cpp index 196118a7f..70c2133c2 100644 --- a/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_source.cpp +++ b/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_source.cpp @@ -203,6 +203,16 @@ core::nanoseconds_t SoxSource::latency() const { return 0; } +bool SoxSource::has_latency() const { + roc_panic_if(!valid_); + + if (!input_) { + roc_panic("sox source: has_latency(): non-open input file or device"); + } + + return false; +} + bool SoxSource::has_clock() const { roc_panic_if(!valid_); diff --git a/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_source.h b/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_source.h index abd6ad13d..58022e0f2 100644 --- a/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_source.h +++ b/src/internal_modules/roc_sndio/target_sox/roc_sndio/sox_source.h @@ -72,6 +72,9 @@ class SoxSource : public ISource, private core::NonCopyable<> { //! Get latency of the source. virtual core::nanoseconds_t latency() const; + //! Check if the source supports latency reports. + virtual bool has_latency() const; + //! Check if the source has own clock. virtual bool has_clock() const; diff --git a/src/tests/roc_pipeline/test_helpers/mock_sink.h b/src/tests/roc_pipeline/test_helpers/mock_sink.h index c95c448b6..747145b20 100644 --- a/src/tests/roc_pipeline/test_helpers/mock_sink.h +++ b/src/tests/roc_pipeline/test_helpers/mock_sink.h @@ -59,6 +59,10 @@ class MockSink : public sndio::ISink, public core::NonCopyable<> { return 0; } + virtual bool has_latency() const { + return false; + } + virtual bool has_clock() const { return false; } diff --git a/src/tests/roc_pipeline/test_helpers/mock_source.h b/src/tests/roc_pipeline/test_helpers/mock_source.h index e0c921963..8e4fb1adf 100644 --- a/src/tests/roc_pipeline/test_helpers/mock_source.h +++ b/src/tests/roc_pipeline/test_helpers/mock_source.h @@ -63,6 +63,10 @@ class MockSource : public sndio::ISource { return 0; } + virtual bool has_latency() const { + return false; + } + virtual bool has_clock() const { return false; } diff --git a/src/tests/roc_sndio/target_sox/test_helpers/mock_sink.h b/src/tests/roc_sndio/target_sox/test_helpers/mock_sink.h index 9ce860b49..e9b5498ac 100644 --- a/src/tests/roc_sndio/target_sox/test_helpers/mock_sink.h +++ b/src/tests/roc_sndio/target_sox/test_helpers/mock_sink.h @@ -53,6 +53,10 @@ class MockSink : public ISink { return 0; } + virtual bool has_latency() const { + return false; + } + virtual bool has_clock() const { return false; } diff --git a/src/tests/roc_sndio/target_sox/test_helpers/mock_source.h b/src/tests/roc_sndio/target_sox/test_helpers/mock_source.h index 31d6af8dd..03b9414d6 100644 --- a/src/tests/roc_sndio/target_sox/test_helpers/mock_source.h +++ b/src/tests/roc_sndio/target_sox/test_helpers/mock_source.h @@ -59,6 +59,10 @@ class MockSource : public ISource { return 0; } + virtual bool has_latency() const { + return false; + } + virtual bool has_clock() const { return false; }