当前位置:   article > 正文

浅析字幕流

视频字幕的逻辑

[时间: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)。示例如下:

  1. WEBVTT
  2. Kind: captions
  3. Language: en
  4. 1
  5. 00:00:00,264 --> 00:00:24,537
  6. line 1
  7. line 2
  8. 2
  9. 00:00:00,306 --> 00:00:04,306
  10. line 1
  11. line 2
  12. line 3
  13. line 4
  14. 3
  15. 00:00:30,544 --> 00:00:32,545
  16. line 1

2 ffplay中字幕渲染逻辑

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

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

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

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

  1. enum AVSubtitleType {
  2. SUBTITLE_NONE,
  3. SUBTITLE_BITMAP, ///< A bitmap, pict will be set
  4. /**
  5. * Plain text, the text field must be set by the decoder and is
  6. * authoritative. ass and pict fields may contain approximations.
  7. */
  8. SUBTITLE_TEXT,
  9. /**
  10. * Formatted text, the ass field must be set by the decoder and is
  11. * authoritative. pict and text fields may contain approximations.
  12. */
  13. SUBTITLE_ASS,
  14. };
  15. typedef struct AVSubtitleRect {
  16. int x; ///< top left corner of pict, undefined when pict is not set
  17. int y; ///< top left corner of pict, undefined when pict is not set
  18. int w; ///< width of pict, undefined when pict is not set
  19. int h; ///< height of pict, undefined when pict is not set
  20. int nb_colors; ///< number of colors in pict, undefined when pict is not set
  21. #if FF_API_AVPICTURE
  22. attribute_deprecated
  23. AVPicture pict;
  24. #endif
  25. /**
  26. * data+linesize for the bitmap of this subtitle.
  27. * Can be set for text/ass as well once they are rendered.
  28. */
  29. uint8_t *data[4];
  30. int linesize[4];
  31. enum AVSubtitleType type;
  32. char *text; ///< 0 terminated plain UTF-8 text
  33. /**
  34. * 0 terminated ASS/SSA compatible event line.
  35. * The presentation of this is unaffected by the other values in this
  36. * struct.
  37. */
  38. char *ass;
  39. int flags;
  40. } AVSubtitleRect;
  41. typedef struct AVSubtitle {
  42. uint16_t format; /* 0 = graphics */
  43. uint32_t start_display_time; /* relative to packet pts, in ms */
  44. uint32_t end_display_time; /* relative to packet pts, in ms */
  45. unsigned num_rects;
  46. AVSubtitleRect **rects;
  47. int64_t pts; ///< Same as packet pts, in AV_TIME_BASE
  48. } AVSubtitle;

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

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

  1. static int subtitle_thread(void *arg)
  2. {
  3. VideoState *is = arg;
  4. Frame *sp;
  5. int got_subtitle;
  6. double pts;
  7. for (;;) {
  8. // 从解码后字幕流队列中取一可用帧
  9. if (!(sp = frame_queue_peek_writable(&is->subpq)))
  10. return 0;
  11. // 解码 AVPacket-> AVSubtitle
  12. if ((got_subtitle = decoder_decode_frame(&is->subdec, NULL, &sp->sub)) < 0)
  13. break;
  14. pts = 0;
  15. /* 这里指定了仅支持图片格式的字幕,文本字幕解码之后直接丢弃了 */
  16. if (got_subtitle && sp->sub.format == 0) {
  17. if (sp->sub.pts != AV_NOPTS_VALUE)
  18. pts = sp->sub.pts / (double)AV_TIME_BASE;
  19. sp->pts = pts;
  20. sp->serial = is->subdec.pkt_serial;
  21. sp->width = is->subdec.avctx->width;
  22. sp->height = is->subdec.avctx->height;
  23. sp->uploaded = 0;
  24. /* 将解码之后的AVSubtitle放入队列中 */
  25. frame_queue_push(&is->subpq);
  26. } else if (got_subtitle) {
  27. avsubtitle_free(&sp->sub);
  28. }
  29. }
  30. return 0;
  31. }

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

  1. // @video_image_display function
  2. Frame *sp = NULL;
  3. if (is->subtitle_st) { // 有字幕流的前提下
  4. // 检查并获取字幕队列的数据
  5. if (frame_queue_nb_remaining(&is->subpq) > 0) {
  6. sp = frame_queue_peek(&is->subpq);
  7. // 根据当前视频帧的时间戳计算字幕帧是否需要显示
  8. if (vp->pts >= sp->pts + ((float) sp->sub.start_display_time / 1000)) {
  9. if (!sp->uploaded) {
  10. uint8_t* pixels[4];
  11. int pitch[4];
  12. int i;
  13. if (!sp->width || !sp->height) {
  14. sp->width = vp->width;
  15. sp->height = vp->height;
  16. }
  17. if (realloc_texture(&is->sub_texture, SDL_PIXELFORMAT_ARGB8888, sp->width, sp->height, SDL_BLENDMODE_BLEND, 1) < 0)
  18. return;
  19. // 将所有AVSubtitleRect合成到一个SDL_Texture中
  20. for (i = 0; i < sp->sub.num_rects; i++) {
  21. AVSubtitleRect *sub_rect = sp->sub.rects[i];
  22. sub_rect->x = av_clip(sub_rect->x, 0, sp->width );
  23. sub_rect->y = av_clip(sub_rect->y, 0, sp->height);
  24. sub_rect->w = av_clip(sub_rect->w, 0, sp->width - sub_rect->x);
  25. sub_rect->h = av_clip(sub_rect->h, 0, sp->height - sub_rect->y);
  26. is->sub_convert_ctx = sws_getCachedContext(is->sub_convert_ctx,
  27. sub_rect->w, sub_rect->h, AV_PIX_FMT_PAL8,
  28. sub_rect->w, sub_rect->h, AV_PIX_FMT_BGRA,
  29. 0, NULL, NULL, NULL);
  30. if (!is->sub_convert_ctx) {
  31. av_log(NULL, AV_LOG_FATAL, "Cannot initialize the conversion context\n");
  32. return;
  33. }
  34. if (!SDL_LockTexture(is->sub_texture, (SDL_Rect *)sub_rect, (void **)pixels, pitch)) {
  35. sws_scale(is->sub_convert_ctx, (const uint8_t * const *)sub_rect->data, sub_rect->linesize,
  36. 0, sub_rect->h, pixels, pitch);
  37. SDL_UnlockTexture(is->sub_texture);
  38. }
  39. }
  40. sp->uploaded = 1;
  41. }
  42. } else
  43. sp = NULL;
  44. }
  45. }
  46. // ... 省略部分代码
  47. if (sp) { // 渲染字幕流
  48. SDL_RenderCopy(renderer, is->sub_texture, NULL, &rect);
  49. }

以上是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

转载于:https://www.cnblogs.com/tocy/p/subtitle-stream-overview.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/588208
推荐阅读
相关标签
  

闽ICP备14008679号