之前写了一个SDL+FFmpeg的视频播放器,仔细的人可以发现其播放窗口无法拖拽,现在加入SDL的事件控制,来对其做优化
// 基于FFmpeg用SDL实现一个视频播放器(.h264)
//
/*
AVFormatContext:统领全局的基本结构体。主要用于处理封装格式(FLV/MKV/RMVB等)。
AVIOContext:输入输出对应的结构体,用于输入输出(读写文件,RTMP协议等)。
AVStream,AVCodecContext:视音频流对应的结构体,用于视音频编解码。
AVFrame:存储非压缩的数据(视频对应RGB/YUV像素数据,音频对应PCM采样数据)
AVPacket:存储压缩数据(视频对应H.264等码流数据,音频对应AAC/MP3等码流数据)
*/
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL2/SDL.h"
#include "SDL2/SDL_thread.h"
}
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 100)
#define SFM_BREAK_EVENT (SDL_USEREVENT + 101)
bool thread_exit = 0;
bool thread_pause = 0;
class SDLHandleEx
{
public:
SDLHandleEx(int w, int h)
{
m_rect.x = 0;
m_rect.y = 0;
m_rect.w = w;
m_rect.h = h;
SdlInit();
}
~SDLHandleEx()
{
if (m_pTexture)
{
SDL_DestroyTexture(m_pTexture);
}
if (m_pRender)
{
SDL_DestroyRenderer(m_pRender);
}
if (m_pWnd)
{
SDL_DestroyWindow(m_pWnd);
}
SDL_Quit();
}
bool CreateSDLWindow(const char* title, Uint32 flag)
{
m_pWnd = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, m_rect.w, m_rect.h, flag);
if (!m_pWnd)
{
printf("CreateWindows error:%s.\n", SDL_GetError());
return false;
}
m_pRender = SDL_CreateRenderer(m_pWnd, -1, 0);
if (!m_pRender)
{
return false;
}
m_pTexture = SDL_CreateTexture(m_pRender, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, m_rect.w, m_rect.h);
if (!m_pTexture)
{
return false;
}
return true;
}
void UpdateTexture(AVFrame* pFrame)
{
if (!pFrame)
{
return;
}
//SDL_UpdateTexture(m_pTexture, &m_rect, pFrame->data[0], pFrame->linesize[0]);
SDL_UpdateYUVTexture(m_pTexture, &m_rect, pFrame->data[0], pFrame->linesize[0], pFrame->data[1], pFrame->linesize[1], pFrame->data[2], pFrame->linesize[2]);
SDL_RenderClear(m_pRender);
SDL_RenderCopy(m_pRender, m_pTexture, nullptr, &m_rect);
SDL_RenderPresent(m_pRender);
SDL_Delay(40);
}
private:
bool SdlInit()
{
if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)
{
printf("sdl_init error:%s\n", SDL_GetError());
return false;
}
return true;
}
private:
SDL_Renderer* m_pRender = nullptr;
SDL_Window* m_pWnd = nullptr;
SDL_Texture* m_pTexture = nullptr;
SDL_Rect m_rect;
};
int RefreshThread(void *opaque)
{
thread_pause = false;
thread_exit = false;
while (!thread_exit)
{
if (!thread_pause)
{
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
}
SDL_Delay(40);
}
// 退出逻辑
SDL_Event event_break;
event_break.type = SFM_BREAK_EVENT;
SDL_PushEvent(&event_break);
thread_exit = 0;
thread_pause = 0;
return 0;
}
int main(int argc, char* argv[])
{
AVFormatContext* pFormatCtx = nullptr;
AVCodecContext* pCodecCtx = nullptr;
AVCodec* pCodec = nullptr; // 解码器
int iVideoIndex = 0, iRet = 0;
AVFrame* pFrame = nullptr;
AVFrame* pFrameYUV = nullptr;
AVPacket* pPacket = nullptr;
unsigned char* out_buffer = nullptr;
SwsContext* pSwsCtx = nullptr;
FILE* fp_yuv = nullptr;
int got_picture = 0;
// sdl
int nScreen_w = 0, nScreen_h = 0;
SDL_Window* pScreen = nullptr; // 播放窗口
SDL_Renderer* pRender = nullptr; // 渲染器
SDL_Texture* pSDLTexture = nullptr; // 纹理
SDL_Event sdlEvent;
char filepath[] = "bigbuckbunny_480x272.h265";
SDLHandleEx* m_pSDlHandle = nullptr;
// ffmpeg
av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context();
if (!pFormatCtx)
{
printf("avformat_alloc_context error!\n");
goto exit;
}
// 该函数用于打开多媒体数据并且获得一些相关的信息
if (avformat_open_input(&pFormatCtx, filepath, nullptr, nullptr) < 0)
{
printf("avformat_open_input failed!\n");
goto exit;
}
// 该函数可以读取一部分视音频数据并且获得一些相关的信息
if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)
{
printf("avformat_find_stream_info error!\n");
goto exit;
}
iVideoIndex = -1;
for (int i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
iVideoIndex = i;
break;
}
}
if (iVideoIndex == -1)
{
printf("not find a video stream.\n");
goto exit;
}
pCodecCtx = pFormatCtx->streams[iVideoIndex]->codec;
nScreen_w = pCodecCtx->width;
nScreen_h = pCodecCtx->height;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); // 查找解码器
if (pCodec == nullptr)
{
printf("avcodec_find_decode error.\n");
goto exit;
}
if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0)
{
printf("avcodec_open2 failed!\n");
goto exit;
}
printf("--------------- File Information ----------------\n");
av_dump_format(pFormatCtx, 0, filepath, 0);
printf("-------------------------------------------------\n");
// 解码准备
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
pPacket = (AVPacket*)av_malloc(sizeof(AVPacket));
// 申请内存 w*h*1.5(Y:w*h U:w*h/4 V:w*h/4)
out_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, nullptr, nullptr, nullptr);
m_pSDlHandle = new SDLHandleEx(pCodecCtx->width, pCodecCtx->height);
if (!m_pSDlHandle->CreateSDLWindow("SDL_TEXT", SDL_WINDOW_OPENGL))
{
printf("CreateSDLWindow error:%s\n", SDL_GetError());
goto exit;
}
if (!SDL_CreateThread(RefreshThread, "refresh", nullptr))
{
printf("SDL_CreateThrad error:%s!\n", SDL_GetError());
goto exit;
}
while (true)
{
SDL_WaitEvent(&sdlEvent);
if (sdlEvent.type == SFM_REFRESH_EVENT) // 刷新播放
{
// 解码
while (true) // 拿一帧停一次
{
if (av_read_frame(pFormatCtx, pPacket) < 0)
{
thread_exit = true;
}
if (pPacket->stream_index == iVideoIndex) // 视频packet
{
break;
}
}
iRet = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, pPacket);
if (iRet < 0)
{
printf("avcodec_decode_video2 error!\n");
goto exit;
}
if (got_picture)
{
sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
m_pSDlHandle->UpdateTexture(pFrame);
}
av_free_packet(pPacket);
}
else if (sdlEvent.type == SFM_BREAK_EVENT) // 停止
{
break;
}
else if (sdlEvent.type == SDL_KEYDOWN)
{
if (sdlEvent.key.keysym.sym == SDLK_SPACE) // 空格键
{
thread_pause = !thread_pause;
}
}
else if (sdlEvent.type == SDL_QUIT) // 退出
{
thread_exit = 1;
}
}
// 解码器虽然读完了 但是还有一些帧缓存
while (true)
{
iRet = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, pPacket);
if (iRet < 0)
{
break;
}
if (!got_picture)
{
break;
}
sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
m_pSDlHandle->UpdateTexture(pFrame);
}
exit:
if (m_pSDlHandle)
{
delete m_pSDlHandle;
m_pSDlHandle = nullptr;
}
if (pSwsCtx)
{
sws_freeContext(pSwsCtx);
}
if (pFrame)
{
av_frame_free(&pFrame);
}
if (pFrameYUV)
{
av_frame_free(&pFrameYUV);
}
if (pCodecCtx)
{
avcodec_close(pCodecCtx);
}
if (pFormatCtx)
{
avformat_close_input(&pFormatCtx);
}
return 0;
}