基于MFC和FFMPEG显示rtsp流。

1、ffmpeg下载地址:
x64版本:
https://github.com/BtbN/FFmpeg-Builds/releases
x86版本:
链接:https://pan.baidu.com/s/1URIN-d6akeG9K4sTJCeI7g
提取码:3z4o

x86版本是我基于msys2编译的,FFmpeg-n6.1.1版本。


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

#include "stdafx.h"
#include "PlayRtsp.h"
#include "PlayRtspDlg.h"
#include "afxdialogex.h"
#include <windows.h>
#include <mmsystem.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
#ifdef __cplusplus
}
#endif

#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "./win32/lib/ffmpeg/avcodec.lib")
#pragma comment(lib, "./win32/lib/ffmpeg/avdevice.lib")
#pragma comment(lib, "./win32/lib/ffmpeg/avfilter.lib")
#pragma comment(lib, "./win32/lib/ffmpeg/avformat.lib")
#pragma comment(lib, "./win32/lib/ffmpeg/avutil.lib")
#pragma comment(lib, "./win32/lib/ffmpeg/swscale.lib")
#pragma comment(lib, "./win32/lib/ffmpeg/swresample.lib")

// 用于应用程序“关于”菜单项的 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()


// CPlayRtspDlg 对话框



CPlayRtspDlg::CPlayRtspDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(IDD_PLAYRTSP_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

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

BEGIN_MESSAGE_MAP(CPlayRtspDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDOK, &CPlayRtspDlg::OnBnClickedOk)
END_MESSAGE_MAP()


// CPlayRtspDlg 消息处理程序

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

	// 将“关于...”菜单项添加到系统菜单中。

	// IDM_ABOUTBOX 必须在系统命令范围内。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		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);		// 设置小图标

	// TODO: 在此添加额外的初始化代码
	CWnd *wnd = AfxGetMainWnd();
	HWND hwnd = wnd->GetSafeHwnd();

	::SetWindowPos(hwnd, NULL, 0, 0, 1024, 768, SWP_NOMOVE | SWP_NOZORDER);
	m_showVideo = new thread(&CPlayRtspDlg::showVideo, hwnd);
	//capturePic(hwnd);

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

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

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

void CPlayRtspDlg::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 CPlayRtspDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}



void CPlayRtspDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码



}


