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.

374 lines
10 KiB

/*
* export_pvn.c -- module for exporting PVN video streams
* (http://www.cse.yorku.ca/~jgryn/research/pvnspecs.html)
* Written by Andrew Church <achurch@achurch.org>
*
* This file is part of transcode, a video stream processing tool.
* transcode is free software, distributable under the terms of the GNU
* General Public License (version 2 or later). See the file COPYING
* for details.
*/
#include "transcode.h"
#include "libtc/libtc.h"
#include "libtc/optstr.h"
#include "libtc/tcmodule-plugin.h"
#include "libtcvideo/tcvideo.h"
#define MOD_NAME "export_pvn.so"
#define MOD_VERSION "v1.0 (2006-10-06)"
#define MOD_CAP "Writes PVN video files"
#define MOD_AUTHOR "Andrew Church"
#define MOD_FEATURES \
TC_MODULE_FEATURE_MULTIPLEX|TC_MODULE_FEATURE_VIDEO
#define MOD_FLAGS \
TC_MODULE_FLAG_RECONFIGURABLE
/*************************************************************************/
/* Local data structure: */
typedef struct {
int width, height; // Frame width and height (to catch changes)
int fd; // Output file descriptor
int framecount; // Number of frames written
off_t framecount_pos; // File position of frame count (for rewriting)
} PrivateData;
/*************************************************************************/
/*************************************************************************/
/* Module interface routines and data. */
/*************************************************************************/
/**
* pvn_init: Initialize this instance of the module. See tcmodule-data.h
* for function details.
*/
static int pvn_init(TCModuleInstance *self, uint32_t features)
{
PrivateData *pd;
TC_MODULE_SELF_CHECK(self, "init");
TC_MODULE_INIT_CHECK(self, MOD_FEATURES, features);
self->userdata = pd = tc_malloc(sizeof(PrivateData));
if (!pd) {
tc_log_error(MOD_NAME, "init: out of memory!");
return -1;
}
pd->fd = -1;
pd->framecount = 0;
pd->framecount_pos = 0;
if (verbose) {
tc_log_info(MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP);
}
return 0;
}
/*************************************************************************/
/**
* pvn_configure: Configure this instance of the module. See
* tcmodule-data.h for function details.
*/
static int pvn_configure(TCModuleInstance *self,
const char *options, vob_t *vob)
{
if (!self) {
return -1;
}
return 0;
}
/*************************************************************************/
/**
* pvn_inspect: Return the value of an option in this instance of the
* module. See tcmodule-data.h for function details.
*/
static int pvn_inspect(TCModuleInstance *self,
const char *param, const char **value)
{
static char buf[TC_BUF_MAX];
if (!self || !param)
return -1;
if (optstr_lookup(param, "help")) {
tc_snprintf(buf, sizeof(buf),
"Overview:\n"
" Writes a PVN video stream (format PV6a, 8-bit data).\n"
" A grayscale file (PV5a) is written instead if the -K\n"
" switch is given to transcode.\n"
" The RGB colorspace must be used (-V rgb24).\n"
"No options available.\n");
*value = buf;
}
return 0;
}
/*************************************************************************/
/**
* pvn_stop: Reset this instance of the module. See tcmodule-data.h for
* function details.
*/
static int pvn_stop(TCModuleInstance *self)
{
PrivateData *pd;
if (!self) {
return -1;
}
pd = self->userdata;
if (pd->fd != -1) {
if (pd->framecount > 0 && pd->framecount_pos > 0) {
/* Write out final frame count, if we can */
if (lseek(pd->fd, pd->framecount_pos, SEEK_SET) != (off_t)-1) {
char buf[11];
int len = tc_snprintf(buf, sizeof(buf), "%10d",pd->framecount);
if (len > 0)
tc_pwrite(pd->fd, buf, len);
}
}
close(pd->fd);
pd->fd = -1;
}
return 0;
}
/*************************************************************************/
/**
* pvn_fini: Clean up after this instance of the module. See
* tcmodule-data.h for function details.
*/
static int pvn_fini(TCModuleInstance *self)
{
if (!self) {
return -1;
}
pvn_stop(self);
tc_free(self->userdata);
self->userdata = NULL;
return 0;
}
/*************************************************************************/
/**
* pvn_multiplex: Multiplex a frame of data. See tcmodule-data.h for
* function details.
*/
static int pvn_multiplex(TCModuleInstance *self,
vframe_list_t *vframe, aframe_list_t *aframe)
{
PrivateData *pd;
if (!self) {
tc_log_error(MOD_NAME, "multiplex: self == NULL!");
return -1;
}
pd = self->userdata;
if (pd->fd == -1) {
tc_log_error(MOD_NAME, "multiplex: no file opened!");
return -1;
}
if (vframe->v_width != pd->width || vframe->v_height != pd->height) {
tc_log_error(MOD_NAME, "Video frame size changed in midstream!");
return -1;
}
if (vframe->v_codec != CODEC_RGB) {
tc_log_error(MOD_NAME, "Invalid codec for video frame!");
return -1;
}
if (vframe->video_len != pd->width * pd->height * 3
&& vframe->video_len != pd->width * pd->height // for grayscale
) {
tc_log_error(MOD_NAME, "Invalid size for video frame!");
return -1;
}
if (tc_pwrite(pd->fd, vframe->video_buf, vframe->video_len)
!= vframe->video_len
) {
tc_log_error(MOD_NAME, "Error writing frame %d to output file: %s",
pd->framecount, strerror(errno));
return -1;
}
pd->framecount++;
return vframe->video_len;
}
/*************************************************************************/
static const TCCodecID pvn_codecs_in[] = { TC_CODEC_RGB, TC_CODEC_ERROR };
static const TCCodecID pvn_codecs_out[] = { TC_CODEC_ERROR };
static const TCFormatID pvn_formats_in[] = { TC_FORMAT_ERROR };
static const TCFormatID pvn_formats_out[] = { TC_FORMAT_PVN, TC_CODEC_ERROR };
static const TCModuleInfo pvn_info = {
.features = MOD_FEATURES,
.flags = MOD_FLAGS,
.name = MOD_NAME,
.version = MOD_VERSION,
.description = MOD_CAP,
.codecs_in = pvn_codecs_in,
.codecs_out = pvn_codecs_out,
.formats_in = pvn_formats_in,
.formats_out = pvn_formats_out
};
static const TCModuleClass pvn_class = {
.info = &pvn_info,
.init = pvn_init,
.fini = pvn_fini,
.configure = pvn_configure,
.stop = pvn_stop,
.inspect = pvn_inspect,
.multiplex = pvn_multiplex,
};
extern const TCModuleClass *tc_plugin_setup(void)
{
return &pvn_class;
}
/*************************************************************************/
/*************************************************************************/
/* Old-fashioned module interface. */
static TCModuleInstance mod;
static int verbose_flag;
static int capability_flag = TC_CAP_RGB;
#define MOD_PRE pvn
#define MOD_CODEC "(video) PVN"
#include "export_def.h"
MOD_init {return 0;}
MOD_stop {return 0;}
/*************************************************************************/
MOD_open
{
PrivateData *pd = NULL;
char buf[1000];
int len;
if (param->flag != TC_VIDEO)
return -1;
if (pvn_init(&mod, TC_MODULE_FEATURE_MULTIPLEX|TC_MODULE_FEATURE_VIDEO) < 0)
return -1;
pd = mod.userdata;
pd->width = vob->ex_v_width;
pd->height = vob->ex_v_height;
/* FIXME: stdout should be handled in a more standard fashion */
if (strcmp(vob->video_out_file, "-") == 0) { // allow /dev/stdout too?
pd->fd = 1;
} else {
pd->fd = open(vob->video_out_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (pd->fd < 0) {
tc_log_error(MOD_NAME, "Unable to open %s: %s",
vob->video_out_file, strerror(errno));
goto fail;
}
}
len = tc_snprintf(buf, sizeof(buf), "PV%da\r\n%d %d\r\n",
tc_get_vob()->decolor ? 5 : 6,
pd->width, pd->height);
if (len < 0)
goto fail;
if (tc_pwrite(pd->fd, buf, len) != len) {
tc_log_error(MOD_NAME, "Unable to write header to %s: %s",
vob->video_out_file, strerror(errno));
goto fail;
}
pd->framecount_pos = lseek(pd->fd, 0, SEEK_CUR); // failure okay
len = tc_snprintf(buf, sizeof(buf), "%10d\r\n8\r\n%lf\r\n",
0, (double)vob->ex_fps);
if (len < 0)
goto fail;
if (tc_pwrite(pd->fd, buf, len) != len) {
tc_log_error(MOD_NAME, "Unable to write header to %s: %s",
vob->video_out_file, strerror(errno));
goto fail;
}
return 0;
fail:
pvn_fini(&mod);
return -1;
}
/*************************************************************************/
MOD_close
{
if (param->flag != TC_VIDEO)
return -1;
pvn_fini(&mod);
return 0;
}
/*************************************************************************/
MOD_encode
{
vframe_list_t vframe;
if (param->flag != TC_VIDEO)
return -1;
vframe.v_width = tc_get_vob()->ex_v_width;
vframe.v_height = tc_get_vob()->ex_v_height;
vframe.v_codec = tc_get_vob()->ex_v_codec;
vframe.video_buf = param->buffer;
vframe.video_len = param->size;
if (!vframe.v_codec)
vframe.v_codec = CODEC_RGB; // assume it's correct
if (tc_get_vob()->decolor) {
// Assume the data is already decolored and just take every third byte
int i;
vframe.video_len /= 3;
for (i = 0; i < vframe.video_len; i++)
vframe.video_buf[i] = vframe.video_buf[i*3];
}
if (pvn_multiplex(&mod, &vframe, NULL) < 0)
return -1;
return 0;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/