common: Add AudioStream class
This commit is contained in:
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;
|
||||||
|
};
|
|
@ -36,6 +36,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="audio.h" />
|
<ClInclude Include="audio.h" />
|
||||||
|
<ClInclude Include="audio_stream.h" />
|
||||||
<ClInclude Include="bitfield.h" />
|
<ClInclude Include="bitfield.h" />
|
||||||
<ClInclude Include="cd_image.h" />
|
<ClInclude Include="cd_image.h" />
|
||||||
<ClInclude Include="display.h" />
|
<ClInclude Include="display.h" />
|
||||||
|
@ -58,6 +59,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="audio.cpp" />
|
<ClCompile Include="audio.cpp" />
|
||||||
|
<ClCompile Include="audio_stream.cpp" />
|
||||||
<ClCompile Include="cd_image.cpp" />
|
<ClCompile Include="cd_image.cpp" />
|
||||||
<ClCompile Include="display.cpp" />
|
<ClCompile Include="display.cpp" />
|
||||||
<ClCompile Include="display_renderer_d3d.cpp" />
|
<ClCompile Include="display_renderer_d3d.cpp" />
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
<ClInclude Include="gl_texture.h" />
|
<ClInclude Include="gl_texture.h" />
|
||||||
<ClInclude Include="fifo_queue.h" />
|
<ClInclude Include="fifo_queue.h" />
|
||||||
<ClInclude Include="cd_image.h" />
|
<ClInclude Include="cd_image.h" />
|
||||||
|
<ClInclude Include="audio_stream.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="hdd_image.cpp" />
|
<ClCompile Include="hdd_image.cpp" />
|
||||||
|
@ -38,6 +39,7 @@
|
||||||
<ClCompile Include="gl_program.cpp" />
|
<ClCompile Include="gl_program.cpp" />
|
||||||
<ClCompile Include="gl_texture.cpp" />
|
<ClCompile Include="gl_texture.cpp" />
|
||||||
<ClCompile Include="cd_image.cpp" />
|
<ClCompile Include="cd_image.cpp" />
|
||||||
|
<ClCompile Include="audio_stream.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="bitfield.natvis" />
|
<Natvis Include="bitfield.natvis" />
|
||||||
|
|
|
@ -50,12 +50,14 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="sdl_audio_mixer.cpp" />
|
<ClCompile Include="sdl_audio_mixer.cpp" />
|
||||||
|
<ClCompile Include="sdl_audio_stream.cpp" />
|
||||||
<ClCompile Include="sdl_interface.cpp" />
|
<ClCompile Include="sdl_interface.cpp" />
|
||||||
<ClCompile Include="main.cpp" />
|
<ClCompile Include="main.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="icon.h" />
|
<ClInclude Include="icon.h" />
|
||||||
<ClInclude Include="sdl_audio_mixer.h" />
|
<ClInclude Include="sdl_audio_mixer.h" />
|
||||||
|
<ClInclude Include="sdl_audio_stream.h" />
|
||||||
<ClInclude Include="sdl_interface.h" />
|
<ClInclude Include="sdl_interface.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Globals">
|
<PropertyGroup Label="Globals">
|
||||||
|
|
|
@ -4,10 +4,12 @@
|
||||||
<ClCompile Include="main.cpp" />
|
<ClCompile Include="main.cpp" />
|
||||||
<ClCompile Include="sdl_audio_mixer.cpp" />
|
<ClCompile Include="sdl_audio_mixer.cpp" />
|
||||||
<ClCompile Include="sdl_interface.cpp" />
|
<ClCompile Include="sdl_interface.cpp" />
|
||||||
|
<ClCompile Include="sdl_audio_stream.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="sdl_audio_mixer.h" />
|
<ClInclude Include="sdl_audio_mixer.h" />
|
||||||
<ClInclude Include="sdl_interface.h" />
|
<ClInclude Include="sdl_interface.h" />
|
||||||
<ClInclude Include="icon.h" />
|
<ClInclude Include="icon.h" />
|
||||||
|
<ClInclude Include="sdl_audio_stream.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -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