浅析字幕流

[时间:2018-11] [状态:Open]
[关键词:多媒体,字幕,文本,ffplay,FFmpeg,subtitle]

0 综述

字幕是指电影、电视,以及戏剧、歌剧等舞台作品中出现的各种用途的文字,如版权标识、片名字幕、演(职)员表、说明字幕、歌词字幕、对白字幕等。这些字幕按照影片放映时出现的先后顺序而分为片头字幕、片间字幕和片尾字幕。一般情况下,片头、片尾字幕叠印在画面上,而对白、歌词等字幕一般出现在屏幕下方,戏剧等舞台伤口则显示于舞台两侧或上方。

字幕与声音语言相比,声音语言有一定的局限性:有声无形,转瞬即逝,不易引起人们的注意,有时不易听懂。如人物的语言和戏词,有的因口音或语种的原因,受众便很难听清或听懂,加上字幕就可以弥补这种局限性。因此,字幕与声音和画面相比,具有独特的功能。

字幕的作用,主要是将语音内容以文字方式显示,以帮助听力较弱的观众理解节目内容。另外,对于不同语言的观众,只有通过字幕才能理解影片内容。而在中国,不同地区语言的发音差别很大,不能正确理解普通话的人很多。但文字写法的差异并不大,看到普通话的文字后人们大都都能理解。所以,近年来华语圈的影视作品中,对应普通话(或方言)的字幕大多被附加在节目中。

本文主要内容是整理下目前常见的字幕格式,之后介绍下ffplay中字幕渲染的主要逻辑。

撰写此文的主要目标在于整理下我在2018年的对字幕方面的主要理解和积累。

1 常见字幕格式及分类

通常字幕分类有两个标准:基于文本的或基于图片的、嵌入容器的或独立存在的。

还有一种分类方式是按照字幕的表现方式,分为三类:

  • 硬字幕。是将字幕叠加到视频画面上。因为这种字幕与视频画面是一体的,所以具有最佳的兼容性,只要能够播放视频,就能显示字幕。缺点是字幕占据视频画面,破坏了视频内容,而且不可取消、不可编辑更改。(严格意义上来说这已经不算字幕了,添加字幕之后视频基本无法恢复,字幕也无法提取出来,甚至从容器来看根本就存在字幕流的概念。)
  • 外挂字幕。将字幕做成一个独立文件(字幕文件有多种格式)。这类字幕的优点是不破坏视频画面,可随时根据需要更换字幕语言,并且可随时编辑字幕内容。缺点是播放较为复杂,需要支持字幕播放的播放器支持。
  • 软字幕。是指通过某种方式将外挂字幕与视频打包在一起,下载、复制时仅需复制一个文件即可。如DVD中的VOB文件,高清视频封装格式MKV、TS、AVI等。这类型文件一般可以同时封装多种字幕文件,播放时通过播放器选择所需字幕,非常方便。在需要的时候,还可以将字幕分离出来进行编辑修改或替换。

当然,你还可以按照实际用途划分。但在谈及字幕格式时,通常我们指的是目前比较流行的字幕封装格式。
字幕格式共分为两类:图形数据格式和文本数据格式。

1.1 图形数据格式

这类字幕数据以图片方式呈现,文件体积较大,不易于修改,有时亦称为“硬字幕”。多用于非PC环境,例如DVD播放器、电视或视频会议等。

  • SUB格式
    SUB格式的字幕数据由字幕图片文件(.sub文档)和字幕索引文件(.idx文档)组成。一个.sub文档可同时包含多个语言的字幕,由.idx进行调用。常见于DVD-VIDEO,但在DVD中,这两个文件被集成到VOB内,需要通过软件分离VOB来获取字幕文件。

1.2 文本数据格式

这类字幕数据以文本格式呈现,文件体积较小,可直接用Windows自带的记事本功能进行修改。

  • SRT格式
    SRT(Subripper)是最简单的文本字幕格式,扩展名为.srt,其组成为:一行字幕序号,一行时间代码,一行字幕数据。如:

45
00:02:52,184 --00:02:53,617
慢慢来

