URY playd
C++ minimalist audio player
audio_sink.cpp
Go to the documentation of this file.
1 // This file is part of playd.
2 // playd is licensed under the MIT licence: see LICENSE.txt.
3 
10 #include <array>
11 #include <algorithm>
12 #include <cassert>
13 #include <climits>
14 #include <cstring>
15 #include <memory>
16 #include <string>
17 
18 #include "SDL.h"
19 
20 #include "../errors.hpp"
21 #include "../messages.h"
22 #include "audio_sink.hpp"
23 #include "audio_source.hpp"
24 #include "ringbuffer.hpp"
25 #include "sample_formats.hpp"
26 
27 //
28 // AudioSink
29 //
30 
32 {
33  return Audio::State::NONE;
34 }
35 
36 //
37 // SdlAudioSink
38 //
39 
40 const size_t SdlAudioSink::RINGBUF_POWER = 16;
41 
42 /* static */ const std::array<SDL_AudioFormat, SAMPLE_FORMAT_COUNT> SdlAudioSink::FORMATS {{
43  AUDIO_U8, // PACKED_UNSIGNED_INT_8
44  AUDIO_S8, // PACKED_SIGNED_INT_8
45  AUDIO_S16, // PACKED_SIGNED_INT_16
46  AUDIO_S32, // PACKED_SIGNED_INT_32
47  AUDIO_F32 // PACKED_FLOAT_32
48 }};
49 
54 static void SDLCallback(void *vsink, std::uint8_t *data, int len)
55 {
56  assert(vsink != nullptr);
57  auto sink = static_cast<SdlAudioSink *>(vsink);
58  sink->Callback(data, len);
59 }
60 
61 SdlAudioSink::SdlAudioSink(const AudioSource &source, int device_id)
62  : bytes_per_sample(source.BytesPerSample()),
63  ring_buf(RINGBUF_POWER, source.BytesPerSample()),
64  position_sample_count(0),
65  source_out(false),
66  state(Audio::State::STOPPED)
67 {
68  const char *name = SDL_GetAudioDeviceName(device_id, 0);
69  if (name == nullptr) {
70  throw ConfigError(std::string("invalid device id: ") +
71  std::to_string(device_id));
72  }
73 
74  SDL_AudioSpec want;
75  SDL_zero(want);
76  want.freq = source.SampleRate();
77  want.format = FORMATS[static_cast<int>(source.OutputSampleFormat())];
78  want.channels = source.ChannelCount();
79  want.callback = &SDLCallback;
80  want.userdata = (void *)this;
81 
82  SDL_AudioSpec have;
83  SDL_zero(have);
84 
85  this->device = SDL_OpenAudioDevice(name, 0, &want, &have, 0);
86  if (this->device == 0) {
87  throw ConfigError(std::string("couldn't open device: ") +
88  SDL_GetError());
89  }
90 }
91 
93 {
94  if (this->device == 0) return;
95 
96  // Silence any currently playing audio.
97  SDL_PauseAudioDevice(this->device, SDL_TRUE);
98  SDL_CloseAudioDevice(this->device);
99 }
100 
101 /* static */ void SdlAudioSink::InitLibrary()
102 {
103  if (SDL_Init(SDL_INIT_AUDIO) != 0) {
104  throw ConfigError(std::string("could not initialise SDL: ") +
105  SDL_GetError());
106  }
107 }
108 
109 /* static */ void SdlAudioSink::CleanupLibrary()
110 {
111  SDL_Quit();
112 }
113 
115 {
116  if (this->state != Audio::State::STOPPED) return;
117 
118  SDL_PauseAudioDevice(this->device, 0);
120 }
121 
123 {
124  if (this->state == Audio::State::STOPPED) return;
125 
126  SDL_PauseAudioDevice(this->device, 1);
128 }
129 
131 {
132  return this->state;
133 }
134 
136 {
137  // The sink should only be out if the source is.
138  assert(this->source_out || this->state != Audio::State::AT_END);
139 
140  this->source_out = true;
141 }
142 
143 std::uint64_t SdlAudioSink::Position()
144 {
145  return this->position_sample_count;
146 }
147 
148 void SdlAudioSink::SetPosition(std::uint64_t samples)
149 {
150  this->position_sample_count = samples;
151 
152  // We might have been at the end of the file previously.
153  // If so, we might not be now, so clear the out flags.
154  this->source_out = false;
155  if (this->state == Audio::State::AT_END) {
157  this->Stop();
158  }
159 
160  // The ringbuf will have been full of samples from the old
161  // position, so we need to get rid of them.
162  this->ring_buf.Flush();
163 }
164 
166  const AudioSink::TransferIterator &end)
167 {
168  assert(start <= end);
169 
170  // No point transferring 0 bytes.
171  if (start == end) return;
172 
173  unsigned long bytes = std::distance(start, end);
174  // There should be a whole number of samples being transferred.
175  assert(bytes % bytes_per_sample == 0);
176  assert(0 < bytes);
177 
178  size_t samples = bytes / this->bytes_per_sample;
179 
180  // Only transfer as many samples as the ring buffer can take.
181  // Don't bother trying to write 0 samples!
182  auto count = std::min(samples, this->ring_buf.WriteCapacity());
183  if (count == 0) return;
184 
185  auto start_ptr = reinterpret_cast<char *>(&*start);
186  unsigned long written_count = this->ring_buf.Write(start_ptr, count);
187  // Since we never write more than the ring buffer can take, the written
188  // count should equal the requested written count.
189  assert(written_count == count);
190 
191  start += (written_count * this->bytes_per_sample);
192  assert(start <= end);
193 }
194 
195 void SdlAudioSink::Callback(std::uint8_t *out, int nbytes)
196 {
197  assert(out != nullptr);
198 
199  assert(0 <= nbytes);
200  unsigned long lnbytes = static_cast<unsigned long>(nbytes);
201 
202  // Make sure anything not filled up with sound later is set to silence.
203  // This is slightly inefficient (two writes to sound-filled regions
204  // instead of one), but more elegant in failure cases.
205  memset(out, 0, lnbytes);
206 
207  // If we're not supposed to be playing, don't play anything.
208  if (this->state != Audio::State::PLAYING) return;
209 
210  // Let's find out how many samples are available in total to give SDL.
211  //
212  // Note: Since we run concurrently with the decoder, which is also
213  // trying to modify the read capacity of the ringbuf (by adding
214  // things), this technically causes a race condition when we try to
215  // read `avail_samples` number of samples later. Not to fear: the
216  // actual read capacity can only be greater than or equal to
217  // `avail_samples`, as this is the only place where we can *decrease*
218  // it.
219  auto avail_samples = this->ring_buf.ReadCapacity();
220 
221  // Have we run out of things to feed?
222  if (avail_samples == 0) {
223  // Is this a temporary condition, or have we genuinely played
224  // out all we can? If the latter, we're now out too.
225  if (this->source_out) this->state = Audio::State::AT_END;
226 
227  // Don't even bother reading from the ring buffer.
228  return;
229  }
230 
231  // How many samples do we want to pull out of the ring buffer?
232  size_t req_samples = lnbytes / this->bytes_per_sample;
233 
234  // How many can we pull out? Send this amount to SDL.
235  auto samples = std::min(req_samples, avail_samples);
236  auto read_samples =
237  this->ring_buf.Read(reinterpret_cast<char *>(out), samples);
238  this->position_sample_count += read_samples;
239 }
240 
241 /* static */ std::vector<std::pair<int, std::string>> SdlAudioSink::GetDevicesInfo()
242 {
243  std::vector<std::pair<int, std::string>> list;
244 
245  // The 0 in SDL_GetNumAudioDevices tells SDL we want playback devices.
246  int is = SDL_GetNumAudioDevices(0);
247  for (int i = 0; i < is; i++) {
248  const char *n = SDL_GetAudioDeviceName(i, 0);
249  if (n != nullptr) list.emplace_back(i, std::string(n));
250  }
251 
252  return list;
253 }
254 
255 /* static */ bool SdlAudioSink::IsOutputDevice(int id)
256 {
257  int ids = SDL_GetNumAudioDevices(0);
258 
259  // See comment in GetDevicesInfo for why this is sufficient.
260  return (0 <= id && id < ids);
261 }
void SourceOut() override
Tells this AudioSink that the source has run out.
Definition: audio_sink.cpp:135
Declaration of the AudioSource class.
State
Enumeration of possible states for this Audio.
Definition: audio.hpp:42
static void CleanupLibrary()
Cleans up the AudioSink&#39;s libraries, if not cleaned up already.
Definition: audio_sink.cpp:109
void SetPosition(std::uint64_t samples) override
Sets the current played position, given a position in samples.
Definition: audio_sink.cpp:148
An object responsible for decoding an audio file.
The Audio is currently playing.
virtual std::uint8_t ChannelCount() const =0
Returns the channel count.
The Audio has ended and can&#39;t play without a seek.
An output stream for audio, using SDL.
Definition: audio_sink.hpp:108
AudioSource::DecodeVector::iterator TransferIterator
Type of iterators used in the Transfer() method.
Definition: audio_sink.hpp:32
void Transfer(TransferIterator &start, const TransferIterator &end) override
Transfers a range of sample bytes into the AudioSink.
Definition: audio_sink.cpp:165
RingBuffer ring_buf
The ring buffer used to transfer samples to the playing callback.
Definition: audio_sink.hpp:174
virtual SampleFormat OutputSampleFormat() const =0
Returns the output sample format.
SDL_AudioDeviceID device
The SDL device to which we are outputting sound.
Definition: audio_sink.hpp:161
void Callback(std::uint8_t *out, int nbytes)
The callback proper.
Definition: audio_sink.cpp:195
void Stop() override
Stops the audio stream.
Definition: audio_sink.cpp:122
std::uint64_t position_sample_count
The current position, in samples.
Definition: audio_sink.hpp:177
There is no Audio.
unsigned long Read(char *start, unsigned long count)
Reads samples from the ring buffer into an array.
Definition: ringbuffer.cpp:65
virtual Audio::State State()
Gets this AudioSink&#39;s current state (playing/stopped/at end).
Definition: audio_sink.cpp:31
static bool IsOutputDevice(int id)
Can a sound device output sound?
Definition: audio_sink.cpp:255
size_t WriteCapacity() const
The current write capacity.
Definition: ringbuffer.cpp:46
std::uint64_t Position() override
Gets the current played position in the song, in samples.
Definition: audio_sink.cpp:143
Audio::State State() override
Gets this AudioSink&#39;s current state (playing/stopped/at end).
Definition: audio_sink.cpp:130
static void InitLibrary()
Initialises the AudioSink&#39;s libraries, if not initialised already.
Definition: audio_sink.cpp:101
Declaration of the AudioSink class.
The RingBuffer class template.
bool source_out
Whether the source has run out of things to feed the sink.
Definition: audio_sink.hpp:180
SdlAudioSink(const AudioSource &source, int device_id)
Constructs an SdlAudioSink.
Definition: audio_sink.cpp:61
void Start() override
Starts the audio stream.
Definition: audio_sink.cpp:114
static const std::array< SDL_AudioFormat, SAMPLE_FORMAT_COUNT > FORMATS
Mapping from SampleFormats to their equivalent SDL_AudioFormats.
Definition: audio_sink.hpp:168
An Error signifying that playd has been improperly configured.
Definition: errors.hpp:45
virtual std::uint32_t SampleRate() const =0
Returns the sample rate.
Audio::State state
The decoder&#39;s current state.
Definition: audio_sink.hpp:183
size_t ReadCapacity() const
The current read capacity.
Definition: ringbuffer.cpp:51
unsigned long Write(const char *start, unsigned long count)
Writes samples from an array into the ring buffer.
Definition: ringbuffer.cpp:56
size_t bytes_per_sample
Number of bytes in one sample.
Definition: audio_sink.hpp:171
The SampleFormat enumeration and related declarations.
~SdlAudioSink() override
Destructs an SdlAudioSink.
Definition: audio_sink.cpp:92
static std::vector< std::pair< int, std::string > > GetDevicesInfo()
Gets the number and name of each output device entry in the AudioSystem.
Definition: audio_sink.cpp:241
void Flush()
Empties the ring buffer.
Definition: ringbuffer.cpp:74
static const size_t RINGBUF_POWER
n, where 2^n is the capacity of the Audio ring buffer.
Definition: audio_sink.hpp:165
The Audio has been stopped, or not yet played.
An audio item.
Definition: audio.hpp:35