mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-17 05:09:58 +01:00
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:
@@ -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);
|
||||||
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_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,46 +1593,23 @@ 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
|
// indicate to update loop that buffer is new
|
||||||
Log::Info("MediaPlayer %s Failed to map the video buffer", std::to_string(id_).c_str());
|
frame_[write_index_].is_new = true;
|
||||||
#endif
|
|
||||||
// free access to frame & exit
|
// set presentation time stamp
|
||||||
frame_[write_index_].status = INVALID;
|
frame_[write_index_].position = buf->pts;
|
||||||
frame_[write_index_].access.unlock();
|
|
||||||
return false;
|
// 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; null buffer for EOS: give a position
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
182
src/Stream.cpp
182
src/Stream.cpp
@@ -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,48 +646,62 @@ 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);
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, textureindex_);
|
// bind texture for writing
|
||||||
|
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
|
|
||||||
pbo_index_ = (pbo_index_ + 1) % 2;
|
|
||||||
pbo_next_index_ = (pbo_index_ + 1) % 2;
|
|
||||||
|
|
||||||
// bind PBO to read pixels
|
// In dual PBO mode, increment current index first then get the next index
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_index_]);
|
pbo_index_ = (pbo_index_ + 1) % 2;
|
||||||
// copy pixels from PBO to texture object
|
pbo_next_index_ = (pbo_index_ + 1) % 2;
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
|
||||||
// bind the next PBO to write pixels
|
// bind PBO to read pixels
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]);
|
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
|
#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
|
|
||||||
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()
|
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);
|
||||||
|
|
||||||
// validate frame format
|
// indicate to update loop that buffer is new
|
||||||
if( GST_VIDEO_INFO_IS_RGB(&(frame_[write_index_].vframe).info) && GST_VIDEO_INFO_N_PLANES(&(frame_[write_index_].vframe).info) == 1)
|
frame_[write_index_].is_new = true;
|
||||||
{
|
|
||||||
// set presentation time stamp
|
|
||||||
frame_[write_index_].position = buf->pts;
|
|
||||||
|
|
||||||
}
|
// set presentation time stamp
|
||||||
// full but invalid frame : will be deleted next iteration
|
frame_[write_index_].position = buf->pts;
|
||||||
// (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 {
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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)",
|
||||||
|
|||||||
Reference in New Issue
Block a user