libass分析2-源码分析-示例程序test.c的源码分析

Author: wencoo
Blog:https://wencoo.blog.csdn.net/
Date: 08/12/2023
Email: jianwen056@aliyun.com
Wechat:wencoo824
QQ:1419440391
Details:

目录

正文 或 背景

main函数解析

int main(int argc, char *argv[])
{
    int frame_w = 1280;
    int frame_h = 720;

    if (argc != 4 && argc != 6) {
        printf("usage: %s <image file> <subtitle file> <time> "
               "[<storage width> <storage height>]\n",
                argv[0] ? argv[0] : "test");
        exit(1);
    }
    char *imgfile = argv[1];
    char *subfile = argv[2];
    double tm = strtod(argv[3], 0);
    if (argc == 6) {
        frame_w = atoi(argv[4]);
        frame_h = atoi(argv[5]);
        if (frame_w <= 0 || frame_h <= 0) {
            printf("storage size must be non-zero and positive!\n");
            exit(1);
        }
    }

    print_font_providers(ass_library);

    init(frame_w, frame_h);
    ASS_Track *track = ass_read_file(ass_library, subfile, NULL);
    if (!track) {
        printf("track init failed!\n");
        return 1;
    }

    ASS_Image *img =
        ass_render_frame(ass_renderer, track, (int) (tm * 1000), NULL);
    image_t *frame = gen_image(frame_w, frame_h);
    blend(frame, img);

    ass_free_track(track);
    ass_renderer_done(ass_renderer);
    ass_library_done(ass_library);

    write_png(imgfile, frame);
    free(frame->buffer);
    free(frame);

    return 0;
}

可以看到,重点函数只有3个,我们一个一个查看就可以了。

init

init实现如下:

static void init(int frame_w, int frame_h)
{
    ass_library = ass_library_init();
    if (!ass_library) {
        printf("ass_library_init failed!\n");
        exit(1);
    }

    ass_set_message_cb(ass_library, msg_callback, NULL);
    ass_set_extract_fonts(ass_library, 1);

    ass_renderer = ass_renderer_init(ass_library);
    if (!ass_renderer) {
        printf("ass_renderer_init failed!\n");
        exit(1);
    }

    ass_set_storage_size(ass_renderer, frame_w, frame_h);
    ass_set_frame_size(ass_renderer, frame_w, frame_h);
    ass_set_fonts(ass_renderer, NULL, "sans-serif",
                  ASS_FONTPROVIDER_AUTODETECT, NULL, 1);
}

ass_read_file

ass_read_file实现如下:

/**
 * \brief Read subtitles from file.
 * \param library libass library object
 * \param fname file name
 * \param codepage recode buffer contents from given codepage
 * \return newly allocated track
*/
ASS_Track *ass_read_file(ASS_Library *library, char *fname,
                         char *codepage)
{
    char *buf;
    ASS_Track *track;
    size_t bufsize;
			//读取ass文件内容,存入buf,并且把ass内容转换成utf-8格式
    buf = read_file_recode(library, fname, codepage, &bufsize);
    if (!buf)
        return 0;
		//重点:解析内存中的数据
    track = parse_memory(library, buf);
    free(buf);
    if (!track)
        return 0;
		//将名字赋值给name
    track->name = strdup(fname);

    ass_msg(library, MSGL_INFO,
            "Added subtitle file: '%s' (%d styles, %d events)",
            fname, track->n_styles, track->n_events);

    return track;
}

parse_memory

parse_memory实现如下:


/*
 * \param buf pointer to subtitle text in utf-8
 */
static ASS_Track *parse_memory(ASS_Library *library, char *buf)
{
    ASS_Track *track;
    int i;
			//创建ass轨道,申请了内存
    track = ass_new_track(library);
    if (!track)
        return NULL;

    // process header
		//	解析文件内容,
    process_text(track, buf);

    // external SSA/ASS subs does not have ReadOrder field  外部SSA/ASS sub没有ReadOrder字段
    for (i = 0; i < track->n_events; ++i)
        track->events[i].ReadOrder = i;

    if (track->track_type == TRACK_TYPE_UNKNOWN) {
        ass_free_track(track);
        return 0;
    }

    ass_process_force_style(track);

    return track;
}

