当前位置: 首页 > news >正文

【FFMPEG源码分析】从ffplay源码摸清ffmpeg框架(一)

ffplay入口

ffmpeg\fftools\ffplay.c
int main(int argc, char **argv)
{/*******************start 动态库加载/网络初始化等**************/int flags;VideoState *is;init_dynload();av_log_set_flags(AV_LOG_SKIP_REPEATED);parse_loglevel(argc, argv, options);/* register all codecs, demux and protocols */
#if CONFIG_AVDEVICEavdevice_register_all();
#endifavformat_network_init();signal(SIGINT , sigterm_handler); /* Interrupt (ANSI).    */signal(SIGTERM, sigterm_handler); /* Termination (ANSI).  */show_banner(argc, argv, options);parse_options(NULL, argc, argv, options, opt_input_file);if (!input_filename) {show_usage();av_log(NULL, AV_LOG_FATAL, "An input file must be specified\n");av_log(NULL, AV_LOG_FATAL,"Use -h to get full help or, even better, run 'man %s'\n", program_name);exit(1);}/*******************end动态库加载/网络初始化等**************//*******************start 视频帧显示窗口初始化*******************/if (display_disable) {video_disable = 1;}flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;if (audio_disable)flags &= ~SDL_INIT_AUDIO;else {/* Try to work around an occasional ALSA buffer underflow issue when the* period size is NPOT due to ALSA resampling by forcing the buffer size. */if (!SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE"))SDL_setenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE","1", 1);}if (display_disable)flags &= ~SDL_INIT_VIDEO;if (SDL_Init (flags)) {av_log(NULL, AV_LOG_FATAL, "Could not initialize SDL - %s\n", SDL_GetError());av_log(NULL, AV_LOG_FATAL, "(Did you set the DISPLAY variable?)\n");exit(1);}SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE);SDL_EventState(SDL_USEREVENT, SDL_IGNORE);if (!display_disable) {int flags = SDL_WINDOW_HIDDEN;if (alwaysontop)
#if SDL_VERSION_ATLEAST(2,0,5)flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#elseav_log(NULL, AV_LOG_WARNING, "Your SDL version doesn't support SDL_WINDOW_ALWAYS_ON_TOP. Feature will be inactive.\n");
#endifif (borderless)flags |= SDL_WINDOW_BORDERLESS;elseflags |= SDL_WINDOW_RESIZABLE;#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITORSDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#endifwindow = SDL_CreateWindow(program_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, default_width, default_height, flags);SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");if (window) {renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);if (!renderer) {av_log(NULL, AV_LOG_WARNING, "Failed to initialize a hardware accelerated renderer: %s\n", SDL_GetError());renderer = SDL_CreateRenderer(window, -1, 0);}if (renderer) {if (!SDL_GetRendererInfo(renderer, &renderer_info))av_log(NULL, AV_LOG_VERBOSE, "Initialized %s renderer.\n", renderer_info.name);}}if (!window || !renderer || !renderer_info.num_texture_formats) {av_log(NULL, AV_LOG_FATAL, "Failed to create window or renderer: %s", SDL_GetError());do_exit(NULL);}}/*******************end 视频帧显示窗口初始化*******************//******************* 下载数据/demux/decoder*******************/is = stream_open(input_filename, file_iformat);if (!is) {av_log(NULL, AV_LOG_FATAL, "Failed to initialize VideoState!\n");do_exit(NULL);}event_loop(is);return 0;
}

从上面main函数入口来看,main中大部分是一些init工作,而ffmpeg的核心工作demux/decoder的工作都封装在stream_open中了。
想要摸清ffmpeg的框架,学习思路是追踪音视频数据的流动过程。

  1. 原始数据通过什么模块获取?
  2. 如何通过原始数据匹配到对应的demuxer ,如: ts, mp4, mov, avi等?
  3. 如何通过demuxer后的audio/video数据匹配到对应的decoder, 如:h264, vp9, aac等?
  4. ffmpeg中数据流转的驱动模型是什么样的?也就是数据是从什么地方驱动开始下载/demux/decode ?
