[FFmpeg-devel] [PATCH] [WIP] lavf: VobSub demuxer.
Nicolas George
nicolas.george at normalesup.org
Fri Sep 7 22:14:10 CEST 2012
Le primidi 21 fructidor, an CCXX, Clément Bœsch a écrit :
> TODO: raise AVPacket with the PTS from the .IDX (+honor delay and related settings)
> TODO: seek?
> TODO: lavf minor bump
> TODO: Changelog entry
> ---
>
> Hey,
>
> This is the VobSub demuxer I've try to write so far. It kind of works, but the
> timestamp from the .IDX are *not* used. It seems the .SUB actually contains
> everything needed to time it properly, but TS look somehow fishy (even though
> they seem to appear at the correct time at playback).
I suspect there are tools to adjust the timing of vobsub files that work
only with the index file. I admit I myself sometimes tweaked a few things
with vim, and obviously I did not alter the timestamps in the sub file. If
the timestamps in the index file were ignored, this would have been rather
annoying.
Also, I would not be surprised if some DVD rip tools would just take the
subtitle packet from the VOB file: in that case, the timestamp in the packet
is relative to the last timestamp reset in the VOB file, while the timestamp
in the index is built using the navigation information.
> I'm still wondering how I am supposed to override the index entries built by
> the MPEG demuxer with one I write myself. Currently, the index is reset after
> the MPEG 1st pass (see around line 90), and then rebuild within the stream
> using the timestamp lines from the .IDX (see around line 160). Unfortunately,
> read_packet() from the MPEG demuxer doesn't use that index to set the pkt->pts
> so the PTS are instead based on the content of the PES packet.
I suspect the solution would be to make most of the work with the vobsub
demuxer, and only use the underlying MPEG demuxer to extract the payload. I
suspect the simplest way to do that would be to use avio_alloc_context with
a custom read_packet function that will read in the sub file exactly at the
offset specified in the index.
> Note: The number of timestamp entries in the .IDX seems to match (minus 1) the
> number of entries detected by the MPEG demuxer with my test sample.
>
> Anyway, if anyone has any idea what extra timing/offset information the .IDX
> really adds, and how I am suppose to exploit them, any suggestion is welcome.
>
> Thanks,
>
> ---
> doc/general.texi | 1 +
> libavformat/Makefile | 1 +
> libavformat/allformats.c | 1 +
> libavformat/vobsubdec.c | 265 +++++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 268 insertions(+)
> create mode 100644 libavformat/vobsubdec.c
>
> diff --git a/doc/general.texi b/doc/general.texi
> index ef9cf72..0c98c4b 100644
> --- a/doc/general.texi
> +++ b/doc/general.texi
> @@ -899,6 +899,7 @@ performance on systems without hardware floating point support).
> @item SAMI @tab @tab X @tab @tab X
> @item SubRip (SRT) @tab X @tab X @tab X @tab X
> @item SubViewer @tab @tab X @tab @tab X
> + at item VobSub (IDX+SUB) @tab @tab X @tab @tab X
> @item 3GPP Timed Text @tab @tab @tab X @tab X
> @item XSUB @tab @tab @tab X @tab X
> @end multitable
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 72f9c22..81f3287 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -342,6 +342,7 @@ OBJS-$(CONFIG_VC1_DEMUXER) += rawdec.o
> OBJS-$(CONFIG_VC1T_DEMUXER) += vc1test.o
> OBJS-$(CONFIG_VC1T_MUXER) += vc1testenc.o
> OBJS-$(CONFIG_VMD_DEMUXER) += sierravmd.o
> +OBJS-$(CONFIG_VOBSUB_DEMUXER) += vobsubdec.o
> OBJS-$(CONFIG_VOC_DEMUXER) += vocdec.o voc.o
> OBJS-$(CONFIG_VOC_MUXER) += vocenc.o voc.o
> OBJS-$(CONFIG_VQF_DEMUXER) += vqf.o
> diff --git a/libavformat/allformats.c b/libavformat/allformats.c
> index 9df6280..193bdcb 100644
> --- a/libavformat/allformats.c
> +++ b/libavformat/allformats.c
> @@ -244,6 +244,7 @@ void av_register_all(void)
> REGISTER_DEMUXER (VC1, vc1);
> REGISTER_MUXDEMUX (VC1T, vc1t);
> REGISTER_DEMUXER (VMD, vmd);
> + REGISTER_DEMUXER (VOBSUB, vobsub);
> REGISTER_MUXDEMUX (VOC, voc);
> REGISTER_DEMUXER (VQF, vqf);
> REGISTER_DEMUXER (W64, w64);
> diff --git a/libavformat/vobsubdec.c b/libavformat/vobsubdec.c
> new file mode 100644
> index 0000000..572c615
> --- /dev/null
> +++ b/libavformat/vobsubdec.c
> @@ -0,0 +1,265 @@
> +/*
> + * Copyright (c) 2012 Clément Bœsch
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +/**
> + * @file
> + * VobSub subtitle demuxer
> + * @see http://wiki.multimedia.cx/index.php?title=VOBsub
> + */
> +
> +#include "avformat.h"
> +#include "internal.h"
> +#include "subtitles.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/bprint.h"
> +#include "libavutil/intreadwrite.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/timestamp.h"
> +
> +typedef struct {
> + const AVClass *class;
> + AVFormatContext *sub_ctx;
> + int cur_st_id;
> + int strip_header;
> +} VobSubContext;
> +
> +#define REF_STRING "# VobSub index file,"
> +
> +static int vobsub_probe(AVProbeData *p)
> +{
> + if (!strncmp(p->buf, REF_STRING, sizeof(REF_STRING) - 1))
> + return AVPROBE_SCORE_MAX;
> + return 0;
> +}
> +
> +static int vobsub_read_header(AVFormatContext *s)
> +{
> + int i, ret, has_subtitle_stream = 0, header_parsed = 0;
> + VobSubContext *vobsub = s->priv_data;
> + char *sub_name = av_strdup(s->filename);
> + size_t fname_len = strlen(sub_name);
> + char *ext = sub_name - 3 + fname_len;
> + AVInputFormat *mpeg_format = av_find_input_format("mpeg");
> + AVBPrint header;
I usually find the style where you declare a lot of stuff with non-trivial
values long before you actually use them rather harder to follow.
> +
> + /* Open the related .SUB bitmaps file */
> + if (!mpeg_format) {
> + av_log(s, AV_LOG_ERROR, "Unable to find MPEG demuxer\n");
> + return AVERROR(EINVAL);
AVERROR_DEMUXER_NOT_FOUND? Or you could even declare
"extern AVInputFormat ff_mpeg_demuxer" and access it directly.
> + }
> + if (fname_len < 4 || *(ext - 1) != '.') {
> + av_log(s, AV_LOG_ERROR, "The input index filename is to short "
too short
> + "to guess the associated .SUB file\n");
> + return AVERROR_INVALIDDATA;
> + }
> + memcpy(ext, !strncmp(ext, "IDX", 3) ? "SUB" : "sub", 3);
> + ret = avformat_open_input(&vobsub->sub_ctx, sub_name, mpeg_format, NULL);
> + if (ret < 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to open %s as MPEG subtitles\n", sub_name);
> + return ret;
> + }
> +
> + /* The .SUB contains enough information to guess the number of subtitles
> + * stream, so we fill the streams in the format context. The .idx file will
> + * help adding metadata. */
> + ret = avformat_find_stream_info(vobsub->sub_ctx, NULL);
> + if (ret < 0)
> + return ret;
> +
> + /* Reset the index entries set by the MPEG demuxer since we'll reconstruct
> + * that index based on the content of the .IDX. Also make sure there is at
> + * least one DVD subtitle stream. */
> + for (i = 0; i < vobsub->sub_ctx->nb_streams; i++) {
> + AVStream *st = vobsub->sub_ctx->streams[i];
> + if (st->codec->codec_id == AV_CODEC_ID_DVD_SUBTITLE) {
> + //av_log(0,0,"MPEG stream[%d] nb index entries = %d\n", i, st->nb_index_entries);
> + st->nb_index_entries = 0;
> + has_subtitle_stream = 1;
> + }
> + }
> + if (!has_subtitle_stream) {
> + av_log(s, AV_LOG_ERROR, "No DVD subtitle stream found in %s\n",
> + vobsub->sub_ctx->filename);
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* Extract .IDX header to avctx->extradata, add some info in the AVStreams
> + * and build indexes */
> + av_bprint_init(&header, FF_INPUT_BUFFER_PADDING_SIZE, AV_BPRINT_SIZE_UNLIMITED);
I do not think FF_INPUT_BUFFER_PADDING_SIZE will do any good here. It is way
below the initial size of a standard bprint anyway, and if you want padding
in the buffer at the end, you have to add it on top of the data.
> + while (!url_feof(s->pb)) {
> + char line[2048];
> + int len = ff_get_line(s->pb, line, sizeof(line));
> +
> + if (!len)
> + break;
> + if (vobsub->strip_header && (line[0] == '#' ||
> + line[0] == '\n' || (line[0] == '\r' && line[1] == '\n')))
> + continue;
> +
> + line[strcspn(line, "\r\n")] = 0;
> +
> + if (!strncmp(line, "id:", 3)) {
> + /* New stream */
> +
> + AVStream *st;
> +#define IDBS 63
> + char id[IDBS+1] = {0};
> + int n = sscanf(line, "id: %" AV_STRINGIFY(IDBS) "[^,], index: %u",
> + id, &vobsub->cur_st_id);
> + if (n != 2) {
> + av_log(s, AV_LOG_WARNING, "Unable to parse index line '%s', "
> + "assuming 'id: und, index: 0'\n", line);
> + strcpy(id, "und");
> + vobsub->cur_st_id = 0;
> + }
> +
> + if (vobsub->cur_st_id >= vobsub->sub_ctx->nb_streams) {
> + av_log(s, AV_LOG_WARNING, "Invalid stream index %d "
> + "(%s contains %d stream%s), assuming 0\n",
> + vobsub->cur_st_id, vobsub->sub_ctx->filename,
> + vobsub->sub_ctx->nb_streams,
> + vobsub->sub_ctx->nb_streams > 1 ? "s" : "");
> + vobsub->cur_st_id = 0;
> + }
> +
> + st = vobsub->sub_ctx->streams[vobsub->cur_st_id];
> + av_dict_set(&st->metadata, "language", id, 0);
> + av_log(s, AV_LOG_DEBUG, "IDX stream[%d] id=%s\n",
> + vobsub->cur_st_id, id);
> + header_parsed = 1;
> +
> + } else if (!strncmp(line, "alt:", 3)) {
> + /* Alternative name for the current stream */
> +
> + char *p = line + 4;
> + AVStream *st = vobsub->sub_ctx->streams[vobsub->cur_st_id];
> +
> + while (*p == ' ')
> + p++;
> + av_dict_set(&st->metadata, "title", p, 0);
> + av_log(s, AV_LOG_DEBUG, "IDX stream[%d] name=%s\n", vobsub->cur_st_id, p);
> + header_parsed = 1;
> +
> + } else if (!strncmp(line, "timestamp:", 10)) {
> + /* timestamp+offset index entry */
> +
> + int hh, mm, ss, ms;
> + int64_t pos, timestamp;
> + AVStream *st = vobsub->sub_ctx->streams[vobsub->cur_st_id];
> +
> + if (sscanf(line, "timestamp: %02d:%02d:%02d:%03d, filepos: %"PRIx64,
> + &hh, &mm, &ss, &ms, &pos) != 5) {
> + av_log(s, AV_LOG_ERROR, "Unable to parse timestamp line '%s', abort parsing\n", line);
> + break;
> + }
> + timestamp = (hh*3600 + mm*60 + ss) * 1000 + ms;
> + timestamp = av_rescale_q(timestamp, (AVRational){1,1000}, st->time_base);
> + //av_log(0,0,"timestamp=%"PRId64" pos=%"PRIx64"\n", timestamp, pos);
> + av_add_index_entry(st, pos, timestamp, 0, 0, 0);
> +
> + } else if (!header_parsed) {
> + /* still parsing the header */
> +
> + av_bprintf(&header, "%s\n", line);
> + }
> + }
> +
> + av_log(s, AV_LOG_DEBUG, "VobSub header:\n%s\n", header.str);
> +
> + /* Dump the header in each subtitles stream and create an identical stream
> + * in the local context */
> + for (i = 0; i < vobsub->sub_ctx->nb_streams; i++) {
> + AVStream *sub_st = vobsub->sub_ctx->streams[i];
> + if (sub_st->codec->codec_id == AV_CODEC_ID_DVD_SUBTITLE) {
> + AVStream *st = avformat_new_stream(s, NULL);
> + if (!st)
> + return AVERROR(ENOMEM);
> + st->id = i;
> + av_bprint_finalize(&header, (char **)&sub_st->codec->extradata);
> + sub_st->codec->extradata_size = header.len + 1;
> + avcodec_copy_context(st->codec, sub_st->codec);
> + //av_log(0,0,"STREAM[%d]: %d nb index entries\n", i, vobsub->sub_ctx->streams[i]->nb_index_entries);
> + }
> + }
> +
> + return 0;
> +}
> +
> +#define CB_IF_AVAILABLE(cb, ...) vobsub->sub_ctx && vobsub->sub_ctx->iformat && \
> + vobsub->sub_ctx->iformat->cb ? vobsub->sub_ctx->iformat->cb(__VA_ARGS__) : 0
> +
> +static int vobsub_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> + VobSubContext *vobsub = s->priv_data;
> + int ret = CB_IF_AVAILABLE(read_packet, vobsub->sub_ctx, pkt);
> + //int i;
> +
> + //for (i = 0; i < vobsub->sub_ctx->nb_streams; i++) {
> + // av_log(0,0,"STREAM[%d]: %d nb index entries | first: PTS=%s POS=%"PRIx64"\n",
> + // i, vobsub->sub_ctx->streams[i]->nb_index_entries,
> + // av_ts2str(vobsub->sub_ctx->streams[i]->index_entries[0].timestamp),
> + // vobsub->sub_ctx->streams[i]->index_entries[0].pos);
> + //}
> + //av_log(0,0,"read pkt pts=%s dts=%s pos=%"PRId64"\n",
> + // av_ts2str(pkt->pts), av_ts2str(pkt->dts), pkt->pos);
> + return ret;
> +}
> +
> +static int64_t vobsub_read_timestamp(AVFormatContext *s, int stream_index,
> + int64_t *pos, int64_t pos_limit)
> +{
> + /* XXX: looks unused */
> + VobSubContext *vobsub = s->priv_data;
> + return CB_IF_AVAILABLE(read_timestamp, vobsub->sub_ctx, stream_index, pos, pos_limit);
> +}
> +
> +static int vobsub_read_close(AVFormatContext *s)
> +{
> + VobSubContext *vobsub = s->priv_data;
> + return CB_IF_AVAILABLE(read_close, vobsub->sub_ctx);
> +}
> +
> +#define OFFSET(x) offsetof(VobSubContext, x)
> +#define FLAGS AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM
> +static const AVOption options[] = {
> + { "strip_header", "Remove comments and empty lines from the header", OFFSET(strip_header), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS },
> + { NULL }
> +};
> +
> +static const AVClass vobsub_demuxer_class = {
> + .class_name = "vobsubdec",
> + .item_name = av_default_item_name,
> + .option = options,
> + .version = LIBAVUTIL_VERSION_INT,
> +};
> +
> +AVInputFormat ff_vobsub_demuxer = {
> + .name = "vobsub",
> + .long_name = NULL_IF_CONFIG_SMALL("VobSub subtitle format"),
> + .priv_data_size = sizeof(VobSubContext),
> + .read_probe = vobsub_probe,
> + .read_header = vobsub_read_header,
> + .read_packet = vobsub_read_packet,
> + .read_timestamp = vobsub_read_timestamp,
> + .read_close = vobsub_read_close,
> + .flags = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT, /* same as mpeg demuxer */
> + .extensions = "idx",
> + .priv_class = &vobsub_demuxer_class,
> +};
Regards,
--
Nicolas George
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 198 bytes
Desc: Digital signature
URL: <http://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20120907/ade43998/attachment.asc>
More information about the ffmpeg-devel
mailing list