245 lines
6.8 KiB
C++
245 lines
6.8 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;
|
|
}
|
|
|
|
std::string SoundDevice::getName() const {
|
|
return device.playback.name;
|
|
}
|
|
|
|
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;
|
|
|
|
// if a end frame is set, make sure to not read over it
|
|
ma_uint32 shouldReadCount = frameCount;
|
|
if((pb->currentFrame + frameCount) > pb->endFrame) {
|
|
shouldReadCount = pb->endFrame - pb->currentFrame;
|
|
}
|
|
|
|
//decoder "reinaddieren"
|
|
ma_uint64 read = 0;
|
|
if(shouldReadCount > 0)
|
|
read = readDecoderandAdd(pDecoder, volume, streamDivider, shouldReadCount, outbuffer);
|
|
|
|
//decoder fertig -> nicht mehr verwenden
|
|
if (read < frameCount) {
|
|
pb->isDone = true;
|
|
--data.decoderCount;
|
|
}
|
|
|
|
pb->currentFrame += read;
|
|
|
|
if(pb->callback) {
|
|
pb->callback(pb->currentFrame / (pDecoder->outputSampleRate / 1000));
|
|
}
|
|
}
|
|
|
|
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);
|
|
if(result != MA_SUCCESS) {
|
|
Log::error << "could not get sound device list";
|
|
return nullptr;
|
|
}
|
|
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();
|
|
data.decoderCount = 0;
|
|
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, uint64_t beginms, uint64_t endms, std::function<void(uint64_t)> callback) {
|
|
cleanupDecoders();
|
|
|
|
Playback* pb = new Playback();
|
|
|
|
if (ma_decoder_init_file((Sound::FOLDER + name).c_str(), NULL, &(pb->decoder)) != MA_SUCCESS) {
|
|
Log::error << "Sound datei: " << name << " konnte nicht geladen werden!";
|
|
throw std::exception();
|
|
}
|
|
|
|
Log::debug << "created decoder with samplerate: " << pb->decoder.outputSampleRate;
|
|
|
|
pb->volume = volume;
|
|
pb->isDone = false;
|
|
pb->callback = callback;
|
|
|
|
if(beginms != 0) {
|
|
pb->startFrame = (pb->decoder.outputSampleRate * beginms) / 1000;
|
|
ma_decoder_seek_to_pcm_frame(&pb->decoder, pb->startFrame);
|
|
pb->currentFrame = pb->startFrame;
|
|
Log::trace << "skip to frame: " << pb->currentFrame;
|
|
}
|
|
|
|
if(endms != 0) {
|
|
pb->endFrame = (pb->decoder.outputSampleRate * endms) / 1000;
|
|
Log::trace << "skip endframe: " << pb->endFrame;
|
|
}
|
|
|
|
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";
|
|
}
|