1
mirror of https://github.com/mpv-player/mpv synced 2025-01-01 04:36:24 +01:00

subs, vo_vdpau: support RGBA color for PGS subtitles

Support passing bitmap subtitles to VOs in full RGBA color, and
implement this for libavcodec-decoded subtitle formats on decoding
side and vo_vdpau on display side. Currently this is enabled for PGS
(blu-ray) and DVB subtitles.

VDPAU seems to have sampling issues similar to known GL ones when
drawing a sub-rectangle from a larger texture with scaling, where
adjacent pixels outside the specified source rectangle affect the
result. As the bitmap subtitles may be scaled, add padding support to
the bitmap packer code.

In principle, this could be used for colored DVD subtitles too.
However, the libavcodec DVD decoder lacks parts of the resolution and
palette handling that are present in spudec.c.

Conflicts:
	libvo/vo_gl.c
	sub/dec_sub.h
	sub/sd_lavc.c
This commit is contained in:
Uoti Urpala 2012-08-31 15:42:30 +03:00 committed by wm4
parent ae9c3d530c
commit 1959ba006c
11 changed files with 273 additions and 52 deletions

View File

@ -364,6 +364,7 @@ static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts)
osd->normal_scale = vf->priv->aspect_correction;
osd->vsfilter_scale = 1;
osd->sub_pts = pts - osd->sub_offset;
osd->support_rgba = false;
struct sub_bitmaps b;
sub_get_bitmaps(osd, &b);
images = b.imgs;

View File