process_line

process_text函数中过滤了空行等,有效数据进入process_line函数进行进一步处理。process_line实现如下:

/**
 * \brief Parse a header line
 * \param track track
 * \param str string to parse, zero-terminated
*/
static int process_line(ASS_Track *track, char *str)
{
    skip_spaces(&str);
    if (!ass_strncasecmp(str, "[Script Info]", 13)) {
        track->parser_priv->state = PST_INFO;
    } else if (!ass_strncasecmp(str, "[V4 Styles]", 11)) {
        track->parser_priv->state = PST_STYLES;
        track->track_type = TRACK_TYPE_SSA;
    } else if (!ass_strncasecmp(str, "[V4+ Styles]", 12)) {
        track->parser_priv->state = PST_STYLES;
        track->track_type = TRACK_TYPE_ASS;
    } else if (!ass_strncasecmp(str, "[Events]", 8)) {
        track->parser_priv->state = PST_EVENTS;
    } else if (!ass_strncasecmp(str, "[Fonts]", 7)) {
        track->parser_priv->state = PST_FONTS;
    } else {
        switch (track->parser_priv->state) {
        case PST_INFO:
            process_info_line(track, str);
            break;
        case PST_STYLES:
            process_styles_line(track, str);
            break;
        case PST_EVENTS:
            process_events_line(track, str);
            break;
        case PST_FONTS:
            process_fonts_line(track, str);
            break;
        default:
            break;
        }
    }
    return 0;
}

process_events_line

process_events_line函数开始具体处理Event的逻辑。

static int process_events_line(ASS_Track *track, char *str)
{
    if (!strncmp(str, "Format:", 7)) {
        char *p = str + 7;
        skip_spaces(&p);
        free(track->event_format);
        track->event_format = strdup(p);
        if (!track->event_format)
            return -1;
        ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
        if (track->track_type == TRACK_TYPE_ASS)
            custom_format_line_compatibility(track, p, ass_event_format);
        else
            custom_format_line_compatibility(track, p, ssa_event_format);

        // Guess if we are dealing with legacy ffmpeg subs and change accordingly
					//猜测我们是否在处理遗留的ffmpeg sub并进行相应的更改
        // If file has no event format it was probably not created by ffmpeg/libav
					//如果文件没有事件格式,它可能不是由ffmpeg/libav创建的
        if (detect_legacy_conv_subs(track)) {
            track->ScaledBorderAndShadow = 1;
            ass_msg(track->library, MSGL_INFO,
                    "Track treated as legacy ffmpeg sub.");
        }
    } else if (!strncmp(str, "Dialogue:", 9)) {
        // This should never be reached for embedded subtitles. 嵌入式字幕永远不应该达到这个要求。
        // They have slightly different format and are parsed in ass_process_chunk,
					//它们的格式略有不同,并在ass_process_chunk中解析,直接从demuxer调用
        // called directly from demuxer
        int eid;
        ASS_Event *event;

        // We can't parse events without event_format  如果没有event_format,我们就无法解析事件
        if (!track->event_format) {
            event_format_fallback(track);
            if (!track->event_format)
                return -1;
        }

        str += 9;
        skip_spaces(&str);

        eid = ass_alloc_event(track);
        if (eid < 0)
            return -1;
        event = track->events + eid;

        int ret = process_event_tail(track, event, str, 0);
        if (!ret)
            return 0;
        // If something went wrong, discard the useless Event  如果出现问题,丢弃无用的Event
        ass_free_event(track, eid);
        track->n_events--;
        return ret;
    } else {
        ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str);
    }
    return 0;
}

通过调试,走到process_event_tail函数中

process_event_tail

