FFmpeg入门:最简单的视频播放器

server/2025/3/1 13:53:04/

FFmpeg入门:最简单的视频播放器

FFmpeg入门第一篇,制作一个简单的MP4视频播放器。

整体流程

话不多说,直接上流程图在这里插入图片描述

视频播放速率控制

这里可以直接看图中的帧率同步模块,可以分为如下几步

  1. 获取到当前帧的预期播放时间:根据时间基time_base和帧pts计算;time_base*pts
  2. 解码渲染过程中,获取当前时间av_gettime,计算和预期播放时间的时间差
  3. 使用SDL_Delay函数延迟播放时间差。

源代码

tutorial01.h

//
//  tutorial02.h
//  tutorial02
//
//  Created by chenhuaiyi on 2025/2/13.
//#ifndef tutorial02_h
#define tutorial02_h#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/time.h>#include <SDL.h>
#include <SDL_thread.h>#include <stdio.h>/**宏定义*/
#define SFM_REFRESH_EVENT (SDL_USEREVENT+1)/**全局变量*/
extern int            thread_exit;/**方法声明*/
char* get_frame_type(AVFrame* frame);#endif /* tutorial02_h */

tutorial02.c

// tutorial02.c
// A pedagogical video player that will stream through every video frame as fast as it can.#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif#include "tutorial02.h"// 控制程序是否结束
int		thread_exit=0;/**获取帧类型*/
char* get_frame_type(AVFrame* frame) {switch (frame->pict_type) {case AV_PICTURE_TYPE_I:return "I";break;case AV_PICTURE_TYPE_P:return "P";break;case AV_PICTURE_TYPE_B:return "B";break;case AV_PICTURE_TYPE_S:return "S";break;case AV_PICTURE_TYPE_SI:return "SI";break;case AV_PICTURE_TYPE_SP:return "SP";break;case AV_PICTURE_TYPE_BI:return "BI";break;default:return "N";break;}
}int main(int argc, char *argv[]) {AVFormatContext*  pFormatCtx = NULL;int               i, videoStream;AVCodecContext*   pCodecCtx = avcodec_alloc_context3(NULL);;AVCodecParameters*  pCodecParam = NULL;const AVCodec*    pCodec = NULL;AVFrame*          pFrame = NULL;AVFrame*          pFrame2 = NULL;AVPacket          packet;AVDictionary*     optionsDict = NULL;struct SwsContext*  sws_ctx = NULL;SDL_Window*       window = NULL;SDL_Renderer*     renderer = NULL;SDL_Texture*      texture=NULL;SDL_Rect          rect;SDL_Event         event;if(argc < 2) {fprintf(stderr, "Usage: test <file>\n");exit(1);}// SDL组件创建if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());exit(1);}// 1. 打开视频文件,获取格式上下文if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)return -1; // Couldn't open file// 2. 对文件探测流信息if(avformat_find_stream_info(pFormatCtx, NULL)<0)return -1; // Couldn't find stream information// 打印信息av_dump_format(pFormatCtx, 0, argv[1], 0);// 3.找到对应的视频流videoStream=-1;for(i=0; i<pFormatCtx->nb_streams; i++)if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO) {videoStream=i;break;}if(videoStream==-1){return -1; // Didn't find a video stream}// 4. 将视频流编码参数写入上下文pCodecParam = pFormatCtx->streams[videoStream]->codecpar;avcodec_parameters_to_context(pCodecCtx, pCodecParam);// 5. 查找流的编码器pCodec=avcodec_find_decoder(pCodecCtx->codec_id);if(pCodec==NULL) {fprintf(stderr, "Unsupported codec!\n");return -1; // Codec not found}// 6. 打开流的编解码器if(avcodec_open2(pCodecCtx, pCodec, &optionsDict)<0)return -1; // Could not open codec// 7.申请缩放颜色空间格式的上下文sws_ctx =sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height,AV_PIX_FMT_YUV420P,SWS_BILINEAR,NULL,NULL,NULL);// 8.帧分配pFrame=av_frame_alloc();pFrame2=av_frame_alloc();// 9.计算并分配frame帧所占内存空间int numBytes=av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);uint8_t* buffer=(uint8_t*)av_malloc(numBytes*sizeof(uint8_t));av_image_fill_arrays(pFrame2->data, pFrame2->linesize, buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);i=0;Uint32 start_time = SDL_GetTicks();// SDL2的最新创建和渲染窗口方式window = SDL_CreateWindow("SDL2 window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, pCodecCtx->width, pCodecCtx->height, SDL_WINDOW_SHOWN);if (!window) {printf("SDL_CreateWindow Error: %s\n", SDL_GetError());SDL_Quit();return 1;}renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);if (!renderer) {printf("SDL_CreateRenderer Error: %s\n", SDL_GetError());SDL_DestroyWindow(window);SDL_Quit();return 1;}texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);AVRational time_base = pFormatCtx->streams[videoStream]->time_base;int64_t av_start_time = av_gettime();								// 播放开始时间int64_t frame_delay = av_q2d(time_base) * AV_TIME_BASE;				// pts单位(ms*1000)int64_t frame_start_time = av_gettime();// 循环一:对文件上下文持续读取packetwhile(av_read_frame(pFormatCtx, &packet)>=0) {if(packet.stream_index==videoStream) {// 将packet写入编解码器int ret = avcodec_send_packet(pCodecCtx, &packet);if (ret < 0) {printf("packet resolve error!");break;}// 循环二:从解码器中不断读取帧while(!avcodec_receive_frame(pCodecCtx, pFrame)) { // 解码得到数据frame// 帧格式转化,转为YUV420Psws_scale(sws_ctx,(uint8_t const * const *)pFrame->data,pFrame->linesize,0,pCodecCtx->height,pFrame2->data,pFrame2->linesize);// 将AVFrame的数据写入到texture中,然后渲染后windows上rect.x = 0;rect.y = 0;rect.w = pCodecCtx->width;rect.h = pCodecCtx->height;// 更新纹理SDL_UpdateYUVTexture(texture, &rect,pFrame2->data[0], pFrame2->linesize[0],	// 	YpFrame2->data[1], pFrame2->linesize[1],	// 	UpFrame2->data[2], pFrame2->linesize[2]);	//  V// 渲染页面SDL_RenderClear(renderer);SDL_RenderCopy(renderer, texture, NULL, NULL);SDL_RenderPresent(renderer);int64_t pts = pFrame->pts;											// ptsint64_t actual_playback_time = av_start_time + pts * frame_delay;	// 实际播放时间int64_t current_time = av_gettime();if (actual_playback_time > current_time) {SDL_Delay((Uint32)(actual_playback_time-current_time)/1000);	// 延迟当前时间和实际播放时间}i++;printf("第%i帧 | 属于%s | pts为%d | 时长为%.2fms | 实际播放点为%.2fs | 预期播放点为%.2fs\n ",i,get_frame_type(pFrame),(int)pFrame->pts,(double)(av_gettime() - frame_start_time)/1000,(double)(av_gettime() - av_start_time)/AV_TIME_BASE,pFrame->pts * av_q2d(time_base));frame_start_time = av_gettime();}}// Free the packet that was allocated by av_read_frameav_packet_unref(&packet);SDL_PollEvent(&event);switch(event.type) {case SDL_QUIT:SDL_Quit();exit(0);break;default:break;}}Uint32 endTime = SDL_GetTicks();/**打印一些关键参数*/printf("格式: %s\n", pFormatCtx->iformat->name);printf("时长: %lld us\n", pFormatCtx->duration);printf("编码器: %s (%s)\n", pCodec->name, avcodec_get_name(pCodecCtx->codec_id));printf("分辨率: %dx%d\n", pCodecCtx->width, pCodecCtx->height);printf("帧率: %.2f\n", av_q2d(pFormatCtx->streams[videoStream]->avg_frame_rate));printf("帧数: %lld\n", pFormatCtx->streams[videoStream]->nb_frames);printf("比特率: %lld\n", pFormatCtx->bit_rate);printf("pts单位(ms*1000): %.2f\n", av_q2d(pFormatCtx->streams[videoStream]->time_base) * AV_TIME_BASE);printf("视频持续时长为 %d,视频帧总数为 %d\n", (endTime-start_time)/1000, i);// Free the YUV frameav_free(pFrame);av_free(pFrame2);// Close the codec paramavcodec_parameters_free(&pCodecParam);// Close the codecavcodec_free_context(&pCodecCtx);// Close the video fileavformat_close_input(&pFormatCtx);return 0;
}

