[FFMPEG播放器]给ffplay添加文字字幕渲染功能

修改ffplay,添加字幕解析功能

结合上上周做出来的assrender
https://blog.csdn.net/qq_40212938/article/details/107754243
这里我用的是vs2015编译的,因为msvc有带辅助编译功能。能够快速c跟c++混编(所谓混编是在双方语法有差异的情况下互相兼容,而不是把c编译成c++).成品已经push到了gitee上。地址是https://gitee.com/heweisheng/AssRender。这只是一个简单的demo。没有进行近一步的封装调整,目前我已经把思路运用到项目中,提供一个可以参考的版本。
首先添加了一个类,用于管理ass字幕解析跟生产字幕图片数据。AssPaintRender。然后开放一个c语言跟c++的接口,当然,c++就不需要通过这个接口访问了。主要是为了给c使用。我才开了这个接口。api.h跟api.cpp。这里由于我是用msvc开发的。所以我也没用使用传统的定义手段,比如#ifndef H #define H啥的。将一切都准备好了,去找ffplay的dll组装版本(由于时间久远,我也找不到是哪个了,如果需要更新,请自己去找)。
把其中的ffplay.c、cmdutils.h、cmdutils.c、config.h拉到我们准备好的项目中。我已经将内容整理好。只需要编译即可
整理好的结构
首先添加#include “api.h”,
接入我们写好的模块
然后修改ffplay的subtitle_thread


static int subtitle_thread(void *arg)
{
    VideoState *is = arg;
	assrender *render=NULL;
    Frame *sp;
    int got_subtitle;
    double pts;

    for (;;) {
        if (!(sp = frame_queue_peek_writable(&is->subpq)))
            return 0;

        if ((got_subtitle = decoder_decode_frame(&is->subdec, NULL, &sp->sub)) < 0)
            break;

        pts = 0;

        if (got_subtitle && (sp->sub.format == 0||(sp->sub.rects&&sp->sub.rects[0]->ass))) {
            if (sp->sub.pts != AV_NOPTS_VALUE)
                pts = sp->sub.pts / (double)AV_TIME_BASE;
            sp->pts = pts;
            sp->serial = is->subdec.pkt_serial;
            sp->width = is->subdec.avctx->width;
            sp->height = is->subdec.avctx->height;
            sp->uploaded = 0;
			if (sp->sub.format != 0)
			{
				if (!render)
				{
					alloc_assrender(&render);
					if (!sp->width || !sp->height)
					{
						sp->width = is->viddec.avctx->width;
						sp->height = is->viddec.avctx->height;
					}
					initasspaintrender(render, is->subdec.avctx, sp->width, sp->height);
				}
				if (render)
				{
					addsubtitlestotrack(render, &sp->sub);
				}
			}
            /* now we can update the picture count */
            frame_queue_push(&is->subpq);
        } else if (got_subtitle) {
            avsubtitle_free(&sp->sub);
        }
    }
	if(render)
		free_assrender(&render);
    return 0;
}

然后修改video_image_display