/**
 * \brief Parse the tail of Dialogue line
 * \param track track
 * \param event parsed data goes here
 * \param str string to parse, zero-terminated
 * \param n_ignored number of format options to skip at the beginning
*/
static int process_event_tail(ASS_Track *track, ASS_Event *event,
                              char *str, int n_ignored)
{
    char *token;
    char *tname;
    char *p = str;
    int i;
    ASS_Event *target = event;

    char *format = strdup(track->event_format);
    if (!format)
        return -1;
    char *q = format;           // format scanning pointer

    for (i = 0; i < n_ignored; ++i) {
        NEXT(q, tname);
    }

    while (1) {
        NEXT(q, tname);
        if (ass_strcasecmp(tname, "Text") == 0) {
            event->Text = strdup(p);
            if (event->Text && *event->Text != 0) {
                char *end = event->Text + strlen(event->Text);
                while (end > event->Text &&
                       (end[-1] == '\r' || end[-1] == '\t' || end[-1] == ' '))
                    *--end = 0;
            }
            event->Duration -= event->Start;
            free(format);
            return event->Text ? 0 : -1;           // "Text" is always the last
        }
        NEXT(p, token);

        ALIAS(End, Duration)    // temporarily store end timecode in event->Duration
        ALIAS(Actor, Name)      // both variants are used in files
        PARSE_START
            INTVAL(Layer)
            STYLEVAL(Style)
            STRVAL(Name)
            STRVAL(Effect)
            INTVAL(MarginL)
            INTVAL(MarginR)
            INTVAL(MarginV)
            TIMEVAL(Start)
            TIMEVAL(Duration)
        PARSE_END
    }
    free(format);
    return 1;
}

在这个函数中,tname是Event项中的Format中的一项,token对应Event项中的Dialogue中的tname的对应项数值。

在该函数中,有很多的宏定义,下面我们就看一看这些宏定义的作用,参考libass分析3-源码分析-libass中的宏定义分析

所以,该函数就是将ass文件内容解析,然后全部赋值给ASS_Track结构。然后继续往下看main函数中接下来的函数ass_render_frame

重点:ass_render_frame

实现:

/**
 * \brief render a frame  渲染一个帧
 * \param priv library handle
 * \param track track
 * \param now current video timestamp (ms)  当前视频时间戳(毫秒)
 * \param detect_change a value describing how the new images differ from the previous ones will be written here:
 *        0 if identical, 1 if different positions, 2 if different content.
 *        Can be NULL, in that case no detection is performed.
 一个描述新图像与之前图像的不同之处的值将写在这里:
*相同为0,位置不同为1,内容不同为2。
*可以为NULL,在这种情况下不执行检测
 */
ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
                            long long now, int *detect_change)
{
    // init frame
    if (!ass_start_frame(priv, track, now)) {
        if (detect_change)
            *detect_change = 2;
        return NULL;
    }

    // render events separately  分别渲染事件
    int cnt = 0;
    for (int i = 0; i < track->n_events; i++) {
        ASS_Event *event = track->events + i;
        if ((event->Start <= now)
            && (now < (event->Start + event->Duration))) {
            if (cnt >= priv->eimg_size) {
                priv->eimg_size += 100;
                priv->eimg =
                    realloc(priv->eimg,
                            priv->eimg_size * sizeof(EventImages));
            }
            if (ass_render_event(&priv->state, event, priv->eimg + cnt))
                cnt++;
        }
    }

    // sort by layer  按层排序
    if (cnt > 0)
        qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer);

    // call fix_collisions for each group of events with the same layer
			// 对同一层的每一组事件调用fix_collisions
    EventImages *last = priv->eimg;
    for (int i = 1; i < cnt; i++)
        if (last->event->Layer != priv->eimg[i].event->Layer) {
            fix_collisions(priv, last, priv->eimg + i - last);
            last = priv->eimg + i;
        }
    if (cnt > 0)
        fix_collisions(priv, last, priv->eimg + cnt - last);

    // concat lists  concat列表
    ASS_Image **tail = &priv->images_root;
    for (int i = 0; i < cnt; i++) {
        ASS_Image *cur = priv->eimg[i].imgs;
        while (cur) {
            *tail = cur;
            tail = &cur->next;
            cur = cur->next;
        }
    }
    ass_frame_ref(priv->images_root);

    if (detect_change)
        *detect_change = ass_detect_change(priv);

    // free the previous image list
    ass_frame_unref(priv->prev_images_root);
    priv->prev_images_root = NULL;

    return priv->images_root;
}

