[FFmpeg-devel] [PATCH 2/4] ffplay: add text subtitles rendering with libass.
Clément Bœsch
ubitux at gmail.com
Tue Nov 27 21:48:56 CET 2012
This allows to render text subtitles. The code is partially based on
lavfi/vf_ass. This commit adds the support for muxed subtitles only.
---
ffplay.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 151 insertions(+), 1 deletion(-)
diff --git a/ffplay.c b/ffplay.c
index 5ba5164..6013cd1 100644
--- a/ffplay.c
+++ b/ffplay.c
@@ -28,7 +28,11 @@
#include <math.h>
#include <limits.h>
#include <signal.h>
+#if CONFIG_LIBASS
+#include <ass/ass.h>
+#endif
#include "libavutil/avstring.h"
+#include "libavutil/avassert.h"
#include "libavutil/colorspace.h"
#include "libavutil/mathematics.h"
#include "libavutil/pixdesc.h"
@@ -51,6 +55,7 @@
# include "libavfilter/avfiltergraph.h"
# include "libavfilter/buffersink.h"
# include "libavfilter/buffersrc.h"
+# include "libavfilter/drawutils.h"
#endif
#include <SDL.h>
@@ -318,6 +323,71 @@ static AVPacket flush_pkt;
static SDL_Surface *screen;
+/* We use libass for the rendering, and the lavfi/drawutils for blending */
+#define TEXT_SUBTITLES_RENDERING (CONFIG_LIBASS && CONFIG_AVFILTER)
+
+#if TEXT_SUBTITLES_RENDERING
+static ASS_Library *libass_library;
+static ASS_Renderer *libass_renderer;
+static ASS_Track *libass_track;
+static FFDrawContext subrender_draw;
+
+static const int libass_log_level_map[] = {
+ [0] = AV_LOG_QUIET,
+ [1] = AV_LOG_PANIC,
+ [2] = AV_LOG_FATAL,
+ [3] = AV_LOG_ERROR,
+ [4] = AV_LOG_WARNING,
+ [5] = AV_LOG_INFO,
+ [6] = AV_LOG_VERBOSE,
+ [7] = AV_LOG_DEBUG,
+};
+
+static void libass_cb_log(int ass_level, const char *fmt, va_list args, void *ctx)
+{
+ int level = libass_log_level_map[ass_level];
+ av_vlog(ctx, level, fmt, args);
+ av_log(ctx, level, "\n");
+}
+
+static int init_text_sub_rendering(void)
+{
+ libass_library = ass_library_init();
+ if (!libass_library) {
+ av_log(NULL, AV_LOG_ERROR, "Could not initialize libass.\n");
+ return AVERROR(EINVAL);
+ }
+ ass_set_message_cb(libass_library, libass_cb_log, NULL);
+
+ libass_renderer = ass_renderer_init(libass_library);
+ if (!libass_renderer) {
+ av_log(NULL, AV_LOG_ERROR, "Could not initialize libass renderer.\n");
+ return AVERROR(EINVAL);
+ }
+
+ libass_track = ass_new_track(libass_library);
+ if (!libass_track) {
+ av_log(NULL, AV_LOG_ERROR, "Could not create a libass track\n");
+ return AVERROR(EINVAL);
+ }
+
+ ass_set_fonts(libass_renderer, NULL, NULL, 1, NULL, 1);
+ return 0;
+}
+
+static void uninit_text_sub_rendering(void)
+{
+ if (libass_renderer)
+ ass_renderer_done(libass_renderer);
+ if (libass_library)
+ ass_library_done(libass_library);
+}
+
+#else
+static int init_text_sub_rendering(void) { return 0; }
+static void uninit_text_sub_rendering(void) { }
+#endif
+
static int packet_queue_put(PacketQueue *q, AVPacket *pkt);
static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
@@ -768,6 +838,44 @@ static void calculate_display_rect(SDL_Rect *rect, int scr_xleft, int scr_ytop,
rect->h = FFMAX(height, 1);
}
+static void blend_text_subtitles(VideoPicture *vp)
+{
+#if TEXT_SUBTITLES_RENDERING
+
+/* libass stores an RGBA color in the format RRGGBBTT, where TT is the transparency level */
+#define AR(c) ( (c)>>24)
+#define AG(c) (((c)>>16)&0xFF)
+#define AB(c) (((c)>>8) &0xFF)
+#define AA(c) ((0xFF-c) &0xFF)
+
+ AVPicture pict;
+ long long now = vp->pts * 1000LL;
+ ASS_Image *image = ass_render_frame(libass_renderer, libass_track,
+ now, NULL);
+
+ SDL_LockYUVOverlay(vp->bmp);
+ pict.data[0] = vp->bmp->pixels[0];
+ pict.data[1] = vp->bmp->pixels[2];
+ pict.data[2] = vp->bmp->pixels[1];
+
+ pict.linesize[0] = vp->bmp->pitches[0];
+ pict.linesize[1] = vp->bmp->pitches[2];
+ pict.linesize[2] = vp->bmp->pitches[1];
+ while (image) {
+ uint8_t rgba_color[] = {AR(image->color), AG(image->color), AB(image->color), AA(image->color)};
+ FFDrawColor color;
+ ff_draw_color(&subrender_draw, &color, rgba_color);
+ ff_blend_mask(&subrender_draw, &color,
+ pict.data, pict.linesize,
+ vp->bmp->w, vp->bmp->h,
+ image->bitmap, image->stride, image->w, image->h,
+ 3, 0, image->dst_x, image->dst_y);
+ image = image->next;
+ }
+ SDL_UnlockYUVOverlay(vp->bmp);
+#endif
+}
+
static void video_image_display(VideoState *is)
{
VideoPicture *vp;
@@ -782,6 +890,9 @@ static void video_image_display(VideoState *is)
if (is->subpq_size > 0) {
sp = &is->subpq[is->subpq_rindex];
+ if (sp->sub.format)
+ blend_text_subtitles(vp);
+ else
if (vp->pts >= sp->pts + ((float) sp->sub.start_display_time / 1000)) {
SDL_LockYUVOverlay (vp->bmp);
@@ -1010,6 +1121,7 @@ static void do_exit(VideoState *is)
printf("\n");
SDL_Quit();
av_log(NULL, AV_LOG_QUIET, "%s", "");
+ uninit_text_sub_rendering();
exit(0);
}
@@ -1957,11 +2069,13 @@ static int subtitle_thread(void *arg)
avcodec_decode_subtitle2(is->subtitle_st->codec, &sp->sub,
&got_subtitle, pkt);
- if (got_subtitle && sp->sub.format == 0) {
+ if (got_subtitle && (sp->sub.format == 0 ||
+ (sp->sub.format && TEXT_SUBTITLES_RENDERING))) {
if (sp->sub.pts != AV_NOPTS_VALUE)
pts = sp->sub.pts / (double)AV_TIME_BASE;
sp->pts = pts;
+ if (sp->sub.format == 0) { // bitmap subtitle
for (i = 0; i < sp->sub.num_rects; i++)
{
for (j = 0; j < sp->sub.rects[i]->nb_colors; j++)
@@ -1973,6 +2087,24 @@ static int subtitle_thread(void *arg)
YUVA_OUT((uint32_t*)sp->sub.rects[i]->pict.data[1] + j, y, u, v, a);
}
}
+ } else if (TEXT_SUBTITLES_RENDERING) { // text subtitles
+ for (i = 0; i < sp->sub.num_rects; i++) {
+ char *ass_line = sp->sub.rects[i]->ass;
+ /* XXX: the FFmpeg ASS decoder is outputting ASS lines
+ * instead of the muxed layout of ASS (such as specified in
+ * Matroska, so without the timing information, but with a
+ * field order), so we process the line as data instead of
+ * using ass_process_chunk() with the packet pts and
+ * duration.
+ * The other FFmpeg text subtitles decoders also follow
+ * this model and currently output their subtitles events
+ * as if they were lines read from a standalone .ass file.
+ * This causes various technical issues and need to be
+ * changed in the long term. */
+ ass_process_data(libass_track, ass_line, strlen(ass_line));
+ }
+ } else
+ av_assert0(0);
/* now we can update the picture count */
if (++is->subpq_windex == SUBPICTURE_QUEUE_SIZE)
@@ -2626,6 +2758,21 @@ static int read_thread(void *arg)
if (infinite_buffer < 0 && is->realtime)
infinite_buffer = 1;
+#if TEXT_SUBTITLES_RENDERING
+ if (is->video_st && is->subtitle_st) {
+ ret = ff_draw_init(&subrender_draw, AV_PIX_FMT_YUV420P, 0);
+ if (ret < 0)
+ return ret;
+ ass_set_frame_size(libass_renderer,
+ is->video_st->codec->width,
+ is->video_st->codec->height);
+ if (is->subtitle_st->codec->subtitle_header)
+ ass_process_codec_private(libass_track,
+ is->subtitle_st->codec->subtitle_header,
+ is->subtitle_st->codec->subtitle_header_size);
+ }
+#endif
+
for (;;) {
if (is->abort_request)
break;
@@ -3318,6 +3465,9 @@ int main(int argc, char **argv)
SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE);
SDL_EventState(SDL_USEREVENT, SDL_IGNORE);
+ if (init_text_sub_rendering() < 0)
+ do_exit(NULL);
+
if (av_lockmgr_register(lockmgr)) {
fprintf(stderr, "Could not initialize lock manager!\n");
do_exit(NULL);
--
1.8.0.1
More information about the ffmpeg-devel
mailing list