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, ¶m, 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, ¶m, 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(¶m, frame_buf /*&buffer*/); + init(¶m, &frame_bufs[0] /*&buffer*/); + if(conceal_errframes) + init(¶m, &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