It seems that if I turn up the quality (-q) option to lavrec too high
while using a zoran mjpeg card, I get occaisional bad frames, which cause 
warnings like this when played back with lavplay or lav2yuv:
        "Corrupt JPEG data: 30 extraneous bytes before marker 0xd9"
When this happens part of a field contains some kind of garbage.

The right thing to do of course is reduce -q or fine-tune my pci and disk 
bandwidth and then re-capture.  
But I've got some recordings of a live performance with this affliction.
While I have an SVHS tape backup, the mjpeg looks so much better than the
tape except for those few frames.

I've whipped up a patch for lav2yuv that adds an option to perform the
simplest, crudest error-concealment to these damaged frames: repeat
the previous frame.  The result looks way better on my test recording.


lav2yuv with no options behaves as before.
"lav2yuv -c" does the corrupt-frame concealment.

No other mjpegutils/lavtools programs should be affected by this, although 
private non-lavtools programs compiled against these libraries might 
notice the internal API change:

jpegutils.c/decode_jpeg_raw() returns an error code which was ignored 
everywhere _except_ by lav_common.c/readframe().

readframe() is only called from lav2yuv.c, which used to ignore its return 
code.

The patch changes both decode_jpeg_raw() and readframe() from returning
success/fatal-error to returning a ternary result: success, fatal error,
or corrupt-frame-warning.  I added a comment near each definition that
explains the return codes for each.


Possible future work for the ambitious (not covered in this patch):
- don't repeat a frame more than once if multiple errored frames occur 
back-to-back
- see if the DV decoding supported by lav_common has similar error status 
that could be used the same way
- turn this into a utility or library so its available in other programs
besides lav2yuv
- figure out from jpeglib how which part of the frame was corrupt, and 
only replace the damaged portion.
- generalize error-concealment to other ways of detecting bad parts of 
frames...  Analog-tape dropout correction? DVB?


friendly mjpegtools developers: feel free to review and commit this if 
desired.  comments welcome.

Steve










--- mjpegtools-1.6.2/lavtools/jpegutils.c       2004-02-02 17:57:54.000000000 
-0500
+++ mjpegtools-1.6.2-laverr/lavtools/jpegutils.c        2004-11-28 
23:53:53.871991185 -0500
@@ -276,6 +276,10 @@
 struct my_error_mgr {
    struct jpeg_error_mgr pub;   /* "public" fields */
    jmp_buf setjmp_buffer;       /* for return to caller */
+
+       /* original emit_message method */
+   JMETHOD(void, original_emit_message, (j_common_ptr cinfo, int msg_level));
+   int warning_seen;           /* was a corrupt-data warning seen */
 };
 
 static void my_error_exit (j_common_ptr cinfo)
@@ -291,6 +295,18 @@
    longjmp (myerr->setjmp_buffer, 1);
 }
 
+static void my_emit_message (j_common_ptr cinfo, int msg_level)
+{
+   /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
+   struct my_error_mgr *myerr = (struct my_error_mgr *) cinfo->err;
+
+   if(msg_level < 0)
+          myerr->warning_seen = 1;
+
+   /* call original emit_message() */
+   (myerr->original_emit_message)(cinfo, msg_level);
+}
+
 #define MAX_LUMA_WIDTH   4096
 #define MAX_CHROMA_WIDTH 2048
 
@@ -432,6 +448,13 @@
  *                  2: Interlaced, Bottom field first
  * ctype            Chroma format for decompression.
  *                  Currently always 420 and hence ignored.
+ *
+ * returns:
+ *     -1 on fatal error
+ *     0 on success
+ *     1 if jpeg lib threw a "corrupt jpeg data" warning.  
+ *             in this case, "a damaged output image is likely."
+ *     
  */
 
 int decode_jpeg_raw (unsigned char *jpeg_data, int len,
@@ -459,6 +482,10 @@
    /* We set up the normal JPEG error routines, then override error_exit. */
    dinfo.err = jpeg_std_error (&jerr.pub);
    jerr.pub.error_exit = my_error_exit;
+   /* also hook the emit_message routine to note corrupt-data warnings */
+   jerr.original_emit_message = jerr.pub.emit_message;
+   jerr.pub.emit_message = my_emit_message;
+   jerr.warning_seen = 0;
 
    /* Establish the setjmp return context for my_error_exit to use. */
    if (setjmp (jerr.setjmp_buffer)) {
@@ -689,7 +716,10 @@
      }
 
    jpeg_destroy_decompress (&dinfo);
-   return 0;
+   if(jerr.warning_seen)
+          return 1;
+   else
+          return 0;
 
  ERR_EXIT:
    jpeg_destroy_decompress (&dinfo);
--- mjpegtools-1.6.2/lavtools/lav_common.c      2004-01-15 14:20:36.000000000 
-0500
+++ mjpegtools-1.6.2-laverr/lavtools/lav_common.c       2004-11-28 
23:56:55.055917175 -0500
@@ -263,8 +263,15 @@
 }
 
 
