获取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);
}
- 成果