soundboard/src/sounddevice.cpp

208 lines
5.7 KiB
C++

#include "sounddevice.h"
#include <cassert>
#include <iostream>
#include <Log.h>
#include "sound.h"
#define CHANNELCOUNT 2
#define MINSTREAMCOUNT 5
static void global_sound_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) {
//es kann davon ausgegangen werden, das der buffer mit stille gefüllt ist
//decoder
if (!pDevice->pUserData) return;
((SoundDevice*) pDevice->pUserData)->sound_callback(pOutput, frameCount);
(void) pInput; //ignore input
}
static ma_uint64 readDecoderandAdd(ma_decoder* decoder, float volume, unsigned int streamDivider, ma_uint32 frameCount, void* outputBuffer) {
assert(frameCount > 0);
//read sound
int16_t* buffer = new int16_t[frameCount * CHANNELCOUNT];
ma_uint64 frameCountRead = ma_decoder_read_pcm_frames(decoder, buffer, frameCount);
if (frameCountRead > frameCount) frameCountRead = frameCount; //drop unwanted frames (sollte eigentlich nicht nötig sein)
//zum mixen: samples durch anzahl decoder teilen (overflow verhindern), volume factor anwenden und auf outputbuffer addieren
int16_t* outbuf = (int16_t*) outputBuffer;
for (ma_uint64 i = 0; i < frameCountRead * CHANNELCOUNT; i++) {
outbuf[i] += (buffer[i] * volume) / streamDivider; //TODO: use sse / avx
}
delete[] buffer;
return frameCountRead;
}
void SoundDevice::sound_callback(void* outbuffer, ma_uint32 frameCount) {
ma_mutex_lock(data.mutex);
unsigned int decoderCount = data.decoderCount;
unsigned int streamDivider = std::max<unsigned int>(decoderCount, MINSTREAMCOUNT); //nicht listplaybacks.size() verwenden, da manche playbacks möglicherweise noch fertig sind, aber noch nicht aus der list entfernt wurden. (wird von anderem thread gemacht)
unsigned int count = 0; //nummer des aktuellen decoders (nur für debugging)
for (SoundDevice::Playback* pb : data.playbacks) {
if (pb->isDone) continue; //fertige encoder ignorieren
// std::cout << "Play decoder " << (++count) << " of " << decoderCount << std::endl; //DEBUG
//informationen beschaffen
ma_decoder* pDecoder = &(pb->decoder);
float volume = pb->volume;
//decoder "reinaddieren"
ma_uint64 read = readDecoderandAdd(pDecoder, volume, streamDivider, frameCount, outbuffer);
//decoder fertig -> nicht mehr verwenden
if (read < frameCount) {
pb->isDone = true;
--data.decoderCount;
}
}
ma_mutex_unlock(data.mutex);
}
SoundDevice::SoundDevice() {
data.mutex = new ma_mutex();
ma_mutex_init(data.mutex);
}
SoundDevice::~SoundDevice() {
ma_device_uninit(&device);
ma_mutex_uninit(data.mutex);
delete data.mutex;
}
SoundDevice* SoundDevice::createDevice(ma_context* ctx, const std::string& name) {
ma_device_info* pPlaybackDeviceInfos;
ma_uint32 playbackDeviceCount;
ma_result result = ma_context_get_devices(ctx, &pPlaybackDeviceInfos, &playbackDeviceCount, NULL, 0);
int8_t choosenDevice = -1;
Log::info << " " << playbackDeviceCount << " playback devices found:";
for(uint8_t i = 0; i < playbackDeviceCount; i++) {
Log::info << " " << (int) i << ": " << pPlaybackDeviceInfos[i].name;
if(pPlaybackDeviceInfos[i].name == name) {
choosenDevice = i;
}
}
if(choosenDevice == -1) {
Log::info << "Device \"" << name << "\" not found!";
return nullptr;
}
return createDevice(ctx, &pPlaybackDeviceInfos[choosenDevice].id);
}
SoundDevice* SoundDevice::createDevice(ma_context* ctx, const ma_device_id* did) {
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.format = ma_format::ma_format_s16;
deviceConfig.playback.channels = CHANNELCOUNT;
if(did != NULL)
deviceConfig.playback.pDeviceID = did;
deviceConfig.sampleRate = 48000; // 44100;
deviceConfig.dataCallback = global_sound_callback;
deviceConfig.pulse.pStreamNamePlayback = "MySoundboard";
SoundDevice* sd = new SoundDevice();
deviceConfig.pUserData = sd;
if (ma_device_init(ctx, &deviceConfig, &sd->device) != MA_SUCCESS) {
Log::error << "Failed to open sound device.";
delete sd;
return nullptr;
}
Log::info << "Sound playback device \"" << sd->device.playback.name << "\" initilized.";
return sd;
}
void SoundDevice::stop() {
if(deviceRunning)
ma_device_stop(&device);
ma_mutex_lock(data.mutex);
for(Playback* pb : data.playbacks) {
ma_decoder_uninit(&pb->decoder);
delete pb;
}
data.playbacks.clear();
ma_mutex_unlock(data.mutex);
if(deviceRunning)
Log::info << "Sound playback device \"" << device.playback.name << "\" stopped.";
deviceRunning = false;
}
void SoundDevice::addPlayback(const std::string& name, float volume) {
cleanupDecoders();
Playback* pb = new Playback();
if (ma_decoder_init_file((Sound::FOLDER + name).c_str(), NULL, &(pb->decoder)) != MA_SUCCESS) {
Log::info << "Sound datei: " << name << " konnte nicht geladen werden!";
throw std::exception();
}
pb->volume = volume;
pb->isDone = false;
ma_mutex_lock(data.mutex);
data.playbacks.push_back(pb);
++data.decoderCount;
ma_mutex_unlock(data.mutex);
startDevice();
}
void SoundDevice::startDevice() {
if (deviceRunning) return;
if (ma_device_start(&device) != MA_SUCCESS) {
Log::error << "Failed to start sound device \"" << device.playback.name << "\"";
stop();
ma_device_uninit(&device);
throw std::exception();
}
deviceRunning = true;
Log::info << "Sound device \"" << device.playback.name << "\" started.";
}
void SoundDevice::cleanupDecoders() {
unsigned int count = 0;
ma_mutex_lock(data.mutex);
for (std::list<Playback*>::iterator it = data.playbacks.begin(); it != data.playbacks.end(); ) {
Playback* pb = *it;
if (pb->isDone) {
//uninit decoder
ma_decoder_uninit(&pb->decoder);
count++;
data.playbacks.erase(it++);
delete pb;
}
else
++it;
}
ma_mutex_unlock(data.mutex);
if (count)
Log::debug << "removed " << count << " decoder";
}