URY playd
C++ minimalist audio player
SdlAudioSink Class Reference

An output stream for audio, using SDL. More...

#include <audio_sink.hpp>

+ Inheritance diagram for SdlAudioSink:
+ Collaboration diagram for SdlAudioSink:

Public Member Functions

 SdlAudioSink (const AudioSource &source, int device_id)
 Constructs an SdlAudioSink. More...
 
 ~SdlAudioSink () override
 Destructs an SdlAudioSink.
 
void Start () override
 Starts the audio stream. More...
 
void Stop () override
 Stops the audio stream. More...
 
Audio::State State () override
 Gets this AudioSink's current state (playing/stopped/at end). More...
 
std::uint64_t Position () override
 Gets the current played position in the song, in samples. More...
 
void SetPosition (std::uint64_t samples) override
 Sets the current played position, given a position in samples. More...
 
void SourceOut () override
 Tells this AudioSink that the source has run out. More...
 
void Transfer (TransferIterator &start, const TransferIterator &end) override
 Transfers a range of sample bytes into the AudioSink. More...
 
void Callback (std::uint8_t *out, int nbytes)
 The callback proper. More...
 
- Public Member Functions inherited from AudioSink
virtual ~AudioSink ()=default
 Virtual, empty destructor for AudioSink.
 

Static Public Member Functions

static std::vector< std::pair< int, std::string > > GetDevicesInfo ()
 Gets the number and name of each output device entry in the AudioSystem. More...
 
static bool IsOutputDevice (int id)
 Can a sound device output sound? More...
 
static void InitLibrary ()
 Initialises the AudioSink's libraries, if not initialised already.
 
static void CleanupLibrary ()
 Cleans up the AudioSink's libraries, if not cleaned up already.
 

Private Attributes

SDL_AudioDeviceID device
 The SDL device to which we are outputting sound.
 
size_t bytes_per_sample
 Number of bytes in one sample.
 
RingBuffer ring_buf
 The ring buffer used to transfer samples to the playing callback.
 
std::uint64_t position_sample_count
 The current position, in samples.
 
bool source_out
 Whether the source has run out of things to feed the sink.
 
Audio::State state
 The decoder's current state.
 

Static Private Attributes

static const size_t RINGBUF_POWER = 16
 n, where 2^n is the capacity of the Audio ring buffer. More...
 
static const std::array< SDL_AudioFormat, SAMPLE_FORMAT_COUNTFORMATS
 Mapping from SampleFormats to their equivalent SDL_AudioFormats. More...
 

Additional Inherited Members

- Public Types inherited from AudioSink
using TransferIterator = AudioSource::DecodeVector::iterator
 Type of iterators used in the Transfer() method.
 

Detailed Description

An output stream for audio, using SDL.

An SdlAudioSink consists of an SDL output device and a buffer that stores decoded samples from the Audio object. While active, the SdlAudioSink periodically transfers samples from its buffer to SDL2 in a separate thread.

Definition at line 108 of file audio_sink.hpp.

Constructor & Destructor Documentation

§ SdlAudioSink()

SdlAudioSink::SdlAudioSink ( const AudioSource source,
int  device_id 
)

Constructs an SdlAudioSink.

Parameters
sourceThe source from which this sink will receive audio.
device_idThe device ID to which this sink will output.

Definition at line 61 of file audio_sink.cpp.

References AudioSource::ChannelCount(), FORMATS, AudioSource::OutputSampleFormat(), and AudioSource::SampleRate().

62  : bytes_per_sample(source.BytesPerSample()),
65  source_out(false),
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 }
virtual std::uint8_t ChannelCount() const =0
Returns the channel count.
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
std::uint64_t position_sample_count
The current position, in samples.
Definition: audio_sink.hpp:177
bool source_out
Whether the source has run out of things to feed the sink.
Definition: audio_sink.hpp:180
virtual size_t BytesPerSample() const
Returns the number of bytes for each sample this decoder outputs.
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 bytes_per_sample
Number of bytes in one sample.
Definition: audio_sink.hpp:171
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.

