/* -*-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 * */ #include #include #include #include #include "windowcapturecallback.h" #include #include #include #include #include namespace VCtrl { //////////////////////////////////////////////////////////////////////////////// void WindowCaptureCallback::operator()(osg::RenderInfo &renderInfo) const { glReadBuffer(_readBuffer); osg::GraphicsContext *gc = renderInfo.getState()->getGraphicsContext(); osg::ref_ptr 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 &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); } }