使用ffmpeg播放rtsp视频流

获取IPC摄像机视频流一般使用GB28181或者RTSP协议,这两款协议是比较常见的;两者都有开源的库,下面介绍如何使用RTSP获取进行IPC视频流;

  • 准备库

ffmepg是个开源的库,该库集成了rtsp协议,可以直接使用;首先需要编译该库,看使用的编译的平台或者编译工具,编译对应的库即可,如何编译,网上有很多方法,可以参考下,我们代码里面也会提供VS2017编译的库,可以直接使用;

window播放使用SDL库,怎么编译使用网上可以参考下;我们代码里面也会提供VS2017编译的库,可以直接使用;

  • 新建工程

这里使用的是VS2017进行编译;

(1)、新建工程,选择MFC应用程序;

(2)、选择基于对话框

(3)、配置编译环境

  • 代码实施

    // rtspplayDlg.cpp: 实现文件
    //

    #include "stdafx.h"
    #include "rtspplay.h"
    #include "rtspplayDlg.h"
    #include "afxdialogex.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif

    // 用于应用程序"关于"菜单项的 CAboutDlg 对话框

    class CAboutDlg : public CDialogEx
    {
    public:
    CAboutDlg();

    // 对话框数据
    #ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_ABOUTBOX };
    #endif

      protected:
      virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持
    

    // 实现
    protected:
    DECLARE_MESSAGE_MAP()
    };

    CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
    {
    }

    void CAboutDlg::DoDataExchange(CDataExchange* pDX)
    {
    CDialogEx::DoDataExchange(pDX);
    }

    BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
    END_MESSAGE_MAP()

    // CrtspplayDlg 对话框

    CrtspplayDlg::CrtspplayDlg(CWnd* pParent /=nullptr/)
    : CDialogEx(IDD_RTSPPLAY_DIALOG, pParent)
    {
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    }

    void CrtspplayDlg::DoDataExchange(CDataExchange* pDX)
    {
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_STATIC_VIDEO, m_staVideo);
    }

    BEGIN_MESSAGE_MAP(CrtspplayDlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDOK, &CrtspplayDlg::OnBnClickedOk)
    ON_BN_CLICKED(IDCANCEL, &CrtspplayDlg::OnBnClickedCancel)
    ON_BN_CLICKED(IDC_BUTTON_STOPPLAY, &CrtspplayDlg::OnBnClickedButtonStopplay)
    END_MESSAGE_MAP()

    // CrtspplayDlg 消息处理程序

    BOOL CrtspplayDlg::OnInitDialog()
    {
    CDialogEx::OnInitDialog();

      // 将"关于..."菜单项添加到系统菜单中。
    
      // IDM_ABOUTBOX 必须在系统命令范围内。
      ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
      ASSERT(IDM_ABOUTBOX < 0xF000);
    
      CMenu* pSysMenu = GetSystemMenu(FALSE);
      if (pSysMenu != nullptr)
      {
      	BOOL bNameValid;
      	CString strAboutMenu;
      	bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
      	ASSERT(bNameValid);
      	if (!strAboutMenu.IsEmpty())
      	{
      		pSysMenu->AppendMenu(MF_SEPARATOR);
      		pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
      	}
      }
    
      // 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
      //  执行此操作
      SetIcon(m_hIcon, TRUE);			// 设置大图标
      SetIcon(m_hIcon, FALSE);		// 设置小图标
    
      WSADATA Ws;
      if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0) {
      	return FALSE;
      }
    
      static BOOL sbInitFfmpeg = FALSE;
      if (FALSE == sbInitFfmpeg) {
      	avformat_network_init();
      	sbInitFfmpeg = TRUE;
      }
    
      return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
    

    }

    void CrtspplayDlg::OnSysCommand(UINT nID, LPARAM lParam)
    {
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
    CAboutDlg dlgAbout;
    dlgAbout.DoModal();
    }
    else
    {
    CDialogEx::OnSysCommand(nID, lParam);
    }
    }

    // 如果向对话框添加最小化按钮,则需要下面的代码
    // 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
    // 这将由框架自动完成。

    void CrtspplayDlg::OnPaint()
    {
    if (IsIconic())
    {
    CPaintDC dc(this); // 用于绘制的设备上下文

      	SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
    
      	// 使图标在工作区矩形中居中
      	int cxIcon = GetSystemMetrics(SM_CXICON);
      	int cyIcon = GetSystemMetrics(SM_CYICON);
      	CRect rect;
      	GetClientRect(&rect);
      	int x = (rect.Width() - cxIcon + 1) / 2;
      	int y = (rect.Height() - cyIcon + 1) / 2;
    
      	// 绘制图标
      	dc.DrawIcon(x, y, m_hIcon);
      }
      else
      {
      	CDialogEx::OnPaint();
      }
    

    }

    //当用户拖动最小化窗口时系统调用此函数取得光标
    //显示。
    HCURSOR CrtspplayDlg::OnQueryDragIcon()
    {
    return static_cast<HCURSOR>(m_hIcon);
    }

    void CrtspplayDlg::OnBnClickedOk()
    {
    if (mbRunning == FALSE) {
    mbRunning = TRUE;
    m_pVideoPlayWorker = AfxBeginThread((AFX_THREADPROC)VideoPlayThread, (LPVOID)this);
    ((CButton*)GetDlgItem(IDOK))->EnableWindow(FALSE);
    }
    else {
    AfxMessageBox(L"正在播放");
    }

      //CDialogEx::OnOK();
    

    }

    void CrtspplayDlg::OnBnClickedCancel()
    {
    if (mbRunning == TRUE) {
    AfxMessageBox(L"请先停止播放");
    return;
    }
    CDialogEx::OnCancel();
    }

    DWORD __cdecl CrtspplayDlg::VideoPlayThread(LPVOID pParam)
    {
    CrtspplayDlg pd = (CrtspplayDlg)pParam;
    pd->RtspPlay();
    return 0;
    }

    // Rtsp播放线程
    void CrtspplayDlg::RtspPlay()
    {
    int nTimeoutCnt = 0;
    CStringW strUrl;
    GetDlgItem(IDC_EDIT_RTSPADDR)->GetWindowText(strUrl);
    ;
    //char filepath[] = "rtsp://xmygkj:7290@192.168.199.45/streaming?transportmode=unicast&profile=Profile_13";
    //char filepath[] = "rtsp://xmygkj:7290@192.168.199.61/streaming?transportmode=unicast&profile=Profile_17";
    //初始化
    m_pFormatCtx = avformat_alloc_context();
    AVDictionary* options = NULL;
    //av_dict_set(&options, "buffer_size", "1024000", 0); //设置缓存大小,1080p可将值跳到最大
    av_dict_set(&options, "rtsp_transport", "udp", 0); //以udp的方式打开,
    av_dict_set(&options, "stimeout", "5000000", 0); //设置超时断开链接时间,单位us
    av_dict_set(&options, "max_delay", "500000", 0); //设置最大时延
    m_pPacket = (AVPacket *)av_malloc(sizeof(AVPacket));

      USES_CONVERSION;
      //打开网络流或文件流
      if (avformat_open_input(&m_pFormatCtx, W2A(strUrl), NULL, &options) != 0) {
      	AfxMessageBox(L"打开网络流失败\n");
      	return;
      }
    
      //查找码流信息
      if (avformat_find_stream_info(m_pFormatCtx, NULL) < 0) {
      	AfxMessageBox(L"查找码流信息失败\n");
      	return;
      }
    
      //查找码流中是否有视频流
      int videoindex = -1;
      for (unsigned i = 0; i < m_pFormatCtx->nb_streams; i++) {
      	if (m_pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
      		videoindex = i;
      		break;
      	}
      }
      if (videoindex == -1) {
      	AfxMessageBox(L"找不到码流信息\n");
      	return;
      }
    
      if (FALSE == InitFfmpeg()) {
      	return;
      }
    
      //保存一段的时间视频,写到文件中
      while (mbRunning) {
      	if (av_read_frame(m_pFormatCtx, m_pPacket) >= 0) {
      		nTimeoutCnt = 0;
      		int ret = 0;
      		ret = avcodec_send_packet(m_pCodecCtx, m_pPacket);
      		ret = avcodec_receive_frame(m_pCodecCtx, m_pFrame);
      		if ((ret == 0) && (m_pCodecCtx->width > 0) && (m_pCodecCtx->height > 0)) {
      			if (m_img_convert_ctx == NULL) {
      				m_img_convert_ctx = sws_getContext(m_pCodecCtx->width, m_pCodecCtx->height, m_pCodecCtx->pix_fmt,
      					m_stSdlRect.w, m_stSdlRect.h, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
      				m_nWidth = m_pCodecCtx->width;
      				m_nHeight = m_pCodecCtx->height;
      			}
      			else {
      				if ((m_pCodecCtx->width != m_nWidth) || (m_pCodecCtx->height != m_nHeight)) {
      					if (m_img_convert_ctx != NULL) {
      						sws_freeContext(m_img_convert_ctx);
      						m_img_convert_ctx = sws_getContext(m_pCodecCtx->width, m_pCodecCtx->height, m_pCodecCtx->pix_fmt,
      							m_stSdlRect.w, m_stSdlRect.h, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
      						m_nWidth = m_pCodecCtx->width;
      						m_nHeight = m_pCodecCtx->height;
      					}
      				}
      			}
      			sws_scale(m_img_convert_ctx, (const uint8_t* const*)m_pFrame->data, m_pFrame->linesize, \
      				0, m_pCodecCtx->height, m_pFrameYUV->data, m_pFrameYUV->linesize);
      			SDL_UpdateYUVTexture(m_pSdlTexture, &m_stSdlRect,
      				m_pFrameYUV->data[0], m_pFrameYUV->linesize[0],
      				m_pFrameYUV->data[1], m_pFrameYUV->linesize[1],
      				m_pFrameYUV->data[2], m_pFrameYUV->linesize[2]);
      			SDL_RenderClear(m_pSdlRenderer);
      			//SDL_RenderCopy( sdlRenderer, sdlTexture, &m_stSdlRect, &m_stSdlRect );  
      			SDL_RenderCopy(m_pSdlRenderer, m_pSdlTexture, NULL, &m_stSdlRect);
      			SDL_Rect sRect;
      			memcpy(&sRect, &m_stSdlRect, sizeof(SDL_Rect));
      			SDL_SetRenderDrawColor(m_pSdlRenderer, 0x00, 0x00, 0x00, 0x00);
      			SDL_RenderPresent(m_pSdlRenderer);
      		}
      		av_packet_unref(m_pPacket);
      	}
      	else {
      		Sleep(500);
      		nTimeoutCnt++;
      		if (nTimeoutCnt > 5) {
      			AfxMessageBox(L"超时断开");
      		}
      		mbRunning = FALSE;
      	}
      }
      SDL_RenderClear(m_pSdlRenderer);
      SDL_SetRenderDrawColor(m_pSdlRenderer, 0x00, 0x00, 0x00, 0x00);
      SDL_RenderPresent(m_pSdlRenderer);
    
      ReleaseFfmpeg();
      mbRunning = FALSE;
    

    }

    BOOL CrtspplayDlg::InitFfmpeg()
    {
    m_pCodec = (AVCodec*)avcodec_find_decoder(AV_CODEC_ID_H264);
    if (m_pCodec == NULL) {
    AfxMessageBox(L"找不到编码器");
    return FALSE;
    }
    m_pCodecCtx = avcodec_alloc_context3(m_pCodec);
    m_pCodecCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
    if (avcodec_open2(m_pCodecCtx, m_pCodec, nullptr) < 0) {
    AfxMessageBox(L"打开编码器失败");
    return FALSE;
    }
    m_pFrame = av_frame_alloc();
    m_pParser = av_parser_init(AV_CODEC_ID_H264);

      CRect staRect;
      m_staVideo.GetWindowRect(&staRect);
      m_stSdlRect.w = staRect.right - staRect.left;
      m_stSdlRect.h = staRect.bottom - staRect.top;
    
      m_pFrameYUV = av_frame_alloc();
      m_pOutBuffer = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, m_stSdlRect.w, m_stSdlRect.h, 1));
      av_image_fill_arrays(m_pFrameYUV->data, m_pFrameYUV->linesize, m_pOutBuffer, AV_PIX_FMT_YUV420P, m_stSdlRect.w, m_stSdlRect.h, 1);
    
      SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
      //显示在MFC控件上
      m_pScreen = SDL_CreateWindowFrom(m_staVideo.GetSafeHwnd());
      if (!m_pScreen) {
      	AfxMessageBox(L"创建窗体失败");
      	return FALSE;
      }
      m_pSdlRenderer = SDL_CreateRenderer(m_pScreen, -1, 0);
      if (!m_pSdlRenderer) {
      	AfxMessageBox(L"无法创建窗体");
      	return FALSE;
      }
      //IYUV: Y + U + V  (3 planes)
      //YV12: Y + V + U  (3 planes)
      m_pSdlTexture = SDL_CreateTexture(m_pSdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, m_stSdlRect.w, m_stSdlRect.h);
      if (!m_pSdlTexture) {
      	AfxMessageBox(L"创建纹理失败");
      	return FALSE;
      }
    
      return TRUE;
    

    }

    void CrtspplayDlg::ReleaseFfmpeg()
    {
    //FIX Small Bug
    //SDL Hide Window When it finished
    if (m_pFormatCtx != NULL) {
    avformat_close_input(&m_pFormatCtx);
    av_free(m_pFormatCtx);
    m_pFormatCtx = NULL;
    }

      if (NULL == m_pPacket) {
      	av_free(m_pPacket);
      	m_pPacket = NULL;
      }
    
      if (m_pSdlRenderer != NULL) {
      	SDL_DestroyRenderer(m_pSdlRenderer);
      	m_pSdlRenderer = NULL;
      }
      if (m_pSdlTexture != NULL) {
      	SDL_DestroyTexture(m_pSdlTexture);
      	m_pSdlTexture = NULL;
      }
    
      if (m_pScreen != NULL) {
      	SDL_DestroyWindow(m_pScreen);
      	m_pScreen = NULL;
      }
    
      if (m_img_convert_ctx != NULL) {
      	sws_freeContext(m_img_convert_ctx);
      	m_img_convert_ctx = NULL;
      }
    
      if (NULL != m_pFrameYUV) {
      	av_frame_free(&m_pFrameYUV);
      	m_pFrameYUV = NULL;
      }
    
      if (NULL != m_pCodecCtx) {
      	avcodec_close(m_pCodecCtx);
      	avcodec_free_context(&m_pCodecCtx);
      	m_pCodecCtx = NULL;
      }
    
      if (m_pOutBuffer != NULL) {
      	av_free(m_pOutBuffer);
      	m_pOutBuffer = NULL;
      }
    

    }

    void CrtspplayDlg::OnBnClickedButtonStopplay()
    {
    mbRunning = FALSE;
    ((CButton*)GetDlgItem(IDOK))->EnableWindow(TRUE);
    }

  • 成果

