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播放效果相差很远😂。