技术标签: ffmpeg 转换flv压缩大小
用FFMPEG SDK进行视频转码压缩的时候,转码成功后去看视频的内容,发现音视频是不同步的。这个的确是一个恼火的事情。我在用FFMPEG SDK做h264格式的FLV文件编码Filter的时候就碰到了这个问题。经过研究发现,FFMPEG SDK写入视频的时候有两个地方用来控制写入的时间戳,一个是AvPacket, 一个是AvFrame。 在调用avcodec_encode_video的时候需要传入AvFrame的对象指针,也就是传入一帧未压缩的视频进行压缩处理,AvFrame包含一个pts的参数,这个参数就是当前帧将来在还原播放的时候的时间戳。而AvPacket里面也有pts,还有dts。说起这个就必须要说明一下I,P,B三种视频压缩帧。I帧就是关键帧,不依赖于其他视频帧,P帧是向前预测的帧,只依赖于前面的视频帧,而B帧是双向预测视频帧,依赖于前后视频帧。由于B帧的存在,因为它是双向的,必须知道前面的视频帧和后面的视频帧的详细内容后,才能知道本B帧最终该呈现什么图像。而pts和dts两个参数就是用来控制视频帧的显示和解码的顺序。pts就是帧显示的顺序。dts就是帧被读取进行解码的顺序。如果没有B帧存在,dts和pts是相同的。反之,则是不相同的。关于这个的详细介绍可以参考一下mpeg的原理。再说说AvPacket中包含的pts和dts两个到底该设置什么值?pts和dts需要设置的就是视频帧解码和显示的顺序。每增加一帧就加一,并不是播放视频的时间戳。
但是实践证明经过rmvb解码的视频有时候并不是固定帧率的,而是变帧率的,这样,如果每压缩一帧,pts和dts加一的方案为导致音视频不同步。那怎么来解决音视频同步的问题呢?请看如下代码段。lTimeStamp 是通过directshow 获取的当前的视频帧的时间戳。m_llframe_index为当前已经经过压缩处理的帧的数量。首先av_rescale计算得到当前压缩处理已经需要处理什么时间戳的视频帧,如果该时间戳尚未到达directshow当前提供的视频帧的时间戳,则将该帧丢弃掉。否则进行压缩操作。并设置AVPacket的pts和dts。这里假设B帧不存在。因为在将来播放的时候视频以我们设定的固定播放帧率进行播放,所以需要根据设定的播放帧率计算得到的视频帧时间戳和directshow提供的当前视频帧的时间戳进行比较,设定是否需要进行实施延缓播放的策略。如果需要延缓播放,则将pts增加步长2,否则以普通速度播放,则设置为1.dts与之相同。__int64 x =av_rescale(m_llframe_index,AV_TIME_BASE*(int64_t)c-
if( x < lTimeStamp ){return TRUE;}m_pVideoFrame2-
m_llframe_index += pkt.duration;}//pkt.pts = lTimeStamp * (__int64)frame_rate.den / 1000;if( c-
请问avcodec_decode_video解码的帧为什么后面的比前面的pts小呢?
请问如下代码:while( av_read_frame(pFormatCtxSource,&packet)<=0 ){if( packet.stream_index==videoStream ){int out_size = avcodec_decode_video(pCodecCtxSource,pFrameSource, &bFrameFinished, packet.data, packet.size); // Decode fromsource frameif( bFrameFinished ){pFrameSource-
在我Decode的时候,第一帧得到的 pFrameSource-
答复:
Because you have B - Framefor example:the Inputsequence for video encoder1 2 3 4 5 6 7I B B P B B ILet's take1,2,3.. as PTS for simplificationthe out sequencefor video encoder ( this equals the decoder sequence)1 4 2 3 7 5 6I P B B I B Byou will get aPTS sequence as following:
1 4 2 3 7 5 6 7 5 6sequence will be same as your question
问:
哦,那是不是我的pts不能这么算呢?而是要每次+1,对吗?那么,packet中的pts和dts要用在什么地方呢?我这样按存储顺序进行解码的话,显示之前是不是要自己进行缓存呢?谢谢!
另外,还有个问题,既然解码的时候,不一定是按照pts递增的顺序得到的解码后的画面,那我在编码图像的时候,是应该按照解码出来的帧顺序进行编码吗?还是把帧先缓存起来,最后严格接照图像的显示顺序来编码呢?用代码来表示,就是:方法一:while(av_read_frame ){ 解码; pts+1; 编码; 输出;}
方法二:while(av_read_frame ){解码;if( pts{
缓存;
}
else
{
编码缓存的帧并写入文件;
}
}
这两个方法,哪个是正确的呢?因为我看到网上的代码都用的是方法一,但是我觉得方法二是对的呀?
答:
the output of decoderis the right order for display because I/P frames will be cacheduntil next I/P
理解:
Decoder 后output的pts 是按正常的顺序,即显示的顺序输出的,如果有B帧,decoder会缓存。
但encoder后,输出的是按dts输出的。
Pts,dts并不是时间戳,而更应该理解为frame的顺序序列号。由于每帧frame的帧率并不一定是一致的,可能会变化的。转换为时间戳的话,应该是(pts*帧率)。为加深理解
可以将pts比做是第pts帧frame,假设每帧的帧率不变的话,则显示的时间戳为(pts*帧率),如果考虑帧率变化的,则要想办法将(pts*当前的帧率)累加到后面。
在tutorial5中在decode 下增加trace后打印情况:
len1 = avcodec_decode_video(is-
packet-
printf("-----------------------------------------------------------------------------\n");
printf("avcodec_decode_videopacket-
printf("avcodec_decode_videopFrame-
if(pFrame-
printf("avcodec_decode_video*(uint64_t *)pFrame-
其中播一个mp4文件的打印情况:
-----------------------------------------------------------------------------
avcodec_decode_video packet-<1ae>
avcodec_decode_videopFrame-<0>
avcodec_decode_video *(uint64_t *)pFrame-<1ae>
-----------------------------------------------------------------------------
avcodec_decode_video packet-<1af>
avcodec_decode_videopFrame-<0>
avcodec_decode_video *(uint64_t *)pFrame-<1af>
-----------------------------------------------------------------------------
avcodec_decode_video packet-<24c>
avcodec_decode_videopFrame-<0>
avcodec_decode_video *(uint64_t *)pFrame-<1ac>
-----------------------------------------------------------------------------
avcodec_decode_video packet-<24d>
avcodec_decode_videopFrame-<0>
avcodec_decode_video *(uint64_t *)pFrame-<24d>
-----------------------------------------------------------------------------
avcodec_decode_video packet-<24e>
avcodec_decode_videopFrame-<0>
avcodec_decode_video*(uint64_t *)pFrame-<24e>
以下为播放rm文件的情况:
-----------------------------------------------------------------------------
avcodec_decode_videopacket-<1831b>
avcodec_decode_videopFrame-<0>
avcodec_decode_video *(uint64_t *)pFrame-<1831b>
-----------------------------------------------------------------------------
avcodec_decode_videopacket-<18704>
avcodec_decode_videopFrame-<0>
avcodec_decode_video *(uint64_t *)pFrame-<18704>
-----------------------------------------------------------------------------
avcodec_decode_videopacket-<18aed>
avcodec_decode_videopFrame-<0>
avcodec_decode_video *(uint64_t *)pFrame-<18aed>
-----------------------------------------------------------------------------
avcodec_decode_videopacket-<18ed6>
avcodec_decode_videopFrame-<0>
avcodec_decode_video *(uint64_t *)pFrame-<18ed6>
-----------------------------------------------------------------------------
avcodec_decode_videopacket-<192bf>
avcodec_decode_videopFrame-<0>
avcodec_decode_video *(uint64_t *)pFrame-<192bf>
-----------------------------------------------------------------------------
avcodec_decode_videopacket-<196a8>
avcodec_decode_videopFrame-<0>
avcodec_decode_video *(uint64_t *)pFrame-<196a8>
可以看出有的pts是+1 累加,有的是加了很多,但都是按顺序累加的。当传人decoder前的packet有pts时,则decoder后获取的frame将会赋值packet的pts;当传人的packet 只是一帧的部分数据或是B帧,由于decoder出来的frame要按正常的pts顺序输出,有可能decoder不会获取到frame ,或decoder内部会缓存也不会输出frame,即frame的pts会为空。Frame pts(即opaque) 为空的话则会看frame-
对于:
pts *= av_q2d(is-
// Did we get avideo frame?
if(frameFinished) {
pts =synchronize_video(is, pFrame, pts);
/ synchronize_video考虑了3中情况:
1. pts拿到的话就用该pts
2. pts没有拿到的话就用前一帧的pts时间
3. 如果该帧要重复显示,则将显示的数量*帧率,再加到前面的pts中。
if(queue_picture(is, pFrame, pts) < 0 decodershowp>
static double synchronize_video(VideoState *is, AVFrame*src_frame, double pts) {
doubleframe_delay;
if(pts != 0) {
/* if we havepts, set video clock to it */
is-
} else {
/* if we aren'tgiven a pts, set it to the clock */
pts =is-
}
/* update thevideo clock */
/很关键:前面传进来的pts已经是时间戳了,是当前frame开始播放的时间戳,
/下面frame_delay是该帧显示完将要花费的时间,(pts+frame_delay)也即是/预测的下一帧将要播放的时间戳。
frame_delay =av_q2d(is-
/* if we arerepeating a frame, adjust clock accordingly */
//重复多帧的话要累加上
frame_delay +=src_frame-
is-
return pts;/此时返回的值即为下一帧将要开始显示的时间戳。
}
///开定时器去显示帧队列中的已经decode过的数据,按前面的分析我们已经知道帧队列中的数据已经是按pts顺序插入到队列中的。Timer的作用就是有帧率不一致及重复帧的情况造成时间戳不是线性的,有快有慢,从而tutorial5才有timer的方式来播放:追赶
以下是一个网友很直观浅显的例子解释:
ccq(183892517) 17:05:21 if(packet-
David Cen(3727567) 17:06:44 就是有一把尺子 一只蚂蚁跟着一个标杆走 David Cen(3727567) 17:06:58 标杆是匀速的 蚂蚁或快或慢 DavidCen(3727567) 17:07:18 慢了你就抽它 让他跑起来 快了就拽它 David Cen(3727567) 17:07:38 这样音(标杆)视频(蚂蚁)就能同步了 DavidCen(3727567) 17:08:00 这里最大的问题就是音频是匀速的 视频是非线性的
另外:此时vp–
static void video_refresh_timer(void *userdata) {
VideoState *is = (VideoState*)userdata;
VideoPicture *vp;
double actual_delay, delay,sync_threshold, ref_clock, diff;
if(is-
if(is-
schedule_refresh(is, 1);
} else {
vp =&is-
delay = vp-
if(delay < delay>= 1.0) {
/* if incorrect delay, useprevious one */
delay =is-
}
/* save for next time */
is-
is-
/* update delay to sync toaudio */
ref_clock = get_audio_clock(is);/获取到声音当前播放的时间戳。
diff = vp-
//也就是说在diff这段时间中声音是匀速发生的,但是在delay这段时间frame的显示可能就会有快//慢的区别。
/* Skip or repeat the frame.Take delay into account
FFPlay still doesn't "know if this is thebest guess." */
sync_threshold = (delay
if(fabs(diff) < AV NOSYNC_THRESHOLD p>
if(diff < -sync_threshold p>
delay = 0;//下一帧画面显示的时间和当前的声音很近的话加快显示下一帧(即后面video_display显示完当前帧后开启定时器很快去显示下一帧)
} else if(diff <=sync_threshold) {
delay = 2 * delay;//下一帧开始显示的时间和当前声音的时间隔的比较长则延缓,即两帧画面间话的显示的时间长度大于两帧画面间的声音播放的时间,则我们将两帧画显示的时候加倍拖长点,比如帧1和帧2的时间显示间隔为40ms,但帧1和帧2的声音播放时间为55ms,怎么办呢?我们不可能去打乱声音的质量的,则我们采用的方法是:将两帧画面的播放间隔加大,本来是过30ms就要开始播下一帧的,我们改成60ms后才播下一帧。
}
}/
当然如果diff大于AV_NOSYNC_THRESHOLD,即快进的模式了,画面跳动太大,不存在音视频同步的问题了。
is-
/* computer the REAL delay*/
actual_delay =is-
if(actual_delay < 0 p>
/* Really it should skipthe picture instead */
actual_delay = 0.010;
}
schedule_refresh(is,(int)(actual_delay * 1000 + 0.5));开定时器去显示下一帧
/* show the picture! */
video_display(is);立马显示当前帧
/* update queue for nextpicture! */
if(++is-
is-
}
SDL_LockMutex(is-
is-
SDL_CondSignal(is-
SDL_UnlockMutex(is-
}
} else {
schedule_refresh(is, 100);
}
196a8>0>196a8>192bf>0>192bf>18ed6>0>18ed6>18aed>0>18aed>18704>0>18704>1831b>0>1831b>24e>0>24e>24d>0>24d>1ac>0>24c>1af>0>1af>1ae>0>1ae>写在最前面:1. php的版本为 php-5.4.14,脚本运行环境在centos5.9 64bit通过.2. 为什么不写lnmp一键安装的了,而单单拆分了,我也不知道.3. 当然,如果你有任何你解决不了的问题,随时联系我.#!/bin/bash #version 1.0 #date 2013-05-04 #author yangcan #mail yoncan...
在winform中利用DataSet、DataTable和DataAdapter将mysql与DataGridView绑定。包含单表绑定、id转换为其他信息、多表绑定到一个DataGridView
文章目录1. EIDORS3.8 基本架构1.1 Fwd_model1.2 Data1.3 Inv_model1.4 Image2 基于 EIDORS3.8 的 EIT 有限元模型建立2.1 二维有限元模型2.2 三维有限元模型1. EIDORS3.8 基本架构EIDORS3.8 是一个基于电学或光学数据可自由分配和自由修改的图像重建软件包。此软件工具作为开发人员对于新算法、新模型的验证与实...
if(expr1,expr2,expr3) 如果expr1返回true 则执行expr2部分语句,否则执行expr3语句//如果条件a不为空 则使用添加a与b关联select a.id,b.* from a join b on if(a.id is null,1=1,a.id=b.id);//这里如果a.id is null,则执行1=1条件 也就是相当于没有查询条件 恒为true;否则执...
BlueZ是Linux官方蓝牙协议栈。它是一个基于GNU General Public License (GPL)发布的开源项目,从Linux 2.4.6开始便成为Linux 内核的一部分。如果你是linux平台开发蓝牙的的工程师,肯定会接触过Bluez协议栈,hciconfig,hciattach,hciconfig等工具都是很好用的工具。
Element UIElement UI 来自中国,由与 Mint UI 相同的开发者所创建。Element UI 是用于 Web 和桌面应用程序的桌面 UI 工具包,如果你需要开发 Electron 应用,这个库会是你的理想之选。iViewiView 是一个 UI 工具包,其中包含简洁又设计优雅的小部件和各种组件。iView 团队维护非常及时,最近一次的更新在19年3月18日,这次更新带来了一个Vue CLI 3的 iView 插件,你可以使用该插件快速构建基于 iView 的项目。Vue
Oracle 要对JAVA收费了,许多人说可以用 OpenJDK替代,但这会对应用有影响吗?OpenJDK 项目主要基于 Sun 捐赠的 HotSpot 源代码。此外,OpenJDK 被选为 Java 的参考实现,由 Oracle 工程师维护。关于 JVM,JDK,JRE 和 OpenJDK 之间的区别,Oracle 博客帖子在 2012 年有一个更详细的答案:问:OpenJDK 存储库中的源代码与用于构建 Oracle JDK 的代码之间有什么区别?答:非常接近 - 我们的 Oracle JD
目录一. 数字基带传输系统原理1. 带限信道的基带系统模型(连续域分析)2. 升余弦滚降滤波器3. 最佳基带系统二.发送滤波器设计1.滤波器的设计2. 频率抽样法设计非匹配形式的基带系统的发送滤波器(平方根升余弦滤波器)2.问题三.基带系统软件设计1.设计思路2.子函数编写四.基带系统仿真与性能测试1.系统主函数设计思路2.仿真与测试五.总结1.问题2.对于理论的理解3.感悟本文旨在记录自己的一...
什么是图像分割(image segmentatioin)?想必你已经了解图像分类,神经网络所应用的任务之一就是对输入图像进行分类,指出其属于哪一类图像。然而,如果你需要从图像中识别出物体,指出图片中的像素点分别归属于什么物体,这种情况下你需要的是分割图像(segment the image),换句话说就是给图像的像素点打上标签。图像分割(image segmentation)的任务就是训练一个...
Android4.4开发项目中的webview在Android各个版本运行的飞起,可是项目升级,最低版本适配5.0之后,webview各种闪退问题真让人头大!!!!!!!!!!!!!!!啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊我太难了!仔细对比了两个项目的差异,有所发现:Android5.0的build.gradle,注意implementation 'androidx.appcompat:appcompa...
好了,可以在R语言里玩耍了。参考资料:ChIP分析流程Chip-seq 实战分析流程1.安装软件(之前安装过,先检测一下)source ("https://bioconductor.org/biocLite.R")biocLite("ChIPseeker")biocLite("TxDb.Mmusculus.UCSC.mm10.knownGene")biocLite("org.Mm.eg.db")biocLite("clusterProfiler")在安装 org.Mm.eg.db 时,