#include <stdio.h>
#include <stdlib.h>
#include <cv.h>
#include <highgui.h>
#include <jpeglib.h>
#include <jerror.h>

/*
  This code captures and displays video from a Linksys/Axis IP Camera's MJPEG stream using OpenCV, popen, and the program "curl"

  The libjpeg code was taken and adapted from code written by George V. Landon
  http://georgelandon.blogspot.com/2008/08/remote-capture-canon-xti.html

  Compiled on Linux with:  gcc -ggdb `pkg-config --cflags --libs opencv` onelinksys.c -o onelinksys /usr/local/lib/libjpeg.so.7

  Usage:  ./onelinksys <URL of MJPEG Stream>
  Example:  ./onelinksys http://mu.webcam.oregonstate.edu/axis-cgi/mjpg/video.cgi

  This code can be modified to parse MJPEG streams in other formats (AirLink's MJPEG stream for example).
 */

//Start of libjpeg methods
typedef struct my_src_mgr my_src_mgr;
struct my_src_mgr
{
  struct jpeg_source_mgr pub;
  JOCTET eoi_buffer[2];
};

static void init_source(j_decompress_ptr cinfo)
{
}

static int fill_input_buffer(j_decompress_ptr cinfo)
{
  return 1;
}

static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
  my_src_mgr *src = (my_src_mgr *)cinfo->src;
  if (num_bytes > 0)
    {
      while (num_bytes > (long)src->pub.bytes_in_buffer)
	{
	  num_bytes -= (long)src->pub.bytes_in_buffer;
	  fill_input_buffer(cinfo);
	}
    }
  src->pub.next_input_byte += num_bytes;
  src->pub.bytes_in_buffer -= num_bytes;
}

static void term_source(j_decompress_ptr cinfo)
{
}

void jpeg_memory_src(j_decompress_ptr cinfo, unsigned char const *buffer, size_t bufsize)
{
  my_src_mgr *src;
  if (! cinfo->src)
    {
      cinfo->src = (struct jpeg_source_mgr*) (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(my_src_mgr));
    }
  src = (my_src_mgr *)cinfo->src;
  src->pub.init_source = init_source;
  src->pub.fill_input_buffer = fill_input_buffer;
  src->pub.skip_input_data = skip_input_data;
  src->pub.resync_to_restart = jpeg_resync_to_restart;
  src->pub.term_source = term_source;
  src->pub.next_input_byte = buffer;
  src->pub.bytes_in_buffer = bufsize;
}
//End of libjpeg methods

int main(int argc, char *argv[])
{
  char command[] = "";
  
  if (argc > 1)
    {
      strcat(command,"curl -Ns ");
      strcat(command,argv[1]);
    }
  else
    return 1;

  //"curl -Ns <URL of MJPEG Stream>" is executed and the output is read from the FILE pointer stream
  FILE *stream = popen(command,"r");

  //No more than 66 chars worth of unused data is read at a time
  char garbage[80];
  
  struct jpeg_decompress_struct cinfo;
  struct jpeg_error_mgr jerr;
  JSAMPROW row_pointer[1];
  char *raw_image = NULL;
  int location, i;
  
  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_decompress(&cinfo);

  //The body of the multipart/x-mixed-replace HTTP response is parsed into pieces
  
  //Each frame's size as text
  char framesize[10];
  
  //While a key is not pressed
  while (cvWaitKey(2) < 0)
    {      
      //Begin Parsing This Frame
      //--ThisRandomString\r\n
      fgets(garbage,sizeof(garbage),stream);
      
      //"Content-type:...\n"
      fgets(garbage,sizeof(garbage),stream);

      //"Content-Length: "
      fread((void *)garbage,sizeof(char),16,stream);

      //Read the frame size
      fgets(framesize,sizeof(framesize),stream);

      //\n before frame
      fgets(garbage,sizeof(garbage),stream);

      //Create space to store the frame
      char frame[atoi(framesize)];
      
      //Read the frame
      fread((void *)frame,sizeof(char),atoi(framesize),stream);
      
      if(!frame)
	{
	  break;
	}

      //\n before the next boundary
      fgets(garbage,sizeof(garbage),stream);
      //Finished Parsing This Frame

      //JPEG to IplImage
      IplImage *fRGB, *fBGR;

      location = 0;
      i = 0;
      
      jpeg_memory_src(&cinfo,frame,sizeof(frame));
      jpeg_read_header(&cinfo,TRUE);
      jpeg_start_decompress(&cinfo);
      
      //Create an empty IplImage to put data into
      fBGR = cvCreateImageHeader(cvSize(cinfo.output_width,cinfo.output_height), IPL_DEPTH_8U, cinfo.num_components);
      cvCreateData(fBGR);
      fRGB = cvCreateImageHeader(cvSize(cinfo.output_width,cinfo.output_height), IPL_DEPTH_8U, cinfo.num_components);
      cvCreateData(fRGB);
      
      raw_image = fBGR->imageData;
      row_pointer[0] = (unsigned char *)malloc(cinfo.output_width*cinfo.num_components);
      
      while(cinfo.output_scanline < cinfo.image_height)
	{
	  jpeg_read_scanlines(&cinfo,row_pointer,1);
	  for (i = 0; i < cinfo.image_width*cinfo.num_components; i++)
	    {
	      raw_image[location++] = row_pointer[0][i];
	    }
	}
      
      jpeg_finish_decompress(&cinfo);
      
      //Convert the frame from BGR to RGB
      cvCvtColor(fBGR,fRGB,CV_BGR2RGB);
      
      //Reset and repeat
      free(row_pointer[0]);
      row_pointer[0] = NULL;

      //Show the images
      cvShowImage("Camera",fRGB);

      //Cleanup
      cvReleaseImage(&fRGB);
      cvReleaseImage(&fBGR);
    }
  jpeg_destroy_decompress(&cinfo);
  pclose(stream);

  return 0;
}
