From 0bad63d01d0544aa881cb2c2e17b38fe0f8fbdc1 Mon Sep 17 00:00:00 2001 From: dynaflash Date: Thu, 20 May 2010 15:28:27 +0000 Subject: [PATCH] Support for reading VOB subtitle tracks from file inputs initital implementation. - Patch by davidfstr, Nice Work! - Adds support for reading VOB subtitle tracks from file inputs. Tested with: - MKV VOB -> MKV VOB passthru. - MKV VOB -> MKV VOB burned in. VOB subtitle palette moved from per-title to per-track. Discussion leading up to commit can be referenced here: http://forum.handbrake.fr/viewtopic.php?f=4&t=16267 git-svn-id: svn://localhost/HandBrake/trunk@3308 b64f7644-9d1e-0410-96f1-a4d463321fa5 --- libhb/common.c | 68 +++++++++++++++++++++++++++++++++++++++++++++ libhb/common.h | 11 ++++++-- libhb/decavcodec.c | 38 +++++++++++++++++++------ libhb/decvobsub.c | 37 ++++++++++++++++++++++++- libhb/dvd.c | 8 +++--- libhb/dvdnav.c | 8 +++--- libhb/internal.h | 13 ++++++++- libhb/muxmkv.c | 23 +--------------- libhb/stream.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 9 files changed, 238 insertions(+), 49 deletions(-) diff --git a/libhb/common.c b/libhb/common.c index 06fdedde..f7b9ee38 100644 --- a/libhb/common.c +++ b/libhb/common.c @@ -1021,3 +1021,71 @@ char * hb_strdup_printf( char * fmt, ... ) } } +/********************************************************************** + * hb_yuv2rgb + ********************************************************************** + * Converts a YCbCr pixel to an RGB pixel. + * + * This conversion is lossy (due to rounding and clamping). + * + * Algorithm: + * http://en.wikipedia.org/w/index.php?title=YCbCr&oldid=361987695#Technical_details + *********************************************************************/ +int hb_yuv2rgb(int yuv) +{ + double y, Cr, Cb; + int r, g, b; + + y = (yuv >> 16) & 0xff; + Cb = (yuv >> 8) & 0xff; + Cr = (yuv ) & 0xff; + + r = 1.164 * (y - 16) + 2.018 * (Cb - 128); + g = 1.164 * (y - 16) - 0.813 * (Cr - 128) - 0.391 * (Cb - 128); + b = 1.164 * (y - 16) + 1.596 * (Cr - 128); + + r = (r < 0) ? 0 : r; + g = (g < 0) ? 0 : g; + b = (b < 0) ? 0 : b; + + r = (r > 255) ? 255 : r; + g = (g > 255) ? 255 : g; + b = (b > 255) ? 255 : b; + + return (r << 16) | (g << 8) | b; +} + +/********************************************************************** + * hb_rgb2yuv + ********************************************************************** + * Converts an RGB pixel to a YCbCr pixel. + * + * This conversion is lossy (due to rounding and clamping). + * + * Algorithm: + * http://en.wikipedia.org/w/index.php?title=YCbCr&oldid=361987695#Technical_details + *********************************************************************/ +int hb_rgb2yuv(int rgb) +{ + double r, g, b; + int y, Cr, Cb; + + r = (rgb >> 16) & 0xff; + g = (rgb >> 8) & 0xff; + b = (rgb ) & 0xff; + + y = 16. + ( 0.257 * r) + (0.504 * g) + (0.098 * b); + Cb = 128. + (-0.148 * r) - (0.291 * g) + (0.439 * b); + Cr = 128. + ( 0.439 * r) - (0.368 * g) - (0.071 * b); + + y = (y < 0) ? 0 : y; + Cb = (Cb < 0) ? 0 : Cb; + Cr = (Cr < 0) ? 0 : Cr; + + y = (y > 255) ? 255 : y; + Cb = (Cb > 255) ? 255 : Cb; + Cr = (Cr > 255) ? 255 : Cr; + + return (y << 16) | (Cb << 8) | Cr; +} + diff --git a/libhb/common.h b/libhb/common.h index cdb4d9fe..e70550eb 100644 --- a/libhb/common.h +++ b/libhb/common.h @@ -478,7 +478,7 @@ struct hb_chapter_s * > format * - format of the packets the subtitle decoder work-object sends to sub->fifo_raw * - for passthru subtitles, is also the format of the final packets sent to sub->fifo_out - * - PICTURESUB for banded 8-bit YAUV pixels + * - PICTURESUB for banded 8-bit YAUV pixels; see decvobsub.c documentation for more info * - TEXTSUB for UTF-8 text marked up with , , or * - read by the muxers, and by the subtitle burn-in logic in the hb_sync_video work-object * > source @@ -508,6 +508,10 @@ struct hb_subtitle_s char lang[1024]; char iso639_2[4]; uint8_t type; /* Closed Caption, Childrens, Directors etc */ + + // Color lookup table for VOB subtitle tracks. Each entry is in YCbCr format. + // Must be filled out by the demuxer for VOB subtitle tracks. + uint32_t palette[16]; int hits; /* How many hits/occurrences of this subtitle */ int forced_hits; /* How many forced hits in this subtitle */ @@ -577,8 +581,6 @@ struct hb_title_s const char *container_name; int data_rate; - uint32_t palette[16]; - hb_metadata_t *metadata; hb_list_t * list_chapter; @@ -780,4 +782,7 @@ extern void hb_register_error_handler( hb_error_handler_t * handler ); char * hb_strdup_printf( char * fmt, ... ); +int hb_yuv2rgb(int yuv); +int hb_rgb2yuv(int rgb); + #endif diff --git a/libhb/decavcodec.c b/libhb/decavcodec.c index 31c0b751..09f15ecd 100644 --- a/libhb/decavcodec.c +++ b/libhb/decavcodec.c @@ -561,7 +561,20 @@ static void flushDelayQueue( hb_work_private_t *pv ) } } -static int decodeFrame( hb_work_private_t *pv, uint8_t *data, int size ) +/* + * Decodes a video frame from the specified raw packet data ('data', 'size', 'sequence'). + * The output of this function is stored in 'pv->list', which contains a list + * of zero or more decoded packets. + * + * The returned packets are guaranteed to have their timestamps in the correct order, + * even if the original packets decoded by libavcodec have misordered timestamps, + * due to the use of 'packed B-frames'. + * + * Internally the set of decoded packets may be buffered in 'pv->delayq' + * until enough packets have been decoded so that the timestamps can be + * correctly rewritten, if this is necessary. + */ +static int decodeFrame( hb_work_private_t *pv, uint8_t *data, int size, int sequence ) { int got_picture, oldlevel = 0; AVFrame frame; @@ -634,6 +647,7 @@ static int decodeFrame( hb_work_private_t *pv, uint8_t *data, int size ) { buf = copy_frame( pv, &frame ); buf->start = pts; + buf->sequence = sequence; hb_list_add( pv->list, buf ); ++pv->nframes; return got_picture; @@ -681,7 +695,9 @@ static int decodeFrame( hb_work_private_t *pv, uint8_t *data, int size ) } // add the new frame to the delayq & push its timestamp on the heap - pv->delayq[slot] = copy_frame( pv, &frame ); + buf = copy_frame( pv, &frame ); + buf->sequence = sequence; + pv->delayq[slot] = buf; heap_push( &pv->pts_heap, pts ); ++pv->nframes; @@ -690,7 +706,7 @@ static int decodeFrame( hb_work_private_t *pv, uint8_t *data, int size ) return got_picture; } -static void decodeVideo( hb_work_private_t *pv, uint8_t *data, int size, +static void decodeVideo( hb_work_private_t *pv, uint8_t *data, int size, int sequence, int64_t pts, int64_t dts ) { /* @@ -710,20 +726,24 @@ static void decodeVideo( hb_work_private_t *pv, uint8_t *data, int size, if ( pout_len > 0 ) { pv->pts = pv->parser->pts; - decodeFrame( pv, pout, pout_len ); + decodeFrame( pv, pout, pout_len, sequence ); } } while ( pos < size ); /* the stuff above flushed the parser, now flush the decoder */ if ( size <= 0 ) { - while ( decodeFrame( pv, NULL, 0 ) ) + while ( decodeFrame( pv, NULL, 0, sequence ) ) { } flushDelayQueue( pv ); } } +/* + * Removes all packets from 'pv->list', links them together into + * a linked-list, and returns the first packet in the list. + */ static hb_buffer_t *link_buf_list( hb_work_private_t *pv ) { hb_buffer_t *head = hb_list_item( pv->list, 0 ); @@ -863,7 +883,7 @@ static int decavcodecvWork( hb_work_object_t * w, hb_buffer_t ** buf_in, /* if we got an empty buffer signaling end-of-stream send it downstream */ if ( in->size == 0 ) { - decodeVideo( pv, in->data, in->size, pts, dts ); + decodeVideo( pv, in->data, in->size, in->sequence, pts, dts ); hb_list_add( pv->list, in ); *buf_out = link_buf_list( pv ); return HB_WORK_DONE; @@ -900,7 +920,7 @@ static int decavcodecvWork( hb_work_object_t * w, hb_buffer_t ** buf_in, pv->new_chap = in->new_chap; pv->chap_time = pts >= 0? pts : pv->pts_next; } - decodeVideo( pv, in->data, in->size, pts, dts ); + decodeVideo( pv, in->data, in->size, in->sequence, pts, dts ); hb_buffer_close( &in ); *buf_out = link_buf_list( pv ); return HB_WORK_OK; @@ -1115,7 +1135,7 @@ static int decavcodecviWork( hb_work_object_t * w, hb_buffer_t ** buf_in, if ( in->size == 0 ) { /* flush any frames left in the decoder */ - while ( pv->context && decodeFrame( pv, NULL, 0 ) ) + while ( pv->context && decodeFrame( pv, NULL, 0, in->sequence ) ) { } flushDelayQueue( pv ); @@ -1146,7 +1166,7 @@ static int decavcodecviWork( hb_work_object_t * w, hb_buffer_t ** buf_in, pv->chap_time = pts >= 0? pts : pv->pts_next; } prepare_ffmpeg_buffer( in ); - decodeFrame( pv, in->data, in->size ); + decodeFrame( pv, in->data, in->size, in->sequence ); hb_buffer_close( &in ); *buf_out = link_buf_list( pv ); return HB_WORK_OK; diff --git a/libhb/decvobsub.c b/libhb/decvobsub.c index e180b232..3b5177ca 100644 --- a/libhb/decvobsub.c +++ b/libhb/decvobsub.c @@ -4,6 +4,26 @@ Homepage: . It may be used under the terms of the GNU General Public License. */ +/* + * Decoder for DVD bitmap subtitles, also known as "VOB subtitles" within the HandBrake source code. + * + * Input format of the subtitle packets is described here: + * http://sam.zoy.org/writings/dvd/subtitles/ + * + * An auxiliary input is the color palette lookup table, in 'subtitle->palette'. + * The demuxer implementation must fill out this table appropriately. + * - In the case of a true DVD input, the palette is read from the IFO file. + * - In the case of an MKV file input, the palette is read from the codec private data of the subtitle track. + * + * Output format of this decoder is PICTURESUB, which is: + * struct PictureSubPacket { + * uint8_t lum[pixelCount]; + * uint8_t alpha[pixelCount]; + * uint8_t chromaU[pixelCount]; + * uint8_t chromaV[pixelCount]; + * } + */ + #include "hb.h" struct hb_work_private_s @@ -42,6 +62,21 @@ int decsubInit( hb_work_object_t * w, hb_job_t * job ) pv->job = job; pv->pts = -1; + + // Warn if the input color palette is empty + int paletteEmpty = 1; + int i; + for (i=0; i<16; i++) + { + if (w->subtitle->palette[i]) + { + paletteEmpty = 0; + break; + } + } + if (paletteEmpty) { + hb_log( "decvobsub: input color palette is empty; not demuxed properly?" ); + } return 0; } @@ -262,7 +297,7 @@ static void ParseControls( hb_work_object_t * w ) * work, but I get the right colours by doing * no conversion. */ - uint32_t color = title->palette[colors[j]]; + uint32_t color = w->subtitle->palette[colors[j]]; uint8_t Cr, Cb, y; y = (color>>16) & 0xff; Cr = (color>>8) & 0xff; diff --git a/libhb/dvd.c b/libhb/dvd.c index e51fd95b..ee10dd50 100644 --- a/libhb/dvd.c +++ b/libhb/dvd.c @@ -443,10 +443,6 @@ static hb_title_t * hb_dvdread_title_scan( hb_dvd_t * e, int t ) hb_list_add( title->list_audio, audio ); } - memcpy( title->palette, - vts->vts_pgcit->pgci_srp[pgc_id-1].pgc->palette, - 16 * sizeof( uint32_t ) ); - /* Check for subtitles */ for( i = 0; i < vts->vtsi_mat->nr_of_vts_subp_streams; i++ ) { @@ -502,6 +498,10 @@ static hb_title_t * hb_dvdread_title_scan( hb_dvd_t * e, int t ) subtitle->config.dest = RENDERSUB; // By default render (burn-in) the VOBSUB. subtitle->type = lang_extension; + + memcpy( subtitle->palette, + vts->vts_pgcit->pgci_srp[pgc_id-1].pgc->palette, + 16 * sizeof( uint32_t ) ); switch( lang_extension ) { diff --git a/libhb/dvdnav.c b/libhb/dvdnav.c index 84b40030..67e7b8b1 100644 --- a/libhb/dvdnav.c +++ b/libhb/dvdnav.c @@ -591,10 +591,6 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t ) hb_list_add( title->list_audio, audio ); } - memcpy( title->palette, - ifo->vts_pgcit->pgci_srp[title_pgcn-1].pgc->palette, - 16 * sizeof( uint32_t ) ); - /* Check for subtitles */ for( i = 0; i < ifo->vtsi_mat->nr_of_vts_subp_streams; i++ ) { @@ -650,6 +646,10 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t ) subtitle->config.dest = RENDERSUB; // By default render (burn-in) the VOBSUB. subtitle->type = lang_extension; + + memcpy( subtitle->palette, + ifo->vts_pgcit->pgci_srp[title_pgcn-1].pgc->palette, + 16 * sizeof( uint32_t ) ); switch( lang_extension ) { diff --git a/libhb/internal.h b/libhb/internal.h index dd217507..f7092f10 100644 --- a/libhb/internal.h +++ b/libhb/internal.h @@ -51,12 +51,23 @@ struct hb_buffer_s uint8_t * data; // packet data int cur; // used internally by packet lists (hb_list_t) + /* + * Corresponds to the order that this packet was read from the demuxer. + * + * It is important that video decoder work-objects pass this value through + * from their input packets to the output packets they generate. Otherwise + * RENDERSUB subtitles (especially VOB subtitles) will break. + * + * Subtitle decoder work-objects that output a renderable subtitle + * format (ex: PICTURESUB) must also be careful to pass the sequence number + * through for the same reason. + */ int64_t sequence; int id; // ID of the track that the packet comes from int64_t start; // Video and subtitle packets: start time of frame/subtitle int64_t stop; // Video and subtitle packets: stop time of frame/subtitle - int new_chap; // Video packets: ??? + int new_chap; // Video packets: if non-zero, is the index of the chapter whose boundary was crossed #define HB_FRAME_IDR 0x01 #define HB_FRAME_I 0x02 diff --git a/libhb/muxmkv.c b/libhb/muxmkv.c index d30e7c01..8fcd30b8 100644 --- a/libhb/muxmkv.c +++ b/libhb/muxmkv.c @@ -34,27 +34,6 @@ struct hb_mux_data_s int sub_format; }; -static int yuv2rgb(int yuv) -{ - double y, Cr, Cb; - int r, g, b; - - y = (yuv >> 16) & 0xff; - Cb = (yuv >> 8) & 0xff; - Cr = (yuv ) & 0xff; - - r = 1.164 * (y - 16) + 2.018 * (Cb - 128); - g = 1.164 * (y - 16) - 0.813 * (Cr - 128) - 0.391 * (Cb - 128); - b = 1.164 * (y - 16) + 1.596 * (Cr - 128); - r = (r < 0) ? 0 : r; - g = (g < 0) ? 0 : g; - b = (b < 0) ? 0 : b; - r = (r > 255) ? 255 : r; - g = (g > 255) ? 255 : g; - b = (b > 255) ? 255 : b; - return (r << 16) | (g << 8) | b; -} - /********************************************************************** * MKVInit ********************************************************************** @@ -296,7 +275,7 @@ static int MKVInit( hb_mux_object_t * m ) case PICTURESUB: track->codecID = MK_SUBTITLE_VOBSUB; for (j = 0; j < 16; j++) - rgb[j] = yuv2rgb(title->palette[j]); + rgb[j] = hb_yuv2rgb(subtitle->palette[j]); len = snprintf(subidx, 2048, subidx_fmt, title->width, title->height, 0, 0, "OFF", diff --git a/libhb/stream.c b/libhb/stream.c index 4b1bd98e..95bf719e 100644 --- a/libhb/stream.c +++ b/libhb/stream.c @@ -2831,6 +2831,73 @@ static void add_ffmpeg_audio( hb_title_t *title, hb_stream_t *stream, int id ) } } +/* + * Parses the 'subtitle->palette' information from the specific VOB subtitle track's private data. + * Returns 0 if successful or 1 if parsing failed or was incomplete. + * + * Format: + * MkvVobSubtitlePrivateData = ( Line )* + * Line = FieldName ':' ' ' FieldValue '\n' + * FieldName = [^:]+ + * FieldValue = [^\n]+ + * + * The line of interest is: + * PaletteLine = "palette" ':' ' ' RRGGBB ( ',' ' ' RRGGBB )* + * + * More information on the format at: + * http://www.matroska.org/technical/specs/subtitles/images.html + */ +static int ffmpeg_parse_vobsub_extradata( AVCodecContext *codec, hb_subtitle_t *subtitle ) +{ + if ( codec->extradata_size <= 0 ) + return 1; + + // lines = (string) codec->extradata; + char *lines = malloc( codec->extradata_size + 1 ); + if ( lines == NULL ) + return 1; + memcpy( lines, codec->extradata, codec->extradata_size ); + lines[codec->extradata_size] = '\0'; + + uint32_t rgb[16]; + int gotPalette = 0; + + char *curLine, *curLine_parserData; + for ( curLine = strtok_r( lines, "\n", &curLine_parserData ); + curLine; + curLine = strtok_r( NULL, "\n", &curLine_parserData ) ) + { + int numElementsRead = sscanf(curLine, "palette: " + "%06x, %06x, %06x, %06x, " + "%06x, %06x, %06x, %06x, " + "%06x, %06x, %06x, %06x, " + "%06x, %06x, %06x, %06x", + &rgb[0], &rgb[1], &rgb[2], &rgb[3], + &rgb[4], &rgb[5], &rgb[6], &rgb[7], + &rgb[8], &rgb[9], &rgb[10], &rgb[11], + &rgb[12], &rgb[13], &rgb[14], &rgb[15]); + + if (numElementsRead == 16) { + gotPalette = 1; + break; + } + } + + free( lines ); + + if ( gotPalette ) + { + int i; + for (i=0; i<16; i++) + subtitle->palette[i] = hb_rgb2yuv(rgb[i]); + return 0; + } + else + { + return 1; + } +} + static void add_ffmpeg_subtitle( hb_title_t *title, hb_stream_t *stream, int id ) { AVStream *st = stream->ffmpeg_ic->streams[id]; @@ -2842,14 +2909,14 @@ static void add_ffmpeg_subtitle( hb_title_t *title, hb_stream_t *stream, int id switch ( codec->codec_id ) { - // TODO(davidfstr): get universal VOB sub input working - /* case CODEC_ID_DVD_SUBTITLE: subtitle->format = PICTURESUB; subtitle->source = VOBSUB; subtitle->config.dest = RENDERSUB; // By default render (burn-in) the VOBSUB. + if ( ffmpeg_parse_vobsub_extradata( codec, subtitle ) ) + hb_log( "add_ffmpeg_subtitle: malformed extradata for VOB subtitle track; " + "subtitle colors likely to be wrong" ); break; - */ case CODEC_ID_TEXT: subtitle->format = TEXTSUB; subtitle->source = UTF8SUB; @@ -2869,7 +2936,7 @@ static void add_ffmpeg_subtitle( hb_title_t *title, hb_stream_t *stream, int id break; */ default: - hb_log("add_ffmpeg_subtitle: unknown subtitle stream type: 0x%x", (int) codec->codec_id); + hb_log( "add_ffmpeg_subtitle: unknown subtitle stream type: 0x%x", (int) codec->codec_id ); free(subtitle); return; } @@ -3132,10 +3199,14 @@ static int ffmpeg_read( hb_stream_t *stream, hb_buffer_t *buf ) /* * Fill out buf->stop for subtitle packets * - * libavcodec's MKV demuxer stores the duration of UTF-8 (TEXT) subtitles + * libavcodec's MKV demuxer stores the duration of UTF-8 subtitles (CODEC_ID_TEXT) * in the 'convergence_duration' field for some reason. * * Other subtitles' durations are stored in the 'duration' field. + * + * VOB subtitles (CODEC_ID_DVD_SUBTITLE) do not have their duration stored in + * either field. This is not a problem because the VOB decoder can extract this + * information from the packet payload itself. */ enum CodecID ffmpeg_pkt_codec = stream->ffmpeg_ic->streams[stream->ffmpeg_pkt->stream_index]->codec->codec_id; if ( ffmpeg_pkt_codec == CODEC_ID_TEXT ) { -- 2.11.0