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.

142 lines
4.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_FRAMECLOCK_HPP
  17. #define SOSSO_FRAMECLOCK_HPP
  18. #include "sosso/Logging.hpp"
  19. #include <sys/errno.h>
  20. #include <time.h>
  21. namespace sosso {
  22. /*!
  23. * \brief Clock using audio frames as time unit.
  24. *
  25. * Provides time as an offset from an initial time zero, usually when the audio
  26. * device was started. Instead of nanoseconds it measures time in frames
  27. * (samples per channel), and thus needs to know the sample rate.
  28. * It also lets a thread sleep until a specified wakeup time, again in frames.
  29. */
  30. class FrameClock {
  31. public:
  32. /*!
  33. * \brief Initialize the clock, set time zero.
  34. * \param sample_rate Sample rate in Hz, for time to frame conversion.
  35. * \return True if successful, false means an error occurred.
  36. */
  37. bool init_clock(unsigned sample_rate) {
  38. return set_sample_rate(sample_rate) && init_zero_time();
  39. }
  40. /*!
  41. * \brief Get current frame time.
  42. * \param result Set to current frame time, as offset from time zero.
  43. * \return True if successful, false means an error occurred.
  44. */
  45. bool now(std::int64_t &result) const {
  46. std::int64_t time_ns = 0;
  47. if (get_time_offset(time_ns)) {
  48. result = time_to_frames(time_ns);
  49. return true;
  50. }
  51. return false;
  52. }
  53. /*!
  54. * \brief Let the thread sleep until wakeup time.
  55. * \param wakeup_frame Wakeup time in frames since time zero.
  56. * \return True if successful, false means an error occurred.
  57. */
  58. bool sleep(std::int64_t wakeup_frame) const {
  59. std::int64_t time_ns = frames_to_time(wakeup_frame);
  60. return sleep_until(time_ns);
  61. }
  62. //! Convert frames to time in nanoseconds.
  63. std::int64_t frames_to_time(std::int64_t frames) const {
  64. return (frames * 1000000000) / _sample_rate;
  65. }
  66. //! Convert time in nanoseconds to frames.
  67. std::int64_t time_to_frames(std::int64_t time_ns) const {
  68. return (time_ns * _sample_rate) / 1000000000;
  69. }
  70. //! Convert frames to system clock time in microseconds.
  71. std::int64_t frames_to_absolute_us(std::int64_t frames) const {
  72. return _zero.tv_sec * 1000000ULL + _zero.tv_nsec / 1000 +
  73. frames_to_time(frames) / 1000;
  74. }
  75. //! Currently used sample rate in Hz.
  76. unsigned sample_rate() const { return _sample_rate; }
  77. //! Set the sample rate in Hz, used for time to frame conversion.
  78. bool set_sample_rate(unsigned sample_rate) {
  79. if (sample_rate > 0) {
  80. _sample_rate = sample_rate;
  81. return true;
  82. }
  83. return false;
  84. }
  85. //! Suggested minimal wakeup step in frames.
  86. unsigned stepping() const { return 16U * (1U + (_sample_rate / 50000)); }
  87. private:
  88. // Initialize time zero now.
  89. bool init_zero_time() { return gettime(_zero); }
  90. // Get current time in nanoseconds, as offset from time zero.
  91. bool get_time_offset(std::int64_t &result) const {
  92. timespec now;
  93. if (gettime(now)) {
  94. result = ((now.tv_sec - _zero.tv_sec) * 1000000000) + now.tv_nsec -
  95. _zero.tv_nsec;
  96. return true;
  97. }
  98. return false;
  99. }
  100. // Let thread sleep until wakeup time, in nanoseconds since time zero.
  101. bool sleep_until(std::int64_t offset_ns) const {
  102. timespec wakeup = {_zero.tv_sec + (_zero.tv_nsec + offset_ns) / 1000000000,
  103. (_zero.tv_nsec + offset_ns) % 1000000000};
  104. if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &wakeup, NULL) != 0) {
  105. Log::warn(SOSSO_LOC, "Sleep failed with error %d.", errno);
  106. return false;
  107. }
  108. return true;
  109. }
  110. // Get current time in nanosecons, as a timespec struct.
  111. bool gettime(timespec &result) const {
  112. if (clock_gettime(CLOCK_MONOTONIC, &result) != 0) {
  113. Log::warn(SOSSO_LOC, "Get time failed with error %d.", errno);
  114. return false;
  115. }
  116. return true;
  117. }
  118. timespec _zero = {0, 0}; // Time zero as a timespec struct.
  119. unsigned _sample_rate = 48000; // Sample rate used for frame conversion.
  120. };
  121. } // namespace sosso
  122. #endif // SOSSO_FRAMECLOCK_HPP