Fixed memory leak in gst media player (Valgrind)

This commit is contained in:
brunoherbelin
2020-08-11 23:02:28 +02:00
parent bb8dcf088e
commit 675856d57c
3 changed files with 64 additions and 66 deletions

View File

@@ -42,9 +42,6 @@ MediaPlayer::MediaPlayer(string name) : id_(name)
width_ = par_width_ = 640; width_ = par_width_ = 640;
height_ = 480; height_ = 480;
position_ = GST_CLOCK_TIME_NONE; position_ = GST_CLOCK_TIME_NONE;
// duration_ = GST_CLOCK_TIME_NONE;
// start_position_ = GST_CLOCK_TIME_NONE;
// frame_duration_ = GST_CLOCK_TIME_NONE;
desired_state_ = GST_STATE_PAUSED; desired_state_ = GST_STATE_PAUSED;
loop_ = LoopMode::LOOP_REWIND; loop_ = LoopMode::LOOP_REWIND;
@@ -124,7 +121,7 @@ void MediaPlayer::execute_open()
// " uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! appsink " // " uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! appsink "
// equivalent to gst-launch-1.0 uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! ximagesink // equivalent to gst-launch-1.0 uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! ximagesink
// build string describing pipeline // Build string describing pipeline
// NB: video convertion chroma-resampler // NB: video convertion chroma-resampler
// Duplicates the samples when upsampling and drops when downsampling 0 // Duplicates the samples when upsampling and drops when downsampling 0
// Uses linear interpolation 1 (default) // Uses linear interpolation 1 (default)
@@ -161,8 +158,6 @@ void MediaPlayer::execute_open()
// instruct the sink to send samples synched in time // instruct the sink to send samples synched in time
gst_base_sink_set_sync (GST_BASE_SINK(sink), true); gst_base_sink_set_sync (GST_BASE_SINK(sink), true);
// gst_base_sink_set_max_lateness (GST_BASE_SINK(sink), 0 );
// gst_base_sink_set_processing_deadline (GST_BASE_SINK(sink), 0 );
// instruct sink to use the required caps // instruct sink to use the required caps
gst_app_sink_set_caps (GST_APP_SINK(sink), caps); gst_app_sink_set_caps (GST_APP_SINK(sink), caps);
@@ -213,16 +208,17 @@ void MediaPlayer::execute_open()
return; return;
} }
// in case discoverer failed to get duration
if (timeline.end() == GST_CLOCK_TIME_NONE) {
gint64 d = GST_CLOCK_TIME_NONE;
if ( gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &d) )
timeline.setEnd(d);
}
// all good // all good
Log::Info("MediaPlayer %s Open %s (%s %d x %d)", id_.c_str(), uri_.c_str(), codec_name_.c_str(), width_, height_); Log::Info("MediaPlayer %s Open %s (%s %d x %d)", id_.c_str(), uri_.c_str(), codec_name_.c_str(), width_, height_);
ready_ = true; ready_ = true;
// // in case discoverer failed to get duration
// if (timeline.end() == GST_CLOCK_TIME_NONE) {
// gint64 d = GST_CLOCK_TIME_NONE;
// if ( gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &d) )
// timeline.setEnd(d);
// }
// register media player // register media player
MediaPlayer::registered_.push_back(this); MediaPlayer::registered_.push_back(this);
@@ -268,14 +264,13 @@ void MediaPlayer::close()
pipeline_ = nullptr; pipeline_ = nullptr;
} }
// // cleanup eventual remaining frame related memory // cleanup eventual remaining frame memory
// for(guint i = 0; i < N_VFRAME; i++){ for(guint i = 0; i < N_VFRAME; i++){
//// frame_[i].access.lock(); if ( frame_[i].full ) {
// if (frame_[i].vframe.buffer) gst_video_frame_unmap(&frame_[i].vframe);
// gst_video_frame_unmap(&frame_[i].vframe); frame_[i].status = INVALID;
// frame_[i].status = EMPTY; }
//// frame_[i].access.unlock(); }
// }
// cleanup opengl texture // cleanup opengl texture
if (textureindex_) if (textureindex_)
@@ -463,7 +458,6 @@ void MediaPlayer::seek(GstClockTime pos)
// apply seek // apply seek
GstClockTime target = CLAMP(pos, 0, timeline.end()); GstClockTime target = CLAMP(pos, 0, timeline.end());
// GstClockTime target = CLAMP(pos, timeline.start(), timeline.end()); // GstClockTime target = CLAMP(pos, timeline.start(), timeline.end());
// TODO: confirm that PTS are not possibly ZERO (use of start() is neceessary)
execute_seek_command(target); execute_seek_command(target);
} }
@@ -603,7 +597,7 @@ void MediaPlayer::update()
guint read_index = 0; guint read_index = 0;
bool need_loop = false; bool need_loop = false;
// locked access to last index // locked access to changing index
index_lock_.lock(); index_lock_.lock();
read_index = last_index_; read_index = last_index_;
// Do NOT miss an EOS or a pre-roll // Do NOT miss an EOS or a pre-roll
@@ -615,15 +609,11 @@ void MediaPlayer::update()
} }
index_lock_.unlock(); index_lock_.unlock();
// // lock frame while reading it // lock frame while reading it
// if (!frame_[read_index].access.try_lock())
// // do not block rendering if everything is too busy
// return;
frame_[read_index].access.lock(); frame_[read_index].access.lock();
// do not fill a frame twice // do not fill a frame twice
if (frame_[read_index].status != EMPTY ) { if (frame_[read_index].status != INVALID ) {
// is this an End-of-Stream frame ? // is this an End-of-Stream frame ?
@@ -632,22 +622,22 @@ void MediaPlayer::update()
// will execute seek command below (after unlock) // will execute seek command below (after unlock)
need_loop = true; need_loop = true;
} }
// otherwise just fill non-empty samples // otherwise just fill non-empty SAMPLE or PREROLL
else else if (frame_[read_index].full)
{ {
// fill the texture with the frame at reading index // fill the texture with the frame at reading index
fill_texture(read_index); fill_texture(read_index);
// double update for pre-roll and dual FPO (ensure frame is displayed now) // double update for pre-roll frame and dual PBO (ensure frame is displayed now)
if (frame_[read_index].status == PREROLL && pbo_size_ > 0) if (frame_[read_index].status == PREROLL && pbo_size_ > 0)
fill_texture(read_index); fill_texture(read_index);
} }
// we just displayed a vframe : set position time to vframe PTS // we just displayed a vframe : set position time to frame PTS
position_ = frame_[read_index].position; position_ = frame_[read_index].position;
// avoid reading it again // avoid reading it again
frame_[read_index].status = EMPTY; frame_[read_index].status = INVALID;
// // TODO : try to do something when the update is too slow :( // // TODO : try to do something when the update is too slow :(
// if ( timecount_.dt() > frame_duration_ * 2) { // if ( timecount_.dt() > frame_duration_ * 2) {
@@ -805,18 +795,18 @@ double MediaPlayer::updateFrameRate() const
// CALLBACKS // CALLBACKS
bool MediaPlayer::fill_frame(GstBuffer *buf, MediaPlayer::FrameStatus status) bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status)
{ {
// lock access to frame // lock access to frame
frame_[write_index_].access.lock(); frame_[write_index_].access.lock();
// always empty frame before filling it again // always empty frame before filling it again
if (frame_[write_index_].vframe.buffer) { if ( frame_[write_index_].full ) {
gst_video_frame_unmap(&frame_[write_index_].vframe); gst_video_frame_unmap(&frame_[write_index_].vframe);
frame_[write_index_].vframe.buffer = nullptr; frame_[write_index_].full = false;
} }
// indicate status of frame received // accept status of frame received
frame_[write_index_].status = status; frame_[write_index_].status = status;
// a buffer is given (not EOS) // a buffer is given (not EOS)
@@ -827,11 +817,14 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, MediaPlayer::FrameStatus status)
{ {
Log::Info("MediaPlayer %s Failed to map the video buffer", id_.c_str()); Log::Info("MediaPlayer %s Failed to map the video buffer", id_.c_str());
// free access to frame & exit // free access to frame & exit
frame_[write_index_].status = EMPTY; frame_[write_index_].status = INVALID;
frame_[write_index_].access.unlock(); frame_[write_index_].access.unlock();
return false; return false;
} }
// successfully filled the frame
frame_[write_index_].full = true;
// validate frame format // 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) if( GST_VIDEO_INFO_IS_RGB(&(frame_[write_index_].vframe).info) && GST_VIDEO_INFO_N_PLANES(&(frame_[write_index_].vframe).info) == 1)
{ {
@@ -842,9 +835,14 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, MediaPlayer::FrameStatus status)
if (timeline.start() == GST_CLOCK_TIME_NONE) if (timeline.start() == GST_CLOCK_TIME_NONE)
timeline.setStart(buf->pts); timeline.setStart(buf->pts);
} }
// full but invalid frame : will be deleted next iteration
// (should never happen)
else
frame_[write_index_].status = INVALID;
} }
// give a position to EOS // else; null buffer for EOS: give a position
else { else {
frame_[write_index_].status = EOS;
frame_[write_index_].position = rate_ > 0.0 ? timeline.end() : timeline.start(); frame_[write_index_].position = rate_ > 0.0 ? timeline.end() : timeline.start();
} }
@@ -885,27 +883,26 @@ GstFlowReturn MediaPlayer::callback_new_preroll (GstAppSink *sink, gpointer p)
if (sample != NULL) { if (sample != NULL) {
// get buffer from sample // get buffer from sample
GstBuffer *buf = /*gst_buffer_ref */( gst_sample_get_buffer (sample) ); GstBuffer *buf = gst_sample_get_buffer (sample);
// send frames to media player only if ready
MediaPlayer *m = (MediaPlayer *)p; MediaPlayer *m = (MediaPlayer *)p;
if (m && m->ready_) { if (m && m->ready_) {
// fill frame from buffer // fill frame from buffer
if ( !m->fill_frame(buf, MediaPlayer::PREROLL) ) if ( !m->fill_frame(buf, MediaPlayer::PREROLL) )
ret = GST_FLOW_ERROR; ret = GST_FLOW_ERROR;
// loop negative rate: emulate an EOS // loop negative rate: emulate an EOS
if (m->playSpeed() < 0.f && buf->pts <= m->timeline.start()) { else if (m->playSpeed() < 0.f && buf->pts <= m->timeline.start()) {
m->fill_frame(NULL, MediaPlayer::EOS); m->fill_frame(NULL, MediaPlayer::EOS);
} }
} }
// free buffers
// gst_buffer_unref (buf);
gst_sample_unref (sample);
} }
else else
ret = GST_FLOW_FLUSHING; ret = GST_FLOW_FLUSHING;
// release sample
gst_sample_unref (sample);
return ret; return ret;
} }
@@ -914,36 +911,32 @@ GstFlowReturn MediaPlayer::callback_new_sample (GstAppSink *sink, gpointer p)
GstFlowReturn ret = GST_FLOW_OK; GstFlowReturn ret = GST_FLOW_OK;
// non-blocking read new sample // non-blocking read new sample
GstSample *sample = gst_app_sink_try_pull_sample(sink, 0); GstSample *sample = gst_app_sink_pull_sample(sink);
// if got a valid sample // if got a valid sample
if (sample != NULL && !gst_app_sink_is_eos (sink)) { if (sample != NULL && !gst_app_sink_is_eos (sink)) {
// get buffer from sample // get buffer from sample (valid until sample is released)
GstBuffer *buf = /*gst_buffer_ref*/ ( gst_sample_get_buffer (sample) ); GstBuffer *buf = gst_sample_get_buffer (sample) ;
// send frames to media player only if ready
MediaPlayer *m = (MediaPlayer *)p; MediaPlayer *m = (MediaPlayer *)p;
if (m && m->ready_) { if (m && m->ready_) {
// fill frame with buffer // fill frame with buffer
if ( !m->fill_frame(buf, MediaPlayer::SAMPLE) ) if ( !m->fill_frame(buf, MediaPlayer::SAMPLE) )
ret = GST_FLOW_ERROR; ret = GST_FLOW_ERROR;
// loop negative rate: emulate an EOS // loop negative rate: emulate an EOS
if (m->playSpeed() < 0.f && buf->pts <= m->timeline.start()) { else if (m->playSpeed() < 0.f && buf->pts <= m->timeline.start()) {
m->fill_frame(NULL, MediaPlayer::EOS); m->fill_frame(NULL, MediaPlayer::EOS);
} }
} }
}
else
ret = GST_FLOW_FLUSHING;
// free buffer & sample // release sample
// gst_buffer_unref (buf);
gst_sample_unref (sample); gst_sample_unref (sample);
}
// else
// ret = GST_FLOW_FLUSHING;
return ret; return ret;
} }

View File

@@ -240,21 +240,22 @@ private:
// frame stack // frame stack
typedef enum { typedef enum {
EMPTY = 0, SAMPLE = 0,
SAMPLE = 1, PREROLL = 1,
PREROLL = 2, EOS = 2,
EOS = 4 INVALID = 3
} FrameStatus; } FrameStatus;
struct Frame { struct Frame {
GstVideoFrame vframe; GstVideoFrame vframe;
FrameStatus status; FrameStatus status;
bool full;
GstClockTime position; GstClockTime position;
std::mutex access; std::mutex access;
Frame() { Frame() {
vframe.buffer = nullptr; full = false;
status = EMPTY; status = INVALID;
position = GST_CLOCK_TIME_NONE; position = GST_CLOCK_TIME_NONE;
} }
}; };

View File

@@ -154,6 +154,10 @@ Source::~Source()
for (auto it = clones_.begin(); it != clones_.end(); it++) for (auto it = clones_.begin(); it != clones_.end(); it++)
(*it)->origin_ = nullptr; (*it)->origin_ = nullptr;
// don't forget that the processing shader
// could be created but not used
if ( renderingshader_ != processingshader_ )
delete processingshader_;
} }
void Source::setName (const std::string &name) void Source::setName (const std::string &name)