2020-07-02 11:02:57 +01:00
|
|
|
#include "qdirectsound.h"
|
|
|
|
|
|
|
|
#include "loggingcategory.h"
|
|
|
|
#include "qdirectsound.h"
|
|
|
|
#include "linux/windows/dsound.h"
|
|
|
|
#include <unordered_map>
|
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
#include <QAudioOutput>
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
class DirectSoundGenerator
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
DirectSoundGenerator(IDirectSoundBuffer * buffer);
|
|
|
|
|
|
|
|
void start();
|
|
|
|
void stop();
|
|
|
|
void writeAudio();
|
|
|
|
void stateChanged(QAudio::State state);
|
2020-07-08 16:52:00 +01:00
|
|
|
void setOptions(const qint32 initialSilence);
|
2020-07-02 11:02:57 +01:00
|
|
|
|
|
|
|
private:
|
|
|
|
IDirectSoundBuffer * myBuffer;
|
|
|
|
|
|
|
|
typedef short int audio_t;
|
|
|
|
|
|
|
|
std::shared_ptr<QAudioOutput> myAudioOutput;
|
|
|
|
QAudioFormat myAudioFormat;
|
|
|
|
QIODevice * myDevice;
|
|
|
|
|
|
|
|
// options
|
|
|
|
qint32 myInitialSilence;
|
|
|
|
|
2020-07-08 16:52:00 +01:00
|
|
|
void setVolume();
|
2020-07-02 11:02:57 +01:00
|
|
|
bool isRunning();
|
|
|
|
void initialise();
|
|
|
|
void writeEnoughSilence(const qint64 ms);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
std::unordered_map<IDirectSoundBuffer *, std::shared_ptr<DirectSoundGenerator>> activeSoundGenerators;
|
|
|
|
|
|
|
|
DirectSoundGenerator::DirectSoundGenerator(IDirectSoundBuffer * buffer) : myBuffer(buffer)
|
|
|
|
{
|
|
|
|
myInitialSilence = 200;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DirectSoundGenerator::initialise()
|
|
|
|
{
|
|
|
|
// only initialise here to skip all the buffers which are not in DSBSTATUS_PLAYING mode
|
|
|
|
QAudioFormat audioFormat;
|
|
|
|
audioFormat.setSampleRate(myBuffer->sampleRate);
|
|
|
|
audioFormat.setChannelCount(myBuffer->channels);
|
|
|
|
audioFormat.setSampleSize(myBuffer->bitsPerSample);
|
|
|
|
audioFormat.setCodec(QString::fromUtf8("audio/pcm"));
|
|
|
|
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
|
|
|
|
audioFormat.setSampleType(QAudioFormat::SignedInt);
|
|
|
|
|
|
|
|
myAudioOutput.reset(new QAudioOutput(audioFormat));
|
|
|
|
myAudioFormat = myAudioOutput->format();
|
|
|
|
}
|
|
|
|
|
2020-07-08 16:52:00 +01:00
|
|
|
void DirectSoundGenerator::setOptions(const qint32 initialSilence)
|
2020-07-02 11:02:57 +01:00
|
|
|
{
|
|
|
|
myInitialSilence = std::max(0, initialSilence);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DirectSoundGenerator::isRunning()
|
|
|
|
{
|
|
|
|
if (!myAudioOutput)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QAudio::State state = myAudioOutput->state();
|
|
|
|
const QAudio::Error error = myAudioOutput->error();
|
|
|
|
if (state == QAudio::ActiveState)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (state == QAudio::IdleState && error == QAudio::NoError)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-08 16:52:00 +01:00
|
|
|
void DirectSoundGenerator::setVolume()
|
|
|
|
{
|
2020-11-14 20:02:04 +00:00
|
|
|
const qreal logVolume = myBuffer->GetLogarithmicVolume();
|
|
|
|
const qreal linVolume = QAudio::convertVolume(logVolume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale);
|
|
|
|
myAudioOutput->setVolume(linVolume);
|
2020-07-08 16:52:00 +01:00
|
|
|
}
|
|
|
|
|
2020-07-02 11:02:57 +01:00
|
|
|
void DirectSoundGenerator::start()
|
|
|
|
{
|
|
|
|
if (isRunning())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DWORD dwStatus;
|
|
|
|
myBuffer->GetStatus(&dwStatus);
|
|
|
|
if (!(dwStatus & DSBSTATUS_PLAYING))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!myAudioOutput)
|
|
|
|
{
|
|
|
|
initialise();
|
|
|
|
}
|
|
|
|
|
|
|
|
// restart as we are either starting or recovering from underrun
|
|
|
|
myDevice = myAudioOutput->start();
|
|
|
|
|
|
|
|
if (!myDevice)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug(appleAudio) << "Restarting the AudioGenerator";
|
|
|
|
|
2020-07-08 16:52:00 +01:00
|
|
|
setVolume();
|
|
|
|
|
2020-07-02 11:02:57 +01:00
|
|
|
const int bytesSize = myAudioOutput->bufferSize();
|
|
|
|
const qint32 frameSize = myAudioFormat.framesForBytes(bytesSize);
|
|
|
|
|
|
|
|
const qint32 framePeriod = myAudioFormat.framesForBytes(myAudioOutput->periodSize());
|
|
|
|
qDebug(appleAudio) << "AudioOutput: size =" << frameSize << "f, period =" << framePeriod << "f";
|
|
|
|
writeEnoughSilence(myInitialSilence); // ms
|
|
|
|
}
|
|
|
|
|
|
|
|
void DirectSoundGenerator::stop()
|
|
|
|
{
|
|
|
|
if (!isRunning())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const qint32 bytesFree = myAudioOutput->bytesFree();
|
|
|
|
|
|
|
|
// fill with zeros and stop
|
|
|
|
std::vector<char> silence(bytesFree);
|
|
|
|
myDevice->write(silence.data(), silence.size());
|
|
|
|
|
|
|
|
const qint32 framesFree = myAudioFormat.framesForBytes(bytesFree);
|
|
|
|
const qint64 duration = myAudioFormat.durationForFrames(framesFree);
|
|
|
|
qDebug(appleAudio) << "Stopping with silence: frames =" << framesFree << ", duration =" << duration / 1000 << "ms";
|
|
|
|
myAudioOutput->stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DirectSoundGenerator::writeEnoughSilence(const qint64 ms)
|
|
|
|
{
|
|
|
|
// write a few ms of silence
|
|
|
|
const qint32 framesSilence = myAudioFormat.framesForDuration(ms * 1000); // target frames to write
|
|
|
|
|
|
|
|
const qint32 bytesFree = myAudioOutput->bytesFree();
|
|
|
|
const qint32 framesFree = myAudioFormat.framesForBytes(bytesFree); // number of frames avilable to write
|
|
|
|
const qint32 framesToWrite = std::min(framesFree, framesSilence);
|
|
|
|
const qint64 bytesToWrite = myAudioFormat.bytesForFrames(framesToWrite);
|
|
|
|
|
|
|
|
std::vector<char> silence(bytesToWrite);
|
|
|
|
myDevice->write(silence.data(), silence.size());
|
|
|
|
|
|
|
|
const qint64 duration = myAudioFormat.durationForFrames(framesToWrite);
|
|
|
|
qDebug(appleAudio) << "Written some silence: frames =" << framesToWrite << ", duration =" << duration / 1000 << "ms";
|
|
|
|
}
|
|
|
|
|
|
|
|
void DirectSoundGenerator::writeAudio()
|
|
|
|
{
|
|
|
|
if (!isRunning())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// we write all we have available (up to the free bytes)
|
|
|
|
const DWORD bytesFree = myAudioOutput->bytesFree();
|
|
|
|
|
|
|
|
LPVOID lpvAudioPtr1, lpvAudioPtr2;
|
|
|
|
DWORD dwAudioBytes1, dwAudioBytes2;
|
|
|
|
// this function reads as much as possible up to bytesFree
|
|
|
|
myBuffer->Read(bytesFree, &lpvAudioPtr1, &dwAudioBytes1, &lpvAudioPtr2, &dwAudioBytes2);
|
|
|
|
|
|
|
|
qint64 bytesWritten = 0;
|
|
|
|
qint64 bytesToWrite = 0;
|
|
|
|
if (lpvAudioPtr1)
|
|
|
|
{
|
|
|
|
bytesWritten += myDevice->write((char *)lpvAudioPtr1, dwAudioBytes1);
|
|
|
|
bytesToWrite += dwAudioBytes1;
|
|
|
|
}
|
|
|
|
if (lpvAudioPtr2)
|
|
|
|
{
|
|
|
|
bytesWritten += myDevice->write((char *)lpvAudioPtr2, dwAudioBytes2);
|
|
|
|
bytesToWrite += dwAudioBytes2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bytesToWrite != bytesWritten)
|
|
|
|
{
|
|
|
|
qDebug(appleAudio) << "Mismatch:" << bytesToWrite << "!=" << bytesWritten;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void registerSoundBuffer(IDirectSoundBuffer * buffer)
|
|
|
|
{
|
|
|
|
const std::shared_ptr<DirectSoundGenerator> generator = std::make_shared<DirectSoundGenerator>(buffer);
|
|
|
|
activeSoundGenerators[buffer] = generator;
|
|
|
|
}
|
|
|
|
|
|
|
|
void unregisterSoundBuffer(IDirectSoundBuffer * buffer)
|
|
|
|
{
|
2020-07-04 19:57:15 +01:00
|
|
|
const auto it = activeSoundGenerators.find(buffer);
|
|
|
|
if (it != activeSoundGenerators.end())
|
|
|
|
{
|
|
|
|
// stop the QAudioOutput before removing. is this necessary?
|
|
|
|
it->second->stop();
|
|
|
|
activeSoundGenerators.erase(it);
|
|
|
|
}
|
2020-07-02 11:02:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace QDirectSound
|
|
|
|
{
|
|
|
|
|
|
|
|
void start()
|
|
|
|
{
|
|
|
|
for (auto & it : activeSoundGenerators)
|
|
|
|
{
|
|
|
|
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
|
|
|
|
generator->start();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void stop()
|
|
|
|
{
|
|
|
|
for (auto & it : activeSoundGenerators)
|
|
|
|
{
|
|
|
|
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
|
|
|
|
generator->stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void writeAudio()
|
|
|
|
{
|
|
|
|
for (auto & it : activeSoundGenerators)
|
|
|
|
{
|
|
|
|
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
|
|
|
|
generator->writeAudio();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-08 16:52:00 +01:00
|
|
|
void setOptions(const qint32 initialSilence)
|
2020-07-02 11:02:57 +01:00
|
|
|
{
|
|
|
|
for (auto & it : activeSoundGenerators)
|
|
|
|
{
|
|
|
|
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
|
2020-07-08 16:52:00 +01:00
|
|
|
generator->setOptions(initialSilence);
|
2020-07-02 11:02:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|