前言:

最近闲这没事,整理了一下之前开发过的音视频编解码库,主要基于ffmpeg,实现音视频的编解码、视频流添加文字,音视频同步到MP4等功能。有需要的小伙伴可以参考参考,如果写的有什么不对的地方,欢迎大家指正。


结构:

大概画下哈,明白意思即可,请自觉忽略画的水平。

主要内容:

  1.  软解码、硬解码 

    1. 软解码:使用CPU解码

    2. 硬解码:使用GPU解码, 实现了 QSV 、NVENC 编解码。(如果使用硬解码,无论是qsv ,还是nvenc ,都需要我们重新编译ffmpeg,来支持 qsv 或 nvenc 编解码)

      1. qsv 编译:https://blog.csdn.net/haiyangyunbao813/article/details/107829583

      2. nvenc 编译: https://blog.csdn.net/aphero/article/details/109060019

      3. 如果使用nvenc编解码,先查看下自己的显卡是否支持。https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new

    3. 代码

      h264 : get_decoder_context(AV_CODEC_ID_H264)nvenc: avcodec_find_decoder_by_name("h264_cuvid");qsv  : avcodec_find_decoder_by_name("h264_qsv")
  2. 访问 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;
    }
  3. 开启视频解码器

    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;
    }
  4. 开启视频编码器(可自行设定参数)

    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;
    }
  5. 解码视频流

    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;}
  6. 解码成功后,添加文字

    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;
    }
  7. 添加水印后,实现转换rgb 和 编码视频流。

    1. 图片转换成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;
      }
    2. 编码视频流

      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;
      }
  8. 写入共享内存

    1. 实现共享内存,主要是因为rtsp有终端访问个数的限制,比如我们用的雄迈相机,只能有3个终端访问,所以增加了共享内存这个功能,让其他终端通过共享内存来实时获取音视频流。

  9. 音视频同步到文件

    1. 创建文件(这里通过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;
      }
    2. 写入音视频

      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;
      }
    3. 关闭文件

      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;
      }

其他:

  1.  关于 pts 和 dts 设置

    1. 关于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;
      }
  2. 关于回调函数
    1. set_real_data_rgb_fun : 返回rgb 格式的视频流, 如果不需要,可设置null ,这样就不会进行rgb转换
    2. set_real_data_h264_fun: 返回 h264 数据流
    3. set_rtsp_status_fun: 返回一些状态信息(rtsp连接失败,断线连接 ,重新连接等状态信息)
  3. 关于音频格式
    1. 音频格式我只尝试过aac和g711a,其他格式我也不知道好不好用。
    2. 如果音频格式是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
  4. 关于 drawtext 滤镜的一些操作
    1. 参考文章:https://blog.csdn.net/toopoo/article/details/105603154
  5. 关于硬解码
    1. 如果使用硬解码,注意图片格式设置成 NV12 ,如果设置 YUV,会报错。
  6. 关于断线重连
    1. 每次断线后,我们需要释放资源,在尝试重新连接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;
      }
  7. 关于系统
    1. 测试过 win10 和 Ubuntu18.04

DEMO:

https://download.csdn.net/download/haiyangyunbao813/13944415 有需要的小伙伴可以瞅瞅。


END:

  1. 文章内容参考过很多文章,有些文章地址找不到了,如果有作者介意,留言给我,我会及时改正。

  2. 以上内容如果有不对的地方,欢迎大佬指正。

  3. 最后最后祝我大连,早日战胜疫情,大连必胜,海蛎子必胜。

