/*
 *  decode_yuv.c
 *
 *  Copyright (C) Thomas Oestreich - 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 "transcode.h"
#include "libtc/libtc.h"
#include "tcinfo.h"

#include "aclib/imgconvert.h"
#include "ioaux.h"
#include "tc.h"

/*
 * About this code:
 *
 * based on video_out.h, video_out.c, video_out_ppm.c
 *
 * Copyright (C) 1999-2001 Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
 * Stripped and rearranged for transcode by
 * Francesco Romani <fromani@gmail.com> - July 2005
 *
 * This file is part of mpeg2dec, a free MPEG-2 video stream decoder.
 *
 * What?
 * Basically, this code does only a colorspace conversion from ingress frames
 * (YV12) to egress frames (RGB). It uses basically the same routines of libvo
 * and old decode_yuv.c
 *
 * Why?
 * decode_yuv was the one and the only transcode module which uses the main
 * libvo routines, not the colorspace conversion ones. It not make sense to me
 * to have an extra library just for one module, so I stripped down to minimum
 * the libvo code used by decode_yuv.c and I moved it here.
 * This code has only few things recalling it's ancestor, but I'm still remark
 * it's origin.
 */

typedef struct vo_s {
    // frame size
    unsigned int width;
    unsigned int height;

    // internal frame buffers
    uint8_t *rgb;
    uint8_t *yuv[3];
} vo_t;

#define vo_convert(instp) \
	ac_imgconvert((instp)->yuv, IMG_YUV420P, &(instp)->rgb, IMG_RGB24, \
		      (instp)->width, (instp)->height)

#define READ_PLANE(fd, V, H, plane) do { \
    int i, bytes; \
    \
    for (i = 0; i < (V); i++) { \
       bytes = tc_pread((fd), (plane) + i * (H), (H)); \
       if (bytes != (H)) { \
    	   if (bytes < 0) \
	          tc_log_error(__FILE__, "read failed: %s", strerror(errno)); \
    	   return 0; \
       } \
    } \
} while (0) 

/*
 * legacy (and working :) ) code:
 * read one YUV420P plane at time from file descriptor (pipe, usually)
 * and store it in internal buffer
 */
static int vo_read_yuv(vo_t *vo, int fd)
{
    unsigned int v = vo->height, h = vo->width;

    /* Read luminance scanlines */

    READ_PLANE(fd, v, h, vo->yuv[0]);
    
    v /= 2;
    h /= 2;

    /* Read chrominance scanlines */
    READ_PLANE(fd, v, h, vo->yuv[1]);
    READ_PLANE(fd, v, h, vo->yuv[2]);

    return 1;
}

#undef READ_PLANE

/*
 * simpler than above:
 * write the whole RGB buffer in to file descriptor (pipe, usually).
 * WARNING: caller must ensure that RGB buffer holds valid data by
 * invoking vo_convert *before* invoking this function
 */
static int vo_write_rgb (vo_t *vo, int fd)
{
    int framesize = vo->width * vo->height * 3;
    int bytes = tc_pwrite(fd, vo->rgb, framesize);
    if (bytes != framesize) {
    	if (bytes < 0)
	        tc_log_error(__FILE__, "read failed: %s", strerror(errno));
    	return 0;
    }
    return 1;
}

/*
 * finalize a vo structure, free()ing it's internal buffers.
 * WARNING: DOES NOT cause a buffer flush, you must do it manually.
 */
static void vo_clean (vo_t *vo)
{
    free(vo->yuv[0]);
    free(vo->yuv[1]);
    free(vo->yuv[2]);
    free(vo->rgb);
}

/*
 * initialize a vo structure, allocate internal buffers
 * and so on
 */
static int vo_alloc(vo_t *vo, int width, int height)
{
    if (width <= 0 || height <= 0) {
        return -1;
    }

    vo->width = (unsigned int)width;
    vo->height = (unsigned int)height;

    vo->yuv[0] = tc_zalloc(width * height);
    if (!vo->yuv[0]) {
        tc_log_error(__FILE__, "out of memory");
    	return -1;
    }
    vo->yuv[1] = tc_zalloc((width/2) * (height/2));
    if (!vo->yuv[1]) {
        tc_log_error(__FILE__, "out of memory");
	    free(vo->yuv[0]);
    	return -1;
    }
    vo->yuv[2] = tc_zalloc((width/2) * (height/2));
    if (!vo->yuv[2]) {
        tc_log_error(__FILE__, "out of memory");
    	free(vo->yuv[0]);
	    free(vo->yuv[1]);
    	return -1;
    }

    vo->rgb = tc_zalloc(width * height * 3);
    if (!vo->rgb) {
        tc_log_error(__FILE__, "out of memory");
    	free(vo->yuv[0]);
	    free(vo->yuv[1]);
    	free(vo->yuv[2]);
        return -1;
    }

    return 0;
}


/* ------------------------------------------------------------
 *
 * decoder thread
 *
 * ------------------------------------------------------------*/

void decode_yuv(decode_t *decode)
{
    vo_t vo;

    if (decode->width <= 0 || decode->height <= 0) {
        tc_log_error(__FILE__, "invalid frame parameter %dx%d",
                     decode->width, decode->height);
        import_exit(1);
    }

    vo_alloc(&vo, decode->width, decode->height);

    // read frame by frame - decode into RGB - pipe to stdout
    while (vo_read_yuv(&vo, decode->fd_in)) {
        vo_convert(&vo);
        vo_write_rgb(&vo, decode->fd_out);
    }

    // ends
    vo_clean(&vo);

    import_exit(0);
}

/*************************************************************************/

/*
 * Local variables:
 *   c-file-style: "stroustrup"
 *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
 *   indent-tabs-mode: nil
 * End:
 *
 * vim: expandtab shiftwidth=4:
 */