这表示:第45个字幕,显示时间从该影片开始的第2分52.184秒到第2分53.617秒,内容为:慢慢来

  • SSA、ASS格式
    SSA(Sub Station Alpha)是为了解决SRT过于简单的字幕功能而开发的高级字幕格式,其扩展名为.SSA。采用SSA V4脚本语言,能实现丰富的字幕功能,除了能设定不同字幕数据的大小和位置外,更能实现动态文本和水印等复杂的功能。
    ASS(Advanced SubStation Alpha)为是更高级的SSA版本,采用SSA V4+脚本语言编写。它包含了所有SSA的所有特性,它可以将任何简单的文本转变成为卡拉OK的字幕样式,数个项目旨在创建这些脚本。ASS的特点在于它比普通的SSA更为规范,如ASS的编程风格。

  • webvtt
    此格式是在网站上配合HTML5视频使用的字幕格式。它是基于SRT格式的,但并不完全兼容(更多资料参考w3c)。示例如下:

WEBVTT
Kind: captions
Language: en

1
00:00:00,264 --> 00:00:24,537
line 1
line 2

2
00:00:00,306 --> 00:00:04,306
line 1
line 2
line 3
line 4

3
00:00:30,544 --> 00:00:32,545
line 1

2 ffplay中字幕渲染逻辑

对于基于图片的字幕,在视频显示时可以直接将字幕图片叠加到画面上。对于基于文本的字幕,需要通过其他技术先将文本转化为可渲染的单元,然后渲染到播放画面上。

FFmpeg,作为一个通用的播放框架,其中提供了对字幕的处理逻辑。本部分将重点介绍下ffplay中针对字幕的处理逻辑。

注意:我撰写本文是FFmpeg最新的release是V4.1。

FFmpeg中添加了字幕的专用结构体,如下:

enum AVSubtitleType {
    SUBTITLE_NONE,

    SUBTITLE_BITMAP,                ///< A bitmap, pict will be set

    /**
     * Plain text, the text field must be set by the decoder and is
     * authoritative. ass and pict fields may contain approximations.
     */
    SUBTITLE_TEXT,

    /**
     * Formatted text, the ass field must be set by the decoder and is
     * authoritative. pict and text fields may contain approximations.
     */
    SUBTITLE_ASS,
};
typedef struct AVSubtitleRect {
    int x;         ///< top left corner  of pict, undefined when pict is not set
    int y;         ///< top left corner  of pict, undefined when pict is not set
    int w;         ///< width            of pict, undefined when pict is not set
    int h;         ///< height           of pict, undefined when pict is not set
    int nb_colors; ///< number of colors in pict, undefined when pict is not set

#if FF_API_AVPICTURE
    attribute_deprecated
    AVPicture pict;
#endif
    /**
     * data+linesize for the bitmap of this subtitle.
     * Can be set for text/ass as well once they are rendered.
     */
    uint8_t *data[4];
    int linesize[4];

    enum AVSubtitleType type;

    char *text;                     ///< 0 terminated plain UTF-8 text

    /**
     * 0 terminated ASS/SSA compatible event line.
     * The presentation of this is unaffected by the other values in this
     * struct.
     */
    char *ass;

    int flags;
} AVSubtitleRect;

typedef struct AVSubtitle {
    uint16_t format; /* 0 = graphics */
    uint32_t start_display_time; /* relative to packet pts, in ms */
    uint32_t end_display_time; /* relative to packet pts, in ms */
    unsigned num_rects;
    AVSubtitleRect **rects;
    int64_t pts;    ///< Same as packet pts, in AV_TIME_BASE
} AVSubtitle;

从上述结构来看,FFmpeg支持三种类型的字幕:位图、普通文本以及ASS。每个AVSubtitle包含多个AVSubtitleRect,每个AVSubtitleRect中都有自己的字幕信息,比如文本内容或者图片格式。

ffplay要实现字幕流的播放,首先需要decoder支持,对应的在ffplay源码中有subtitle_thread线程专门用于字幕流解码,其代码如下:

static int subtitle_thread(void *arg)
{
    VideoState *is = arg;
    Frame *sp;
    int got_subtitle;
    double pts;

    for (;;) {
		// 从解码后字幕流队列中取一可用帧
        if (!(sp = frame_queue_peek_writable(&is->subpq)))
            return 0;
		// 解码 AVPacket-> AVSubtitle
        if ((got_subtitle = decoder_decode_frame(&is->subdec, NULL, &sp->sub)) < 0)
            break;

        pts = 0;
		/* 这里指定了仅支持图片格式的字幕,文本字幕解码之后直接丢弃了 */
        if (got_subtitle && sp->sub.format == 0) {
            if (sp->sub.pts != AV_NOPTS_VALUE)
                pts = sp->sub.pts / (double)AV_TIME_BASE;
            sp->pts = pts;
            sp->serial = is->subdec.pkt_serial;
            sp->width = is->subdec.avctx->width;
            sp->height = is->subdec.avctx->height;
            sp->uploaded = 0;

            /* 将解码之后的AVSubtitle放入队列中 */
            frame_queue_push(&is->subpq);
        } else if (got_subtitle) {
            avsubtitle_free(&sp->sub);
        }
    }
    return 0;
}

