/*
 *  export_dvraw.c
 *
 *  Copyright (C) Thomas Östreich - June 2001
 *
 *  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, 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <libdv/dv.h>
#include "transcode.h"
#include "vid_aux.h"
#include "optstr.h"
#include "ioaux.h"

#define MOD_NAME    "export_dvraw.so"
#define MOD_VERSION "v0.4a3 (2006-02-01)"
#define MOD_CODEC   "(video) Digital Video | (audio) PCM"

static int verbose_flag=TC_QUIET;
static int capability_flag=TC_CAP_PCM|TC_CAP_RGB|TC_CAP_YUV|TC_CAP_VID|TC_CAP_YUV422;

#define MOD_PRE dvraw
#include "export_def.h"

static int fd;

static int16_t *audio_bufs[4];

static uint8_t *target = NULL;

static dv_encoder_t *encoder = NULL;
static uint8_t *pixels[3], *tmp_buf;

static int frame_size=0, format=0;
static int pass_through=0;

static int chans, rate;
static int dv_yuy2_mode=0;
static int dv_uyvy_mode=0;

static int media_type = 0;

/* Allocate a buffer aligned to the machine's page size, if known.  The
 * buffer must be freed with buffree() (not free()). */

void *_tc_bufalloc(const char *file, int line, size_t size)
{
#ifdef HAVE_GETPAGESIZE
    unsigned long pagesize = getpagesize();
    int8_t *base = malloc(size + sizeof(void *) + pagesize);
    int8_t *ptr = NULL;
    unsigned long offset = 0;

    if (base == NULL) {
        fprintf(stderr, "[%s:%d] tc_bufalloc(): can't allocate %lu bytes\n",
                        file, line, (unsigned long)size);
    } else {
        ptr = base + sizeof(void *);
        offset = (unsigned long)ptr % pagesize;

        if (offset)
            ptr += (pagesize - offset);
        ((void **)ptr)[-1] = base;  /* save the base pointer for freeing */
    }
    return ptr;
#else  /* !HAVE_GETPAGESIZE */
    return malloc(size);
#endif
}

/* Free a buffer allocated with tc_bufalloc(). */
void tc_buffree(void *ptr)
{
#ifdef HAVE_GETPAGESIZE
    if (ptr)
	free(((void **)ptr)[-1]);
#else
    free(ptr);
#endif
}

#define tc_bufalloc(size) \
    _tc_bufalloc(__FILE__, __LINE__, size)


/* ------------------------------------------------------------ 
 *
 * init codec
 *
 * ------------------------------------------------------------*/

MOD_init
{
    int i;
    if (param->flag == TC_VIDEO) {
    	int want_tmp_buf = 0;
        target = tc_bufalloc(TC_FRAME_DV_PAL);

        if (vob->dv_yuy2_mode) {
            tmp_buf = tc_bufalloc(PAL_W*PAL_H*2); //max frame
            dv_yuy2_mode = 1;
	        want_tmp_buf = 1;
        }
        if (vob->im_v_codec == CODEC_YUV422) {
            tmp_buf = tc_bufalloc(PAL_W*PAL_H*2); //max frame
            dv_uyvy_mode = 1;
	        want_tmp_buf = 1;
        }
        if (!target || (want_tmp_buf && !tmp_buf)) {
            fprintf(stderr, "[%s] can't allocate video buffers\n", MOD_NAME);
            return(TC_EXPORT_ERROR); 
        }
        
        encoder = dv_encoder_new(FALSE, FALSE, FALSE);
        if (!encoder) {
            fprintf(stderr, "[%s] can't allocate video encoder\n", MOD_NAME);
            return(TC_EXPORT_ERROR); 
        }
   
        media_type |= TC_VIDEO;
        return(TC_EXPORT_OK);
    }
  
    if (param->flag == TC_AUDIO) {
        if (!(media_type & TC_VIDEO)) {
            return(TC_EXPORT_ERROR);
        }
        // tmp audio buffer
        for (i = 0; i < 4; i++) {
            audio_bufs[i] = malloc(DV_AUDIO_MAX_SAMPLES * sizeof(int16_t));
            if (!(audio_bufs[i])) {
                fprintf(stderr, "[%s] can't allocate audio buffers\n", MOD_NAME);
                return(TC_EXPORT_ERROR); 
            }  
        }     
    
        media_type |= TC_AUDIO;
        return(TC_EXPORT_OK);
    }
  
    // invalid flag
    return(TC_EXPORT_ERROR); 
}