Member Function Documentation

§ Callback()

void SdlAudioSink::Callback ( std::uint8_t *  out,
int  nbytes 
)

The callback proper.

This is executed in a separate thread by SDL once a stream is playing with the callback registered to it.

Parameters
outThe output buffer to which our samples should be written.
nbytesThe number of bytes SDL wants to read from out.

Definition at line 195 of file audio_sink.cpp.

References Audio::AT_END, bytes_per_sample, Audio::PLAYING, position_sample_count, RingBuffer::Read(), RingBuffer::ReadCapacity(), ring_buf, source_out, and state.

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 }
The Audio is currently playing.
The Audio has ended and can&#39;t play without a seek.
RingBuffer ring_buf
The ring buffer used to transfer samples to the playing callback.
Definition: audio_sink.hpp:174
std::uint64_t position_sample_count
The current position, in samples.
Definition: audio_sink.hpp:177
unsigned long Read(char *start, unsigned long count)
Reads samples from the ring buffer into an array.
Definition: ringbuffer.cpp:65
bool source_out
Whether the source has run out of things to feed the sink.
Definition: audio_sink.hpp:180
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
size_t bytes_per_sample
Number of bytes in one sample.
Definition: audio_sink.hpp:171

§ GetDevicesInfo()

std::vector< std::pair< int, std::string > > SdlAudioSink::GetDevicesInfo ( )
static

Gets the number and name of each output device entry in the AudioSystem.

Returns
List of output devices, as strings.

Definition at line 241 of file audio_sink.cpp.

Referenced by ExitWithUsage().

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 }

§ IsOutputDevice()

bool SdlAudioSink::IsOutputDevice ( int  id)
static

Can a sound device output sound?

Parameters
idDevice ID.
Returns
If the device can handle outputting sound.

Definition at line 255 of file audio_sink.cpp.

Referenced by GetDeviceID().

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 }

§ Position()

std::uint64_t SdlAudioSink::Position ( )
overridevirtual

Gets the current played position in the song, in samples.

As this may be executing whilst the playing callback is running, do not expect it to be highly accurate.

Returns
The current position, as a count of elapsed samples.

Implements AudioSink.

Definition at line 143 of file audio_sink.cpp.

References position_sample_count.

144 {
145  return this->position_sample_count;
146 }
std::uint64_t position_sample_count
The current position, in samples.
Definition: audio_sink.hpp:177

§ SetPosition()

void SdlAudioSink::SetPosition ( std::uint64_t  samples)
overridevirtual

Sets the current played position, given a position in samples.

This flushes out the AudioSink ready to receive sample data from the new position.

Parameters
samplesThe new position, as a count of elapsed samples.
See also
Position

Implements AudioSink.

Definition at line 148 of file audio_sink.cpp.

References Audio::AT_END, RingBuffer::Flush(), position_sample_count, ring_buf, source_out, state, Stop(), and Audio::STOPPED.

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 }
The Audio has ended and can&#39;t play without a seek.
RingBuffer ring_buf
The ring buffer used to transfer samples to the playing callback.
Definition: audio_sink.hpp:174
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
bool source_out
Whether the source has run out of things to feed the sink.
Definition: audio_sink.hpp:180
Audio::State state
The decoder&#39;s current state.
Definition: audio_sink.hpp:183
void Flush()
Empties the ring buffer.
Definition: ringbuffer.cpp:74
The Audio has been stopped, or not yet played.

§ SourceOut()

void SdlAudioSink::SourceOut ( )
overridevirtual

Tells this AudioSink that the source has run out.

When this occurs, the next time the ringbuf goes empty, the sink has also run out and should stop.

Implements AudioSink.

Definition at line 135 of file audio_sink.cpp.