字幕解码之后的图像数据需要通过视频渲染线程才能最终被看到。其实现代码如下:

// @video_image_display function
Frame *sp = NULL;
if (is->subtitle_st) { // 有字幕流的前提下
	// 检查并获取字幕队列的数据
    if (frame_queue_nb_remaining(&is->subpq) > 0) {
        sp = frame_queue_peek(&is->subpq);

		// 根据当前视频帧的时间戳计算字幕帧是否需要显示
        if (vp->pts >= sp->pts + ((float) sp->sub.start_display_time / 1000)) {
            if (!sp->uploaded) {
                uint8_t* pixels[4];
                int pitch[4];
                int i;
                if (!sp->width || !sp->height) {
                    sp->width = vp->width;
                    sp->height = vp->height;
                }
                if (realloc_texture(&is->sub_texture, SDL_PIXELFORMAT_ARGB8888, sp->width, sp->height, SDL_BLENDMODE_BLEND, 1) < 0)
                    return;
				// 将所有AVSubtitleRect合成到一个SDL_Texture中
                for (i = 0; i < sp->sub.num_rects; i++) {
                    AVSubtitleRect *sub_rect = sp->sub.rects[i];

                    sub_rect->x = av_clip(sub_rect->x, 0, sp->width );
                    sub_rect->y = av_clip(sub_rect->y, 0, sp->height);
                    sub_rect->w = av_clip(sub_rect->w, 0, sp->width  - sub_rect->x);
                    sub_rect->h = av_clip(sub_rect->h, 0, sp->height - sub_rect->y);

                    is->sub_convert_ctx = sws_getCachedContext(is->sub_convert_ctx,
                        sub_rect->w, sub_rect->h, AV_PIX_FMT_PAL8,
                        sub_rect->w, sub_rect->h, AV_PIX_FMT_BGRA,
                        0, NULL, NULL, NULL);
                    if (!is->sub_convert_ctx) {
                        av_log(NULL, AV_LOG_FATAL, "Cannot initialize the conversion context\n");
                        return;
                    }
                    if (!SDL_LockTexture(is->sub_texture, (SDL_Rect *)sub_rect, (void **)pixels, pitch)) {
                        sws_scale(is->sub_convert_ctx, (const uint8_t * const *)sub_rect->data, sub_rect->linesize,
                                  0, sub_rect->h, pixels, pitch);
                        SDL_UnlockTexture(is->sub_texture);
                    }
                }
                sp->uploaded = 1;
            }
        } else
            sp = NULL;
    }
}

// ... 省略部分代码

if (sp) { // 渲染字幕流
    SDL_RenderCopy(renderer, is->sub_texture, NULL, &rect);
}

以上是ffplay中对字幕处理的两个主要逻辑:解码、渲染。
还有一部分关于字幕原始帧的丢帧逻辑,位于video_refresh函数中,通常会在seek之后触发,有兴趣的读者可以自行研究。

从上面的介绍来看,ffplay并不支持文本字幕的显示,真正要显示文本的话需要借助于filter,比如subtitlesass。用法如下:

./ffplay zuimei.mp4 -vf "subtitles=zuimei.lrc" -x 800 -y 600

由此可见,此处filter基本上实现了文本到图像的转换。

3 后续本系列内容提要

  • 浅析LRC歌词文件格式,其中包含歌词播放逻辑
  • SRT字幕流格式
  • ASS字幕格式
  • FFmpeg中的字幕demuxer的实现
  • libass库用法
  • webvtt字幕格式

4 总结

本文主要是对目前常见的字幕格式做了简单总结,并基于ffplay的代码介绍了其字幕渲染的主要逻辑,仅供参考。

有任何错误或遗漏的地方,欢迎提出。

4.1 参考资料

  1. 字幕格式-wiki
  2. subtitle-wiki
  3. 字幕基础:字幕介绍、字幕种类及常见格式
  4. subtile-formats
  5. ffmpeg-doc
posted @ 2018-11-30 21:18  Tocy  阅读(3021)  评论(0编辑  收藏  举报