/* -*- Mode: C -*-
 *
 * $Header: /home/gene/library/website/docsrc/vwu/src/RCS/vidf.c,v 395.1 2008/04/20 17:25:48 gene Exp $
 *
 * Copyright (c) 2006 Gene Michael Stover.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

#include "this.h"

/*
 */
static DWORD
S_MakeAviFile (Vidf *vidf)
{
  static unsigned mode = OF_CREATE | OF_WRITE;

  vidf->avi_error = AVIFileOpen (&vidf->file, vidf->pathname, mode, NULL);
  if (vidf->avi_error == 0) {
    /* good */
  } else {
    LOG_LastError ("AVIFileOpen", __FILE__, __LINE__);
    printf ("\n%s:%d: AVIFileOpen failed", __FILE__, __LINE__);
    vidf->file = NULL;
  }
  return VIDF_Error (vidf);
}

/*
 */
static DWORD
S_MakeRawStream (Vidf *vidf, Frame *frame)
{
  AVISTREAMINFO strm_info;

  assert (vidf != NULL);
  assert (frame != NULL);
  bzero (&strm_info, sizeof strm_info);
  strm_info.fccType = streamtypeVIDEO;
  strm_info.dwScale = 1;
  strm_info.dwRate = VIDF_GetFps (vidf);
  strm_info.dwQuality = 9000;
  SetRect (&strm_info.rcFrame, 0, 0, frame->bmih->biWidth,
           frame->bmih->biHeight);
  vidf->avi_error = AVIFileCreateStream (vidf->file, &vidf->raw, &strm_info);
  if (vidf->avi_error == 0) {
    /* good */
  } else {
    LOG_AviError (vidf->avi_error, "AVIFileCreateStream", __FILE__,
                  __LINE__);
    printf ("\n%s:%d: AVIFileCreateStream failed", __FILE__, __LINE__);
  }
  return VIDF_Error (vidf);
}

/*
 */
static DWORD
S_MakeCompressStream (Vidf *vidf, Frame *frame)
{
  static unsigned flags = ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_DATARATE |
    ICMF_CHOOSE_PREVIEW;
  LONG sz;

  assert (vidf != NULL);
  assert (frame != NULL);
  vidf->avi_error = AVIMakeCompressedStream (&vidf->compress, vidf->raw,
                                             &vidf->opts, NULL);
  if (vidf->avi_error == 0) {
    sz = frame->bmih->biSize + frame->bmih->biClrUsed * sizeof (RGBQUAD);
    vidf->avi_error = AVIStreamSetFormat (vidf->compress, 0, frame->bmih, sz);
    if (vidf->avi_error == 0) {
      /* good */
    } else {
      LOG_AviError (vidf->avi_error, "AVIStreamSetFormat", __FILE__,
                    __LINE__);
    }
  } else {
    LOG_AviError (vidf->avi_error, "AVIMakeCompressedStream",
                  __FILE__, __LINE__);
  }
  return VIDF_Error (vidf);
}

/*
 */
static DWORD
S_CreateFileAndStreams (Vidf *vidf, Frame *frame)
{
  assert (vidf != NULL);
  assert (vidf->pathname != NULL);
  assert (vidf->file == NULL);
  assert (vidf->raw == NULL);
  assert (vidf->compress == NULL);
  assert (vidf->n == 0);
  if (S_MakeAviFile (vidf) == 0) {
    if (S_MakeRawStream (vidf, frame) == 0) {
      if (S_MakeCompressStream (vidf, frame) == 0) {
        /* good */
      } else {
        printf ("\n%s:%d: S_MakeCompressStream failed", __FILE__, __LINE__);
      }
    } else {
      printf ("\n%s:%d: S_MakeRawStream failed", __FILE__, __LINE__);
    }
  } else {
    printf ("\n%s:%d: S_MakeAviFile failed", __FILE__, __LINE__);
  }
  return VIDF_Error (vidf);
}

/*
 * Appends the frame to the video stream.
 * Assumes the frame has the same height, width, colors, & other
 * dimensions as previous frames.
 * Return 0 on success, non-zero on error.
 * It's called "AppendFrame" to leave the possibility for some
 * kind of "WriteFrame" which writes a frame at a specific
 * position in the file.  On the other hand, I don't expect to
 * ever need such a function.
 */
