一、前言
VLC播放音视频的核心流程梳理,从live555收流到ffmpeg解码的整套流程
涉及到MultiFramedRTPSource、RTPSource、FramedSource、live555、es_out、decoder、video、clock、video_output、araw、mtime、dec、input、output、filters、directx等核心类。
二、核心点备注
1、RTPSource中使用报文时间戳与当前时间计算抖动的核心代码
unsigned arrival = (timestampFrequency*timeNow.tv_sec);//音频8000,视频90000 arrival += (unsigned) ((2.0*timestampFrequency*timeNow.tv_usec + 1000000.0)/2000000); // note: rounding int transit = arrival - rtpTimestamp; if (fLastTransit == (~0)) fLastTransit = transit; // hack for first time int d = transit - fLastTransit; fLastTransit = transit; if (d < 0) d = -d; fJitter += (1.0/16.0) * ((double)d - fJitter);
2、RTPSource中使用报文时间戳差值计算出timeval 类型的presentationTime的核心代码
// Return the 'presentation time' that corresponds to "rtpTimestamp": if (fSyncTime.tv_sec == 0 && fSyncTime.tv_usec == 0) { // This is the first timestamp that we've seen, so use the current // 'wall clock' time as the synchronization time. (This will be // corrected later when we receive RTCP SRs.) fSyncTimestamp = rtpTimestamp; fSyncTime = timeNow; } int timestampDiff = rtpTimestamp - fSyncTimestamp; // Note: This works even if the timestamp wraps around // (as long as "int" is 32 bits) // Divide this by the timestamp frequency to get real time://视频timeDiff =0.040000000000000001 double timeDiff = timestampDiff/(double)timestampFrequency; // Add this to the 'sync time' to get our result: unsigned const million = 1000000; unsigned seconds, uSeconds; if (timeDiff >= 0.0) { seconds = fSyncTime.tv_sec + (unsigned)(timeDiff); uSeconds = fSyncTime.tv_usec + (unsigned)((timeDiff - (unsigned)timeDiff)*million); if (uSeconds >= million) { uSeconds -= million; ++seconds; } } else { timeDiff = -timeDiff; seconds = fSyncTime.tv_sec - (unsigned)(timeDiff); uSeconds = fSyncTime.tv_usec - (unsigned)((timeDiff - (unsigned)timeDiff)*million); if ((int)uSeconds < 0) { uSeconds += million; --seconds; } }
3、live555.cpp中将timeval转换为微秒级时间戳i_pts的要点
int64_t i_pts = (int64_t)pts.tv_sec * INT64_C(1000000) + (int64_t)pts.tv_usec; /* XXX Beurk beurk beurk Avoid having negative value XXX */ i_pts &= INT64_C(0x00ffffffffffffff); <span style="color:#3333FF;">1)注意避免翻转为负数 2、block里i_pts+1,i_dts根据sdp中的packetization-mode=1字段判断</span>
4、处理H.264视频的要点
1)丢帧策略可以设置丢B帧、非参考帧、除关键帧以外所有帧等,可以在处理延时的情况下加速解码,尝试赶上发流速度,是明智之举;
2)如果存在过期的帧,且当前时间减第一个过期帧的时间>5S,则认为该帧过期时间太长,直接释放;如果统计的过期帧数>4且<12,则设置ffmpeg的解码器丢帧策略;
3)解码后得到pts,如果frame_rate和frame_rate_base>0,或者p_context->time_base.den > 0计算获得下一帧的pts,更新p_sys->i_pts,用于下一帧解码时校验数据;
4)每次ffmpeg解码后,使用clock机制将pts流时间戳转换为系统时间,然后检查转换后的时间是否过期,过期算法为:判断显示时间<=mdate() 说明已过期,累加过期帧数;备注:*pi_ts0 >= mdate() + i_ts_delay + i_ts_buffering + i_ts_bound( i_ts_bound默认值为9s)
5)最后在送渲染队列前使用clock::input_clock_ConvertTS将时戳拉伸到播放rate对应区间。
5、处理音频的要点
1)计算比特率和样本数 unsigned samples = (8 * p_block->i_buffer) / framebits;
2)通过采样数计算时间增量 mtime_t date_Increment( date_t *p_date, uint32_t i_nb_samples )
3)a、发现正确的播放频率 b、重新制作音频帧。核心流程如下:
1、仅正常播放速率支持音频播放,否则丢弃 2、if ( start_date != VLC_TS_INVALID && start_date < now )期望时间戳帧已严重过期,重刷音频缓存数据,停止重采样(主要发生在用户暂停或解码器错误) 3、if ( p_buffer->i_pts < now + AOUT_MIN_PREPARE_TIME )帧已过期,直接丢弃;AOUT_MIN_PREPARE_TIME为40ms 4、mtime_t drift = start_date - p_buffer->i_pts;偏移量drift = 期望播放时间戳-当前帧的实际时间戳,drift<0说明帧滞后来了,drift>0说明帧提前来了; 5、if( drift < -i_input_rate * 3 * AOUT_MAX_PTS_ADVANCE / INPUT_RATE_DEFAULT )如果偏移量<-3*40ms,则停止缓冲,停止重采样。预设值。 6、if( drift > +i_input_rate * 3 * AOUT_MAX_PTS_DELAY / INPUT_RATE_DEFAULT )如果偏移量>3*60ms,缓冲太晚,直接丢弃 7、if ( ( p_input->i_resampling_type == AOUT_RESAMPLING_NONE ) && ( drift < -AOUT_MAX_PTS_ADVANCE || drift > +AOUT_MAX_PTS_DELAY ) && p_input->i_nb_resamplers > 0 )drift漂移量<-40ms或>60ms,则重新发现播放频率.即滞后40ms或提前60ms(主要原因是:1、输入时钟偏移有误2、用户短暂暂停 3、输出端有点延时,丢失了一点同步--费解,4、现网有部分摄像头的采样率和码流里的时间戳不匹配,也会导致该问题) 8、根据偏移结果微调采样频率 9、p_buffer->i_pts = start_date;最终将期望时间戳送渲染;
三、核心流程时序图