void CPlayRtspDlg::showVideo(void* pParam)
{
	HWND hWnd = (HWND)pParam;

	AVFormatContext *pFormatCtx = NULL;
	AVCodecContext *pVideoCodecCtx = NULL;
	AVCodecContext *pAudioCodecCtx = NULL;
	const AVCodec *pVideoCodec = NULL;
	const AVCodec *pAudioCodec = NULL;
	AVDictionary *options = NULL;
	AVPacket *packet = NULL;
	AVFrame *pAVFrame = NULL;
	AVFrame *pFrameRGB = NULL;
	uint8_t *buffer;
	HWAVEOUT hWaveOut;
	WAVEFORMATEX waveFormat;
	char filepath[] = "rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid";
	const char *outputYUVFileName = "F:/vs2015/PlayRtsp/x64/Debug/getYUV.yuv";

	av_dict_set(&options, "buffer_size", "1024000", 0); //设置缓存大小,1080p可将值跳到最大
	av_dict_set(&options, "rtsp_transport", "tcp", 0); //以tcp的方式打开,
	av_dict_set(&options, "stimeout", "5000000", 0); //设置超时断开链接时间,单位us
	av_dict_set(&options, "max_delay", "500000", 0); //设置最大时延

	pFormatCtx = avformat_alloc_context(); //用来申请AVFormatContext类型变量并初始化默认参数,申请的空间

										   //打开网络流或文件流
	if (avformat_open_input(&pFormatCtx, filepath, NULL, &options) != 0)
	{
		printf("Couldn't open input stream.\n");
		return;
	}

	//获取视频文件信息
	if (avformat_find_stream_info(pFormatCtx, NULL)<0)
	{
		printf("Couldn't find stream information.\n");
		avformat_close_input(&pFormatCtx);
		return;
	}

	//查找码流中是否有视频流
	int videoindex = -1;
	int audioindex = -1;
	for (unsigned i = 0; i < pFormatCtx->nb_streams; i++) {
		if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			videoindex = i;
		}
		if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			audioindex = i;
		}
	}

	if (videoindex == -1)
	{
		printf("Didn't find a video stream.\n");
		//avformat_close_input(&pFormatCtx);
	}
	else
	{
		pVideoCodecCtx = avcodec_alloc_context3(NULL);
		avcodec_parameters_to_context(pVideoCodecCtx, pFormatCtx->streams[videoindex]->codecpar);

		pVideoCodec = avcodec_find_decoder(pVideoCodecCtx->codec_id);  //寻找解码器
		if (pVideoCodec == NULL)
		{
			printf("Codec not found.\n");
			avcodec_free_context(&pVideoCodecCtx);
			avformat_close_input(&pFormatCtx);
			return;
		}
		if (avcodec_open2(pVideoCodecCtx, pVideoCodec, NULL)<0)  //打开解码器
		{
			printf("Could not open codec.\n");
			avcodec_free_context(&pVideoCodecCtx);
			avformat_close_input(&pFormatCtx);
			return;
		}

		
		pFrameRGB = av_frame_alloc(); // 申请空间,存放每一帧编码后的YUV数据

		 设置图像转换上下文
		//struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
		//	pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGRA,
		//	SWS_BILINEAR, NULL, NULL, NULL);

		// 分配输出RGB图像缓冲区
		int numBytes = av_image_get_buffer_size(AV_PIX_FMT_BGRA, pVideoCodecCtx->width, pVideoCodecCtx->height, 1);
		buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
	}

	//加载音频解码器
	if (audioindex == -1)
	{
		printf("Didn't find an audio stream.\n");
	}
	else
	{
		pAudioCodecCtx = avcodec_alloc_context3(NULL);
		avcodec_parameters_to_context(pAudioCodecCtx, pFormatCtx->streams[audioindex]->codecpar);

		//寻找音频解码器
		pAudioCodec = avcodec_find_decoder(pAudioCodecCtx->codec_id);
		if (pAudioCodec == NULL) {
			printf("Audio Codec not found.\n");
			avcodec_free_context(&pAudioCodecCtx);
			avformat_close_input(&pFormatCtx);
			return;
		}

		//打开音频解码器
		if (avcodec_open2(pAudioCodecCtx, pAudioCodec, NULL) < 0) {
			printf("Could not open audio codec.\n");
			avcodec_free_context(&pAudioCodecCtx);
			avformat_close_input(&pFormatCtx);
			return;
		}

		//获取音频信息
		waveFormat.wFormatTag = WAVE_FORMAT_PCM;
		waveFormat.nChannels = pAudioCodecCtx->channels;
		waveFormat.nSamplesPerSec = pAudioCodecCtx->sample_rate;
		waveFormat.wBitsPerSample = 16;
		waveFormat.nBlockAlign = (waveFormat.nChannels * waveFormat.wBitsPerSample) / 8;
		waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;

		//打开音频设备
		if (waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL) != MMSYSERR_NOERROR)
		{
			printf("Error opening audio device.\n");
			avcodec_free_context(&pAudioCodecCtx);
			avformat_close_input(&pFormatCtx);
			return ;
		}

	}

	packet = av_packet_alloc(); // 申请空间,存放的每一帧数据 (h264、h265)
	pAVFrame = av_frame_alloc();
	uint8_t *audioBuffer = (uint8_t *)malloc(44100 * 2 * 5);

	//上屏函数初始化
	HDC hdc = ::GetDC(hWnd);
	CDC dc;
	dc.Attach(hdc);

	//这边可以调整i的大小来改变文件中的视频时间,每 +1 就是一帧数据
	while(av_read_frame(pFormatCtx, packet) >= 0) {
		if (packet->stream_index == videoindex) {
			avcodec_send_packet(pVideoCodecCtx, packet);
			int ret = avcodec_receive_frame(pVideoCodecCtx, pAVFrame);
			if (ret < 0) {
				printf("Error decoding frame\n");
				continue;
			}

			RECT clientRect;
			::GetClientRect(hWnd, &clientRect);
			int width = (clientRect.right - clientRect.left) / 4 * 4;
			int height = (clientRect.bottom - clientRect.top) / 4 * 4;
			//int width = 1920;
			//int height = 1080;
			if (width == 0 || height == 0)
				continue;

			av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer, AV_PIX_FMT_BGRA, width, pVideoCodecCtx->height, 1);


			// 设置图像转换上下文
			struct SwsContext *sws_ctx = sws_getContext(pVideoCodecCtx->width, pVideoCodecCtx->height, pVideoCodecCtx->pix_fmt,
				width, height, AV_PIX_FMT_BGRA,
				SWS_LANCZOS, NULL, NULL, NULL);

			ret = sws_scale(sws_ctx, (const uint8_t * const*)pAVFrame->data, pAVFrame->linesize,
				0, pVideoCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
			if (ret < 0) {
				printf("Error in sws_scale\n");
				return;
			}
			
			// 创建位图
			CBitmap bitmap;
			bitmap.CreateBitmap(width, height, 1, 32, pFrameRGB->data[0]);

			// 创建内存DC
			CDC memDC;
			memDC.CreateCompatibleDC(&dc);
			CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);

			// 将位图绘制到窗口DC
			dc.BitBlt(0, 0, width, height, &memDC, 0, 0, SRCCOPY);

			// 释放资源
			memDC.SelectObject(pOldBitmap);
			bitmap.DeleteObject();
			sws_freeContext(sws_ctx);

		}
		else if(audioindex == packet->stream_index)
		{
			avcodec_send_packet(pAudioCodecCtx, packet);
			if (avcodec_receive_frame(pAudioCodecCtx, pAVFrame) == 0)
			{
				//将音频数据写入音频设备
				SwrContext *swrCtx = swr_alloc();
				av_opt_set_int(swrCtx, "in_channel_layout", pAVFrame->channel_layout, 0);
				av_opt_set_int(swrCtx, "in_sample_rate", pAVFrame->sample_rate, 0);
				av_opt_set_sample_fmt(swrCtx, "in_sample_fmt", (AVSampleFormat)pAVFrame->format, 0);

				av_opt_set_int(swrCtx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
				av_opt_set_int(swrCtx, "out_sample_rate", 44100, 0);
				av_opt_set_sample_fmt(swrCtx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);

				swr_init(swrCtx);

				int convertedSamples = swr_convert(swrCtx, &audioBuffer, AUDIO_BUFFER_SIZE,
					(const uint8_t**)pAVFrame->data, pAVFrame->nb_samples);

				WAVEHDR waveHdr;
				memset(&waveHdr, 0, sizeof(WAVEHDR));
				waveHdr.lpData = reinterpret_cast<LPSTR>(audioBuffer);
				waveHdr.dwBufferLength = convertedSamples * waveFormat.nBlockAlign;
				waveOutPrepareHeader(hWaveOut, &waveHdr, sizeof(WAVEHDR));
				if (waveOutWrite(hWaveOut, &waveHdr, sizeof(WAVEHDR)) != MMSYSERR_NOERROR)
				{
					printf("Failed to write audio to device");
				}
				swr_free(&swrCtx);
			}
		}
		av_packet_unref(packet);
	}

	free(audioBuffer);
	// 等待播放完成
	waveOutReset(hWaveOut);
	waveOutClose(hWaveOut);
	// 释放资源
	av_frame_unref(pFrameRGB);
	avformat_close_input(&pFormatCtx);
	avcodec_free_context(&pVideoCodecCtx);
	av_frame_free(&pFrameRGB);
	av_packet_free(&packet);


}