static VideoState *stream_open(const char *filename,const AVInputFormat *iformat)
{VideoState *is = av_mallocz(sizeof(VideoState));is->last_video_stream = is->video_stream = -1;is->last_audio_stream = is->audio_stream = -1;is->last_subtitle_stream = is->subtitle_stream = -1;is->filename = av_strdup(filename);is->iformat = iformat;is->ytop    = 0;is->xleft   = 0;is->continue_read_thread = SDL_CreateCond();/* start video display */frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1);frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0);frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1);packet_queue_init(&is->videoq);packet_queue_init(&is->audioq);packet_queue_init(&is->subtitleq);init_clock(&is->vidclk, &is->videoq.serial);init_clock(&is->audclk, &is->audioq.serial);init_clock(&is->extclk, &is->extclk.serial);is->audio_clock_serial = -1;startup_volume = av_clip(startup_volume, 0, 100);startup_volume = av_clip(SDL_MIX_MAXVOLUME * startup_volume / 100, 0, SDL_MIX_MAXVOLUME);is->audio_volume = startup_volume;is->muted = 0;is->av_sync_type = av_sync_type;is->read_tid     = SDL_CreateThread(read_thread, "read_thread", is);if (!is->read_tid) {av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %s\n", SDL_GetError());
fail:stream_close(is);return NULL;return is;
}

从上面的代码看,stream_open主要是用于初始化VideoState这个结构体,初始化audio/video/subtitile解码后数据的buffer queque。结构体的具体细节先暂不深入。
另外在stream_open中创建了一个thread: read_thread, 看来干活的是这个了。。。

static int read_thread(void *arg)
{VideoState *is = arg;AVPacket *pkt = av_packet_alloc();AVFormatContext *ic = avformat_alloc_context();//启动数据下载模块,demux模块avformat_open_input(&ic, is->filename, is->iformat, &format_opts);is->ic = ic;av_format_inject_global_side_data(ic);if (find_stream_info) {AVDictionary **opts = setup_find_stream_info_opts(ic, codec_opts);avformat_find_stream_info(ic, opts);}if (!video_disable)st_index[AVMEDIA_TYPE_VIDEO] =av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);if (!audio_disable)st_index[AVMEDIA_TYPE_AUDIO] =av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,st_index[AVMEDIA_TYPE_AUDIO],st_index[AVMEDIA_TYPE_VIDEO],NULL, 0);if (!video_disable && !subtitle_disable)st_index[AVMEDIA_TYPE_SUBTITLE] =av_find_best_stream(ic, AVMEDIA_TYPE_SUBTITLE,st_index[AVMEDIA_TYPE_SUBTITLE],(st_index[AVMEDIA_TYPE_AUDIO] >= 0 ?st_index[AVMEDIA_TYPE_AUDIO] :st_index[AVMEDIA_TYPE_VIDEO]),NULL, 0);// 启动decoder threadstream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]);stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);stream_component_open(is, st_index[AVMEDIA_TYPE_SUBTITLE]);for (;;) {if (is->abort_request)break;if (is->paused != is->last_paused) {is->last_paused = is->paused;if (is->paused)is->read_pause_return = av_read_pause(ic);elseav_read_play(ic);}//读取demux后的数据,并存入audio/video/subtitle的buffer queue中av_read_frame(ic, pkt);/* check if packet is in play range specified by user, then queue, otherwise discard */stream_start_time = ic->streams[pkt->stream_index]->start_time;pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;pkt_in_play_range = duration == AV_NOPTS_VALUE ||(pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *av_q2d(ic->streams[pkt->stream_index]->time_base) -(double)(start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000<= ((double)duration / 1000000);if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {packet_queue_put(&is->audioq, pkt);} else if (pkt->stream_index == is->video_stream && pkt_in_play_range&& !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {packet_queue_put(&is->videoq, pkt);} else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {packet_queue_put(&is->subtitleq, pkt);} else {av_packet_unref(pkt);}}..............;return 0;
}

从上面read_thread代码看,主要是通过avformat_open_input(…)启动数据下载和demux,再通过stream_component_open(…) 启动audio/video/subtitle的decoder。最后启动死循环通过av_read_frame(…) 读取demux出来的audio/video/subtitle存入对应的buffer queue供decoder使用。
下面再看看decoder是如何消耗数据的?以及decoder完成的数据缓存在什么地方?

