Putting the puzzle together (Sound::PlaybackLength)
This commit is contained in:
@@ -29,14 +29,30 @@ namespace ReMixer
|
||||
class PulseAudioSubsystem : public SoundSubsystem
|
||||
{
|
||||
std::map<std::string, PulseStream> streams;
|
||||
public:
|
||||
Event<> OnReady;
|
||||
Event<> OnTerminate;
|
||||
Event<> OnFail;
|
||||
Event<> OnStateChange;
|
||||
Event<bool> OnSuccess;
|
||||
Event<const pa_server_info*> OnServerInfo;
|
||||
public:
|
||||
/// Constructs a new PulseAudio context with the given name.
|
||||
/// @param context_name The context name. All streams created with this context will be grouped together by this name.
|
||||
PulseAudioSubsystem(const std::string& context_name);
|
||||
|
||||
void Play(const PulseStream& stream, const Sound& sound);
|
||||
/// Plays the given sound over the given audio stream.
|
||||
void Play(PulseStream& stream, const Sound& sound);
|
||||
|
||||
bool Connected();
|
||||
void ListDevices();
|
||||
|
||||
/// Returns the name of the PulseAudio context.
|
||||
std::string ContextName() const;
|
||||
|
||||
void SetContextName(const std::string& ctx_name);
|
||||
|
||||
bool Connected() const;
|
||||
std::string ServerName() const;
|
||||
|
||||
PulseStream CreateStream(const std::string& stream_name);
|
||||
protected:
|
||||
@@ -54,9 +70,22 @@ namespace ReMixer
|
||||
int operation_success;
|
||||
};
|
||||
private:
|
||||
|
||||
#pragma region PulseAudio Internal Callbacks
|
||||
static void OnCtxSuccess(pa_context* c, int success, void* userdata);
|
||||
static void OnCtxStateChange(pa_context* c, void* userdata);
|
||||
static void OnSinkDevice(pa_context* c, const pa_sink_info* info, int eol, void* userdata);
|
||||
static void OnSourceDevice(pa_context* c, const pa_source_info* info, int eol, void* userdata);
|
||||
static void OnListServerInfo(pa_context* c, const pa_server_info* i, void* userdata);
|
||||
static void OnListSinkInfo(pa_context* c, const pa_sink_info* i, int eol, void* userdata);
|
||||
static void OnStreamStateChange(pa_stream* s, void* userdata);
|
||||
static void OnStreamRequest(pa_stream* s, size_t length, void* userdata);
|
||||
#pragma endregion
|
||||
|
||||
pa_impl* pa;
|
||||
bool ctx_connected = false;
|
||||
std::string ctx_name;
|
||||
|
||||
|
||||
};
|
||||
}
|
@@ -21,5 +21,8 @@ namespace ReMixer {
|
||||
class PulseAudioSubsystem;
|
||||
class PulseStream;
|
||||
|
||||
|
||||
void Init();
|
||||
void Cleanup();
|
||||
}
|
||||
|
||||
|
@@ -73,13 +73,18 @@ namespace ReMixer {
|
||||
void LoadPCMBuffer(std::vector<char> buffer, uint sampleRate = 44.1_kHz, StreamMode streamMode = StreamMode::STEREO);
|
||||
|
||||
/// Returns the sample count of the sound.
|
||||
unsigned int SampleCount() const { return audio_data.size();};
|
||||
uint SampleCount() const;;
|
||||
|
||||
/// Returns the amount of bytes the sounds' data occupies.
|
||||
uint DataSizeInBytes() const;
|
||||
|
||||
/// Returns the samples-per-second of this sound.
|
||||
uint SampleRate() const { return sample_rate;}
|
||||
uint SampleRate() const;
|
||||
|
||||
StreamMode GetStreamMode() const;
|
||||
uint Channels() const;
|
||||
|
||||
float PlaybackLength();
|
||||
|
||||
/// Returns a pointer to the beginning of the audio data
|
||||
char* ptr() { return audio_data.data();}
|
||||
|
@@ -34,7 +34,7 @@ namespace ReMixer {
|
||||
/// TODO: It may not be possible to actually change these parameters after creation of the stream object.
|
||||
void SetStreamDirection(const StreamDirection& direction);
|
||||
void SetName(const std::string& name);
|
||||
void SetSampleRate(uint sample_rate);
|
||||
virtual void SetSampleRate(uint sample_rate);
|
||||
void SetChannelCount(uint channel_count);
|
||||
|
||||
public:
|
||||
@@ -59,8 +59,11 @@ namespace ReMixer {
|
||||
|
||||
// PulseStream(PulseAudioSubsystem* parent_system) : Stream(parent_system) { }
|
||||
|
||||
void SetSampleRate(uint sample_rate) override;
|
||||
|
||||
pa_stream* stream;
|
||||
|
||||
static void OnStreamSuccess(pa_stream* s, int success, void* userdata);
|
||||
static void OnStreamStateChanged(pa_stream* s, void* userdata);
|
||||
static void OnStreamRequest(pa_stream* s, size_t length, void* userdata);
|
||||
static void OnLatencyUpdate(pa_stream* s, void* userdata);
|
||||
|
26
main.cpp
26
main.cpp
@@ -19,10 +19,8 @@
|
||||
#include <ReMixer/PulseSubsystem.h>
|
||||
#include <ReMixer/ReMixer.h>
|
||||
|
||||
|
||||
[[noreturn]] int main() {
|
||||
|
||||
|
||||
using namespace ReMixer;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -30,20 +28,32 @@
|
||||
|
||||
PulseAudioSubsystem test = PulseAudioSubsystem("PulseAudio Test");
|
||||
|
||||
PulseStream test_stream = test.CreateStream("Test Stream");
|
||||
PulseStream test_stream2 = test.CreateStream("Another Test Stream");
|
||||
test.OnServerInfo += [] (const pa_server_info* info) {
|
||||
std::cout << info->host_name << " @ " << info->user_name << ", "
|
||||
<< info->server_name << " v" << info->server_version << ", "
|
||||
<< "default sink: " << info->default_sink_name << ", "
|
||||
<< "default source" << info->default_source_name << std::endl;
|
||||
};
|
||||
|
||||
Sound test_sound = Sound::FromPCMFile("wind.raw");
|
||||
Sound ogg_test_sound = Sound::FromOGGVorbisFile("wind.ogg");
|
||||
PulseStream test_stream = test.CreateStream("Test Stream");
|
||||
//PulseStream test_stream2 = test.CreateStream("Another Test Stream");
|
||||
|
||||
//Sound test_sound = Sound::FromPCMFile("output.raw");
|
||||
Sound ogg_test_sound = Sound::FromOGGVorbisFile("song.ogg");
|
||||
|
||||
auto sample_depth = 2;
|
||||
|
||||
std::cout << "Sound Playback Length: " << ogg_test_sound.PlaybackLength() << " seconds." << std::endl;
|
||||
|
||||
std::thread playback_thread([](PulseAudioSubsystem system, PulseStream stream, Sound sound){
|
||||
system.Play(stream, sound);
|
||||
}, test, test_stream, ogg_test_sound);
|
||||
|
||||
playback_thread.join();
|
||||
|
||||
playback_thread.detach();
|
||||
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(25ms);
|
||||
}
|
||||
|
||||
//test.Play(test_stream2, ogg_test_sound);
|
||||
|
||||
|
@@ -316,7 +316,7 @@ int pa_system_enumerate_devices(pa_system* p)
|
||||
for (;;) {
|
||||
int r = pa_operation_get_state(op);
|
||||
|
||||
if (r == PA_OPERATION_DONE || r == PA_OPERATION_CANCELLED || r == PA_OPERATION_CANCELED)
|
||||
if (r == PA_OPERATION_DONE || r == PA_OPERATION_CANCELLED)
|
||||
break;
|
||||
|
||||
pa_threaded_mainloop_wait(p->mainloop);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#include <ReMixer/PulseSubsystem.h>
|
||||
|
||||
void ReMixer::PulseAudioSubsystem::Play(const ReMixer::PulseStream &stream, const ReMixer::Sound &sound) {
|
||||
void ReMixer::PulseAudioSubsystem::Play(ReMixer::PulseStream &stream, const ReMixer::Sound &sound) {
|
||||
|
||||
// Check parameters and compatibility between stream and sound.
|
||||
if (stream.SampleRate() != sound.SampleRate())
|
||||
@@ -8,6 +8,8 @@ void ReMixer::PulseAudioSubsystem::Play(const ReMixer::PulseStream &stream, cons
|
||||
std::cerr << "There is an incongruency in sample rate between the stream and the sound file. "
|
||||
<< std::format("Stream: {}hz, meanwhile sound: {}hz", stream.SampleRate(), sound.SampleRate())
|
||||
<< std::endl;
|
||||
|
||||
stream.SetSampleRate(sound.SampleRate());
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_lock(pa->mainloop);
|
||||
@@ -35,10 +37,24 @@ void ReMixer::PulseAudioSubsystem::Play(const ReMixer::PulseStream &stream, cons
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
}
|
||||
|
||||
bool ReMixer::PulseAudioSubsystem::Connected() {
|
||||
bool ReMixer::PulseAudioSubsystem::Connected() const {
|
||||
return ctx_connected;
|
||||
}
|
||||
|
||||
std::string ReMixer::PulseAudioSubsystem::ContextName() const {
|
||||
return ctx_name;
|
||||
}
|
||||
|
||||
std::string ReMixer::PulseAudioSubsystem::ServerName() const
|
||||
{
|
||||
return pa_context_get_server(this->pa->context);
|
||||
}
|
||||
|
||||
void ReMixer::PulseAudioSubsystem::SetContextName(const std::string &new_context_name) {
|
||||
this->ctx_name = new_context_name;
|
||||
pa_context_set_name(this->pa->context, new_context_name.c_str(), OnCtxSuccess, this);
|
||||
}
|
||||
|
||||
ReMixer::PulseStream ReMixer::PulseAudioSubsystem::CreateStream(const std::string& stream_name) {
|
||||
// Assumed defaults
|
||||
// TODO: Expose to the API later
|
||||
@@ -69,7 +85,7 @@ ReMixer::PulseStream ReMixer::PulseAudioSubsystem::CreateStream(const std::strin
|
||||
pa_stream_set_latency_update_callback(new_stream.stream, PulseStream::OnLatencyUpdate, pa->mainloop);
|
||||
|
||||
pa_stream_flags flags = static_cast<pa_stream_flags>(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY |
|
||||
PA_STREAM_AUTO_TIMING_UPDATE);
|
||||
PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_VARIABLE_RATE);
|
||||
|
||||
// TODO: Support Playback and Recording streams
|
||||
int r = pa_stream_connect_playback(new_stream.stream, dev, attr, flags, volume, NULL);
|
||||
@@ -105,6 +121,8 @@ ReMixer::PulseStream ReMixer::PulseAudioSubsystem::CreateStream(const std::strin
|
||||
|
||||
ReMixer::PulseAudioSubsystem::PulseAudioSubsystem(const std::string &context_name) : SoundSubsystem()
|
||||
{
|
||||
ctx_name = context_name;
|
||||
|
||||
const char *server = NULL;
|
||||
pa = pa_xnew(pa_impl, 1);
|
||||
|
||||
@@ -124,7 +142,7 @@ ReMixer::PulseAudioSubsystem::PulseAudioSubsystem(const std::string &context_nam
|
||||
|
||||
pa_context_flags flags = pa_context_flags::PA_CONTEXT_NOAUTOSPAWN;
|
||||
|
||||
pa_context_set_state_callback(pa->context, OnCtxStateChange, pa);
|
||||
pa_context_set_state_callback(pa->context, OnCtxStateChange, this);
|
||||
|
||||
|
||||
if (pa_context_connect(pa->context, server, flags, NULL) < 0)
|
||||
@@ -158,25 +176,41 @@ ReMixer::PulseAudioSubsystem::PulseAudioSubsystem(const std::string &context_nam
|
||||
pa_threaded_mainloop_wait(pa->mainloop);
|
||||
}
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
|
||||
pa_context_get_server_info(pa->context, OnListServerInfo, this);
|
||||
pa_context_get_sink_info_list(pa->context, OnListSinkInfo, this);
|
||||
|
||||
std::cout << "PulseAudio context is ready." << std::endl;
|
||||
}
|
||||
|
||||
void ReMixer::PulseAudioSubsystem::OnCtxSuccess(pa_context* c, int success, void* userdata)
|
||||
{
|
||||
auto* p = static_cast<PulseAudioSubsystem *>(userdata);
|
||||
|
||||
if (success)
|
||||
p->OnSuccess.Invoke(true);
|
||||
else
|
||||
p->OnSuccess.Invoke(false);
|
||||
}
|
||||
|
||||
void ReMixer::PulseAudioSubsystem::OnCtxStateChange(pa_context *c, void *userdata) {
|
||||
std::cout << "context state callback: ";
|
||||
|
||||
auto *p = static_cast<pa_impl *>(userdata);
|
||||
auto* p = static_cast<PulseAudioSubsystem *>(userdata);
|
||||
|
||||
|
||||
switch(pa_context_get_state(c)) {
|
||||
case PA_CONTEXT_READY:
|
||||
std::cout << "Context Ready" << std::endl;
|
||||
pa_threaded_mainloop_signal(p->mainloop, 0);
|
||||
pa_threaded_mainloop_signal(p->pa->mainloop, 0);
|
||||
break;
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
std::cout << "Context Terminated" << std::endl;
|
||||
pa_threaded_mainloop_signal(p->mainloop, 0);
|
||||
pa_threaded_mainloop_signal(p->pa->mainloop, 0);
|
||||
break;
|
||||
case PA_CONTEXT_FAILED:
|
||||
std::cout << "Context Failed" << std::endl;
|
||||
pa_threaded_mainloop_signal(p->mainloop, 0);
|
||||
pa_threaded_mainloop_signal(p->pa->mainloop, 0);
|
||||
break;
|
||||
case PA_CONTEXT_UNCONNECTED:
|
||||
std::cout << "Context Unconnected" << std::endl;
|
||||
@@ -195,3 +229,52 @@ void ReMixer::PulseAudioSubsystem::OnCtxStateChange(pa_context *c, void *userdat
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ReMixer::PulseAudioSubsystem::ListDevices() {
|
||||
pa_threaded_mainloop_lock(pa->mainloop);
|
||||
|
||||
pa_operation * op;
|
||||
// List 'Sinks' aka playback devices
|
||||
op = pa_context_get_sink_info_list(pa->context, OnSinkDevice, this);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int r = pa_operation_get_state(op);
|
||||
|
||||
if (r == PA_OPERATION_DONE || r == PA_OPERATION_CANCELLED)
|
||||
break;
|
||||
|
||||
pa_threaded_mainloop_wait(pa->mainloop);
|
||||
}
|
||||
|
||||
pa_operation_unref(op);
|
||||
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
}
|
||||
|
||||
void ReMixer::PulseAudioSubsystem::OnListServerInfo(pa_context *c, const pa_server_info *i, void *userdata) {
|
||||
auto* p = static_cast<PulseAudioSubsystem*>(userdata);
|
||||
|
||||
p->OnServerInfo.Invoke(i);
|
||||
|
||||
pa_threaded_mainloop_signal(p->pa->mainloop, 0);
|
||||
}
|
||||
|
||||
void ReMixer::PulseAudioSubsystem::OnListSinkInfo(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
|
||||
|
||||
}
|
||||
|
||||
void ReMixer::PulseAudioSubsystem::OnSinkDevice(pa_context* c, const pa_sink_info* info, int eol, void* userdata)
|
||||
{
|
||||
if (eol != 0)
|
||||
{
|
||||
auto* p = static_cast<PulseAudioSubsystem*> (userdata);
|
||||
|
||||
pa_threaded_mainloop_signal(p->pa->mainloop, 0);
|
||||
}
|
||||
|
||||
if (info)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@@ -103,4 +103,27 @@ void ReMixer::Sound::LoadOGGVorbisFile(const std::filesystem::path &file_name) {
|
||||
while ((bytes = ov_read(&vf, pcmout, sizeof(pcmout), 0, 2, 1, ¤t_section)) > 0) {
|
||||
audio_data.insert(audio_data.end(), pcmout, pcmout + bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint ReMixer::Sound::Channels() const {
|
||||
if (stream_mode == StreamMode::MONO) return 1;
|
||||
if (stream_mode == StreamMode::STEREO) return 2;
|
||||
}
|
||||
|
||||
ReMixer::StreamMode ReMixer::Sound::GetStreamMode() const { return stream_mode;}
|
||||
|
||||
uint ReMixer::Sound::SampleRate() const { return sample_rate;}
|
||||
|
||||
float ReMixer::Sound::PlaybackLength() {
|
||||
// TODO: Factor in sample_depth properly
|
||||
float sample_depth = 2;
|
||||
float sample_count = (float)SampleCount();
|
||||
|
||||
return sample_count / (SampleRate() * Channels() * sample_depth);
|
||||
}
|
||||
|
||||
uint ReMixer::Sound::SampleCount() const {
|
||||
// TODO: Should we divide the audio buffer's real size by sample_depth?
|
||||
// Consider sample_depth = 2, each 16-bit sample would hold two elements in the char buffer.
|
||||
return audio_data.size();
|
||||
}
|
||||
|
@@ -10,14 +10,29 @@ void ReMixer::Stream::SetName(const std::string& name)
|
||||
}
|
||||
|
||||
void ReMixer::Stream::SetSampleRate(uint sample_rate)
|
||||
{ this->sample_rate = sample_rate;}
|
||||
{
|
||||
this->sample_rate = sample_rate;
|
||||
}
|
||||
|
||||
void ReMixer::Stream::SetChannelCount(uint channel_count)
|
||||
{ this->channel_count = channel_count;}
|
||||
|
||||
void ReMixer::PulseStream::SetSampleRate(uint sampleRate)
|
||||
{
|
||||
|
||||
//pa_threaded_mainloop_lock(this->parent-)
|
||||
|
||||
Stream::SetSampleRate(sampleRate);
|
||||
pa_operation* op;
|
||||
|
||||
|
||||
op = pa_stream_update_sample_rate(this->stream, sampleRate, OnStreamSuccess, this);
|
||||
|
||||
pa_operation_unref(op);
|
||||
}
|
||||
|
||||
void ReMixer::PulseStream::OnStreamStateChanged(pa_stream *s, void *userdata) {
|
||||
std::cout << "stream state callback: ";
|
||||
//std::cout << "stream state callback: ";
|
||||
|
||||
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
|
||||
|
||||
@@ -45,7 +60,7 @@ void ReMixer::PulseStream::OnStreamStateChanged(pa_stream *s, void *userdata) {
|
||||
}
|
||||
|
||||
void ReMixer::PulseStream::OnStreamRequest(pa_stream *s, size_t length, void *userdata) {
|
||||
std::cout << "Received stream request callback: " << length << std::endl;
|
||||
//std::cout << "Received stream request callback: " << length << std::endl;
|
||||
|
||||
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
|
||||
|
||||
@@ -53,8 +68,13 @@ void ReMixer::PulseStream::OnStreamRequest(pa_stream *s, size_t length, void *us
|
||||
}
|
||||
|
||||
void ReMixer::PulseStream::OnLatencyUpdate(pa_stream *s, void *userdata) {
|
||||
std::cout << "Received stream latency update callback" << std::endl;
|
||||
//std::cout << "Received stream latency update callback" << std::endl;
|
||||
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
|
||||
|
||||
pa_threaded_mainloop_signal(p, 0);
|
||||
}
|
||||
|
||||
void ReMixer::PulseStream::OnStreamSuccess(pa_stream *s, int success, void *userdata) {
|
||||
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user