jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

219 lines
7.9KB

  1. /*
  2. * Copyright (c) 2023 Florian Walpen <dev@submerge.ch>
  3. *
  4. * Permission to use, copy, modify, and distribute this software for any
  5. * purpose with or without fee is hereby granted, provided that the above
  6. * copyright notice and this permission notice appear in all copies.
  7. *
  8. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. #ifndef SOSSO_DOUBLEBUFFER_HPP
  17. #define SOSSO_DOUBLEBUFFER_HPP
  18. #include "sosso/Buffer.hpp"
  19. #include "sosso/Logging.hpp"
  20. #include <algorithm>
  21. #include <limits>
  22. namespace sosso {
  23. /*!
  24. * \brief Double Buffering for Channel
  25. *
  26. * Manages double buffering on top of a ReadChannel or WriteChannel. It takes
  27. * two buffers with corresponding end positions. One of these is selected
  28. * for processing, depending on the buffer and channel positions. The buffers
  29. * can be overlapping or have gaps in between.
  30. * A buffer is marked as finished when all buffer data was processed and the
  31. * channel progress has reached the buffer end. This provides steady buffer
  32. * replacement times, synchronized with channel progress.
  33. * The wakeup times for processing are adapted to available channel data and
  34. * work pending (unprocessed buffer data).
  35. */
  36. template <class Channel> class DoubleBuffer : public Channel {
  37. /*!
  38. * \brief Store a buffer and its end position.
  39. *
  40. * The end position of the buffer corresponds to channel progress in frames.
  41. * Marking the end position allows to map the buffer content to the matching
  42. * channel data, independent of read and write positions.
  43. */
  44. struct BufferRecord {
  45. Buffer buffer; // External buffer, may be empty.
  46. std::int64_t end_frames = 0; // Buffer end position in frames.
  47. };
  48. public:
  49. //! Indicate that buffer is ready for processing.
  50. bool ready() const { return _buffer_a.buffer.valid(); }
  51. /*!
  52. * \brief Set the next consecutive buffer to be processed.
  53. * \param buffer External buffer ready for processing.
  54. * \param end_frames End position of the buffer in frames.
  55. * \return True if successful, false means there are already two buffers.
  56. */
  57. bool set_buffer(Buffer &&buffer, std::int64_t end_frames) {
  58. // Set secondary buffer if available.
  59. if (!_buffer_b.buffer.valid()) {
  60. _buffer_b.buffer = std::move(buffer);
  61. _buffer_b.end_frames = end_frames;
  62. // Promote secondary buffer to primary if primary is not set.
  63. if (!_buffer_a.buffer.valid()) {
  64. std::swap(_buffer_b, _buffer_a);
  65. }
  66. return ready();
  67. }
  68. return false;
  69. }
  70. /*!
  71. * \brief Reset the buffer end positions in case of over- and underruns.
  72. * \param end_frames New end position of the primary buffer.
  73. * \return True if ready to proceed.
  74. */
  75. bool reset_buffers(std::int64_t end_frames) {
  76. // Reset primary buffer.
  77. if (_buffer_a.buffer.valid()) {
  78. std::memset(_buffer_a.buffer.data(), 0, _buffer_a.buffer.length());
  79. _buffer_a.buffer.reset();
  80. Log::info(SOSSO_LOC, "Primary buffer reset from %lld to %lld.",
  81. _buffer_a.end_frames, end_frames);
  82. _buffer_a.end_frames = end_frames;
  83. }
  84. // Reset secondary buffer.
  85. if (_buffer_b.buffer.valid()) {
  86. std::memset(_buffer_b.buffer.data(), 0, _buffer_b.buffer.length());
  87. _buffer_b.buffer.reset();
  88. end_frames += _buffer_b.buffer.length() / Channel::frame_size();
  89. Log::info(SOSSO_LOC, "Secondary buffer reset from %lld to %lld.",
  90. _buffer_b.end_frames, end_frames);
  91. _buffer_b.end_frames = end_frames;
  92. }
  93. return ready();
  94. }
  95. //! Retrieve the primary buffer, may be empty.
  96. Buffer &&take_buffer() {
  97. std::swap(_buffer_a, _buffer_b);
  98. return std::move(_buffer_b.buffer);
  99. }
  100. /*!
  101. * \brief Process channel with given buffers to read or write.
  102. * \param now Time offset from channel start in frames, see FrameClock.
  103. * \return True if there were no processing errors.
  104. */
  105. bool process(std::int64_t now) {
  106. // Round frame time down to steppings, ignore timing jitter.
  107. now = now - now % Channel::stepping();
  108. // Always process primary buffer, No-Op if already done.
  109. bool ok = Channel::process(_buffer_a.buffer, _buffer_a.end_frames, now);
  110. // Process secondary buffer when primary is done.
  111. if (ok && _buffer_a.buffer.done() && _buffer_b.buffer.valid()) {
  112. ok = Channel::process(_buffer_b.buffer, _buffer_b.end_frames, now);
  113. }
  114. return ok;
  115. }
  116. //! End position of the primary buffer.
  117. std::int64_t end_frames() const {
  118. if (ready()) {
  119. return _buffer_a.end_frames;
  120. }
  121. return 0;
  122. }
  123. //! Expected frame time when primary buffer is finished.
  124. std::int64_t period_end() const {
  125. if (ready()) {
  126. return end_frames() + Channel::balance();
  127. }
  128. return 0;
  129. }
  130. //! Expected frame time when both buffers are finished.
  131. std::int64_t total_end() const {
  132. if (ready()) {
  133. if (_buffer_b.buffer.valid()) {
  134. return _buffer_b.end_frames + Channel::balance();
  135. }
  136. return end_frames() + Channel::balance();
  137. }
  138. return 0;
  139. }
  140. /*!
  141. * \brief Calculate next wakeup time for processing.
  142. * \param now Current frame time as offset from channel start, see FrameClock.
  143. * \return Next suggested wakeup in frame time.
  144. */
  145. std::int64_t wakeup_time(std::int64_t now) const {
  146. // No need to wake up if channel is not running.
  147. if (!Channel::is_open()) {
  148. return std::numeric_limits<std::int64_t>::max();
  149. }
  150. // Wakeup immediately if there's more work to do now.
  151. if (Channel::oss_available() > 0 &&
  152. (!_buffer_a.buffer.done() || !_buffer_b.buffer.done())) {
  153. Log::log(SOSSO_LOC, "Immediate wakeup at %lld for more work.", now);
  154. return now;
  155. }
  156. // Get upcoming buffer end and compute next channel wakeup time.
  157. std::int64_t sync_frames = now;
  158. if (_buffer_a.buffer.valid() && !finished(now)) {
  159. sync_frames = period_end();
  160. } else if (_buffer_b.buffer.valid() && !total_finished(now)) {
  161. sync_frames = _buffer_b.end_frames + Channel::balance();
  162. } else {
  163. sync_frames = std::numeric_limits<std::int64_t>::max();
  164. }
  165. return Channel::wakeup_time(sync_frames);
  166. }
  167. //! Indicate progress on processing the primary buffer, in frames.
  168. std::int64_t buffer_progress() const {
  169. return _buffer_a.buffer.progress() / Channel::frame_size();
  170. }
  171. //! Indicate that primary buffer is finished at current frame time.
  172. bool finished(std::int64_t now) const {
  173. return period_end() <= now && _buffer_a.buffer.done();
  174. }
  175. //! Indicate that both buffers are finished at current frame time.
  176. bool total_finished(std::int64_t now) const {
  177. return total_end() <= now && _buffer_a.buffer.done() &&
  178. _buffer_b.buffer.done();
  179. }
  180. //! Print channel state as user information, at current frame time.
  181. void log_state(std::int64_t now) const {
  182. const char *direction = Channel::playback() ? "Out" : "In";
  183. const char *sync = (Channel::last_sync() == now) ? "sync" : "frame";
  184. std::int64_t buf_a = _buffer_a.buffer.progress() / Channel::frame_size();
  185. std::int64_t buf_b = _buffer_b.buffer.progress() / Channel::frame_size();
  186. Log::log(SOSSO_LOC,
  187. "%s %s, %lld bal %lld, buf A %lld B %lld OSS %lld, %lld left, "
  188. "req %u min %lld",
  189. direction, sync, now, Channel::balance(), buf_a, buf_b,
  190. Channel::oss_available(), period_end() - now,
  191. Channel::sync_level(), Channel::min_progress());
  192. }
  193. private:
  194. BufferRecord _buffer_a; // Primary buffer, may be empty.
  195. BufferRecord _buffer_b; // Secondary buffer, may be empty.
  196. };
  197. } // namespace sosso
  198. #endif // SOSSO_DOUBLEBUFFER_HPP