第一个逻辑就是初始化一个帧,看看初始化帧内容有什么东西。

ass_start_frame

实现:

/**
 * \brief Start a new frame
 */
static bool
ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track,
                long long now)
{
    if (!render_priv->settings.frame_width
        && !render_priv->settings.frame_height)
        return false;               // library not initialized

    if (!render_priv->fontselect)
        return false;

    if (render_priv->library != track->library)
        return false;

    if (track->n_events == 0)
        return false;               // nothing to do

    render_priv->track = track;
    render_priv->time = now;
			//为渲染准备轨道,对轨道参数先设定一些预设值,分辨率的设置
    ass_lazy_track_init(render_priv->library, render_priv->track);

    if (render_priv->library->num_fontdata != render_priv->num_emfonts) {
        assert(render_priv->library->num_fontdata > render_priv->num_emfonts);
        render_priv->num_emfonts = ass_update_embedded_fonts(
            render_priv->fontselect, render_priv->num_emfonts);
    }

    setup_shaper(render_priv->state.shaper, render_priv);

    // PAR correction
    double par = render_priv->settings.par;
    bool lr_track = track->LayoutResX > 0 && track->LayoutResY > 0;
    if (par == 0. || lr_track) {
        if (render_priv->frame_content_width && render_priv->frame_content_height && (lr_track ||
                (render_priv->settings.storage_width && render_priv->settings.storage_height))) {
            double dar = ((double) render_priv->frame_content_width) /
                         render_priv->frame_content_height;
            ASS_Vector layout_res = ass_layout_res(render_priv);
            double sar = ((double) layout_res.x) / layout_res.y;
            par = dar / sar;
        } else
            par = 1.0;
    }
    render_priv->par_scale_x = par;

    render_priv->prev_images_root = render_priv->images_root;
    render_priv->images_root = NULL;

    check_cache_limits(render_priv, &render_priv->cache);

    return true;
}

ass_render_event

融合渲染一帧的实现:

/**
 * \brief Main ass rendering function, glues everything together 主ass渲染功能,将所有内容粘合在一起
 * \param event event to render  渲染事件
 * \param event_images struct containing resulting images, will also be initialized  
 * Process event, appending resulting ASS_Image's to images_root.
 包含结果图像的结构,也将被初始化处理事件,将结果ASS_Image's附加到images_root
 */
