将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;
}
演示效果二