/* ------------------------------------------------------------ 
 *
 * open outputfile
 *
 * ------------------------------------------------------------*/

MOD_open
{
    if (param->flag == TC_VIDEO) {
        // video
        if ((fd = open(vob->video_out_file, O_RDWR|O_CREAT|O_TRUNC,
                       S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH))<0) {
            perror("open file");
            return(TC_EXPORT_ERROR);
        }     
    
        switch (vob->im_v_codec) {
          case CODEC_RGB:
            format = 0;
            if (verbose & TC_DEBUG) {
                fprintf(stderr, "[%s] raw format is RGB\n", MOD_NAME);
            }
            break;
          case CODEC_YUV:
            format = 1;
            if (verbose & TC_DEBUG) {
                fprintf(stderr, "[%s] raw format is YV12\n", MOD_NAME);
            }
            break;
          case CODEC_YUV422:
            format = 1;
            if (verbose & TC_DEBUG) {
                fprintf(stderr, "[%s] raw format is UYVY\n", MOD_NAME);
            }
            break;
          case CODEC_RAW: /* fallthrough */
          case CODEC_RAW_YUV:
            format = 1;
            pass_through = 1;
            break;
          default:
            fprintf(stderr, "[%s] codec not supported (0x%x)\n",
                            MOD_NAME, vob->im_v_codec);
            return(TC_EXPORT_ERROR); 
        }
    
        // for reading
        frame_size = (vob->ex_v_height==PAL_H) ? TC_FRAME_DV_PAL:TC_FRAME_DV_NTSC;

        if (verbose & TC_DEBUG) {
            fprintf(stderr, "[%s] encoding to %s DV\n",
                            MOD_NAME, (vob->ex_v_height==PAL_H) ? "PAL":"NTSC");
        }

        // Store aspect ratio - ex_asr uses the value 3 for 16x9
        encoder->is16x9 = ((vob->ex_asr<0) ? vob->im_asr:vob->ex_asr) == 3;
        encoder->isPAL = (vob->ex_v_height==PAL_H);
        encoder->vlc_encode_passes = 3;
        encoder->static_qno = 0;
        
        if (vob->ex_v_string != NULL) {
            if (optstr_get (vob->ex_v_string, "qno", "%d", &encoder->static_qno) == 1) {
                fprintf(stderr, "[%s] using quantisation: %d\n",
                                MOD_NAME, encoder->static_qno);
            }
        }   
        encoder->force_dct = DV_DCT_AUTO;

        return(TC_EXPORT_OK);
    }
  
    if (param->flag == TC_AUDIO) {
        int bytealignment;
        int bytespersecond;
        int bytesperframe;
  
        chans = vob->dm_chan;
        //re-sampling only with -J resample possible
        rate = vob->a_rate;

        bytealignment = (chans==2) ? 4:2;
        bytespersecond = rate * bytealignment;
        bytesperframe = bytespersecond/(encoder->isPAL ? 25 : 30);

        if (verbose & TC_DEBUG) {
            fprintf(stderr, "[%s] audio: CH=%d, f=%d, balign=%d, bps=%d, bpf=%d\n",
                            MOD_NAME, chans, rate, bytealignment, bytespersecond, bytesperframe);
        }

        return(TC_EXPORT_OK);
    }
    // invalid flag
    return(TC_EXPORT_ERROR); 
}   

/* ------------------------------------------------------------ 
 *
 * encode and export
 *
 * ------------------------------------------------------------*/