static bool
ass_render_event(RenderContext *state, ASS_Event *event,
                 EventImages *event_images)
{
    ASS_Renderer *render_priv = state->renderer;
    if (event->Style >= render_priv->track->n_styles) {
        ass_msg(render_priv->library, MSGL_WARN, "No style found");
        return false;
    }
    if (!event->Text) {
        ass_msg(render_priv->library, MSGL_WARN, "Empty event");
        return false;
    }

    free_render_context(state);
    init_render_context(state, event);

    if (!parse_events(state, event))
        return false;

    TextInfo *text_info = state->text_info;
    if (text_info->length == 0) {
        // no valid symbols in the event; this can be smth like {comment}
        free_render_context(state);
        return false;
    }

    split_style_runs(state);

    // Find shape runs and shape text
    ass_shaper_set_base_direction(state->shaper,
            ass_resolve_base_direction(state->font_encoding));
    ass_shaper_find_runs(state->shaper, render_priv, text_info->glyphs,
            text_info->length);
    if (!ass_shaper_shape(state->shaper, text_info)) {
        ass_msg(render_priv->library, MSGL_ERR, "Failed to shape text");
        free_render_context(state);
        return false;
    }

    retrieve_glyphs(state);

    preliminary_layout(state);

    int valign = state->alignment & 12;

    int MarginL =
        (event->MarginL) ? event->MarginL : state->style->MarginL;
    int MarginR =
        (event->MarginR) ? event->MarginR : state->style->MarginR;
    int MarginV =
        (event->MarginV) ? event->MarginV : state->style->MarginV;

    // calculate max length of a line
    double max_text_width =
        x2scr_right(state, render_priv->track->PlayResX - MarginR) -
        x2scr_left(state, MarginL);

    // wrap lines
    wrap_lines_smart(state, max_text_width);

    // depends on glyph x coordinates being monotonous within runs, so it should be done before reorder
    ass_process_karaoke_effects(state);

    reorder_text(state);

    align_lines(state, max_text_width);

    // determing text bounding box
    ASS_DRect bbox;
    compute_string_bbox(text_info, &bbox);

    apply_baseline_shear(state);

    // determine device coordinates for text
    double device_x = 0;
    double device_y = 0;

    // handle positioned events first: an event can be both positioned and
    // scrolling, and the scrolling effect overrides the position on one axis
    if (state->evt_type & EVENT_POSITIONED) {
        double base_x = 0;
        double base_y = 0;
        get_base_point(&bbox, state->alignment, &base_x, &base_y);
        device_x =
            x2scr_pos(render_priv, state->pos_x) - base_x;
        device_y =
            y2scr_pos(render_priv, state->pos_y) - base_y;
    }

    // x coordinate
    if (state->evt_type & EVENT_HSCROLL) {
        if (state->scroll_direction == SCROLL_RL)
            device_x =
                x2scr_pos(render_priv,
                      render_priv->track->PlayResX -
                      state->scroll_shift);
        else if (state->scroll_direction == SCROLL_LR)
            device_x =
                x2scr_pos(render_priv, state->scroll_shift) -
                (bbox.x_max - bbox.x_min);
    } else if (!(state->evt_type & EVENT_POSITIONED)) {
        device_x = x2scr_left(state, MarginL);
    }

    // y coordinate
    if (state->evt_type & EVENT_VSCROLL) {
        if (state->scroll_direction == SCROLL_TB)
            device_y =
                y2scr(state,
                      state->scroll_y0 +
                      state->scroll_shift) -
                bbox.y_max;
        else if (state->scroll_direction == SCROLL_BT)
            device_y =
                y2scr(state,
                      state->scroll_y1 -
                      state->scroll_shift) -
                bbox.y_min;
    } else if (!(state->evt_type & EVENT_POSITIONED)) {
        if (valign == VALIGN_TOP) {     // toptitle
            device_y =
                y2scr_top(state,
                          MarginV) + text_info->lines[0].asc;
        } else if (valign == VALIGN_CENTER) {   // midtitle
            double scr_y =
                y2scr(state, render_priv->track->PlayResY / 2.0);
            device_y = scr_y - (bbox.y_max + bbox.y_min) / 2.0;
        } else {                // subtitle
            double line_pos = state->explicit ?
                0 : render_priv->settings.line_position;
            double scr_top, scr_bottom, scr_y0;
            if (valign != VALIGN_SUB)
                ass_msg(render_priv->library, MSGL_V,
                       "Invalid valign, assuming 0 (subtitle)");
            scr_bottom =
                y2scr_sub(state,
                          render_priv->track->PlayResY - MarginV);
            scr_top = y2scr_top(state, 0); //xxx not always 0?
            device_y = scr_bottom + (scr_top - scr_bottom) * line_pos / 100.0;
            device_y -= text_info->height;
            device_y += text_info->lines[0].asc;
            // clip to top to avoid confusion if line_position is very high,
            // turning the subtitle into a toptitle
            // also, don't change behavior if line_position is not used
            scr_y0 = scr_top + text_info->lines[0].asc;
            if (device_y < scr_y0 && line_pos > 0) {
                device_y = scr_y0;
            }
        }
    }

    // fix clip coordinates
    if (state->explicit || !render_priv->settings.use_margins) {
        state->clip_x0 =
            x2scr_pos_scaled(render_priv, state->clip_x0);
        state->clip_x1 =
            x2scr_pos_scaled(render_priv, state->clip_x1);
        state->clip_y0 =
            y2scr_pos(render_priv, state->clip_y0);
        state->clip_y1 =
            y2scr_pos(render_priv, state->clip_y1);

        if (state->explicit) {
            // we still need to clip against screen boundaries
            double zx = x2scr_pos_scaled(render_priv, 0);
            double zy = y2scr_pos(render_priv, 0);
            double sx = x2scr_pos_scaled(render_priv, render_priv->track->PlayResX);
            double sy = y2scr_pos(render_priv, render_priv->track->PlayResY);

            state->clip_x0 = FFMAX(state->clip_x0, zx);
            state->clip_y0 = FFMAX(state->clip_y0, zy);
            state->clip_x1 = FFMIN(state->clip_x1, sx);
            state->clip_y1 = FFMIN(state->clip_y1, sy);
        }
    } else {
        // no \clip (explicit==0) and use_margins => only clip to screen with margins
        state->clip_x0 = 0;
        state->clip_y0 = 0;
        state->clip_x1 = render_priv->settings.frame_width;
        state->clip_y1 = render_priv->settings.frame_height;
    }

    if (state->evt_type & EVENT_VSCROLL) {
        double y0 = y2scr_pos(render_priv, state->scroll_y0);
        double y1 = y2scr_pos(render_priv, state->scroll_y1);

        state->clip_y0 = FFMAX(state->clip_y0, y0);
        state->clip_y1 = FFMIN(state->clip_y1, y1);
    }

    calculate_rotation_params(state, &bbox, device_x, device_y);

    render_and_combine_glyphs(state, device_x, device_y);

    memset(event_images, 0, sizeof(*event_images));
    // VSFilter does *not* shift lines with a border > margin to be within the
    // frame, so negative values for top and left may occur
    event_images->top = device_y - text_info->lines[0].asc - text_info->border_top;
    event_images->height =
        text_info->height + text_info->border_bottom + text_info->border_top;
    event_images->left =
        (device_x + bbox.x_min) * render_priv->par_scale_x - text_info->border_x + 0.5;
    event_images->width =
        (bbox.x_max - bbox.x_min) * render_priv->par_scale_x
        + 2 * text_info->border_x + 0.5;
    event_images->detect_collisions = state->detect_collisions;
    event_images->shift_direction = (valign == VALIGN_SUB) ? -1 : 1;
    event_images->event = event;
    event_images->imgs = render_text(state);

    if (state->border_style == 4)
        add_background(state, event_images);

    ass_shaper_cleanup(state->shaper, text_info);
    free_render_context(state);

    return true;
}

