[media] gspca - sn9c20x: Add automatic JPEG compression mechanism
authorJean-François Moine <moinejf@free.fr>
Mon, 19 Mar 2012 07:47:24 +0000 (04:47 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Tue, 20 Mar 2012 01:31:46 +0000 (22:31 -0300)
The JPEG compression may be adjusted from the packet fill ratio and from
the flag 'USB FIFO full' returned in each frame.
The code is adapted from the one in gspca sonixj and uses a workqueue.

Signed-off-by: Jean-François Moine <moinejf@free.fr>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
drivers/media/video/gspca/sn9c20x.c

index 26ee3c9..2a670ea 100644 (file)
@@ -89,6 +89,12 @@ struct sd {
 
        struct gspca_ctrl ctrls[NCTRLS];
 
+       struct work_struct work;
+       struct workqueue_struct *work_thread;
+
+       u32 pktsz;                      /* (used by pkt_scan) */
+       u16 npkt;
+       s8 nchg;
        u8 fmt;                         /* (used for JPEG QTAB update */
 
 #define MIN_AVG_LUM 80
@@ -108,6 +114,8 @@ struct sd {
        u8 flags;
 };
 
+static void qual_upd(struct work_struct *work);
+
 struct i2c_reg_u8 {
        u8 reg;
        u8 val;
@@ -1842,6 +1850,7 @@ static int sd_config(struct gspca_dev *gspca_dev,
 
        gspca_dev->cam.ctrls = sd->ctrls;
 
+       INIT_WORK(&sd->work, qual_upd);
 
        return 0;
 }
@@ -2101,6 +2110,15 @@ static int sd_start(struct gspca_dev *gspca_dev)
 
        reg_r(gspca_dev, 0x1061, 1);
        reg_w1(gspca_dev, 0x1061, gspca_dev->usb_buf[0] | 0x02);
+
+       /* if JPEG, prepare the compression quality update */
+       if (mode & MODE_JPEG) {
+               sd->pktsz = sd->npkt = 0;
+               sd->nchg = 0;
+               sd->work_thread =
+                       create_singlethread_workqueue(KBUILD_MODNAME);
+       }
+
        return gspca_dev->usb_err;
 }
 
@@ -2112,6 +2130,20 @@ static void sd_stopN(struct gspca_dev *gspca_dev)
        reg_w1(gspca_dev, 0x1061, gspca_dev->usb_buf[0] & ~0x02);
 }
 
+/* called on streamoff with alt==0 and on disconnect */
+/* the usb_lock is held at entry - restore on exit */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+       struct sd *sd = (struct sd *) gspca_dev;
+
+       if (sd->work_thread != NULL) {
+               mutex_unlock(&gspca_dev->usb_lock);
+               destroy_workqueue(sd->work_thread);
+               mutex_lock(&gspca_dev->usb_lock);
+               sd->work_thread = NULL;
+       }
+}
+
 static void do_autoexposure(struct gspca_dev *gspca_dev, u16 avg_lum)
 {
        struct sd *sd = (struct sd *) gspca_dev;
@@ -2195,6 +2227,19 @@ static void sd_dqcallback(struct gspca_dev *gspca_dev)
                do_autoexposure(gspca_dev, avg_lum);
 }
 
+/* JPEG quality update */
+/* This function is executed from a work queue. */
+static void qual_upd(struct work_struct *work)
+{
+       struct sd *sd = container_of(work, struct sd, work);
+       struct gspca_dev *gspca_dev = &sd->gspca_dev;
+
+       mutex_lock(&gspca_dev->usb_lock);
+       PDEBUG(D_STREAM, "qual_upd %d%%", sd->ctrls[QUALITY].val);
+       set_quality(gspca_dev);
+       mutex_unlock(&gspca_dev->usb_lock);
+}
+
 #if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE)
 static int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
                        u8 *data,               /* interrupt packet */
@@ -2213,6 +2258,50 @@ static int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
 }
 #endif
 
+/* check the JPEG compression */
+static void transfer_check(struct gspca_dev *gspca_dev,
+                       u8 *data)
+{
+       struct sd *sd = (struct sd *) gspca_dev;
+       int new_qual, r;
+
+       new_qual = 0;
+
+       /* if USB error, discard the frame and decrease the quality */
+       if (data[6] & 0x08) {                           /* USB FIFO full */
+               gspca_dev->last_packet_type = DISCARD_PACKET;
+               new_qual = -5;
+       } else {
+
+               /* else, compute the filling rate and a new JPEG quality */
+               r = (sd->pktsz * 100) /
+                       (sd->npkt *
+                               gspca_dev->urb[0]->iso_frame_desc[0].length);
+               if (r >= 85)
+                       new_qual = -3;
+               else if (r < 75)
+                       new_qual = 2;
+       }
+       if (new_qual != 0) {
+               sd->nchg += new_qual;
+               if (sd->nchg < -6 || sd->nchg >= 12) {
+                       sd->nchg = 0;
+                       new_qual += sd->ctrls[QUALITY].val;
+                       if (new_qual < QUALITY_MIN)
+                               new_qual = QUALITY_MIN;
+                       else if (new_qual > QUALITY_MAX)
+                               new_qual = QUALITY_MAX;
+                       if (new_qual != sd->ctrls[QUALITY].val) {
+                               sd->ctrls[QUALITY].val = new_qual;
+                               queue_work(sd->work_thread, &sd->work);
+                       }
+               }
+       } else {
+               sd->nchg = 0;
+       }
+       sd->pktsz = sd->npkt = 0;
+}
+
 static void sd_pkt_scan(struct gspca_dev *gspca_dev,
                        u8 *data,                       /* isoc packet */
                        int len)                        /* iso packet length */
@@ -2248,6 +2337,11 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev,
                            (data[33] << 10);
                avg_lum >>= 9;
                atomic_set(&sd->avg_lum, avg_lum);
+
+               if (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv
+                               & MODE_JPEG)
+                       transfer_check(gspca_dev, data);
+
                gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
                len -= 64;
                if (len == 0)
@@ -2266,6 +2360,12 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev,
                                data, len);
                }
        } else {
+               /* if JPEG, count the packets and their size */
+               if (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv
+                               & MODE_JPEG) {
+                       sd->npkt++;
+                       sd->pktsz += len;
+               }
                gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
        }
 }
@@ -2280,6 +2380,7 @@ static const struct sd_desc sd_desc = {
        .isoc_init = sd_isoc_init,
        .start = sd_start,
        .stopN = sd_stopN,
+       .stop0 = sd_stop0,
        .pkt_scan = sd_pkt_scan,
 #if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE)
        .int_pkt_scan = sd_int_pkt_scan,