当前位置:   article > 正文

基于FFmpeg 实现RTSP, 音视频编解码,视频流添加文字,音视频合成MP4_ffmpeg4 rtsp流含音频编解码

ffmpeg4 rtsp流含音频编解码

 

前言: 

最近闲这没事,整理了一下之前开发过的音视频编解码库,主要基于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. 代码

      1. h264 : get_decoder_context(AV_CODEC_ID_H264)
      2. nvenc: avcodec_find_decoder_by_name("h264_cuvid");
      3. qsv : avcodec_find_decoder_by_name("h264_qsv")

       

  2.  访问 rtsp ,获取 rtsp 信息流

    1. int Qffmpeg::find_stream_info(char *rtsp, AVFormatContext **ifmt_ctx)
    2. {
    3. if ((m_ret = avformat_open_input(ifmt_ctx, rtsp, 0, &p_optionsDict)) < 0)
    4. {
    5. printf("Could not open input file:%s\n", rtsp);
    6. return -1;
    7. }
    8. if ((m_ret = avformat_find_stream_info(*ifmt_ctx, NULL)) < 0)
    9. { // Get information on the input file (number of streams etc.).
    10. printf("Could not open find stream info:%s\n", rtsp);
    11. return -1;
    12. }
    13. for (unsigned int i = 0; i < (*ifmt_ctx)->nb_streams; i++)
    14. { // dump information
    15. av_dump_format(*ifmt_ctx, i, rtsp, 0);
    16. }
    17. return 1;
    18. }

     

  3. 开启视频解码器

    1. AVCodecContext *Qffmpeg::get_decoder_context(AVStream *p_vst)
    2. {
    3. #if QVS
    4. AVCodec *videoCodec = avcodec_find_decoder_by_name("h264_qsv");
    5. #elif CUDA
    6. AVCodec *videoCodec = avcodec_find_decoder_by_name("h264_cuvid");
    7. #else
    8. AVCodec *videoCodec = avcodec_find_decoder(p_vst->codecpar->codec_id);
    9. #endif
    10. AVCodecContext *videoCodecCtx = avcodec_alloc_context3(videoCodec);
    11. avcodec_parameters_to_context(videoCodecCtx, p_vst->codecpar);
    12. if (videoCodec == NULL)
    13. {
    14. printf("encode avcodec_parameters_to_context faild.\n");
    15. avcodec_free_context(&videoCodecCtx);
    16. return nullptr;
    17. }
    18. if (avcodec_open2(videoCodecCtx, videoCodec, NULL) < 0)
    19. {
    20. printf("decode avcodec_open2 faild.\n");
    21. avcodec_free_context(&videoCodecCtx);
    22. return nullptr;
    23. }
    24. return videoCodecCtx;
    25. }

     

  4. 开启视频编码器(可自行设定参数)

    1. AVCodecContext *Qffmpeg::get_encoder_context(AVStream *p_vst, AVCodecID codecId, AVPixelFormat pix_fmt, int fps, int width, int height)
    2. {
    3. #if QVS
    4. AVCodec *pCodec264 = avcodec_find_encoder_by_name("h264_qsv");
    5. #elif CUDA
    6. AVCodec *pCodec264 = avcodec_find_encoder_by_name("h264_nvenc");
    7. #else
    8. AVCodec *pCodec264 = avcodec_find_encoder(codecId);
    9. #endif
    10. if (NULL == pCodec264)
    11. {
    12. printf("avcodec_find_encoder fail. \n");
    13. return NULL;
    14. }
    15. AVCodecContext *c = avcodec_alloc_context3(pCodec264);
    16. c->codec_id = p_vst->codecpar->codec_id;
    17. c->codec_type = p_vst->codecpar->codec_type;
    18. c->width = p_vst->codecpar->width;
    19. c->height = p_vst->codecpar->height;
    20. #if QVS
    21. c->pix_fmt = AV_PIX_FMT_NV12;
    22. #else
    23. c->pix_fmt = pix_fmt;
    24. #endif
    25. int frame_rate = p_vst->avg_frame_rate.num / p_vst->avg_frame_rate.den;
    26. c->time_base = { 1, frame_rate * 2 };
    27. c->flags = 0;
    28. // ========================= CBR ==========================
    29. c->bit_rate = 4000 * 1000;
    30. c->bit_rate_tolerance = 4000 * 1000;
    31. c->gop_size = 10;
    32. c->max_b_frames = 0;
    33. c->qmin = 10;
    34. c->qmax = 50;
    35. c->qblur = 0.0;
    36. c->spatial_cplx_masking = 0.3;
    37. c->me_pre_cmp = 2;
    38. c->b_quant_factor = 1.25;
    39. c->b_quant_offset = 1.25;
    40. c->i_quant_factor = 0.8;
    41. c->i_quant_offset = 0.0;
    42. c->dct_algo = 0;
    43. c->lumi_masking = 0.0;
    44. c->dark_masking = 0.0;
    45. // ========================= CBR ==========================
    46. #if QVS
    47. av_opt_set(c->priv_data, "tune", "zerolatency", 0);
    48. #elif CUDA
    49. av_opt_set(c->priv_data, "tune", "zerolatency", 0);
    50. #else
    51. // 修改编码形式 ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo
    52. av_opt_set(c->priv_data, "preset", "ultrafast", 0);
    53. // film 电影、真人类型 animation 动画 grain 需要保留大量的grain时用 stillimage 静态图像编码时使用
    54. // psnr 为提高psnr做了优化的参 ssim 为提高ssim做了优化的参数
    55. // fastdecode 可以快速解码的参 zerolatency 零延迟,用在需要非常低的延迟的情况下,比如电视电话会议的编码
    56. av_opt_set(c->priv_data, "tune", "zerolatency", 0);
    57. #endif
    58. int ret = avcodec_open2(c, pCodec264, NULL);
    59. if (ret < 0)
    60. {
    61. printf("encode avcodec_open2 faild.[%d] \n", ret);
    62. avcodec_free_context(&c);
    63. return NULL;
    64. }
    65. return c;
    66. }

     

  5. 解码视频流

    1. int ret = avcodec_send_packet(p_videoDeCodecCtx, &m_pkt);
    2. av_packet_unref(&m_pkt);
    3. if (ret != 0)
    4. {
    5. continue;
    6. }
    7. AVFrame *frame_in = av_frame_alloc();
    8. ret = avcodec_receive_frame(p_videoDeCodecCtx, frame_in);
    9. if (ret != 0)
    10. {
    11. printf("avcodec_receive_frame failed.[%d] \n", m_ret);
    12. av_frame_free(&frame_in);
    13. continue;
    14. }

     

  6. 解码成功后,添加文字

    1. AVFrame *Qffmpeg::add_osd_info(AVFrame *frame_in, OSD_INFO *info)
    2. {
    3. // todo
    4. if (info == NULL)
    5. {
    6. //printf("=============== NO OSD INFO ===============");
    7. return frame_in;
    8. }
    9. AVFilterContext *buffersink_ctx;
    10. AVFilterContext *buffersrc_ctx;
    11. // 添加osd
    12. AVFilterGraph *filter_graph = add_filter(frame_in, &buffersink_ctx, &buffersrc_ctx, p_videoDeCodecCtx, info);
    13. if (filter_graph == nullptr)
    14. {
    15. printf("=================== add_filter failed ===================\n");
    16. av_frame_free(&frame_in);
    17. return nullptr;
    18. }
    19. // 添加水印
    20. m_ret = av_buffersrc_add_frame(buffersrc_ctx, frame_in);
    21. if (m_ret < 0)
    22. {
    23. printf("=================== av_buffersrc_add_frame failed.[%d] ===================\n", m_ret);
    24. av_frame_free(&frame_in);
    25. avfilter_graph_free(&filter_graph);
    26. filter_graph = NULL;
    27. return nullptr;
    28. }
    29. AVFrame *frame_out = av_frame_alloc();
    30. // 解码水印
    31. m_ret = av_buffersink_get_frame(buffersink_ctx, frame_out);
    32. if (m_ret < 0)
    33. {
    34. printf("=================== av_buffersink_get_frame failed.[%d]===================\n", m_ret);
    35. av_frame_free(&frame_in);
    36. av_frame_free(&frame_out);
    37. avfilter_graph_free(&filter_graph);
    38. filter_graph = NULL;
    39. return nullptr;
    40. }
    41. av_frame_free(&frame_in);
    42. avfilter_graph_free(&filter_graph);
    43. filter_graph = NULL;
    44. return frame_out;
    45. }

     

  7.  添加水印后,实现转换rgb 和 编码视频流。

    1. 图片转换成rgb格式(硬解码: nv12 -> rgb , 软解码: yuv-> rgb)  。转换rgb格式,主要是项目中需要,如果不需要,可pass。

      1. uint8_t *Qffmpeg::convert_rgb(AVFrame *frame, int srcW, int srcH, AVPixelFormat srcPixFmt, int desW, int desH, AVPixelFormat desPixFmt, int &nSize)
      2. {
      3. // old
      4. //int bytes = avpicture_get_size(desPixFmt, desW, desH);
      5. int bytes = av_image_get_buffer_size(desPixFmt, desW, desH, 1);
      6. uint8_t *buffer_rgb = (uint8_t *)av_malloc(bytes);
      7. AVFrame *pFrameRGB = av_frame_alloc();
      8. // old
      9. //avpicture_fill((AVPicture *)pFrameRGB, buffer_rgb, desPixFmt, desW, desH);
      10. av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer_rgb, desPixFmt, desW, desH, 1);
      11. SwsContext *img_convert_ctx = sws_getContext(srcW, srcH, srcPixFmt, desW, desH, desPixFmt, NULL, NULL, NULL, NULL);
      12. if (img_convert_ctx == NULL)
      13. {
      14. printf("can't init convert context.\n");
      15. av_frame_free(&pFrameRGB);
      16. av_free(buffer_rgb);
      17. return nullptr;
      18. }
      19. sws_scale(img_convert_ctx, frame->data, frame->linesize, 0, srcH, pFrameRGB->data, pFrameRGB->linesize);
      20. sws_freeContext(img_convert_ctx);
      21. av_frame_free(&pFrameRGB);
      22. nSize = bytes;
      23. return buffer_rgb;
      24. }

       

    2. 编码视频流

      1. int Qffmpeg::encode_and_call_back(AVFrame *frame, int w, int h)
      2. {
      3. if (p_videoEnCodecCtx == NULL)
      4. return -1;
      5. int ret = avcodec_send_frame(p_videoEnCodecCtx, frame);
      6. if (ret != 0)
      7. {
      8. return -1;
      9. }
      10. AVPacket pkt;
      11. av_init_packet(&pkt);
      12. pkt.data = NULL;
      13. pkt.size = 0;
      14. ret = avcodec_receive_packet(p_videoEnCodecCtx, &pkt);
      15. if (ret != 0)
      16. {
      17. printf("=================== avcodec_receive_packet failed.[%d] ===================\n", ret);
      18. av_packet_unref(&pkt);
      19. return -1;
      20. }
      21. pkt.pts = m_pts;
      22. pkt.dts = m_dts;
      23. pkt.duration = m_duration;
      24. PACKET_INFO info;
      25. get_av_packet_info(pkt, w, h, PACKET_TYPE_VIDEO, m_frame_pts_diff_v, info);
      26. m_real_data_h264_fun(&info, m_user_id);
      27. av_packet_unref(&pkt);
      28. return 1;
      29. }

       

  8. 写入共享内存

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

  9. 音视频同步到文件

    1. 创建文件(这里通过rtsp音视频流信息来创建文件信息)

      1. int ff_file::create_file(const char *file_name)
      2. {
      3. avformat_alloc_output_context2(&p_ofmt_ctx, NULL, NULL, file_name);
      4. if (!p_ofmt_ctx)
      5. {
      6. printf("Could not create output context\n");
      7. m_ret = AVERROR_UNKNOWN;
      8. return -1;
      9. }
      10. p_ofmt = p_ofmt_ctx->oformat;
      11. if (!p_ifmt_ctx)
      12. return -1;
      13. for (unsigned int i = 0; i < p_ifmt_ctx->nb_streams; i++)
      14. {
      15. AVMediaType type = p_ifmt_ctx->streams[i]->codecpar->codec_type;
      16. if (type == AVMEDIA_TYPE_VIDEO)
      17. {
      18. m_vi_stream_in = i;
      19. }
      20. else if (type == AVMEDIA_TYPE_AUDIO)
      21. {
      22. m_ai_stream_in = i;
      23. }
      24. AVStream *in_stream = p_ifmt_ctx->streams[i];
      25. AVCodec *codec = avcodec_find_decoder(in_stream->codecpar->codec_id);
      26. // 开辟流通道
      27. AVStream *out_stream = avformat_new_stream(p_ofmt_ctx, codec);
      28. if (!out_stream)
      29. {
      30. printf("Failed allocating output stream");
      31. m_ret = AVERROR_UNKNOWN;
      32. return -1;
      33. }
      34. if (type == AVMEDIA_TYPE_VIDEO)
      35. {
      36. m_vi_stream_out = out_stream->index;
      37. }
      38. else if (type == AVMEDIA_TYPE_AUDIO)
      39. {
      40. m_ai_stream_out = out_stream->index;
      41. }
      42. AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
      43. m_ret = avcodec_parameters_to_context(codecCtx, in_stream->codecpar);
      44. if (m_ret < 0)
      45. {
      46. printf("avcodec_parameters_to_context failed..");
      47. return -1;
      48. }
      49. codecCtx->codec_tag = 0;
      50. if (type == AVMEDIA_TYPE_VIDEO)
      51. codecCtx->has_b_frames = 0;
      52. if (p_ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
      53. codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
      54. if (type == AVMEDIA_TYPE_VIDEO)
      55. {
      56. int frame_rate = in_stream->avg_frame_rate.num / in_stream->avg_frame_rate.den;
      57. codecCtx->time_base = { 1, frame_rate };
      58. }
      59. m_ret = avcodec_parameters_from_context(out_stream->codecpar, codecCtx);
      60. if (m_ret < 0)
      61. {
      62. printf("avcodec_parameters_from_context failed..");
      63. return -1;
      64. }
      65. }
      66. av_dump_format(p_ofmt_ctx, 0, file_name, 1);
      67. if (!(p_ofmt->flags & AVFMT_NOFILE))
      68. {
      69. if (avio_open(&p_ofmt_ctx->pb, file_name, AVIO_FLAG_WRITE) < 0)
      70. {
      71. printf("Could not open output file '%s'", file_name);
      72. return -1;
      73. }
      74. }
      75. m_ret = avformat_write_header(p_ofmt_ctx, NULL);
      76. if (m_ret < 0)
      77. {
      78. printf("Error occurred when opening output file {0}\n", m_ret);
      79. return -1;
      80. }
      81. return 1;
      82. }

       

    2. 写入音视频

      1. int ff_file::write_packet_info(unsigned char *buffer, PACKET_INFO *info)
      2. {
      3. if (NULL == p_ifmt_ctx)
      4. return -1;
      5. AVPacket pkt;
      6. av_init_packet(&pkt);
      7. pkt.data = (unsigned char *)buffer;
      8. pkt.size = info->nPacketSize;
      9. pkt.pts = info->nPts;
      10. pkt.dts = info->nDts;
      11. pkt.duration = info->nDurtion;
      12. pkt.pos = -1;
      13. pkt.stream_index = info->nPacketType == 1001 ? m_vi_stream_out : m_ai_stream_out;
      14. AVStream *in_stream;
      15. AVStream *out_stream;
      16. if (pkt.stream_index == m_vi_stream_out)
      17. {
      18. in_stream = p_ifmt_ctx->streams[m_vi_stream_in];
      19. out_stream = p_ofmt_ctx->streams[m_vi_stream_out];
      20. pkt.pts = m_cur_pts_v + info->nFramePtsDiff;
      21. pkt.dts = pkt.pts;
      22. pkt.duration = info->nDurtion;
      23. m_cur_pts_v = pkt.pts;
      24. pkt.pts = av_rescale_q(pkt.pts, in_stream->time_base, out_stream->time_base);
      25. pkt.dts = av_rescale_q(pkt.dts, in_stream->time_base, out_stream->time_base);
      26. pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
      27. //m_frame_index_v++;
      28. }
      29. else
      30. {
      31. in_stream = p_ifmt_ctx->streams[m_ai_stream_in];
      32. out_stream = p_ofmt_ctx->streams[m_ai_stream_out];
      33. pkt.pts = m_cur_pts_a + info->nFramePtsDiff;
      34. pkt.dts = pkt.pts;
      35. pkt.duration = info->nDurtion;
      36. m_cur_pts_a = pkt.pts;
      37. pkt.pts = av_rescale_q(pkt.pts, in_stream->time_base, out_stream->time_base);
      38. pkt.dts = av_rescale_q(pkt.dts, in_stream->time_base, out_stream->time_base);
      39. pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
      40. //m_frame_index_a++;
      41. }
      42. if (av_interleaved_write_frame(p_ofmt_ctx, &pkt) == 0)
      43. {
      44. //if (info->nPacketType == 1001)
      45. // printf("write video packet Success. \n");
      46. //else
      47. // printf("write audio packet Success. \n");
      48. }
      49. av_packet_unref(&pkt);
      50. return 1;
      51. }

       

    3. 关闭文件

      1. int ff_file::close_file()
      2. {
      3. if (NULL == p_ofmt_ctx)
      4. {
      5. return 1;
      6. }
      7. av_write_trailer(p_ofmt_ctx);
      8. if (!(p_ofmt->flags & AVFMT_NOFILE))
      9. avio_close(p_ofmt_ctx->pb);
      10. avformat_free_context(p_ofmt_ctx);
      11. p_ofmt_ctx = NULL;
      12. return 1;
      13. }

       

其他:

  1.  关于 pts 和 dts 设置
    1.  关于pts 设置, 我是通过获取第一帧数据流的pts  - 数据流的start_time,然后在计算后面每一帧的相隔时间,由于没有b帧 ,所以 dts = pts. (音视频同理). 这样在写入文件中时,就能做到音视频同步了。
      1. void Qffmpeg::set_pts(AVPacket &pkt)
      2. {
      3. if (pkt.stream_index == m_audio_st_index)
      4. {
      5. if (is_first_frame_a)
      6. {
      7. pkt.pts = pkt.pts - p_ifmt_ctx->streams[m_audio_st_index]->start_time;
      8. pkt.dts = pkt.pts;
      9. m_frame_pts_diff_a = 0;
      10. m_last_pts_a = 0;
      11. is_first_frame_a = false;
      12. return;
      13. }
      14. m_frame_pts_diff_a = pkt.pts - m_last_pts_a;
      15. m_last_pts_a = pkt.pts;
      16. return;
      17. }
      18. if (is_first_frame_v)
      19. {
      20. pkt.pts = pkt.pts - p_ifmt_ctx->streams[m_video_st_index]->start_time;
      21. pkt.dts = pkt.pts;
      22. m_frame_pts_diff_v = 0;
      23. m_last_pts_v = 0;
      24. is_first_frame_v = false;
      25. return;
      26. }
      27. m_frame_pts_diff_v = pkt.pts - m_last_pts_v;
      28. m_last_pts_v = pkt.pts;
      29. return;
      30. }

       

  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, 否则重连不上。
      1. void Qffmpeg::release()
      2. {
      3. av_dict_free(&p_optionsDict);
      4. if (NULL != p_videoDeCodecCtx)
      5. {
      6. avcodec_free_context(&p_videoDeCodecCtx);
      7. p_videoDeCodecCtx = NULL;
      8. }
      9. if (NULL != p_videoEnCodecCtx)
      10. {
      11. avcodec_free_context(&p_videoEnCodecCtx);
      12. p_videoEnCodecCtx = NULL;
      13. }
      14. if (NULL != p_ifmt_ctx)
      15. {
      16. avformat_close_input(&p_ifmt_ctx);
      17. p_ifmt_ctx = NULL;
      18. }
      19. is_first_frame_a = 1;
      20. is_first_frame_v = 1;
      21. }

       

  7. 关于系统
    1. 测试过 win10 和 Ubuntu18.04 

DEMO:

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


END:

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

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

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

 

        

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/901684
推荐阅读
相关标签
  

闽ICP备14008679号