From b2ce0f393492e8c47bcb6d913747b8a6b3e41400 Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Fri, 4 Oct 2024 16:29:02 +0200 Subject: [PATCH] Re-implementation of MediaPlayer and Stream update Avoid using gst video frames, and simply copy buffer instead. Use gst memory map to access pointer to RGBA data. unreferencing the buffer frees the memory (apparently). Also free OpenGL objects on close to free memory. Overall, memory consumption seems to be reduced. --- src/MediaPlayer.cpp | 164 +++++++++++++++++---------------------- src/MediaPlayer.h | 8 +- src/Stream.cpp | 182 +++++++++++++++++++------------------------- src/Stream.h | 8 +- src/TextSource.cpp | 7 +- 5 files changed, 159 insertions(+), 210 deletions(-) diff --git a/src/MediaPlayer.cpp b/src/MediaPlayer.cpp index 3839407..317babb 100644 --- a/src/MediaPlayer.cpp +++ b/src/MediaPlayer.cpp @@ -96,14 +96,6 @@ MediaPlayer::~MediaPlayer() { close(); - // cleanup opengl texture - if (textureindex_) - glDeleteTextures(1, &textureindex_); - - // cleanup picture buffer - if (pbo_[0]) - glDeleteBuffers(2, pbo_); - #ifdef MEDIA_PLAYER_DEBUG g_printerr("MediaPlayer %s deleted\n", std::to_string(id_).c_str()); #endif @@ -426,7 +418,6 @@ void MediaPlayer::execute_open() else { g_object_set( G_OBJECT (pipeline_), "video-filter", effect, nullptr); Log::Info("MediaPlayer %s Added filter '%s'", std::to_string(id_).c_str(), video_filter_.c_str()); - } } @@ -750,14 +741,6 @@ bool MediaPlayer::failed() const return failed_; } -void MediaPlayer::Frame::unmap() -{ - if (full) - gst_video_frame_unmap(&vframe); - - full = false; -} - void MediaPlayer::pipeline_terminate( GstElement *p, GstBus *b ) { gchar *name = gst_element_get_name(p); @@ -819,7 +802,8 @@ void MediaPlayer::close() // cleanup eventual remaining frame memory for(guint i = 0; i < N_VFRAME; i++) { frame_[i].access.lock(); - frame_[i].unmap(); + if (frame_[i].buffer) + gst_buffer_unref(frame_[i].buffer); frame_[i].status = INVALID; frame_[i].access.unlock(); } @@ -834,6 +818,18 @@ void MediaPlayer::close() pipeline_ = nullptr; bus_ = nullptr; } + + // cleanup opengl texture + if (textureindex_) { + glDeleteTextures(1, &textureindex_); + textureindex_ = 0; + } + + // cleanup picture buffer + if (pbo_[0]) { + glDeleteBuffers(2, pbo_); + pbo_[0] = 0; + } } @@ -1155,15 +1151,23 @@ void MediaPlayer::init_texture(guint index) glGenTextures(1, &textureindex_); glBindTexture(GL_TEXTURE_2D, textureindex_); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, media_.width, media_.height); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, media_.width, media_.height, - GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]); + + // fill texture frame with frame at given index + if (frame_[index].buffer) { + GstMapInfo map; + gst_buffer_map(frame_[index].buffer, &map, GST_MAP_READ); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, media_.width, media_.height, + GL_RGBA, GL_UNSIGNED_BYTE, map.data); + gst_buffer_unmap (frame_[index].buffer, &map); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + // use Pixel Buffer Objects only for performance needs of videos if ( !singleFrame() ) { - // set pbo image size pbo_size_ = media_.height * media_.width * 4; @@ -1172,31 +1176,7 @@ void MediaPlayer::init_texture(guint index) glDeleteBuffers(2, pbo_); glGenBuffers(2, pbo_); - for(int i = 0; i < 2; i++ ) { - // create 2 PBOs - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[i]); - // glBufferDataARB with NULL pointer reserves only memory space. - glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW); - // fill in with reset picture - GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); - if (ptr) { - // update data directly on the mapped buffer - memmove(ptr, frame_[index].vframe.data[0], pbo_size_); - // release pointer to mapping buffer - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - } - else { - // did not work, disable PBO - glDeleteBuffers(2, pbo_); - pbo_[0] = pbo_[1] = 0; - pbo_size_ = 0; - break; - } - - } - - // should be good to go, wrap it up - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + // should be good to go pbo_index_ = 0; pbo_next_index_ = 1; @@ -1214,14 +1194,21 @@ void MediaPlayer::fill_texture(guint index) // is this the first frame ? if (textureindex_ < 1) { - // initialize texture + // initialize texture on first run + // (this also fills the texture with frame at index) init_texture(index); } else { + // Use GST mapping to access pointer to RGBA data + GstMapInfo map; + gst_buffer_map(frame_[index].buffer, &map, GST_MAP_READ); + + // bind texture for writing glBindTexture(GL_TEXTURE_2D, textureindex_); - // use dual Pixel Buffer Object - if (pbo_size_ > 0) { + // use dual Pixel Buffer Object (faster) + if (pbo_size_ > 0 && map.size == pbo_size_) { + // In dual PBO mode, increment current index first then get the next index pbo_index_ = (pbo_index_ + 1) % 2; pbo_next_index_ = (pbo_index_ + 1) % 2; @@ -1232,8 +1219,9 @@ void MediaPlayer::fill_texture(guint index) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, media_.width, media_.height, GL_RGBA, GL_UNSIGNED_BYTE, 0); // bind the next PBO to write pixels glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]); + #ifdef USE_GL_BUFFER_SUBDATA - glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, pbo_size_, frame_[index].vframe.data[0]); + glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, pbo_size_, map.data); #else // update data directly on the mapped buffer // NB : equivalent but faster than glBufferSubData (memmove instead of memcpy ?) @@ -1242,7 +1230,8 @@ void MediaPlayer::fill_texture(guint index) // map the buffer object into client's memory GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); if (ptr) - memmove(ptr, frame_[index].vframe.data[0], pbo_size_); + memmove(ptr, map.data, pbo_size_); + // release pointer to mapping buffer glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); #endif @@ -1252,9 +1241,15 @@ void MediaPlayer::fill_texture(guint index) else { // without PBO, use standard opengl (slower) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, media_.width, media_.height, - GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]); + GL_RGBA, GL_UNSIGNED_BYTE, map.data); } + + // unbind texture glBindTexture(GL_TEXTURE_2D, 0); + + // unmap buffer to let it free + gst_buffer_unmap (frame_[index].buffer, &map); + } } @@ -1318,7 +1313,7 @@ void MediaPlayer::update() need_loop = true; } // otherwise just fill non-empty SAMPLE or PREROLL - else if (frame_[read_index].full) + else if (frame_[read_index].is_new) { // fill the texture with the frame at reading index fill_texture(read_index); @@ -1327,8 +1322,9 @@ void MediaPlayer::update() if ( (frame_[read_index].status == PREROLL || seeking_ ) && pbo_size_ > 0) fill_texture(read_index); - // free frame - frame_[read_index].unmap(); + // // free frame + // frame_[read_index].unmap(); + frame_[read_index].is_new = false; } // we just displayed a vframe : set position time to frame PTS @@ -1586,7 +1582,10 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status) frame_[write_index_].access.lock(); // always empty frame before filling it again - frame_[write_index_].unmap(); + if (frame_[write_index_].buffer) { + gst_buffer_unref(frame_[write_index_].buffer); + frame_[write_index_].buffer = NULL; + } // accept status of frame received frame_[write_index_].status = status; @@ -1594,46 +1593,23 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status) // a buffer is given (not EOS) if (buf != NULL) { - // get the frame from buffer - if ( !gst_video_frame_map (&frame_[write_index_].vframe, &v_frame_video_info_, buf, GST_MAP_READ ) ) - { -#ifdef MEDIA_PLAYER_DEBUG - Log::Info("MediaPlayer %s Failed to map the video buffer", std::to_string(id_).c_str()); -#endif - // free access to frame & exit - frame_[write_index_].status = INVALID; - frame_[write_index_].access.unlock(); - return false; + // copy the buffer in the frame + frame_[write_index_].buffer = gst_buffer_copy(buf); + + // indicate to update loop that buffer is new + frame_[write_index_].is_new = true; + + // set presentation time stamp + frame_[write_index_].position = buf->pts; + + // set the start position (i.e. pts of first frame we got) + if (timeline_.first() == GST_CLOCK_TIME_NONE) { + timeline_.setFirst(buf->pts); + // add a gap to show that before + if (buf->pts > 0 && !timeline_.gapAt( buf->pts )) + timeline_.addGap(0, buf->pts); } - // successfully filled the frame - frame_[write_index_].full = true; - - // validate frame format - if( GST_VIDEO_INFO_IS_RGB(&(frame_[write_index_].vframe).info) && GST_VIDEO_INFO_N_PLANES(&(frame_[write_index_].vframe).info) == 1) - { - // set presentation time stamp - frame_[write_index_].position = buf->pts; - - // set the start position (i.e. pts of first frame we got) - if (timeline_.first() == GST_CLOCK_TIME_NONE) { - timeline_.setFirst(buf->pts); - // add a gap to show that before - if (buf->pts > 0 && !timeline_.gapAt( buf->pts )) - timeline_.addGap(0, buf->pts); - } - } - // full but invalid frame : will be deleted next iteration - // (should never happen) - else { -#ifdef MEDIA_PLAYER_DEBUG - Log::Info("MediaPlayer %s Received an Invalid frame", std::to_string(id_).c_str()); -#endif - // free access to frame & exit - frame_[write_index_].status = INVALID; - frame_[write_index_].access.unlock(); - return false; - } } // else; null buffer for EOS: give a position else { diff --git a/src/MediaPlayer.h b/src/MediaPlayer.h index c6c7afe..fa91b2b 100644 --- a/src/MediaPlayer.h +++ b/src/MediaPlayer.h @@ -368,18 +368,18 @@ private: } FrameStatus; struct Frame { - GstVideoFrame vframe; + GstBuffer *buffer; FrameStatus status; - bool full; + bool is_new; GstClockTime position; std::mutex access; Frame() { - full = false; + buffer = NULL; + is_new = false; status = INVALID; position = GST_CLOCK_TIME_NONE; } - void unmap(); }; Frame frame_[N_VFRAME]; guint write_index_; diff --git a/src/Stream.cpp b/src/Stream.cpp index d2d42c5..414c2c4 100644 --- a/src/Stream.cpp +++ b/src/Stream.cpp @@ -77,14 +77,6 @@ Stream::~Stream() { Stream::close(); - // cleanup opengl texture - if (textureindex_) - glDeleteTextures(1, &textureindex_); - - // cleanup picture buffer - if (pbo_[0]) - glDeleteBuffers(2, pbo_); - #ifdef STREAM_DEBUG g_printerr("Stream %s deleted\n", std::to_string(id_).c_str()); #endif @@ -137,6 +129,7 @@ GstFlowReturn callback_stream_discoverer (GstAppSink *sink, gpointer p) // release info to let StreamDiscoverer go forward info->discovered.notify_all(); } + gst_caps_unref(caps); } else ret = GST_FLOW_FLUSHING; @@ -265,8 +258,6 @@ void Stream::execute_open() g_clear_error (&error); return; } - // ref gst_object (unrefed on close) - gst_object_ref(pipeline_); // set pipeline name g_object_set(G_OBJECT(pipeline_), "name", std::to_string(id_).c_str(), NULL); @@ -290,6 +281,7 @@ void Stream::execute_open() // instruct sink to use the required caps gst_app_sink_set_caps (GST_APP_SINK(sink), caps); + gst_caps_unref (caps); // Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached. gst_app_sink_set_max_buffers( GST_APP_SINK(sink), 30); @@ -340,10 +332,6 @@ void Stream::execute_open() gst_bus_set_sync_handler(bus_, stream_signal_handler, this, NULL); #endif - // done with refs - gst_object_unref (sink); - gst_caps_unref (caps); - // all good Log::Info("Stream %s Opened '%s' (%d x %d)", std::to_string(id_).c_str(), description.c_str(), width_, height_); opened_ = true; @@ -382,14 +370,6 @@ bool Stream::failed() const return failed_; } -void Stream::Frame::unmap() -{ - if (full) - gst_video_frame_unmap(&vframe); - - full = false; -} - void Stream::pipeline_terminate( GstElement *p, GstBus *b ) { @@ -444,7 +424,8 @@ void Stream::close() // cleanup eventual remaining frame memory for(guint i = 0; i < N_FRAME; ++i){ frame_[i].access.lock(); - frame_[i].unmap(); + if (frame_[i].buffer) + gst_buffer_unref(frame_[i].buffer); frame_[i].status = INVALID; frame_[i].access.unlock(); } @@ -460,6 +441,17 @@ void Stream::close() bus_ = nullptr; } + // cleanup opengl texture + if (textureindex_) { + glDeleteTextures(1, &textureindex_); + textureindex_ = 0; + } + + // cleanup picture buffer + if (pbo_[0]) { + glDeleteBuffers(2, pbo_); + pbo_[0] = 0; + } } @@ -607,13 +599,21 @@ void Stream::init_texture(guint index) glGenTextures(1, &textureindex_); glBindTexture(GL_TEXTURE_2D, textureindex_); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width_, height_); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, - GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]); + + // fill texture with frame at given index + if (frame_[index].buffer) { + GstMapInfo map; + gst_buffer_map(frame_[index].buffer, &map, GST_MAP_READ); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, map.data); + gst_buffer_unmap(frame_[index].buffer, &map); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + // use Pixel Buffer Objects only for performance needs of videos if (!single_frame_) { // set pbo image size @@ -624,31 +624,7 @@ void Stream::init_texture(guint index) glDeleteBuffers(2, pbo_); glGenBuffers(2, pbo_); - for(int i = 0; i < 2; i++ ) { - // create 2 PBOs - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[i]); - // glBufferDataARB with NULL pointer reserves only memory space. - glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW); - // fill in with reset picture - GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); - if (ptr) { - // update data directly on the mapped buffer - memmove(ptr, frame_[index].vframe.data[0], pbo_size_); - // release pointer to mapping buffer - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - } - else { - // did not work, disable PBO - glDeleteBuffers(2, pbo_); - pbo_[0] = pbo_[1] = 0; - pbo_size_ = 0; - break; - } - - } - - // should be good to go, wrap it up - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + // should be good to go pbo_index_ = 0; pbo_next_index_ = 1; @@ -670,48 +646,62 @@ void Stream::fill_texture(guint index) if ( !textureinitialized_ || !textureindex_) { // initialize texture + // (this also fills the texture with frame at index) init_texture(index); } + else { + // Use GST mapping to access pointer to RGBA data + GstMapInfo map; + gst_buffer_map(frame_[index].buffer, &map, GST_MAP_READ); - glBindTexture(GL_TEXTURE_2D, textureindex_); + // bind texture for writing + glBindTexture(GL_TEXTURE_2D, textureindex_); - // use dual Pixel Buffer Object - if (pbo_size_ > 0) { - // In dual PBO mode, increment current index first then get the next index - pbo_index_ = (pbo_index_ + 1) % 2; - pbo_next_index_ = (pbo_index_ + 1) % 2; + // use dual Pixel Buffer Object + if (pbo_size_ > 0 && map.size == pbo_size_) { - // bind PBO to read pixels - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_index_]); - // copy pixels from PBO to texture object - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, 0); - // bind the next PBO to write pixels - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]); + // In dual PBO mode, increment current index first then get the next index + pbo_index_ = (pbo_index_ + 1) % 2; + pbo_next_index_ = (pbo_index_ + 1) % 2; + + // bind PBO to read pixels + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_index_]); + // copy pixels from PBO to texture object + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, 0); + // bind the next PBO to write pixels + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]); #ifdef USE_GL_BUFFER_SUBDATA - glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, pbo_size_, frame_[index].vframe.data[0]); + glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, pbo_size_, map.data); #else - // update data directly on the mapped buffer - // NB : equivalent but faster than glBufferSubData (memmove instead of memcpy ?) + // update data directly on the mapped buffer \ + // NB : equivalent but faster than glBufferSubData (memmove instead of memcpy ?) \ // See http://www.songho.ca/opengl/gl_pbo.html#map for more details glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW); + // map the buffer object into client's memory GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); if (ptr) - memmove(ptr, frame_[index].vframe.data[0], pbo_size_); + memmove(ptr, map.data, pbo_size_); + // release pointer to mapping buffer glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); #endif - // done with PBO - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - } - else { - // without PBO, use standard opengl (slower) - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, - GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]); - } - glBindTexture(GL_TEXTURE_2D, 0); + // done with PBO + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + else { + // without PBO, use standard opengl (slower) + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, + GL_RGBA, GL_UNSIGNED_BYTE, map.data); + } + // unbind texture + glBindTexture(GL_TEXTURE_2D, 0); + + // unmap buffer to let it free + gst_buffer_unmap (frame_[index].buffer, &map); + } } void Stream::update() @@ -778,7 +768,7 @@ void Stream::update() need_loop = true; } // otherwise just fill non-empty SAMPLE or PREROLL - else if (frame_[read_index].full) + else if (frame_[read_index].is_new) { // fill the texture with the frame at reading index fill_texture(read_index); @@ -788,7 +778,7 @@ void Stream::update() fill_texture(read_index); // free frame - frame_[read_index].unmap(); + frame_[read_index].is_new = false; } // we just displayed a vframe : set position time to frame PTS @@ -833,43 +823,25 @@ bool Stream::fill_frame(GstBuffer *buf, FrameStatus status) frame_[write_index_].access.lock(); // always empty frame before filling it again - frame_[write_index_].unmap(); + if (frame_[write_index_].buffer) { + gst_buffer_unref(frame_[write_index_].buffer); + frame_[write_index_].buffer = NULL; + } // accept status of frame received frame_[write_index_].status = status; // a buffer is given (not EOS) if (buf != NULL) { - // get the frame from buffer - if ( !gst_video_frame_map (&frame_[write_index_].vframe, &v_frame_video_info_, buf, GST_MAP_READ ) ) - { - Log::Info("Stream %s Failed to map the video buffer", std::to_string(id_).c_str()); - // free access to frame & exit - frame_[write_index_].status = INVALID; - frame_[write_index_].access.unlock(); - return false; - } - // successfully filled the frame - frame_[write_index_].full = true; + // copy the buffer in the frame + frame_[write_index_].buffer = gst_buffer_copy(buf); - // validate frame format - if( GST_VIDEO_INFO_IS_RGB(&(frame_[write_index_].vframe).info) && GST_VIDEO_INFO_N_PLANES(&(frame_[write_index_].vframe).info) == 1) - { - // set presentation time stamp - frame_[write_index_].position = buf->pts; + // indicate to update loop that buffer is new + frame_[write_index_].is_new = true; - } - // full but invalid frame : will be deleted next iteration - // (should never happen) - else { -#ifdef STREAM_DEBUG - Log::Info("Stream %s Received an Invalid frame", std::to_string(id_).c_str()); -#endif - frame_[write_index_].status = INVALID; - frame_[write_index_].access.unlock(); - return false; - } + // set presentation time stamp + frame_[write_index_].position = buf->pts; } // else; null buffer for EOS: give a position else { diff --git a/src/Stream.h b/src/Stream.h index 32a635c..402e665 100644 --- a/src/Stream.h +++ b/src/Stream.h @@ -213,18 +213,18 @@ protected: } FrameStatus; struct Frame { - GstVideoFrame vframe; + GstBuffer *buffer; FrameStatus status; - bool full; + bool is_new; GstClockTime position; std::mutex access; Frame() { - full = false; + buffer = NULL; + is_new = false; status = INVALID; position = GST_CLOCK_TIME_NONE; } - void unmap(); }; Frame frame_[N_FRAME]; guint write_index_; diff --git a/src/TextSource.cpp b/src/TextSource.cpp index 62b36ca..432d5bb 100644 --- a/src/TextSource.cpp +++ b/src/TextSource.cpp @@ -118,6 +118,7 @@ void TextContents::execute_open() // instruct sink to use the required caps gst_app_sink_set_caps(GST_APP_SINK(sink), caps); + gst_caps_unref(caps); // Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached. gst_app_sink_set_max_buffers(GST_APP_SINK(sink), 30); @@ -203,9 +204,9 @@ void TextContents::execute_open() // instruct the sink to send samples synched in time if not live source gst_base_sink_set_sync(GST_BASE_SINK(sink), !live_); - // done with refs - gst_object_unref(sink); - gst_caps_unref(caps); + bus_ = gst_element_get_bus(pipeline_); + // avoid filling up bus with messages + gst_bus_set_flushing(bus_, true); // all good Log::Info("TextContents: %s Opened '%s' (%d x %d)",