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.

211 lines
7.6KB

  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_CHANNEL_HPP
  17. #define SOSSO_CHANNEL_HPP
  18. #include "sosso/Device.hpp"
  19. #include <algorithm>
  20. namespace sosso {
  21. /*!
  22. * \brief Audio Channel of a Device
  23. *
  24. * As a base class for read and write channels, this class provides generic
  25. * handling of progress, loss and wakeup times. Progress here means the OSS
  26. * device captures or consumes audio data, in frames. When progress is detected
  27. * within a short wakeup interval, this counts as a sync where we can exactly
  28. * match the device progress to current time.
  29. * The balance indicates the drift between device progress and external time,
  30. * usually taken from FrameClock.
  31. * At device start and after loss, device progress can be irregular and is
  32. * temporarily decoupled from Channel progress (freewheel). Sync events are
  33. * required to change into normal mode which strictly follows device progress.
  34. */
  35. class Channel : public Device {
  36. public:
  37. /*!
  38. * \brief Open the device, initialize Channel
  39. * \param device Full device path.
  40. * \param mode Open mode (read / write).
  41. * \return True if successful.
  42. */
  43. bool open(const char *device, int mode) {
  44. // Reset all internal statistics from last run.
  45. _last_processing = 0;
  46. _last_sync = 0;
  47. _last_progress = 0;
  48. _balance = 0;
  49. _min_progress = 0;
  50. _max_progress = 0;
  51. _total_loss = 0;
  52. _sync_level = 8;
  53. return Device::open(device, mode);
  54. }
  55. //! Total progress of the device since start.
  56. std::int64_t last_progress() const { return _last_progress; }
  57. //! Balance (drift) compared to external time.
  58. std::int64_t balance() const { return _balance; }
  59. //! Last time there was a successful sync.
  60. std::int64_t last_sync() const { return _last_sync; }
  61. //! Last time the Channel was processed (mark_progress()).
  62. std::int64_t last_processing() const { return _last_processing; }
  63. //! Maximum progress step encountered.
  64. std::int64_t max_progress() const { return _max_progress; }
  65. //! Minimum progress step encountered.
  66. std::int64_t min_progress() const { return _min_progress; }
  67. //! Current number of syncs required to change to normal mode.
  68. unsigned sync_level() const { return _sync_level; }
  69. //! Indicate Channel progress decoupled from device progress.
  70. bool freewheel() const { return _sync_level > 4; }
  71. //! Indicate a full resync with small wakeup steps is required.
  72. bool full_resync() const { return _sync_level > 2; }
  73. //! Indicate a resync is required.
  74. bool resync() const { return _sync_level > 0; }
  75. //! Total number of frames lost due to over- or underruns.
  76. std::int64_t total_loss() const { return _total_loss; }
  77. //! Next time a device progress could be expected.
  78. std::int64_t next_min_progress() const {
  79. return _last_progress + _min_progress + _balance;
  80. }
  81. //! Calculate safe wakeup time to avoid over- or underruns.
  82. std::int64_t safe_wakeup(std::int64_t oss_available) const {
  83. return next_min_progress() + buffer_frames() - oss_available -
  84. max_progress();
  85. }
  86. //! Estimate the time to expect over- or underruns.
  87. std::int64_t estimated_dropout(std::int64_t oss_available) const {
  88. return _last_progress + _balance + buffer_frames() - oss_available;
  89. }
  90. /*!
  91. * \brief Calculate next wakeup time.
  92. * \param sync_target External wakeup target like the next buffer end.
  93. * \param oss_available Number of frames available in OSS buffer.
  94. * \return Next wakeup time in external frame time.
  95. */
  96. std::int64_t wakeup_time(std::int64_t sync_target,
  97. std::int64_t oss_available) const {
  98. // Use one sync step by default.
  99. std::int64_t wakeup = _last_processing + Device::stepping();
  100. if (freewheel() || full_resync()) {
  101. // Small steps when doing a full resync.
  102. } else if (resync() || wakeup + max_progress() > sync_target) {
  103. // Sync required, wake up prior to next progress if possible.
  104. if (next_min_progress() > wakeup) {
  105. wakeup = next_min_progress() - Device::stepping();
  106. } else if (next_min_progress() > _last_processing) {
  107. wakeup = next_min_progress();
  108. }
  109. } else {
  110. // Sleep until prior to sync target, then sync again.
  111. wakeup = sync_target - max_progress();
  112. }
  113. // Make sure we wake up at sync target.
  114. if (sync_target > _last_processing && sync_target < wakeup) {
  115. wakeup = sync_target;
  116. }
  117. // Make sure we don't sleep into an OSS under- or overrun.
  118. if (safe_wakeup(oss_available) < wakeup) {
  119. wakeup = std::max(safe_wakeup(oss_available),
  120. _last_processing + Device::stepping());
  121. }
  122. return wakeup;
  123. }
  124. protected:
  125. // Account for progress detected, at current time.
  126. void mark_progress(std::int64_t progress, std::int64_t now) {
  127. if (progress > 0) {
  128. if (freewheel()) {
  129. // Some cards show irregular progress at the beginning, correct that.
  130. // Also correct loss after under- and overruns, assume same balance.
  131. _last_progress = now - progress - _balance;
  132. // Require a sync before transition back to normal processing.
  133. if (now <= _last_processing + stepping()) {
  134. _sync_level -= 1;
  135. }
  136. } else if (now <= _last_processing + stepping()) {
  137. // Successful sync on progress within small processing steps.
  138. _balance = now - (_last_progress + progress);
  139. _last_sync = now;
  140. if (_sync_level > 0) {
  141. _sync_level -= 1;
  142. }
  143. if (progress < _min_progress || _min_progress == 0) {
  144. _min_progress = progress;
  145. }
  146. if (progress > _max_progress) {
  147. _max_progress = progress;
  148. }
  149. } else {
  150. // Big step with progress but no sync, requires a resync.
  151. _sync_level += 1;
  152. }
  153. _last_progress += progress;
  154. }
  155. _last_processing = now;
  156. }
  157. // Account for loss given progress and current time.
  158. std::int64_t mark_loss(std::int64_t progress, std::int64_t now) {
  159. // Estimate frames lost due to over- or underrun.
  160. std::int64_t loss = (now - _balance) - (_last_progress + progress);
  161. return mark_loss(loss);
  162. }
  163. // Account for loss.
  164. std::int64_t mark_loss(std::int64_t loss) {
  165. if (loss > 0) {
  166. _total_loss += loss;
  167. // Resync OSS progress to frame time (now) to recover from loss.
  168. _sync_level = std::max(_sync_level, 6U);
  169. } else {
  170. loss = 0;
  171. }
  172. return loss;
  173. }
  174. private:
  175. std::int64_t _last_processing = 0; // Last processing time.
  176. std::int64_t _last_sync = 0; // Last sync time.
  177. std::int64_t _last_progress = 0; // Total device progress.
  178. std::int64_t _balance = 0; // Channel drift.
  179. std::int64_t _min_progress = 0; // Minimum progress step encountered.
  180. std::int64_t _max_progress = 0; // Maximum progress step encountered.
  181. std::int64_t _total_loss = 0; // Total loss due to over- or underruns.
  182. unsigned _sync_level = 0; // Syncs required.
  183. };
  184. } // namespace sosso
  185. #endif // SOSSO_CHANNEL_HPP