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.
1040 lines
32 KiB
1040 lines
32 KiB
/*
|
|
* import_pv3.c -- module for importing audio/video data in the EarthSoft
|
|
* PV3 codec
|
|
* 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.
|
|
*/
|
|
|
|
#define MOD_NAME "import_pv3.so"
|
|
#define MOD_VERSION "v1.1 (2006-06-02)"
|
|
#define MOD_CAP "Imports Earth Soft PV3 codec audio/video streams"
|
|
#define MOD_AUTHOR "Andrew Church"
|
|
|
|
/*%*
|
|
*%* DESCRIPTION
|
|
*%* This module provides access to Earth Soft PV3 audio/video streams using
|
|
*%* win32 binary codecs and an internal win32 emulation layer (NO wine needed).
|
|
*%*
|
|
*%* #BUILD-DEPENDS
|
|
*%*
|
|
*%* DEPENDS
|
|
*%* PV3 win32 dlls.
|
|
*%*
|
|
*%* PROCESSING
|
|
*%* import/demuxer
|
|
*%*
|
|
*%* MEDIA
|
|
*%* video, audio
|
|
*%*
|
|
*%* #INPUT
|
|
*%*
|
|
*%* OUTPUT
|
|
*%* YUV420P, YUV422P, PCM
|
|
*%*
|
|
*%* OPTION
|
|
*%* dllpath (string)
|
|
*%* set path/filename to load dv.dll from
|
|
*%*/
|
|
|
|
#define MOD_FEATURES \
|
|
TC_MODULE_FEATURE_DEMULTIPLEX|TC_MODULE_FEATURE_DECODE|TC_MODULE_FEATURE_VIDEO
|
|
#define MOD_FLAGS \
|
|
TC_MODULE_FLAG_RECONFIGURABLE
|
|
|
|
#include "transcode.h"
|
|
#include "libtc/libtc.h"
|
|
#include "libtc/optstr.h"
|
|
#include "libtc/tcmodule-plugin.h"
|
|
#include "libtcvideo/tcvideo.h"
|
|
#include "aclib/ac.h"
|
|
#include "w32dll.h"
|
|
|
|
#ifndef PROBE_ONLY // FIXME: temp hack, all the way down to probe_pv3()
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Structures used by dv.dll: */
|
|
|
|
/* Main codec handle and function table */
|
|
struct pv3_codec_handle {
|
|
struct {
|
|
int (*init)(int a, int b);
|
|
int (*fini)(void);
|
|
int (*get_video_handle)(void);
|
|
int (*get_audio_handle)(void);
|
|
} *funcs;
|
|
};
|
|
|
|
/* Input video frame parameters */
|
|
struct pv3_input_vframe_params {
|
|
uint8_t w8; // width / 8
|
|
uint8_t h8; // height / 8
|
|
uint16_t unknown1;
|
|
uint32_t unknown2;
|
|
int progressive; // Version 2 only
|
|
};
|
|
|
|
/* Output video frame parameters */
|
|
struct pv3_output_vframe_params {
|
|
uint32_t stride;
|
|
void *outbuf;
|
|
};
|
|
|
|
/* video_functable.decode() parameter block */
|
|
struct pv3_video_decode_params {
|
|
uint32_t dataset; // Selects data set 0 or 1 (see pv3_decode())
|
|
void *workbuf; // Work buffer of at least 0x424? bytes
|
|
struct pv3_input_vframe_params *in_params;
|
|
const void **frameptr; // Pointer to input frame
|
|
struct pv3_output_vframe_params *out_params;
|
|
};
|
|
|
|
/* Video codec handle and function table. Note that particular versions of
|
|
* dv.dll only handle a single file format; it is necessary to use the
|
|
* proper version of dv.dll for the file to be transcoded. */
|
|
struct pv3_video_handle {
|
|
union {
|
|
struct {
|
|
void *func0;
|
|
void *func1;
|
|
void *func2;
|
|
void *func3;
|
|
void *func4;
|
|
void (*decode)(struct pv3_video_decode_params *params);
|
|
} *funcs_v1;
|
|
struct {
|
|
void *func0;
|
|
void *func1;
|
|
void *func2;
|
|
void *func3;
|
|
void *func4;
|
|
void *func5;
|
|
void *func6;
|
|
/* `unknown' here points to at least 0x500 bytes of memory */
|
|
void (*set_quantizers)(void *unknown, const uint16_t *quantizers);
|
|
void (*decode)(struct pv3_video_decode_params *params);
|
|
} *funcs_v2;
|
|
} u;
|
|
};
|
|
|
|
/* Raw audio data parameters */
|
|
struct pv3_audio_params {
|
|
uint32_t rate; // Sampling rate
|
|
uint32_t pad04;
|
|
uint64_t frame_index; // Index of first audio frame (file start = 0)
|
|
uint32_t frame_count; // Number of audio frames for this video frame
|
|
uint32_t pad14;
|
|
void *audiobuf;
|
|
uint32_t pad1C;
|
|
};
|
|
|
|
/* Encoded audio data parameters */
|
|
struct pv3_audio_encoded_params {
|
|
uint32_t unknown; // ???
|
|
void *frame; // Encoded frame
|
|
};
|
|
|
|
/* Audio codec handle and function table */
|
|
struct pv3_audio_handle {
|
|
struct {
|
|
void (*encode)(struct pv3_audio_params *in,
|
|
struct pv3_audio_encoded_params *out);
|
|
void (*decode)(struct pv3_audio_encoded_params *in,
|
|
struct pv3_audio_params *out);
|
|
} *funcs;
|
|
};
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Maximum encoded frame size (as in PV3's AviUtl plugin) */
|
|
#define MAX_FRAME_SIZE 0x400000
|
|
|
|
/* Private data used by this module. */
|
|
typedef struct {
|
|
char *dll_path; // Pathname (incl. file) for dv.dll
|
|
W32DLLHandle codec_dll; // DLL and codec handles
|
|
struct pv3_codec_handle *codec_handle;
|
|
struct pv3_video_handle *video_handle;
|
|
struct pv3_audio_handle *audio_handle;
|
|
uint32_t saved_fs; // Saved %fs value (for DLL)
|
|
|
|
TCVHandle tcvhandle; // tcvideo handle for YUY2->planar
|
|
|
|
int fd; // File descriptor to read from
|
|
int pv3_version; // PV3 file version (1 or 2)
|
|
int width, height; // Width and height for v2 files
|
|
int progressive; // Progressive flag for v2, 0=interlace
|
|
uint16_t qtable[128]; // Quantizer tables for v2 files
|
|
int framenum; // Frame number of loaded frame
|
|
uint8_t framebuf[MAX_FRAME_SIZE]; // Buffer for loaded frame
|
|
} PrivateData;
|
|
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
/* PV3 codec DLL (dv.dll) interface. */
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* pv3_call: Call the given function with the given handle and up to two
|
|
* additional arguments (of type intptr_t), using the calling sequence
|
|
* expected by the DLL. Used only by the DLL interface functions.
|
|
*
|
|
* Parameters:
|
|
* fs: Value to set %fs to.
|
|
* handle: Handle for the function.
|
|
* func: Function pointer to call.
|
|
* Return value:
|
|
* Return value of the function, as an intptr_t.
|
|
* Preconditions:
|
|
* func != NULL
|
|
*/
|
|
|
|
static intptr_t pv3_call(uint32_t fs, const void *handle, const void *func,...)
|
|
{
|
|
va_list args;
|
|
intptr_t arg1, arg2, retval;
|
|
uint32_t spsave = 0;
|
|
|
|
va_start(args, func);
|
|
arg1 = va_arg(args, intptr_t);
|
|
arg2 = va_arg(args, intptr_t);
|
|
va_end(args);
|
|
asm("mov %%eax, %%fs" : : "a" (fs));
|
|
asm("mov %%esp, %5;" // Since the DLL uses the __stdcall calling
|
|
"push %3;" // convention, it will pop the "correct" number
|
|
"push %2;" // of arguments off the stack when it returns.
|
|
"call *%1;" // Explicitly save and restore the stack pointer
|
|
"mov %5, %%esp;" // to make sure we get the right value back.
|
|
: "=a" (retval)
|
|
: "r" (func), "r" (arg1), "r" (arg2), "c" (handle), "m" (spsave));
|
|
return retval;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* pv3_load_dll: Load and initialize the PV3 codec DLL (dv.dll).
|
|
*
|
|
* Parameters:
|
|
* pd: PrivateData structure into which to store the DLL and codec
|
|
* handles.
|
|
* path: Path to dv.dll (including filename); if NULL or empty,
|
|
* "dv.dll" in the current directory is used.
|
|
* Return value:
|
|
* Nonzero on success, zero on error.
|
|
* Preconditions:
|
|
* pd != NULL
|
|
*/
|
|
|
|
static void pv3_unload_dll(PrivateData *pd); /* forward declaration */
|
|
|
|
static int pv3_load_dll(PrivateData *pd)
|
|
{
|
|
const char *path;
|
|
void *(*entry)(void);
|
|
|
|
pd->codec_dll = 0;
|
|
pd->codec_handle = NULL;
|
|
pd->video_handle = NULL;
|
|
pd->audio_handle = NULL;
|
|
|
|
path = pd->dll_path;
|
|
if (!path || !*path)
|
|
path = "dv.dll";
|
|
|
|
if (!(pd->codec_dll = w32dll_load(path, 1))) {
|
|
tc_log_error(MOD_NAME, "Cannot load %s: %s", path,
|
|
errno==ENOEXEC ? "Not a valid Win32 DLL file" :
|
|
errno==ETXTBSY ? "DLL initialization failed" :
|
|
strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
/* Save %fs and restore before each call, just in case */
|
|
asm("mov %%fs,%%eax" : "=a" (pd->saved_fs));
|
|
|
|
entry = w32dll_lookup_by_name(pd->codec_dll, "_");
|
|
if (!entry) {
|
|
tc_log_error(MOD_NAME, "Cannot find dv.dll entry point");
|
|
pv3_unload_dll(pd);
|
|
return 0;
|
|
}
|
|
|
|
pd->codec_handle = (*entry)();
|
|
if (!pd->codec_handle) {
|
|
tc_log_error(MOD_NAME, "Unable to initialize dv.dll");
|
|
pv3_unload_dll(pd);
|
|
return 0;
|
|
}
|
|
|
|
pv3_call(pd->saved_fs, pd->codec_handle, pd->codec_handle->funcs->init,
|
|
4, pd->pv3_version==1 ? 2 : 122); /* magic numbers */
|
|
pd->video_handle = (void *)pv3_call(pd->saved_fs, pd->codec_handle,
|
|
pd->codec_handle->funcs->get_video_handle);
|
|
pd->audio_handle = (void *)pv3_call(pd->saved_fs, pd->codec_handle,
|
|
pd->codec_handle->funcs->get_audio_handle);
|
|
if (!pd->video_handle || !pd->audio_handle) {
|
|
tc_log_error(MOD_NAME, "Unable to retrieve codec handles");
|
|
pv3_unload_dll(pd);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* pv3_decode_frame: Decode a frame.
|
|
*
|
|
* Parameters:
|
|
* pd: PrivateData structure.
|
|
* in_frame: Input (encoded) frame.
|
|
* out_video: Output video frame buffer (YUY2).
|
|
* out_audio: Output audio frame buffer.
|
|
* Return value:
|
|
* Nonzero on success, zero on failure.
|
|
* Preconditions:
|
|
* pd != NULL
|
|
* in_frame != NULL
|
|
* Notes:
|
|
* The maximum theoretical video size is 2040x2040, for a total of
|
|
* just under 8MB of video data. Audio will always be under 2048
|
|
* samples (audio frames), or 8192 bytes.
|
|
*/
|
|
|
|
static int pv3_decode_frame(PrivateData *pd, uint8_t *in_frame,
|
|
void *out_video, void *out_audio)
|
|
{
|
|
if (!pd->codec_dll) {
|
|
if (!pv3_load_dll(pd))
|
|
return 0;
|
|
}
|
|
|
|
if (out_video) {
|
|
struct pv3_input_vframe_params in_vparams;
|
|
struct pv3_output_vframe_params out_vparams;
|
|
struct pv3_video_decode_params vparams;
|
|
char work_mem[0x800];
|
|
int i;
|
|
|
|
if (!pd->video_handle)
|
|
return 0;
|
|
memset(&in_vparams, 0, sizeof(in_vparams));
|
|
if (pd->pv3_version == 1) {
|
|
in_vparams.w8 = ((uint8_t *)in_frame)[4];
|
|
in_vparams.h8 = ((uint8_t *)in_frame)[5];
|
|
} else {
|
|
in_vparams.w8 = pd->width/8;
|
|
in_vparams.h8 = pd->height/8;
|
|
in_vparams.progressive = pd->progressive;
|
|
}
|
|
memset(&out_vparams, 0, sizeof(out_vparams));
|
|
out_vparams.stride = in_vparams.w8 * 8 * 2;
|
|
out_vparams.outbuf = out_video;
|
|
memset(&vparams, 0, sizeof(vparams));
|
|
|
|
/* Set up quantizers for version 2 */
|
|
if (pd->pv3_version == 2) {
|
|
pv3_call(pd->saved_fs, pd->video_handle,
|
|
pd->video_handle->u.funcs_v2->set_quantizers,
|
|
work_mem, pd->qtable);
|
|
}
|
|
|
|
/* Process each set of data */
|
|
vparams.workbuf = work_mem;
|
|
vparams.in_params = &in_vparams;
|
|
vparams.frameptr = (const void **)&in_frame;
|
|
vparams.out_params = &out_vparams;
|
|
for (i = 0; i < (pd->pv3_version==2 && !pd->progressive ? 4 : 2); i++){
|
|
if (pd->pv3_version == 1) {
|
|
vparams.dataset = i;
|
|
} else {
|
|
vparams.dataset = 1<<i;
|
|
}
|
|
if (pv3_call(pd->saved_fs, pd->video_handle,
|
|
pd->pv3_version == 1
|
|
? pd->video_handle->u.funcs_v1->decode
|
|
: pd->video_handle->u.funcs_v2->decode,
|
|
&vparams) < 0
|
|
) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (out_audio) {
|
|
int nsamples, i;
|
|
uint16_t *dest = (uint16_t *)out_audio;
|
|
|
|
if (pd->pv3_version == 1) {
|
|
nsamples = in_frame[24]<<8 | in_frame[25];
|
|
} else { // PV3 version 2
|
|
nsamples = in_frame[6]<<8 | in_frame[7];
|
|
}
|
|
if (nsamples > 0x800) {
|
|
tc_log_warn(MOD_NAME, "Too many audio samples (%d) in frame %d,"
|
|
" truncating to %d", nsamples, pd->framenum, 0x800);
|
|
nsamples = 0x800;
|
|
}
|
|
for (i = 0; i < nsamples*2; i++) {
|
|
dest[i] = in_frame[0x200+i*2]<<8 | in_frame[0x201+i*2];
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* pv3_unload_dll: Shut down and unload the PV3 codec DLL. Does nothing
|
|
* if the codec has not been loaded.
|
|
*
|
|
* Parameters:
|
|
* pd: PrivateData structure in which handles are stored.
|
|
* Return value:
|
|
* None.
|
|
* Preconditions:
|
|
* pd != NULL
|
|
*/
|
|
|
|
static void pv3_unload_dll(PrivateData *pd)
|
|
{
|
|
if (pd->codec_dll) {
|
|
pd->video_handle = NULL;
|
|
pd->audio_handle = NULL;
|
|
if (pd->codec_handle) {
|
|
pv3_call(pd->saved_fs, pd->codec_handle,
|
|
pd->codec_handle->funcs->fini);
|
|
}
|
|
pd->codec_handle = NULL;
|
|
w32dll_unload(pd->codec_dll);
|
|
pd->codec_dll = 0;
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
/* Module interface routines and data. */
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* pv3_init: Initialize this instance of the module. See tcmodule-data.h
|
|
* for function details.
|
|
*/
|
|
|
|
static int pv3_init(TCModuleInstance *self, uint32_t features)
|
|
{
|
|
PrivateData *pd;
|
|
|
|
TC_MODULE_SELF_CHECK(self, "init");
|
|
if (features == ~(uint32_t)0) { // i.e. if called from old-style code
|
|
self->features = MOD_FEATURES;
|
|
} else {
|
|
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 TC_ERROR;
|
|
}
|
|
pd->dll_path = NULL;
|
|
pd->codec_dll = 0;
|
|
pd->codec_handle = NULL;
|
|
pd->video_handle = NULL;
|
|
pd->audio_handle = NULL;
|
|
pd->fd = -1;
|
|
pd->framenum = -1;
|
|
|
|
pd->tcvhandle = tcv_init();
|
|
if (!pd->tcvhandle) {
|
|
tc_log_error(MOD_NAME, "init: tcv_init() failed");
|
|
free(pd);
|
|
self->userdata = NULL;
|
|
return TC_ERROR;
|
|
}
|
|
|
|
if (verbose) {
|
|
tc_log_info(MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP);
|
|
}
|
|
return TC_OK;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* pv3_fini: Clean up after this instance of the module. See
|
|
* tcmodule-data.h for function details.
|
|
*/
|
|
|
|
static int pv3_fini(TCModuleInstance *self)
|
|
{
|
|
PrivateData *pd;
|
|
|
|
TC_MODULE_SELF_CHECK(self, "fini");
|
|
|
|
pd = self->userdata;
|
|
|
|
pd->framenum = -1;
|
|
if (pd->fd != -1) {
|
|
close(pd->fd);
|
|
pd->fd = -1;
|
|
}
|
|
if (pd->tcvhandle) {
|
|
tcv_free(pd->tcvhandle);
|
|
pd->tcvhandle = 0;
|
|
}
|
|
if (pd->codec_dll) {
|
|
pv3_unload_dll(pd);
|
|
}
|
|
if (pd->dll_path) {
|
|
free(pd->dll_path);
|
|
pd->dll_path = NULL;
|
|
}
|
|
|
|
tc_free(self->userdata);
|
|
self->userdata = NULL;
|
|
return TC_OK;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* pv3_configure: Configure this instance of the module. See
|
|
* tcmodule-data.h for function details.
|
|
*/
|
|
|
|
static int pv3_configure(TCModuleInstance *self,
|
|
const char *options, vob_t *vob)
|
|
{
|
|
PrivateData *pd = NULL;
|
|
|
|
TC_MODULE_SELF_CHECK(self, "configure");
|
|
|
|
pd = self->userdata;
|
|
|
|
free(pd->dll_path);
|
|
pd->dll_path = NULL;
|
|
if (options) {
|
|
char buf[1024];
|
|
*buf = 0;
|
|
optstr_get(options, "dllpath", "%1024s", buf);
|
|
if (*buf)
|
|
pd->dll_path = tc_strdup(buf);
|
|
}
|
|
return TC_OK;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* pv3_stop: Reset this instance of the module. See tcmodule-data.h for
|
|
* function details.
|
|
*/
|
|
|
|
static int pv3_stop(TCModuleInstance *self)
|
|
{
|
|
PrivateData *pd = NULL;
|
|
|
|
TC_MODULE_SELF_CHECK(self, "stop");
|
|
|
|
pd = self->userdata;
|
|
|
|
pd->framenum = -1;
|
|
if (pd->fd != -1) {
|
|
close(pd->fd);
|
|
pd->fd = -1;
|
|
}
|
|
return TC_OK;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* pv3_inspect: Return the value of an option in this instance of the
|
|
* module. See tcmodule-data.h for function details.
|
|
*/
|
|
|
|
static int pv3_inspect(TCModuleInstance *self,
|
|
const char *param, const char **value)
|
|
{
|
|
PrivateData *pd;
|
|
static char buf[TC_BUF_MAX];
|
|
|
|
TC_MODULE_SELF_CHECK(self, "inspect");
|
|
TC_MODULE_SELF_CHECK(param, "inspect");
|
|
TC_MODULE_SELF_CHECK(value, "inspect");
|
|
|
|
pd = self->userdata;
|
|
|
|
if (optstr_lookup(param, "help")) {
|
|
tc_snprintf(buf, sizeof(buf),
|
|
"Overview:\n"
|
|
" Decodes streams recorded by the Earth Soft PV3 recorder.\n"
|
|
"Options available:\n"
|
|
" dllpath=path Set path/filename to load dv.dll from\n");
|
|
*value = buf;
|
|
}
|
|
if (optstr_lookup(param, "dllpath")) {
|
|
tc_snprintf(buf, sizeof(buf), "%s", pd->dll_path ? pd->dll_path : "");
|
|
*value = buf;
|
|
}
|
|
return TC_IMPORT_OK;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* pv3_demultiplex: Demultiplex a frame of data. See tcmodule-data.h for
|
|
* function details.
|
|
*
|
|
* Notes:
|
|
* For PV3, we pass the entire frame to the codec DLL, so this just
|
|
* copies the input frame to the video frame buffer. The audio is
|
|
* decoded here and passed on as PCM.
|
|
*/
|
|
|
|
static int pv3_demultiplex(TCModuleInstance *self,
|
|
vframe_list_t *vframe, aframe_list_t *aframe)
|
|
{
|
|
PrivateData *pd;
|
|
int framesize;
|
|
off_t fpos;
|
|
|
|
TC_MODULE_SELF_CHECK(self, "demultiplex");
|
|
|
|
pd = self->userdata;
|
|
if (pd->fd < 0) {
|
|
tc_log_error(MOD_NAME, "demultiplex: no file opened!");
|
|
return TC_ERROR;
|
|
}
|
|
fpos = lseek(pd->fd, 0, SEEK_CUR); // for error messages
|
|
|
|
/* Read frame header (but if this is a version 1 file and we're on the
|
|
* first frame, the header will already have been read in) */
|
|
if (!(pd->pv3_version == 1 && pd->framenum == -1)
|
|
&& (tc_pread(pd->fd, pd->framebuf, 512) != 512)
|
|
) {
|
|
if (verbose & TC_DEBUG)
|
|
tc_log_msg(MOD_NAME, "EOF reached");
|
|
return TC_ERROR;
|
|
}
|
|
if (pd->pv3_version == 1 && memcmp(pd->framebuf, "PV3\1", 4) != 0) {
|
|
tc_log_warn(MOD_NAME, "Not a valid PV3-1 frame at frame %d (ofs=%llX)",
|
|
pd->framenum+1, fpos);
|
|
return TC_ERROR;
|
|
}
|
|
|
|
/* Find total frame length and read */
|
|
framesize = 512; // header
|
|
if (pd->pv3_version == 1) {
|
|
framesize += (pd->framebuf[24]<<8 | pd->framebuf[25]) * 4; // audio
|
|
framesize = (framesize+0xFFF) & -0x1000; // align
|
|
/* Seems to reserve 8192-512 bytes for audio no matter what */
|
|
if (framesize < 8192)
|
|
framesize = 8192;
|
|
framesize += pd->framebuf[28]<<24 | pd->framebuf[29]<<16 // video 0
|
|
| pd->framebuf[30]<< 8 | pd->framebuf[31];
|
|
framesize = (framesize+0x1F) & -0x20; // align
|
|
framesize += pd->framebuf[32]<<24 | pd->framebuf[33]<<16 // video 1
|
|
| pd->framebuf[34]<< 8 | pd->framebuf[35];
|
|
framesize = (framesize+0xFFF) & -0x1000; // align
|
|
} else { // PV3 version 2
|
|
framesize += (pd->framebuf[6]<<8 | pd->framebuf[7]) * 4; // audio
|
|
framesize = (framesize+0xFFF) & -0x1000; // align
|
|
framesize += pd->framebuf[384]<<24 | pd->framebuf[385]<<16 // video 0
|
|
| pd->framebuf[386]<< 8 | pd->framebuf[387];
|
|
framesize = (framesize+0x1F) & -0x20; // align
|
|
framesize += pd->framebuf[388]<<24 | pd->framebuf[389]<<16 // video 1
|
|
| pd->framebuf[390]<< 8 | pd->framebuf[391];
|
|
framesize = (framesize+0x1F) & -0x20; // align
|
|
framesize += pd->framebuf[392]<<24 | pd->framebuf[393]<<16 // video 2
|
|
| pd->framebuf[394]<< 8 | pd->framebuf[395];
|
|
framesize = (framesize+0x1F) & -0x20; // align
|
|
framesize += pd->framebuf[396]<<24 | pd->framebuf[397]<<16 // video 3
|
|
| pd->framebuf[398]<< 8 | pd->framebuf[399];
|
|
framesize = (framesize+0xFFF) & -0x1000; // align
|
|
}
|
|
if (tc_pread(pd->fd, pd->framebuf+512, framesize-512) != framesize-512) {
|
|
tc_log_warn(MOD_NAME, "Truncated frame at frame %d (ofs=%llX)",
|
|
pd->framenum+1, fpos);
|
|
return TC_ERROR;
|
|
}
|
|
pd->framenum++;
|
|
|
|
if (vframe) {
|
|
ac_memcpy(vframe->video_buf, pd->framebuf, framesize);
|
|
vframe->video_size = framesize;
|
|
vframe->v_codec = TC_CODEC_PV3;
|
|
}
|
|
|
|
if (aframe) {
|
|
/* The full frame won't fit in an audio buffer, so just decode it
|
|
* here and pass it on as PCM. */
|
|
if (pd->pv3_version == 1) {
|
|
aframe->a_rate = pd->framebuf[12] << 24
|
|
| pd->framebuf[13] << 16
|
|
| pd->framebuf[14] << 8
|
|
| pd->framebuf[15];
|
|
aframe->audio_size = (pd->framebuf[24]<<8 | pd->framebuf[25]) * 4;
|
|
} else { // PV3 version 2
|
|
aframe->a_rate = pd->framebuf[ 8] << 24
|
|
| pd->framebuf[ 9] << 16
|
|
| pd->framebuf[10] << 8
|
|
| pd->framebuf[11];
|
|
aframe->audio_size = (pd->framebuf[6]<<8 | pd->framebuf[7]) * 4;
|
|
}
|
|
aframe->a_bits = 16;
|
|
aframe->a_chan = 2;
|
|
if (!pv3_decode_frame(pd, pd->framebuf, NULL, aframe->audio_buf)) {
|
|
tc_log_warn(MOD_NAME,
|
|
"demultiplex: decode audio failed, inserting silence");
|
|
memset(aframe->audio_buf, 0, aframe->audio_size);
|
|
}
|
|
aframe->a_codec = TC_CODEC_PCM;
|
|
}
|
|
|
|
return framesize;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* pv3_decode_video: Decode a frame of data. See tcmodule-data.h for
|
|
* function details.
|
|
*/
|
|
|
|
static int pv3_decode_video(TCModuleInstance *self,
|
|
vframe_list_t *inframe, vframe_list_t *outframe)
|
|
{
|
|
vob_t *vob = tc_get_vob(); // for output codec--will this be in outframe?
|
|
PrivateData *pd;
|
|
static uint8_t yuy2_frame[2040*2040*2]; // max PV3 frame size
|
|
|
|
TC_MODULE_SELF_CHECK(self, "decode_video");
|
|
TC_MODULE_SELF_CHECK(inframe, "decode_video");
|
|
TC_MODULE_SELF_CHECK(outframe, "decode_video");
|
|
|
|
pd = self->userdata;
|
|
|
|
if (!pv3_decode_frame(pd, inframe->video_buf, yuy2_frame, NULL))
|
|
return TC_ERROR;
|
|
|
|
// FIXME: do we set these here?
|
|
if (pd->pv3_version == 1) {
|
|
outframe->v_width = pd->framebuf[4] * 8;
|
|
outframe->v_height = pd->framebuf[5] * 8;
|
|
} else {
|
|
outframe->v_width = pd->width;
|
|
outframe->v_height = pd->height;
|
|
}
|
|
|
|
if (!tcv_convert(pd->tcvhandle, yuy2_frame, outframe->video_buf,
|
|
outframe->v_width, outframe->v_height, IMG_YUY2,
|
|
vob->im_v_codec==CODEC_YUV422 ? IMG_YUV422P : IMG_YUV420P)
|
|
) {
|
|
tc_log_warn(MOD_NAME, "Video format conversion failed");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
outframe->video_size = outframe->v_width * outframe->v_height;
|
|
if (vob->im_v_codec == CODEC_YUV422) {
|
|
outframe->video_size +=
|
|
(outframe->v_width/2) * outframe->v_height * 2;
|
|
} else {
|
|
outframe->video_size +=
|
|
(outframe->v_width/2) * (outframe->v_height/2) * 2;
|
|
}
|
|
|
|
return TC_OK;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static const TCCodecID pv3_codecs_in[] = { TC_CODEC_PV3, TC_CODEC_ERROR };
|
|
static const TCCodecID pv3_codecs_out[] = { TC_CODEC_YUV420P, TC_CODEC_YUV422P,
|
|
TC_CODEC_ERROR };
|
|
static const TCFormatID pv3_formats_in[] = { TC_FORMAT_PV3, TC_FORMAT_ERROR };
|
|
static const TCFormatID pv3_formats_out[] = { TC_FORMAT_ERROR };
|
|
|
|
static const TCModuleInfo pv3_info = {
|
|
.features = MOD_FEATURES,
|
|
.flags = MOD_FLAGS,
|
|
.name = MOD_NAME,
|
|
.version = MOD_VERSION,
|
|
.description = MOD_CAP,
|
|
.codecs_in = pv3_codecs_in,
|
|
.codecs_out = pv3_codecs_out,
|
|
.formats_in = pv3_formats_in,
|
|
.formats_out = pv3_formats_out
|
|
};
|
|
|
|
static const TCModuleClass pv3_class = {
|
|
TC_MODULE_CLASS_HEAD(pv3),
|
|
|
|
.init = pv3_init,
|
|
.fini = pv3_fini,
|
|
.configure = pv3_configure,
|
|
.stop = pv3_stop,
|
|
.inspect = pv3_inspect,
|
|
|
|
.decode_video = pv3_decode_video,
|
|
.demultiplex = pv3_demultiplex,
|
|
};
|
|
|
|
TC_MODULE_ENTRY_POINT(pv3)
|
|
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
/* Old-fashioned module interface. */
|
|
|
|
static TCModuleInstance mod_video, mod_audio;
|
|
|
|
static int verbose_flag;
|
|
static int capability_flag = TC_CAP_YUV | TC_CAP_YUV422 | TC_CAP_PCM;
|
|
#define MOD_PRE pv3
|
|
#define MOD_CODEC "(video) PV3 | (audio) PCM"
|
|
#include "import_def.h"
|
|
#include "magic.h"
|
|
|
|
/*************************************************************************/
|
|
|
|
MOD_open
|
|
{
|
|
TCModuleInstance *mod = NULL;
|
|
PrivateData *pd = NULL;
|
|
const char *fname = NULL;
|
|
uint8_t buf[512];
|
|
|
|
if (param->flag == TC_VIDEO) {
|
|
mod = &mod_video;
|
|
fname = vob->video_in_file;
|
|
} else if (param->flag == TC_AUDIO) {
|
|
mod = &mod_audio;
|
|
fname = vob->audio_in_file;
|
|
} else {
|
|
return TC_ERROR;
|
|
}
|
|
|
|
if (pv3_init(mod, ~(uint32_t)0) < 0)
|
|
return TC_ERROR;
|
|
pd = mod->userdata;
|
|
if (vob->im_v_string)
|
|
pd->dll_path = tc_strdup(vob->im_v_string);
|
|
|
|
param->fd = NULL; /* we handle the reading ourselves */
|
|
pd->fd = open(fname, O_RDONLY);
|
|
if (pd->fd < 0) {
|
|
tc_log_error(MOD_NAME, "Unable to open %s: %s", fname,
|
|
strerror(errno));
|
|
free(pd->framebuf);
|
|
pv3_fini(mod);
|
|
return TC_ERROR;
|
|
}
|
|
if (tc_pread(pd->fd, buf, 512) != 512) {
|
|
tc_log_error(MOD_NAME, "%s is too short", fname);
|
|
free(pd->framebuf);
|
|
pv3_fini(mod);
|
|
return TC_ERROR;
|
|
}
|
|
if (memcmp(buf, "PV3", 3) != 0) {
|
|
tc_log_warn(MOD_NAME, "%s is not a valid PV3 file", fname);
|
|
free(pd->framebuf);
|
|
pv3_fini(mod);
|
|
return TC_ERROR;
|
|
}
|
|
if (buf[3] != 1 && buf[3] != 2) {
|
|
tc_log_warn(MOD_NAME, "Invalid PV3 version %d in %s", buf[3], fname);
|
|
free(pd->framebuf);
|
|
pv3_fini(mod);
|
|
return TC_ERROR;
|
|
}
|
|
pd->pv3_version = buf[3];
|
|
if (pd->pv3_version == 1) {
|
|
/* For version 1 files, copy the 512-byte header we just read
|
|
* into the frame buffer, so that the demultiplexer has access
|
|
* to it without requiring a seek on the input file */
|
|
memcpy(pd->framebuf, buf, 512);
|
|
} else { /* A version 2 file */
|
|
/* Copy file header data into private data structure */
|
|
int i;
|
|
pd->width = buf[4] * 16;
|
|
pd->height = buf[5] * 8;
|
|
pd->progressive = buf[6] & 1;
|
|
for (i = 0; i < 128; i++) {
|
|
pd->qtable[i] = buf[256+i*2]<<8 | buf[257+i*2];
|
|
}
|
|
/* Skip to the first frame header */
|
|
{
|
|
uint8_t dummy[16384-512];
|
|
if (tc_pread(pd->fd, dummy, sizeof(dummy)) != sizeof(dummy)) {
|
|
tc_log_error(MOD_NAME, "Unexpected EOF reading %s header",
|
|
fname);
|
|
free(pd->framebuf);
|
|
pv3_fini(mod);
|
|
return TC_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TC_OK;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
MOD_close
|
|
{
|
|
TCModuleInstance *mod = NULL;
|
|
|
|
if (param->flag == TC_VIDEO) {
|
|
mod = &mod_video;
|
|
} else if (param->flag == TC_AUDIO) {
|
|
mod = &mod_audio;
|
|
} else {
|
|
return TC_ERROR;
|
|
}
|
|
|
|
pv3_fini(mod);
|
|
return TC_OK;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
MOD_decode
|
|
{
|
|
TCModuleInstance *mod = NULL;
|
|
PrivateData *pd = NULL;
|
|
|
|
if (param->flag == TC_VIDEO) {
|
|
mod = &mod_video;
|
|
} else if (param->flag == TC_AUDIO) {
|
|
mod = &mod_audio;
|
|
} else {
|
|
return TC_ERROR;
|
|
}
|
|
pd = mod->userdata;
|
|
|
|
if (pd->fd < 0) {
|
|
tc_log_error(MOD_NAME, "No file open in decode!");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
if (param->flag == TC_VIDEO) {
|
|
vframe_list_t vframe1, vframe2;
|
|
vframe1.video_buf = pd->framebuf;
|
|
vframe2.video_buf = param->buffer;
|
|
if (param->attributes & TC_FRAME_IS_OUT_OF_RANGE) {
|
|
if (pv3_demultiplex(mod, &vframe2, NULL) < 0)
|
|
return TC_ERROR;
|
|
} else {
|
|
if (pv3_demultiplex(mod, &vframe1, NULL) < 0)
|
|
return TC_ERROR;
|
|
if (pv3_decode_video(mod, &vframe1, &vframe2) < 0)
|
|
return TC_ERROR;
|
|
}
|
|
param->size = vframe2.video_size;
|
|
} else if (param->flag == TC_AUDIO) {
|
|
aframe_list_t aframe;
|
|
aframe.audio_buf = param->buffer;
|
|
if (pv3_demultiplex(mod, NULL, &aframe) < 0)
|
|
return TC_ERROR;
|
|
param->size = aframe.audio_size;
|
|
}
|
|
|
|
return TC_OK;
|
|
}
|
|
|
|
#endif // !PROBE_ONLY
|
|
|
|
/*************************************************************************/
|
|
|
|
#ifdef PROBE_ONLY
|
|
|
|
#include "tcinfo.h"
|
|
#include "tc.h"
|
|
#include "magic.h"
|
|
|
|
void probe_pv3(info_t *ipipe)
|
|
{
|
|
uint8_t buf[0x4200];
|
|
int aspect_w, aspect_h, interlaced;
|
|
|
|
if (tc_pread(ipipe->fd_in, buf, sizeof(buf)) != sizeof(buf)) {
|
|
tc_log_warn(MOD_NAME, "Premature end of input file");
|
|
ipipe->error = 1;
|
|
return;
|
|
}
|
|
/* Sanity check--this should be caught by the caller */
|
|
if (memcmp(buf, "PV3", 3) != 0) {
|
|
tc_log_warn(MOD_NAME, "Input is not PV3 video");
|
|
ipipe->error = 1;
|
|
return;
|
|
}
|
|
if (buf[3] != 1 && buf[3] != 2) { /* version number */
|
|
tc_log_warn(MOD_NAME, "Invalid PV3 version %d", buf[3]);
|
|
ipipe->error = 1;
|
|
return;
|
|
}
|
|
|
|
ipipe->probe_info->magic = TC_MAGIC_PV3;
|
|
ipipe->probe_info->codec = TC_CODEC_PV3;
|
|
|
|
if (buf[3] == 1) {
|
|
/* Version 1 file processing */
|
|
ipipe->probe_info->width = buf[4] * 8;
|
|
ipipe->probe_info->height = buf[5] * 8;
|
|
aspect_w = buf[6];
|
|
aspect_h = buf[7];
|
|
interlaced = !(buf[8] & 1);
|
|
ipipe->probe_info->track[0].samplerate =
|
|
buf[12]<<24 | buf[13]<<16 | buf[14]<<8 | buf[15];
|
|
} else {
|
|
/* Version 2 file processing */
|
|
ipipe->probe_info->width = buf[4] * 16;
|
|
ipipe->probe_info->height = buf[5] * 8;
|
|
aspect_w = buf[0x4100]<<8 | buf[0x4101];
|
|
aspect_h = buf[0x4102]<<8 | buf[0x4103];
|
|
interlaced = !(buf[6] & 1);
|
|
ipipe->probe_info->track[0].samplerate =
|
|
buf[0x4008]<<24 | buf[0x4009]<<16 | buf[0x400A]<<8 | buf[0x400B];
|
|
}
|
|
if (aspect_w == 4 && aspect_h == 3)
|
|
ipipe->probe_info->asr = 2;
|
|
else if (aspect_w == 16 && aspect_h == 9)
|
|
ipipe->probe_info->asr = 3;
|
|
ipipe->probe_info->fps = (interlaced ? 30 : 60)/1.001; // argh stupid NTSC
|
|
ipipe->probe_info->frc = (interlaced ? 4 : 7);
|
|
|
|
ipipe->probe_info->track[0].bits = 16;
|
|
ipipe->probe_info->track[0].chan = 2;
|
|
ipipe->probe_info->track[0].bitrate =
|
|
ipipe->probe_info->track[0].samplerate * 32 / 1000;
|
|
ipipe->probe_info->track[0].format = TC_CODEC_PCM;
|
|
ipipe->probe_info->num_tracks = 1;
|
|
}
|
|
|
|
#endif // PROBE_ONLY
|
|
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-file-style: "stroustrup"
|
|
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vim: expandtab shiftwidth=4:
|
|
*/
|