@ -119,6 +119,7 @@ static int control(struct vf_instance *vf, int request, void *data)
}
case VFCTRL_DRAW_EOSD: {
struct osd_state *osd = data;
osd->support_rgba = vf->default_caps & VFCAP_EOSD_RGBA;
osd->dim = (struct mp_eosd_res){0};
if (!video_out->config_ok ||
vo_control(video_out, VOCTRL_GET_EOSD_RES, &osd->dim) != true)

View File

@ -48,5 +48,6 @@
#define VOCAP_NOSLICES 0x8000
#define VFCAP_OSD_FILTER 0x10000 // OSD is drawn in filter chain
#define VFCAP_EOSD_FILTER 0x20000 // EOSD is drawn in filter chain
#define VFCAP_EOSD_RGBA 0x40000
#endif /* MPLAYER_VFCAP_H */

View File

@ -28,6 +28,7 @@
#include "mp_msg.h"
#include "mpcommon.h"
#include "sub/ass_mp.h"
#include "sub/dec_sub.h"
#define HEIGHT_SORT_BITS 4
@ -55,7 +56,7 @@ static int size_index(int s)
* free rectangle with corners (13, 20)-(w, 50) is filled recursively.
*/
static int pack_rectangles(struct pos *in, struct pos *out, int num_rects,
int w, int h, int *scratch)
int w, int h, int *scratch, int *used_width)
{
int bins[16 << HEIGHT_SORT_BITS];
int sizes[16 << HEIGHT_SORT_BITS] = { 0 };
@ -100,11 +101,12 @@ static int pack_rectangles(struct pos *in, struct pos *out, int num_rects,
s.x = right;
maxy = FFMAX(maxy, bottom);
}
*used_width = FFMAX(*used_width, s.x);
if (maxy > 0)
s.bottom = maxy;
}
}
return num_rects ? -1 : 0;
return num_rects ? -1 : y;
}
int packer_pack(struct bitmap_packer *packer)
@ -115,7 +117,7 @@ int packer_pack(struct bitmap_packer *packer)
struct pos *in = packer->in;
int xmax = 0, ymax = 0;
for (int i = 0; i < packer->count; i++) {
if (in[i].x == 0 || in[i].y == 0)
if (in[i].x <= packer->padding || in[i].y <= packer->padding)
in[i] = (struct pos){0, 0};
if (in[i].x < 0 || in [i].x > 65535 || in[i].y < 0 || in[i].y > 65535) {
mp_msg(MSGT_VO, MSGL_FATAL, "Invalid OSD / subtitle bitmap size\n");
@ -124,20 +126,33 @@ int packer_pack(struct bitmap_packer *packer)
xmax = FFMAX(xmax, in[i].x);
ymax = FFMAX(ymax, in[i].y);
}
xmax = FFMAX(0, xmax - packer->padding);
ymax = FFMAX(0, ymax - packer->padding);
if (xmax > packer->w)
packer->w = 1 << av_log2(xmax - 1) + 1;
if (ymax > packer->h)
packer->h = 1 << av_log2(ymax - 1) + 1;
while (1) {
if (pack_rectangles(in, packer->result, packer->count, packer->w,
packer->h, packer->scratch) >= 0)
int used_width = 0;
int y = pack_rectangles(in, packer->result, packer->count,
packer->w + packer->padding,
packer->h + packer->padding,
packer->scratch, &used_width);
if (y >= 0) {
// No padding at edges
packer->used_width = FFMIN(used_width, packer->w);
packer->used_height = FFMIN(y, packer->h);
return packer->w != w_orig || packer->h != h_orig;
}
if (packer->w <= packer->h && packer->w != packer->w_max)
packer->w = FFMIN(packer->w * 2, packer->w_max);
else if (packer->h != packer->h_max)
packer->h = FFMIN(packer->h * 2, packer->h_max);
else
else {
packer->w = w_orig;
packer->h = h_orig;
return -1;
}
}
}
@ -156,8 +171,8 @@ void packer_set_size(struct bitmap_packer *packer, int size)
packer->asize + 16);
}
int packer_pack_from_assimg(struct bitmap_packer *packer,
struct ass_image *imglist)
static int packer_pack_from_assimg(struct bitmap_packer *packer,
struct ass_image *imglist)
{
int count = 0;
struct ass_image *img = imglist;
@ -172,3 +187,20 @@ int packer_pack_from_assimg(struct bitmap_packer *packer,
packer->count = count;
return packer_pack(packer);
}
int packer_pack_from_subbitmaps(struct bitmap_packer *packer,
struct sub_bitmaps *b, int padding_pixels)
{
packer->padding = 0;
packer->count = 0;
if (b->type == SUBBITMAP_EMPTY)
return 0;
if (b->type == SUBBITMAP_LIBASS)
return packer_pack_from_assimg(packer, b->imgs);
packer->padding = padding_pixels;
packer_set_size(packer, b->part_count);
int a = packer->padding;
for (int i = 0; i < b->part_count; i++)
packer->in[i] = (struct pos){b->parts[i].w + a, b->parts[i].h + a};
return packer_pack(packer);
}

View File

@ -11,9 +11,12 @@ struct bitmap_packer {
int h;
int w_max;
int h_max;
int padding;
int count;
struct pos *in;
struct pos *result;
int used_width;
int used_height;
// internal
int *scratch;
@ -21,6 +24,7 @@ struct bitmap_packer {
};
struct ass_image;
struct sub_bitmaps;
/* Reallocate packer->in for at least to desired number of items.
* Also sets packer->count to the same value.
@ -41,7 +45,7 @@ int packer_pack(struct bitmap_packer *packer);
* packer->in will be reallocated if needed and filled from the
* given image list.
*/
int packer_pack_from_assimg(struct bitmap_packer *packer,
struct ass_image *imglist);
int packer_pack_from_subbitmaps(struct bitmap_packer *packer,
struct sub_bitmaps *b, int padding_pixels);
#endif

View File

@ -172,6 +172,7 @@ struct vdpctx {
// EOSD
struct eosd_bitmap_surface {
VdpRGBAFormat format;
VdpBitmapSurface surface;
uint32_t max_width;
uint32_t max_height;
@ -1007,16 +1008,35 @@ static void generate_eosd(struct vo *vo, mp_eosd_images_t *imgs)
vc->eosd_render_count = 0;
if (!imgs->imgs)
return; // There's nothing to render!
if (imgs->type == SUBBITMAP_EMPTY)
return;
if (imgs->bitmap_id == vc->bitmap_id)
goto eosd_skip_upload;
need_upload = true;
VdpRGBAFormat format;
int format_size;
switch (imgs->type) {
case SUBBITMAP_LIBASS:
format = VDP_RGBA_FORMAT_A8;
format_size = 1;
break;
case SUBBITMAP_RGBA:
format = VDP_RGBA_FORMAT_B8G8R8A8;
format_size = 4;
break;
default:
abort();
};
if (sfc->format != format) {
talloc_free(sfc->packer);
sfc->packer = NULL;
};
sfc->format = format;
if (!sfc->packer)
sfc->packer = make_packer(vo, VDP_RGBA_FORMAT_A8);
int r = packer_pack_from_assimg(sfc->packer, imgs->imgs);
sfc->packer = make_packer(vo, format);
int r = packer_pack_from_subbitmaps(sfc->packer, imgs, imgs->scaled);
if (r < 0) {
mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] EOSD bitmaps do not fit on "
"a surface with the maximum supported size\n");
@ -1028,13 +1048,21 @@ static void generate_eosd(struct vo *vo, mp_eosd_images_t *imgs)
}
mp_msg(MSGT_VO, MSGL_V, "[vdpau] Allocating a %dx%d surface for "
"EOSD bitmaps.\n", sfc->packer->w, sfc->packer->h);
vdp_st = vdp->bitmap_surface_create(vc->vdp_device, VDP_RGBA_FORMAT_A8,
vdp_st = vdp->bitmap_surface_create(vc->vdp_device, format,
sfc->packer->w, sfc->packer->h,
true, &sfc->surface);
if (vdp_st != VDP_STATUS_OK)
sfc->surface = VDP_INVALID_HANDLE;
CHECK_ST_WARNING("EOSD: error when creating surface");
}
if (imgs->scaled) {
char zeros[sfc->packer->used_width * format_size];
memset(zeros, 0, sizeof(zeros));
vdp_st = vdp->bitmap_surface_put_bits_native(sfc->surface,
&(const void *){zeros}, &(uint32_t){0},
&(VdpRect){0, 0, sfc->packer->used_width,
sfc->packer->used_height});
}
eosd_skip_upload:
if (sfc->surface == VDP_INVALID_HANDLE)
@ -1046,31 +1074,54 @@ eosd_skip_upload:
* sizeof(*vc->eosd_targets));
}
int i = 0;
for (ASS_Image *p = imgs->imgs; p; p = p->next, i++) {
if (p->w == 0 || p->h == 0)
continue;
struct eosd_target *target = &vc->eosd_targets[vc->eosd_render_count];
int x = sfc->packer->result[i].x;
int y = sfc->packer->result[i].y;
target->source = (VdpRect){x, y, x + p->w, y + p->h};
if (need_upload) {
vdp_st = vdp->
bitmap_surface_put_bits_native(sfc->surface,
(const void *) &p->bitmap,
&p->stride, &target->source);
CHECK_ST_WARNING("EOSD: putbits failed");
if (imgs->type == SUBBITMAP_LIBASS) {
int i = 0;
for (ASS_Image *p = imgs->imgs; p; p = p->next, i++) {
if (p->w == 0 || p->h == 0)
continue;
struct eosd_target *target = vc->eosd_targets +
vc->eosd_render_count;
int x = sfc->packer->result[i].x;
int y = sfc->packer->result[i].y;
target->source = (VdpRect){x, y, x + p->w, y + p->h};
if (need_upload) {
vdp_st = vdp->
bitmap_surface_put_bits_native(sfc->surface,
(const void *) &p->bitmap,
&p->stride, &target->source);
CHECK_ST_WARNING("EOSD: putbits failed");
}
// Render dest, color, etc.
target->color.alpha = 1.0 - ((p->color >> 0) & 0xff) / 255.0;
target->color.blue = ((p->color >> 8) & 0xff) / 255.0;
target->color.green = ((p->color >> 16) & 0xff) / 255.0;
target->color.red = ((p->color >> 24) & 0xff) / 255.0;
target->dest.x0 = p->dst_x;
target->dest.y0 = p->dst_y;
target->dest.x1 = p->w + p->dst_x;
target->dest.y1 = p->h + p->dst_y;
vc->eosd_render_count++;
}
} else {
for (int i = 0 ;i < sfc->packer->count; i++) {
struct sub_bitmap *b = &imgs->parts[i];
struct eosd_target *target = vc->eosd_targets +
vc->eosd_render_count;
int x = sfc->packer->result[i].x;
int y = sfc->packer->result[i].y;
target->source = (VdpRect){x, y, x + b->w, y + b->h};
if (need_upload) {
vdp_st = vdp->
bitmap_surface_put_bits_native(sfc->surface,
&(const void *){b->bitmap},
&(uint32_t){b->w * 4},
&target->source);
CHECK_ST_WARNING("EOSD: putbits failed");
}
target->color = (VdpColor){1, 1, 1, 1};
target->dest = (VdpRect){b->x, b->y, b->x + b->dw, b->y + b->dh};
vc->eosd_render_count++;
}
// Render dest, color, etc.
target->color.alpha = 1.0 - ((p->color >> 0) & 0xff) / 255.0;
target->color.blue = ((p->color >> 8) & 0xff) / 255.0;
target->color.green = ((p->color >> 16) & 0xff) / 255.0;
target->color.red = ((p->color >> 24) & 0xff) / 255.0;
target->dest.x0 = p->dst_x;
target->dest.y0 = p->dst_y;
target->dest.x1 = p->w + p->dst_x;
target->dest.y1 = p->h + p->dst_y;
vc->eosd_render_count++;
}
vc->bitmap_id = imgs->bitmap_id;
vc->bitmap_pos_id = imgs->bitmap_pos_id;
@ -1529,7 +1580,7 @@ static int query_format(uint32_t format)
{
int default_flags = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW
| VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_OSD | VFCAP_EOSD
| VFCAP_EOSD_UNSCALED | VFCAP_FLIP;
| VFCAP_EOSD_UNSCALED | VFCAP_EOSD_RGBA | VFCAP_FLIP;
switch (format) {
case IMGFMT_YV12:
case IMGFMT_I420:

View File

@ -62,7 +62,8 @@ void sub_get_bitmaps(struct osd_state *osd, struct sub_bitmaps *res)
{
struct MPOpts *opts = osd->opts;
*res = (struct sub_bitmaps){ .bitmap_id = osd->bitmap_id,
*res = (struct sub_bitmaps){ .type = SUBBITMAP_EMPTY,
.bitmap_id = osd->bitmap_id,
.bitmap_pos_id = osd->bitmap_pos_id };
if (!opts->sub_visibility || !osd->sh_sub || !osd->sh_sub->active) {
/* Change ID in case we just switched from visible subtitles

View File

@ -5,13 +5,32 @@ struct sh_sub;
struct osd_state;
struct ass_track;
enum sub_bitmap_type {
SUBBITMAP_EMPTY,
SUBBITMAP_LIBASS,
SUBBITMAP_RGBA,
};
typedef struct mp_eosd_res {
int w, h; // screen dimensions, including black borders
int mt, mb, ml, mr; // borders (top, bottom, left, right)
} mp_eosd_res_t;
typedef struct sub_bitmaps {
enum sub_bitmap_type type;
struct ass_image *imgs;
struct sub_bitmap {
int w, h;
int x, y;
// Note: not clipped, going outside the screen area is allowed
int dw, dh;
void *bitmap;
} *parts;
int part_count;
bool scaled;
int bitmap_id, bitmap_pos_id;
} mp_eosd_images_t;

View File

@ -147,6 +147,7 @@ static void get_bitmaps(struct sh_sub *sh, struct osd_state *osd,
res->bitmap_id = ++res->bitmap_pos_id;
else if (changed)
res->bitmap_pos_id++;
res->type = SUBBITMAP_LIBASS;
}
static void reset(struct sh_sub *sh, struct osd_state *osd)

View File

@ -19,6 +19,7 @@
#include <libavcodec/avcodec.h>
#include "talloc.h"
#include "mp_msg.h"
#include "libmpdemux/stheader.h"
#include "sd.h"
@ -26,8 +27,17 @@
// Current code still pushes subs directly to global spudec
#include "sub.h"
static void avsub_to_spudec(AVSubtitleRect **rects, int num_rects,
double pts, double endpts)
struct sd_lavc_priv {
AVCodecContext *avctx;
int count;
struct sub_bitmap *inbitmaps;
struct sub_bitmap *outbitmaps;
bool bitmaps_changed;
double endpts;
};
static void old_avsub_to_spudec(AVSubtitleRect **rects, int num_rects,
double pts, double endpts)
{
int i, xmin = INT_MAX, ymin = INT_MAX, xmax = INT_MIN, ymax = INT_MIN;
struct spu_packet_t *packet;
@ -69,6 +79,9 @@ static void avsub_to_spudec(AVSubtitleRect **rects, int num_rects,
static int init(struct sh_sub *sh, struct osd_state *osd)
{
if (sh->initialized)
return 0;
struct sd_lavc_priv *priv = talloc_zero(NULL, struct sd_lavc_priv);
enum CodecID cid = CODEC_ID_NONE;
switch (sh->type) {
case 'b':
@ -77,6 +90,8 @@ static int init(struct sh_sub *sh, struct osd_state *osd)
cid = CODEC_ID_HDMV_PGS_SUBTITLE; break;
case 'x':
cid = CODEC_ID_XSUB; break;
case 'v':
cid = CODEC_ID_DVD_SUBTITLE; break;
}
AVCodecContext *ctx = NULL;
AVCodec *sub_codec = avcodec_find_decoder(cid);
@ -85,25 +100,41 @@ static int init(struct sh_sub *sh, struct osd_state *osd)
ctx = avcodec_alloc_context3(sub_codec);
if (!ctx)
goto error;
ctx->extradata_size = sh->extradata_len;
ctx->extradata = sh->extradata;
if (avcodec_open2(ctx, sub_codec, NULL) < 0)
goto error;
sh->context = ctx;
priv->avctx = ctx;
sh->context = priv;
return 0;
error:
mp_msg(MSGT_SUBREADER, MSGL_ERR,
"Could not open libavcodec subtitle decoder\n");
av_free(ctx);
talloc_free(priv);
return -1;
}
static void clear(struct sd_lavc_priv *priv)
{
priv->count = 0;
talloc_free(priv->inbitmaps);
talloc_free(priv->outbitmaps);
priv->inbitmaps = priv->outbitmaps = NULL;
priv->bitmaps_changed = true;
priv->endpts = MP_NOPTS_VALUE;
}
static void decode(struct sh_sub *sh, struct osd_state *osd, void *data,
int data_len, double pts, double duration)
{
AVCodecContext *ctx = sh->context;
struct sd_lavc_priv *priv = sh->context;
AVCodecContext *ctx = priv->avctx;
AVSubtitle sub;
AVPacket pkt;
clear(priv);
av_init_packet(&pkt);
pkt.data = data;
pkt.size = data_len;
@ -127,10 +158,37 @@ static void decode(struct sh_sub *sh, struct osd_state *osd, void *data,
if (sub.num_rects > 0) {
switch (sub.rects[0]->type) {
case SUBTITLE_BITMAP:
if (!vo_spudec)
vo_spudec = spudec_new_scaled(NULL, ctx->width, ctx->height, NULL, 0);
avsub_to_spudec(sub.rects, sub.num_rects, pts, endpts);
vo_osd_changed(OSDTYPE_SPU);
// Assume resolution heuristics only work for PGS and DVB
if (!osd->support_rgba || sh->type != 'p' && sh->type != 'b') {
if (!vo_spudec)
vo_spudec = spudec_new_scaled(NULL, ctx->width, ctx->height,
NULL, 0);
old_avsub_to_spudec(sub.rects, sub.num_rects, pts, endpts);
vo_osd_changed(OSDTYPE_SPU);
break;
}
priv->inbitmaps = talloc_array(priv, struct sub_bitmap,
sub.num_rects);
for (int i = 0; i < sub.num_rects; i++) {
struct AVSubtitleRect *r = sub.rects[i];
struct sub_bitmap *b = &priv->inbitmaps[i];
uint32_t *outbmp = talloc_size(priv->inbitmaps,
r->w * r->h * 4);
b->bitmap = outbmp;
b->w = r->w;
b->h = r->h;
b->x = r->x;
b->y = r->y;
uint8_t *inbmp = r->pict.data[0];
uint32_t *palette = (uint32_t *) r->pict.data[1];
for (int y = 0; y < r->h; y++) {
for (int x = 0; x < r->w; x++)
*outbmp++ = palette[*inbmp++];
inbmp += r->pict.linesize[0] - r->w;
};
}
priv->count = sub.num_rects;
priv->endpts = endpts;
break;
default:
mp_msg(MSGT_SUBREADER, MSGL_ERR, "sd_lavc: unsupported subtitle "
@ -141,21 +199,72 @@ static void decode(struct sh_sub *sh, struct osd_state *osd, void *data,
avsubtitle_free(&sub);
}
static void get_bitmaps(struct sh_sub *sh, struct osd_state *osd,
struct sub_bitmaps *res)
{
struct sd_lavc_priv *priv = sh->context;
if (priv->endpts != MP_NOPTS_VALUE && (osd->sub_pts >= priv->endpts ||
osd->sub_pts < priv->endpts - 300))
clear(priv);
if (!osd->support_rgba)
return;
if (priv->bitmaps_changed && priv->count > 0)
priv->outbitmaps = talloc_memdup(priv, priv->inbitmaps,
talloc_get_size(priv->inbitmaps));
bool pos_changed = false;
// Hope that PGS subs set these and 720/576 works for dvb subs
int inw = priv->avctx->width;
if (!inw)
inw = 720;
int inh = priv->avctx->height;
if (!inh)
inh = 576;
struct mp_eosd_res *d = &osd->dim;
double xscale = (double) (d->w - d->ml - d->mr) / inw;
double yscale = (double) (d->h - d->mt - d->mb) / inh;
for (int i = 0; i < priv->count; i++) {
struct sub_bitmap *bi = &priv->inbitmaps[i];
struct sub_bitmap *bo = &priv->outbitmaps[i];
#define SET(var, val) pos_changed |= var != (int)(val); var = (val)
SET(bo->x, bi->x * xscale + d->ml);
SET(bo->y, bi->y * yscale + d->mt);
SET(bo->dw, bi->w * xscale);
SET(bo->dh, bi->h * yscale);
}
res->parts = priv->outbitmaps;
res->part_count = priv->count;
if (priv->bitmaps_changed)
res->bitmap_id = ++res->bitmap_pos_id;
else if (pos_changed)
res->bitmap_pos_id++;
priv->bitmaps_changed = false;
res->type = SUBBITMAP_RGBA;
res->scaled = xscale != 1 || yscale != 1;
}
static void reset(struct sh_sub *sh, struct osd_state *osd)
{
struct sd_lavc_priv *priv = sh->context;
clear(priv);
// lavc might not do this right for all codecs; may need close+reopen
avcodec_flush_buffers(sh->context);
avcodec_flush_buffers(priv->avctx);
}
static void uninit(struct sh_sub *sh)
{
avcodec_close(sh->context);
av_free(sh->context);
struct sd_lavc_priv *priv = sh->context;
avcodec_close(priv->avctx);
av_free(priv->avctx);
talloc_free(priv);
}
const struct sd_functions sd_lavc = {
.init = init,
.decode = decode,
.get_bitmaps = get_bitmaps,
.reset = reset,
.switch_off = reset,
.uninit = uninit,

View File

@ -71,6 +71,7 @@ struct osd_state {
double normal_scale;
double vsfilter_scale;
bool unscaled;
bool support_rgba;
struct ass_renderer *osd_render;
struct ass_library *osd_ass_library;