References Audio::AT_END, source_out, and state.

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 }
The Audio has ended and can&#39;t play without a seek.
bool source_out
Whether the source has run out of things to feed the sink.
Definition: audio_sink.hpp:180
Audio::State state
The decoder&#39;s current state.
Definition: audio_sink.hpp:183

§ Start()

void SdlAudioSink::Start ( )
overridevirtual

Starts the audio stream.

See also
Stop
IsHalted

Implements AudioSink.

Definition at line 114 of file audio_sink.cpp.

References device, Audio::PLAYING, state, and Audio::STOPPED.

115 {
116  if (this->state != Audio::State::STOPPED) return;
117 
118  SDL_PauseAudioDevice(this->device, 0);
120 }
The Audio is currently playing.
SDL_AudioDeviceID device
The SDL device to which we are outputting sound.
Definition: audio_sink.hpp:161
Audio::State state
The decoder&#39;s current state.
Definition: audio_sink.hpp:183
The Audio has been stopped, or not yet played.

§ State()

Audio::State SdlAudioSink::State ( )
overridevirtual

Gets this AudioSink's current state (playing/stopped/at end).

Returns
The Audio::State representing this AudioSink's state.
See also
Audio::State

Reimplemented from AudioSink.

Definition at line 130 of file audio_sink.cpp.

References state.

131 {
132  return this->state;
133 }
Audio::State state
The decoder&#39;s current state.
Definition: audio_sink.hpp:183

§ Stop()

void SdlAudioSink::Stop ( )
overridevirtual

Stops the audio stream.

See also
Start
IsHalted

Implements AudioSink.

Definition at line 122 of file audio_sink.cpp.

References device, state, and Audio::STOPPED.

Referenced by SetPosition().

123 {
124  if (this->state == Audio::State::STOPPED) return;
125 
126  SDL_PauseAudioDevice(this->device, 1);
128 }
SDL_AudioDeviceID device
The SDL device to which we are outputting sound.
Definition: audio_sink.hpp:161
Audio::State state
The decoder&#39;s current state.
Definition: audio_sink.hpp:183
The Audio has been stopped, or not yet played.

§ Transfer()

void SdlAudioSink::Transfer ( TransferIterator start,
const TransferIterator end 
)
overridevirtual

Transfers a range of sample bytes into the AudioSink.

The range may be empty, but must be valid.

  • Precondition: start <= end, start and end point to a valid contiguous block of sample bytes.
  • Postcondition: start <= end, old(start) <= start, *start and end point to a valid contiguous block of sample bytes.
Parameters
startAn iterator denoting the start of the range. This iterator will be advanced by the number of bytes accepted.
endAn iterator denoting the end of the range.

Implements AudioSink.

Definition at line 165 of file audio_sink.cpp.

References bytes_per_sample, ring_buf, RingBuffer::Write(), and RingBuffer::WriteCapacity().

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 }
RingBuffer ring_buf
The ring buffer used to transfer samples to the playing callback.
Definition: audio_sink.hpp:174
size_t WriteCapacity() const
The current write capacity.
Definition: ringbuffer.cpp:46
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

Member Data Documentation

§ FORMATS

const std::array< SDL_AudioFormat, SAMPLE_FORMAT_COUNT > SdlAudioSink::FORMATS
staticprivate
Initial value:
{{
AUDIO_U8,
AUDIO_S8,
AUDIO_S16,
AUDIO_S32,
AUDIO_F32
}}

Mapping from SampleFormats to their equivalent SDL_AudioFormats.

Definition at line 168 of file audio_sink.hpp.

Referenced by SdlAudioSink(), and AudioSink::State().

§ RINGBUF_POWER

const size_t SdlAudioSink::RINGBUF_POWER = 16
staticprivate

n, where 2^n is the capacity of the Audio ring buffer.

See also
RINGBUF_SIZE

Definition at line 165 of file audio_sink.hpp.

Referenced by AudioSink::State().


The documentation for this class was generated from the following files: