FFmpeg+SDL---The production of video player-graphic interface version

Chapter VI FFmpeg+SDL Video Player Production-Graphical Interface Version

It is recommended to read before reading this chapter: FFmpeg+SDL-----Syllabus

table of Contents

• MFC knowledge
• FFmpeg + SDL video player knowledge review
• FFmpeg+SDL+MFC to achieve a graphical interface video player
• Practice

MFC knowledge

1. How to create an MFC project
▫ Open VC++
▫ File -> New -> Project -> MFC Application
▫ Application Type -> Dialog-based
▫ Uncheck "Use Unicode Library" (not detailed yet)
2. Set Controls
▫ Find the "Toolbox" and drag the corresponding control to the application dialog box.
▫ Commonly used controls include: Button, Edit Control, Static Text, etc.
▫ Find the "Properties" tab: (1) You can click on the "Caption Modify the text on the control on the property. (2) You can modify the ID on the control on the "ID" property (ID is the identifier of the control and cannot be repeated)
3. Add a message response function
▫ Double-click the Button control to add a message response function to the control.
▫ At "Project->Class Wizard" in the menu bar, you can add more types of message response functions.
▫ The simplest MFC pop-up message box function is AfxMessageBox("HelloWorld")
▫ The dialog box attached to the main dialog box pops up

void CSFFPlayerDlg::OnBnClickedAbout()
{
	CAboutDlg dlg;
	dlg.DoModal();		//DoModal使用这个函数弹出的对话框必须关闭之后才能点击其他按钮。
}

4. Delete button The
delete button cannot only delete the button on the interface, it will not compile, there are two methods: (1) Delete all the code related to this button in the code (2) Menu bar item -> Class Wizard, Select the corresponding class and button ID in it, click on the right to delete in batches, and the buttons in the code will be commented out.
5. Get the characters entered into the input box:
menu bar item -> class wizard, so a member m_url will be added to the code, and the entered value can be obtained by accessing this variable.
Insert picture description here

/* 获取输入的字符串并打印出来 */
void CSFFPlayerDlg::OnBnClickedFiledialog()
{
	CString str1;
	m_url.GetWindowText(str1);

	AfxMessageBox(str1);
}

6. Simply call the function in ffmpeg and print it out to verify that the environment is feasible

CString str1;
str1.Format("%s", avcodec_configuration());			//类似于C中的sprintf函数
AfxMessageBox(str1);

7. You can add an extra picture, use the drawing software to save a picture in bmp format to the res directory
(1) Create a new picture control to the panel, select the attribute type as Bitmap
(2) Resource view -> Right click -> Add resource ->Import (must be a picture in bmp format). After importing, you can find this picture in the resource view, and then set its id.
(3) Select the attribute image to the id of the corresponding image
8. Add a menu bar similar to windows
(1) Resource view -> right click -> add resource -> menu -> new: then build the menu we want
(2) Associate the menu with the main interface: click on the main interface -> properties -> miscellaneous items -> menu
(3) Add a corresponding function to the menu bar: click on an item in the menu, and change its id to any id in the main interface , It has the same function as the corresponding button.

4. Other steps
▫ are no longer listed. Demonstrate through actual operation.
5. Practice
▫ Make the interface of the video player (not including functions)

Encountered problems and solutions:
Problem 1: LINK: fatal error LNK1123: Failed during conversion to COFF: Invalid or damaged file
Solution: LINK: fatal error LNK1158: Unable to run "E:\VS2010\VC\bin\cvtres.exe "(My VS is installed on the E drive) There is no need to replace it at all, just delete E:\VS2010\VC\bin\cvtres.exe, which can solve the problem

2. You cannot play directly in the button function, you need to create a thread to play.

Source code

#include "stdafx.h"
#include "SFFPlayer.h"
#include "SFFPlayerDlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define __STDC_CONSTANT_MACROS

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "SDL2/SDL.h"
};



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

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

// 对话框数据
	enum { IDD = IDD_ABOUTBOX };

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

// 实现
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{
}

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

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CSFFPlayerDlg 对话框




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

void CSFFPlayerDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_URL, m_url);
}

BEGIN_MESSAGE_MAP(CSFFPlayerDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(ID_START, &CSFFPlayerDlg::OnBnClickedStart)
	ON_BN_CLICKED(ID_PAUSE, &CSFFPlayerDlg::OnBnClickedPause)
//	ON_BN_CLICKED(ID_CONTINUE, &CSFFPlayerDlg::OnBnClickedContinue)
	ON_BN_CLICKED(ID_STOP, &CSFFPlayerDlg::OnBnClickedStop)
	ON_BN_CLICKED(ID_ABOUT, &CSFFPlayerDlg::OnBnClickedAbout)
	ON_BN_CLICKED(IDCANCEL, &CSFFPlayerDlg::OnBnClickedCancel)
	ON_BN_CLICKED(IDC_FILEBROWSER, &CSFFPlayerDlg::OnBnClickedFilebrowser)
END_MESSAGE_MAP()


// CSFFPlayerDlg 消息处理程序

BOOL CSFFPlayerDlg::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: 在此添加额外的初始化代码

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

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

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

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


//Refresh Event
#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)

#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)

int thread_exit=0;
int thread_pause=0;

int sfp_refresh_thread(void *opaque){

	thread_exit=0;
	thread_pause=0;

	while (thread_exit==0) {
		if(!thread_pause){
			SDL_Event event;
			event.type = SFM_REFRESH_EVENT;
			SDL_PushEvent(&event);
		}
		SDL_Delay(40);
	}
	//Quit
	SDL_Event event;
	event.type = SFM_BREAK_EVENT;
	SDL_PushEvent(&event);
	thread_exit=0;
	thread_pause=0;
	return 0;
}


int simplest_ffmpeg_player(LPVOID lpParam)
{

	AVFormatContext	*pFormatCtx;
	int				i, videoindex;
	AVCodecContext	*pCodecCtx;
	AVCodec			*pCodec;
	AVFrame	*pFrame,*pFrameYUV;
	uint8_t *out_buffer;
	AVPacket *packet;
	int ret, got_picture;

	//------------SDL----------------
	int screen_w,screen_h;
	SDL_Window *screen; 
	SDL_Renderer* sdlRenderer;
	SDL_Texture* sdlTexture;
	SDL_Rect sdlRect;
	SDL_Thread *video_tid;
	SDL_Event event;

	struct SwsContext *img_convert_ctx;
	//===========================================
	//文件路径
	CSFFPlayerDlg *dlg=(CSFFPlayerDlg *)lpParam;
	char filepath[250]={0};
	GetWindowTextA(dlg->m_url,(LPSTR)filepath,250);
	//===========================================

	av_register_all();
	avformat_network_init();
	pFormatCtx = avformat_alloc_context();

	if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){
		AfxMessageBox("Couldn't open input stream.\n");
		return -1;
	}
	if(avformat_find_stream_info(pFormatCtx,NULL)<0){
		AfxMessageBox("Couldn't find stream information.\n");
		return -1;
	}
	videoindex=-1;
	for(i=0; i<pFormatCtx->nb_streams; i++) 
		if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
			videoindex=i;
			break;
		}
		if(videoindex==-1){
			AfxMessageBox("Didn't find a video stream.\n");
			return -1;
		}
		pCodecCtx=pFormatCtx->streams[videoindex]->codec;
		pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
		if(pCodec==NULL){
			AfxMessageBox("Codec not found.\n");
			return -1;
		}
		if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
			AfxMessageBox("Could not open codec.\n");
			return -1;
		}
		pFrame=av_frame_alloc();
		pFrameYUV=av_frame_alloc();
		out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
		avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

		img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, 
			pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); 


		if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) {  
			AfxMessageBox( "Could not initialize SDL\n"); 
			return -1;
		} 
		//SDL 2.0 Support for multiple windows
		screen_w = pCodecCtx->width;
		screen_h = pCodecCtx->height;

		//显示在弹出窗口
		//screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
		//	screen_w, screen_h,SDL_WINDOW_OPENGL);
		//===========================================
		//显示在MFC控件上,这样显示视频的框就在MFC控件上了,而不是sdl一样的弹框
		screen = SDL_CreateWindowFrom(dlg->GetDlgItem(IDC_SCREEN)->GetSafeHwnd());
		//===========================================
		if(!screen) {  
			AfxMessageBox("SDL: could not create window - exiting\n");
			return -1;
		}
		sdlRenderer = SDL_CreateRenderer(screen, -1, 0);  
		//IYUV: Y + U + V  (3 planes)
		//YV12: Y + V + U  (3 planes)
		sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,pCodecCtx->width,pCodecCtx->height);  

		sdlRect.x=0;
		sdlRect.y=0;
		sdlRect.w=screen_w;
		sdlRect.h=screen_h;

		packet=(AVPacket *)av_malloc(sizeof(AVPacket));

		video_tid = SDL_CreateThread(sfp_refresh_thread,NULL,NULL);
		//------------SDL End------------
		//Event Loop

		for (;;) {
			//Wait
			SDL_WaitEvent(&event);
			if(event.type==SFM_REFRESH_EVENT){
				//------------------------------
				if(av_read_frame(pFormatCtx, packet)>=0){
					if(packet->stream_index==videoindex){
						ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
						if(ret < 0){
							AfxMessageBox("Decode Error.\n");
							return -1;
						}
						if(got_picture){
							sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
							//SDL---------------------------
							SDL_UpdateTexture( sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0] );  
							SDL_RenderClear( sdlRenderer );  
							//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );  
							SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, NULL);  
							SDL_RenderPresent( sdlRenderer );  
							//SDL End-----------------------
							TRACE("Decode 1 frame\n");
						}
					}
					av_free_packet(packet);
				}else{
					//Exit Thread
					thread_exit=1;
				}
			}else if(event.type==SDL_QUIT){
				thread_exit=1;
			}else if(event.type==SFM_BREAK_EVENT){
				break;
			}

		}

		sws_freeContext(img_convert_ctx);

		SDL_DestroyWindow(screen);
		SDL_Quit();
		//FIX Small Bug
		//SDL Hide Window When it finished
		dlg->GetDlgItem(IDC_SCREEN)->ShowWindow(SW_SHOWNORMAL);
		//--------------
		av_frame_free(&pFrameYUV);
		av_frame_free(&pFrame);
		avcodec_close(pCodecCtx);
		avformat_close_input(&pFormatCtx);

		return 0;
}



UINT Thread_Play(LPVOID lpParam){
	CSFFPlayerDlg *dlg=(CSFFPlayerDlg *)lpParam;
	simplest_ffmpeg_player(lpParam);
	return 0;
}



void CSFFPlayerDlg::OnBnClickedStart()
{
	/*
	char url[250]={0};
	m_url.GetWindowTextA(url,250);
	if(strcmp(url,"")==0){
		AfxMessageBox("文件路径为空!");
		return ;
	}
	*/
	pThreadPlay = AfxBeginThread(Thread_Play,this);//开启线程
}


void CSFFPlayerDlg::OnBnClickedPause()
{
	thread_pause=!thread_pause;
}


void CSFFPlayerDlg::OnBnClickedStop()
{
	thread_exit=1;
}


void CSFFPlayerDlg::OnBnClickedAbout()
{
	CAboutDlg dlg;
	dlg.DoModal();
}


void CSFFPlayerDlg::OnBnClickedCancel()
{
	// TODO: 在此添加控件通知处理程序代码
	CDialogEx::OnCancel();
}


void CSFFPlayerDlg::OnBnClickedFilebrowser()
{
	CString FilePathName;
	CFileDialog dlg(TRUE,NULL,NULL,NULL,NULL);///TRUE为OPEN对话框,FALSE为SAVE AS对话框 
	if(dlg.DoModal()==IDOK) {
		FilePathName=dlg.GetPathName();
		m_url.SetWindowText(FilePathName);
	}
}

Guess you like

Origin blog.csdn.net/weixin_37921201/article/details/89367525