文章最后,感谢音视频领域的大神@雷霄骅,雷神永垂不朽!!!


http://www.ppmy.cn/server/171562.html

相关文章

【无人集群系列---无人机集群编队算法】

【无人集群系列---无人机集群编队算法】 一、核心目标二、主流编队控制方法1. 领航-跟随法&#xff08;Leader-Follower&#xff09;2. 虚拟结构法&#xff08;Virtual Structure&#xff09;3. 行为法&#xff08;Behavior-Based&#xff09;4. 人工势场法&#xff08;Artific…

代理服务器与内网穿透/打洞

内网穿透 简单来说内网穿透就是让一个在私人IP的设备&#xff0c;能在公网上被别的主机访问到资源。 中间经过服务器将获取的数据转发给主机。 内网打洞 内网打洞&#xff0c;也叫 P2P 穿透或 NAT 穿越&#xff0c;是一种用于实现位于不同内网中的设备之间直接建立连接的技…

DeepSeek【部署 02】Linux本地化部署及SpringBoot2.X集成Ollama

安装资源分享&#xff1a; 百度网盘链接: https://pan.baidu.com/s/17qK0Nx73bFOsicLgLmA8-A?pwdtc61 提取码: tc61 包含文件&#xff1a; Windows OllamaStep.exe&#xff08;版本 0.5.7&#xff09;Linux ollama-linux-amd64-039.tgz&#xff08;版本 0.3.9&#xff09;Lin…

广州4399游戏25届春招游戏策划管培生内推

【热招岗位】 游戏策划管培生、产品培训生、游戏文案策划、游戏数值策划、游戏系统策划、游戏产品运营、游戏战斗策划、游戏关卡策划 【其他岗位】产品类&#xff08;产品培训生、产品运营等&#xff09;、技术类&#xff08;开发、测试、算法、运维等&#xff09;、运营市场类…

HAProxy的ACL

访问控制列表&#xff08;ACL&#xff0c;Access Control Lists&#xff09;是一种基于包过滤的访问控制技术&#xff0c;它可以根据设定的条件对经过服务器传输的数据包进行过滤(条件匹配)&#xff0c;即对接收到的报文进行匹配和过滤&#xff0c;基于请求报文头部中的源地址、…

01文件IO

一、linux_C函数接口手册 在线手册 Linux 常用C函数(中文版) linux一切皆文件 在linux系统下一切都是以文件的形式存储在系统中的&#xff0c;通过linux系统提供的IO(input/output)接口开发板就可以往linux系统中 输入一些信息和获取一些信息。 linux系统的文件分类: - …

自动化问题汇总

PLC【1】伺服轴使用前 机器人【1】机器人使用前 上位机【1】 系统【1】Window的性能测试工具无法加载性能计数器 其他【1】 PLC 【1】伺服轴使用前 机器人 【1】机器人使用前 上位机 【1】 系统 【1】Window的性能测试工具无法加载性能计数器 使用.NET时&#xff0c;编…

git上传仓库操作

在 Visual Studio Code (VSCode) 中&#xff0c;手动将本地仓库与远程仓库关联起来是一个常见的需求。以下是详细的操作步骤和解释&#xff1a; 前提条件 已安装 Git&#xff1a;确保你的系统中已经安装了 Git&#xff0c;并且可以通过命令行运行 git 命令。已初始化本地仓库&…