mirror of https://github.com/stenzek/duckstation
				
				
				
			common: Add AudioStream class
							parent
							
								
									14d32c882a
								
							
						
					
					
						commit
						bc51cc6d7d
					
				@ -0,0 +1,201 @@
 | 
			
		||||
#include "audio_stream.h"
 | 
			
		||||
#include "YBaseLib/Assert.h"
 | 
			
		||||
#include "YBaseLib/Log.h"
 | 
			
		||||
Log_SetChannel(Audio);
 | 
			
		||||
 | 
			
		||||
AudioStream::AudioStream() = default;
 | 
			
		||||
 | 
			
		||||
AudioStream::~AudioStream() = default;
 | 
			
		||||
 | 
			
		||||
bool AudioStream::Reconfigure(u32 output_sample_rate /*= DefaultOutputSampleRate*/, u32 channels /*= 1*/,
 | 
			
		||||
                              u32 buffer_size /*= DefaultBufferSize*/, u32 buffer_count /*= DefaultBufferCount*/)
 | 
			
		||||
{
 | 
			
		||||
  if (IsDeviceOpen())
 | 
			
		||||
    CloseDevice();
 | 
			
		||||
 | 
			
		||||
  m_output_sample_rate = output_sample_rate;
 | 
			
		||||
  m_channels = channels;
 | 
			
		||||
  m_buffer_size = buffer_size;
 | 
			
		||||
  AllocateBuffers(buffer_count);
 | 
			
		||||
  m_output_paused = true;
 | 
			
		||||
 | 
			
		||||
  if (!OpenDevice())
 | 
			
		||||
  {
 | 
			
		||||
    EmptyBuffers();
 | 
			
		||||
    m_buffers.clear();
 | 
			
		||||
    m_buffer_size = 0;
 | 
			
		||||
    m_output_sample_rate = 0;
 | 
			
		||||
    m_channels = 0;
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioStream::PauseOutput(bool paused)
 | 
			
		||||
{
 | 
			
		||||
  if (m_output_paused == paused)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  PauseDevice(paused);
 | 
			
		||||
  m_output_paused = paused;
 | 
			
		||||
 | 
			
		||||
  // Empty buffers on pause.
 | 
			
		||||
  if (paused)
 | 
			
		||||
    EmptyBuffers();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioStream::Shutdown()
 | 
			
		||||
{
 | 
			
		||||
  if (!IsDeviceOpen())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  CloseDevice();
 | 
			
		||||
  EmptyBuffers();
 | 
			
		||||
  m_buffers.clear();
 | 
			
		||||
  m_buffer_size = 0;
 | 
			
		||||
  m_output_sample_rate = 0;
 | 
			
		||||
  m_channels = 0;
 | 
			
		||||
  m_output_paused = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioStream::BeginWrite(SampleType** buffer_ptr, u32* num_samples)
 | 
			
		||||
{
 | 
			
		||||
  m_buffer_lock.lock();
 | 
			
		||||
 | 
			
		||||
  if (m_num_free_buffers == 0)
 | 
			
		||||
    DropBuffer();
 | 
			
		||||
 | 
			
		||||
  Buffer& buffer = m_buffers[m_first_free_buffer];
 | 
			
		||||
  *buffer_ptr = buffer.data.data() + buffer.write_position;
 | 
			
		||||
  *num_samples = m_buffer_size - buffer.write_position;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioStream::WriteSamples(const SampleType* samples, u32 num_samples)
 | 
			
		||||
{
 | 
			
		||||
  std::lock_guard<std::mutex> guard(m_buffer_lock);
 | 
			
		||||
  u32 remaining_samples = num_samples;
 | 
			
		||||
 | 
			
		||||
  while (remaining_samples > 0)
 | 
			
		||||
  {
 | 
			
		||||
    if (m_num_free_buffers == 0)
 | 
			
		||||
      DropBuffer();
 | 
			
		||||
 | 
			
		||||
    Buffer& buffer = m_buffers[m_first_free_buffer];
 | 
			
		||||
    const u32 to_this_buffer = std::min(m_buffer_size - buffer.write_position, remaining_samples);
 | 
			
		||||
 | 
			
		||||
    const u32 copy_count = to_this_buffer * m_channels;
 | 
			
		||||
    std::memcpy(&buffer.data[buffer.write_position * m_channels], samples, copy_count * sizeof(SampleType));
 | 
			
		||||
    samples += copy_count;
 | 
			
		||||
 | 
			
		||||
    remaining_samples -= to_this_buffer;
 | 
			
		||||
    buffer.write_position += to_this_buffer;
 | 
			
		||||
 | 
			
		||||
    // End of the buffer?
 | 
			
		||||
    if (buffer.write_position == m_buffer_size)
 | 
			
		||||
    {
 | 
			
		||||
      // Reset it back to the start, and enqueue it.
 | 
			
		||||
      buffer.write_position = 0;
 | 
			
		||||
      m_num_free_buffers--;
 | 
			
		||||
      m_first_free_buffer = (m_first_free_buffer + 1) % m_buffers.size();
 | 
			
		||||
      m_num_available_buffers++;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioStream::EndWrite(u32 num_samples)
 | 
			
		||||
{
 | 
			
		||||
  Buffer& buffer = m_buffers[m_first_free_buffer];
 | 
			
		||||
  DebugAssert((buffer.write_position + num_samples) <= m_buffer_size);
 | 
			
		||||
  buffer.write_position += num_samples;
 | 
			
		||||
 | 
			
		||||
  // End of the buffer?
 | 
			
		||||
  if (buffer.write_position == m_buffer_size)
 | 
			
		||||
  {
 | 
			
		||||
    // Reset it back to the start, and enqueue it.
 | 
			
		||||
    // Log_DevPrintf("Enqueue buffer %u", m_first_free_buffer);
 | 
			
		||||
    buffer.write_position = 0;
 | 
			
		||||
    m_num_free_buffers--;
 | 
			
		||||
    m_first_free_buffer = (m_first_free_buffer + 1) % m_buffers.size();
 | 
			
		||||
    m_num_available_buffers++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_buffer_lock.unlock();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 AudioStream::ReadSamples(SampleType* samples, u32 num_samples)
 | 
			
		||||
{
 | 
			
		||||
  std::lock_guard<std::mutex> guard(m_buffer_lock);
 | 
			
		||||
  u32 remaining_samples = num_samples;
 | 
			
		||||
 | 
			
		||||
  while (remaining_samples > 0 && m_num_available_buffers > 0)
 | 
			
		||||
  {
 | 
			
		||||
    Buffer& buffer = m_buffers[m_first_available_buffer];
 | 
			
		||||
    const u32 from_this_buffer = std::min(m_buffer_size - buffer.read_position, remaining_samples);
 | 
			
		||||
 | 
			
		||||
    const u32 copy_count = from_this_buffer * m_channels;
 | 
			
		||||
    std::memcpy(samples, &buffer.data[buffer.read_position * m_channels], copy_count * sizeof(SampleType));
 | 
			
		||||
    samples += copy_count;
 | 
			
		||||
 | 
			
		||||
    remaining_samples -= from_this_buffer;
 | 
			
		||||
    buffer.read_position += from_this_buffer;
 | 
			
		||||
 | 
			
		||||
    if (buffer.read_position == m_buffer_size)
 | 
			
		||||
    {
 | 
			
		||||
      // Log_DevPrintf("Finish dequeing buffer %u", m_first_available_buffer);
 | 
			
		||||
      // End of this buffer.
 | 
			
		||||
      buffer.read_position = 0;
 | 
			
		||||
      m_num_available_buffers--;
 | 
			
		||||
      m_first_available_buffer = (m_first_available_buffer + 1) % m_buffers.size();
 | 
			
		||||
      m_num_free_buffers++;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return num_samples - remaining_samples;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioStream::AllocateBuffers(u32 buffer_count)
 | 
			
		||||
{
 | 
			
		||||
  m_buffers.resize(buffer_count);
 | 
			
		||||
  for (u32 i = 0; i < buffer_count; i++)
 | 
			
		||||
  {
 | 
			
		||||
    Buffer& buffer = m_buffers[i];
 | 
			
		||||
    buffer.data.resize(m_buffer_size * m_channels);
 | 
			
		||||
    buffer.read_position = 0;
 | 
			
		||||
    buffer.write_position = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_first_available_buffer = 0;
 | 
			
		||||
  m_num_available_buffers = 0;
 | 
			
		||||
  m_first_free_buffer = 0;
 | 
			
		||||
  m_num_free_buffers = buffer_count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioStream::DropBuffer()
 | 
			
		||||
{
 | 
			
		||||
  DebugAssert(m_num_available_buffers > 0);
 | 
			
		||||
  // Log_DevPrintf("Dropping buffer %u", m_first_free_buffer);
 | 
			
		||||
 | 
			
		||||
  // Out of space. We'll overwrite the oldest buffer with the new data.
 | 
			
		||||
  // At the same time, we shift the available buffer forward one.
 | 
			
		||||
  m_first_available_buffer = (m_first_available_buffer + 1) % m_buffers.size();
 | 
			
		||||
  m_num_available_buffers--;
 | 
			
		||||
 | 
			
		||||
  m_buffers[m_first_free_buffer].read_position = 0;
 | 
			
		||||
  m_buffers[m_first_free_buffer].write_position = 0;
 | 
			
		||||
  m_num_free_buffers++;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioStream::EmptyBuffers()
 | 
			
		||||
{
 | 
			
		||||
  for (Buffer& buffer : m_buffers)
 | 
			
		||||
  {
 | 
			
		||||
    buffer.read_position = 0;
 | 
			
		||||
    buffer.write_position = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_first_free_buffer = 0;
 | 
			
		||||
  m_num_free_buffers = static_cast<u32>(m_buffers.size());
 | 
			
		||||
  m_first_available_buffer = 0;
 | 
			
		||||
  m_num_available_buffers = 0;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,77 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "types.h"
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
// Uses signed 16-bits samples.
 | 
			
		||||
 | 
			
		||||
class AudioStream
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
  using SampleType = s16;
 | 
			
		||||
 | 
			
		||||
  enum
 | 
			
		||||
  {
 | 
			
		||||
    DefaultOutputSampleRate = 44100,
 | 
			
		||||
    DefaultBufferSize = 2048,
 | 
			
		||||
    DefaultBufferCount = 3,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  AudioStream();
 | 
			
		||||
  virtual ~AudioStream();
 | 
			
		||||
 | 
			
		||||
  u32 GetOutputSampleRate() const { return m_output_sample_rate; }
 | 
			
		||||
  u32 GetChannels() const { return m_channels; }
 | 
			
		||||
  u32 GetBufferSize() const { return m_buffer_size; }
 | 
			
		||||
  u32 GetBufferCount() const { return static_cast<u32>(m_buffers.size()); }
 | 
			
		||||
 | 
			
		||||
  bool Reconfigure(u32 output_sample_rate = DefaultOutputSampleRate, u32 channels = 1,
 | 
			
		||||
                   u32 buffer_size = DefaultBufferSize, u32 buffer_count = DefaultBufferCount);
 | 
			
		||||
 | 
			
		||||
  void PauseOutput(bool paused);
 | 
			
		||||
  void EmptyBuffers();
 | 
			
		||||
 | 
			
		||||
  void Shutdown();
 | 
			
		||||
 | 
			
		||||
  void BeginWrite(SampleType** buffer_ptr, u32* num_samples);
 | 
			
		||||
  void WriteSamples(const SampleType* samples, u32 num_samples);
 | 
			
		||||
  void EndWrite(u32 num_samples);
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
  virtual bool OpenDevice() = 0;
 | 
			
		||||
  virtual void PauseDevice(bool paused) = 0;
 | 
			
		||||
  virtual void CloseDevice() = 0;
 | 
			
		||||
 | 
			
		||||
  bool IsDeviceOpen() const { return (m_output_sample_rate > 0); }
 | 
			
		||||
 | 
			
		||||
  u32 ReadSamples(SampleType* samples, u32 num_samples);
 | 
			
		||||
 | 
			
		||||
  u32 m_output_sample_rate = 0;
 | 
			
		||||
  u32 m_channels = 0;
 | 
			
		||||
  u32 m_buffer_size = 0;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  struct Buffer
 | 
			
		||||
  {
 | 
			
		||||
    std::vector<SampleType> data;
 | 
			
		||||
    u32 write_position;
 | 
			
		||||
    u32 read_position;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  void AllocateBuffers(u32 buffer_count);
 | 
			
		||||
  void DropBuffer();
 | 
			
		||||
 | 
			
		||||
  std::vector<Buffer> m_buffers;
 | 
			
		||||
  std::mutex m_buffer_lock;
 | 
			
		||||
 | 
			
		||||
  // For input.
 | 
			
		||||
  u32 m_first_free_buffer = 0;
 | 
			
		||||
  u32 m_num_free_buffers = 0;
 | 
			
		||||
 | 
			
		||||
  // For output.
 | 
			
		||||
  u32 m_num_available_buffers = 0;
 | 
			
		||||
  u32 m_first_available_buffer = 0;
 | 
			
		||||
 | 
			
		||||
  bool m_output_paused = true;
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,61 @@
 | 
			
		||||
#include "sdl_audio_stream.h"
 | 
			
		||||
#include "YBaseLib/Assert.h"
 | 
			
		||||
#include "YBaseLib/Log.h"
 | 
			
		||||
#include <SDL.h>
 | 
			
		||||
Log_SetChannel(SDLAudioStream);
 | 
			
		||||
 | 
			
		||||
SDLAudioStream::SDLAudioStream() = default;
 | 
			
		||||
 | 
			
		||||
SDLAudioStream::~SDLAudioStream()
 | 
			
		||||
{
 | 
			
		||||
  if (m_is_open)
 | 
			
		||||
    SDLAudioStream::CloseDevice();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SDLAudioStream::OpenDevice()
 | 
			
		||||
{
 | 
			
		||||
  DebugAssert(!m_is_open);
 | 
			
		||||
 | 
			
		||||
  SDL_AudioSpec spec = {};
 | 
			
		||||
  spec.freq = m_output_sample_rate;
 | 
			
		||||
  spec.channels = m_channels;
 | 
			
		||||
  spec.format = AUDIO_S16;
 | 
			
		||||
  spec.samples = m_buffer_size;
 | 
			
		||||
  spec.callback = AudioCallback;
 | 
			
		||||
  spec.userdata = static_cast<void*>(this);
 | 
			
		||||
 | 
			
		||||
  SDL_AudioSpec obtained = {};
 | 
			
		||||
  if (SDL_OpenAudio(&spec, &obtained) < 0)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("SDL_OpenAudio failed");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_is_open = true;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SDLAudioStream::PauseDevice(bool paused)
 | 
			
		||||
{
 | 
			
		||||
  SDL_PauseAudio(paused ? 1 : 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SDLAudioStream::CloseDevice()
 | 
			
		||||
{
 | 
			
		||||
  DebugAssert(m_is_open);
 | 
			
		||||
  SDL_CloseAudio();
 | 
			
		||||
  m_is_open = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len)
 | 
			
		||||
{
 | 
			
		||||
  SDLAudioStream* const this_ptr = static_cast<SDLAudioStream*>(userdata);
 | 
			
		||||
  const u32 num_samples = len / sizeof(SampleType) / this_ptr->m_channels;
 | 
			
		||||
  const u32 read_samples = this_ptr->ReadSamples(reinterpret_cast<SampleType*>(stream), num_samples);
 | 
			
		||||
  const u32 silence_samples = num_samples - read_samples;
 | 
			
		||||
  if (silence_samples > 0)
 | 
			
		||||
  {
 | 
			
		||||
    std::memset(reinterpret_cast<SampleType*>(stream) + (read_samples * this_ptr->m_channels), 0,
 | 
			
		||||
                silence_samples * this_ptr->m_channels * sizeof(SampleType));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "common/audio_stream.h"
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
class SDLAudioStream final : public AudioStream
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
  SDLAudioStream();
 | 
			
		||||
  ~SDLAudioStream();
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
  bool OpenDevice() override;
 | 
			
		||||
  void PauseDevice(bool paused) override;
 | 
			
		||||
  void CloseDevice() override;
 | 
			
		||||
 | 
			
		||||
  static void AudioCallback(void* userdata, uint8_t* stream, int len);
 | 
			
		||||
 | 
			
		||||
  bool m_is_open = false;
 | 
			
		||||
};
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue