SDL2+FFmpeg5.0播放视频文件
一、概述
上一节使用SDL2播放了YUV视频文件,本节使用SDL2+FFmpeg5.0播放一个视频文件(只播放视频,不播放声音)
播放效果图:
二、代码示例
#include "sdl_ffmpeg_play.h" //sdl刷新事件 #define SFM_REFRESH_EVENT (SDL_USEREVENT + 1) //sdl退出事件 #define SFM_BREAK_EVENT (SDL_USEREVENT + 2) int sdl_thread_exit = 0; int sfp_refresh_thread(void* opaque) { sdl_thread_exit = 0; while (!sdl_thread_exit) { SDL_Event event; event.type = SFM_REFRESH_EVENT; SDL_PushEvent(&event); SDL_Delay(10); } sdl_thread_exit = 0; //Break SDL_Event event; event.type = SFM_BREAK_EVENT; SDL_PushEvent(&event); return 0; } SDLFFmpegPlay::SDLFFmpegPlay() { AVFormatContext* pFormatCtx; int i, videoindex; AVCodecContext* pCodecCtx; const 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; char retError[128] = {0}; struct SwsContext* img_convert_ctx; char filepath[] = "E:/tony/demo/visualstudio_workspace/SDLDemo/SDLDemo/videos/diao_si_nan_shi.mov"; avformat_network_init(); pFormatCtx = avformat_alloc_context(); //尝试打开媒体流文件 ret = avformat_open_input(&pFormatCtx, filepath, NULL, NULL); if (ret != 0) { av_strerror(ret, retError,sizeof(retError)); cout << "无法打开文件" << ret << ":" << retError << endl; return; } //获取文件媒体流信息 ret = avformat_find_stream_info(pFormatCtx, NULL); if ( ret < 0) { av_strerror(ret, retError, sizeof(retError)); cout << "无法获取文件媒体流信息:" << ret << ":" << retError << endl; avformat_close_input(&pFormatCtx); return; } //查找视频流索引 videoindex = -1; videoindex = av_find_best_stream(pFormatCtx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0); if (AVERROR_STREAM_NOT_FOUND == videoindex) { cout << "无法找到视频流" << endl; avformat_close_input(&pFormatCtx); return; } //查找解码器 pCodec = avcodec_find_decoder(pFormatCtx->streams[videoindex]->codecpar->codec_id); if (NULL == pCodec) { cout << "无法找到解码器" << endl; avformat_close_input(&pFormatCtx); return; } //初始化解码器上下文 pCodecCtx = avcodec_alloc_context3(pCodec); //初始化解码器上下文 ret = avcodec_parameters_to_context(pCodecCtx,pFormatCtx->streams[videoindex]->codecpar); if (ret < 0) { av_strerror(ret, retError, sizeof(retError)); cout << "初始化解码器上下文失败:" << ret << ":" << retError << endl; avformat_close_input(&pFormatCtx); return; } //打开解码器 ret = avcodec_open2(pCodecCtx,pCodec,NULL); if (ret != 0) { av_strerror(ret, retError, sizeof(retError)); cout << "无法打开解码器:" << ret << ":" << retError << endl; avformat_close_input(&pFormatCtx); return; } pFrame = av_frame_alloc(); pFrameYUV = av_frame_alloc(); pFrameYUV->width = pCodecCtx->width; pFrameYUV->height = pCodecCtx->height; pFrameYUV->format = AV_PIX_FMT_YUV420P; ret = av_frame_get_buffer(pFrameYUV, 0); cout << "ret:" << ret << endl; cout << "width:" << pCodecCtx->width << endl; cout << "height" <<pCodecCtx->height<< endl; //给frameYUV分配内存 //av_frame_get_buffer(pFrameYUV, AV_PIX_FMT_YUV420P); //out_buffer = (uint8_t*)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P), pCodecCtx->width, pCodecCtx->height)); //avpicture_fill((AVPicture*)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) { printf("Could not initialize SDL - %s\n", SDL_GetError()); return; } //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); if (!screen) { printf("SDL: could not create window - exiting:%s\n", SDL_GetError()); return; } 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 cout << "开始循环解码" << endl; for (;;) { //Wait SDL_WaitEvent(&event); if (event.type == SFM_REFRESH_EVENT) { cout << "开始解码" << endl; //------------------------------ if (av_read_frame(pFormatCtx, packet) >= 0) { if (packet->stream_index == videoindex) { //把编码数据送入解码器解码 ret = avcodec_send_packet(pCodecCtx,packet);//返回0代表解码成功 if (ret < 0) { cout << "解码packet失败" << endl; return; } //从解码器中拿出解码后的数据 //got_picture = avcodec_receive_frame(pCodecCtx, pFrame); //拿到数据后对数据进行转换 while (avcodec_receive_frame(pCodecCtx, pFrame)==0) { cout <<"解码数据->width:"<< pFrame->width<<",height="<<pFrame->height << endl; 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----------------------- } } av_packet_unref(packet); } else { //Exit Thread sdl_thread_exit = 1; } } else if (event.type == SDL_QUIT) { sdl_thread_exit = 1; } else if (event.type == SFM_BREAK_EVENT) { break; } } if (img_convert_ctx) { sws_freeContext(img_convert_ctx); } SDL_Quit(); //-------------- av_frame_free(&pFrameYUV); av_frame_free(&pFrame); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); } SDLFFmpegPlay::~SDLFFmpegPlay() { }
三、遇到的问题
1.目标frame未分配内存空间
[swscaler @ 000002a75aa0a700] bad dst image pointers
解决办法:使用av_frame_get_buffer分配一下即可
pFrameYUV = av_frame_alloc(); pFrameYUV->width = pCodecCtx->width; pFrameYUV->height = pCodecCtx->height; pFrameYUV->format = AV_PIX_FMT_YUV420P; ret = av_frame_get_buffer(pFrameYUV, 0);
2.avcodec_receive_frame用法错误,导致解码后的AVFrame中的数据是空的、宽高为0,错误提示如下
[swscaler @ 0000022b16e2a700] bad src image pointers
错误用法:
//从解码器中拿出解码后的数据 got_picture = avcodec_receive_frame(pCodecCtx, pFrame); //拿到数据后对数据进行转换 got_picture = avcodec_receive_frame(pCodecCtx, pFrame); if (got_picture) { cout <<"解码数据->width:"<< pFrame->width<<",height="<<pFrame->height << endl; 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----------------------- }
正确用法
if (avcodec_receive_frame(pCodecCtx, pFrame)==0) { cout <<"解码数据->width:"<< pFrame->width<<",height="<<pFrame->height << endl; 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----------------------- }
更新代码:
上面使用SDL2+FFmpeg播放视频文件并展示的小案例中有一个色差问题。因为视频格式是YUV420P的,但是我们用SDL更新纹理的方式(SDL_UpdateTexture)并不是YUV形式,的需要把更新纹理形式换成如下所示才不会有色差问题:
//由于视频的格式是YUV的,所以SDL更新纹理的方式也需要使用SDL_UpdateYUVTexture,如果使用上面的去更新纹理会出现色差情况 SDL_UpdateYUVTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0], pFrameYUV->data[1], pFrameYUV->linesize[1], pFrameYUV->data[2], pFrameYUV->linesize[2]);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
2021-11-16 C/C++使用FIFO实现非父子进程之间的通讯