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.

281 lines
11KB

  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_WRITECHANNEL_HPP
  17. #define SOSSO_WRITECHANNEL_HPP
  18. #include "sosso/Buffer.hpp"
  19. #include "sosso/Channel.hpp"
  20. #include "sosso/Logging.hpp"
  21. #include <fcntl.h>
  22. namespace sosso {
  23. /*!
  24. * \brief Playback Channel
  25. *
  26. * Specializes the generic Channel class into a playback channel. It keeps track
  27. * of the OSS playback progress, and writes audio data from an external buffer
  28. * to the available OSS buffer. If the OSS buffer is memory mapped, the audio
  29. * data is copied there. Otherwise I/O write() system calls are used.
  30. */
  31. class WriteChannel : public Channel {
  32. public:
  33. /*!
  34. * \brief Open a device for playback.
  35. * \param device Path to the device, e.g. "/dev/dsp1".
  36. * \param exclusive Try to get exclusive access to the device.
  37. * \return True if the device was opened successfully.
  38. */
  39. bool open(const char *device, bool exclusive = true) {
  40. int mode = O_WRONLY | O_NONBLOCK;
  41. if (exclusive) {
  42. mode |= O_EXCL;
  43. }
  44. return Channel::open(device, mode);
  45. }
  46. //! Available OSS buffer space for writing, in frames.
  47. std::int64_t oss_available() const {
  48. std::int64_t result = last_progress() + buffer_frames() - _write_position;
  49. if (result < 0) {
  50. result = 0;
  51. } else if (result > buffer_frames()) {
  52. result = buffer_frames();
  53. }
  54. return result;
  55. }
  56. /*!
  57. * \brief Calculate next wakeup time.
  58. * \param sync_frames Required sync event (e.g. buffer end), in frame time.
  59. * \return Suggested and safe wakeup time for next process(), in frame time.
  60. */
  61. std::int64_t wakeup_time(std::int64_t sync_frames) const {
  62. return Channel::wakeup_time(sync_frames, oss_available());
  63. }
  64. /*!
  65. * \brief Check OSS progress and write playback audio to the OSS buffer.
  66. * \param buffer Buffer of playback audio data, untouched if invalid.
  67. * \param end Buffer end position, matching channel progress.
  68. * \param now Current time in frame time, see FrameClock.
  69. * \return True if successful, false means there was an error.
  70. */
  71. bool process(Buffer &buffer, std::int64_t end, std::int64_t now) {
  72. if (map()) {
  73. return (progress_done(now) || check_map_progress(now)) &&
  74. (buffer_done(buffer, end) || process_mapped(buffer, end, now));
  75. } else {
  76. return (progress_done(now) || check_write_progress(now)) &&
  77. (buffer_done(buffer, end) || process_write(buffer, end, now));
  78. }
  79. }
  80. protected:
  81. // Indicate that OSS progress has already been checked.
  82. bool progress_done(std::int64_t now) { return (last_processing() == now); }
  83. // Check OSS progress in case of memory mapped buffer.
  84. bool check_map_progress(std::int64_t now) {
  85. // Get OSS progress through map pointer.
  86. if (get_play_pointer()) {
  87. std::int64_t progress = map_progress() - _oss_progress;
  88. if (progress > 0) {
  89. // Sometimes OSS playback starts with a bogus extra buffer cycle.
  90. if (progress > buffer_frames() &&
  91. now - last_processing() < buffer_frames() / 2) {
  92. Log::warn(SOSSO_LOC,
  93. "OSS playback bogus buffer cycle, %lld frames in %lld.",
  94. progress, now - last_processing());
  95. progress = progress % buffer_frames();
  96. }
  97. // Clear obsolete audio data in the buffer.
  98. write_map(nullptr, (_oss_progress % buffer_frames()) * frame_size(),
  99. progress * frame_size());
  100. _oss_progress = map_progress();
  101. }
  102. std::int64_t loss =
  103. mark_loss(last_progress() + progress - _write_position);
  104. mark_progress(progress, now);
  105. if (loss > 0) {
  106. Log::warn(SOSSO_LOC, "OSS playback buffer underrun, %lld lost.", loss);
  107. _write_position = last_progress();
  108. }
  109. }
  110. return progress_done(now);
  111. }
  112. // Write playback audio data to a memory mapped OSS buffer.
  113. bool process_mapped(Buffer &buffer, std::int64_t end, std::int64_t now) {
  114. // Buffer position should be between OSS progress and last write position.
  115. std::int64_t position = buffer_position(buffer.remaining(), end);
  116. if (std::int64_t skip =
  117. buffer_advance(buffer, last_progress() - position)) {
  118. // First part of the buffer already played, skip it.
  119. Log::info(SOSSO_LOC, "@%lld - %lld Write %lld already played, skip %lld.",
  120. now, end, last_progress() - position, skip);
  121. position += skip;
  122. } else if (position != _write_position) {
  123. // Position mismatch, rewrite as much as possible.
  124. if (std::int64_t rewind =
  125. buffer_rewind(buffer, position - last_progress())) {
  126. Log::info(SOSSO_LOC,
  127. "@%lld - %lld Write position mismatch, rewrite %lld.", now,
  128. end, rewind);
  129. position -= rewind;
  130. }
  131. }
  132. // The writable window is the whole buffer, starting from OSS progress.
  133. if (!buffer.done() && position >= last_progress() &&
  134. position < last_progress() + buffer_frames()) {
  135. if (_write_position < position && _write_position + 8 >= position) {
  136. // Small remaining gap between writes, fill in a replay patch.
  137. std::int64_t offset = _write_position - last_progress();
  138. unsigned pointer = (_oss_progress + offset) % buffer_frames();
  139. std::size_t length = (position - _write_position) * frame_size();
  140. length = buffer.remaining(length);
  141. std::size_t written =
  142. write_map(buffer.position(), pointer * frame_size(), length);
  143. Log::info(SOSSO_LOC, "@%lld - %lld Write small gap %lld, replay %lld.",
  144. now, end, position - _write_position, written / frame_size());
  145. }
  146. // Write from buffer offset up to either OSS or write buffer end.
  147. std::int64_t offset = position - last_progress();
  148. unsigned pointer = (_oss_progress + offset) % buffer_frames();
  149. std::size_t length = (buffer_frames() - offset) * frame_size();
  150. length = buffer.remaining(length);
  151. std::size_t written =
  152. write_map(buffer.position(), pointer * frame_size(), length);
  153. buffer.advance(written);
  154. _write_position = buffer_position(buffer.remaining(), end);
  155. }
  156. _write_position += freewheel_finish(buffer, end, now);
  157. return true;
  158. }
  159. // Check progress when using I/O write() system call.
  160. bool check_write_progress(std::int64_t now) {
  161. // Check for OSS buffer underruns.
  162. std::int64_t overdue = now - estimated_dropout(oss_available());
  163. if ((overdue > 0 && get_play_underruns() > 0) || overdue > max_progress()) {
  164. // OSS buffer underrun, estimate loss and progress from time.
  165. std::int64_t progress = _write_position - last_progress();
  166. std::int64_t loss = mark_loss(progress, now);
  167. Log::warn(SOSSO_LOC, "OSS playback buffer underrun, %lld lost.", loss);
  168. mark_progress(progress + loss, now);
  169. _write_position = last_progress();
  170. } else {
  171. // Infer progress from OSS queue changes.
  172. std::int64_t queued = queued_samples();
  173. std::int64_t progress = (_write_position - last_progress()) - queued;
  174. mark_progress(progress, now);
  175. _write_position = last_progress() + queued;
  176. }
  177. return progress_done(now);
  178. }
  179. // Write playback audio data to OSS buffer using I/O write() system call.
  180. bool process_write(Buffer &buffer, std::int64_t end, std::int64_t now) {
  181. bool ok = true;
  182. // Adjust buffer position to OSS write position, if possible.
  183. std::int64_t position = buffer_position(buffer.remaining(), end);
  184. if (std::int64_t rewind =
  185. buffer_rewind(buffer, position - _write_position)) {
  186. // Gap between buffers, replay parts to fill it up.
  187. Log::info(SOSSO_LOC, "@%lld - %lld Write buffer gap %lld, replay %lld.",
  188. now, end, position - _write_position, rewind);
  189. position -= rewind;
  190. } else if (std::int64_t skip =
  191. buffer_advance(buffer, _write_position - position)) {
  192. // Overlapping buffers, skip the overlapping part.
  193. Log::info(SOSSO_LOC, "@%lld - %lld Write buffer overlap %lld, skip %lld.",
  194. now, end, _write_position - position, skip);
  195. position += skip;
  196. }
  197. if (oss_available() == 0) {
  198. // OSS buffer is full, nothing to do.
  199. } else if (position > _write_position) {
  200. // Replay to fill remaining gap, limit the write to just fill the gap.
  201. std::int64_t gap = position - _write_position;
  202. std::size_t write_limit = buffer.remaining(gap * frame_size());
  203. std::size_t bytes_written = 0;
  204. ok = write_io(buffer.position(), write_limit, bytes_written);
  205. Log::info(SOSSO_LOC, "@%lld - %lld Write buffer gap %lld, fill %lld.",
  206. now, end, gap, bytes_written / frame_size());
  207. _write_position += bytes_written / frame_size();
  208. } else if (position == _write_position) {
  209. // Write as much as currently possible.
  210. std::size_t write_limit = buffer.remaining();
  211. std::size_t bytes_written = 0;
  212. ok = write_io(buffer.position(), write_limit, bytes_written);
  213. _write_position += bytes_written / frame_size();
  214. buffer.advance(bytes_written);
  215. }
  216. // Make sure buffers finish in time, despite irregular progress (freewheel).
  217. freewheel_finish(buffer, end, now);
  218. return ok;
  219. }
  220. private:
  221. // Calculate write position of the remaining buffer.
  222. std::int64_t buffer_position(std::size_t remaining, std::int64_t end) const {
  223. return end - (remaining / frame_size());
  224. }
  225. // Indicate that a buffer doesn't need further processing.
  226. bool buffer_done(const Buffer &buffer, std::int64_t end) const {
  227. return buffer.done() && end <= _write_position;
  228. }
  229. // Avoid stalled buffers with irregular OSS progress in freewheel mode.
  230. std::int64_t freewheel_finish(Buffer &buffer, std::int64_t end,
  231. std::int64_t now) {
  232. std::int64_t advance = 0;
  233. // Make sure buffers finish in time, despite irregular progress (freewheel).
  234. if (freewheel() && now >= end + balance() && !buffer.done()) {
  235. advance = buffer.advance(buffer.remaining()) / frame_size();
  236. Log::info(SOSSO_LOC,
  237. "@%lld - %lld Write freewheel finish remaining buffer %lld.",
  238. now, end, advance);
  239. }
  240. return advance;
  241. }
  242. // Skip writing part of the buffer to match OSS write position.
  243. std::int64_t buffer_advance(Buffer &buffer, std::int64_t frames) {
  244. if (frames > 0) {
  245. return buffer.advance(frames * frame_size()) / frame_size();
  246. }
  247. return 0;
  248. }
  249. // Rewind part of the buffer to match OSS write postion.
  250. std::int64_t buffer_rewind(Buffer &buffer, std::int64_t frames) {
  251. if (frames > 0) {
  252. return buffer.rewind(frames * frame_size()) / frame_size();
  253. }
  254. return 0;
  255. }
  256. std::int64_t _oss_progress = 0; // Last memory mapped OSS progress.
  257. std::int64_t _write_position = 0; // Current write position of the channel.
  258. };
  259. } // namespace sosso
  260. #endif // SOSSO_WRITECHANNEL_HPP