339 lines
11 KiB
C++
Executable File
339 lines
11 KiB
C++
Executable File
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
|
|
*
|
|
* This application is open source and may be redistributed and/or modified
|
|
* freely and without restriction, both in commericial and non commericial applications,
|
|
* as long as this copyright notice is maintained.
|
|
*
|
|
* This application is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
/* -*-c++-*-
|
|
*
|
|
* Copyright (C) 2009 Cedric Pinson
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Authors:
|
|
* Cedric Pinson <cedric.pinson@plopbyte.net>
|
|
*
|
|
*/
|
|
|
|
#include <osg/RenderInfo>
|
|
#include <osg/GL>
|
|
#include <osg/GLExtensions>
|
|
|
|
#include <sstream>
|
|
|
|
#include "windowcapturecallback.h"
|
|
|
|
#include <gst/gst.h>
|
|
#include <gst/app/gstappsrc.h>
|
|
#include <dtEntity/property.h>
|
|
#include <dtEntity/core.h>
|
|
#include <dtEntity/windowinterface.h>
|
|
|
|
namespace VCtrl
|
|
{
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void WindowCaptureCallback::operator()(osg::RenderInfo &renderInfo) const
|
|
{
|
|
glReadBuffer(_readBuffer);
|
|
|
|
osg::GraphicsContext *gc = renderInfo.getState()->getGraphicsContext();
|
|
osg::ref_ptr<ContextData> cd = getContextData(gc);
|
|
cd->read();
|
|
dtEntity::GetWindowInterface()->RequestRedraw(_contextId);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
WindowCaptureCallback::WindowCaptureCallback(GLenum readBuffer, unsigned int contextid, const std::string &path, int w, int h, int framerate, int quality):
|
|
_readBuffer(readBuffer),
|
|
_contextId(contextid),
|
|
_filename(path),
|
|
_w(w),
|
|
_h(h),
|
|
_framerate(framerate),
|
|
_quality(quality)
|
|
{
|
|
dtEntity::GetWindowInterface()->RequestRedraw(_contextId);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
WindowCaptureCallback::ContextData *WindowCaptureCallback::createContextData(osg::GraphicsContext *gc) const
|
|
{
|
|
return new ContextData(gc, _readBuffer, _filename, _w, _h, _framerate, _quality);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
WindowCaptureCallback::ContextData *WindowCaptureCallback::getContextData(osg::GraphicsContext *gc) const
|
|
{
|
|
osg::ref_ptr<ContextData> &data = _contextDataMap[gc];
|
|
if (!data) data = createContextData(gc);
|
|
|
|
return data.get();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
static gboolean bus_message(GstBus *, GstMessage *message, WindowCaptureCallback *)
|
|
{
|
|
switch (GST_MESSAGE_TYPE(message))
|
|
{
|
|
case GST_MESSAGE_WARNING:
|
|
{
|
|
GError *err = nullptr;
|
|
gchar *dbg_info = nullptr;
|
|
|
|
gst_message_parse_warning(message, &err, &dbg_info);
|
|
g_printerr("WARN from element %s: %s\n",
|
|
GST_OBJECT_NAME(message->src), err->message);
|
|
g_printerr("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
|
|
g_error_free(err);
|
|
g_free(dbg_info);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_ERROR:
|
|
{
|
|
GError *err = nullptr;
|
|
gchar *dbg_info = nullptr;
|
|
|
|
gst_message_parse_error(message, &err, &dbg_info);
|
|
g_printerr("ERROR from element %s: %s\n",
|
|
GST_OBJECT_NAME(message->src), err->message);
|
|
g_printerr("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
|
|
g_error_free(err);
|
|
g_free(dbg_info);
|
|
break;
|
|
}
|
|
#ifdef LTS_DEBUG
|
|
case GST_MESSAGE_PROGRESS:
|
|
{
|
|
GstProgressType type;
|
|
gchar *code, *text;
|
|
bool in_progress = false;
|
|
|
|
gst_message_parse_progress (message, &type, &code, &text);
|
|
|
|
switch (type) {
|
|
case GST_PROGRESS_TYPE_START:
|
|
case GST_PROGRESS_TYPE_CONTINUE:
|
|
in_progress = true;
|
|
break;
|
|
case GST_PROGRESS_TYPE_COMPLETE:
|
|
case GST_PROGRESS_TYPE_CANCELED:
|
|
case GST_PROGRESS_TYPE_ERROR:
|
|
in_progress = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
g_printerr("Progress: (%s) %s, is in progress: %d\n", code, text, in_progress);
|
|
g_free (code);
|
|
g_free (text);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_QOS:
|
|
{
|
|
gboolean live;
|
|
guint64 running_time, stream_time, time_stamp, duration;
|
|
gst_message_parse_qos(message, &live, &running_time, &stream_time, &time_stamp, &duration);
|
|
g_printerr("QOS message: %d, %ld, %ld, %ld, %ld\n", live, running_time, stream_time, time_stamp, duration);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_EOS:
|
|
g_printerr("End-Of-Stream received!n");
|
|
break;
|
|
case GST_MESSAGE_STATE_CHANGED:
|
|
{
|
|
GstState oldState, newState, pendingState;
|
|
gst_message_parse_state_changed(message, &oldState, &newState, &pendingState);
|
|
g_printerr("State change from %d to %d (pending: %d)\n", oldState, newState, pendingState);
|
|
break;
|
|
}
|
|
default:
|
|
g_printerr("got message %s\n",
|
|
gst_message_type_get_name(GST_MESSAGE_TYPE(message)));
|
|
break;
|
|
#else
|
|
default:
|
|
g_print("got message %s\n",
|
|
gst_message_type_get_name(GST_MESSAGE_TYPE(message)));
|
|
break;
|
|
#endif
|
|
}
|
|
return GST_BUS_PASS;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
WindowCaptureCallback::ContextData::ContextData(osg::GraphicsContext *gc, GLenum readBuffer,
|
|
const std::string &path, int w, int h, int framerate, int quality):
|
|
_gc(gc),
|
|
_readBuffer(readBuffer),
|
|
_fileName(path),
|
|
_width(0),
|
|
_height(0)
|
|
{
|
|
(void)quality;
|
|
|
|
_startTick = _previousFrameTick = osg::Timer::instance()->tick();
|
|
_pboBuffer.push_back(0);
|
|
|
|
getSize(gc, _width, _height);
|
|
|
|
osg::notify(osg::INFO) << this << " Window size " << _width << ", " << _height << std::endl;
|
|
|
|
GstBus *bus;
|
|
|
|
/* Initialize GStreamer */
|
|
gst_init(nullptr, nullptr);
|
|
|
|
std::ostringstream gstpipeline;
|
|
gstpipeline << "appsrc name=\"inputsrc\" ";
|
|
gstpipeline << "! videorate ";
|
|
gstpipeline << "! videoscale ";
|
|
gstpipeline << "! videoconvert ";
|
|
gstpipeline << "! video/x-raw, width=" << w << ", height=" << h << ", framerate=" << framerate << "/1 ";
|
|
gstpipeline << "! videoflip method=5 ";
|
|
|
|
//#define SHOW_DIRECTLY
|
|
|
|
#ifdef SHOW_DIRECTLY
|
|
gstpipeline << "! xvimagesink ";
|
|
#else
|
|
gstpipeline << " ! vp9enc end-usage=cbr";
|
|
gstpipeline << " ! webmmux ";
|
|
gstpipeline << " ! filesink location=" << path;
|
|
#endif
|
|
// g_warning("pipeline: '%s'\n", gstpipeline.str().c_str());
|
|
|
|
/* Build the pipeline */
|
|
_pipeline = gst_parse_launch(gstpipeline.str().c_str(), nullptr);
|
|
|
|
_app_source = gst_bin_get_by_name((GstBin *) _pipeline, "inputsrc");
|
|
|
|
bus = gst_pipeline_get_bus(GST_PIPELINE(_pipeline));
|
|
g_assert(bus);
|
|
|
|
gst_bus_add_watch(bus, (GstBusFunc) bus_message, this);
|
|
gst_object_unref(bus);
|
|
|
|
GstCaps *video_caps = gst_caps_new_simple("video/x-raw",
|
|
"format", G_TYPE_STRING, "RGBA",
|
|
"width", G_TYPE_INT, _width,
|
|
"height", G_TYPE_INT, _height,
|
|
"framerate", GST_TYPE_FRACTION, framerate, 1,
|
|
nullptr);
|
|
gst_app_src_set_caps(GST_APP_SRC(_app_source), video_caps);
|
|
g_object_set(GST_APP_SRC(_app_source), "format", GST_FORMAT_TIME, nullptr);
|
|
gst_caps_unref(video_caps);
|
|
|
|
// make gstreamer more verbose:
|
|
g_signal_connect( _pipeline, "deep-notify", G_CALLBACK(gst_object_default_deep_notify ), nullptr );
|
|
|
|
/* Start playing */
|
|
gst_element_set_state(_pipeline, GST_STATE_PLAYING);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void WindowCaptureCallback::ContextData::getSize(osg::GraphicsContext *gc, int &width, int &height)
|
|
{
|
|
if (gc->getTraits())
|
|
{
|
|
width = gc->getTraits()->width;
|
|
height = gc->getTraits()->height;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void WindowCaptureCallback::ContextData::read()
|
|
{
|
|
osg::GLExtensions *ext = osg::GLExtensions::Get(_gc->getState()->getContextID(), true);
|
|
int width = 0, height = 0;
|
|
getSize(_gc, width, height);
|
|
if (width != _width || _height != height)
|
|
{
|
|
osg::notify(osg::INFO) << this << " Window resized " << width << ", " << height << std::endl;
|
|
_width = width;
|
|
_height = height;
|
|
}
|
|
|
|
unsigned int size = _width * _height * 4;
|
|
|
|
GLuint &pbo = _pboBuffer[0];
|
|
|
|
if (pbo == 0)
|
|
{
|
|
ext->glGenBuffers(1, &pbo);
|
|
ext->glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo);
|
|
ext->glBufferData(GL_PIXEL_PACK_BUFFER_ARB, size, nullptr, GL_STREAM_READ);
|
|
|
|
osg::notify(osg::INFO) << this << " Generating pbo " << pbo << std::endl;
|
|
}
|
|
else
|
|
ext->glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo);
|
|
|
|
osg::Timer_t tick_now = osg::Timer::instance()->tick();
|
|
|
|
glReadPixels(0, 0, _width, _height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
|
|
|
GLubyte *src = (GLubyte *)ext->glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB,
|
|
GL_READ_ONLY_ARB);
|
|
if (src)
|
|
{
|
|
guint64 timestamp = (tick_now - _startTick) * 1000;
|
|
guint64 duration = (tick_now - _previousFrameTick) * 1000;
|
|
GstBuffer *buffer = gst_buffer_new_allocate(nullptr, size, nullptr);
|
|
GstMapInfo info;
|
|
gst_buffer_map(buffer, &info, GST_MAP_WRITE);
|
|
memcpy(info.data, src, size);
|
|
gst_buffer_unmap(buffer, &info);
|
|
GST_BUFFER_PTS(buffer) = timestamp;
|
|
GST_BUFFER_DTS(buffer) = timestamp;
|
|
GST_BUFFER_DURATION(buffer) = duration;
|
|
_previousFrameTick = tick_now;
|
|
|
|
GstFlowReturn ret;
|
|
g_signal_emit_by_name(_app_source, "push-buffer", buffer, &ret);
|
|
gst_buffer_unref(buffer);
|
|
|
|
if (ret != GST_FLOW_OK)
|
|
{
|
|
// some error, stop sending data
|
|
GST_DEBUG("some error");
|
|
}
|
|
|
|
ext->glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB);
|
|
}
|
|
|
|
ext->glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
WindowCaptureCallback::ContextData::~ContextData()
|
|
{
|
|
gboolean ret = gst_element_send_event(_pipeline, gst_event_new_eos());
|
|
if (ret)
|
|
{
|
|
GstMessageType types = GST_MESSAGE_EOS;
|
|
GstMessage *msg = gst_bus_timed_pop_filtered(GST_ELEMENT_BUS(_pipeline), GST_CLOCK_TIME_NONE, types);
|
|
gst_message_unref(msg);
|
|
}
|
|
gst_element_set_state(_pipeline, GST_STATE_NULL);
|
|
gst_object_unref(_pipeline);
|
|
}
|
|
|
|
}
|