
#define inline __inline		  /* needed for FFmpeg library */

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <stdio.h>
#include <stdlib.h>

AVFormatContext		*pFormatCtx;
AVCodecContext		*pCodecCtx;
uint8_t				*buffer;
AVFrame				*pFrame,*pFrameRGB;
int					videoStream;
struct SwsContext	*pSwsCtx;
int					SearchType;	  /* 0=>.asf uses course search; 1=>.mpg uses exact search (see below) */

		/* Uses ffmpeg library calls to open up a video file, identify its codec, and set up memory to receive a frame */
		/* Returns 1 if successful, 0 if fails for any reason.  Determines ImageRows and ImageCols so caller can allocate image array. */
		/* Subsequently, ReadVideoFrame() can be called to read a single frame at a time */
		/* Must call CloseVideoFile() to clean up allocated memory and gracefully close ffmpeg library */

int OpenVideoFile(char *filename,
				  int *ImageRows,
				  int *ImageCols)

{
AVCodec				*pCodec;
int					numBytes,i;

		/* Register all formats and codecs */
av_register_all();
		/* Open video file */
pFormatCtx=NULL;
if (avformat_open_input(&pFormatCtx,filename,NULL,NULL) != 0)
  return(0);
		/* retrieve stream information */
if (avformat_find_stream_info(pFormatCtx,NULL) < 0)
  {
  avformat_close_input(&pFormatCtx);
  return(0);
  }
		/* find the first video stream */
videoStream = -1;
for (i=0; i<(int)(pFormatCtx->nb_streams); i++)
  if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
	{
	videoStream=i;
	break;
	}
if (videoStream == -1)
  {
  avformat_close_input(&pFormatCtx);
  return(0);
  }
		/* get a pointer to the codec context for the video stream */
pCodecCtx=pFormatCtx->streams[videoStream]->codec;
		/* find the decoder for the video stream */
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL)
  {
  avformat_close_input(&pFormatCtx);
  return(0);
  }
		/* open codec */
if (avcodec_open2(pCodecCtx,pCodec,NULL) < 0)
  {
  avformat_close_input(&pFormatCtx);
  return(0);
  }
		/* allocate video frame */
pFrame=av_frame_alloc();
		/* allocate an AVFrame structure */
pFrameRGB=av_frame_alloc();
		/* determine required buffer size and allocate buffer */
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);
buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

		// Assign appropriate parts of buffer to image planes in pFrameRGB
		// Note that pFrameRGB is an AVFrame, but AVFrame is a superset of AVPicture
avpicture_fill((AVPicture *)pFrameRGB,buffer,PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height);
pSwsCtx=sws_getContext(pCodecCtx->width,
			pCodecCtx->height, pCodecCtx->pix_fmt,
			pCodecCtx->width, pCodecCtx->height,
			PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
if (pSwsCtx == NULL)
  {
  avformat_close_input(&pFormatCtx);
  avcodec_close(pCodecCtx);
  return(0);
  }

		/* tell calling function how big images will be */
*ImageRows=pCodecCtx->height;
*ImageCols=pCodecCtx->width;

if (filename[strlen(filename)-1] == 'f')
  SearchType=0;
else
  SearchType=1;
return(1);
}



int ReadVideoFrame(int	Milliseconds,			  /* time in video to retrieve from (e.g. 1033 = 1.033 seconds = 32nd frame of 30Hz video */
				   unsigned char *FrameImage)

{
int					frame_done,i;
AVPacket			packet;
int					seek_ret;
int					frameFinished;

		/* seek to the closest key frame prior to the desired frame */

		/* normally, a movie file defines its frame rate as .num/.dev; but the .asf cafeteria movie files only show 1/1000 */
		/* there are appx 15 frames per second in a cafeteria video but they are not evenly spaced by the decoding timestamps (dts) */
		/* this search can be super-accurate if a video file is encoded better (see next line commented out and below) */
//seek_ret=av_seek_frame(pFormatCtx,videoStream,(seek_seconds*pCodecCtx->time_base.den)/pCodecCtx->time_base.num,AVSEEK_FLAG_BACKWARD);
if (SearchType == 1)	  /* I am not sure why milliseconds scales by this but it is accurate for our mpg files */
  seek_ret=av_seek_frame(pFormatCtx,videoStream,(int)((double)Milliseconds*1201.2),AVSEEK_FLAG_BACKWARD);  /* 1201.2 = (1001*40)/(1000/30) */
else
  seek_ret=av_seek_frame(pFormatCtx,videoStream,Milliseconds,AVSEEK_FLAG_BACKWARD);
if (seek_ret < 0)
  return(0);	/* no frame found at that seek */

avcodec_flush_buffers(pCodecCtx);  /* reset buffers to begin anew */

		/* read frames until the desired frame is obtained */
frame_done=0;
while (av_read_frame(pFormatCtx,&packet) >= 0)
  {
  if (packet.stream_index == videoStream)
	{
	avcodec_decode_video2(pCodecCtx,pFrame,&frameFinished,&packet);
	if (frameFinished)
	  {
	  if ((SearchType == 1  &&  (int)((double)packet.dts/1201.2) <= Milliseconds)
			  ||  (SearchType == 0  &&  packet.dts < Milliseconds))
	  // if (packet.dts <= (seek_seconds*pCodecCtx->time_base.den/pCodecCtx->time_base.num))
	  // if (packet.dts < Milliseconds)
		continue;
			/* convert the image from its native format to RGB */
	  sws_scale(pSwsCtx,(const uint8_t * const *)pFrame->data,
				pFrame->linesize,0,pCodecCtx->height,
				pFrameRGB->data,pFrameRGB->linesize);
			/* convert frame to simple array (reverse RGB to BGR because Win32 functions assume BGR order) */
	  for (i=0; i<(pCodecCtx->height)*(pCodecCtx->width); i++)
		{
		FrameImage[i*3+0]=(unsigned char)(pFrameRGB->data[0][i*3+2]);
		FrameImage[i*3+1]=(unsigned char)(pFrameRGB->data[0][i*3+1]);
		FrameImage[i*3+2]=(unsigned char)(pFrameRGB->data[0][i*3+0]);
		}
	  frame_done=1;
	  }
	}
  av_free_packet(&packet);	/* free the packet that was allocated by av_read_frame */
  if (frame_done)
	break;
  }
return(1);
}



void CloseVideoFile()

{
av_free(buffer);
av_frame_free(&pFrameRGB);			// Free the RGB image
av_frame_free(&pFrame);				// Free the YUV frame
avcodec_close(pCodecCtx);			// Close the codec
avformat_close_input(&pFormatCtx);	// Close the video file
}
