FFmpeg与SDL双剑合璧之Windows

将FFMPEG解码一个视频文件,解码出来的每一帧YUV数据放入SDL进行渲染播放;

以下代码实现几个基本功能:

①解码一个视频文件,只取视频数据,解码出yuv数据,封装成易用的接口,支持多实例;

②将SDL封装成一个简单的类,支持多实例,实现窗口消息,可缩放,视频随窗口变化而变化;

③简单的调用例子,一个单窗口播放,一个多窗口播放。

基本开发环境:

①windows7+vs2005

②ffmpeg 2.7.1 该版本已经支持h265解码 ffmpeg下载地址

③SDL2.0  SDL下载地址


代码如下:

1)封装解码功能

//FFDecoder.h
#pragma once

extern "C" {
	#include "libavcodec/avcodec.h"
	#include "libavformat/avformat.h"
	#include "libavutil/avutil.h" 
	#include "libavutil/opt.h"
	#include "libswscale/swscale.h"
}

#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"swresample.lib")
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"swscale.lib")

class CFFDecoder
{
public:
	CFFDecoder();
	virtual ~CFFDecoder();

	int					OpenFile(const char *pFilePath);
	int					GetMediaInfo(int &nFrameW,int &nFrameH);
	int					GetOneFrame(AVFrame *pFrame);

private:
	AVFormatContext		*m_pFormatCxt;
	AVCodecContext		*m_pCodecCtx;
	AVCodec				*m_pCodec;
	AVPacket			m_Packet;
	int					m_nVideoIndex;
	int					m_nAudioIndex;
};



解码类的实现

//FFDecoder.cpp

#include "FFDecoder.h"

CFFDecoder::CFFDecoder()
{
	m_pFormatCxt = avformat_alloc_context();
	m_pCodecCtx = NULL;
	m_pCodec = NULL;
	m_nVideoIndex = -1;
	m_nAudioIndex = -1;
}

CFFDecoder::~CFFDecoder()
{

}

int CFFDecoder::OpenFile(const char *pFilePath)
{
	av_register_all();
	
	if(avformat_open_input(&m_pFormatCxt,pFilePath,NULL,NULL)<0)
	{
		return -1;
	}

	if (avformat_find_stream_info(m_pFormatCxt,NULL)<0)
	{
		return -2;
	}

	//找到音视频对应的流通道

	for (int i=0;i<m_pFormatCxt->nb_streams;i++)
	{
		if (m_pFormatCxt->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			m_nVideoIndex = i;
		}
		else if (m_pFormatCxt->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			m_nAudioIndex = i;
		}
	}

	if (m_nVideoIndex == -1)
	{
		return -3;
	}

	//打开相应的解码器
	m_pCodecCtx = m_pFormatCxt->streams[m_nVideoIndex]->codec;
	m_pCodec = avcodec_find_decoder(m_pCodecCtx->codec_id);;
	if(m_pCodec==NULL){  
		return -4;  
	}

	if(avcodec_open2(m_pCodecCtx, m_pCodec,NULL)<0){
		return -5;
	}

	return 0;
}

int CFFDecoder::GetMediaInfo(int &nFrameW,int &nFrameH)
{
	if(m_pCodecCtx==NULL)
		return -1;

	nFrameW = m_pCodecCtx->width;
	nFrameH = m_pCodecCtx->height;
	return 0;
}

int CFFDecoder::GetOneFrame(AVFrame *pFrame)
{
	if(m_pFormatCxt==NULL)
		return 0;

	int nGotPicture=-1;

	if(av_read_frame(m_pFormatCxt,&m_Packet)>=0)
	{
		//判断是否为当前视频流中的包
		if (m_Packet.stream_index == m_nVideoIndex)
		{
			int nLen = avcodec_decode_video2(m_pCodecCtx,pFrame,&nGotPicture,&m_Packet);
			if (nLen<0)
			{
				return 0;
			}

			if (nGotPicture)
			{
				//成功得到一帧数据
				return nLen;
			}
		}
	}
	return 0;
}

2)封装了SDL的播放功能

//SDLPlayer.h
#pragma once
extern "C" {
    #include "SDL.h"
}

#pragma comment(lib,"SDL2.lib")

#define        MSG_REFRESH_VIDEO        (SDL_USEREVENT+10)

class CSDLPlayer
{
public:
    CSDLPlayer();
    virtual ~CSDLPlayer();
    //初始化播放器,设置播放器宽高
    int                InitPlayer(int nWinW, int nWinH);

    //初始化纹理,设置纹理宽高
    int                InitTexture(int nFrameW, int nFrameH);

    int                InputFrame(unsigned char *pY, unsigned long Ylinesize,
                                unsigned char *pU, unsigned long Ulinesize,
                                unsigned char *pV, unsigned long Vlinesize);
    //(unsigned char *pYUVData, unsigned long linesize);

private:
    static int        Thread2Refresh(void *opaque);
private:
    SDL_Window        *m_pPlayer;
    SDL_Rect        m_rect;  
    SDL_Renderer    *m_pReader;
    SDL_Texture        *m_pTexture;
};

实现SDL播放器的封装,支持同时打开多个窗口

//SDLPlayer.cpp
#include "SDLPlayer.h"

CSDLPlayer::CSDLPlayer()
{
	m_pPlayer = NULL;
	m_pReader = NULL;
	m_pTexture = NULL;
}

CSDLPlayer::~CSDLPlayer()
{

}

int CSDLPlayer::Thread2Refresh(void *opaque)
{  
	bool bQuit=false;
	while(!bQuit)
	{
		SDL_Event evt0;
		if(SDL_PollEvent(&evt0))//从消息队列里面取出一个消息
		{
			if (evt0.type==SDL_QUIT)
			{
				bQuit = true;
			}
		}

		SDL_Event evt;
		evt.type = MSG_REFRESH_VIDEO;
		SDL_PushEvent(&evt);
		SDL_Delay(40);
	}
	return 0;  
}  

int CSDLPlayer::InitPlayer(int nWinW, int nWinH)
{
	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {  
		return -1;  
	}   

	m_pPlayer=SDL_CreateWindow("Hello SDL",
		SDL_WINDOWPOS_UNDEFINED,
		SDL_WINDOWPOS_UNDEFINED,
		nWinW,nWinH,
		SDL_WINDOW_RESIZABLE|SDL_WINDOW_OPENGL);
	
	SDL_Thread *refresh_thread = SDL_CreateThread(Thread2Refresh,NULL,NULL);
	
	return 0;
}

int CSDLPlayer::InitTexture(int nFrameW, int nFrameH)
{
	if(m_pPlayer==NULL)
		return 0;

	m_pReader = SDL_CreateRenderer(m_pPlayer,-1,0);
	m_pTexture = SDL_CreateTexture(m_pReader,SDL_PIXELFORMAT_IYUV,SDL_TEXTUREACCESS_STREAMING,nFrameW,nFrameH);

	m_rect.x = 0;
	m_rect.y = 0;
	m_rect.w = nFrameW;
	m_rect.h = nFrameH;

	return 0;
}

int	CSDLPlayer::InputFrame(unsigned char *pY, unsigned long Ylinesize,
						   unsigned char *pU, unsigned long Ulinesize,
						   unsigned char *pV, unsigned long Vlinesize)
{
 	if(m_pPlayer==NULL || m_pReader==NULL || m_pTexture==NULL)
 		return 0;

	//实现消息,是为了控制播放速度已经避免窗口出现未响应状态
	SDL_Event evt;
	SDL_WaitEvent(&evt);

	//当SDL产生多个窗口时,需要这样来判断某个窗口点击了关闭按钮
	if(evt.type == SDL_WINDOWEVENT)
	{
		if(evt.window.event==SDL_WINDOWEVENT_CLOSE)
		{
			SDL_Window *pWindow=SDL_GetWindowFromID(evt.window.windowID);
			if(pWindow == m_pPlayer)
			{
				SDL_DestroyWindow(pWindow);
				m_pPlayer = NULL;
			}
		}
	}

	if(evt.type!=MSG_REFRESH_VIDEO)
		return -1;

	SDL_Rect sdlRect; 
						 
	sdlRect.x = 0;    
	sdlRect.y = 0;    
	SDL_GetWindowSize(m_pPlayer,&sdlRect.w,&sdlRect.h);

	//可以处理那些yuv内存数据不连续的情况
	SDL_UpdateYUVTexture(m_pTexture,&m_rect,
		pY,Ylinesize,
		pU,Ulinesize,
		pV,Vlinesize);
	SDL_RenderClear( m_pReader );     
	SDL_RenderCopy( m_pReader, m_pTexture, &m_rect, &sdlRect);    
	SDL_RenderPresent( m_pReader ); 
	
	return 0;
}

3)调用测试用例

#include "FFDecoder.h"
#include "SDLPlayer.h"
int _tmain(int argc, _TCHAR* argv[])
{
	CFFDecoder dec;
	CSDLPlayer player;

	dec.OpenFile("F:\\Video\\h265\\4K风光6声道2012.mkv");

	int nFrameW=0,nFrameH=0;
	dec.GetMediaInfo(nFrameW,nFrameH);

	player.InitPlayer(800,800);
	player.InitTexture(nFrameW,nFrameH);

	AVFrame *pFrame=av_frame_alloc();
	while(1)
	{
		if(dec.GetOneFrame(pFrame)>0)
		{
			player.InputFrame(pFrame->data[0],pFrame->linesize[0],
								pFrame->data[1],pFrame->linesize[1],
								pFrame->data[2],pFrame->linesize[2]);
		}
	}
	return 0;
}

演示效果一


4)多路播放窗口,对测试用例稍作修改,显示在多个窗口中

#include "FFDecoder.h"
#include "SDLPlayer.h"
#include <windows.h>


int _tmain(int argc, _TCHAR* argv[])
{
	CFFDecoder dec;
	CSDLPlayer player[2];

	dec.OpenFile("F:\\Video\\h265\\4K风光6声道2012.mkv");

	int nFrameW=0,nFrameH=0;
	dec.GetMediaInfo(nFrameW,nFrameH);

	player[0].InitPlayer(800,600);
	player[0].InitTexture(nFrameW,nFrameH);

	player[1].InitPlayer(800,600);
	player[1].InitTexture(nFrameW,nFrameH);

	AVFrame *pFrame=av_frame_alloc(); 

	while(1)
	{
		if(dec.GetOneFrame(pFrame)>0)
		{
			player[0].InputFrame(pFrame->data[0],pFrame->linesize[0],
								pFrame->data[1],pFrame->linesize[1],
								pFrame->data[2],pFrame->linesize[2]);

			player[1].InputFrame(pFrame->data[0],pFrame->linesize[0],
				pFrame->data[1],pFrame->linesize[1],
				pFrame->data[2],pFrame->linesize[2]);
		}
	}
	return 0;
}

演示效果二


posted on 2017-10-12 21:27  zhuxian2009  阅读(264)  评论(0编辑  收藏  举报

导航