mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-05 23:40:02 +01:00
366 lines
9.5 KiB
C++
366 lines
9.5 KiB
C++
#include <algorithm>
|
|
|
|
// Desktop OpenGL function loader
|
|
#include <glad/glad.h>
|
|
|
|
// gstreamer
|
|
#include <gst/gstformat.h>
|
|
#include <gst/video/video.h>
|
|
|
|
#include "defines.h"
|
|
#include "Log.h"
|
|
#include "GstToolkit.h"
|
|
#include "BaseToolkit.h"
|
|
#include "FrameBuffer.h"
|
|
|
|
#include "FrameGrabber.h"
|
|
|
|
|
|
|
|
FrameGrabbing::FrameGrabbing(): pbo_index_(0), pbo_next_index_(0), size_(0), width_(0), height_(0), use_alpha_(0), caps_(NULL)
|
|
{
|
|
pbo_[0] = 0;
|
|
pbo_[1] = 0;
|
|
}
|
|
|
|
FrameGrabbing::~FrameGrabbing()
|
|
{
|
|
// stop and delete all frame grabbers
|
|
clearAll();
|
|
|
|
// cleanup
|
|
if (caps_)
|
|
gst_caps_unref (caps_);
|
|
// if (pbo_[0] > 0) // automatically deleted at shutdown
|
|
// glDeleteBuffers(2, pbo_);
|
|
}
|
|
|
|
void FrameGrabbing::add(FrameGrabber *rec)
|
|
{
|
|
if (rec != nullptr)
|
|
grabbers_.push_back(rec);
|
|
}
|
|
|
|
void FrameGrabbing::verify(FrameGrabber **rec)
|
|
{
|
|
if ( std::find(grabbers_.begin(), grabbers_.end(), *rec) == grabbers_.end() )
|
|
*rec = nullptr;
|
|
}
|
|
|
|
FrameGrabber *FrameGrabbing::front()
|
|
{
|
|
if (grabbers_.empty())
|
|
return nullptr;
|
|
|
|
return grabbers_.front();
|
|
}
|
|
|
|
struct fgId: public std::unary_function<FrameGrabber*, bool>
|
|
{
|
|
inline bool operator()(const FrameGrabber* elem) const {
|
|
return (elem && elem->id() == _id);
|
|
}
|
|
explicit fgId(uint64_t id) : _id(id) { }
|
|
private:
|
|
uint64_t _id;
|
|
};
|
|
|
|
FrameGrabber *FrameGrabbing::get(uint64_t id)
|
|
{
|
|
if (id > 0 && grabbers_.size() > 0 )
|
|
{
|
|
std::list<FrameGrabber *>::iterator iter = std::find_if(grabbers_.begin(), grabbers_.end(), fgId(id));
|
|
if (iter != grabbers_.end())
|
|
return (*iter);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void FrameGrabbing::stopAll()
|
|
{
|
|
std::list<FrameGrabber *>::iterator iter;
|
|
for (iter=grabbers_.begin(); iter != grabbers_.end(); ++iter )
|
|
(*iter)->stop();
|
|
}
|
|
|
|
void FrameGrabbing::clearAll()
|
|
{
|
|
std::list<FrameGrabber *>::iterator iter;
|
|
for (iter=grabbers_.begin(); iter != grabbers_.end(); )
|
|
{
|
|
FrameGrabber *rec = *iter;
|
|
rec->stop();
|
|
iter = grabbers_.erase(iter);
|
|
delete rec;
|
|
}
|
|
}
|
|
|
|
|
|
void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
|
|
{
|
|
if (frame_buffer == nullptr)
|
|
return;
|
|
|
|
// if different frame buffer from previous frame
|
|
if ( frame_buffer->width() != width_ ||
|
|
frame_buffer->height() != height_ ||
|
|
frame_buffer->use_alpha() != use_alpha_) {
|
|
|
|
// define stream properties
|
|
width_ = frame_buffer->width();
|
|
height_ = frame_buffer->height();
|
|
use_alpha_ = frame_buffer->use_alpha();
|
|
size_ = width_ * height_ * (use_alpha_ ? 4 : 3);
|
|
|
|
// first time initialization
|
|
if ( pbo_[0] == 0 )
|
|
glGenBuffers(2, pbo_);
|
|
|
|
// re-affect pixel buffer object
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[1]);
|
|
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[0]);
|
|
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
|
|
|
|
// reset indices
|
|
pbo_index_ = 0;
|
|
pbo_next_index_ = 0;
|
|
|
|
// new caps
|
|
if (caps_)
|
|
gst_caps_unref (caps_);
|
|
caps_ = gst_caps_new_simple ("video/x-raw",
|
|
"format", G_TYPE_STRING, use_alpha_ ? "RGBA" : "RGB",
|
|
"width", G_TYPE_INT, width_,
|
|
"height", G_TYPE_INT, height_,
|
|
"framerate", GST_TYPE_FRACTION, 30, 1,
|
|
NULL);
|
|
}
|
|
|
|
// fill a frame in buffer
|
|
if (!grabbers_.empty() && size_ > 0) {
|
|
|
|
GstBuffer *buffer = nullptr;
|
|
|
|
// set buffer target for writing in a new frame
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_index_]);
|
|
|
|
#ifdef USE_GLREADPIXEL
|
|
// get frame
|
|
frame_buffer->readPixels();
|
|
#else
|
|
glBindTexture(GL_TEXTURE_2D, frame_buffer->texture());
|
|
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
#endif
|
|
|
|
// update case ; alternating indices
|
|
if ( pbo_next_index_ != pbo_index_ ) {
|
|
|
|
// set buffer target for saving the frame
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_next_index_]);
|
|
|
|
// new buffer
|
|
buffer = gst_buffer_new_and_alloc (size_);
|
|
|
|
// map gst buffer into a memory WRITE target
|
|
GstMapInfo map;
|
|
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
|
|
|
|
// map PBO pixels into a memory READ pointer
|
|
unsigned char* ptr = (unsigned char*) glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
|
|
|
// transfer pixels from PBO memory to buffer memory
|
|
if (NULL != ptr)
|
|
memmove(map.data, ptr, size_);
|
|
|
|
// un-map
|
|
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
|
gst_buffer_unmap (buffer, &map);
|
|
}
|
|
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
|
|
// alternate indices
|
|
pbo_next_index_ = pbo_index_;
|
|
pbo_index_ = (pbo_index_ + 1) % 2;
|
|
|
|
// a frame was successfully grabbed
|
|
if (buffer != nullptr) {
|
|
|
|
// give the frame to all recorders
|
|
std::list<FrameGrabber *>::iterator iter = grabbers_.begin();
|
|
while (iter != grabbers_.end())
|
|
{
|
|
FrameGrabber *rec = *iter;
|
|
rec->addFrame(buffer, caps_, dt);
|
|
|
|
if (rec->finished()) {
|
|
iter = grabbers_.erase(iter);
|
|
delete rec;
|
|
}
|
|
else
|
|
++iter;
|
|
}
|
|
|
|
// unref / free the frame
|
|
gst_buffer_unref(buffer);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FrameGrabber::FrameGrabber(): finished_(false), active_(false), accept_buffer_(false),
|
|
pipeline_(nullptr), src_(nullptr), caps_(nullptr), timestamp_(0)
|
|
{
|
|
// unique id
|
|
id_ = BaseToolkit::uniqueId();
|
|
// configure fix parameter
|
|
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS
|
|
timeframe_ = 2 * frame_duration_;
|
|
}
|
|
|
|
FrameGrabber::~FrameGrabber()
|
|
{
|
|
if (src_ != nullptr)
|
|
gst_object_unref (src_);
|
|
if (caps_ != nullptr)
|
|
gst_caps_unref (caps_);
|
|
if (pipeline_ != nullptr) {
|
|
gst_element_set_state (pipeline_, GST_STATE_NULL);
|
|
gst_object_unref (pipeline_);
|
|
}
|
|
}
|
|
|
|
bool FrameGrabber::finished() const
|
|
{
|
|
return finished_;
|
|
}
|
|
|
|
bool FrameGrabber::busy() const
|
|
{
|
|
if (active_)
|
|
return accept_buffer_ ? true : false;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
uint64_t FrameGrabber::duration() const
|
|
{
|
|
return GST_TIME_AS_MSECONDS(timestamp_);
|
|
}
|
|
|
|
void FrameGrabber::stop ()
|
|
{
|
|
// send end of stream
|
|
gst_app_src_end_of_stream (src_);
|
|
|
|
// stop recording
|
|
active_ = false;
|
|
}
|
|
|
|
std::string FrameGrabber::info() const
|
|
{
|
|
if (active_)
|
|
return GstToolkit::time_to_string(timestamp_);
|
|
else
|
|
return "Inactive";
|
|
}
|
|
|
|
// appsrc needs data and we should start sending
|
|
void FrameGrabber::callback_need_data (GstAppSrc *, guint , gpointer p)
|
|
{
|
|
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
|
|
if (grabber)
|
|
grabber->accept_buffer_ = true;
|
|
}
|
|
|
|
// appsrc has enough data and we can stop sending
|
|
void FrameGrabber::callback_enough_data (GstAppSrc *, gpointer p)
|
|
{
|
|
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
|
|
if (grabber)
|
|
grabber->accept_buffer_ = false;
|
|
}
|
|
|
|
void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
|
|
{
|
|
// ignore
|
|
if (buffer == nullptr)
|
|
return;
|
|
|
|
// first time initialization
|
|
if (pipeline_ == nullptr)
|
|
init(caps);
|
|
|
|
// cancel if finished
|
|
if (finished_)
|
|
return;
|
|
|
|
// stop if an incompatilble frame buffer given
|
|
if ( !gst_caps_is_equal( caps_, caps ))
|
|
{
|
|
stop();
|
|
// Log::Warning("FrameGrabber interrupted: new session (%s)\nincompatible with recording (%s)", gst_caps_to_string(frame.caps), gst_caps_to_string(caps_));
|
|
Log::Warning("FrameGrabber interrupted because the resolution changed.");
|
|
}
|
|
|
|
// store a frame if recording is active
|
|
if (active_)
|
|
{
|
|
// calculate dt in ns
|
|
timeframe_ += gst_gdouble_to_guint64( dt * 1000000.f );
|
|
|
|
// if time is passed one frame duration (with 10% margin)
|
|
// and if the encoder accepts data
|
|
if ( timeframe_ > frame_duration_ - 3000000 && accept_buffer_) {
|
|
|
|
// set timing of buffer
|
|
buffer->pts = timestamp_;
|
|
buffer->duration = frame_duration_;
|
|
|
|
// increment ref counter to make sure the frame remains available
|
|
gst_buffer_ref(buffer);
|
|
|
|
// push
|
|
gst_app_src_push_buffer (src_, buffer);
|
|
// NB: buffer will be unrefed by the appsrc
|
|
|
|
accept_buffer_ = false;
|
|
|
|
// next timestamp
|
|
timestamp_ += frame_duration_;
|
|
|
|
// restart frame counter
|
|
timeframe_ = 0;
|
|
}
|
|
}
|
|
// did the recording terminate with sink receiving end-of-stream ?
|
|
else {
|
|
|
|
if (!finished_)
|
|
{
|
|
// Wait for EOS message
|
|
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
|
|
GstMessage *msg = gst_bus_poll(bus, GST_MESSAGE_EOS, GST_TIME_AS_USECONDS(1));
|
|
// received EOS
|
|
if (msg) {
|
|
// stop the pipeline
|
|
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
|
|
if (ret == GST_STATE_CHANGE_FAILURE)
|
|
Log::Warning("FrameGrabber Could not stop.");
|
|
|
|
finished_ = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (finished_)
|
|
terminate();
|
|
|
|
}
|