static void video_image_display(VideoState *is)
{
    Frame *vp;
    Frame *sp = NULL;
    SDL_Rect rect;

    vp = frame_queue_peek_last(&is->pictq);
    if (is->subtitle_st) {
        if (frame_queue_nb_remaining(&is->subpq) > 0) {
            sp = frame_queue_peek(&is->subpq);

            if (vp->pts >= sp->pts + ((float) sp->sub.start_display_time / 1000)) {
                if (!sp->uploaded) {
                    uint8_t* pixels[4];
                    int pitch[4];
                    int i;
                    if (!sp->width || !sp->height) {
                        sp->width = vp->width;
                        sp->height = vp->height;
                    }
                    if (realloc_texture(&is->sub_texture, SDL_PIXELFORMAT_ARGB8888, sp->width, sp->height, SDL_BLENDMODE_BLEND, 1) < 0)
                        return;

                    for (i = 0; i < sp->sub.num_rects; i++) {
                        AVSubtitleRect *sub_rect = sp->sub.rects[i];

                        sub_rect->x = av_clip(sub_rect->x, 0, sp->width );
                        sub_rect->y = av_clip(sub_rect->y, 0, sp->height);
                        sub_rect->w = av_clip(sub_rect->w, 0, sp->width  - sub_rect->x);
                        sub_rect->h = av_clip(sub_rect->h, 0, sp->height - sub_rect->y);
						if (!sub_rect->ass)
						{
							is->sub_convert_ctx = sws_getCachedContext(is->sub_convert_ctx,
								sub_rect->w, sub_rect->h, AV_PIX_FMT_PAL8,
								sub_rect->w, sub_rect->h, AV_PIX_FMT_BGRA,
								0, NULL, NULL, NULL);
							if (!is->sub_convert_ctx) {
								av_log(NULL, AV_LOG_FATAL, "Cannot initialize the conversion context\n");
								return;
							}
						}
						if (!sub_rect->ass)
						{
							if (!SDL_LockTexture(is->sub_texture, (SDL_Rect *)sub_rect, (void **)pixels, pitch)) {
								sws_scale(is->sub_convert_ctx, (const uint8_t * const *)sub_rect->data, sub_rect->linesize,
									0, sub_rect->h, pixels, pitch);
								SDL_UnlockTexture(is->sub_texture);
							}
						}
						else
							SDL_UpdateTexture(is->sub_texture, (SDL_Rect *)sub_rect, sub_rect->data[0], sub_rect->linesize[0]);
                    }
                    sp->uploaded = 1;
                }
            } else
                sp = NULL;
        }
    }

    calculate_display_rect(&rect, is->xleft, is->ytop, is->width, is->height, vp->width, vp->height, vp->sar);

    if (!vp->uploaded) {
        if (upload_texture(&is->vid_texture, vp->frame, &is->img_convert_ctx) < 0)
            return;
        vp->uploaded = 1;
        vp->flip_v = vp->frame->linesize[0] < 0;
    }

    set_sdl_yuv_conversion_mode(vp->frame);
    SDL_RenderCopyEx(renderer, is->vid_texture, NULL, &rect, 0, NULL, vp->flip_v ? SDL_FLIP_VERTICAL : 0);
    set_sdl_yuv_conversion_mode(NULL);
    if (sp) {
#if USE_ONEPASS_SUBTITLE_RENDER
        SDL_RenderCopy(renderer, is->sub_texture, NULL, &rect);
#else
        int i;
        double xratio = (double)rect.w / (double)sp->width;
        double yratio = (double)rect.h / (double)sp->height;
        for (i = 0; i < sp->sub.num_rects; i++) {
            SDL_Rect *sub_rect = (SDL_Rect*)sp->sub.rects[i];
            SDL_Rect target = {.x = rect.x + sub_rect->x * xratio,
                               .y = rect.y + sub_rect->y * yratio,
                               .w = sub_rect->w * xratio,
                               .h = sub_rect->h * yratio};
            SDL_RenderCopy(renderer, is->sub_texture, sub_rect, &target);
        }
#endif
    }
}

最后一个比较重要的是修改字幕解码,让文本都解码成准格式的ass格式字幕

static int stream_component_open(VideoState *is, int stream_index)
{
	....
	if (avctx->codec_type == AVMEDIA_TYPE_VIDEO || avctx->codec_type == AVMEDIA_TYPE_AUDIO)
        av_dict_set(&opts, "refcounted_frames", "1", 0);
	if(avctx->codec_type==AVMEDIA_TYPE_SUBTITLE)
		if (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 26, 100))
			av_dict_set(&opts, "sub_text_format", "ass", 0);
}

这三处工作做完。我们的程序就可以运行了
字幕解析结果
结果搞定了。跟传统ffplay一样,使用同样的命令。我们只是添加了个不用过滤器的ass字幕。效果看起来还行。

成品展示出来了。该说libass了。
项目中我用到了三个比较重要的结构体,ASS_Library、ASS_Renderer、ASS_Track。
ASS_Library是ass的库管理器,用于将解析功能的函数进行对接。c语言中对象操作是要自己实现的。所以ASS_Library充当桥梁进行接入跟销毁。
ASS_Renderer就比较重要了,它用于生产图片,这里提一嘴,ASS_Renderer生成的ASS_image是每位存储了一个像素透明度信息的8位位图。跟传统位图不一样,它只存储了一个透明度,然后有一个color存储了当前颜色。也就是一个ASS_image只能储存固定颜色,然后通过alpha通道进行颜色调整。这个理解起来特别抽象。只能自己去看源码理解了。因为这里涉及到透明度叠加的算法,那个算法又比较抽象。
ASS_Track用于存储未解析的数据。这个东西内置了map的机制。会对内容进行过滤插入。之前没有加入

		if (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 26, 100))
			av_dict_set(&opts, "sub_text_format", "ass", 0);

这段代码的时候,一直添加不进去,一看哈希索引冲突了。如果不加会多一个Dialog:0,这个东西会导致解析失败。
libass的库使用也比较简单,主要分为初始化,添加到Track,解析出字幕。当然,我这样做字幕是不能出特效的,ass字幕还有个特效功能。不过现代字幕比较少用那种特效了。另外会在跳转的时候失去当前的字幕。我也推荐利用track的map功能,添加多线程解析渲染。不过这个需要涉及很多内容。我也比较长时间没有接触c了。就不进行实现了。
关于接口内的函数就不详细介绍了,因为c接口很多内容都是透明的,不需要我分析了,遇到问题的时候多看内容。实在不行对比vf_subtitle找找区别。
此项目仅供参考,没有进行错误考虑,存在若干不完善的地方。各位如果发现有问题很正常,比较详细的参考ffmpeg的avfilter中的vf_subtitle.c。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值