static int stream_component_open(VideoState *is, int stream_index)
{AVFormatContext *ic = is->ic;const AVCodec *codec;const char *forced_codec_name = NULL;AVDictionary *opts = NULL;const AVDictionaryEntry *t = NULL;int sample_rate;AVChannelLayout ch_layout = { 0 };int ret = 0;int stream_lowres = lowres;AVCodecContext *avctx = avcodec_alloc_context3(NULL);avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);const AVCodec * codec = avcodec_find_decoder(avctx->codec_id);avctx->codec_id = codec->id;AVDictionary *opts = filter_codec_opts(codec_opts, avctx->codec_id, ic, ic->streams[stream_index], codec);avcodec_open2(avctx, codec, &opts));is->eof = 0;ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;switch (avctx->codec_type) {case AVMEDIA_TYPE_AUDIO:/* prepare audio output */audio_open(is, &ch_layout, sample_rate, &is->audio_tgt))decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread))decoder_start(&is->auddec, audio_thread, "audio_decoder", is))static SDL_AudioDeviceID audio_dev;SDL_PauseAudioDevice(audio_dev, 0);break;case AVMEDIA_TYPE_VIDEO:is->video_stream = stream_index;is->video_st = ic->streams[stream_index];decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread))decoder_start(&is->viddec, video_thread, "video_decoder", is));is->queue_attachments_req = 1;break;case AVMEDIA_TYPE_SUBTITLE:is->subtitle_stream = stream_index;is->subtitle_st = ic->streams[stream_index];decoder_init(&is->subdec, avctx, &is->subtitleq, is->continue_read_thread))decoder_start(&is->subdec, subtitle_thread, "subtitle_decoder", is))break;default:break;}....................;return ret;
}

ffplay内部结构图

通过stream_component_open函数的代码可以看出,先初始化decoder,再创建thread。
至此, ffplay的大致脉络已经摸清楚了,具体看下图:
ffplay_thread
结构关系图:
ff_state
后面的文章继续深入分析demuxer和decoder。

相关文章:

【FFMPEG源码分析】从ffplay源码摸清ffmpeg框架(一)

