diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
commit | 51de1d8436100f725f3576aefa24a2bd2057bc28 (patch) | |
tree | c6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /sub/sd_lavc.c | |
parent | Initial commit. (diff) | |
download | mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip |
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sub/sd_lavc.c')
-rw-r--r-- | sub/sd_lavc.c | 676 |
1 files changed, 676 insertions, 0 deletions
diff --git a/sub/sd_lavc.c b/sub/sd_lavc.c new file mode 100644 index 0000000..30aa641 --- /dev/null +++ b/sub/sd_lavc.c @@ -0,0 +1,676 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <assert.h> +#include <math.h> + +#include <libavcodec/avcodec.h> +#include <libavutil/common.h> +#include <libavutil/intreadwrite.h> +#include <libavutil/opt.h> + +#include "mpv_talloc.h" +#include "common/msg.h" +#include "common/av_common.h" +#include "demux/stheader.h" +#include "options/options.h" +#include "video/mp_image.h" +#include "video/out/bitmap_packer.h" +#include "img_convert.h" +#include "sd.h" +#include "dec_sub.h" + +#define MAX_QUEUE 4 + +struct sub { + bool valid; + AVSubtitle avsub; + struct sub_bitmap *inbitmaps; + int count; + struct mp_image *data; + int bound_w, bound_h; + int src_w, src_h; + double pts; + double endpts; + int64_t id; +}; + +struct seekpoint { + double pts; + double endpts; +}; + +struct sd_lavc_priv { + AVCodecContext *avctx; + AVPacket *avpkt; + AVRational pkt_timebase; + struct sub subs[MAX_QUEUE]; // most recent event first + struct sub_bitmap *outbitmaps; + struct sub_bitmap *prevret; + int prevret_num; + int64_t displayed_id; + int64_t new_id; + struct mp_image_params video_params; + double current_pts; + struct seekpoint *seekpoints; + int num_seekpoints; + struct bitmap_packer *packer; +}; + +static int init(struct sd *sd) +{ + enum AVCodecID cid = mp_codec_to_av_codec_id(sd->codec->codec); + + // Supported codecs must be known to decode to paletted bitmaps + switch (cid) { + case AV_CODEC_ID_DVB_SUBTITLE: + case AV_CODEC_ID_DVB_TELETEXT: + case AV_CODEC_ID_HDMV_PGS_SUBTITLE: + case AV_CODEC_ID_XSUB: + case AV_CODEC_ID_DVD_SUBTITLE: + break; + default: + return -1; + } + + struct sd_lavc_priv *priv = talloc_zero(NULL, struct sd_lavc_priv); + AVCodecContext *ctx = NULL; + const AVCodec *sub_codec = avcodec_find_decoder(cid); + if (!sub_codec) + goto error; + ctx = avcodec_alloc_context3(sub_codec); + if (!ctx) + goto error; + priv->avpkt = av_packet_alloc(); + if (!priv->avpkt) + goto error; + if (mp_set_avctx_codec_headers(ctx, sd->codec) < 0) + goto error; + priv->pkt_timebase = mp_get_codec_timebase(sd->codec); + ctx->pkt_timebase = priv->pkt_timebase; + if (avcodec_open2(ctx, sub_codec, NULL) < 0) + goto error; + priv->avctx = ctx; + sd->priv = priv; + priv->displayed_id = -1; + priv->current_pts = MP_NOPTS_VALUE; + priv->packer = talloc_zero(priv, struct bitmap_packer); + return 0; + + error: + MP_FATAL(sd, "Could not open libavcodec subtitle decoder\n"); + avcodec_free_context(&ctx); + mp_free_av_packet(&priv->avpkt); + talloc_free(priv); + return -1; +} + +static void clear_sub(struct sub *sub) +{ + sub->count = 0; + sub->pts = MP_NOPTS_VALUE; + sub->endpts = MP_NOPTS_VALUE; + if (sub->valid) + avsubtitle_free(&sub->avsub); + sub->valid = false; +} + +static void alloc_sub(struct sd_lavc_priv *priv) +{ + clear_sub(&priv->subs[MAX_QUEUE - 1]); + struct sub tmp = priv->subs[MAX_QUEUE - 1]; + for (int n = MAX_QUEUE - 1; n > 0; n--) + priv->subs[n] = priv->subs[n - 1]; + priv->subs[0] = tmp; + // clear only some fields; the memory allocs can be reused + priv->subs[0].valid = false; + priv->subs[0].count = 0; + priv->subs[0].src_w = 0; + priv->subs[0].src_h = 0; + priv->subs[0].id = priv->new_id++; +} + +static void convert_pal(uint32_t *colors, size_t count, bool gray) +{ + for (int n = 0; n < count; n++) { + uint32_t c = colors[n]; + uint32_t b = c & 0xFF; + uint32_t g = (c >> 8) & 0xFF; + uint32_t r = (c >> 16) & 0xFF; + uint32_t a = (c >> 24) & 0xFF; + if (gray) + r = g = b = (r + g + b) / 3; + // from straight to pre-multiplied alpha + b = b * a / 255; + g = g * a / 255; + r = r * a / 255; + colors[n] = b | (g << 8) | (r << 16) | (a << 24); + } +} + +// Initialize sub from sub->avsub. +static void read_sub_bitmaps(struct sd *sd, struct sub *sub) +{ + struct mp_subtitle_opts *opts = sd->opts; + struct sd_lavc_priv *priv = sd->priv; + AVSubtitle *avsub = &sub->avsub; + + MP_TARRAY_GROW(priv, sub->inbitmaps, avsub->num_rects); + + packer_set_size(priv->packer, avsub->num_rects); + + // If we blur, we want a transparent region around the bitmap data to + // avoid "cut off" artifacts on the borders. + bool apply_blur = opts->sub_gauss != 0.0f; + int extend = apply_blur ? 5 : 0; + // Assume consumers may use bilinear scaling on it (2x2 filter) + int padding = 1 + extend; + + priv->packer->padding = padding; + + // For the sake of libswscale, which in some cases takes sub-rects as + // source images, and wants 16 byte start pointer and stride alignment. + int align = 4; + + for (int i = 0; i < avsub->num_rects; i++) { + struct AVSubtitleRect *r = avsub->rects[i]; + struct sub_bitmap *b = &sub->inbitmaps[sub->count]; + + if (r->type != SUBTITLE_BITMAP) { + MP_ERR(sd, "unsupported subtitle type from libavcodec\n"); + continue; + } + if (!(r->flags & AV_SUBTITLE_FLAG_FORCED) && opts->sub_forced_events_only) + continue; + if (r->w <= 0 || r->h <= 0) + continue; + + b->bitmap = r; // save for later (dumb hack to avoid more complexity) + + priv->packer->in[sub->count] = (struct pos){r->w + (align - 1), r->h}; + sub->count++; + } + + priv->packer->count = sub->count; + + if (packer_pack(priv->packer) < 0) { + MP_ERR(sd, "Unable to pack subtitle bitmaps.\n"); + sub->count = 0; + } + + if (!sub->count) + return; + + struct pos bb[2]; + packer_get_bb(priv->packer, bb); + + sub->bound_w = bb[1].x; + sub->bound_h = bb[1].y; + + if (!sub->data || sub->data->w < sub->bound_w || sub->data->h < sub->bound_h) { + talloc_free(sub->data); + sub->data = mp_image_alloc(IMGFMT_BGRA, priv->packer->w, priv->packer->h); + if (!sub->data) { + sub->count = 0; + return; + } + 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]; + struct AVSubtitleRect *r = b->bitmap; + uint8_t **data = r->data; + int *linesize = r->linesize; + b->w = r->w; + b->h = r->h; + b->x = r->x; + b->y = r->y; + + // Choose such that the extended start position is aligned. + pos.x = MP_ALIGN_UP(pos.x - extend, align) + extend; + + b->src_x = pos.x; + b->src_y = pos.y; + b->stride = sub->data->stride[0]; + b->bitmap = sub->data->planes[0] + pos.y * b->stride + pos.x * 4; + + sub->src_w = MPMAX(sub->src_w, b->x + b->w); + sub->src_h = MPMAX(sub->src_h, b->y + b->h); + + assert(r->nb_colors > 0); + assert(r->nb_colors <= 256); + uint32_t pal[256] = {0}; + memcpy(pal, data[1], r->nb_colors * 4); + convert_pal(pal, 256, opts->sub_gray); + + for (int y = -padding; y < b->h + padding; y++) { + uint32_t *out = (uint32_t*)((char*)b->bitmap + y * b->stride); + int start = 0; + for (int x = -padding; x < 0; x++) + out[x] = 0; + if (y >= 0 && y < b->h) { + uint8_t *in = data[0] + y * linesize[0]; + for (int x = 0; x < b->w; x++) + *out++ = pal[*in++]; + start = b->w; + } + for (int x = start; x < b->w + padding; x++) + *out++ = 0; + } + + b->bitmap = (char*)b->bitmap - extend * b->stride - extend * 4; + b->src_x -= extend; + b->src_y -= extend; + b->x -= extend; + b->y -= extend; + b->w += extend * 2; + b->h += extend * 2; + + if (apply_blur) + mp_blur_rgba_sub_bitmap(b, opts->sub_gauss); + } +} + +static void decode(struct sd *sd, struct demux_packet *packet) +{ + struct mp_subtitle_opts *opts = sd->opts; + struct sd_lavc_priv *priv = sd->priv; + AVCodecContext *ctx = priv->avctx; + double pts = packet->pts; + double endpts = MP_NOPTS_VALUE; + AVSubtitle sub; + + if (pts == MP_NOPTS_VALUE) + MP_WARN(sd, "Subtitle with unknown start time.\n"); + + mp_set_av_packet(priv->avpkt, packet, &priv->pkt_timebase); + + if (ctx->codec_id == AV_CODEC_ID_DVB_TELETEXT) { + char page[4]; + snprintf(page, sizeof(page), "%d", opts->teletext_page); + av_opt_set(ctx, "txt_page", page, AV_OPT_SEARCH_CHILDREN); + } + + int got_sub; + int res = avcodec_decode_subtitle2(ctx, &sub, &got_sub, priv->avpkt); + if (res < 0 || !got_sub) + return; + + if (sub.pts != AV_NOPTS_VALUE) + pts = sub.pts / (double)AV_TIME_BASE; + + if (pts != MP_NOPTS_VALUE) { + if (sub.end_display_time > sub.start_display_time && + sub.end_display_time != UINT32_MAX) + { + endpts = pts + sub.end_display_time / 1000.0; + } + pts += sub.start_display_time / 1000.0; + + // set end time of previous sub + struct sub *prev = &priv->subs[0]; + if (prev->valid) { + if (prev->endpts == MP_NOPTS_VALUE || prev->endpts > pts) + prev->endpts = pts; + + if (opts->sub_fix_timing && pts - prev->endpts <= SUB_GAP_THRESHOLD) + prev->endpts = pts; + + for (int n = 0; n < priv->num_seekpoints; n++) { + if (priv->seekpoints[n].pts == prev->pts) { + priv->seekpoints[n].endpts = prev->endpts; + break; + } + } + } + + // This subtitle packet only signals the end of subtitle display. + if (!sub.num_rects) { + avsubtitle_free(&sub); + return; + } + } + + alloc_sub(priv); + struct sub *current = &priv->subs[0]; + + current->valid = true; + current->pts = pts; + current->endpts = endpts; + current->avsub = sub; + + read_sub_bitmaps(sd, current); + + if (pts != MP_NOPTS_VALUE) { + for (int n = 0; n < priv->num_seekpoints; n++) { + if (priv->seekpoints[n].pts == pts) + goto skip; + } + // Set arbitrary limit as safe-guard against insane files. + if (priv->num_seekpoints >= 10000) + MP_TARRAY_REMOVE_AT(priv->seekpoints, priv->num_seekpoints, 0); + MP_TARRAY_APPEND(priv, priv->seekpoints, priv->num_seekpoints, + (struct seekpoint){.pts = pts, .endpts = endpts}); + skip: ; + } +} + +static struct sub *get_current(struct sd_lavc_priv *priv, double pts) +{ + struct sub *current = NULL; + for (int n = 0; n < MAX_QUEUE; n++) { + struct sub *sub = &priv->subs[n]; + if (!sub->valid) + continue; + if (pts == MP_NOPTS_VALUE || + ((sub->pts == MP_NOPTS_VALUE || pts + 1e-6 >= sub->pts) && + (sub->endpts == MP_NOPTS_VALUE || pts + 1e-6 < sub->endpts))) + { + // Ignore "trailing" subtitles with unknown length after 1 minute. + if (sub->endpts == MP_NOPTS_VALUE && pts >= sub->pts + 60) + break; + current = sub; + break; + } + } + return current; +} + +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; + + priv->current_pts = pts; + + struct sub *current = get_current(priv, pts); + + if (!current) + 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) + res->change_id++; + priv->displayed_id = current->id; + res->packed = current->data; + res->packed_w = current->bound_w; + res->packed_h = current->bound_h; + res->format = SUBBITMAP_BGRA; + + double video_par = 0; + if (priv->avctx->codec_id == AV_CODEC_ID_DVD_SUBTITLE && + opts->stretch_dvd_subs) + { + // For DVD subs, try to keep the subtitle PAR at display PAR. + double par = priv->video_params.p_w / (double)priv->video_params.p_h; + if (isnormal(par)) + video_par = par; + } + if (priv->avctx->codec_id == AV_CODEC_ID_HDMV_PGS_SUBTITLE) + video_par = -1; + if (opts->stretch_image_subs) + d.ml = d.mr = d.mt = d.mb = 0; + int w = priv->avctx->width; + int h = priv->avctx->height; + if (w <= 0 || h <= 0 || opts->image_subs_video_res) { + w = priv->video_params.w; + h = priv->video_params.h; + } + if (current->src_w > w || current->src_h > h) { + w = MPMAX(priv->video_params.w, current->src_w); + h = MPMAX(priv->video_params.h, current->src_h); + } + + if (opts->sub_pos != 100.0f && opts->ass_style_override) { + float offset = (100.0f - opts->sub_pos) / 100.0f * h; + + for (int n = 0; n < res->num_parts; n++) { + struct sub_bitmap *sub = &res->parts[n]; + + // Decide by heuristic whether this is a sub-title or something + // else (top-title, covering whole screen). + if (sub->y < h / 2) + continue; + + // Allow moving up the subtitle, but only until it clips. + sub->y = MPMAX(sub->y - offset, 0); + sub->y = MPMIN(sub->y + sub->h, h) - sub->h; + } + } + + osd_rescale_bitmaps(res, w, h, d, video_par); + + if (opts->sub_scale != 1.0 && opts->ass_style_override) { + for (int n = 0; n < res->num_parts; n++) { + struct sub_bitmap *sub = &res->parts[n]; + + float shit = (opts->sub_scale - 1.0f) / 2; + + // Fortunately VO isn't supposed to give a FUCKING FUCK about + // whether the sub might e.g. go outside of the screen. + sub->x -= sub->dw * shit; + sub->y -= sub->dh * shit; + sub->dw += sub->dw * shit * 2; + sub->dh += sub->dh * shit * 2; + } + } + + if (priv->prevret_num != res->num_parts) + res->change_id++; + + if (!res->change_id) { + assert(priv->prevret_num == res->num_parts); + for (int n = 0; n < priv->prevret_num; n++) { + struct sub_bitmap *a = &res->parts[n]; + struct sub_bitmap *b = &priv->prevret[n]; + + if (a->x != b->x || a->y != b->y || + a->dw != b->dw || a->dh != b->dh) + { + res->change_id++; + break; + } + } + } + + priv->prevret_num = res->num_parts; + MP_TARRAY_GROW(priv, priv->prevret, priv->prevret_num); + memcpy(priv->prevret, res->parts, res->num_parts * sizeof(priv->prevret[0])); + + return sub_bitmaps_copy(NULL, res); +} + +static struct sd_times get_times(struct sd *sd, double pts) +{ + struct sd_lavc_priv *priv = sd->priv; + struct sd_times res = { .start = MP_NOPTS_VALUE, .end = MP_NOPTS_VALUE }; + + if (pts == MP_NOPTS_VALUE) + return res; + + struct sub *current = get_current(priv, pts); + + if (!current) + return res; + + res.start = current->pts; + res.end = current->endpts; + + return res; +} + +static bool accepts_packet(struct sd *sd, double min_pts) +{ + struct sd_lavc_priv *priv = sd->priv; + + double pts = priv->current_pts; + if (min_pts != MP_NOPTS_VALUE) { + // guard against bogus rendering PTS in the future. + if (pts == MP_NOPTS_VALUE || min_pts < pts) + pts = min_pts; + // Heuristic: we assume rendering cannot lag behind more than 1 second + // behind decoding. + if (pts + 1 < min_pts) + pts = min_pts; + } + + int last_needed = -1; + for (int n = 0; n < MAX_QUEUE; n++) { + struct sub *sub = &priv->subs[n]; + if (!sub->valid) + continue; + if (pts == MP_NOPTS_VALUE || + ((sub->pts == MP_NOPTS_VALUE || sub->pts >= pts) || + (sub->endpts == MP_NOPTS_VALUE || pts < sub->endpts))) + { + last_needed = n; + } + } + // We can accept a packet if it wouldn't overflow the fixed subtitle queue. + // We assume that get_bitmaps() never decreases the PTS. + return last_needed + 1 < MAX_QUEUE; +} + +static void reset(struct sd *sd) +{ + struct sd_lavc_priv *priv = sd->priv; + + for (int n = 0; n < MAX_QUEUE; n++) + clear_sub(&priv->subs[n]); + // lavc might not do this right for all codecs; may need close+reopen + avcodec_flush_buffers(priv->avctx); + + priv->current_pts = MP_NOPTS_VALUE; +} + +static void uninit(struct sd *sd) +{ + struct sd_lavc_priv *priv = sd->priv; + + for (int n = 0; n < MAX_QUEUE; n++) + clear_sub(&priv->subs[n]); + avcodec_free_context(&priv->avctx); + mp_free_av_packet(&priv->avpkt); + talloc_free(priv); +} + +static int compare_seekpoint(const void *pa, const void *pb) +{ + const struct seekpoint *a = pa, *b = pb; + return a->pts == b->pts ? 0 : (a->pts < b->pts ? -1 : +1); +} + +// taken from ass_step_sub(), libass (ISC) +static double step_sub(struct sd *sd, double now, int movement) +{ + struct sd_lavc_priv *priv = sd->priv; + int best = -1; + double target = now; + int direction = (movement > 0 ? 1 : -1) * !!movement; + + if (priv->num_seekpoints == 0) + return MP_NOPTS_VALUE; + + qsort(priv->seekpoints, priv->num_seekpoints, sizeof(priv->seekpoints[0]), + compare_seekpoint); + + do { + int closest = -1; + double closest_time = 0; + for (int i = 0; i < priv->num_seekpoints; i++) { + struct seekpoint *p = &priv->seekpoints[i]; + double start = p->pts; + if (direction < 0) { + double end = p->endpts == MP_NOPTS_VALUE ? INFINITY : p->endpts; + if (end < target) { + if (closest < 0 || end > closest_time) { + closest = i; + closest_time = end; + } + } + } else if (direction > 0) { + if (start > target) { + if (closest < 0 || start < closest_time) { + closest = i; + closest_time = start; + } + } + } else { + if (start < target) { + if (closest < 0 || start >= closest_time) { + closest = i; + closest_time = start; + } + } + } + } + if (closest < 0) + break; + target = closest_time + direction; + best = closest; + movement -= direction; + } while (movement); + + return best < 0 ? now : priv->seekpoints[best].pts; +} + +static int control(struct sd *sd, enum sd_ctrl cmd, void *arg) +{ + struct sd_lavc_priv *priv = sd->priv; + switch (cmd) { + case SD_CTRL_SUB_STEP: { + double *a = arg; + double res = step_sub(sd, a[0], a[1]); + if (res == MP_NOPTS_VALUE) + return false; + a[0] = res; + return true; + } + case SD_CTRL_SET_VIDEO_PARAMS: + priv->video_params = *(struct mp_image_params *)arg; + return CONTROL_OK; + default: + return CONTROL_UNKNOWN; + } +} + +const struct sd_functions sd_lavc = { + .name = "lavc", + .init = init, + .decode = decode, + .get_bitmaps = get_bitmaps, + .get_times = get_times, + .accepts_packet = accepts_packet, + .control = control, + .reset = reset, + .uninit = uninit, +}; |