You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

466 lines
12 KiB

/*
* demultiplex_x11.c -- extract full-screen images from an X11 connection.
* (C) 2006-2010 Francesco Romani <fromani at gmail dot com>
*
* This file is part of transcode, a video stream processing tool.
*
* transcode 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.
*
* transcode 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, see <http://www.gnu.org/licenses/>.
*/
#include "transcode.h"
#include "libtc/optstr.h"
#include "libtc/tcmodule-plugin.h"
#include "libtc/tctimer.h"
#include "libtc/optstr.h"
#include "x11source.h"
/*%*
*%* DESCRIPTION
*%* This module captures video frames from X window system using libX11.
*%*
*%* BUILD-DEPENDS
*%* libcx11-6 >= 1.0.0
*%*
*%* DEPENDS
*%* libcx11-6 >= 1.0.0
*%*
*%* PROCESSING
*%* import/demuxer
*%*
*%* MEDIA
*%* video
*%*
*%* #INPUT
*%*
*%* OUTPUT
*%* YUV420P, YUV422P, RGB24*
*%*
*%* OPTION
*%* skew_limit (integer)
*%* maximum frame A/V skew (ms) before correction attempt
*%*/
/*
* TODO (approx. priority order)
* - Improve framerate emulation.
* It isn't easy without encoder support, and we will not have
* any smarter encoder at least until 1.2.0.
* - Grab cursor.
* - Make faster where feasible.
*/
#define DEBUG 1
#define LEGACY 1
#ifdef LEGACY
# define MOD_NAME "import_x11.so"
#else
# define MOD_NAME "demultiplex_x11.so"
#endif
#define MOD_VERSION "v0.1.0 (2007-07-21)"
#define MOD_CAP "fetch full-screen frames from an X11 connection"
#define MOD_FEATURES \
TC_MODULE_FEATURE_DEMULTIPLEX|TC_MODULE_FEATURE_VIDEO
#define MOD_FLAGS \
TC_MODULE_FLAG_RECONFIGURABLE
/*************************************************************************/
static const char tc_x11_help[] = ""
"Overview:\n"
" This module acts as a bridge from transcode an a X11 server.\n"
" It grabs screenshots at fixed rate from X11 connection, allowing\n"
" to record screencast and so on.\n"
"Options:\n"
" skew_limit=N tune maximum frame skew (ms) before correction\n"
" help produce module overview and options explanations\n";
#define SKEW_LIM_DEFAULT 0
#define SKEW_LIM_MIN 0
#define SKEW_LIM_MAX 5
static const int frame_delay_divs[] = {
/* div skew_lim */
1, /* 0 (disabled) */
2, /* 1 (weakest) */
3,
5,
10,
20 /* 5 (strongest) */
};
typedef struct tcx11privatedata_ TCX11PrivateData;
struct tcx11privatedata_ {
TCX11Source src;
TCTimer timer;
uint64_t frame_delay;
/* how much (ms) we must sleep to properly emulate frame rate? */
uint32_t expired; /* counter for execessively delayed frames */
uint64_t reftime; /* reference time (ms) for skew computation */
int64_t skew; /* take in account excess of retard (ms) */
int64_t skew_limit; /* how much (ms) skew we can tolerate? */
};
/*************************************************************************/
/* helpers */
static void tdebug(const TCX11PrivateData *priv, const char *str)
{
#ifdef DEBUG
uint64_t now = tc_gettime();
tc_log_info(MOD_NAME, "%-18s %lu", str, (unsigned long)(now - priv->reftime));
#endif
return;
}
/*************************************************************************/
static int tc_x11_init(TCModuleInstance *self, uint32_t features)
{
TCX11PrivateData *priv = NULL;
TC_MODULE_SELF_CHECK(self, "init");
TC_MODULE_INIT_CHECK(self, MOD_FEATURES, features);
if (verbose) {
tc_log_info(MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP);
}
priv = tc_malloc(sizeof(TCX11PrivateData));
if (priv == NULL) {
return TC_ERROR;
}
self->userdata = priv;
return TC_OK;
}
static int tc_x11_fini(TCModuleInstance *self)
{
TC_MODULE_SELF_CHECK(self, "fini");
tc_free(self->userdata);
self->userdata = NULL;
return TC_OK;
}
static int tc_x11_configure(TCModuleInstance *self,
const char *options, vob_t *vob)
{
TCX11PrivateData *priv = NULL;
int ret = 0, skew_lim = SKEW_LIM_DEFAULT;
TC_MODULE_SELF_CHECK(self, "configure");
priv = self->userdata;
if (options != NULL) {
optstr_get(options, "skew_limit", "%i", &skew_lim);
if (skew_lim < SKEW_LIM_MIN || skew_lim > SKEW_LIM_MAX) {
tc_log_warn(MOD_NAME, "skew limit value out of range,"
" reset to defaults [%i]",
SKEW_LIM_DEFAULT);
}
}
priv->skew = 0;
priv->reftime = 0;
priv->expired = 0;
priv->frame_delay = (uint64_t)(1000000.0 / vob->fps); /* microseconds */
priv->skew_limit = priv->frame_delay / frame_delay_divs[skew_lim];
if (verbose >= TC_DEBUG) {
tc_log_info(MOD_NAME, "frame delay: %lu ms",
(unsigned long)priv->frame_delay);
tc_log_info(MOD_NAME, "skew limit: %li ms",
(long)priv->skew_limit);
}
ret = tc_timer_init_soft(&priv->timer, 0);
if (ret != 0) {
tc_log_error(MOD_NAME, "configure: can't initialize timer");
return TC_ERROR;
}
/* nothing to do here, yet */
ret = tc_x11source_is_display_name(vob->video_in_file);
if (ret == TC_FALSE) {
tc_log_error(MOD_NAME, "configure: given source doesn't look like"
" a DISPLAY specifier");
return TC_ERROR;
}
ret = tc_x11source_open(&priv->src, vob->video_in_file,
TC_X11_MODE_BEST, vob->im_v_codec);
if (ret != 0) {
tc_log_error(MOD_NAME, "configure: failed to open X11 connection"
" to '%s'", vob->video_in_file);
return TC_ERROR;
}
return TC_OK;
}
static int tc_x11_inspect(TCModuleInstance *self,
const char *param, const char **value)
{
TC_MODULE_SELF_CHECK(self, "inspect");
if (optstr_lookup(param, "help")) {
*value = tc_x11_help;
}
return TC_OK;
}
static int tc_x11_stop(TCModuleInstance *self)
{
TCX11PrivateData *priv = NULL;
int ret = 0;
TC_MODULE_SELF_CHECK(self, "stop");
priv = self->userdata;
ret = tc_x11source_close(&priv->src);
if (ret != 0) {
tc_log_error(MOD_NAME, "stop: failed to close X11 connection");
return TC_ERROR;
}
ret = tc_timer_fini(&priv->timer);
if (ret != 0) {
tc_log_error(MOD_NAME, "stop: failed to stop timer");
return TC_ERROR;
}
if (verbose >= TC_DEBUG) {
tc_log_info(MOD_NAME, "expired frames count: %lu",
(unsigned long)priv->expired);
}
return TC_OK;
}
static int tc_x11_demultiplex(TCModuleInstance *self,
vframe_list_t *vframe, aframe_list_t *aframe)
{
TCX11PrivateData *priv = NULL;
uint64_t now = 0;
int ret = 0;
TC_MODULE_SELF_CHECK(self, "demultiplex");
priv = self->userdata;
priv->reftime = tc_gettime();
tdebug(priv, "begin demultiplex");
if (aframe != NULL) {
aframe->audio_len = 0; /* no audio from here */
}
if (vframe != NULL) {
tdebug(priv, " begin acquire");
ret = tc_x11source_acquire(&priv->src, vframe->video_buf,
vframe->video_size);
tdebug(priv, " end acquire");
if (ret > 0) {
int64_t naptime = 0;
uint64_t now = 0;
vframe->attributes |= TC_FRAME_IS_KEYFRAME;
vframe->video_len = ret;
now = tc_gettime();
naptime = (priv->frame_delay - (now - priv->reftime));
if (priv->skew >= priv->skew_limit) {
tc_log_info(MOD_NAME, " skew correction (naptime was %lu)",
(unsigned long)naptime);
int64_t t = naptime;
naptime -= priv->skew;
priv->skew = TC_MAX(0, priv->skew - t);
}
if (naptime <= 0) {
/* don't sleep at all if delay is already excessive */
tc_log_info(MOD_NAME, "%-18s", " NO SLEEP!");
priv->expired++;
} else {
tc_log_info(MOD_NAME, "%-18s %lu", " sleep time",
(unsigned long)(naptime));
tc_timer_sleep(&priv->timer, (uint64_t)naptime);
}
}
}
now = tc_gettime();
now -= priv->reftime;
priv->skew += now - priv->frame_delay;
tdebug(priv, "end multiplex");
tc_log_info(MOD_NAME, "%-18s %li", "detected skew", (long)(priv->skew));
return (ret > 0) ?ret :-1;
}
/*************************************************************************/
static const TCCodecID tc_x11_codecs_in[] = { TC_CODEC_ERROR };
/* a multiplexor is at the end of pipeline */
static const TCCodecID tc_x11_codecs_out[] = {
TC_CODEC_RGB, TC_CODEC_YUV420P, TC_CODEC_YUV422P, TC_CODEC_ERROR
};
static const TCFormatID tc_x11_formats_in[] = { TC_FORMAT_X11, TC_FORMAT_ERROR };
static const TCFormatID tc_x11_formats_out[] = { TC_FORMAT_ERROR };
static const TCModuleInfo tc_x11_info = {
.features = MOD_FEATURES,
.flags = MOD_FLAGS,
.name = MOD_NAME,
.version = MOD_VERSION,
.description = MOD_CAP,
.codecs_in = tc_x11_codecs_in,
.codecs_out = tc_x11_codecs_out,
.formats_in = tc_x11_formats_in,
.formats_out = tc_x11_formats_out
};
static const TCModuleClass tc_x11_class = {
TC_MODULE_CLASS_HEAD(tc_x11),
.init = tc_x11_init,
.fini = tc_x11_fini,
.configure = tc_x11_configure,
.stop = tc_x11_stop,
.inspect = tc_x11_inspect,
.demultiplex = tc_x11_demultiplex,
};
TC_MODULE_ENTRY_POINT(tc_x11)
/*************************************************************************/
/*************************************************************************/
/* Old-fashioned module interface. */
static TCModuleInstance mod_video;
static int verbose_flag;
static int capability_flag = TC_CAP_YUV|TC_CAP_RGB|TC_CAP_YUV422|TC_CAP_VID;
#define MOD_PRE x11
#define MOD_CODEC "(video) X11"
#include "import_def.h"
/*************************************************************************/
#define RETURN_IF_FAILED(ret) do { \
if ((ret) != TC_OK) { \
return ret; \
} \
} while (0)
#define COMMON_CHECK(param) do { \
if ((param)->flag != TC_VIDEO) { \
return TC_ERROR; \
} \
} while (0)
MOD_open
{
int ret;
COMMON_CHECK(param);
ret = tc_x11_init(&mod_video, TC_MODULE_FEATURE_DEMULTIPLEX);
RETURN_IF_FAILED(ret);
ret = tc_x11_configure(&mod_video, "", vob);
RETURN_IF_FAILED(ret);
return TC_OK;
}
MOD_decode
{
vframe_list_t vframe;
int ret = 0;
COMMON_CHECK(param);
vframe.attributes = 0;
vframe.video_buf = param->buffer;
vframe.video_size = param->size;
ret = tc_x11_demultiplex(&mod_video, &vframe, NULL);
if (ret <= 0) {
/* well, frames from X11 never "ends", really :) */
return TC_ERROR;
}
param->size = ret;
param->attributes = vframe.attributes;
return TC_OK;
}
MOD_close
{
int ret;
COMMON_CHECK(param);
ret = tc_x11_stop(&mod_video);
RETURN_IF_FAILED(ret);
ret = tc_x11_fini(&mod_video);
RETURN_IF_FAILED(ret);
return TC_OK;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/