MOD_encode
{
    int i;
    time_t now = time(NULL);

    if (param->flag == TC_VIDEO) { 
        if (pass_through) {
            tc_memcpy(target, param->buffer, frame_size);
        } else {
            pixels[0] = param->buffer;
      
            if (encoder->isPAL) {
                pixels[2] = param->buffer + PAL_W*PAL_H;
                pixels[1] = param->buffer + (PAL_W*PAL_H*5)/4;
            } else {
                pixels[2] = param->buffer + NTSC_W*NTSC_H;
                pixels[1] = param->buffer + (NTSC_W*NTSC_H*5)/4;
            }
      
            if (dv_yuy2_mode && !dv_uyvy_mode) {   
                yv12toyuy2(pixels[0], pixels[1], pixels[2], tmp_buf,
                           PAL_W, (encoder->isPAL)? PAL_H : NTSC_H);
                pixels[0]=tmp_buf;
            }

            if (dv_uyvy_mode) {
                uyvytoyuy2(pixels[0], tmp_buf,
                           PAL_W, (encoder->isPAL)? PAL_H : NTSC_H);
                pixels[0]=tmp_buf;
            }
      
            dv_encode_full_frame(encoder, pixels, (format)?e_dv_color_yuv:e_dv_color_rgb, target);
      
        }//no pass-through
#ifdef LIBDV_099
        encoder->samples_this_frame = param->size;
#endif
        dv_encode_metadata(target, encoder->isPAL, encoder->is16x9, &now, 0);
        dv_encode_timecode(target, encoder->isPAL, 0);

        if (media_type == TC_VIDEO) { /* only video */
             if (p_write(fd, target, frame_size) != frame_size) {    
                perror("write video frame");
                return(TC_EXPORT_ERROR);
            }     
        }
        /* 
         * else (only case: this module used *also* for audio) be lazy
         * and wait for writing data after the audio encoding
         */

        return(TC_EXPORT_OK);
    }
  
    if (param->flag == TC_AUDIO) {
#ifdef WORDS_BIGENDIAN
        for (i = 0; i < param->size; i += 2) {
            char tmp = param->buffer[i];
            param->buffer[i] = param->buffer[i+1];
            param->buffer[i+1] = tmp;
        }
#endif
        // Although dv_encode_full_audio supports 4 channels, the internal
        // PCM data (param->buffer) is only carrying 2 channels so only deal
        // with 1 or 2 channel audio.
        // Work around apparent bug in dv_encode_full_audio when chans == 1
        // by putting silence in 2nd channel and calling with chans = 2
        if (chans == 1) {
            audio_bufs[0] = (int16_t *)param->buffer;
            memset(audio_bufs[1], 0, DV_AUDIO_MAX_SAMPLES * 2);
            dv_encode_full_audio(encoder, audio_bufs, 2, rate, target);
        } else {
            // assume 2 channel, demultiplex for libdv API
            for (i = 0; i < param->size/4; i++) {
                audio_bufs[0][i] = ((int16_t *)param->buffer)[i*2];
                audio_bufs[1][i] = ((int16_t *)param->buffer)[i*2+1];
            }
            dv_encode_full_audio(encoder, audio_bufs, chans, rate, target);
        }

        /* write raw DV frame. */
        if (p_write(fd, target, frame_size) != frame_size) {    
            perror("write frame");
            return(TC_EXPORT_ERROR);
        }     
        return(TC_EXPORT_OK);
    }
    // invalid flag
    return(TC_EXPORT_ERROR);
}

/* ------------------------------------------------------------ 
 *
 * stop encoder
 *
 * ------------------------------------------------------------*/

MOD_stop 
{
    int i;
  
    if (param->flag == TC_VIDEO) {
        dv_encoder_free(encoder);  
        return(TC_EXPORT_OK);
    }
  
    if (param->flag == TC_AUDIO) {
        for (i = 0; i < 4; i++) {
            free(audio_bufs[i]);
        }
        return(TC_EXPORT_OK);
    }  
    return(TC_EXPORT_ERROR);
}

/* ------------------------------------------------------------ 
 *
 * close outputfiles
 *
 * ------------------------------------------------------------*/

MOD_close
{  
    if (param->flag == TC_VIDEO) {
        close(fd);
        return(TC_EXPORT_OK);
    }

    if (param->flag == TC_AUDIO) {
        return(TC_EXPORT_OK);
    }
    return(TC_EXPORT_ERROR);  
}

