淺析字幕流


[時間: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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM