当前位置:网站首页>FFmpeg 视频添加水印
FFmpeg 视频添加水印
2022-07-26 04:24:00 【Mr.codeee】
1.简介
本示例,在视频中添加一个logo图片,把添加水印好的图片保存到本地。
2.流程

2.1打开输入的文件
首先打开输入的视频文件,查找到视频流索引,找到对应的视频解码器,拷贝一些重要的参数到解码器,最后打开解码器。
//av_register_all();
avformat_network_init();
///打开输入的流
int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
if (ret != 0)
{
printf("Couldn't open input stream.\n");
return -1;
}
//查找流信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0)
{
printf("Couldn't find stream information.\n");
return -1;
}
//找到视频流索引
video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVStream* st = fmt_ctx->streams[video_index];
AVCodec* codec = nullptr;
//找到解码器
codec = avcodec_find_decoder(st->codecpar->codec_id);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
return -1;
}
//申请AVCodecContext
dec_ctx = avcodec_alloc_context3(codec);
if (!dec_ctx)
{
return -1;
}
avcodec_parameters_to_context(dec_ctx, fmt_ctx->streams[video_index]->codecpar);
//打开解码器
if ((ret = avcodec_open2(dec_ctx, codec, NULL) < 0))
{
return -1;
}
return 0;2.2初始化滤镜
2.2.1获取滤镜处理的源
获得滤镜处理的源及滤镜处理的sink滤镜,同时申请输入和输出的滤镜结构AVFilterInOut。
const AVFilter* buffersrc = avfilter_get_by_name("buffer");
const AVFilter* buffersink = avfilter_get_by_name("buffersink");
AVFilterInOut* outputs = avfilter_inout_alloc();
AVFilterInOut* inputs = avfilter_inout_alloc();2.2.2处理AVFilterGraph
需要的AVFilter和AVFilterInOut申请完成之后,需要申请一个AVFilterGraph,用来存储Filter的in和out描述信息
AVFilterGraph* filter_graph = NULL;
filter_graph = avfilter_graph_alloc();
if (!outputs || !inputs || !filter_graph)
{
ret = AVERROR(ENOMEM);
return ret;
}2.2.3创建AVFilterContext
接下来创建一个AVFilterContext结构用来存储Filter的处理内容,包括input与output的Filter信息,在创建input信息时,需要加入原视频的相关信息,比如pix_fmt、time_base等。
首先输入参数:
char args[512];
snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
time_base.num, time_base.den,
dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);然后创建AVFilterContext:
AVFilterContext* buffersink_ctx = NULL;
AVFilterContext* buffersrc_ctx = NULL;
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
args, NULL, filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n");
return ret;
}
/* buffer video sink: to terminate the filter chain. */
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, NULL, filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");
return ret;
}2.2.4设置其他参数
创建完输入与输出的AVFilterContext之后,如果还需要设置一些其他与Filter相关的参数。可以通过使用av_opt_set_int_list进行设置,例如设置输出的pix_fmt参数。
ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n");
return ret;
}2.2.5建立滤镜解析器
参数设置完毕后,可以针对前面设置的Filter相关的内容建立滤镜解析器。
const char* filter_descr = "movie=logo.jpg[wm];[in][wm]overlay=5:5[out]";
outputs->name = av_strdup("in");
outputs->filter_ctx = buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
&inputs, &outputs, NULL)) < 0)
{
return ret;
}
if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
{
return ret;
}2.3读取数据解码并获取到添加好水印的数据。
while (av_read_frame(fmt_ctx, pkt) >= 0)
{
if (pkt->stream_index == video_index)
{
int ret = avcodec_send_packet(dec_ctx, pkt);
if (ret >= 0)
{
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
continue;
}
else if (ret < 0)
{
continue;
}
frame->pts = frame->best_effort_timestamp;
/* push the decoded frame into the filtergraph */
if (av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0)
{
av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n");
break;
}
/* pull filtered frames from the filtergraph */
while (1)
{
ret = av_buffersink_get_frame(buffersink_ctx, filt_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
if (ret < 0)
break;
switch (dec_ctx->pix_fmt)
{
case AV_PIX_FMT_YUV420P:
{
int size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, filt_frame->width, filt_frame->height, 1);
char fileName[20] = { 0 };
sprintf(fileName, "img2/%d.yuv", dec_ctx->frame_number);
FILE* fp;
fp = fopen(fileName, "wb");
for (int i = 0; i < filt_frame->height; i++)
{
fwrite(filt_frame->data[0] + filt_frame->linesize[0] * i, 1, filt_frame->width, fp);
}
for (int i = 0; i < filt_frame->height / 2; i++)
{
fwrite(filt_frame->data[1] + filt_frame->linesize[1] * i, 1, filt_frame->width / 2, fp);
}
for (int i = 0; i < filt_frame->height / 2; i++)
{
fwrite(filt_frame->data[2] + filt_frame->linesize[2] * i, 1, filt_frame->width / 2, fp);
}
fclose(fp);
}
break;
default:
return -1;
}
av_frame_unref(filt_frame);
}
av_frame_unref(frame);
}
}
}3.效果
logo图如下