相关推荐
oushaojun24 小时前
ubuntu中使用ffmpeg和nginx推流rtmp视频
nginx·ubuntu·ffmpeg·rtmp
莫固执,朋友5 小时前
网络抓包工具tcpdump 在海思平台上的编译使用
网络·ffmpeg·音视频·tcpdump
lxkj_20245 小时前
修改ffmpeg实现https-flv内容加密
网络协议·https·ffmpeg
cuijiecheng20185 小时前
音视频入门基础:MPEG2-TS专题(6)——FFmpeg源码中,获取MPEG2-TS传输流每个transport packet长度的实现
ffmpeg·音视频
VisionX Lab10 小时前
数据脱敏工具:基于 FFmpeg 的视频批量裁剪
python·ffmpeg·音视频
柳鲲鹏1 天前
全网首发:Ubuntu编译跨平台嵌入式支持ffmpeg的OpenCV
linux·ubuntu·ffmpeg
冰山一脚20131 天前
ffplay音频SDL播放处理
ffmpeg
cuijiecheng20181 天前
音视频入门基础:MPEG2-TS专题(7)——FFmpeg源码中,读取出一个transport packet数据的实现
ffmpeg·音视频
Maxwellhang2 天前
【java-ffmpeg】java 内存测试和集成
java·ffmpeg·jni
qq762118222 天前
Ubuntu20.04 Rk3588 交叉编译ffmpeg7.0
ffmpeg·rk3588