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.
This commit is contained in:
Bruno Herbelin
2024-10-04 16:29:02 +02:00
parent 01d3a91e40
commit b2ce0f3934
5 changed files with 159 additions and 210 deletions

View File

@@ -96,14 +96,6 @@ MediaPlayer::~MediaPlayer()
{ {
close(); close();
// cleanup opengl texture
if (textureindex_)
glDeleteTextures(1, &textureindex_);
// cleanup picture buffer
if (pbo_[0])
glDeleteBuffers(2, pbo_);
#ifdef MEDIA_PLAYER_DEBUG #ifdef MEDIA_PLAYER_DEBUG
g_printerr("MediaPlayer %s deleted\n", std::to_string(id_).c_str()); g_printerr("MediaPlayer %s deleted\n", std::to_string(id_).c_str());
#endif #endif
@@ -426,7 +418,6 @@ void MediaPlayer::execute_open()
else { else {
g_object_set( G_OBJECT (pipeline_), "video-filter", effect, nullptr); 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()); 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_; return failed_;
} }
void MediaPlayer::Frame::unmap()
{
if (full)
gst_video_frame_unmap(&vframe);
full = false;
}
void MediaPlayer::pipeline_terminate( GstElement *p, GstBus *b ) void MediaPlayer::pipeline_terminate( GstElement *p, GstBus *b )
{ {
gchar *name = gst_element_get_name(p); gchar *name = gst_element_get_name(p);
@@ -819,7 +802,8 @@ void MediaPlayer::close()
// cleanup eventual remaining frame 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(); frame_[i].access.lock();
frame_[i].unmap(); if (frame_[i].buffer)
gst_buffer_unref(frame_[i].buffer);
frame_[i].status = INVALID; frame_[i].status = INVALID;
frame_[i].access.unlock(); frame_[i].access.unlock();
} }
@@ -834,6 +818,18 @@ void MediaPlayer::close()
pipeline_ = nullptr; pipeline_ = nullptr;
bus_ = 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_); glGenTextures(1, &textureindex_);
glBindTexture(GL_TEXTURE_2D, textureindex_); glBindTexture(GL_TEXTURE_2D, textureindex_);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, media_.width, media_.height); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, media_.width, media_.height);
// 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, 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);
gst_buffer_unmap (frame_[index].buffer, &map);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 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_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 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() ) { if ( !singleFrame() ) {
// set pbo image size // set pbo image size
pbo_size_ = media_.height * media_.width * 4; pbo_size_ = media_.height * media_.width * 4;
@@ -1172,31 +1176,7 @@ void MediaPlayer::init_texture(guint index)
glDeleteBuffers(2, pbo_); glDeleteBuffers(2, pbo_);
glGenBuffers(2, pbo_); glGenBuffers(2, pbo_);
for(int i = 0; i < 2; i++ ) { // should be good to go
// 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);
pbo_index_ = 0; pbo_index_ = 0;
pbo_next_index_ = 1; pbo_next_index_ = 1;
@@ -1214,14 +1194,21 @@ void MediaPlayer::fill_texture(guint index)
// is this the first frame ? // is this the first frame ?
if (textureindex_ < 1) if (textureindex_ < 1)
{ {
// initialize texture // initialize texture on first run
// (this also fills the texture with frame at index)
init_texture(index); init_texture(index);
} }
else { 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_); glBindTexture(GL_TEXTURE_2D, textureindex_);
// use dual Pixel Buffer Object // use dual Pixel Buffer Object (faster)
if (pbo_size_ > 0) { if (pbo_size_ > 0 && map.size == pbo_size_) {
// In dual PBO mode, increment current index first then get the next index // In dual PBO mode, increment current index first then get the next index
pbo_index_ = (pbo_index_ + 1) % 2; pbo_index_ = (pbo_index_ + 1) % 2;
pbo_next_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); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, media_.width, media_.height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
// bind the next PBO to write pixels // bind the next PBO to write pixels
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]);
#ifdef USE_GL_BUFFER_SUBDATA #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 #else
// update data directly on the mapped buffer // update data directly on the mapped buffer
// NB : equivalent but faster than glBufferSubData (memmove instead of memcpy ?) // 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 // map the buffer object into client's memory
GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
if (ptr) if (ptr)
memmove(ptr, frame_[index].vframe.data[0], pbo_size_); memmove(ptr, map.data, pbo_size_);
// release pointer to mapping buffer // release pointer to mapping buffer
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
#endif #endif
@@ -1252,9 +1241,15 @@ void MediaPlayer::fill_texture(guint index)
else { else {
// without PBO, use standard opengl (slower) // without PBO, use standard opengl (slower)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 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]); GL_RGBA, GL_UNSIGNED_BYTE, map.data);
} }
// unbind texture
glBindTexture(GL_TEXTURE_2D, 0); 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; need_loop = true;
} }
// otherwise just fill non-empty SAMPLE or PREROLL // 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 the texture with the frame at reading index
fill_texture(read_index); fill_texture(read_index);
@@ -1327,8 +1322,9 @@ void MediaPlayer::update()
if ( (frame_[read_index].status == PREROLL || seeking_ ) && pbo_size_ > 0) if ( (frame_[read_index].status == PREROLL || seeking_ ) && pbo_size_ > 0)
fill_texture(read_index); fill_texture(read_index);
// free frame // // free frame
frame_[read_index].unmap(); // frame_[read_index].unmap();
frame_[read_index].is_new = false;
} }
// we just displayed a vframe : set position time to frame PTS // 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(); frame_[write_index_].access.lock();
// always empty frame before filling it again // 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 // accept status of frame received
frame_[write_index_].status = status; frame_[write_index_].status = status;
@@ -1594,24 +1593,12 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status)
// a buffer is given (not EOS) // a buffer is given (not EOS)
if (buf != NULL) { if (buf != NULL) {
// get the frame from buffer // copy the buffer in the frame
if ( !gst_video_frame_map (&frame_[write_index_].vframe, &v_frame_video_info_, buf, GST_MAP_READ ) ) frame_[write_index_].buffer = gst_buffer_copy(buf);
{
#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;
}
// successfully filled the frame // indicate to update loop that buffer is new
frame_[write_index_].full = true; frame_[write_index_].is_new = 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 // set presentation time stamp
frame_[write_index_].position = buf->pts; frame_[write_index_].position = buf->pts;
@@ -1622,18 +1609,7 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status)
if (buf->pts > 0 && !timeline_.gapAt( buf->pts )) if (buf->pts > 0 && !timeline_.gapAt( buf->pts ))
timeline_.addGap(0, 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; null buffer for EOS: give a position
else { else {

View File

@@ -368,18 +368,18 @@ private:
} FrameStatus; } FrameStatus;
struct Frame { struct Frame {
GstVideoFrame vframe; GstBuffer *buffer;
FrameStatus status; FrameStatus status;
bool full; bool is_new;
GstClockTime position; GstClockTime position;
std::mutex access; std::mutex access;
Frame() { Frame() {
full = false; buffer = NULL;
is_new = false;
status = INVALID; status = INVALID;
position = GST_CLOCK_TIME_NONE; position = GST_CLOCK_TIME_NONE;
} }
void unmap();
}; };
Frame frame_[N_VFRAME]; Frame frame_[N_VFRAME];
guint write_index_; guint write_index_;

View File

@@ -77,14 +77,6 @@ Stream::~Stream()
{ {
Stream::close(); Stream::close();
// cleanup opengl texture
if (textureindex_)
glDeleteTextures(1, &textureindex_);
// cleanup picture buffer
if (pbo_[0])
glDeleteBuffers(2, pbo_);
#ifdef STREAM_DEBUG #ifdef STREAM_DEBUG
g_printerr("Stream %s deleted\n", std::to_string(id_).c_str()); g_printerr("Stream %s deleted\n", std::to_string(id_).c_str());
#endif #endif
@@ -137,6 +129,7 @@ GstFlowReturn callback_stream_discoverer (GstAppSink *sink, gpointer p)
// release info to let StreamDiscoverer go forward // release info to let StreamDiscoverer go forward
info->discovered.notify_all(); info->discovered.notify_all();
} }
gst_caps_unref(caps);
} }
else else
ret = GST_FLOW_FLUSHING; ret = GST_FLOW_FLUSHING;
@@ -265,8 +258,6 @@ void Stream::execute_open()
g_clear_error (&error); g_clear_error (&error);
return; return;
} }
// ref gst_object (unrefed on close)
gst_object_ref(pipeline_);
// set pipeline name // set pipeline name
g_object_set(G_OBJECT(pipeline_), "name", std::to_string(id_).c_str(), NULL); 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 // 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);
gst_caps_unref (caps);
// Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached. // 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); 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); gst_bus_set_sync_handler(bus_, stream_signal_handler, this, NULL);
#endif #endif
// done with refs
gst_object_unref (sink);
gst_caps_unref (caps);
// all good // all good
Log::Info("Stream %s Opened '%s' (%d x %d)", std::to_string(id_).c_str(), description.c_str(), width_, height_); Log::Info("Stream %s Opened '%s' (%d x %d)", std::to_string(id_).c_str(), description.c_str(), width_, height_);
opened_ = true; opened_ = true;
@@ -382,14 +370,6 @@ bool Stream::failed() const
return failed_; return failed_;
} }
void Stream::Frame::unmap()
{
if (full)
gst_video_frame_unmap(&vframe);
full = false;
}
void Stream::pipeline_terminate( GstElement *p, GstBus *b ) void Stream::pipeline_terminate( GstElement *p, GstBus *b )
{ {
@@ -444,7 +424,8 @@ void Stream::close()
// cleanup eventual remaining frame memory // cleanup eventual remaining frame memory
for(guint i = 0; i < N_FRAME; ++i){ for(guint i = 0; i < N_FRAME; ++i){
frame_[i].access.lock(); frame_[i].access.lock();
frame_[i].unmap(); if (frame_[i].buffer)
gst_buffer_unref(frame_[i].buffer);
frame_[i].status = INVALID; frame_[i].status = INVALID;
frame_[i].access.unlock(); frame_[i].access.unlock();
} }
@@ -460,6 +441,17 @@ void Stream::close()
bus_ = 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;
}
} }
@@ -607,13 +599,21 @@ void Stream::init_texture(guint index)
glGenTextures(1, &textureindex_); glGenTextures(1, &textureindex_);
glBindTexture(GL_TEXTURE_2D, textureindex_); glBindTexture(GL_TEXTURE_2D, textureindex_);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width_, height_); 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_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_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_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 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_) { if (!single_frame_) {
// set pbo image size // set pbo image size
@@ -624,31 +624,7 @@ void Stream::init_texture(guint index)
glDeleteBuffers(2, pbo_); glDeleteBuffers(2, pbo_);
glGenBuffers(2, pbo_); glGenBuffers(2, pbo_);
for(int i = 0; i < 2; i++ ) { // should be good to go
// 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);
pbo_index_ = 0; pbo_index_ = 0;
pbo_next_index_ = 1; pbo_next_index_ = 1;
@@ -670,13 +646,20 @@ void Stream::fill_texture(guint index)
if ( !textureinitialized_ || !textureindex_) if ( !textureinitialized_ || !textureindex_)
{ {
// initialize texture // initialize texture
// (this also fills the texture with frame at index)
init_texture(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_); glBindTexture(GL_TEXTURE_2D, textureindex_);
// use dual Pixel Buffer Object // use dual Pixel Buffer Object
if (pbo_size_ > 0) { if (pbo_size_ > 0 && map.size == pbo_size_) {
// In dual PBO mode, increment current index first then get the next index // In dual PBO mode, increment current index first then get the next index
pbo_index_ = (pbo_index_ + 1) % 2; pbo_index_ = (pbo_index_ + 1) % 2;
pbo_next_index_ = (pbo_index_ + 1) % 2; pbo_next_index_ = (pbo_index_ + 1) % 2;
@@ -688,30 +671,37 @@ void Stream::fill_texture(guint index)
// bind the next PBO to write pixels // bind the next PBO to write pixels
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]);
#ifdef USE_GL_BUFFER_SUBDATA #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 #else
// update data directly on the mapped buffer // update data directly on the mapped buffer \
// NB : equivalent but faster than glBufferSubData (memmove instead of memcpy ?) // NB : equivalent but faster than glBufferSubData (memmove instead of memcpy ?) \
// See http://www.songho.ca/opengl/gl_pbo.html#map for more details // See http://www.songho.ca/opengl/gl_pbo.html#map for more details
glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW); glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW);
// map the buffer object into client's memory // map the buffer object into client's memory
GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
if (ptr) if (ptr)
memmove(ptr, frame_[index].vframe.data[0], pbo_size_); memmove(ptr, map.data, pbo_size_);
// release pointer to mapping buffer // release pointer to mapping buffer
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
#endif #endif
// done with PBO // done with PBO
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
} }
else { else {
// without PBO, use standard opengl (slower) // without PBO, use standard opengl (slower)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, 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); glBindTexture(GL_TEXTURE_2D, 0);
// unmap buffer to let it free
gst_buffer_unmap (frame_[index].buffer, &map);
}
} }
void Stream::update() void Stream::update()
@@ -778,7 +768,7 @@ void Stream::update()
need_loop = true; need_loop = true;
} }
// otherwise just fill non-empty SAMPLE or PREROLL // 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 the texture with the frame at reading index
fill_texture(read_index); fill_texture(read_index);
@@ -788,7 +778,7 @@ void Stream::update()
fill_texture(read_index); fill_texture(read_index);
// free frame // free frame
frame_[read_index].unmap(); frame_[read_index].is_new = false;
} }
// we just displayed a vframe : set position time to frame PTS // 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(); frame_[write_index_].access.lock();
// always empty frame before filling it again // 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 // 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)
if (buf != NULL) { 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 // copy the buffer in the frame
frame_[write_index_].full = true; frame_[write_index_].buffer = gst_buffer_copy(buf);
// indicate to update loop that buffer is new
frame_[write_index_].is_new = 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 // set presentation time stamp
frame_[write_index_].position = buf->pts; frame_[write_index_].position = buf->pts;
}
// 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;
}
} }
// else; null buffer for EOS: give a position // else; null buffer for EOS: give a position
else { else {

View File

@@ -213,18 +213,18 @@ protected:
} FrameStatus; } FrameStatus;
struct Frame { struct Frame {
GstVideoFrame vframe; GstBuffer *buffer;
FrameStatus status; FrameStatus status;
bool full; bool is_new;
GstClockTime position; GstClockTime position;
std::mutex access; std::mutex access;
Frame() { Frame() {
full = false; buffer = NULL;
is_new = false;
status = INVALID; status = INVALID;
position = GST_CLOCK_TIME_NONE; position = GST_CLOCK_TIME_NONE;
} }
void unmap();
}; };
Frame frame_[N_FRAME]; Frame frame_[N_FRAME];
guint write_index_; guint write_index_;

View File

@@ -118,6 +118,7 @@ void TextContents::execute_open()
// 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);
gst_caps_unref(caps);
// Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached. // 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); 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 // instruct the sink to send samples synched in time if not live source
gst_base_sink_set_sync(GST_BASE_SINK(sink), !live_); gst_base_sink_set_sync(GST_BASE_SINK(sink), !live_);
// done with refs bus_ = gst_element_get_bus(pipeline_);
gst_object_unref(sink); // avoid filling up bus with messages
gst_caps_unref(caps); gst_bus_set_flushing(bus_, true);
// all good // all good
Log::Info("TextContents: %s Opened '%s' (%d x %d)", Log::Info("TextContents: %s Opened '%s' (%d x %d)",