效果图如下,可以看见添加到了左上角。

4.源码
#include "pch.h"
#include <iostream>
#include <Windows.h>
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/dict.h"
#include "libavutil/opt.h"
#include "libavutil/timestamp.h"
#include "libavutil/avutil.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h"
#include "libavfilter/avfilter.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
};
static AVFormatContext* fmt_ctx = NULL;
static AVCodecContext* dec_ctx = NULL;
AVFilterContext* buffersink_ctx = NULL;
AVFilterContext* buffersrc_ctx = NULL;
AVFilterGraph* filter_graph = NULL;
int video_index = -1;
const char* filter_descr = "movie=logo.jpg[wm];[in][wm]overlay=5:5[out]";
static int64_t last_pts = AV_NOPTS_VALUE;
static int open_input_file(const char* filename)
{
//av_register_all();
avformat_network_init();
///打开输入的流
int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
if (ret != 0)
{
printf("Couldn't open input stream.\n");
return -1;
}
//查找流信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0)
{
printf("Couldn't find stream information.\n");
return -1;
}
//找到视频流索引
video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVStream* st = fmt_ctx->streams[video_index];
AVCodec* codec = nullptr;
//找到解码器
codec = avcodec_find_decoder(st->codecpar->codec_id);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
return -1;
}
//申请AVCodecContext
dec_ctx = avcodec_alloc_context3(codec);
if (!dec_ctx)
{
return -1;
}
avcodec_parameters_to_context(dec_ctx, fmt_ctx->streams[video_index]->codecpar);
//打开解码器
if ((ret = avcodec_open2(dec_ctx, codec, NULL) < 0))
{
return -1;
}
return 0;
}
static int init_filters(const char* filters_descr)
{
char args[512];
int ret = 0;
const AVFilter* buffersrc = avfilter_get_by_name("buffer");
const AVFilter* buffersink = avfilter_get_by_name("buffersink");
AVFilterInOut* outputs = avfilter_inout_alloc();
AVFilterInOut* inputs = avfilter_inout_alloc();
AVRational time_base = fmt_ctx->streams[video_index]->time_base;
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
filter_graph = avfilter_graph_alloc();
if (!outputs || !inputs || !filter_graph)
{
ret = AVERROR(ENOMEM);
return ret;
}
/* buffer video source: the decoded frames from the decoder will be inserted here. */
snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
time_base.num, time_base.den,
dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
args, NULL, filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n");
return ret;
}
/* buffer video sink: to terminate the filter chain. */
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, NULL, filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");
return ret;
}
ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n");
return ret;
}
outputs->name = av_strdup("in");
outputs->filter_ctx = buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
&inputs, &outputs, NULL)) < 0)
{
return ret;
}
if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
{
return ret;
}
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}
int main()
{
///1.打开文件
const char* inputUrl = "test.mp4";
int ret = -1;
if ((ret = open_input_file(inputUrl) < 0))
{
return -1;
}
///2.初始化滤镜
if ((ret = init_filters(filter_descr)) < 0)
{
return -1;
}
AVPacket* pkt = av_packet_alloc();
//av_init_packet(pkt);
AVFrame* frame = av_frame_alloc();
AVFrame *filt_frame = av_frame_alloc();
while (av_read_frame(fmt_ctx, pkt) >= 0)
{
if (pkt->stream_index == video_index)
{
int ret = avcodec_send_packet(dec_ctx, pkt);
if (ret >= 0)
{
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
continue;
}
else if (ret < 0)
{
continue;
}
frame->pts = frame->best_effort_timestamp;
/* push the decoded frame into the filtergraph */
if (av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0)
{
av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n");
break;
}
/* pull filtered frames from the filtergraph */
while (1)
{
ret = av_buffersink_get_frame(buffersink_ctx, filt_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
if (ret < 0)
break;
switch (dec_ctx->pix_fmt)
{
case AV_PIX_FMT_YUV420P:
{
int size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, filt_frame->width, filt_frame->height, 1);
char fileName[20] = { 0 };
sprintf(fileName, "img2/%d.yuv", dec_ctx->frame_number);
FILE* fp;
fp = fopen(fileName, "wb");
for (int i = 0; i < filt_frame->height; i++)
{
fwrite(filt_frame->data[0] + filt_frame->linesize[0] * i, 1, filt_frame->width, fp);
}
for (int i = 0; i < filt_frame->height / 2; i++)
{
fwrite(filt_frame->data[1] + filt_frame->linesize[1] * i, 1, filt_frame->width / 2, fp);
}
for (int i = 0; i < filt_frame->height / 2; i++)
{
fwrite(filt_frame->data[2] + filt_frame->linesize[2] * i, 1, filt_frame->width / 2, fp);
}
fclose(fp);
}
break;
default:
return -1;
}
av_frame_unref(filt_frame);
}
av_frame_unref(frame);
}
}
}
avfilter_graph_free(&filter_graph);
avcodec_close(dec_ctx);
avcodec_free_context(&dec_ctx);
avformat_close_input(&fmt_ctx);
av_frame_free(&frame);
av_frame_free(&filt_frame);
av_packet_free(&pkt);
return 0;
}
边栏推荐
- MapReduce中分区数与ReduceTask个数关系比较
- 零售连锁门店收银系统源码管理商品分类的功能逻辑分享
- Firewall command simple operation
- Pat class a 1039 course list for student
- Comprehensive evaluation and decision-making method
- How does win11 change the power mode? Win11 method of changing power mode
- Under the high debt of Red Star Macalline, is it eyeing new energy?
- MySQL - multi table query - Cartesian product sum, correct multi table query, equivalent connection and unequal connection, inner connection and outer connection
- Use of rule engine drools
- Compiled by egg serialize JS
猜你喜欢
![[SVN] please execute the 'cleanup' command appears all the time. The solution is that there is no response after cleanup](/img/44/69c434fc9564078e11735881d9bf34.png)
[SVN] please execute the 'cleanup' command appears all the time. The solution is that there is no response after cleanup