init_render_context

    ass_apply_transition_effects(state);
    state->explicit = state->evt_type != EVENT_NORMAL ||
                      ass_event_has_hard_overrides(event->Text);
			ass_reset_render_context(state, NULL);

ass_event_has_hard_overrides

// Return 1 if the event contains tags that will apply overrides the selective
// style override code should not touch. Return 0 otherwise.
int ass_event_has_hard_overrides(char *str)
{
    // look for \pos and \move tags inside {...}
    // mirrors ass_get_next_char, but is faster and doesn't change any global state
    while (*str) {
        if (str[0] == '\\' && str[1] != '\0') {
            str += 2;
        } else if (str[0] == '{') {
            str++;
            while (*str && *str != '}') {
                if (*str == '\\') {
                    char *p = str + 1;
                    if (mystrcmp(&p, "pos") || mystrcmp(&p, "move") ||
                        mystrcmp(&p, "clip") || mystrcmp(&p, "iclip") ||
                        mystrcmp(&p, "org") || mystrcmp(&p, "pbo") ||
                        mystrcmp(&p, "p"))
                        return 1;
                }
                str++;
            }
        } else {
            str++;
        }
    }
    return 0;
}

ass_apply_transition_effects

void ass_apply_transition_effects(RenderContext *state)
{
    ASS_Renderer *render_priv = state->renderer;
    int v[4];
    int cnt;
    ASS_Event *event = state->event;
    char *p = event->Effect;

    if (!p || !*p)
        return;

    cnt = 0;
    while (cnt < 4 && (p = strchr(p, ';'))) {
        v[cnt++] = atoi(++p);
    }

    ASS_Vector layout_res = ass_layout_res(render_priv);
    if (strncmp(event->Effect, "Banner;", 7) == 0) {
        double delay;
        if (cnt < 1) {
            ass_msg(render_priv->library, MSGL_V,
                    "Error parsing effect: '%s'", event->Effect);
            return;
        }
        if (cnt >= 2 && v[1])   // left-to-right
            state->scroll_direction = SCROLL_LR;
        else                    // right-to-left
            state->scroll_direction = SCROLL_RL;

        delay = v[0];
        // VSF works in storage coordinates, but scales delay to PlayRes canvas
        // before applying max(scaled_ delay, 1). This means, if scaled_delay < 1
        // (esp. delay=0) we end up with 1 ms per _storage pixel_ without any
        // PlayRes scaling.
        // The way libass deals with delay, it is automatically relative to the
        // PlayRes canvas, so we only want to "unscale" the small delay values.
        //
        // VSF also casts the scaled delay to int, which if not emulated leads to
        // easily noticeable deviations from VSFilter as the effect goes on.
        // To achieve both we need to keep our Playres-relative delay with high precision,
        // but must temporarily convert to storage-relative and truncate and take the
        // maxuimum there, before converting back.
        double scale_x = ((double) layout_res.x) / render_priv->track->PlayResX;
        delay = ((int) FFMAX(delay / scale_x, 1)) * scale_x;
        state->scroll_shift =
            (render_priv->time - event->Start) / delay;
        state->evt_type |= EVENT_HSCROLL;
        state->detect_collisions = 0;
        state->wrap_style = 2;
        return;
    }

    if (strncmp(event->Effect, "Scroll up;", 10) == 0) {
        state->scroll_direction = SCROLL_BT;
    } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) {
        state->scroll_direction = SCROLL_TB;
    } else {
        ass_msg(render_priv->library, MSGL_DBG2,
                "Unknown transition effect: '%s'", event->Effect);
        return;
    }
    // parse scroll up/down parameters
    {
        double delay;
        int y0, y1;
        if (cnt < 3) {
            ass_msg(render_priv->library, MSGL_V,
                    "Error parsing effect: '%s'", event->Effect);
            return;
        }
        delay = v[2];
        // See explanation for Banner
        double scale_y = ((double) layout_res.y) / render_priv->track->PlayResY;
        delay = ((int) FFMAX(delay / scale_y, 1)) * scale_y;
        state->scroll_shift =
            (render_priv->time - event->Start) / delay;
        if (v[0] < v[1]) {
            y0 = v[0];
            y1 = v[1];
        } else {
            y0 = v[1];
            y1 = v[0];
        }
        state->scroll_y0 = y0;
        state->scroll_y1 = y1;
        state->evt_type |= EVENT_VSCROLL;
        state->detect_collisions = 0;
    }

}