int
VIDF_AppendFrame (Frame *frame, Vidf *vidf)
{
  int rc = 0;
  static DWORD flags = 0;
  LONG bytes_written = 0;
  DIBSECTION dibs;

  if (VIDF_IsFirstFrame (vidf)) {
    if (S_CreateFileAndStreams (vidf, frame) == 0) {
      /* good so far */
    } else {
      printf ("\n%s:%d: S_CreateFileAndStreams failed", __FILE__, __LINE__);
      rc = 26;
    }
  } else {
    /*
     * We're not writing the first frame, so we assume that the
     * AVI file & streams exist.
     */
  }
  if (VIDF_Error (vidf) == 0) {
    vidf->avi_error = AVIStreamWrite (vidf->compress,
                                      vidf->n, /* frame number */
                                      1, /* number of frames */
                                      frame->pixels, /* pixels go here? */
                                      frame->bmih->biSizeImage,
                                      flags,
                                      NULL,
                                      &bytes_written);
    if (vidf->avi_error == 0) {
      /* good */
      ++vidf->n;
    } else {
      LOG_AviError (vidf->avi_error, "AVIStreamWrite", __FILE__, __LINE__);
      printf ("\n%s:%d: VIDF_AppendFrame: AVIStreamWrite failed", __FILE__,
              __LINE__);
      rc = 24;
    }
  }
  return rc;
}

/*
 * Dispose of a Vidf.  Writes any un-written data.
 * Return NULL as a convenience to the caller.
 * It is NOT an error to call this on NULL.
 */
Vidf *
VIDF_Close (Vidf *vidf)
{
  if (vidf != NULL) {
    if (vidf->compress != NULL) {
      AVIStreamRelease (vidf->compress); vidf->compress = NULL;
    }
    if (vidf->raw != NULL) {
      AVIStreamRelease (vidf->raw); vidf->raw = NULL;
    }
    if (vidf->file != NULL) {
      AVIFileRelease (vidf->file); vidf->file = NULL;
    }
    vidf = (Vidf *) xfree (vidf);
  } else {
    printf ("\n%s:%d: VIDF_Close: warning: Closing NULL", __FILE__, __LINE__);
  }
  return NULL;
}

/*
 */
Vidf *
VIDF_Create (char pathname[], char codec[], unsigned fps,
             unsigned long quality)
{
  Vidf *vidf = NULL;

  assert (pathname != NULL);
  assert (fps > 0);
  if (pathname != NULL) {
    if (fps > 0) {
      vidf = (Vidf *) xmalloc (sizeof *vidf);
      if (vidf != NULL) {
        bzero (vidf, sizeof *vidf);
        vidf->pathname = pathname;
        vidf->opts.fccType = streamtypeVIDEO;
        vidf->opts.fccHandler = mmioFOURCC (codec[0], codec[1], codec[2],
                                            codec[3]);
        vidf->opts.dwKeyFrameEvery = fps;
        vidf->opts.dwQuality = quality;
        vidf->opts.dwBytesPerSecond = 6 * 1024; /* modem can achieve this */
        vidf->opts.dwFlags = AVICOMPRESSF_DATARATE | AVICOMPRESSF_KEYFRAMES;
      } else {
        printf ("\n%s:%d: xmalloc of %u chars failed", __FILE__, __LINE__,
                (unsigned) sizeof *vidf);
      }
    } else {
      printf ("\n%s:%d: VIDF_Create:", __FILE__, __LINE__);
      printf (" Frames-per-second (fps) must be positive.");
    }
  } else {
    printf ("\n%s:%d: VIDF_Create: pathname must not be NULL.", __FILE__,
            __LINE__);
  }
  return vidf;
}

int
VIDF_Error (Vidf *vidf)
{
  int rc = 0;

  if (vidf->win_error != 0 || vidf->avi_error != 0) {
    /*
     * Get a non-predictable, non-zero number to return.
     */
    do {
      rc = rand ();
    } while (rc == 0);
  } else {
    /*
     * No errors, so return 0.
     */
    assert (rc == 0);
  }
  return rc;
}

/*
 */
int
VIDF_GetFps (Vidf *vidf)
{
  return vidf->opts.dwKeyFrameEvery;
}

/*
 */
Boolean
VIDF_IsFirstFrame (Vidf *vidf)
{
  assert (vidf != NULL);
  return vidf->n == 0;
}

/* --- end of file --- */