#define FFMPEG_CMD "F:\\vs2015\\PlayRtsp\\Debug\\ffmpeg.exe"

void CPlayRtspDlg::capturePic(void* pParam)
{
	string strRtsp = "rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid";
	char cmd[1024] = { 0 };
	sprintf(cmd, "/c %s -y -i \"%s\" -frames:v 1 -q:v 2 \"%s\"", FFMPEG_CMD, strRtsp.c_str()/*threadParam[fileInfo->nWndID].url.c_str()*/, "F:\\vs2015\\PlayRtsp\\Debug\\a.jpg");
	//sprintf(cmd, "/K echo hello");
	CString cstr_cmd(cmd);

	SHELLEXECUTEINFO ShExecInfo = { 0 };
	ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
	ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
	ShExecInfo.hwnd = NULL;
	ShExecInfo.lpVerb = _T("open");
	ShExecInfo.lpFile = _T("C:\\Windows\\System32\\cmd.exe");
	ShExecInfo.lpParameters = cstr_cmd;
	ShExecInfo.lpDirectory = NULL;
	ShExecInfo.nShow = SW_SHOW;
	ShExecInfo.hInstApp = NULL;

	if (ShellExecuteEx(&ShExecInfo))
	{
		//WaitForSingleObject(ShExecInfo.hProcess, INFINITE);
		//CloseHandle(ShExecInfo.hProcess);
	}
}

音频处理的比较少,不知道为什么和vlc播放效果相差很远😂。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值