看了雷神的一些文章和解释,自己重新实现了一下相关代码的东西,做为加深。
一起在音视频领域加油咯!
// 基于FFmpeg用SDL实现一个视频播放器(.h264)
//
/*
AVFormatContext:统领全局的基本结构体。主要用于处理封装格式(FLV/MKV/RMVB等)。
AVIOContext:输入输出对应的结构体,用于输入输出(读写文件,RTMP协议等)。
AVStream,AVCodecContext:视音频流对应的结构体,用于视音频编解码。
AVFrame:存储非压缩的数据(视频对应RGB/YUV像素数据,音频对应PCM采样数据)
AVPacket:存储压缩数据(视频对应H.264等码流数据,音频对应AAC/MP3等码流数据)
原文链接:/leixiaohua1020/article/details/41181155
*/
extern "C"
{
#include "libavcodec/"
#include "libavformat/"
#include "libswscale/"
#include "libavutil/"
#include "SDL2/"
}
class SDLHandle
{
public:
SDLHandle(int w, int h)
{
m_rect.x = 0;
m_rect.y = 0;
m_rect.w = w;
m_rect.h = h;
SdlInit();
}
~SDLHandle()
{
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 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; // 纹理
char filepath[] = "bigbuckbunny_480x272.h265";
SDLHandle* 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 SDLHandle(pCodecCtx->width, pCodecCtx->height);
if (!m_pSDlHandle->CreateSDLWindow("SDL_TEXT", SDL_WINDOW_OPENGL))
{
printf("CreateSDLWindow error:%s\n", SDL_GetError());
goto exit;
}
// 解码
while (av_read_frame(pFormatCtx,pPacket) >= 0)
{
if (pPacket->stream_index == iVideoIndex) // 视频packet
{
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);
}
// 解码器虽然读完了 但是还有一些帧缓存
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;
}