从学龄前开始解读FFMPEG代码 之 avformat_find_stream_info函数 四
这是avformat_find_stream_info函数解读文章的最后一篇,在最后这一篇解读文章中会对最后的两次大循环进行解读。最后的两次循环,是为了find stream这样一个操作做好兜底的准备,将没有做好初始化的属性最后进行一次检查和补充,完成函数的全部任务。
在进入第五次循环之前,find stream函数还进行了一个时间参数的计算以及做了一部分提前设置
if (probesize)
estimate_timings(ic, old_offset);
av_opt_set(ic, "skip_clear", "0", AV_OPT_SEARCH_CHILDREN);
if (ret >= 0 && ic->nb_streams)
/* We could not have all the codec parameters before EOF. */
ret = -1;
这里使用了estimate_timings(ic, old_offset) 函数来帮助计算时间有关的参数,之后将skip_clear设置到avctx ic中,再将之前可能由于codec设置失败所改变的ret复位到-1s
在estimate_timings函数中,其实也进行了所有stream的循环和操作:
static void estimate_timings(AVFormatContext *ic, int64_t old_offset)
{
int64_t file_size;
/* get the file size, if possible */
if (ic->iformat->flags & AVFMT_NOFILE) {
file_size = 0;
} else {
file_size = avio_size(ic->pb);
file_size = FFMAX(0, file_size);
}
if ((!strcmp(ic->iformat->name, "mpeg") ||
!strcmp(ic->iformat->name, "mpegts")) &&
file_size && (ic->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
/* get accurate estimate from the PTSes */
estimate_timings_from_pts(ic, old_offset);
ic->duration_estimation_method = AVFMT_DURATION_FROM_PTS;
} else if (has_duration(ic)) {
/* at least one component has timings - we use them for all
* the components */
fill_all_stream_timings(ic);
ic->duration_estimation_method = AVFMT_DURATION_FROM_STREAM;
} else {
/* less precise: use bitrate info */
estimate_timings_from_bit_rate(ic);
ic->duration_estimation_method = AVFMT_DURATION_FROM_BITRATE;
}
update_stream_timings(ic);
{
int i;
AVStream av_unused *st;
for (i = 0; i < ic->nb_streams; i++) {
st = ic->streams[i];
if (st->time_base.den)
av_log(ic, AV_LOG_TRACE, "stream %d: start_time: %0.3f duration: %0.3f\n", i,
(double) st->start_time * av_q2d(st->time_base),
(double) st->duration * av_q2d(st->time_base));
}
av_log(ic, AV_LOG_TRACE,
"format: start_time: %0.3f duration: %0.3f bitrate=%"PRId64" kb/s\n",
(double) ic->start_time / AV_TIME_BASE,
(double) ic->duration / AV_TIME_BASE,
(int64_t)ic->bit_rate / 1000);
}
}
可以看见,在该函数中主要是通过pts,或者已知的stream的时长,或者通过bitrate计算来完成avctx中关于时间的参数设置,之后再对ic中所有的streams进行了一次循环,但这里的循环并没有做相关计算和操作,只是进行了时间参数的信息打印,具体的时间参数计算函数则要到estimate_timings_from_pts,fill_all_stream_timings,还有estimate_timings_from_bit_rate里面去寻找了,由于是find stream函数讲解,不做过多的音视频计算说明的讲解展开。
回到find stream中,接下来是第五次循环
for (i = 0; i < ic->nb_streams; i++) {
const char *errmsg;
st = ic->streams[i];
/* if no packet was ever seen, update context now for has_codec_parameters */
if (!st->internal->avctx_inited) {
if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
st->codecpar->format == AV_SAMPLE_FMT_NONE)
st->codecpar->format = st->internal->avctx->sample_fmt;
ret = avcodec_parameters_to_context(st->internal->avctx, st->codecpar);
if (ret < 0)
goto find_stream_info_err;
}
if (!has_codec_parameters(st, &errmsg)) {
char buf[256];
avcodec_string(buf, sizeof(buf), st->internal->avctx, 0);
av_log(ic, AV_LOG_WARNING,
"Could not find codec parameters for stream %d (%s): %s\n"
"Consider increasing the value for the 'analyzeduration' and 'probesize' options\n",
i, buf, errmsg);
} else {
ret = 0;
}
}
compute_chapters_end(ic);
第五次循环首先对每个stream的avctx_inited标识位进行检查,在没有进行标识,没有进行packet检查时,就需要更新avctx了,对于音频格式且未设置forma时,设置好format并且使用avcodec_parameters_to_context设置好当前遍历到的stream的编解码参数;
之后,再次使用has_codec_parameters来检查stream中是否已经设置好各项参数,在没有设置好的情况下进行警告。
在完成第五次循环后,使用了compute_chapters_end来设置好AVChapter章节的end末尾属性。在end属性的的设置过程中也用到了之前设置好的avctx上下文中的各项参数。
在最后一次循环里,则是通过stream的内部codec参数来做好stream自身剩下一些参数的补充和设置。
/* update the stream parameters from the internal codec contexts */
for (i = 0; i < ic->nb_streams; i++) {
st = ic->streams[i];
if (st->internal->avctx_inited) {
int orig_w = st->codecpar->width;
int orig_h = st->codecpar->height;
ret = avcodec_parameters_from_context(st->codecpar, st->internal->avctx);
if (ret < 0)
goto find_stream_info_err;
#if FF_API_LOWRES
// The decoder might reduce the video size by the lowres factor.
if (st->internal->avctx->lowres && orig_w) {
st->codecpar->width = orig_w;
st->codecpar->height = orig_h;
}
#endif
}
#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS
ret = avcodec_parameters_to_context(st->codec, st->codecpar);
if (ret < 0)
goto find_stream_info_err;
#if FF_API_LOWRES
// The old API (AVStream.codec) "requires" the resolution to be adjusted
// by the lowres factor.
if (st->internal->avctx->lowres && st->internal->avctx->width) {
st->codec->lowres = st->internal->avctx->lowres;
st->codec->width = st->internal->avctx->width;
st->codec->height = st->internal->avctx->height;
}
#endif
if (st->codec->codec_tag != MKTAG('t','m','c','d')) {
st->codec->time_base = st->internal->avctx->time_base;
st->codec->ticks_per_frame = st->internal->avctx->ticks_per_frame;
}
st->codec->framerate = st->avg_frame_rate;
if (st->internal->avctx->subtitle_header) {
st->codec->subtitle_header = av_malloc(st->internal->avctx->subtitle_header_size);
if (!st->codec->subtitle_header)
goto find_stream_info_err;
st->codec->subtitle_header_size = st->internal->avctx->subtitle_header_size;
memcpy(st->codec->subtitle_header, st->internal->avctx->subtitle_header,
st->codec->subtitle_header_size);
}
// Fields unavailable in AVCodecParameters
st->codec->coded_width = st->internal->avctx->coded_width;
st->codec->coded_height = st->internal->avctx->coded_height;
st->codec->properties = st->internal->avctx->properties;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
st->internal->avctx_inited = 0;
}
进入循环后,检查avctx_inited标识位,对于已经完成初始化的stream,先使用avcodec_parameters_from_context,根据已经设置好的AVCodecContext来设置好当前遍历到的stream中的parameter各项属性,也设置好宽高属性。
之后,使用avcodec_parameters_to_context来反向地通过codecpar来设置好编解码器codec的各项属性,也就是整体属性数据流程是 avctx–>codecpar–>codec来变化的。
要注意到这里ffmpeg是加了版本控制的,对于比较新的版本(LIBAVFORMAT_VERSION_MAJOR>=59)来说这一段代码是不需要的。在最后将遍历到的stream的inited属性重置为0,完成设置工作。
在函数最后,是上面的函数实现出现异常时的跳转以及正常的收尾工作
find_stream_info_err:
for (i = 0; i < ic->nb_streams; i++) {
st = ic->streams[i];
if (st->info)
av_freep(&st->info->duration_error);
avcodec_close(ic->streams[i]->internal->avctx);
av_freep(&ic->streams[i]->info);
av_bsf_free(&ic->streams[i]->internal->extract_extradata.bsf);
av_packet_free(&ic->streams[i]->internal->extract_extradata.pkt);
}
if (ic->pb)
av_log(ic, AV_LOG_DEBUG, "After avformat_find_stream_info() pos: %"PRId64" bytes read:%"PRId64" seeks:%d frames:%d\n",
avio_tell(ic->pb), ic->pb->bytes_read, ic->pb->seek_count, count);
return ret;
收尾工作中实际上也经历了一次循环,将所有stream进行了遍历,关闭了在find stream工作中所用到的各项codec,内存空间以及packet等等。最后进行了输出打印,并返回处理结果ret
avformat_find_stream_info的四篇解读总算是写完了。总体来说,这个函数的任务还是非常重要且复杂的。整个stream中的各项参数在最开始时几乎都是空的,需要做填空题一个个填上,open_input只是读取文件头,并保存一部分的数据,但这之后AVStream中的数据大部分都是没有完成填空的。
find_stream_info则是负责来将各项重要属性字段完成填空,在open_input的时候,已经解析到很多数据了,所以可以直接来完成填空,填空完成直接成功返回。而在需要更深入的数据,比如pix_fmt等等数据属性时,就需要进行一部分数据的解码(一旦涉及到解码,那就可能出现各种特殊的异常情况,比如EOF,比如缓存中数据需要flushing等等),这就导致该函数的功能复杂,循环也多。同时,start_time,帧率等等数据也会在这个函数里完成计算。该函数包含的很多子函数也涉及到很多音视频的基础知识,对于学习音视频原理和理解ffmpeg来说都值得往下详细展开和深入解读。不过这里空间太小,我写不下(费马式开摆!)
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p
有几种方法:first_or_create_by、find_or_create_by等,它们的工作原理是:与数据库对话以尝试找到我们想要的东西如果我们找不到,就自己做保存到数据库显然,并发调用这些方法可能会使两个线程都找不到它们想要的东西,并且在第3步中一个线程会意外失败。似乎更好的解决方案是,创建或查找即:提前在您的数据库中创建合理的唯一性约束。如果你想保存一些东西,就保存它如果有效,那就太好了。如果它因为RecordNotUnique异常而无法工作,它已经存在,太好了,加载它那么在什么情况下我想使用Rails内置的东西而不是我自己的(看起来更可靠)create_or_find?
我正在尝试使用以下代码通过将ffmpeg实用程序作为子进程运行并获取其输出并解析它来确定视频分辨率:IO.popen'ffmpeg-i'+path_to_filedo|ffmpegIO|#myparsegoeshereend...但是ffmpeg输出仍然连接到标准输出并且ffmepgIO.readlines是空的。ffmpeg实用程序是否需要一些特殊处理?或者还有其他方法可以获得ffmpeg输出吗?我在WinXP和FedoraLinux下测试了这段代码-结果是一样的。 最佳答案 要跟进mouviciel的评论,您需要使用类似pope
-if!request.path_info.include?'A'%{:id=>'A'}"Text"-else"Text"“文本”写了两次。我怎样才能只写一次并同时检查path_info是否包含“A”? 最佳答案 有两种方法可以做到这一点。使用部分,或使用content_forblock:如果“文本”较长,或者是一个重要的子树,您可以将其提取到一个部分。这会使您的代码变干一点。在给出的示例中,这似乎有点矫枉过正。在这种情况下更好的方法是使用content_forblock,如下所示:-if!request.path_info.inc
我有一些模型需要在它们上面放置自定义查找条件。例如,如果我有一个联系人模型,每次调用Contact.find时,我都想限制返回的联系人只属于正在使用的帐户。我通过Google找到了这个(我对其进行了一些自定义):defself.find(*args)with_scope(:find=>{:conditions=>"account_id=#{$account.id}"})dosuper(*args)endend这很好用,除了少数情况下account_id不明确,所以我将其调整为:defself.find(*args)with_scope(:find=>{:conditions=>"#{s
我有一个任务列表(名称、starts_at),我试图在每日View中显示它们(就像iCal)。deftodays_tasks(day)Task.find(:all,:conditions=>["starts_atbetween?and?",day.beginning,day.ending]end我不知道如何将Time.now(例如“2009-04-1210:00:00”)动态转换为一天的开始(和结束),以便进行比较。 最佳答案 deftodays_tasks(now=Time.now)Task.find(:all,:conditio
我有一个哈希条目数组,并希望根据传递给函数的参数进行过滤。如果散列中有三个值,A、B和C,我想做类似的事情:data=[{A:'a1',B:'b1',C:'c1'},{A:'a1',B:'b2',C:'c1'},{A:'a1',B:'b2',C:'c2'},{A:'a2',B:'b1',C:'c1'},{A:'a2',B:'b2',C:'c1'}]data.find_all{|d|d[:A].include?params[:A]}.find_all{|d|d[:B].include?params[:B]}.find_all{|d|d[:C].include?params[:C]}找到所
我正在使用来自learn-rails.com的“学习RubyOnRails”pdf书。我在第13章“配置”。我们应该在哪里执行命令:“railsgeneratefigaro:install”在第12章中,我们安装了figarogem:“我们已经在Gemfile中安装了figarogem并运行bundleinstall。”当我运行该命令时,我得到:“找不到生成器figaro:install。”我开始搜索类似的问题,我确实在这里找到了这个问题:railsgenerate-"Couldnotfindgenerator"他们被建议在命令中也包含“迁移”。我将它包含在我的命令中并让它做某事,但我
问题你能用thin吗?与ActionController::Live实现服务器端事件(SSE)和长轮询?如果是,怎么办?上下文虽然标题是HowtogetRails4ActionController::LivestreamingworkingwithThinandRuby2?AndhowdoThinandPumascalewithlivestreaming?的重复,OP通过问两个问题混淆了水域,这个问题从未得到回答。许多其他帖子建议您可以使用thin对于服务器端事件(sse),如果您通过execthinstart--threaded启动它:DoesHerokusupportActionC