近些年,每一年的风口,几乎都离不开音视频。音视频依然很火,只不过是火的领域有所不同,从年初受疫情而爆发的视频会议,到后来为了大力发展网络经济的直播带货!不过呢,本篇我们暂时不讨论这个,咱们聊技术,说到直播,rtmp协议依然是主流!不过呢,音视频中,流媒体协议rtsp也占有很大份额!日常生活呢,我们也会遇到一些不同流媒体协议转换的问题!本篇记录实现一个rtsp转rtmp直播流的程序!闲言少叙,接下来置入主题。

需求

有一个摄像头,比如海康的监控摄像头,可以通过rtsp流的方式访问其视频画面!需要将其画面转换为rtmp协议,并实现直播!

实现思路

我们的程序,称之为rtsp2rtmp,使用该程序实现拉取摄像头rtsp视频流,并将rtmp视频流转换为rtmp视频流,然后推送到直播服务器,直播服务器采用nginx+rtmp_module的方式实现,环境搭建的步骤可以参考之前的文章:搭一个简单的直播平台,嗨起来。

rtsp2rtmp,使用FFmpeg API来实现!

【相关学习资料推荐,点击下方链接免费报名,先码住不迷路~】

【免费分享】我整理了一些比较好的面试题、学习资料、教学视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击加群领取~

准备工作

  • visual studio(笔者使用vs2019)
  • FFmpeg sdk: 下载下载地址:
https://ffmpeg.zeranoe.com/builds/

具体实现步骤

  • 打开输入流上下文
  //av_register_all();
  avformat_network_init();


  m_strRtspUrl = rtspUrl;
  m_strRtmpUrl = rtmpUrl;

  m_pRtspAVFormatContext = NULL;
  m_nRet = 0;

  m_nVideoIndex = -1;
  m_nAudioIndex = -1;
  
  memset(errBuf, 0, 1024);

  // 1. 申请输出流上下文
  m_pRtmpAVFormatContext = NULL;
  avformat_alloc_output_context2(&m_pRtmpAVFormatContext, NULL, "flv", m_strRtmpUrl.c_str());
  if (!m_pRtmpAVFormatContext)
  {
    std::cout << "avformat_alloc_output_contex2 failed!" << std::endl;
    return false;
  }
  std::cout << "[" << m_strRtmpUrl.c_str() << "] avformat_alloc_output_context2 success " << std::endl;


  //1.打开输入流
  m_nRet = avformat_open_input(&m_pRtspAVFormatContext, m_strRtspUrl.c_str(), 0, 0);
  if (m_nRet != 0)
  {
    std::cout << "open " << m_strRtspUrl.c_str() << "error!" << std::endl;
    return m_nRet;
  }
  std::cout << "open " << m_strRtspUrl.c_str() << "success !" << std::endl;

  // 2.获得流信息
  m_nRet = avformat_find_stream_info(m_pRtspAVFormatContext, 0);
  if (m_nRet < 0)
  {
    std::cout << "avformat_find_stream_info failed" << std::endl;
    return m_nRet;
  }

  std::cout << "avformat_find_stream_info success" << std::endl;
  av_dump_format(m_pRtspAVFormatContext, 0, m_strRtspUrl.c_str(),0);



  for (int i = 0; i < m_pRtspAVFormatContext->nb_streams; i++)
  {
    if (m_pRtspAVFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) 
    {
      m_nVideoIndex = i;
    }

    if (m_pRtspAVFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) 
    { 
      m_nAudioIndex = i;
    }
  }

打开输出流上下文

  //3. 申请输出AVStream(rtmp)
  const AVCodec* outCodec = avcodec_find_decoder(m_pRtspAVFormatContext->streams[m_nVideoIndex]->codecpar->codec_id);
  m_pRtmpAVStream = avformat_new_stream(m_pRtmpAVFormatContext, outCodec);
  if (!m_pRtmpAVStream)
  {
    std::cout << "[rtmp] Failed allocating output stream!" << std::endl;
    return m_nRet;
  }

  std::cout << "[rtmp] avformat_new_stream success!" << std::endl;

  //4. stream信息复制
  AVCodecContext* rtmpOutCodec = avcodec_alloc_context3(outCodec);

  m_nRet = avcodec_parameters_to_context(rtmpOutCodec, m_pRtspAVFormatContext->streams[m_nVideoIndex]->codecpar);
  if (m_nRet < 0)
  {
    std::cout << "[rtmp] avcodec_paramertes_to_context failed " << std::endl;
    return m_nRet;
  }
  std::cout << "[rtmp] avcodec_paramertes_to_context success" << std::endl;

  m_nRet = avcodec_parameters_from_context(m_pRtmpAVStream->codecpar, rtmpOutCodec);
  if (m_nRet < 0)
  {
    std::cout << "[rtmp] avcodec_parameters_from_context failed";
    return m_nRet;
  }


  if (!(m_pRtmpAVFormatContext->flags & AVFMT_NOFILE)) {
    m_nRet = avio_open2(&m_pRtmpAVFormatContext->pb, m_strRtmpUrl.c_str(), AVIO_FLAG_WRITE, NULL, NULL);
    if (m_nRet < 0) {
      av_strerror(m_nRet, errBuf, 1024);
      //std::cout << "avio_open2 failed: " << errBuf << std::endl;
      printf("avio_open2 failed: %s\n", errBuf);
      return m_nRet;
    }
  }


  //5. 写rtmp头信息
  m_nRet = avformat_write_header(m_pRtmpAVFormatContext, NULL);
  if (m_nRet < 0)
  {
    std::cout << "[rtmp] avformat_write_header failed" << std::endl;

    av_strerror(m_nRet, errBuf, 1024);
    printf("errBuf: %s\n", errBuf);
    //std::cout << err << std::endl;
    return m_nRet;
  }

  std::cout << "[rtmp] avformat_write_header success!" << std::endl;

读取输入流

    m_nRet = av_read_frame(m_pRtspAVFormatContext, &pkt);
    if (m_nRet < 0)
    {
      break;
    }

写入输出流

   rtspStream = m_pRtspAVFormatContext->streams[pkt.stream_index];
    rtmpStream = m_pRtmpAVFormatContext->streams[pkt.stream_index];

    pkt.pts = av_rescale_q_rnd(pkt.pts, rtspStream->time_base, rtmpStream->time_base, AVRounding(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
    pkt.dts = av_rescale_q_rnd(pkt.dts, rtspStream->time_base, rtmpStream->time_base, AVRounding(AV_ROUND_NEAR_INF |AV_ROUND_PASS_MINMAX));
    pkt.duration = av_rescale_q(pkt.duration, rtspStream->time_base, rtmpStream->time_base);

    pkt.pos = -1;
    m_nRet = av_interleaved_write_frame(m_pRtmpAVFormatContext, &pkt);
    if (m_nRet < 0)
    {
      break;
    }
    av_packet_unref(&pkt);

github传送门

https://github.com/mlfcjob/Rtsp2Rtmp.git

欢迎star,欢迎使用修改和提交!