-
-
+/*
+ * readframe - read jpeg or dv frame into yuv buffer
+ *
+ * returns:
+ *     0   success
+ *     1   fatal error
+ *     2   corrupt data encountered; 
+ *             decoding can continue, but this frame may be damaged 
+ */
 int readframe(int numframe, 
              uint8_t *frame[],
              LavParam *param,
@@ -272,6 +279,8 @@
 {
   int len, i, res, data_format;
   uint8_t *frame_tmp;
+  int warn;
+  warn = 0;
 
   if (MAX_JPEG_LEN < el.max_frame_size) {
     mjpeg_error_exit1( "Max size of JPEG frame = %ld: too big",
@@ -362,6 +371,10 @@
                          param->output_width, param->output_height,
                          frame[0], frame[1], frame[2]);
     
+    if(res == 1) {
+           warn = 1;
+           res = 0;
+    }
   }
   
   
@@ -380,8 +393,11 @@
       frame[2][i] = 0x80;
     }
   }
-  return 0;
-  
+
+  if(warn)
+         return 2;
+  else
+         return 0;
 }
 
 
--- mjpegtools-1.6.2/lavtools/lav2yuv.c 2003-12-20 12:33:38.000000000 -0500
+++ mjpegtools-1.6.2-laverr/lavtools/lav2yuv.c  2004-11-28 23:30:07.671743355 
-0500
@@ -59,7 +59,8 @@
    "              (default:  read from DV files, or assume 4:3 for MJPEG)\n"
    "   -o num     Frame offset - skip num frames in the beginning\n"
    "              if num is negative, all but the last num frames are 
skipped\n"
-   "   -f num     Only num frames are written to stdout (0 means all 
frames)\n",
+   "   -f num     Only num frames are written to stdout (0 means all frames)\n"
+   "   -c         Conceal frames with corrupt jpeg data by repeating previous 
frame\n",
   str);
    exit(0);
 }
@@ -71,7 +72,10 @@
 static int scene_start;
 
 LavParam param;
-uint8_t *frame_buf[3];
+uint8_t *frame_bufs[6];
+
+static int conceal_errframes;
+static int altbuf;
 
 void streamout(void)
 {
@@ -129,7 +133,29 @@
 
        for (framenum = param.offset; framenum < (param.offset + param.frames); 
++framenum) 
        {
-         readframe(framenum, frame_buf, &param, el);
+               int rf;
+               uint8_t **read_buf;
+               uint8_t **frame_buf;
+               if(conceal_errframes)
+                       read_buf = &frame_bufs[4 * (altbuf ^ 1)];
+               else
+                       read_buf = &frame_bufs[0];
+               
+               rf = readframe(framenum, read_buf, &param, el);
+               if(conceal_errframes) {
+                       if(rf == 2) {  // corrupt frame; repeat previous
+                               mjpeg_warn("corrupt jpeg data in frame %d; 
repeating previous frame.", framenum);
+                               frame_buf = &frame_bufs[4 * altbuf];
+                       } else {  // use new frame
+                               frame_buf = read_buf;
+                               altbuf ^= 1;
+                       }
+               } else {
+                       if(rf == 2)
+                               mjpeg_warn("corrupt jpeg data in frame %d", 
framenum);
+                       frame_buf = &frame_bufs[0];
+               }
+               
                if (param.scenefile) 
                {
                        lum_mean =
@@ -198,7 +224,7 @@
        param.sar = y4m_sar_UNKNOWN;
        param.dar = y4m_dar_4_3;
 
-       while ((n = getopt(argc, argv, "mYv:S:T:D:o:f:P:A:")) != EOF) {
+       while ((n = getopt(argc, argv, "mcYv:S:T:D:o:f:P:A:")) != EOF) {
                switch (n) {
 
                case 'v':
@@ -211,6 +237,9 @@
                case 'm':
                        param.mono = 1;
                        break;
+               case 'c':
+                       conceal_errframes = 1;
+                       break;
                case 'S':
                        param.scenefile = optarg;
                        break;
@@ -276,8 +305,9 @@
 
        param.interlace = el.video_inter;
 
-
-       init(&param, frame_buf /*&buffer*/);
+       init(&param, &frame_bufs[0] /*&buffer*/);
+       if(conceal_errframes)
+               init(&param, &frame_bufs[4] /*&buffer*/);
 
 #ifdef HAVE_LIBDV
        lav_init_dv_decoder();
--- mjpegtools-1.6.2/docs/lav2yuv.1     2003-11-14 22:36:17.000000000 -0500
+++ mjpegtools-1.6.2-laverr/docs/lav2yuv.1      2004-11-28 23:35:35.483034491 
-0500
@@ -33,6 +33,10 @@
 .BI \-m 
 Force mono\-chrome
 .TP 5
+.BI \-c 
+Conceal frames with libjpeg corrupt\-data warnings by repeating the
+preceeding good frame.
+.TP 5
 .BI \-S " list.el"
 Output a scene list with scene detection
 .TP 5

Reply via email to