ffplay debug 01
从这里开始,我将实时记录debug ffplay的过程,也算是一种学习了吧。(第一次debug这种大项目,对我来说很大了,快4000行了欸,搞不好中途就放弃了哈哈)
/* Called from the main */
int main(int argc, char **argv)
{
int flags, ret;
VideoState *is;
init_dynload();
一开始,就不知道这个VideoState
是干啥的。
init_dynload
也不知道欸。
debug是在vscode上,但翻阅代码就在github上了。毕竟我debug技巧没那么熟练。gdb用不好,vscode也是,vs also。
我cao了呀,这VideoState
有100行的结构体,算了还是具体调试时再看吧。
第一个,init_dynload
,听起来像 initialize dynamic load,初始化动态加载,分配内存用的吗。
// cmdutils.c
void init_dynload(void)
{
#if HAVE_SETDLLDIRECTORY && defined(_WIN32)
/* Calling SetDllDirectory with the empty string (but not NULL) removes the
* current working directory from the DLL search path as a security pre-caution. */
SetDllDirectory("");
#endif
}
这好像是特定win平台时重置dll的路径一样。
好吧HAVE_SETDLLDIRECTORY
在config.h
中为0,这说明我的平台上是没有这个func的吧。
那这个func就什么都没干喽。
ok,下一个。
av_log_set_flags(AV_LOG_SKIP_REPEATED);
parse_loglevel(argc, argv, options);
av_log_set_flags
设置log flags。
AV_LOG_SKIP_PEREATED
用于将av_log
所打的日志中重复的log跳过。
哦,对了。我会尝试同步自己实现一遍ffplay。不过只实现本地音频播放的功能,用cpp。该项目请参考link。
static const OptionDef options[] = {
CMDUTILS_COMMON_OPTIONS
{ "x", OPT_TYPE_FUNC, OPT_FUNC_ARG, { .func_arg = opt_width }, "force displayed width", "width" },
options
是OptionDef
的一个数组,里面有超~~多的选项,估计是为了分析args来映射使用的功能。
void parse_loglevel(int argc, char **argv, const OptionDef *options)
{
int idx = locate_option(argc, argv, options, "loglevel");
char *env;
check_options(options);
if (!idx)
idx = locate_option(argc, argv, options, "v");
if (idx && argv[idx + 1])
opt_loglevel(NULL, "loglevel", argv[idx + 1]);
idx = locate_option(argc, argv, options, "report");
env = getenv_utf8("FFREPORT");
if (env || idx) {
FILE *report_file = NULL;
init_report(env, &report_file);
if (report_file) {
int i;
fprintf(report_file, "Command line:\n");
for (i = 0; i < argc; i++) {
dump_argument(report_file, argv[i]);
fputc(i < argc - 1 ? ' ' : '\n', report_file);
}
fflush(report_file);
}
}
freeenv_utf8(env);
idx = locate_option(argc, argv, options, "hide_banner");
if (idx)
hide_banner = 1;
}
我勒个龟龟,直接把所有代码贴出来还是太多了,我之后还是调着贴吧。
for (i = 1; i < argc; i++) {
const char *cur_opt = argv[i];
if (*cur_opt++ != '-')
continue;
靠,我不知道为啥locate_option
这里cur_opt指向的是argv[1]的第二个字符。argv[1]为test.mp3,结果cur_opt被赋值为est.mp3?我也没开编译器优化选项啊。为啥
嗯,好吧,locate_option
这里就是查看你参数里有没有-xxx
这类的选项,我因为只传的test.mp3
所以这里就直接退出了。
我只调试test.mp3的情况的代码哦,音频的部分也暂且先不看,毕竟我最初只是为了解决ffmpeg流式解码的问题。当然,视频的部分等到时候想要做视频库的时候再看哈。(对了,我调试的的代码是ffmpeg官网上下的7.1源码,并不是github上最新的(main分支的),所以不同版本的具体细节上还会不一样的)。
check_options
用来检索options的定义是否正确,这个就不用管。
然后是开始检查是否第一步的locate_option
检查出东西了,因为我没有传-xxx
的选项,所以实际上locate_option
的调用会一直是0。
欸油,你这写的也不行嘛哈哈哈。应该直接检查我是否有-xxx
的选项,没有就直接不调用locate_option
就行了哈哈。不过它这么写或许会有模块化或逻辑化的考量,我不知道就是了嘛。
getenv_utf8
是获取环境变量的,不过我这里它获取的FFREPORT
为0就是了。
很好,parse_loglevel
这个func根本没有起到任何作用。
然后两行是注册所有codec, demux protocols的,和网络的初始化(用不到)。
之后是设了俩signal的处理函数,处理退出程序是返回123。
之后show_banner
就打印了一串版本信息。
之后对环境进行各种设定,初始化SDL,以及有段代码是为了解决ALSA的问题。
SDL的统统跳过,好啦,终于到第一个重头戏了。
is = stream_open(input_filename, file_iformat);
if (!is) {
av_log(NULL, AV_LOG_FATAL, "Failed to initialize VideoState!\n");
do_exit(NULL);
}
static VideoState *stream_open(const char *filename,
const AVInputFormat *iformat)
{
VideoState *is;
is = av_mallocz(sizeof(VideoState));
if (!is)
return NULL;
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); // copy string
if (!is->filename)
goto fail;
is->iformat = iformat;
is->ytop = 0;
is->xleft = 0;
stream_open
先是给VideoState
分配了内存。
然后是初始化。
/* start video display */
if (frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1) < 0)
goto fail;
if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
goto fail;
if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
goto fail;
这里调用三个frame_queue_init
,第一个是初始化video的我不看,第二个是初始化subtitle(字幕)的我也不看,我只看第三个初始化audio的哈。
static int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last);
进来后直接俩队列FrameQueue
和PacketQueue
,然后是队列的大小max_size
,以及是否保留最后一帧keep_last
。
typedef struct PacketQueue {
AVFifo *pkt_list; // AVFifo,ffmpeg提供的一个动态队列(可切换为固定容量)。用于实际存取包数据
int nb_packets; // 包数量
int size; // 容量
int64_t duration; // 总包播放的时间
int abort_request; // 终止请求,用于暂停寻道之类
int serial; // 这个还不太清楚,是流的序号吗?
SDL_mutex *mutex; // 锁
SDL_cond *cond; // 条件变量
} PacketQueue;
/* Common struct for handling all types of decoded data and allocated render buffers. */
typedef struct Frame {
AVFrame *frame; // 解码后的帧
AVSubtitle sub; // 字幕
int serial; // 当前帧的序列号
double pts; /* presentation timestamp for the frame */
double duration; /* estimated duration of the frame */
int64_t pos; /* byte position of the frame in the input file */
int width; // 宽度(像素)
int height; // 高度(像素)
int format; // 像素格式
AVRational sar; // 采样宽高比 sample aspect ratio
int uploaded; // 是否上传到GPU,以此来硬件加速
int flip_v; // 是否垂直翻转
} Frame;
typedef struct FrameQueue {
Frame queue[FRAME_QUEUE_SIZE]; // 真正存数据的数组
int rindex; // 读索引
int windex; // 写索引
int size; // 当前大小
int max_size; // 最大容量
int keep_last; // 是否保留最后一项
int rindex_shown; // 当前显示的帧的索引
SDL_mutex *mutex; // 锁
SDL_cond *cond; // 条件变量
PacketQueue *pktq; // 未解码的包队列
} FrameQueue;
相关的结构体的内容我都标在注释里了。
frame_queue_init
就是对FrameQueue
进行初始化的,该清空的清空,alloc的alloc。
f->keep_last = !!keep_last;
这句话看起来是不是挺迷惑的,其实就是将keep_last
的任意整数值全转化为0或1。所以为什么不一开始就用bool嘞?(为了扩展性吗!?)
if (packet_queue_init(&is->videoq) < 0 ||
packet_queue_init(&is->audioq) < 0 ||
packet_queue_init(&is->subtitleq) < 0)
goto fail;
帧分配完然后是包分配,虽然我觉得应该先包再帧吧。
if (!(is->continue_read_thread = SDL_CreateCond())) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
goto fail;
}
is
就是之前那个VideoState
,反正把它当作全局变量就可。
continue_read_thread
是条件变量,至少字面意思翻译是继续读线程。
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;
typedef struct Clock {
double pts; /* clock base */
double pts_drift; /* clock base minus time at which we updated the clock */
double last_updated; // 上次更新时间
double speed; // 速率
int serial; /* clock is based on a packet with this serial */
int paused; // 是否被暂停
int *queue_serial; /* pointer to the current packet queue serial, used for obsolete clock detection */
} Clock;
Clock
,每一个包都有一个(Clock
的serial
是packet_serial
)。
Clock
正常初始化。
剩下的也是对VideoState
中的一些其他变量进行初始化也没什么好看的。
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;
}
open_stream
的最后一部分,是创建并执行了一个read_thread
函数的线程,read_thread
的内容待会儿再看(其实是明天了吧,今天已经好累了。。。)。
至此,整个open_stream
结束。可以看到open_stream
就干了两件事,初始化,和read_thread
,这个可能就是直接从test.mp3
中读数据到VideoState
了吧。嘛,也算初始化的部分。
好了,明天再看read_thread
的部分,今天就先到这儿。