基于FFmpeg 实现RTSP, 音视频编解码,视频流添加文字,音视频合成MP4
前言:
最近闲这没事,整理了一下之前开发过的音视频编解码库,主要基于ffmpeg,实现音视频的编解码、视频流添加文字,音视频同步到MP4等功能。有需要的小伙伴可以参考参考,如果写的有什么不对的地方,欢迎大家指正。
结构:
大概画下哈,明白意思即可,请自觉忽略画的水平。
主要内容:
软解码、硬解码
软解码:使用CPU解码
硬解码:使用GPU解码, 实现了 QSV 、NVENC 编解码。(如果使用硬解码,无论是qsv ,还是nvenc ,都需要我们重新编译ffmpeg,来支持 qsv 或 nvenc 编解码)
qsv 编译:https://blog.csdn.net/haiyangyunbao813/article/details/107829583
nvenc 编译: https://blog.csdn.net/aphero/article/details/109060019
如果使用nvenc编解码,先查看下自己的显卡是否支持。https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new
代码
h264 : get_decoder_context(AV_CODEC_ID_H264)nvenc: avcodec_find_decoder_by_name("h264_cuvid");qsv : avcodec_find_decoder_by_name("h264_qsv")
访问 rtsp ,获取 rtsp 信息流
int Qffmpeg::find_stream_info(char *rtsp, AVFormatContext **ifmt_ctx) {if ((m_ret = avformat_open_input(ifmt_ctx, rtsp, 0, &p_optionsDict)) < 0){printf("Could not open input file:%s\n", rtsp);return -1;}if ((m_ret = avformat_find_stream_info(*ifmt_ctx, NULL)) < 0){ // Get information on the input file (number of streams etc.).printf("Could not open find stream info:%s\n", rtsp);return -1;}for (unsigned int i = 0; i < (*ifmt_ctx)->nb_streams; i++){ // dump informationav_dump_format(*ifmt_ctx, i, rtsp, 0);}return 1; }
开启视频解码器
AVCodecContext *Qffmpeg::get_decoder_context(AVStream *p_vst) { #if QVSAVCodec *videoCodec = avcodec_find_decoder_by_name("h264_qsv"); #elif CUDAAVCodec *videoCodec = avcodec_find_decoder_by_name("h264_cuvid"); #elseAVCodec *videoCodec = avcodec_find_decoder(p_vst->codecpar->codec_id); #endifAVCodecContext *videoCodecCtx = avcodec_alloc_context3(videoCodec);avcodec_parameters_to_context(videoCodecCtx, p_vst->codecpar);if (videoCodec == NULL){printf("encode avcodec_parameters_to_context faild.\n");avcodec_free_context(&videoCodecCtx);return nullptr;}if (avcodec_open2(videoCodecCtx, videoCodec, NULL) < 0){printf("decode avcodec_open2 faild.\n");avcodec_free_context(&videoCodecCtx);return nullptr;}return videoCodecCtx; }
开启视频编码器(可自行设定参数)
AVCodecContext *Qffmpeg::get_encoder_context(AVStream *p_vst, AVCodecID codecId, AVPixelFormat pix_fmt, int fps, int width, int height) {#if QVSAVCodec *pCodec264 = avcodec_find_encoder_by_name("h264_qsv"); #elif CUDAAVCodec *pCodec264 = avcodec_find_encoder_by_name("h264_nvenc"); #elseAVCodec *pCodec264 = avcodec_find_encoder(codecId); #endifif (NULL == pCodec264){printf("avcodec_find_encoder fail. \n");return NULL;}AVCodecContext *c = avcodec_alloc_context3(pCodec264);c->codec_id = p_vst->codecpar->codec_id;c->codec_type = p_vst->codecpar->codec_type;c->width = p_vst->codecpar->width;c->height = p_vst->codecpar->height; #if QVSc->pix_fmt = AV_PIX_FMT_NV12; #elsec->pix_fmt = pix_fmt; #endifint frame_rate = p_vst->avg_frame_rate.num / p_vst->avg_frame_rate.den;c->time_base = { 1, frame_rate * 2 };c->flags = 0;// ========================= CBR ==========================c->bit_rate = 4000 * 1000;c->bit_rate_tolerance = 4000 * 1000;c->gop_size = 10;c->max_b_frames = 0;c->qmin = 10;c->qmax = 50;c->qblur = 0.0;c->spatial_cplx_masking = 0.3;c->me_pre_cmp = 2;c->b_quant_factor = 1.25;c->b_quant_offset = 1.25;c->i_quant_factor = 0.8;c->i_quant_offset = 0.0;c->dct_algo = 0;c->lumi_masking = 0.0;c->dark_masking = 0.0;// ========================= CBR ==========================#if QVSav_opt_set(c->priv_data, "tune", "zerolatency", 0); #elif CUDAav_opt_set(c->priv_data, "tune", "zerolatency", 0); #else// 修改编码形式 ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placeboav_opt_set(c->priv_data, "preset", "ultrafast", 0);// film 电影、真人类型 animation 动画 grain 需要保留大量的grain时用 stillimage 静态图像编码时使用// psnr 为提高psnr做了优化的参 ssim 为提高ssim做了优化的参数// fastdecode 可以快速解码的参 zerolatency 零延迟,用在需要非常低的延迟的情况下,比如电视电话会议的编码av_opt_set(c->priv_data, "tune", "zerolatency", 0); #endifint ret = avcodec_open2(c, pCodec264, NULL);if (ret < 0){printf("encode avcodec_open2 faild.[%d] \n", ret);avcodec_free_context(&c);return NULL;}return c; }
解码视频流
int ret = avcodec_send_packet(p_videoDeCodecCtx, &m_pkt);av_packet_unref(&m_pkt);if (ret != 0){continue;}AVFrame *frame_in = av_frame_alloc();ret = avcodec_receive_frame(p_videoDeCodecCtx, frame_in);if (ret != 0){printf("avcodec_receive_frame failed.[%d] \n", m_ret);av_frame_free(&frame_in);continue;}
解码成功后,添加文字
AVFrame *Qffmpeg::add_osd_info(AVFrame *frame_in, OSD_INFO *info) {// todoif (info == NULL){//printf("=============== NO OSD INFO ===============");return frame_in;}AVFilterContext *buffersink_ctx;AVFilterContext *buffersrc_ctx;// 添加osdAVFilterGraph *filter_graph = add_filter(frame_in, &buffersink_ctx, &buffersrc_ctx, p_videoDeCodecCtx, info);if (filter_graph == nullptr){printf("=================== add_filter failed ===================\n");av_frame_free(&frame_in);return nullptr;}// 添加水印m_ret = av_buffersrc_add_frame(buffersrc_ctx, frame_in);if (m_ret < 0){printf("=================== av_buffersrc_add_frame failed.[%d] ===================\n", m_ret);av_frame_free(&frame_in);avfilter_graph_free(&filter_graph);filter_graph = NULL;return nullptr;}AVFrame *frame_out = av_frame_alloc();// 解码水印m_ret = av_buffersink_get_frame(buffersink_ctx, frame_out);if (m_ret < 0){printf("=================== av_buffersink_get_frame failed.[%d]===================\n", m_ret);av_frame_free(&frame_in);av_frame_free(&frame_out);avfilter_graph_free(&filter_graph);filter_graph = NULL;return nullptr;}av_frame_free(&frame_in);avfilter_graph_free(&filter_graph);filter_graph = NULL;return frame_out; }
添加水印后,实现转换rgb 和 编码视频流。
图片转换成rgb格式(硬解码: nv12 -> rgb , 软解码: yuv-> rgb) 。转换rgb格式,主要是项目中需要,如果不需要,可pass。
uint8_t *Qffmpeg::convert_rgb(AVFrame *frame, int srcW, int srcH, AVPixelFormat srcPixFmt, int desW, int desH, AVPixelFormat desPixFmt, int &nSize) {// old//int bytes = avpicture_get_size(desPixFmt, desW, desH);int bytes = av_image_get_buffer_size(desPixFmt, desW, desH, 1);uint8_t *buffer_rgb = (uint8_t *)av_malloc(bytes);AVFrame *pFrameRGB = av_frame_alloc();// old//avpicture_fill((AVPicture *)pFrameRGB, buffer_rgb, desPixFmt, desW, desH);av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer_rgb, desPixFmt, desW, desH, 1);SwsContext *img_convert_ctx = sws_getContext(srcW, srcH, srcPixFmt, desW, desH, desPixFmt, NULL, NULL, NULL, NULL);if (img_convert_ctx == NULL){printf("can't init convert context.\n");av_frame_free(&pFrameRGB);av_free(buffer_rgb);return nullptr;}sws_scale(img_convert_ctx, frame->data, frame->linesize, 0, srcH, pFrameRGB->data, pFrameRGB->linesize);sws_freeContext(img_convert_ctx);av_frame_free(&pFrameRGB);nSize = bytes;return buffer_rgb; }
编码视频流
int Qffmpeg::encode_and_call_back(AVFrame *frame, int w, int h) {if (p_videoEnCodecCtx == NULL)return -1;int ret = avcodec_send_frame(p_videoEnCodecCtx, frame);if (ret != 0){return -1;}AVPacket pkt;av_init_packet(&pkt);pkt.data = NULL;pkt.size = 0;ret = avcodec_receive_packet(p_videoEnCodecCtx, &pkt);if (ret != 0){printf("=================== avcodec_receive_packet failed.[%d] ===================\n", ret);av_packet_unref(&pkt);return -1;}pkt.pts = m_pts;pkt.dts = m_dts;pkt.duration = m_duration;PACKET_INFO info;get_av_packet_info(pkt, w, h, PACKET_TYPE_VIDEO, m_frame_pts_diff_v, info);m_real_data_h264_fun(&info, m_user_id);av_packet_unref(&pkt);return 1; }
写入共享内存
实现共享内存,主要是因为rtsp有终端访问个数的限制,比如我们用的雄迈相机,只能有3个终端访问,所以增加了共享内存这个功能,让其他终端通过共享内存来实时获取音视频流。
音视频同步到文件
创建文件(这里通过rtsp音视频流信息来创建文件信息)
int ff_file::create_file(const char *file_name) {avformat_alloc_output_context2(&p_ofmt_ctx, NULL, NULL, file_name);if (!p_ofmt_ctx){printf("Could not create output context\n");m_ret = AVERROR_UNKNOWN;return -1;}p_ofmt = p_ofmt_ctx->oformat;if (!p_ifmt_ctx)return -1;for (unsigned int i = 0; i < p_ifmt_ctx->nb_streams; i++){AVMediaType type = p_ifmt_ctx->streams[i]->codecpar->codec_type;if (type == AVMEDIA_TYPE_VIDEO){m_vi_stream_in = i;}else if (type == AVMEDIA_TYPE_AUDIO){m_ai_stream_in = i;}AVStream *in_stream = p_ifmt_ctx->streams[i];AVCodec *codec = avcodec_find_decoder(in_stream->codecpar->codec_id);// 开辟流通道AVStream *out_stream = avformat_new_stream(p_ofmt_ctx, codec);if (!out_stream){printf("Failed allocating output stream");m_ret = AVERROR_UNKNOWN;return -1;}if (type == AVMEDIA_TYPE_VIDEO){m_vi_stream_out = out_stream->index;}else if (type == AVMEDIA_TYPE_AUDIO){m_ai_stream_out = out_stream->index;}AVCodecContext *codecCtx = avcodec_alloc_context3(codec);m_ret = avcodec_parameters_to_context(codecCtx, in_stream->codecpar);if (m_ret < 0){printf("avcodec_parameters_to_context failed..");return -1;}codecCtx->codec_tag = 0;if (type == AVMEDIA_TYPE_VIDEO)codecCtx->has_b_frames = 0;if (p_ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;if (type == AVMEDIA_TYPE_VIDEO){int frame_rate = in_stream->avg_frame_rate.num / in_stream->avg_frame_rate.den;codecCtx->time_base = { 1, frame_rate };}m_ret = avcodec_parameters_from_context(out_stream->codecpar, codecCtx);if (m_ret < 0){printf("avcodec_parameters_from_context failed..");return -1;}}av_dump_format(p_ofmt_ctx, 0, file_name, 1);if (!(p_ofmt->flags & AVFMT_NOFILE)){if (avio_open(&p_ofmt_ctx->pb, file_name, AVIO_FLAG_WRITE) < 0){printf("Could not open output file '%s'", file_name);return -1;}}m_ret = avformat_write_header(p_ofmt_ctx, NULL);if (m_ret < 0){printf("Error occurred when opening output file {0}\n", m_ret);return -1;}return 1; }
写入音视频
int ff_file::write_packet_info(unsigned char *buffer, PACKET_INFO *info) {if (NULL == p_ifmt_ctx)return -1;AVPacket pkt;av_init_packet(&pkt);pkt.data = (unsigned char *)buffer;pkt.size = info->nPacketSize;pkt.pts = info->nPts;pkt.dts = info->nDts;pkt.duration = info->nDurtion;pkt.pos = -1;pkt.stream_index = info->nPacketType == 1001 ? m_vi_stream_out : m_ai_stream_out;AVStream *in_stream;AVStream *out_stream;if (pkt.stream_index == m_vi_stream_out){in_stream = p_ifmt_ctx->streams[m_vi_stream_in];out_stream = p_ofmt_ctx->streams[m_vi_stream_out];pkt.pts = m_cur_pts_v + info->nFramePtsDiff;pkt.dts = pkt.pts;pkt.duration = info->nDurtion;m_cur_pts_v = pkt.pts;pkt.pts = av_rescale_q(pkt.pts, in_stream->time_base, out_stream->time_base);pkt.dts = av_rescale_q(pkt.dts, in_stream->time_base, out_stream->time_base);pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);//m_frame_index_v++;}else{in_stream = p_ifmt_ctx->streams[m_ai_stream_in];out_stream = p_ofmt_ctx->streams[m_ai_stream_out];pkt.pts = m_cur_pts_a + info->nFramePtsDiff;pkt.dts = pkt.pts;pkt.duration = info->nDurtion;m_cur_pts_a = pkt.pts;pkt.pts = av_rescale_q(pkt.pts, in_stream->time_base, out_stream->time_base);pkt.dts = av_rescale_q(pkt.dts, in_stream->time_base, out_stream->time_base);pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);//m_frame_index_a++;}if (av_interleaved_write_frame(p_ofmt_ctx, &pkt) == 0){//if (info->nPacketType == 1001)// printf("write video packet Success. \n");//else// printf("write audio packet Success. \n");}av_packet_unref(&pkt);return 1; }
关闭文件
int ff_file::close_file() {if (NULL == p_ofmt_ctx){return 1;}av_write_trailer(p_ofmt_ctx);if (!(p_ofmt->flags & AVFMT_NOFILE))avio_close(p_ofmt_ctx->pb);avformat_free_context(p_ofmt_ctx);p_ofmt_ctx = NULL;return 1; }
其他:
- 关于 pts 和 dts 设置
- 关于pts 设置, 我是通过获取第一帧数据流的pts - 数据流的start_time,然后在计算后面每一帧的相隔时间,由于没有b帧 ,所以 dts = pts. (音视频同理). 这样在写入文件中时,就能做到音视频同步了。
void Qffmpeg::set_pts(AVPacket &pkt) {if (pkt.stream_index == m_audio_st_index){if (is_first_frame_a){pkt.pts = pkt.pts - p_ifmt_ctx->streams[m_audio_st_index]->start_time;pkt.dts = pkt.pts;m_frame_pts_diff_a = 0;m_last_pts_a = 0;is_first_frame_a = false;return;}m_frame_pts_diff_a = pkt.pts - m_last_pts_a;m_last_pts_a = pkt.pts;return;}if (is_first_frame_v){pkt.pts = pkt.pts - p_ifmt_ctx->streams[m_video_st_index]->start_time;pkt.dts = pkt.pts;m_frame_pts_diff_v = 0;m_last_pts_v = 0;is_first_frame_v = false;return;}m_frame_pts_diff_v = pkt.pts - m_last_pts_v;m_last_pts_v = pkt.pts;return; }
- 关于pts 设置, 我是通过获取第一帧数据流的pts - 数据流的start_time,然后在计算后面每一帧的相隔时间,由于没有b帧 ,所以 dts = pts. (音视频同理). 这样在写入文件中时,就能做到音视频同步了。
- 关于回调函数
- set_real_data_rgb_fun : 返回rgb 格式的视频流, 如果不需要,可设置null ,这样就不会进行rgb转换
- set_real_data_h264_fun: 返回 h264 数据流
- set_rtsp_status_fun: 返回一些状态信息(rtsp连接失败,断线连接 ,重新连接等状态信息)
- 关于音频格式
- 音频格式我只尝试过aac和g711a,其他格式我也不知道好不好用。
- 如果音频格式是G711a ,对应ffmpeg pcm_alaw, 需要重新编译ffmpeg,来支持 pcm_alaw格式 。参考: https://blog.csdn.net/zhuyunier/article/details/80814227。 或者 将 g711a 转成 acc , 参考 :https://blog.csdn.net/haiyangyunbao813/article/details/101788264
- 关于 drawtext 滤镜的一些操作
- 参考文章:https://blog.csdn.net/toopoo/article/details/105603154
- 关于硬解码
- 如果使用硬解码,注意图片格式设置成 NV12 ,如果设置 YUV,会报错。
- 关于断线重连
- 每次断线后,我们需要释放资源,在尝试重新连接RTSP, 否则重连不上。
void Qffmpeg::release() {av_dict_free(&p_optionsDict);if (NULL != p_videoDeCodecCtx){avcodec_free_context(&p_videoDeCodecCtx);p_videoDeCodecCtx = NULL;}if (NULL != p_videoEnCodecCtx){avcodec_free_context(&p_videoEnCodecCtx);p_videoEnCodecCtx = NULL;}if (NULL != p_ifmt_ctx){avformat_close_input(&p_ifmt_ctx);p_ifmt_ctx = NULL;}is_first_frame_a = 1;is_first_frame_v = 1; }
- 每次断线后,我们需要释放资源,在尝试重新连接RTSP, 否则重连不上。
- 关于系统
- 测试过 win10 和 Ubuntu18.04
DEMO:
https://download.csdn.net/download/haiyangyunbao813/13944415 有需要的小伙伴可以瞅瞅。
END:
文章内容参考过很多文章,有些文章地址找不到了,如果有作者介意,留言给我,我会及时改正。
以上内容如果有不对的地方,欢迎大佬指正。
最后最后祝我大连,早日战胜疫情,大连必胜,海蛎子必胜。
基于FFmpeg 实现RTSP, 音视频编解码,视频流添加文字,音视频合成MP4相关推荐
- 音视频编解码的国际标准
文章目录 1 音视频编解码的国际标准 1.1 音视频编解码相关的组织 1.2 H.265介绍 1 音视频编解码的国际标准 1.1 音视频编解码相关的组织 音视频编解码主要有如下三大组织推动并发展: I ...
- 即时通讯音视频开发(一):视频编解码之理论概述
前言 即时通讯应用中的实时音视频技术,几乎是IM开发中的最后一道高墙.原因在于:实时音视频技术 = 音视频处理技术 + 网络传输技术 的横向技术应用集合体,而公共互联网不是为了实时通信设计的. 系列文 ...
- AV1:为互联网提供开放、免费的视频编解码工具
从学术研究到进入工业界,Zoe Liu一直在算法和音视频领域,目前在谷歌编解码团队为编解码器AV1做开发支持.Zoe畅谈了评定编解码器的标准,以及AV1的最新进度.本文是『下一代编码器』系列采访之一, ...
- 实时通信RTC技术栈之:视频编解码
1.前言 RTC(Real-time Communications),实时通信,是一个正在兴起的风口行业,经过短短一年的时间,已经有很多玩家进入了这个行业,最典型的应用就是直播连麦和实时音视频通信.但 ...
- 中国音视频编解码标准(AVS+) 认证体系研究
针对国内外相关音视频产业标准.认证技术进行研究,本文结合我国自主知识产权的广播电视先进音视频编解码标准(AVS+)产业发展,对AVS+产品的特点进行分析,确定认证要素:认证范围.认证特性.认证标准.认 ...
- 【视频编解码性能优化与实现】
点击上方"LiveVideoStack"关注我们 " "音视频+无限可能" 是一扇LiveVideoStackCon面向新兴领域开启的大门,在移动互联 ...
- 视频编解码标准情况概述
视频编解码技术框架 现有的视频编解码框架实际上从上世纪70年代以来几乎没有大的变化. <图片来源:贾川民, 赵政辉, 王苫社,等. 基于神经网络的图像视频编码[J]. 电信科学, 2019, 3 ...
- 深入浅出理解视频编解码技术
导读:随着移动互联网技术的蓬勃发展,视频已无处不在.视频直播.视频点播.短视频.视频聊天,已经完全融入了每个人的生活.Cisco 发布的最新报告中写道,到 2022 年,在移动互联网流量中,视频数据占 ...
- 【FFMPEG】各种音视频编解码学习详解 h264 ,mpeg4 ,aac 等所有音视频格式
目录(?)[-] 编解码学习笔记二codec类型 编解码学习笔记三Mpeg系列Mpeg 1和Mpeg 2 编解码学习笔记四Mpeg系列Mpeg 4 编解码学习笔记五Mpeg系列AAC音频 编解码学习笔 ...
最新文章
- 让我去健身的不是漂亮小姐姐,居然是贝叶斯统计!
- 携程用的什么地图_2020什么项目最值得投资
- action中写php函数,WordPress中add_action(将函数连接到指定action)
- 融云任杰:强互动,RTC下一个“爆点”场景|拟合
- Distributed Representations of Sentences and Documents
- 设计一个Enum Class
- 删除隐藏网卡(本机IP地址被占用)
- flowable理论(一)工作流理论
- Drool学习记录(一) 概念、Helloworld
- 2020光学期刊一区二区影响因子发布(科睿唯安)
- QQ空间内容批量删除脚本
- python实现输出日历_python实现输出日历
- outlook html 格式签名,Outlook HTML电子邮件签名
- 我个人的人生学习感想!
- Allegro PCB Design GXL (legacy) - 更新 PCB 中的元件封装
- 2010互联网第一件大事件——谷歌退出中国内地
- vulnhub靶场GoldenEye靶场
- Vue模块语法下(事件处理器自定义组件组件通信)
- [微软认证]MCP问题解答
- 028-实现阿里云ESC多FLAT网络