Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions TextToSpeech/impl/TTSSpeaker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ bool TTSSpeaker::pause(uint32_t id) {
if(m_pipeline) {
if(!m_isPaused) {
m_isPaused = true;
gst_element_set_state(m_pipeline, GST_STATE_PAUSED);
setPipelineState(GST_STATE_PAUSED);
TTSLOG_INFO("Set state to PAUSED");
return true;
}
Expand All @@ -552,7 +552,7 @@ bool TTSSpeaker::resume(uint32_t id) {

if(m_pipeline) {
if(m_isPaused) {
gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
setPipelineState(GST_STATE_PLAYING);
TTSLOG_INFO("Set state to PLAYING");
return true;
}
Expand Down Expand Up @@ -923,6 +923,18 @@ void TTSSpeaker::createPipeline(PipelineType type) {
resetPipeline();
}

void TTSSpeaker::setPipelineState(GstState state) {
if(m_pipeline)
{
std::lock_guard<std::mutex> lock(m_pipelineMutex);
gst_element_set_state(m_pipeline, state);
}
else
{
Comment on lines +927 to +933
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setPipelineState function has a time-of-check-time-of-use (TOCTOU) race condition. The m_pipeline pointer is checked for NULL before acquiring the mutex lock, but m_pipeline could be set to NULL by another thread (e.g., in destroyPipeline at line 975 or createPipeline at line 910) between the check and the lock acquisition. The NULL check should be performed after acquiring the mutex lock to ensure thread safety.

Suggested change
if(m_pipeline)
{
std::lock_guard<std::mutex> lock(m_pipelineMutex);
gst_element_set_state(m_pipeline, state);
}
else
{
std::lock_guard<std::mutex> lock(m_pipelineMutex);
if (m_pipeline) {
gst_element_set_state(m_pipeline, state);
} else {

Copilot uses AI. Check for mistakes.
TTSLOG_ERROR("Pipeline error occured");
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error: "occured" should be "occurred" (with two 'r's).

Copilot uses AI. Check for mistakes.
}
}

void TTSSpeaker::resetPipeline() {
TTSLOG_WARNING("Resetting Pipeline...");

Expand All @@ -944,7 +956,7 @@ void TTSSpeaker::resetPipeline() {
createPipeline(m_pipelinetype);
} else {
// If pipeline is present, bring it to NULL state
gst_element_set_state(m_pipeline, GST_STATE_NULL);
setPipelineState(GST_STATE_NULL);
while(!waitForStatus(GST_STATE_NULL, 60*1000));
}
}
Expand All @@ -953,7 +965,7 @@ void TTSSpeaker::destroyPipeline() {
TTSLOG_WARNING("Destroying Pipeline...");

if(m_pipeline) {
gst_element_set_state(m_pipeline, GST_STATE_NULL);
setPipelineState(GST_STATE_NULL);
waitForStatus(GST_STATE_NULL, 1*1000);
g_source_remove(m_busWatch);
gst_object_unref(m_pipeline);
Comment on lines 967 to 971
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gst_object_unref operation on m_pipeline is not protected by the m_pipelineMutex. This creates a race condition where another thread could be calling setPipelineState and accessing m_pipeline while it's being unreferenced and destroyed here. The unref operation and the subsequent NULL assignment should be protected by the m_pipelineMutex to ensure thread safety.

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -1015,8 +1027,7 @@ void TTSSpeaker::waitForAudioToFinishTimeout(float timeout_s) {
m_isEOS, m_pipeline, m_pipelineError, m_flushed);

// Irrespective of EOS / Timeout reset pipeline
if(m_pipeline)
gst_element_set_state(m_pipeline, GST_STATE_NULL);
setPipelineState(GST_STATE_NULL);

if(!m_isEOS)
TTSLOG_ERROR("Stopped waiting for audio to finish without hitting EOS!");
Expand Down Expand Up @@ -1068,7 +1079,7 @@ void TTSSpeaker::play(string url, SpeechData &data, bool authrequired, string to
// PCM Sink seems to be accepting volume change before PLAYING state
g_object_set(G_OBJECT(m_audioVolume), "volume", (double) (data.client->configuration()->volume() / MAX_VOLUME), NULL);

gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
setPipelineState(GST_STATE_PLAYING);

#if defined(PLATFORM_AMLOGIC) || defined(PLATFORM_REALTEK) || defined(PLATFORM_BROADCOM)
setMixGain(MIXGAIN_PRIM,data.primVolDuck);
Expand Down Expand Up @@ -1148,7 +1159,7 @@ void TTSSpeaker::GStreamerThreadFunc(void *ctx) {
// Stop thread on Speaker's cue
if(!speaker->m_runThread) {
if(speaker->m_pipeline) {
gst_element_set_state(speaker->m_pipeline, GST_STATE_NULL);
speaker->setPipelineState(GST_STATE_NULL);
speaker->waitForStatus(GST_STATE_NULL, 1*1000);
}
TTSLOG_INFO("Stopping GStreamerThread");
Expand Down
2 changes: 2 additions & 0 deletions TextToSpeech/impl/TTSSpeaker.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class TTSSpeaker {
void flushQueue();
SpeechData dequeueData();
PipelineType m_pipelinetype;
std::mutex m_pipelineMutex;

// Private functions
inline void setSpeakingState(bool state, TTSSpeakerClient *client=NULL);
Expand Down Expand Up @@ -181,6 +182,7 @@ class TTSSpeaker {
void resetPipeline();
PipelineType getUrlPipelineType(string url);
void destroyPipeline();
void setPipelineState(GstState state);

// GStreamer Helper functions
bool needsPipelineUpdate();
Expand Down
Loading