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