/*
 * 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:
 */