基于FFmpeg 实现RTSP, 音视频编解码,视频流添加文字,音视频合成MP4相关推荐

  1. 音视频编解码的国际标准

    文章目录 1 音视频编解码的国际标准 1.1 音视频编解码相关的组织 1.2 H.265介绍 1 音视频编解码的国际标准 1.1 音视频编解码相关的组织 音视频编解码主要有如下三大组织推动并发展: I ...

  2. 即时通讯音视频开发(一):视频编解码之理论概述

    前言 即时通讯应用中的实时音视频技术,几乎是IM开发中的最后一道高墙.原因在于:实时音视频技术 = 音视频处理技术 + 网络传输技术 的横向技术应用集合体,而公共互联网不是为了实时通信设计的. 系列文 ...

  3. AV1:为互联网提供开放、免费的视频编解码工具

    从学术研究到进入工业界,Zoe Liu一直在算法和音视频领域,目前在谷歌编解码团队为编解码器AV1做开发支持.Zoe畅谈了评定编解码器的标准,以及AV1的最新进度.本文是『下一代编码器』系列采访之一, ...

  4. 实时通信RTC技术栈之:视频编解码

    1.前言 RTC(Real-time Communications),实时通信,是一个正在兴起的风口行业,经过短短一年的时间,已经有很多玩家进入了这个行业,最典型的应用就是直播连麦和实时音视频通信.但 ...

  5. 中国音视频编解码标准(AVS+) 认证体系研究

    针对国内外相关音视频产业标准.认证技术进行研究,本文结合我国自主知识产权的广播电视先进音视频编解码标准(AVS+)产业发展,对AVS+产品的特点进行分析,确定认证要素:认证范围.认证特性.认证标准.认 ...

  6. 【视频编解码性能优化与实现】

    点击上方"LiveVideoStack"关注我们 " "音视频+无限可能" 是一扇LiveVideoStackCon面向新兴领域开启的大门,在移动互联 ...

  7. 视频编解码标准情况概述

    视频编解码技术框架 现有的视频编解码框架实际上从上世纪70年代以来几乎没有大的变化. <图片来源:贾川民, 赵政辉, 王苫社,等. 基于神经网络的图像视频编码[J]. 电信科学, 2019, 3 ...

  8. 深入浅出理解视频编解码技术

    导读:随着移动互联网技术的蓬勃发展,视频已无处不在.视频直播.视频点播.短视频.视频聊天,已经完全融入了每个人的生活.Cisco 发布的最新报告中写道,到 2022 年,在移动互联网流量中,视频数据占 ...

  9. 【FFMPEG】各种音视频编解码学习详解 h264 ,mpeg4 ,aac 等所有音视频格式

    目录(?)[-] 编解码学习笔记二codec类型 编解码学习笔记三Mpeg系列Mpeg 1和Mpeg 2 编解码学习笔记四Mpeg系列Mpeg 4 编解码学习笔记五Mpeg系列AAC音频 编解码学习笔 ...

最新文章

  1. 让我去健身的不是漂亮小姐姐,居然是贝叶斯统计!
  2. 携程用的什么地图_2020什么项目最值得投资
  3. action中写php函数,WordPress中add_action(将函数连接到指定action)
  4. 融云任杰:强互动,RTC下一个“爆点”场景|拟合
  5. Distributed Representations of Sentences and Documents
  6. 设计一个Enum Class
  7. 删除隐藏网卡(本机IP地址被占用)
  8. flowable理论(一)工作流理论
  9. Drool学习记录(一) 概念、Helloworld
  10. 2020光学期刊一区二区影响因子发布(科睿唯安)
  11. QQ空间内容批量删除脚本
  12. python实现输出日历_python实现输出日历
  13. outlook html 格式签名,Outlook HTML电子邮件签名
  14. 我个人的人生学习感想!
  15. Allegro PCB Design GXL (legacy) - 更新 PCB 中的元件封装
  16. 2010互联网第一件大事件——谷歌退出中国内地
  17. vulnhub靶场GoldenEye靶场
  18. Vue模块语法下(事件处理器自定义组件组件通信)
  19. [微软认证]MCP问题解答
  20. 028-实现阿里云ESC多FLAT网络

热门文章

  1. BZOJ 1003 物流运输
  2. CSP 序列查询新解 202112-2
  3. 2021-08-11王汕8.12黄金TD走势外汇黄金价格,现货白银TD投资操作策略
  4. python字典多重
  5. 仅30行代码,实现一个搜索引擎(1.0版)
  6. C++ 常用的STL库
  7. Echarts - 柱状图(Simple Encode) 样式修改
  8. 项目分包后出的测评报告能盖cnas/cma标识章吗
  9. Unity3D之AssetBundle资源加载封装
  10. 【互动媒体技术赏析作业】