ass_reset_render_context


parse_events

由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 wencoo824。QQ:1419440391。

参考

技术交流

欢迎加微信,搜索"wencoo824",进行技术交流,备注”博客音视频技术交流“

相关推荐

  1. SDWebImage分析

    2024-01-12 02:48:01       12 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-12 02:48:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-12 02:48:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-12 02:48:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-12 02:48:01       20 阅读

热门阅读

  1. 分布式事务(1)

    2024-01-12 02:48:01       34 阅读
  2. Vue2-动态路由传参的基本用法

    2024-01-12 02:48:01       33 阅读
  3. 【前端】vue3打包命令 ,Vue2与Vue3 的命令区别

    2024-01-12 02:48:01       28 阅读
  4. Docker 有什么优势?

    2024-01-12 02:48:01       31 阅读
  5. 【js】js数组对象去重:

    2024-01-12 02:48:01       38 阅读
  6. LeetCode 2651. 计算列车到站时间

    2024-01-12 02:48:01       37 阅读
  7. flask web学习之表单(一)

    2024-01-12 02:48:01       34 阅读
  8. 力扣289. 生命游戏

    2024-01-12 02:48:01       26 阅读
  9. 数学建模 | 一文读懂:支持向量机(matlab源码)

    2024-01-12 02:48:01       33 阅读
  10. CAS-ABA问题编码实战

    2024-01-12 02:48:01       37 阅读