ffplay入口 ffmpeg\fftools\ffplay.c int main(int argc, char **argv) {/*******************start 动态库加载/网络初始化等**************/int flags;VideoState *is;init_dynload();av_log_set_flags(AV_LOG_SKIP_REPEATED);parse_loglevel(argc, argv, options);/* regis…...

C++蓝桥杯 基础练习,高精度加法,输入两个整数a和b,输出这两个整数的和。a和b都不超过100位。

C蓝桥杯 基础练习&#xff0c;高精度加法 问题描述 输入两个整数a和b&#xff0c;输出这两个整数的和。a和b都不超过100位。 算法描述 由于a和b都比较大&#xff0c;所以不能直接使用语言中的标准数据类型来存储。对于这种问题&#xff0c;一般使用数组来处理。   定义一…...

MySQL面试题:SQL语句的基本语法

MySQL目录一、数据库入门1. 数据管理技术的三个阶段2. 关系型数据库与非关系型数据库3. 四大非关系型数据库a. 基于列的数据库&#xff08;column-oriented&#xff09;b. 键值对存储&#xff08;Key-Value Stores&#xff09;c. 文档存储&#xff08;Document Stores&#xff…...

Fluid-数据编排能力原理解析

前言本文对Fluid基础功能-数据编排能力进行原理解析。其中涉及到Fluid架构和k8s csi driver相关知识。建议先了解相关概念&#xff0c;为了便于理解&#xff0c;本文使用JuiceFS作为后端runtime引擎。原理概述Fuild数据编排能力&#xff0c;主要是在云原生环境中&#xff0c;能…...

并发线程、锁、ThreadLocal

并发编程并发编程Java内存模型&#xff08;JMM&#xff09;并发编程核心问题—可见性、原子性、有序性volatile关键字原子性原子类CAS(Compare-And-Swap 比较并交换)ABA问题Java中的锁乐观锁和悲观锁可重入锁读写锁分段锁自旋锁共享锁/独占锁公平锁/非公平锁偏向锁/轻量级锁/重…...

CMMI-结项管理

结项管理&#xff08;ProjectClosing Management, PCM&#xff09;是指在项目开发工作结束后&#xff0c;对项目的有形资产和无形资产进行清算&#xff1b;对项目进行综合评估&#xff1b;总结经验教训等。结项管理过程域是SPP模型的重要组成部分。本规范阐述了结项管理的规程&…...

网络通信协议是什么?

网络通信基本模式 常见的通信模式有如下2种形式&#xff1a;Client-Server(CS) 、 Browser/Server(BS) 实现网络编程关键的三要素 IP地址&#xff1a;设备在网络中的地址&#xff0c;是唯一的标识。 端口&#xff1a;应用程序在设备中唯一的标识。 协议: 数据在网络中传输的…...

阶段5:Java分布式与微服务实战

目录 第33-34周 Spring Cloud电商实战 一、Eureka-server模块开发 1、引入依赖 2、配置文件 3、启动注解 一、Eureka-server模块开发 第33-34周 Spring Cloud电商实战 一、Eureka-server模块开发 1、引入依赖 父项目依赖&#xff1a;cloud-mall-practice springboot的…...

我的创作纪念日

目录 机缘 收获 日常 憧憬 机缘 其实本来从大一上学期后半段(2017)就开始谢谢零星的博客&#xff0c;只不过当时是自己用hexo搭建了一个小网站&#xff0c;还整了个域名&#xff1a;jiayoudangdang.top&#xff0c;虽然这个早就过期&#xff1b; 后来发现了CSDN&#xff…...

Qml学习——动态加载控件

最近在学习Qml&#xff0c;但对Qml的各种用法都不太熟悉&#xff0c;总是会搞忘&#xff0c;所以写几篇文章对学习过程中的遇到的东西做一个记录。 学习参考视频&#xff1a;https://www.bilibili.com/video/BV1Ay4y1W7xd?p1&vd_source0b527ff208c63f0b1150450fd7023fd8 目…...

设计模式之职责链模式

什么是职责链模式 职责链模式是避免请求发送者与接受者耦合在一起&#xff0c;让多个对象都可以接受到请求&#xff0c;从而将这些对象连接成一条链&#xff0c;并且沿着这条链传递请求&#xff0c;直到有对象处理为止。     职责链模式包含以下几个角色&#xff1a;    …...

MySQL入门篇-MySQL 8.0 延迟复制

备注:测试数据库版本为MySQL 8.0 这个blog我们来聊聊MySQL 延迟复制 概述 MySQL的复制一般都很快&#xff0c;虽然有时候因为 网络原因、大事务等原因造成延迟&#xff0c;但是这个无法人为控制。 生产中可能会存在主库误操作&#xff0c;导致数据被删除了&#xff0c;Oracl…...

FPGA时序约束与分析 --- 实例教程(1)

注意&#xff1a; 时序约束辅助工具或者相关的TCL命令&#xff0c;都必须在 open synthesis design / open implemention design 后才能有效运行。 1、时序约束辅助工具 2、查看相关时序信息 3、一般的时序约束顺序 1、 时序约束辅助工具&#xff08;1&#xff09;时序约束编辑…...

go深拷贝和浅拷贝

1、深拷贝&#xff08;Deep Copy&#xff09;拷贝的是数据本身&#xff0c;创造一个样的新对象&#xff0c;新创建的对象与原对象不共享内存&#xff0c;新创建的对象在内存中开辟一个新的内存地址&#xff0c;新对象值修改时不会影响原对象值。既然内存地址不同&#xff0c;释…...

linux网络系统层面的配置、管理及操作命令汇总

前几篇文章一一介绍了LINUX进程管理控制命令&#xff0c;关于linux系统中的软件包管理内容等&#xff0c;作为一名运维工程师&#xff0c;前两天刚处理了一起linux网络层面的情况&#xff0c;那么今天这篇文章就以linux网络层面为主题吧。当说到linux网络系统层面&#xff0c;e…...

R数据分析:孟德尔随机化中介的原理和实操

中介本身就是回归&#xff0c;基本上我看到的很多的调查性研究中在中介分析的方法部分都不会去提混杂&#xff0c;都是默认一个三角形画好&#xff0c;中介关系就算过去了&#xff0c;这里面默认的逻辑就是前两步回归中的混杂是一样的&#xff0c;计算中介效应的时候就自动消掉…...

【C++】 类和对象 (下)

文章目录&#x1f4d5;再谈构造函数1. 构造函数体赋值2. 初始化列表3. explicit 关键字&#x1f4d5;static 成员1. 概念2. static 成员变量3. static 成员函数&#x1f4d5; 友元1. 友元函数2. 友元类&#x1f4d5;内部类&#x1f4d5;编译器优化&#x1f4d5;再谈构造函数 1…...

asp获取毫秒时间戳的方法 asp获取13位时间戳的方案

一、背景。时间戳就是计算当前与"1970-01-01 08:00:00"的时间差&#xff0c;在asp中通常是使用Datediff函数来计算两个日期差&#xff0c;代码&#xff1a;timestamp Datediff("s", "1970-01-01 08:00:00",now)返回结果&#xff1a;1675951060可…...

Python基础篇(十五)-- Python程序接入MySQL数据库

程序运行时&#xff0c;数据都在内存中&#xff0c;程序终止时&#xff0c;需要将数据保存到磁盘上。为了便于程序保存和读取&#xff0c;并能直接通过条件快速查询到指定数据&#xff0c;数据库(Database)应运而生&#xff0c;本篇主要学习使用Python操作数据库&#xff0c;在…...

程序员不得不知道的 API 接口常识

说实话&#xff0c;我非常希望自己能早点看到本篇文章&#xff0c;大学那个时候懵懵懂懂&#xff0c;跟着网上的免费教程做了一个购物商城就屁颠屁颠往简历上写。 至今我仍清晰地记得&#xff0c;那个电商教程是怎么定义接口的&#xff1a; 管它是增加、修改、删除、带参查询…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...