Move Opus encoding off-thread because of SDL

SDL gives its audio threads a very small stack size and it was making Opus fuck up
This commit is contained in:
Kae 2023-07-14 22:47:49 +10:00
parent 52ba6fa7f7
commit 73c5a17746
2 changed files with 96 additions and 48 deletions

View File

@ -140,10 +140,22 @@ Voice::Voice(ApplicationControllerPtr appController) : m_encoder(nullptr, opus_e
m_channelMode = VoiceChannelMode::Mono; m_channelMode = VoiceChannelMode::Mono;
m_applicationController = appController; m_applicationController = appController;
m_stopThread = false;
m_thread = Thread::invoke("Voice::thread", mem_fn(&Voice::thread), this);
s_singleton = this; s_singleton = this;
} }
Voice::~Voice() { Voice::~Voice() {
m_stopThread = true;
{
MutexLocker locker(m_threadMutex);
m_threadCond.broadcast();
}
m_thread.finish();
save(); save();
closeDevice(); closeDevice();
@ -232,14 +244,18 @@ void Voice::readAudioData(uint8_t* stream, int len) {
if (m_inputMode == VoiceInputMode::VoiceActivity) { if (m_inputMode == VoiceInputMode::VoiceActivity) {
if (decibels > m_threshold) if (decibels > m_threshold)
m_lastThresholdTime = now; m_lastThresholdTime = now;
active = now - m_lastThresholdTime < 50; active = now - m_lastThresholdTime < 50;
} }
bool added = false;
MutexLocker captureLock(m_captureMutex);
if (active) { if (active) {
m_capturedChunksFrames += sampleCount / m_deviceChannels; m_capturedChunksFrames += sampleCount / m_deviceChannels;
auto data = (opus_int16*)malloc(len); auto data = (opus_int16*)malloc(len);
memcpy(data, stream, len); memcpy(data, stream, len);
m_capturedChunks.emplace(data, sampleCount); // takes ownership m_capturedChunks.emplace(data, sampleCount); // takes ownership
added = true;
} }
else { // Clear out any residual data so they don't manifest at the start of the next encode, whenever that is else { // Clear out any residual data so they don't manifest at the start of the next encode, whenever that is
while (!m_capturedChunks.empty()) while (!m_capturedChunks.empty())
@ -248,46 +264,8 @@ void Voice::readAudioData(uint8_t* stream, int len) {
m_capturedChunksFrames = 0; m_capturedChunksFrames = 0;
} }
ByteArray encoded(VOICE_MAX_PACKET_SIZE, 0); if (added)
size_t frameSamples = VOICE_FRAME_SIZE * (size_t)m_deviceChannels; m_threadCond.signal();
while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) {
std::vector<opus_int16> samples;
samples.reserve(frameSamples);
size_t samplesLeft = frameSamples;
while (samplesLeft && !m_capturedChunks.empty()) {
auto& front = m_capturedChunks.front();
if (front.exhausted())
m_capturedChunks.pop();
else
samplesLeft -= front.takeSamples(samples, samplesLeft);
}
m_capturedChunksFrames -= VOICE_FRAME_SIZE;
if (m_inputVolume != 1.0f) {
for (size_t i = 0; i != samples.size(); ++i)
samples[i] *= m_inputVolume;
}
if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) {
if (encodedSize == 1)
continue;
encoded.resize(encodedSize);
{
MutexLocker lock(m_captureMutex);
m_encodedChunks.emplace_back(move(encoded)); // reset takes ownership of data buffer
m_encodedChunksLength += encodedSize;
encoded = ByteArray(VOICE_MAX_PACKET_SIZE, 0);
}
Logger::info("Voice: encoded Opus chunk {} bytes big", encodedSize);
}
else if (encodedSize < 0)
Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize));
}
} }
void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) {
@ -368,15 +346,17 @@ void Voice::setDeviceName(Maybe<String> deviceName) {
int Voice::send(DataStreamBuffer& out, size_t budget) { int Voice::send(DataStreamBuffer& out, size_t budget) {
out.setByteOrder(ByteOrder::LittleEndian); out.setByteOrder(ByteOrder::LittleEndian);
out.write<uint16_t>(VOICE_VERSION); out.write<uint16_t>(VOICE_VERSION);
MutexLocker captureLock(m_captureMutex);
if (m_capturedChunks.empty()) MutexLocker encodeLock(m_encodeMutex);
if (m_encodedChunks.empty())
return 0; return 0;
std::vector<ByteArray> encodedChunks = move(m_encodedChunks); std::vector<ByteArray> encodedChunks = move(m_encodedChunks);
size_t encodedChunksLength = m_encodedChunksLength; size_t encodedChunksLength = m_encodedChunksLength;
m_encodedChunksLength = 0; m_encodedChunksLength = 0;
captureLock.unlock();
encodeLock.unlock();
for (auto& chunk : encodedChunks) { for (auto& chunk : encodedChunks) {
out.write<uint32_t>(chunk.size()); out.write<uint32_t>(chunk.size());
@ -518,4 +498,63 @@ bool Voice::playSpeaker(SpeakerPtr const& speaker, int channels) {
return true; return true;
} }
void Voice::thread() {
while (true) {
MutexLocker locker(m_threadMutex);
m_threadCond.wait(m_threadMutex);
if (m_stopThread)
return;
{
MutexLocker locker(m_captureMutex);
ByteArray encoded(VOICE_MAX_PACKET_SIZE, 0);
size_t frameSamples = VOICE_FRAME_SIZE * (size_t)m_deviceChannels;
while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) {
std::vector<opus_int16> samples;
samples.reserve(frameSamples);
size_t samplesLeft = frameSamples;
while (samplesLeft && !m_capturedChunks.empty()) {
auto& front = m_capturedChunks.front();
if (front.exhausted())
m_capturedChunks.pop();
else
samplesLeft -= front.takeSamples(samples, samplesLeft);
}
m_capturedChunksFrames -= VOICE_FRAME_SIZE;
if (m_inputVolume != 1.0f) {
for (size_t i = 0; i != samples.size(); ++i)
samples[i] *= m_inputVolume;
}
if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) {
if (encodedSize == 1)
continue;
encoded.resize(encodedSize);
{
MutexLocker lock(m_encodeMutex);
m_encodedChunks.emplace_back(move(encoded)); // reset takes ownership of data buffer
m_encodedChunksLength += encodedSize;
encoded = ByteArray(VOICE_MAX_PACKET_SIZE, 0);
}
Logger::info("Voice: encoded Opus chunk {} bytes big", encodedSize);
}
else if (encodedSize < 0)
Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize));
}
}
continue;
locker.unlock();
Thread::yield();
}
return;
}
} }

