JPEG at SC

Robert Olson olson at mcs.anl.gov
Thu Nov 2 11:25:29 CST 2000


Vic as distributed can decode MJPEG. I've made mods that include support 
for the Intel Jpeg library for fast MMX-based decode on Windows.

We're using the Pinnacle DC-10plus jpeg capture card to capture the jpeg. 
It uses the DC10 drivers from

         http://www.cicese.mx/~mirsev/Linux/DC10plus/

with a slight modification for nonblocking reads (attached).

The vic grabber is also attached.

On the windows side, the latest code drop

http://www-unix.mcs.anl.gov/~olson/AG/Software/NT/ag-2000-1027.zip

includes the new display vic and the intel libs. Note that this requires 
the new Linux code that is on the cdrom image. You can upgrade an existing 
installation with the script in

http://www-unix.mcs.anl.gov/~olson/AG/Software/Linux/vv-update

The distribution stuff is a bit discombobulated due to the rushing around 
here the last few weeks...

--bob

At 10:46 AM 11/2/2000 -0600, Stuart Levy wrote:
>Hey, a vic that can receive motion jpeg sounds really neat!
>Can there be a source tarball for the same software at some point?
>I'd love to be able to run such a thing on Linux and Irix as well as
>Windows (and would be happy to try to build it).
>
>Could you say more about what you use to transmit the MJPEG stream?
>Does it do software compression, use the LinuxMediaArts hardware
>compressor card, or what?  And does it use a modified vic?
>
>     Stuart Levy, slevy at ncsa.uiuc.edu
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mcs.anl.gov/pipermail/ag-tech/attachments/20001102/a5802329/attachment.htm>
-------------- next part --------------
Index: dc10.c
===================================================================
RCS file: /home/FuturesLab/Software/CVSMaster/linux-dc10/dc10.c,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 dc10.c
*** dc10.c	2000/10/11 13:39:32	1.1.1.1
--- dc10.c	2000/10/11 14:08:10
***************
*** 2254,2260 ****
   *   Sync on a MJPEG buffer
   */

