summaryrefslogtreecommitdiffstats
path: root/sub/dec_sub.c
diff options
context:
space:
mode:
Diffstat (limited to 'sub/dec_sub.c')
-rw-r--r--sub/dec_sub.c370
1 files changed, 308 insertions, 62 deletions
diff --git a/sub/dec_sub.c b/sub/dec_sub.c
index d3cedea80d..b72630470c 100644
--- a/sub/dec_sub.c
+++ b/sub/dec_sub.c
@@ -22,105 +22,351 @@
#include "config.h"
#include "demux/stheader.h"
-#include "sub/sd.h"
-#include "sub/sub.h"
-#include "sub/dec_sub.h"
+#include "sd.h"
+#include "sub.h"
+#include "dec_sub.h"
+#include "subreader.h"
#include "core/options.h"
+#include "core/mp_msg.h"
extern const struct sd_functions sd_ass;
extern const struct sd_functions sd_lavc;
+extern const struct sd_functions sd_spu;
+extern const struct sd_functions sd_movtext;
+extern const struct sd_functions sd_srt;
+extern const struct sd_functions sd_microdvd;
+extern const struct sd_functions sd_lavc_conv;
-bool is_text_sub(const char *t)
+static const struct sd_functions *sd_list[] = {
+#ifdef CONFIG_ASS
+ &sd_ass,
+#endif
+ &sd_lavc,
+ &sd_spu,
+ &sd_movtext,
+ &sd_srt,
+ &sd_microdvd,
+ &sd_lavc_conv,
+ NULL
+};
+
+#define MAX_NUM_SD 3
+
+struct dec_sub {
+ struct MPOpts *opts;
+ struct sd init_sd;
+
+ struct sd *sd[MAX_NUM_SD];
+ int num_sd;
+};
+
+struct dec_sub *sub_create(struct MPOpts *opts)
{
- return t && (is_ass_sub(t) ||
- strcmp(t, "text") == 0 ||
- strcmp(t, "subrip") == 0 ||
- strcmp(t, "mov_text") == 0);
+ struct dec_sub *sub = talloc_zero(NULL, struct dec_sub);
+ sub->opts = opts;
+ return sub;
}
-bool is_ass_sub(const char *t)
+static void sub_uninit(struct dec_sub *sub)
{
- return t && (strcmp(t, "ass") == 0 ||
- strcmp(t, "ssa") == 0);
+ sub_reset(sub);
+ for (int n = 0; n < sub->num_sd; n++) {
+ if (sub->sd[n]->driver->uninit)
+ sub->sd[n]->driver->uninit(sub->sd[n]);
+ talloc_free(sub->sd[n]);
+ }
+ sub->num_sd = 0;
}
-bool is_dvd_sub(const char *t)
+void sub_destroy(struct dec_sub *sub)
{
- return t && strcmp(t, "dvd_subtitle") == 0;
+ if (!sub)
+ return;
+ sub_uninit(sub);
+ talloc_free(sub);
}
-void sub_init(struct sh_sub *sh, struct osd_state *osd)
+bool sub_is_initialized(struct dec_sub *sub)
{
- struct MPOpts *opts = sh->opts;
+ return !!sub->num_sd;
+}
- assert(!osd->sh_sub);
- if (sd_lavc.probe(sh))
- sh->sd_driver = &sd_lavc;
-#ifdef CONFIG_ASS
- if (opts->ass_enabled && sd_ass.probe(sh))
- sh->sd_driver = &sd_ass;
-#endif
- if (sh->sd_driver) {
- if (sh->sd_driver->init(sh, osd) < 0)
+struct sd *sub_get_last_sd(struct dec_sub *sub)
+{
+ return sub->num_sd ? sub->sd[sub->num_sd - 1] : NULL;
+}
+
+void sub_set_video_res(struct dec_sub *sub, int w, int h)
+{
+ sub->init_sd.sub_video_w = w;
+ sub->init_sd.sub_video_h = h;
+}
+
+void sub_set_extradata(struct dec_sub *sub, void *data, int data_len)
+{
+ sub->init_sd.extradata = data_len ? talloc_memdup(sub, data, data_len) : NULL;
+ sub->init_sd.extradata_len = data_len;
+}
+
+void sub_set_ass_renderer(struct dec_sub *sub, struct ass_library *ass_library,
+ struct ass_renderer *ass_renderer)
+{
+ sub->init_sd.ass_library = ass_library;
+ sub->init_sd.ass_renderer = ass_renderer;
+}
+
+static void print_chain(struct dec_sub *sub)
+{
+ mp_msg(MSGT_OSD, MSGL_V, "Subtitle filter chain: ");
+ for (int n = 0; n < sub->num_sd; n++) {
+ struct sd *sd = sub->sd[n];
+ mp_msg(MSGT_OSD, MSGL_V, "%s%s (%s)", n > 0 ? " -> " : "",
+ sd->driver->name, sd->codec);
+ }
+ mp_msg(MSGT_OSD, MSGL_V, "\n");
+}
+
+// Subtitles read with subreader.c
+static void read_sub_data(struct dec_sub *sub, struct sub_data *subdata)
+{
+ assert(sub_accept_packets_in_advance(sub));
+ char *temp = NULL;
+
+ struct sd *sd = sub_get_last_sd(sub);
+
+ sd->no_remove_duplicates = true;
+
+ for (int i = 0; i < subdata->sub_num; i++) {
+ subtitle *st = &subdata->subtitles[i];
+ // subdata is in 10 ms ticks, pts is in seconds
+ double t = subdata->sub_uses_time ? 0.01 : (1 / subdata->fallback_fps);
+
+ int len = 0;
+ for (int j = 0; j < st->lines; j++)
+ len += st->text[j] ? strlen(st->text[j]) : 0;
+
+ len += 2 * st->lines; // '\N', including the one after the last line
+ len += 6; // {\anX}
+ len += 1; // '\0'
+
+ if (talloc_get_size(temp) < len) {
+ talloc_free(temp);
+ temp = talloc_array(NULL, char, len);
+ }
+
+ char *p = temp;
+ char *end = p + len;
+
+ if (st->alignment)
+ p += snprintf(p, end - p, "{\\an%d}", st->alignment);
+
+ for (int j = 0; j < st->lines; j++)
+ p += snprintf(p, end - p, "%s\\N", st->text[j]);
+
+ if (st->lines > 0)
+ p -= 2; // remove last "\N"
+ *p = 0;
+
+ struct demux_packet pkt = {0};
+ pkt.pts = st->start * t;
+ pkt.duration = (st->end - st->start) * t;
+ pkt.buffer = temp;
+ pkt.len = strlen(temp);
+
+ sub_decode(sub, &pkt);
+ }
+
+ // Hack for broken FFmpeg packet format: make sd_ass keep the subtitle
+ // events on reset(), even though broken FFmpeg ASS packets were received
+ // (from sd_lavc_conv.c). Normally, these events are removed on seek/reset,
+ // but this is obviously unwanted in this case.
+ if (sd && sd->driver->fix_events)
+ sd->driver->fix_events(sd);
+
+ sd->no_remove_duplicates = false;
+
+ talloc_free(temp);
+}
+
+static int sub_init_decoder(struct dec_sub *sub, struct sd *sd)
+{
+ sd->driver = NULL;
+ for (int n = 0; sd_list[n]; n++) {
+ if (sd_list[n]->supports_format(sd->codec)) {
+ sd->driver = sd_list[n];
+ break;
+ }
+ }
+
+ if (!sd->driver)
+ return -1;
+
+ if (sd->driver->init(sd) < 0)
+ return -1;
+
+ return 0;
+}
+
+void sub_init_from_sh(struct dec_sub *sub, struct sh_sub *sh)
+{
+ assert(!sub->num_sd);
+
+ if (sh->extradata && !sub->init_sd.extradata)
+ sub_set_extradata(sub, sh->extradata, sh->extradata_len);
+ struct sd init_sd = sub->init_sd;
+ init_sd.codec = sh->gsh->codec;
+ init_sd.ass_track = sh->track;
+
+ while (sub->num_sd < MAX_NUM_SD) {
+ struct sd *sd = talloc(NULL, struct sd);
+ *sd = init_sd;
+ sd->opts = sub->opts;
+ if (sub_init_decoder(sub, sd) < 0) {
+ talloc_free(sd);
+ break;
+ }
+ sub->sd[sub->num_sd] = sd;
+ sub->num_sd++;
+ // Try adding new converters until a decoder is reached
+ if (sd->driver->get_bitmaps || sd->driver->get_text) {
+ print_chain(sub);
+ if (sh->sub_data)
+ read_sub_data(sub, sh->sub_data);
return;
- osd->sh_sub = sh;
- osd->switch_sub_id++;
- sh->initialized = true;
- sh->active = true;
+ }
+ init_sd = (struct sd) {
+ .codec = sd->output_codec,
+ .converted_from = sd->codec,
+ .extradata = sd->output_extradata,
+ .extradata_len = sd->output_extradata_len,
+ .ass_library = sub->init_sd.ass_library,
+ .ass_renderer = sub->init_sd.ass_renderer,
+ };
+ }
+
+ sub_uninit(sub);
+ mp_msg(MSGT_OSD, MSGL_ERR, "Could not find subtitle decoder for format '%s'.\n",
+ sh->gsh->codec ? sh->gsh->codec : "<unknown>");
+}
+
+bool sub_accept_packets_in_advance(struct dec_sub *sub)
+{
+ // Converters are assumed to always accept packets in advance
+ struct sd *sd = sub_get_last_sd(sub);
+ return sd && sd->driver->accept_packets_in_advance;
+}
+
+static void decode_next(struct dec_sub *sub, int n, struct demux_packet *packet)
+{
+ struct sd *sd = sub->sd[n];
+ sd->driver->decode(sd, packet);
+ if (n + 1 >= sub->num_sd || !sd->driver->get_converted)
+ return;
+ while (1) {
+ struct demux_packet *next =
+ sd->driver->get_converted ? sd->driver->get_converted(sd) : NULL;
+ if (!next)
+ break;
+ decode_next(sub, n + 1, next);
}
}
-void sub_decode(struct sh_sub *sh, struct osd_state *osd, void *data,
- int data_len, double pts, double duration)
+void sub_decode(struct dec_sub *sub, struct demux_packet *packet)
{
- if (sh->active && sh->sd_driver->decode)
- sh->sd_driver->decode(sh, osd, data, data_len, pts, duration);
+ if (sub->num_sd > 0)
+ decode_next(sub, 0, packet);
}
-void sub_get_bitmaps(struct osd_state *osd, struct mp_osd_res dim, double pts,
+void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts,
struct sub_bitmaps *res)
{
- struct MPOpts *opts = osd->opts;
+ struct MPOpts *opts = sub->opts;
+ struct sd *sd = sub_get_last_sd(sub);
*res = (struct sub_bitmaps) {0};
- if (!opts->sub_visibility || !osd->sh_sub || !osd->sh_sub->active) {
- /* Change ID in case we just switched from visible subtitles
- * to current state. Hopefully, unnecessarily claiming that
- * things may have changed is harmless for empty contents.
- * Increase osd-> values ahead so that _next_ returned id
- * is also guaranteed to differ from this one.
- */
- osd->switch_sub_id++;
- } else {
- if (osd->sh_sub->sd_driver->get_bitmaps)
- osd->sh_sub->sd_driver->get_bitmaps(osd->sh_sub, osd, dim, pts, res);
+ if (sd && opts->sub_visibility) {
+ if (sd->driver->get_bitmaps)
+ sd->driver->get_bitmaps(sd, dim, pts, res);
}
+}
- res->bitmap_id += osd->switch_sub_id;
- res->bitmap_pos_id += osd->switch_sub_id;
- osd->switch_sub_id = 0;
+bool sub_has_get_text(struct dec_sub *sub)
+{
+ struct sd *sd = sub_get_last_sd(sub);
+ return sd && sd->driver->get_text;
}
-void sub_reset(struct sh_sub *sh, struct osd_state *osd)
+char *sub_get_text(struct dec_sub *sub, double pts)
{
- if (sh->active && sh->sd_driver->reset)
- sh->sd_driver->reset(sh, osd);
+ struct MPOpts *opts = sub->opts;
+ struct sd *sd = sub_get_last_sd(sub);
+ char *text = NULL;
+ if (sd && opts->sub_visibility) {
+ if (sd->driver->get_text)
+ text = sd->driver->get_text(sd, pts);
+ }
+ return text;
}
-void sub_switchoff(struct sh_sub *sh, struct osd_state *osd)
+void sub_reset(struct dec_sub *sub)
{
- if (sh->active && sh->sd_driver->switch_off) {
- assert(osd->sh_sub == sh);
- sh->sd_driver->switch_off(sh, osd);
- osd->sh_sub = NULL;
+ for (int n = 0; n < sub->num_sd; n++) {
+ if (sub->sd[n]->driver->reset)
+ sub->sd[n]->driver->reset(sub->sd[n]);
}
- sh->active = false;
}
-void sub_uninit(struct sh_sub *sh)
+#define MAX_PACKETS 10
+#define MAX_BYTES 10000
+
+struct sd_conv_buffer {
+ struct demux_packet pkt[MAX_PACKETS];
+ int num_pkt;
+ int read_pkt;
+ char buffer[MAX_BYTES];
+ int cur_buffer;
+};
+
+void sd_conv_add_packet(struct sd *sd, void *data, int data_len, double pts,
+ double duration)
+{
+ if (!sd->sd_conv_buffer)
+ sd->sd_conv_buffer = talloc_zero(sd, struct sd_conv_buffer);
+ struct sd_conv_buffer *buf = sd->sd_conv_buffer;
+ if (buf->num_pkt >= MAX_PACKETS || buf->cur_buffer + data_len + 1 > MAX_BYTES)
+ goto out_of_space;
+ if (buf->read_pkt == buf->num_pkt)
+ sd_conv_def_reset(sd);
+ assert(buf->read_pkt == 0); // no mixing of reading/adding allowed
+ struct demux_packet *pkt = &buf->pkt[buf->num_pkt++];
+ *pkt = (struct demux_packet) {
+ .buffer = &buf->buffer[buf->cur_buffer],
+ .len = data_len,
+ .pts = pts,
+ .duration = duration,
+ };
+ memcpy(pkt->buffer, data, data_len);
+ pkt->buffer[data_len] = 0;
+ buf->cur_buffer += data_len + 1;
+ return;
+
+out_of_space:
+ mp_msg(MSGT_OSD, MSGL_ERR, "Subtitle too big.\n");
+}
+
+struct demux_packet *sd_conv_def_get_converted(struct sd *sd)
{
- assert (!sh->active);
- if (sh->initialized && sh->sd_driver->uninit)
- sh->sd_driver->uninit(sh);
- sh->initialized = false;
+ struct sd_conv_buffer *buf = sd->sd_conv_buffer;
+ if (buf && buf->read_pkt < buf->num_pkt)
+ return &buf->pkt[buf->read_pkt++];
+ return NULL;
+}
+
+void sd_conv_def_reset(struct sd *sd)
+{
+ struct sd_conv_buffer *buf = sd->sd_conv_buffer;
+ if (buf) {
+ buf->read_pkt = buf->num_pkt = 0;
+ buf->cur_buffer = 0;
+ }
}