Retail chain store cashier system source code management commodity classification function logic sharing

What if win11 cannot wake up from sleep? Solution of win11 unable to wake up during sleep

机器学习之桑基图(用于用户行为分析)
![[C language foundation] 13 preprocessor](/img/4c/ab25d88e9a0cf29bde6e33a2b14225.jpg)
[C language foundation] 13 preprocessor

LeetCode:1184. 公交站间的距离————简单

Dijango learning

Keil v5安装和使用

How to transfer English documents to Chinese?

How engineers treat open source -- the heartfelt words of an old engineer
随机推荐
再获认可 | 赛宁网安连续上榜《CCSIP 2022中国网络安全产业全景图》
数据库连接数查看和修改
MySQL日志分类:错误日志、二进制日志、查询日志、慢查询日志
图论:拓扑排序
Dijango learning
荐书|《DBT技巧训练手册》:宝贝,你就是你活着的原因
Can literature | relationship research draw causal conclusions
Pathmatchingresourcepatternresolver parsing configuration file resource file
规则引擎Drools的使用
Day24 job
What if win11 cannot wake up from sleep? Solution of win11 unable to wake up during sleep
Huawei executives talk about the 35 year old crisis. How can programmers overcome the worry of age?
MySQL - multi table query - Cartesian product sum, correct multi table query, equivalent connection and unequal connection, inner connection and outer connection
Design and implementation of smart campus applet based on cloud development
When you try to delete all bad code in the program | daily anecdotes
Recommendation | DBT skills training manual: baby, you are the reason why you live
Graph translation model
How to download the supplementary literature?
(translation) the button position convention can strengthen the user's usage habits
Compiled by egg serialize JS