! static int jpg_sync(struct zoran *zr, struct zoran_sync *bs)
  {
  	unsigned long flags;
  	int frame, timeout;
--- 2254,2260 ----
   *   Sync on a MJPEG buffer
   */

! static int jpg_sync(struct zoran *zr, struct zoran_sync *bs, int nonblocking)
  {
  	unsigned long flags;
  	int frame, timeout;
***************
*** 2266,2271 ****
--- 2266,2275 ----
  	while (zr->jpg_que_tail == zr->jpg_dma_tail) {
  		if (zr->jpg_dma_tail == zr->jpg_dma_head)
  			break;
+
+ 		if (nonblocking)
+ 		    return -EWOULDBLOCK;
+ 		
  		timeout = interruptible_sleep_on_timeout(&zr->jpg_capq, 10 * HZ);
  		if (!timeout) {
  			btand(~ZR36057_JMC_Go_en, ZR36057_JMC);
***************
*** 3732,3739 ****
  			int res;

  			DEBUG3(printk("zoran ioctl BUZIOC_SYNC\n"));
! 			res = jpg_sync(zr, &bs);
  			if (copy_to_user(arg, &bs, sizeof(bs))) {
  				return -EFAULT;
  			}
  			return res;
--- 3736,3757 ----
  			int res;

  			DEBUG3(printk("zoran ioctl BUZIOC_SYNC\n"));
! 			res = jpg_sync(zr, &bs, 0);
  			if (copy_to_user(arg, &bs, sizeof(bs))) {
+ 				return -EFAULT;
+ 			}
+ 			return res;
+ 		}
+ 		break;
+
+ 	case BUZIOC_SYNC_NONBLOCKING:
+ 		{
+ 			struct zoran_sync bs;
+ 			int res;
+
+ 			DEBUG3(printk("zoran ioctl BUZIOC_SYNC\n"));
+ 			res = jpg_sync(zr, &bs, 1);
+ 			if (res >= 0 && copy_to_user(arg, &bs, sizeof(bs))) {
  				return -EFAULT;
  			}
  			return res;
Index: dc10.h
===================================================================
RCS file: /home/FuturesLab/Software/CVSMaster/linux-dc10/dc10.h,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 dc10.h
*** dc10.h	2000/10/11 13:39:32	1.1.1.1
--- dc10.h	2000/10/11 14:08:10
***************
*** 156,161 ****
--- 156,162 ----
  #define BUZIOC_QBUF_PLAY      _IOW ('v', BASE_VIDIOCPRIVATE+4,  int)
  #define BUZIOC_SYNC           _IOR ('v', BASE_VIDIOCPRIVATE+5,  struct zoran_sync)
  #define BUZIOC_G_STATUS       _IOWR('v', BASE_VIDIOCPRIVATE+6,  struct zoran_status)
+ #define BUZIOC_SYNC_NONBLOCKING           _IOR ('v', BASE_VIDIOCPRIVATE+7,  struct zoran_sync)


  #ifdef __KERNEL__

-------------- next part --------------

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/fcntl.h>  
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/mman.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>


extern "C" {
#include <asm/types.h>
#include <linux/videodev.h>
}
#include "dc10.h"

#include "grabber.h"
#include "vic_tcl.h"
#include "device-input.h"
#include "module.h"


u_char *scan_frame(u_char *frame, int length, int *newLength);
u_char *first_field(u_char *frame, int length, int *newLength);

/* The number of mjpeg buffers used whilst recording */
#define NUMBUF 32
#define NTSC_WIDTH 768
#define NTSC_HEIGHT 492

static const char *devlist[] = {
    "/dev/video0", "/dev/video1", "/dev/video2", "/dev/video3",
    NULL
};

static const char *palette_name[] = {
    "", "grey", "hi240", "rgb16", "rgb24", "rgb32", "rgb15", 
    "yuv422", "yuyv422", "uyvy422", "yuv420", "yuv411", "RAW", "yuv422P", "yuv411P", "yuv420P", "yuv410P" };

class BuzGrabber : public Grabber {
public:
    BuzGrabber(const char * cformat, const char *dev);
    virtual ~BuzGrabber();
    virtual int  command(int argc, const char*const* argv);
    virtual void start();
    virtual void stop();
    virtual int  grab();
  void set_size_JPEG(int w, int h);     
  void remove_header(char *dest, char *src);

    int isRunning() { return running; }
protected:

    void format();
    void setsize();
    char *mjpeg_buffer; /* The MJPEG exchange buffer */

    int running;

    int norm;
    int input;
    int scaling;
    int num_frames;	
    int verbose;
    int debug;
    int quality;
    int time_lapse;
    int result; // Storing temporary file op results

    char **channelNames;
    int nChannels;

    struct zoran_status bstat_c, bstat_s;
    int buzdev, frames;

    struct video_picture     pict;

    unsigned long ringBufferEntrySize;
    unsigned long ringBufferCount;

    struct zoran_params bp;
    int i, tmp;
     FILE *fp;
      
};


class BuzDevice: public InputDevice
{
   public:
    BuzDevice(const char *dev, const char*, char *attr);
    virtual int command(int argc, const char*const* argv);

    private:
    const char *dev_;
    
};

class StopCaptureObj
{
private:
    BuzGrabber *grabber_;
    int wasCapturing_;

public:
    StopCaptureObj(BuzGrabber *g) {
	grabber_ = g;
	wasCapturing_ = g->isRunning();

	if (wasCapturing_)
	    grabber_->stop();
    }

    ~StopCaptureObj() {
	if (wasCapturing_)
	    grabber_->start();
    }
};

BuzDevice::BuzDevice(const char *dev, const char *name, char *attr) : InputDevice(name)
{
    dev_ = dev;
    attributes_ = attr;
    fprintf(stderr,"Buz:  ==> %s\n",attr);
}

int BuzDevice::command(int argc, const char*const* argv)
{
    Tcl& tcl = Tcl::instance();


    if (argc == 3) {
	if (strcmp(argv[1], "open") == 0) {
	    TclObject* o = 0;
	    o = new BuzGrabber(argv[2],dev_);
	    if (o != 0)
		tcl.result(o->name());
	    return (TCL_OK);
	}


    }
    return (InputDevice::command(argc, argv));
}


BuzGrabber::BuzGrabber(const char * cformat, const char *dev)
{
    UNUSED(cformat);

    running = 0;
    
    norm = 1;
    input = -1;
    scaling = 1;
    num_frames = 1;
    verbose = 0;
    debug = 1;
    quality = 90;
    time_lapse = 1;
  
    buzdev = open(dev, O_RDWR);
    if (buzdev < 0)
    {
	perror("open");
	exit(1);
    }
    //sets parameters and memory maps
  
    result = ioctl(buzdev, BUZIOC_G_PARAMS, &bp);
    if (result < 0)
    {
	perror("BUZIOC_G_PARAMS");
	exit(1);
    }

    /*
     * Set some defaults
     */
    
    input= bp.input      = 0;
    norm = bp.norm       = VIDEO_MODE_NTSC;
    quality = bp.quality    = 90;

    bp.decimation = 2; /* decimation 2 or 4 needs 1 field per frame */

    if (bp.decimation == 1)
	bp.field_per_buff = 2;
    else
	bp.field_per_buff = 1;


    bp.img_width = 640;
    result = ioctl(buzdev, BUZIOC_S_PARAMS, &bp);
    if (result < 0)
    {
	perror("BUZIOC_S_PARAMS");
	exit(1);
    }

    printf("Image size will be %dx%d, %d field(s) per buffer\n",
	   bp.img_width / bp.HorDcm, bp.img_height/bp.VerDcm, bp.field_per_buff);

    /* Request buffers */

    struct zoran_requestbuffers br;

    br.count = NUMBUF;
    br.size  = 256*1024; /* 256 kb for each captured JPEG frame */
    result = ioctl(buzdev, BUZIOC_REQBUFS,&br);
    
    if (result < 0)
    {
	perror("BUZIOC_REQBUFS");
	exit(1);
    }
    printf("Got %ld buffers of size %ld\n",br.count, br.size);
    ringBufferEntrySize = br.size;
    ringBufferCount= br.count;
  
    /* Map the buffers */

    mjpeg_buffer = (char *) mmap(0, br.count * br.size, PROT_READ,
				 MAP_SHARED, buzdev, 0);

    if (mjpeg_buffer == MAP_FAILED)
    {
	perror("mmap");
	exit(1);
    }


    set_size_JPEG(NTSC_WIDTH,NTSC_HEIGHT);

    /*
     * V4L stuff.
     *
     * Initialize the pict structure (used later for brightness/
     * contrast/hue settings).
     */

    if (-1 == ioctl(buzdev, VIDIOCGPICT,&pict)) {
	perror("ioctl VIDIOCGPICT");
    }
    fprintf(stderr,"V4l:   depth=%d, palette=%d %s\n",
	    pict.depth,
	    pict.palette,
	    (pict.palette<sizeof(palette_name)/sizeof(char*))?
	    palette_name[pict.palette]:"??");

    /*
     * Get the list of ports we have.
     */

    struct video_capability capability;
    
    if (-1 == ioctl(buzdev,VIDIOCGCAP,&capability))
    {   
	perror("ioctl VIDIOCGCAP");
	
    }
    else
    {
	struct video_channel channel;

	channelNames = new char *[capability.channels];
	nChannels = capability.channels;
	for (int i = 0; i < capability.channels; i++)
	{
	    channel.channel = i;
	    if (ioctl(buzdev, VIDIOCGCHAN, &channel) < 0)
		perror("ioctl VIDIOCGCHAN");
	    else
	    {
		printf("Got channel %d is %s\n", i, channel.name);
		channelNames[i] = new char [strlen(channel.name) + 1];
		strcpy(channelNames[i], channel.name);
	    }
	    
	}
    }
}

void BuzGrabber::start()
{
    unsigned long frame = 0;

    if (running)
	return;
    
    //Queue buffers for recording
    for(frame = 0; frame < ringBufferCount; frame++)
    {
	if (ioctl(buzdev, BUZIOC_QBUF_CAPT, &frame) < 0)
	{
	    perror("BUZIOC_QBUF_CAPT");
	    exit(1);
	}
    }

    running = 1;

   Grabber::start();
}

void BuzGrabber::stop()
{
    //
    // Queuing frame -1 means to stop capture (doh)
    //

    if (!running)
	return;

    int frame = -1;

    if (ioctl(buzdev,BUZIOC_QBUF_CAPT,&frame) < 0)
    {
	perror("ioctl BUZIOC_QBUF_CAPT");
    }

    running = 0;
   
    Grabber::stop();
}


int BuzGrabber::grab()
{
    u_char  *fr=NULL;
    struct zoran_sync bs, last_bs;

    //
    // Invoke the nonblocking sync until it says we would block.
    // This should clear out anything that's in the ring buffer
    //

//    printf("Enter grab\n");
    int haveFrame = 0;
#define NONBLOCKING_BUZ
#ifdef NONBLOCKING_BUZ
    while (1)
    {
	int rc = ioctl(buzdev, BUZIOC_SYNC_NONBLOCKING, &bs);
	if (rc < 0)
	{
	    if (errno == EWOULDBLOCK)
	    {
//		printf("call woudl block\n");
		//
		// we're done, break out.
		//
		break;
	    }
	    else
	    {
		// some other error
		perror("ioctl BUZIOC_SYNC_NONBLOCKING");
		return 0;
	    }
	}
	//
	// Got a frame. If we have had a frame already, return it to the queue
	//
	// printf("got frame %d\n", bs.frame);
	if (haveFrame)
	{
//	    printf("Requeuing frame %d\n", last_bs.frame);
	    if (ioctl(buzdev, BUZIOC_QBUF_CAPT, &last_bs.frame) < 0)
	    {
		printf("requeue BUZIOC_QBUF_CAPT frame %ld: %s\n",
		       last_bs.frame, strerror(errno));
	    }
	}
	last_bs = bs;
	haveFrame++;
    }
#endif

    //
    // If we didn't find a frame, block until we get one
    //

    if (!haveFrame)
    {
//	printf("Getting new frame\n");
	if (ioctl(buzdev, BUZIOC_SYNC, &bs) < 0)
	{
	    perror("ioctl BUZIOC_SYNC after loop");
	    return 0;
	}
    }
//    printf("Got frame %d\n", bs.frame);

    unsigned long frameNumber = bs.frame;

    fr = (u_char*) mjpeg_buffer + frameNumber * ringBufferEntrySize;

    int sendLength;
//    u_char *sendBuffer = scan_frame(fr, bs.length, &sendLength);

    u_char *sendBuffer;

    if (bp.decimation == 1)
	sendBuffer = first_field(fr, bs.length, &sendLength);
    else
    {
	sendBuffer = fr;
	sendLength = bs.length;
    }
	
    JpegFrame f(media_ts(),
		sendBuffer,
		sendLength,
		quality, 0,
		bp.img_width/bp.HorDcm,
		bp.img_height/bp.VerDcm);
#if 1
    {
	static int first = 1;
	if (first)
	{
	    FILE *fp = fopen("capture","w");
	    fwrite(fr, bs.length, 1, fp);
	    fclose(fp);
	    first = 0;
	}
    }
#endif

    int rc = target_->consume(&f);
    
    if (0>ioctl(buzdev, BUZIOC_QBUF_CAPT, &bs.frame))
    {
	perror("BUZIOC_QBUF_CAPT"); 
    }

    return rc;
}

void BuzGrabber::set_size_JPEG(int w, int h)
{
	delete framebase_;

	inw_ = w;
	inh_ = h;
	w &=~ 0xf;
	h &=~ 0xf;
	outw_ = w;
	outh_ = h;

	int s = w * h;
	framesize_ = s;
	int n = s + (s >> 1) + 2 * GRABBER_VPAD * outw_;
	framebase_ = new u_char[n];
	/* initialize to gray */
	memset(framebase_, 0x80, n);
	frame_ = framebase_ + GRABBER_VPAD * outw_;
	crinit(w, h); //

	vstart_ = 0;
	vstop_ = blkh_;
	hstart_ = 0;
	hstop_ = blkw_;
}


void BuzGrabber::remove_header(char *dest, char *src)
{
  *dest=*src; 
    dest+=8;      
}



int BuzGrabber::command(int argc, const char* const* argv)
{
    Tcl &tcl = Tcl::instance();

    UNUSED(tcl);

    if (argc == 3)
    {
	if (strcmp(argv[1], "decimate") == 0) {
	    int decimate = atoi(argv[2]);

	    bp.decimation = decimate;
	    if (bp.decimation == 1)
		bp.field_per_buff = 2;
	    else
		bp.field_per_buff = 1;

	    StopCaptureObj stopme(this);

	    result = ioctl(buzdev, BUZIOC_S_PARAMS, &bp);
	    if (result < 0)
	    {
		perror("BUZIOC_S_PARAMS");
		exit(1);
	    }

	    if (bp.decimation == 1)
		set_size_JPEG(720, 240);
	    else
		set_size_JPEG(720 / bp.decimation, 480 / bp.decimation);

	    return TCL_OK;
	}

	if (strcmp(argv[1], "port") == 0) {

	    /* Find what channelNum this is */

	    int channelNum = -1;
	    for (int i = 0; i < nChannels; i++)
	    {
		if (strcmp(argv[2], channelNames[i]) == 0)
		{
		    channelNum = i;
		    break;
		}
	    }

	    if (channelNum == -1)
	    {
		printf("Couldn't find port '%s'\n", argv[2]);
	    }
	    else
	    {
		struct video_channel channel;

		bzero(&channel, sizeof(struct video_channel));
		if (-1 == ioctl(buzdev, VIDIOCGCHAN, &channel))
		    perror("ioctl VIDIOCGCHAN");

		input = channel.channel = channelNum;
		channel.norm = norm;
		
		printf("Setting port %s %d\n", argv[2], channelNum);

		StopCaptureObj stop(this);
		
		if (-1 == ioctl(buzdev, VIDIOCSCHAN, &channel))
		    perror("ioctl VIDIOCSCHAN");

	    }
    	    return (TCL_OK);
	}

	if(strcmp(argv[1], "q")==0)
	{
	    quality=atoi(argv[2]);
	    bp.quality=quality;
	    printf("*** input=%d,decimation=%d,norm=%d,quality=%d\n",bp.input,bp.decimation,bp.norm,bp.quality);

	    StopCaptureObj stopme(this);

	    if (-1 == ioctl(buzdev,BUZIOC_S_PARAMS,&bp))
		perror("ioctl BUZIOC_S_PARAMS setting quality");

	    return (TCL_OK);
	} 
	else if (strcmp(argv[1], "brightness") == 0) {
                        u_char val = atoi(argv[2]);

			pict.brightness=val*256;
			
    			if (-1 == ioctl(buzdev,VIDIOCSPICT,&pict))
        		perror("ioctl VIDIOCSPICT");

			fprintf(stderr, "V4l: Brightness = %d\n", val);
                        return (TCL_OK);
        }
        else if (strcmp(argv[1], "contrast") == 0) {
                        u_char val = atoi(argv[2]);

			pict.contrast=val*256;

    			if (-1 == ioctl(buzdev,VIDIOCSPICT,&pict))
			    perror("ioctl VIDIOCSPICT");

                        return (TCL_OK);
        }
        else if (strcmp(argv[1], "hue") == 0) {
                        u_char val = atoi(argv[2]);

			pict.hue=val*256;

    			if (-1 == ioctl(buzdev, VIDIOCSPICT,&pict))
			    perror("ioctl VIDIOCSPICT");

                        return (TCL_OK);
        }
        else if (strcmp(argv[1], "saturation") == 0) {
                        u_char val = atoi(argv[2]);

                        pict.colour=val*256;

    			if (-1 == ioctl(buzdev,VIDIOCSPICT,&pict))
			    perror("ioctl VIDIOCSPICT");

                        return (TCL_OK);
        }
    }
      
    return (Grabber::command(argc, argv));
}


BuzGrabber::~BuzGrabber()
{
    munmap(mjpeg_buffer, ringBufferEntrySize * ringBufferCount);
    close(buzdev);
}



class BuzDeviceMaker
{
 public:

  BuzDeviceMaker(const char **dev);
  

};


BuzDeviceMaker::BuzDeviceMaker(const char **dev)
{

        
    static const char *palette_name[] = {
	"", "grey", "hi240", "rgb16", "rgb24", "rgb32", "rgb15", 
	"yuv422", "yuyv422", "uyvy422", "yuv420", "yuv411", "RAW", "yuv422P", "yuv411P", "yuv420P", "yuv410P" };
    
    struct video_capability  capability;
    struct video_channel     channel;
    struct video_picture     pict;
    int  j,i,fd;
    char *nick, *attr;
    struct zoran_params     gparams;
//    struct zoran_params     sparams;

    char *myDev = getenv("VIC_DEVICE");
    if (myDev != 0)
    {
	dev = new char *[2];
	dev[0] = new char[strlen(myDev) + 1];
	strcpy(dev[0], myDev);
	dev[1] = 0;
    }

    for (i = 0; dev[i] != NULL; i++) {
	fprintf(stderr,"V4l: trying %s... ",dev[i]);
	if (-1 == (fd = open(dev[i],O_RDONLY))) {
	    perror("open");
	    continue;
	}
	//querying capabilities
	if (-1 == ioctl(fd,VIDIOCGCAP,&capability)) {   
	    perror("ioctl VIDIOCGCAP");
	    close(fd);
	    continue;
	}
              
	if (!(capability.type & VID_TYPE_CAPTURE)) {
	    fprintf(stderr,"device can't capture\n");
	    close(fd);
	    continue;
	}
        if(-1==ioctl(fd,BUZIOC_G_PARAMS,&gparams)){

	   perror("ioctl BUZIOC_G_PARAMS");
           close(fd);
           continue;
        }
         
	fprintf(stderr,"ok, %s\nV4l:   %s; size: %dx%d => %dx%d%s\n",
		capability.name,
		capability.type & VID_TYPE_MONOCHROME ? "mono" : "color",
		capability.minwidth,capability.minheight,
		capability.maxwidth,capability.maxheight,
		capability.type & VID_TYPE_SCALES ? " (scales)" : "");

	attr = new char[512];
	strcpy(attr,"format { jpeg } ");

        if (capability.maxwidth  >768/2 &&
	    capability.maxheight > 576/2) {
	    strcat(attr,"size { small large cif } ");
	} else {
	    strcat(attr,"size { small cif } ");
	}
	
	fprintf(stderr,"V4l:   ports:");
	strcat(attr,"port { ");
	for (j = 0; j < capability.channels; j++) {
	    channel.channel = j;
	    if (-1 == ioctl(fd,VIDIOCGCHAN,&channel)) {
		perror("ioctl VIDIOCGCHAN");
	    } else {
		fprintf(stderr," %s",channel.name);
		strcat(attr,channel.name);
		strcat(attr," ");
	    }
	}
	fprintf(stderr,"\n");
	strcat(attr,"} ");

	if (-1 == ioctl(fd,VIDIOCGPICT,&pict)) {
	    perror("ioctl VIDIOCGPICT");
	}
	fprintf(stderr,"V4l:   depth=%d, palette=%d %s\n",
		pict.depth,
		pict.palette,
		(pict.palette<sizeof(palette_name)/sizeof(char*))?
		palette_name[pict.palette]:"??");

	strcat(attr,"type {auto pal ntsc secam}");
	// strcat(attr,"port { composite svideo }");
	nick = new char[strlen(capability.name)+6];
	sprintf(nick,"v4l- %s",capability.name);
	new BuzDevice(dev[i],nick,attr);

	close(fd);
    }
  
// attr= "format { jpeg } size { small large } port { composite svideo }";
 
}

static BuzDeviceMaker finddevice(devlist);

/*
 * Scan the frame for the end of image tag.
 */
u_char *scan_frame(u_char *frame, int length, int *newLength)
{
    int startOffset;
    
    if (0)
    {
	*newLength = length;
	return frame;
    }

    u_char *ptr;

    for (ptr = frame; ptr < frame + length; ptr++)
    {
	if (ptr[0] == 0xff && ptr[1] == 0xda)
	{
	    int offset = ptr - frame;
//	    printf("found sos at %d\n", offset);


	    *newLength = length - offset;
	    return ptr;
	}
    }
}

u_char *first_field(u_char *frame, int length, int *newLength)
{
    //
    // scan for the EOI (end of image) tag, ff d9
    //

    u_char *ptr;

    for (ptr = frame; ptr < frame + length; ptr++)
    {
	if (ptr[0] == 0xff && ptr[1] == 0xd9)
	{
	    int offset = ptr - frame;
//	    printf("found EOI at %d of %d\n", offset, length);

	    *newLength = offset + 2;
	    return frame;
	}
    }
    *newLength = length;
    return frame;
}


More information about the ag-tech mailing list