心理健康教育网站建设,设计发明的网站,网站建设服务收费,做地理题的网站From: http://www.360doc.com/content/11/1117/09/8050095_165108638.shtml FFMPEG解码流程 1. 注册所有容器格式和CODEC:av_register_all()
2. 打开文件:av_open_input_file()
3. 从文件中提取流信息:av_find_stream_info()
4. 穷举所有的流#xff0c;查找其中种类为CODE…From: http://www.360doc.com/content/11/1117/09/8050095_165108638.shtml FFMPEG解码流程 1. 注册所有容器格式和CODEC:av_register_all()
2. 打开文件:av_open_input_file()
3. 从文件中提取流信息:av_find_stream_info()
4. 穷举所有的流查找其中种类为CODEC_TYPE_VIDEO
5. 查找对应的解码器:avcodec_find_decoder()
6. 打开编解码器:avcodec_open()
7. 为解码帧分配内存:avcodec_alloc_frame()
8. 不停地从码流中提取出帧数据:av_read_frame()
9. 判断帧的类型对于视频帧调用:avcodec_decode_video()
10. 解码完后释放解码器:avcodec_close()
11. 关闭输入文件:av_close_input_file() 首先第一件事情就是开一个视频文件并从中得到流。我们要做的第一件事情就是使用av_register_all();来初始化libavformat/libavcodec:
这一步注册库中含有的所有可用的文件格式和编码器这样当打开一个文件时它们才能够自动选择相应的文件格式和编码器。av_register_all()只需调用一次所以要放在初始化代码中。也可以仅仅注册个人的文件格式和编码。
下一步打开文件
AVFormatContext *pFormatCtx; const char *filenamemyvideo.mpg; av_open_input_file(pFormatCtx, filename, NULL, 0, NULL) // 打开视频文件 最后三个参数描述了文件格式缓冲区大小size和格式参数我们通过简单地指明NULL或0告诉 libavformat 去自动探测文件格式并且使用默认的缓冲区大小。这里的格式参数指的是视频输出参数比如宽高的坐标。
下一步我们需要取出包含在文件中的流信息 av_find_stream_info(pFormatCtx) // 取出流信息
AVFormatContext 结构体
dump_format(pFormatCtx, 0, filename, false);//我们可以使用这个函数把获取到得参数全部输出。
for(i0; ipFormatCtx-nb_streams; i) //区分视频流和音频流 if(pFormatCtx-streams-codec.codec_typeCODEC_TYPE_VIDEO) //找到视频流这里也可以换成音频 { videoStreami; break; }
接下来就需要寻找解码器
AVCodec *pCodec; pCodecavcodec_find_decoder(pCodecCtx-codec_id);
avcodec_open(pCodecCtx, pCodec) // 打开解码器 给视频帧分配空间以便存储解码后的图片
AVFrame *pFrame; pFrameavcodec_alloc_frame();
/开始解码///
第一步当然是读数据
我们将要做的是通过读取包来读取整个视频流然后把它解码成帧最后转换格式并且保存。
while(av_read_frame(pFormatCtx, packet)0) { //读数据 if(packet.stream_indexvideoStream){ //判断是否视频流 avcodec_decode_video(pCodecCtx,pFrame, frameFinished,
packet.data, packet.size); //解码 if(frameFinished) {
img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,(AVPicture*)pFrame, pCodecCtx-pix_fmt, pCodecCtx-width,pCodecCtx-height);//转换 }
SaveFrame(pFrameRGB, pCodecCtx-width,pCodecCtx-height, i); //保存数据
av_free_packet(packet); //释放
av_read_frame()读取一个包并且把它保存到AVPacket结构体中。这些数据可以在后面通过av_free_packet()来释 放。函数avcodec_decode_video()把包转换为帧。然而当解码一个包的时候我们可能没有得到我们需要的关于帧的信息。因此当我们得 到下一帧的时候avcodec_decode_video()为我们设置了帧结束标志frameFinished。最后我们使用 img_convert()函数来把帧从原始格式pCodecCtx-pix_fmt转换成为RGB格式。要记住你可以把一个 AVFrame结构体的指针转换为AVPicture结构体的指针。最后我们把帧和高度宽度信息传递给我们的SaveFrame函数。
到此解码完毕显示过程使用SDL完成考虑到我们以后会使用firmware进行显示操作SDL忽略不讲。
音视频同步
DTS解码时间戳和PTS显示时间戳
当我们调用av_read_frame()得到一个包的时候PTS和DTS的信息也会保存在包中。但是我们真正想要的PTS是我们刚刚解码出来的 原始帧 的PTS这样我们才能知道什么时候来显示它。然而我们从avcodec_decode_video()函数中得到的帧只是一个AVFrame其中并 没有包含有用的PTS值注意AVFrame并没有包含时间戳信息但当我们等到帧的时候并不是我们想要的样子。。我们保存一帧的第一个包的PTS 这将作为整个这一帧的PTS。我们 可以通过函数avcodec_decode_video()来计算出哪个包是一帧的第一个包。怎样实现呢任何时候当一个包开始一帧的时 候avcodec_decode_video()将调用一个函数来为一帧申请一个缓冲。当然ffmpeg允许我们重新定义那个分配内存的函数。计算前 一帧和现在这一帧的时间戳来预测出下一个时间戳的时间。同时我们需要同步视频到音频。我们将设置一个音频时间audioclock一个内部值记录了我 们正在播放的音频的位置。就像从任意的mp3播放器中读出来的数字一样。既然我们把视频同步到音频视频线程使用这个值来算出是否太快还是太慢。 用FFMPEG SDK进行视频转码压缩时解决音视频不同步问题的方法(转)
ffmpeg 2010-07-21 19:54:16 阅读163 评论0
用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-time_base.num,c-time_base.den); if( x lTimeStamp ) { return TRUE; } m_pVideoFrame2-pts lTimeStamp; m_pVideoFrame2-pict_type 0; int out_size avcodec_encode_video( c, m_pvideo_outbuf, video_outbuf_size,m_pVideoFrame2 ); /* if zero size, it means the image was buffered */ if (out_size 0) { AVPacket pkt; av_init_packet(pkt); if( x lTimeStamp ) { pkt.pts pkt.dts m_llframe_index; pkt.duration 0; } else { pkt.duration (lTimeStamp - x)*c-time_base.den/1000000 1; pkt.pts m_llframe_index; pkt.dts pkt.pts; m_llframe_index pkt.duration; } //pkt.pts lTimeStamp * (__int64)frame_rate.den / 1000; if( c-coded_frame c-coded_frame-key_frame ) { pkt.flags | PKT_FLAG_KEY; } pkt.stream_index m_pVideoStream-index; pkt.data m_pvideo_outbuf; pkt.size out_size; /* write the compressed frame in the media file */ ret av_interleaved_write_frame( m_pAvFormatContext, pkt ); } else { ret 0; } 请问avcodec_decode_video解码的帧为什么后面的比前面的pts小呢
请问如下代码 while( av_read_frame(pFormatCtxSource,packet)0 ) { if( packet.stream_indexvideoStream ) { int out_size avcodec_decode_video(pCodecCtxSource,pFrameSource, bFrameFinished, packet.data, packet.size); // Decode fromsource frame if( bFrameFinished ) { pFrameSource-pts av_rescale_q(packet.pts, pCodecCtxSource-time_base,pStCodec-time_base); int out_size avcodec_encode_video(pStCodec, video_buffer, 200000, pFrameSource); // Encodeto output if( out_size0 ) { // ... } } } av_free_packet(packet); } 在我Decode的时候第一帧得到的 pFrameSource-pts 是96再解第二帧的时候pFrameSource-pts 计算完后就成了80几后几帧也是比96小过一会又会解出来一个100多的接下来又是比100多小的这是为什么在Encode的时候先 Encode一个pts96的再去Encode比96小的帧就返回-1了直到找到一个比96大的。 另外我计算pts的方法正确吗 答复
Because you have B - Frame for example: the Inputsequence for video encoder 1 2 3 4 5 6 7 I B B P B B I Lets take1,2,3.. as PTS for simplification the out sequencefor video encoder ( this equals the decoder sequence) 1 4 2 3 7 5 6 I P B B I B B you 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 ) { 解码; pts1; 编码; 输出; } 方法二 while(av_read_frame ) { 解码; if( ptsprevious ) { 缓存; } 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-video_st-codec,pFrame, frameFinished, packet-data,packet-size); printf(-----------------------------------------------------------------------------\n); printf(avcodec_decode_videopacket-pts:%x,packet-dts:%x\n,packet-pts,packet-dts); printf(avcodec_decode_videopFrame-pkt_pts:%x,pFrame-pkt_dts:%x,pFrame-pts:%x\n,pFrame-pkt_pts,pFrame-pkt_dts,pFrame-pts); if(pFrame-opaque) printf(avcodec_decode_video*(uint64_t *)pFrame-opaque:%x\n,*(uint64_t *)pFrame-opaque); 其中播一个mp4文件的打印情况
-----------------------------------------------------------------------------
avcodec_decode_video packet-pts:1ae,packet-dts:0
avcodec_decode_videopFrame-pkt_pts:0,pFrame-pkt_dts:80000000,pFrame-pts:0
avcodec_decode_video *(uint64_t *)pFrame-opaque:1ae
-----------------------------------------------------------------------------
avcodec_decode_video packet-pts:1af,packet-dts:0
avcodec_decode_videopFrame-pkt_pts:0,pFrame-pkt_dts:80000000,pFrame-pts:0
avcodec_decode_video *(uint64_t *)pFrame-opaque:1af
-----------------------------------------------------------------------------
avcodec_decode_video packet-pts:24c,packet-dts:0
avcodec_decode_videopFrame-pkt_pts:0,pFrame-pkt_dts:80000000,pFrame-pts:0
avcodec_decode_video *(uint64_t *)pFrame-opaque:1ac
-----------------------------------------------------------------------------
avcodec_decode_video packet-pts:24d,packet-dts:0
avcodec_decode_videopFrame-pkt_pts:0,pFrame-pkt_dts:80000000,pFrame-pts:0
avcodec_decode_video *(uint64_t *)pFrame-opaque:24d
-----------------------------------------------------------------------------
avcodec_decode_video packet-pts:24e,packet-dts:0
avcodec_decode_videopFrame-pkt_pts:0,pFrame-pkt_dts:80000000,pFrame-pts:0 avcodec_decode_video*(uint64_t *)pFrame-opaque:24e 以下为播放rm文件的情况
-----------------------------------------------------------------------------
avcodec_decode_videopacket-pts:1831b,packet-dts:0
avcodec_decode_videopFrame-pkt_pts:0,pFrame-pkt_dts:80000000,pFrame-pts:0
avcodec_decode_video *(uint64_t *)pFrame-opaque:1831b
-----------------------------------------------------------------------------
avcodec_decode_videopacket-pts:18704,packet-dts:0
avcodec_decode_videopFrame-pkt_pts:0,pFrame-pkt_dts:80000000,pFrame-pts:0
avcodec_decode_video *(uint64_t *)pFrame-opaque:18704
-----------------------------------------------------------------------------
avcodec_decode_videopacket-pts:18aed,packet-dts:0
avcodec_decode_videopFrame-pkt_pts:0,pFrame-pkt_dts:80000000,pFrame-pts:0
avcodec_decode_video *(uint64_t *)pFrame-opaque:18aed
-----------------------------------------------------------------------------
avcodec_decode_videopacket-pts:18ed6,packet-dts:0
avcodec_decode_videopFrame-pkt_pts:0,pFrame-pkt_dts:80000000,pFrame-pts:0
avcodec_decode_video *(uint64_t *)pFrame-opaque:18ed6
-----------------------------------------------------------------------------
avcodec_decode_videopacket-pts:192bf,packet-dts:0
avcodec_decode_videopFrame-pkt_pts:0,pFrame-pkt_dts:80000000,pFrame-pts:0
avcodec_decode_video *(uint64_t *)pFrame-opaque:192bf
-----------------------------------------------------------------------------
avcodec_decode_videopacket-pts:196a8,packet-dts:0
avcodec_decode_videopFrame-pkt_pts:0,pFrame-pkt_dts:80000000,pFrame-pts:0
avcodec_decode_video *(uint64_t *)pFrame-opaque: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-dts,dts都没有的话才认为frame-pts为0. 对于
pts * av_q2d(is-video_st-time_base);即pts*帧率 // 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) {/传人decoder后的帧队列中以便后续去获取show。 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-video_clock pts; } else { /* if we arentgiven a pts, set it to the clock */ pts is-video_clock; } /* update thevideo clock */
/很关键前面传进来的pts已经是时间戳了是当前frame开始播放的时间戳
/下面frame_delay是该帧显示完将要花费的时间ptsframe_delay也即是/预测的下一帧将要播放的时间戳。 frame_delay av_q2d(is-video_st-codec-time_base); /* if we arerepeating a frame, adjust clock accordingly */ //重复多帧的话要累加上 frame_delay src_frame-repeat_pict * (frame_delay * 0.5); is-video_clock frame_delay; return pts;/此时返回的值即为下一帧将要开始显示的时间戳。
} ///开定时器去显示帧队列中的已经decode过的数据按前面的分析我们已经知道帧队列中的数据已经是按pts顺序插入到队列中的。 Timer的作用就是有帧率不一致及重复帧的情况造成时间戳不是线性的有快有慢从而tutorial5才有timer的方式来播放追赶
以下是一个网友很直观浅显的例子解释
ccq(183892517) 17:05:21 if(packet-dts AV_NOPTS_VALUE 是不是就是没有获取到dts的情况
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–pts获取到的pts已经转化为时间戳了这个时间戳为就是当前帧显示结束的时间戳也即是下一帧将显示的预测时间戳。
static void video_refresh_timer(void *userdata) { VideoState *is (VideoState*)userdata; VideoPicture *vp; double actual_delay, delay,sync_threshold, ref_clock, diff; if(is-video_st) { if(is-pictq_size 0) { schedule_refresh(is, 1); } else { vp is-pictq[is-pictq_rindex]; delay vp-pts -is-frame_last_pts; /* the pts from last time */ 这是当前要显示的frame和下一 副 //将要显示的 frame的间隔时间 if(delay 0 || delay 1.0) { /* if incorrect delay, useprevious one */ delay is-frame_last_delay; } /* save for next time */ is-frame_last_delay delay; is-frame_last_pts vp-pts; /* update delay to sync toaudio */ ref_clock get_audio_clock(is);/获取到声音当前播放的时间戳。 diff vp-pts -ref_clock;// vp-pts实际上是预测的下一帧将要播放的开始时间, //也就是说在diff这段时间中声音是匀速发生的但是在delay这段时间frame的显示可能就会有快//慢的区别。 /* Skip or repeat the frame.Take delay into account FFPlay still doesnt know if this is thebest guess. */ sync_threshold (delay AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD; if(fabs(diff) AV_NOSYNC_THRESHOLD) { if(diff -sync_threshold) { 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-frame_timer delay; /* computer the REAL delay*/ actual_delay is-frame_timer - (av_gettime() / 1000000.0); if(actual_delay 0.010){ /* 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-pictq_rindex VIDEO_PICTURE_QUEUE_SIZE) { is-pictq_rindex 0; } SDL_LockMutex(is-pictq_mutex); is-pictq_size--; SDL_CondSignal(is-pictq_cond); SDL_UnlockMutex(is-pictq_mutex); } } else { schedule_refresh(is, 100); }