diff --git a/player/command.c b/player/command.c index a5105ffe92..2c44a3f095 100644 --- a/player/command.c +++ b/player/command.c @@ -4017,6 +4017,9 @@ static void recreate_overlays(struct MPContext *mpctx) goto done; } + if (!mp_image_make_writeable(new->packed)) + goto done; + // clear padding mp_image_clear(new->packed, 0, 0, new->packed->w, new->packed->h); diff --git a/sub/ass_mp.c b/sub/ass_mp.c index 172701495f..45328fb920 100644 --- a/sub/ass_mp.c +++ b/sub/ass_mp.c @@ -240,6 +240,11 @@ static bool pack(struct mp_ass_packer *p, struct sub_bitmaps *res, int imgfmt) talloc_steal(p, p->cached_img); } + if (!mp_image_make_writeable(p->cached_img)) { + packer_reset(p->packer); + return false; + } + res->packed = p->cached_img; for (int n = 0; n < res->num_parts; n++) { diff --git a/sub/dec_sub.c b/sub/dec_sub.c index 3842235c46..76e96bc16b 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -109,16 +109,6 @@ static double pts_from_subtitle(struct dec_sub *sub, double pts) return pts; } -void sub_lock(struct dec_sub *sub) -{ - pthread_mutex_lock(&sub->lock); -} - -void sub_unlock(struct dec_sub *sub) -{ - pthread_mutex_unlock(&sub->lock); -} - static void wakeup_demux(void *ctx) { struct mp_dispatch_queue *q = ctx; @@ -335,12 +325,12 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts) return r; } -// You must call sub_lock/sub_unlock if more than 1 thread access sub. -// The issue is that *res will contain decoder allocated data, which might -// be deallocated on the next decoder access. -void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, int format, - double pts, struct sub_bitmaps *res) +// Unref sub_bitmaps.rc to free the result. May return NULL. +struct sub_bitmaps *sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, + int format, double pts) { + pthread_mutex_lock(&sub->lock); + struct mp_subtitle_opts *opts = sub->opts; pts = pts_to_subtitle(sub, pts); @@ -348,11 +338,14 @@ void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, int format, sub->last_vo_pts = pts; update_segment(sub); - if (sub->end != MP_NOPTS_VALUE && pts >= sub->end) - return; + struct sub_bitmaps *res = NULL; - if (opts->sub_visibility && sub->sd->driver->get_bitmaps) - sub->sd->driver->get_bitmaps(sub->sd, dim, format, pts, res); + if (!(sub->end != MP_NOPTS_VALUE && pts >= sub->end) && + opts->sub_visibility && sub->sd->driver->get_bitmaps) + res = sub->sd->driver->get_bitmaps(sub->sd, dim, format, pts); + + pthread_mutex_unlock(&sub->lock); + return res; } // See sub_get_bitmaps() for locking requirements. diff --git a/sub/dec_sub.h b/sub/dec_sub.h index 030b8d21e1..8d0c76cd14 100644 --- a/sub/dec_sub.h +++ b/sub/dec_sub.h @@ -35,14 +35,12 @@ struct attachment_list { struct dec_sub *sub_create(struct mpv_global *global, struct sh_stream *sh, struct attachment_list *attachments); void sub_destroy(struct dec_sub *sub); -void sub_lock(struct dec_sub *sub); -void sub_unlock(struct dec_sub *sub); bool sub_can_preload(struct dec_sub *sub); void sub_preload(struct dec_sub *sub); bool sub_read_packets(struct dec_sub *sub, double video_pts); -void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, int format, - double pts, struct sub_bitmaps *res); +struct sub_bitmaps *sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, + int format, double pts); char *sub_get_text(struct dec_sub *sub, double pts); struct sd_times sub_get_times(struct dec_sub *sub, double pts); void sub_reset(struct dec_sub *sub); diff --git a/sub/draw_bmp.c b/sub/draw_bmp.c index 0c913c86fd..ba027838ec 100644 --- a/sub/draw_bmp.c +++ b/sub/draw_bmp.c @@ -494,11 +494,8 @@ static void chroma_down(struct mp_image *old_src, struct mp_image *temp) } } -// cache: if not NULL, the function will set *cache to a talloc-allocated cache -// containing scaled versions of sbs contents - free the cache with -// talloc_free() -void mp_draw_sub_bitmaps(struct mp_draw_sub_cache **cache, struct mp_image *dst, - struct sub_bitmaps *sbs) +static void draw_sbs(struct mp_draw_sub_cache **cache, struct mp_image *dst, + struct sub_bitmaps *sbs) { assert(mp_draw_sub_formats[sbs->format]); if (!mp_sws_supported_format(dst->imgfmt)) @@ -542,4 +539,14 @@ void mp_draw_sub_bitmaps(struct mp_draw_sub_cache **cache, struct mp_image *dst, } } +// cache: if not NULL, the function will set *cache to a talloc-allocated cache +// containing scaled versions of sbs contents - free the cache with +// talloc_free() +void mp_draw_sub_bitmaps(struct mp_draw_sub_cache **cache, struct mp_image *dst, + struct sub_bitmap_list *sbs_list) +{ + for (int n = 0; n < sbs_list->num_items; n++) + draw_sbs(cache, dst, sbs_list->items[n]); +} + // vim: ts=4 sw=4 et tw=80 diff --git a/sub/draw_bmp.h b/sub/draw_bmp.h index 11fbd0daa0..6adfd2c6c5 100644 --- a/sub/draw_bmp.h +++ b/sub/draw_bmp.h @@ -4,10 +4,9 @@ #include "osd.h" struct mp_image; -struct sub_bitmaps; struct mp_draw_sub_cache; void mp_draw_sub_bitmaps(struct mp_draw_sub_cache **cache, struct mp_image *dst, - struct sub_bitmaps *sbs); + struct sub_bitmap_list *sbs_list); extern const bool mp_draw_sub_formats[SUBBITMAP_COUNT]; diff --git a/sub/osd.c b/sub/osd.c index 93e1a07162..87f9186d21 100644 --- a/sub/osd.c +++ b/sub/osd.c @@ -152,6 +152,7 @@ void osd_free(struct osd_state *osd) if (!osd) return; osd_destroy_backend(osd); + talloc_free(osd->objs[OSDTYPE_EXTERNAL2]->external2); pthread_mutex_destroy(&osd->lock); talloc_free(osd); } @@ -233,8 +234,10 @@ void osd_set_progbar(struct osd_state *osd, struct osd_progbar_state *s) void osd_set_external2(struct osd_state *osd, struct sub_bitmaps *imgs) { pthread_mutex_lock(&osd->lock); - osd->objs[OSDTYPE_EXTERNAL2]->external2 = imgs; - osd->objs[OSDTYPE_EXTERNAL2]->vo_change_id += 1; + struct osd_object *obj = osd->objs[OSDTYPE_EXTERNAL2]; + talloc_free(obj->external2); + obj->external2 = sub_bitmaps_copy(NULL, imgs); + obj->vo_change_id += 1; osd->want_redraw_notification = true; pthread_mutex_unlock(&osd->lock); } @@ -265,48 +268,53 @@ void osd_resize(struct osd_state *osd, struct mp_osd_res res) pthread_mutex_unlock(&osd->lock); } -static void render_object(struct osd_state *osd, struct osd_object *obj, - struct mp_osd_res res, double video_pts, - const bool sub_formats[SUBBITMAP_COUNT], - struct sub_bitmaps *out_imgs) +static struct sub_bitmaps *render_object(struct osd_state *osd, + struct osd_object *obj, + struct mp_osd_res osdres, double video_pts, + const bool sub_formats[SUBBITMAP_COUNT]) { int format = SUBBITMAP_LIBASS; if (!sub_formats[format] || osd->opts->force_rgba_osd) format = SUBBITMAP_RGBA; - *out_imgs = (struct sub_bitmaps) {0}; + struct sub_bitmaps *res = NULL; - check_obj_resize(osd, res, obj); + check_obj_resize(osd, osdres, obj); if (obj->type == OSDTYPE_SUB || obj->type == OSDTYPE_SUB2) { if (obj->sub) - sub_get_bitmaps(obj->sub, obj->vo_res, format, video_pts, out_imgs); + res = sub_get_bitmaps(obj->sub, obj->vo_res, format, video_pts); } else if (obj->type == OSDTYPE_EXTERNAL2) { if (obj->external2 && obj->external2->format) { - *out_imgs = *obj->external2; + res = sub_bitmaps_copy(NULL, obj->external2); // need to be owner obj->external2->change_id = 0; } } else { - osd_object_get_bitmaps(osd, obj, format, out_imgs); + res = osd_object_get_bitmaps(osd, obj, format); } - obj->vo_change_id += out_imgs->change_id; + if (res) { + obj->vo_change_id += res->change_id; - if (out_imgs->num_parts == 0) - return; + res->render_index = obj->type; + res->change_id = obj->vo_change_id; + } - out_imgs->render_index = obj->type; - out_imgs->change_id = obj->vo_change_id; + return res; } +// Render OSD to a list of bitmap and return it. The returned object is +// refcounted. Typically you should hold it only for a short time, and then +// release it. // draw_flags is a bit field of OSD_DRAW_* constants -void osd_draw(struct osd_state *osd, struct mp_osd_res res, - double video_pts, int draw_flags, - const bool formats[SUBBITMAP_COUNT], - void (*cb)(void *ctx, struct sub_bitmaps *imgs), void *cb_ctx) +struct sub_bitmap_list *osd_render(struct osd_state *osd, struct mp_osd_res res, + double video_pts, int draw_flags, + const bool formats[SUBBITMAP_COUNT]) { pthread_mutex_lock(&osd->lock); + struct sub_bitmap_list *list = talloc_zero(NULL, struct sub_bitmap_list); + if (osd->force_video_pts != MP_NOPTS_VALUE) video_pts = osd->force_video_pts; @@ -325,31 +333,26 @@ void osd_draw(struct osd_state *osd, struct mp_osd_res res, if ((draw_flags & OSD_DRAW_OSD_ONLY) && obj->is_sub) continue; - if (obj->sub) - sub_lock(obj->sub); - char *stat_type_render = obj->is_sub ? "sub-render" : "osd-render"; - char *stat_type_draw = obj->is_sub ? "sub-draw" : "osd-draw"; stats_time_start(osd->stats, stat_type_render); - struct sub_bitmaps imgs; - render_object(osd, obj, res, video_pts, formats, &imgs); + struct sub_bitmaps *imgs = + render_object(osd, obj, res, video_pts, formats); stats_time_end(osd->stats, stat_type_render); - if (imgs.num_parts > 0) { - if (formats[imgs.format]) { - stats_time_start(osd->stats, stat_type_draw); - cb(cb_ctx, &imgs); - stats_time_end(osd->stats, stat_type_draw); + if (imgs && imgs->num_parts > 0) { + if (formats[imgs->format]) { + talloc_steal(list, imgs); + MP_TARRAY_APPEND(list, list->items, list->num_items, imgs); + imgs = NULL; } else { MP_ERR(osd, "Can't render OSD part %d (format %d).\n", - obj->type, imgs.format); + obj->type, imgs->format); } } - if (obj->sub) - sub_unlock(obj->sub); + talloc_free(imgs); } // If this is called with OSD_DRAW_SUB_ONLY or OSD_DRAW_OSD_ONLY set, assume @@ -360,35 +363,34 @@ void osd_draw(struct osd_state *osd, struct mp_osd_res res, osd->want_redraw_notification = false; pthread_mutex_unlock(&osd->lock); + return list; } -struct draw_on_image_closure { - struct osd_state *osd; - struct mp_image *dest; - struct mp_image_pool *pool; - bool changed; -}; - -static void draw_on_image(void *ctx, struct sub_bitmaps *imgs) +// Warning: this function should be considered legacy. Use osd_render() instead. +void osd_draw(struct osd_state *osd, struct mp_osd_res res, + double video_pts, int draw_flags, + const bool formats[SUBBITMAP_COUNT], + void (*cb)(void *ctx, struct sub_bitmaps *imgs), void *cb_ctx) { - struct draw_on_image_closure *closure = ctx; - struct osd_state *osd = closure->osd; - if (!mp_image_pool_make_writeable(closure->pool, closure->dest)) - return; // on OOM, skip - mp_draw_sub_bitmaps(&osd->draw_cache, closure->dest, imgs); - talloc_steal(osd, osd->draw_cache); - closure->changed = true; + struct sub_bitmap_list *list = + osd_render(osd, res, video_pts, draw_flags, formats); + + stats_time_start(osd->stats, "draw"); + + for (int n = 0; n < list->num_items; n++) + cb(cb_ctx, list->items[n]); + + stats_time_end(osd->stats, "draw"); + + talloc_free(list); } // Calls mp_image_make_writeable() on the dest image if something is drawn. -// Returns whether anything was drawn. -bool osd_draw_on_image(struct osd_state *osd, struct mp_osd_res res, +// draw_flags as in osd_render(). +void osd_draw_on_image(struct osd_state *osd, struct mp_osd_res res, double video_pts, int draw_flags, struct mp_image *dest) { - struct draw_on_image_closure closure = {osd, dest}; - osd_draw(osd, res, video_pts, draw_flags, mp_draw_sub_formats, - &draw_on_image, &closure); - return closure.changed; + osd_draw_on_image_p(osd, res, video_pts, draw_flags, NULL, dest); } // Like osd_draw_on_image(), but if dest needs to be copied to make it @@ -398,9 +400,26 @@ void osd_draw_on_image_p(struct osd_state *osd, struct mp_osd_res res, double video_pts, int draw_flags, struct mp_image_pool *pool, struct mp_image *dest) { - struct draw_on_image_closure closure = {osd, dest, pool}; - osd_draw(osd, res, video_pts, draw_flags, mp_draw_sub_formats, - &draw_on_image, &closure); + struct sub_bitmap_list *list = + osd_render(osd, res, video_pts, draw_flags, mp_draw_sub_formats); + + if (!list->num_items) { + talloc_free(list); + return; + } + + if (!mp_image_pool_make_writeable(pool, dest)) + return; // on OOM, skip + + // Need to lock for the dumb osd->draw_cache thing. + pthread_mutex_lock(&osd->lock); + + mp_draw_sub_bitmaps(&osd->draw_cache, dest, list); + talloc_steal(osd, osd->draw_cache); + + pthread_mutex_unlock(&osd->lock); + + talloc_free(list); } // Setup the OSD resolution to render into an image with the given parameters. @@ -471,3 +490,40 @@ void osd_rescale_bitmaps(struct sub_bitmaps *imgs, int frame_w, int frame_h, bi->dh = (int)(bi->h * yscale + 0.5); } } + +// Copy *in and return a new allocation of it. Free with talloc_free(). This +// will contain a refcounted copy of the image data. +// +// in->packed must be set and must be a refcounted image, unless there is no +// data (num_parts==0). +// +// p_cache: if not NULL, then this points to a struct sub_bitmap_copy_cache* +// variable. The function may set this to an allocation and may later +// read it. You have to free it with talloc_free() when done. +// in: valid struct, or NULL (in this case it also returns NULL) +// returns: new copy, or NULL if there was no data in the input +struct sub_bitmaps *sub_bitmaps_copy(struct sub_bitmap_copy_cache **p_cache, + struct sub_bitmaps *in) +{ + if (!in || !in->num_parts) + return NULL; + + struct sub_bitmaps *res = talloc(NULL, struct sub_bitmaps); + *res = *in; + + // Note: the p_cache thing is a lie and unused. + + // The bitmaps being refcounted is essential for performance, and for + // not invalidating in->parts[*].bitmap pointers. + assert(in->packed && in->packed->bufs[0]); + + res->packed = mp_image_new_ref(res->packed); + MP_HANDLE_OOM(res->packed); + talloc_steal(res, res->packed); + + res->parts = NULL; + MP_RESIZE_ARRAY(res, res->parts, res->num_parts); + memcpy(res->parts, in->parts, sizeof(res->parts[0]) * res->num_parts); + + return res; +} diff --git a/sub/osd.h b/sub/osd.h index 5a6809817f..207d789094 100644 --- a/sub/osd.h +++ b/sub/osd.h @@ -76,6 +76,15 @@ struct sub_bitmaps { int change_id; // Incremented on each change }; +struct sub_bitmap_list { + struct sub_bitmaps **items; + int num_items; +}; + +struct sub_bitmap_copy_cache; +struct sub_bitmaps *sub_bitmaps_copy(struct sub_bitmap_copy_cache **cache, + struct sub_bitmaps *in); + struct mp_osd_res { int w, h; // screen dimensions, including black borders int mt, mb, ml, mr; // borders (top, bottom, left, right) @@ -183,8 +192,12 @@ void osd_draw(struct osd_state *osd, struct mp_osd_res res, const bool formats[SUBBITMAP_COUNT], void (*cb)(void *ctx, struct sub_bitmaps *imgs), void *cb_ctx); +struct sub_bitmap_list *osd_render(struct osd_state *osd, struct mp_osd_res res, + double video_pts, int draw_flags, + const bool formats[SUBBITMAP_COUNT]); + struct mp_image; -bool osd_draw_on_image(struct osd_state *osd, struct mp_osd_res res, +void osd_draw_on_image(struct osd_state *osd, struct mp_osd_res res, double video_pts, int draw_flags, struct mp_image *dest); struct mp_image_pool; diff --git a/sub/osd_libass.c b/sub/osd_libass.c index bf527e17f3..9aac0eb3ae 100644 --- a/sub/osd_libass.c +++ b/sub/osd_libass.c @@ -634,8 +634,8 @@ static void append_ass(struct ass_state *ass, struct mp_osd_res *res, } } -void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj, - int format, struct sub_bitmaps *out_imgs) +struct sub_bitmaps *osd_object_get_bitmaps(struct osd_state *osd, + struct osd_object *obj, int format) { if (obj->type == OSDTYPE_OSD && obj->osd_changed) update_osd(osd, obj); @@ -656,8 +656,11 @@ void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj, } } + struct sub_bitmaps out_imgs = {0}; mp_ass_packer_pack(obj->ass_packer, obj->ass_imgs, obj->num_externals + 1, - obj->changed, format, out_imgs); + obj->changed, format, &out_imgs); obj->changed = false; + + return sub_bitmaps_copy(&obj->copy_cache, &out_imgs); } diff --git a/sub/osd_state.h b/sub/osd_state.h index 3469f4383d..054a98775b 100644 --- a/sub/osd_state.h +++ b/sub/osd_state.h @@ -54,6 +54,7 @@ struct osd_object { bool changed; struct ass_state ass; struct mp_ass_packer *ass_packer; + struct sub_bitmap_copy_cache *copy_cache; struct ass_image **ass_imgs; }; @@ -82,9 +83,9 @@ struct osd_state { struct mp_draw_sub_cache *draw_cache; }; -// defined in osd_libass.c and osd_dummy.c -void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj, - int format, struct sub_bitmaps *out_imgs); +// defined in osd_libass.c +struct sub_bitmaps *osd_object_get_bitmaps(struct osd_state *osd, + struct osd_object *obj, int format); void osd_init_backend(struct osd_state *osd); void osd_destroy_backend(struct osd_state *osd); diff --git a/sub/sd.h b/sub/sd.h index a3085be864..d3d70bf594 100644 --- a/sub/sd.h +++ b/sub/sd.h @@ -38,8 +38,8 @@ struct sd_functions { bool (*accepts_packet)(struct sd *sd, double pts); // implicit default if NULL: true int (*control)(struct sd *sd, enum sd_ctrl cmd, void *arg); - void (*get_bitmaps)(struct sd *sd, struct mp_osd_res dim, int format, - double pts, struct sub_bitmaps *res); + struct sub_bitmaps *(*get_bitmaps)(struct sd *sd, struct mp_osd_res dim, + int format, double pts); char *(*get_text)(struct sd *sd, double pts); struct sd_times (*get_times)(struct sd *sd, double pts); }; diff --git a/sub/sd_ass.c b/sub/sd_ass.c index 40e5093d8e..d51f892dd4 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -50,7 +50,7 @@ struct sd_ass_priv { bool clear_once; bool on_top; struct mp_ass_packer *packer; - struct sub_bitmap *bs; + struct sub_bitmap_copy_cache *copy_cache; char last_text[500]; struct mp_image_params video_params; struct mp_image_params last_params; @@ -498,8 +498,8 @@ static long long find_timestamp(struct sd *sd, double pts) #undef END -static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, int format, - double pts, struct sub_bitmaps *res) +static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim, + int format, double pts) { struct sd_ass_priv *ctx = sd->priv; struct mp_subtitle_opts *opts = sd->opts; @@ -508,9 +508,10 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, int format, bool converted = ctx->is_converted || no_ass; ASS_Track *track = no_ass ? ctx->shadow_track : ctx->ass_track; ASS_Renderer *renderer = ctx->ass_renderer; + struct sub_bitmaps *res = &(struct sub_bitmaps){0}; if (pts == MP_NOPTS_VALUE || !renderer) - return; + goto done; double scale = dim.display_par; if (!converted && (!opts->ass_style_override || @@ -544,14 +545,14 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, int format, ASS_Image *imgs = ass_render_frame(renderer, track, ts, &changed); mp_ass_packer_pack(ctx->packer, &imgs, 1, changed, format, res); - if (!converted && res->num_parts > 0) { - // mangle_colors() modifies the color field, so copy the thing. - MP_TARRAY_GROW(ctx, ctx->bs, res->num_parts); - memcpy(ctx->bs, res->parts, sizeof(ctx->bs[0]) * res->num_parts); - res->parts = ctx->bs; +done: + // mangle_colors() modifies the color field, so copy the thing _before_. + res = sub_bitmaps_copy(&ctx->copy_cache, res); + if (!converted && res) mangle_colors(sd, res); - } + + return res; } struct buf { @@ -752,6 +753,7 @@ static void uninit(struct sd *sd) ass_free_track(ctx->shadow_track); enable_output(sd, false); ass_library_done(ctx->ass_library); + talloc_free(ctx->copy_cache); } static int control(struct sd *sd, enum sd_ctrl cmd, void *arg) diff --git a/sub/sd_lavc.c b/sub/sd_lavc.c index 3d2f137dd3..620b3f85a7 100644 --- a/sub/sd_lavc.c +++ b/sub/sd_lavc.c @@ -226,6 +226,11 @@ static void read_sub_bitmaps(struct sd *sd, struct sub *sub) talloc_steal(priv, sub->data); } + if (!mp_image_make_writeable(sub->data)) { + sub->count = 0; + return; + } + for (int i = 0; i < sub->count; i++) { struct sub_bitmap *b = &sub->inbitmaps[i]; struct pos pos = priv->packer->result[i]; @@ -400,8 +405,8 @@ static struct sub *get_current(struct sd_lavc_priv *priv, double pts) return current; } -static void get_bitmaps(struct sd *sd, struct mp_osd_res d, int format, - double pts, struct sub_bitmaps *res) +static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res d, + int format, double pts) { struct sd_lavc_priv *priv = sd->priv; struct mp_subtitle_opts *opts = sd->opts; @@ -411,12 +416,13 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res d, int format, struct sub *current = get_current(priv, pts); if (!current) - return; + return NULL; MP_TARRAY_GROW(priv, priv->outbitmaps, current->count); for (int n = 0; n < current->count; n++) priv->outbitmaps[n] = current->inbitmaps[n]; + struct sub_bitmaps *res = &(struct sub_bitmaps){0}; res->parts = priv->outbitmaps; res->num_parts = current->count; if (priv->displayed_id != current->id) @@ -484,6 +490,7 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res d, int format, } } + return sub_bitmaps_copy(NULL, res); } static struct sd_times get_times(struct sd *sd, double pts)