View File

@ -134,17 +134,19 @@ public:
void setInput(bool input = true); void setInput(bool input = true);
inline int encoderChannels() const { return (int)m_channelMode; } inline int encoderChannels() const { return (int)m_channelMode; }
private:
static Voice* s_singleton;
static OpusDecoder* createDecoder(int channels); static OpusDecoder* createDecoder(int channels);
static OpusEncoder* createEncoder(int channels); static OpusEncoder* createEncoder(int channels);
private:
static Voice* s_singleton;
void resetEncoder(); void resetEncoder();
void openDevice(); void openDevice();
void closeDevice(); void closeDevice();
bool playSpeaker(SpeakerPtr const& speaker, int channels); bool playSpeaker(SpeakerPtr const& speaker, int channels);
void thread();
SpeakerId m_speakerId = 0; SpeakerId m_speakerId = 0;
SpeakerPtr m_clientSpeaker; SpeakerPtr m_clientSpeaker;
HashMap<SpeakerId, SpeakerPtr> m_speakers; HashMap<SpeakerId, SpeakerPtr> m_speakers;
@ -152,6 +154,8 @@ private:
Mutex m_activeSpeakersMutex; Mutex m_activeSpeakersMutex;
HashSet<SpeakerPtr> m_activeSpeakers; HashSet<SpeakerPtr> m_activeSpeakers;
OpusEncoderPtr m_encoder; OpusEncoderPtr m_encoder;
float m_outputVolume = 1.0f; float m_outputVolume = 1.0f;
@ -171,6 +175,11 @@ private:
VoiceInputMode m_inputMode; VoiceInputMode m_inputMode;
VoiceChannelMode m_channelMode; VoiceChannelMode m_channelMode;
ThreadFunction<void> m_thread;
Mutex m_threadMutex;
ConditionVariable m_threadCond;
atomic<bool> m_stopThread;
ApplicationControllerPtr m_applicationController; ApplicationControllerPtr m_applicationController;
struct EncodedChunk { struct EncodedChunk {
@ -183,13 +192,13 @@ private:
} }
}; };
Mutex m_encodeMutex;
std::vector<ByteArray> m_encodedChunks; std::vector<ByteArray> m_encodedChunks;
size_t m_encodedChunksLength = 0; size_t m_encodedChunksLength = 0;
Mutex m_captureMutex;
std::queue<VoiceAudioChunk> m_capturedChunks; std::queue<VoiceAudioChunk> m_capturedChunks;
size_t m_capturedChunksFrames = 0; size_t m_capturedChunksFrames = 0;
Mutex m_captureMutex;
}; };
} }