記事ディレクトリ
1. 音声とビデオの同期の基本
ビデオとオーディオは別のスレッドであり、同じポイントのオーディオ フレームとビデオ フレームは同時にデコードされないため、オーディオとビデオの同期が必要です。
1.1 オーディオとビデオの同期戦略
- オーディオに基づいて
- ビデオが遅い場合、一部のビデオ フレームが失われます (視覚的にはフレームがドロップします)。
- ビデオが速すぎる場合は、前のフレームのレンダリングを続けます。
- ビデオに基づいて
- 音声が遅い場合、再生が加速されます (またはフレームがドロップされます。フレームがドロップされると、音が中断され、エクスペリエンスが特に低下します)。
- オーディオが速すぎる場合は、データ ポイントを遅くします (または前のフレームを繰り返します)。
- オーディオ速度の変更にはリサンプリングが必要です
- 外部クロックに基づく
- 総合1 2、外部クロックに応じて再生速度を変更
- さまざまなビデオ出力とオーディオ出力が同期していません (通常はこのようなものではありません)
人間は視覚的な変化よりも聴覚的な変化に敏感であるため、同期戦略を選択する際のベンチマークとして音声がよく使用されます。
1.2 オーディオとビデオの同期の概念
- DTS (デコード タイム スタンプ): デコード タイムスタンプ。プレーヤーにこのデータ フレームをいつデコードするかを指示します。
- PTS (プレゼンテーション タイム スタンプ): プレーヤーにこのデータ フレームをいつ再生するかを伝えるタイムスタンプを表示します。
- time_base タイムベース: FFmpeg で使用される単位です。
ビデオに B フレームがない場合、DTS と PTS の順序は同じになります。
time_base 構造体は次のとおりです。
typedef struct AVRational
int num; ///< 分子
int den; ///< 分母
} AVRational;
タイムスタンプを計算します:
timestame(秒)=pts*av_qtd(st->time_base)
フレーム期間を計算します。
時間(秒)=期間*av_qtd(st->time_base)
異なるタイムベース変換:
int64_t av_rescale_q(int64_ta、AVRational bq、AVRational cq)
音声と映像を同期させるときに「クロック」という概念が必要になりますが、音声、映像、外部クロックはそれぞれ独自の時計を持っており、それぞれが独自の時計を設定しており、誰を基準にして時計を取得しているのでしょうか?
typedef struct Clock {
double pts; // 时钟基础, 当前帧(待播放)显示时间戳,播放后,当前帧变成上一帧
// 当前pts与当前系统时钟的差值, audio、video对于该值是独立的
double pts_drift; // clock base minus time at which we updated the clock
// 当前时钟(如视频时钟)最后一次更新时间,也可称当前时钟时间
double last_updated; // 最后一次更新的系统时钟
double speed; // 时钟速度控制,用于控制播放速度
// 播放序列,所谓播放序列就是一段连续的播放动作,一个seek操作会启动一段新的播放序列
int serial; // clock is based on a packet with this serial
int paused; // = 1 说明是暂停状态
// 指向packet_serial
int *queue_serial; //指向当前数据包队列串行的指针,用于过时时钟检测
} Clock;
時計の仕組み:
-
「時刻を修正する」には常に set_lock_at を呼び出す必要があります。それには、pts、serial、time (システム時刻) が必要です。
-
取得される時間も推定値であり、その推定値は「時刻調整」の pts_drift によって推定されます。
図のタイムラインは時間に応じて増加しますが、set_Clock を呼び出して時間を調整し、pts が時間より遅れているとして、pts_drift=pts-time として、pts と時間の差を計算します。
しばらくすると、pts を取得するために get_lock が必要になります。pts_drift と今の時間の差 (pts=time+pts_drift) を通じて計算できます。時間は、ffmpeg が提供する av_gettime_relative 関数を通じて取得できます。
1.3 FFmpeg の時間単位
OFF_TIME_BASE
- #define AV_TIME_BASE 1000000
- FFmpeg 内部タイミング ユニット
AV_TIME_BASE_Q
- #define AV_TIME_BASE_Q (AVRational){1、AV_TIME_BASE}
- FFmpeg の内部タイムベースの小数表現は AV_TIME_BASE の逆数です
タイムベース換算式
- タイムスタンプ (FFmpeg 内部タイムスタンプ)=AV_TIME_BASE*時間 (秒)
- 時間(秒)=タイムスタンプ*AV_TIME_BASE_Q
1.4 さまざまな構造の時間ベース/継続時間分析
FFmpeg にはさまざまな構造に対応する多くのタイム ベースがあり、各タイム ベースの値は異なります。
-
AVフォーマットコンテキスト
- 継続時間: コード ストリーム全体の長さを表します。通常の継続時間を取得するには、それをAV_TIME_BASEで割る必要があります。結果は秒です。
-
AVストリーム
- time_base: 単位は秒です。たとえば、AAC オーディオは {1,44100}、TS ストリームは {1,90KHZ} になります。
- duration: データストリームの継続時間を示します。単位は AVStream->time_base です。
-
AVStream の time_base は dumuxer または muxer で設定されます
TS
- avpriv_set_pts_info(st,33,1,90000)
FLV
- avpriv_set_pts_info(st,32,1,10000)
MP4
- avpriv_set_pts_info(st,64,1,sc->time_scale)
- avpriv_set_pts_info(st,64,1,track->timescale)
1.5 さまざまな構造の pts/dts の分析
AVPacket と AVFrame の時間は両方とも AVStream->time_base から取得されます。
ここでのポイントは、エンコード時の各ストリームの time_base が avformat_write_header の後に設定されていることです。具体的なソースコードは当面解析しませんが、エンコードに夢中になって time_base がどこから来たのか分からないように注意してください。
1.6 ffplayにおけるフレーム構造の解析
typedef struct Frame {
AVFrame *frame; // 指向数据帧
AVSubtitle sub; // 用于字幕
int serial; // 帧序列,在seek的操作时serial会变化
double pts; // 时间戳,单位为秒
double duration; // 该帧持续时间,单位为秒
int64_t pos; // 该帧在输入文件中的字节位置
int width; // 图像宽度
int height; // 图像高读
int format; // 对于图像为(enum AVPixelFormat),
// 对于声音则为(enum AVSampleFormat)
AVRational sar; // 图像的宽高比(16:9,4:3...),如果未知或未指定则为0/1
int uploaded; // 用来记录该帧是否已经显示过?
int flip_v; // =1则垂直翻转, = 0则正常播放
} Frame;
Frame 構造の pts とduration が double 型なのはなぜですか?
queue_picture 関数で実装されているフレームの書き込み時にポイントとデュレーションを変換したため、このときのポイントとデュレーションの単位は秒である必要があります。
1.7 Vidoe Frame PTS の取得と補正
ポイント補正
フレーム->pts=フレーム->best_effort_timestamp;
/**
*使用各种启发式估计的帧时间戳,以流时基为单位
*-编码:未使用
*-解码:由libavcodec设置,由用户读取。
*/
int64_t best_effort_timestamp;
フレームの pts を修正します。特定の修正アルゴリズムは libavcodec によって設定されます。フレームのタイムスタンプは基本的に pts と同じです。現在の pts が不当な値を持つ場合は、このより妥当な値を取得するために一連の調整が試行されます。
1.8 オーディオフレームPTSの取得
ffplay には 3 ポイントのコンバージョンがあります
-
AVStream->time_base から {1, サンプリング レート} に変換
サンプリングレートが変わるのはリサンプリングだと思うのですが、AVStream->time_baseも使うとエラーになります!
frame->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);
-
サンプリング レートを秒に変換します。これは、queue_picture がフレーム キューに入れられるときに必要になります。
af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
-
SDL にコピーされるデータの量に基づいた調整の見積もり
set_clock_at(&is->audclk, is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec, is->audio_clock_serial, audio_callback_time / 1000000.0);
2. 音声ベース
ffplay はデフォルトでオーディオ同期モードを採用しており、オーディオ同期モードのクロック設定は sdl_audio_callback で設定されます。
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
{
audio_callback_time = av_gettime_relative(); // while可能产生延迟
.............................................
is->audio_write_buf_size = is->audio_buf_size - is->audio_buf_index;
/* Let's assume the audio driver that is used by SDL has two periods. */
if (!isnan(is->audio_clock)) {
set_clock_at(&is->audclk, is->audio_clock -
(double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size)
/ is->audio_tgt.bytes_per_sec,
is->audio_clock_serial,
audio_callback_time / 1000000.0);
sync_clock_to_slave(&is->extclk, &is->audclk);
}
}
以前解析した動画再生スレッドと同じですので、ご覧ください。
以下の処理は動画再生の主な処理となりますので、前回の説明も併せてご覧ください。
重要なのは、前のフレームの表示時間を計算することがより重要であるということです。コードを見てください。
static void video_refresh(void *opaque, double *remaining_time)
{
VideoState *is = opaque;
double time;
Frame *sp, *sp2;
if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime)
check_external_clock_speed(is);
if (!display_disable && is->show_mode != SHOW_MODE_VIDEO && is->audio_st) {
time = av_gettime_relative() / 1000000.0;
if (is->force_refresh || is->last_vis_time + rdftspeed < time) {
video_display(is);
is->last_vis_time = time;
}
*remaining_time = FFMIN(*remaining_time, is->last_vis_time + rdftspeed - time);
}
if (is->video_st) {
retry:
if (frame_queue_nb_remaining(&is->pictq) == 0) {
// 帧队列是否为空
// nothing to do, no picture to display in the queue
// 什么都不做,队列中没有图像可显示
} else {
// 重点是音视频同步
double last_duration, duration, delay;
Frame *vp, *lastvp;
/* dequeue the picture */
// 从队列取出上一个Frame
lastvp = frame_queue_peek_last(&is->pictq);//读取上一帧
vp = frame_queue_peek(&is->pictq); // 读取待显示帧
// lastvp 上一帧(正在显示的帧)
// vp 等待显示的帧
if (vp->serial != is->videoq.serial) {
// 如果不是最新的播放序列,则将其出队列,以尽快读取最新序列的帧
frame_queue_next(&is->pictq);
goto retry;
}
if (lastvp->serial != vp->serial) {
// 新的播放序列重置当前时间
is->frame_timer = av_gettime_relative() / 1000000.0;
}
if (is->paused)
{
goto display;
printf("视频暂停is->paused");
}
/* compute nominal last_duration */
//lastvp上一帧,vp当前帧 ,nextvp下一帧
//last_duration 计算上一帧应显示的时长
last_duration = vp_duration(is, lastvp, vp);
// 经过compute_target_delay方法,计算出待显示帧vp需要等待的时间
// 如果以video同步,则delay直接等于last_duration。
// 如果以audio或外部时钟同步,则需要比对主时钟调整待显示帧vp要等待的时间。
delay = compute_target_delay(last_duration, is); // 上一帧需要维持的时间
time= av_gettime_relative()/1000000.0;
// is->frame_timer 实际上就是上一帧lastvp的播放时间,
// is->frame_timer + delay 是待显示帧vp该播放的时间
if (time < is->frame_timer + delay) {
//判断是否继续显示上一帧
// 当前系统时刻还未到达上一帧的结束时刻,那么还应该继续显示上一帧。
// 计算出最小等待时间
*remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
goto display;
}
// 走到这一步,说明已经到了或过了该显示的时间,待显示帧vp的状态变更为当前要显示的帧
is->frame_timer += delay; // 更新当前帧播放的时间
if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX) {
is->frame_timer = time; //如果和系统时间差距太大,就纠正为系统时间
}
SDL_LockMutex(is->pictq.mutex);
if (!isnan(vp->pts))
update_video_pts(is, vp->pts, vp->pos, vp->serial); // 更新video时钟
SDL_UnlockMutex(is->pictq.mutex);
//丢帧逻辑
if (frame_queue_nb_remaining(&is->pictq) > 1) {
//有nextvp才会检测是否该丢帧
Frame *nextvp = frame_queue_peek_next(&is->pictq);
duration = vp_duration(is, vp, nextvp);
if(!is->step // 非逐帧模式才检测是否需要丢帧 is->step==1 为逐帧播放
&& (framedrop>0 || // cpu解帧过慢
(framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) // 非视频同步方式
&& time > is->frame_timer + duration // 确实落后了一帧数据
) {
printf("%s(%d) dif:%lfs, drop frame\n", __FUNCTION__, __LINE__,
(is->frame_timer + duration) - time);
is->frame_drops_late++; // 统计丢帧情况
frame_queue_next(&is->pictq); // 这里实现真正的丢帧
//(这里不能直接while丢帧,因为很可能audio clock重新对时了,这样delay值需要重新计算)
goto retry; //回到函数开始位置,继续重试
}
}
........
}
遅延に焦点を当てる = compute_target_lay(last_duration, is);
static double compute_target_delay(double delay, VideoState *is)
{
double sync_threshold, diff = 0;
/* update delay to follow master synchronisation source */
/* 如果发现当前主Clock源不是video,则计算当前视频时钟与主时钟的差值 */
if (get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER) {
/* if video is slave, we try to correct big delays by
duplicating or deleting a frame
通过重复帧或者删除帧来纠正延迟*/
diff = get_clock(&is->vidclk) - get_master_clock(is);
/* skip or repeat frame. We take into account the
delay to compute the threshold. I still don't know
if it is the best guess */
sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN,
FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
if (!isnan(diff) && fabs(diff) < is->max_frame_duration) {
// diff在最大帧duration内
if (diff <= -sync_threshold) {
// 视频已经落后了
delay = FFMAX(0, delay + diff); // 上一帧持续的时间往小的方向去调整
}
else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD) {
// delay = 0.2秒
// diff = 1秒
// delay = 0.2 + 1 = 1.2
// 视频超前
//AV_SYNC_FRAMEDUP_THRESHOLD是0.1,此时如果delay>0.1, 如果2*delay时间就有点久
delay = delay + diff; // 上一帧持续时间往大的方向去调整
av_log(NULL, AV_LOG_INFO, "video: delay=%0.3f A-V=%f\n",
delay, -diff);
}
else if (diff >= sync_threshold) {
// 上一帧持续时间往大的方向去调整
// delay = 0.2 *2 = 0.4
delay = 2 * delay; // 保持在 2 * AV_SYNC_FRAMEDUP_THRESHOLD内, 即是2*0.1 = 0.2秒内
// delay = delay + diff; // 上一帧持续时间往大的方向去调整
} else {
// 音视频同步精度在 -sync_threshold ~ +sync_threshold
// 其他条件就是 delay = delay; 维持原来的delay, 依靠frame_timer+duration和当前时间进行对比
}
}
} else {
// 如果是以video为同步,则直接返回last_duration
}
av_log(NULL, AV_LOG_TRACE, "video: delay=%0.3f A-V=%f\n",
这个 delay, -diff);
return delay;
}
この関数は、計算されたビデオクロックとオーディオクロックの差を遅延と比較することにより、遅延の値を決定します。
ステップ:
-
差分を計算するには、ビデオ クロックとオーディオ クロックの値を使用します。
-
sync_threshold は、AV_SYNC_THRESHOLD_MIN と AV_SYNC_THRESHOLD_MIN の範囲の遅延の値であり、計算方法は次のとおりです。
sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
-
diff の差が設定された max_frame_duration よりも大きい場合、遅延は調整されずに直接返され、フレームをドロップするか前のフレームを続行するかの外部決定が行われます。
-
diff が max_frame_duration より小さい場合、再生時間を常に継続時間として固定できるわけではなく、より実際的な誤差によって決定されるため、遅延を調整する必要があります。たとえば、オーディオがビデオよりも 0.5 フレーム速い場合、ビデオの再生時間が長すぎることはできません。ビデオの再生時間を短くする必要があります。半フレーム遅い場合は、ビデオの再生時間を延長する必要があります。
-
if (diff <= -sync_threshold) { // 视频已经落后了 delay = FFMAX(0, delay + diff); // 上一帧持续的时间往小的方向去调整 } else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD) { // delay = 0.2秒 // diff = 1秒 // delay = 0.2 + 1 = 1.2 // 视频超前 //AV_SYNC_FRAMEDUP_THRESHOLD是0.1,此时如果delay>0.1, 如果2*delay时间就有点久 delay = delay + diff; // 上一帧持续时间往大的方向去调整 av_log(NULL, AV_LOG_INFO, "video: delay=%0.3f A-V=%f\n", delay, -diff); } else if (diff >= sync_threshold) { // 上一帧持续时间往大的方向去调整 // delay = 0.2 *2 = 0.4 delay = 2 * delay; // 保持在 2 * AV_SYNC_FRAMEDUP_THRESHOLD内, 即是2*0.1 = 0.2秒内 // delay = delay + diff; // 上一帧持续时间往大的方向去调整 } else { // 音视频同步精度在 -sync_threshold ~ +sync_threshold // 其他条件就是 delay = delay; 维持原来的delay, 依靠frame_timer+duration和当前时间进行对比 }
-
最初の if (diff <= -sync_threshold)
これは映像が遅れているという意味ですが、このときの差分は負の数なので遅延は小さくなるはずですが、遅延=FFMAX(0,遅延+差分)という方法を採用していますが、やはり0にはなりません。
-
2 番目の if (diff >= sync_threshold && 遅延 > AV_SYNC_FRAMEDUP_THRESHOLD)
これは、ビデオがオーディオよりも速く、表示時間が AV_SYNC_FRAMEDUP_THRESHOLD よりも長いことを意味します。その場合、取るべき対策は、遅延 = 遅延 + 差分を延長することです。
-
3 番目の場合 (diff >= sync_threshold) がオーディオより速く、表示時間が AV_SYNC_FRAMEDUP_THRESHOLD より短い場合、遅延 = 2 * 遅延が測定されます。
-
3.ビデオに基づいて
メディア ストリームにビデオ コンポーネントのみが含まれている場合は、ビデオがベースとして使用されます。
音声をベンチマークとして使用する場合は、フレームを落とすか待つという戦略が使用されますが、ビデオをベンチマークとして使用する場合は、単純にフレームを落とすことはできません。人は音に非常に敏感であり、一度音が鳴ると非常に簡単に感じられるためです。中断される!
3.1 オーディオメインプロセス
オーディオ同期戦略では、フレーム内のサンプル数を変更して再サンプリングし、可変速度効果を実現します。オーディオが遅い場合はサンプル数を減らし、オーディオが速い場合はサンプル数を増やします。
特定の実装については、audio_decode_frame を参照してください。
static int audio_decode_frame(VideoState *is)
{
int data_size, resampled_data_size;
int64_t dec_channel_layout;
av_unused double audio_clock0;
int wanted_nb_samples;
Frame *af;
if (is->paused)
return -1;
do {
// 若队列头部可读,则由af指向可读帧
if (!(af = frame_queue_peek_readable(&is->sampq)))
return -1;
frame_queue_next(&is->sampq);
} while (af->serial != is->audioq.serial);
// 根据frame中指定的音频参数获取缓冲区的大小 af->frame->channels * af->frame->nb_samples * 2
data_size = av_samples_get_buffer_size(NULL,
af->frame->channels,
af->frame->nb_samples,
af->frame->format, 1);
// 获取声道布局
dec_channel_layout =
(af->frame->channel_layout &&
af->frame->channels == av_get_channel_layout_nb_channels(af->frame->channel_layout)) ?
af->frame->channel_layout : av_get_default_channel_layout(af->frame->channels);
// 获取样本数校正值:若同步时钟是音频,则不调整样本数;否则根据同步需要调整样本数
wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples);
// is->audio_tgt是SDL可接受的音频帧数,是audio_open()中取得的参数
// 在audio_open()函数中又有"is->audio_src = is->audio_tgt""
// 此处表示:如果frame中的音频参数 == is->audio_src == is->audio_tgt,
// 那音频重采样的过程就免了(因此时is->swr_ctr是NULL)
// 否则使用frame(源)和is->audio_tgt(目标)中的音频参数来设置is->swr_ctx,
// 并使用frame中的音频参数来赋值is->audio_src
if (af->frame->format != is->audio_src.fmt || // 采样格式
dec_channel_layout != is->audio_src.channel_layout || // 通道布局
af->frame->sample_rate != is->audio_src.freq || // 采样率
// 第4个条件, 要改变样本数量, 那就是需要初始化重采样
(wanted_nb_samples != af->frame->nb_samples && !is->swr_ctx) // samples不同且swr_ctx没有初始化
) {
swr_free(&is->swr_ctx);
is->swr_ctx = swr_alloc_set_opts(NULL,
is->audio_tgt.channel_layout, // 目标输出
is->audio_tgt.fmt,
is->audio_tgt.freq,
dec_channel_layout, // 数据源
af->frame->format,
af->frame->sample_rate,
0, NULL);
if (!is->swr_ctx || swr_init(is->swr_ctx) < 0) {
av_log(NULL, AV_LOG_ERROR,
"Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!\n",
af->frame->sample_rate, av_get_sample_fmt_name(af->frame->format), af->frame->channels,
is->audio_tgt.freq, av_get_sample_fmt_name(is->audio_tgt.fmt), is->audio_tgt.channels);
swr_free(&is->swr_ctx);
return -1;
}
is->audio_src.channel_layout = dec_channel_layout;
is->audio_src.channels = af->frame->channels;
is->audio_src.freq = af->frame->sample_rate;
is->audio_src.fmt = af->frame->format;
}
if (is->swr_ctx) {
// 重采样输入参数1:输入音频样本数是af->frame->nb_samples
// 重采样输入参数2:输入音频缓冲区
const uint8_t **in = (const uint8_t **)af->frame->extended_data; // data[0] data[1]
// 重采样输出参数1:输出音频缓冲区尺寸
uint8_t **out = &is->audio_buf1; //真正分配缓存audio_buf1,指向是用audio_buf
// 重采样输出参数2:输出音频缓冲区
int out_count = (int64_t)wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate
+ 256;
int out_size = av_samples_get_buffer_size(NULL, is->audio_tgt.channels,
out_count, is->audio_tgt.fmt, 0);
int len2;
if (out_size < 0) {
av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size() failed\n");
return -1;
}
// 如果frame中的样本数经过校正,则条件成立
if (wanted_nb_samples != af->frame->nb_samples) {
int sample_delta = (wanted_nb_samples - af->frame->nb_samples) * is->audio_tgt.freq
/ af->frame->sample_rate;
int compensation_distance = wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate;
// swr_set_compensation
if (swr_set_compensation(is->swr_ctx,
sample_delta,
compensation_distance) < 0) {
av_log(NULL, AV_LOG_ERROR, "swr_set_compensation() failed\n");
return -1;
}
}
av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_size);
if (!is->audio_buf1)
return AVERROR(ENOMEM);
// 音频重采样:返回值是重采样后得到的音频数据中单个声道的样本数
len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples);
if (len2 < 0) {
av_log(NULL, AV_LOG_ERROR, "swr_convert() failed\n");
return -1;
}
if (len2 == out_count) {
av_log(NULL, AV_LOG_WARNING, "audio buffer is probably too small\n");
if (swr_init(is->swr_ctx) < 0)
swr_free(&is->swr_ctx);
}
// 重采样返回的一帧音频数据大小(以字节为单位)
is->audio_buf = is->audio_buf1;
resampled_data_size = len2 * is->audio_tgt.channels * av_get_bytes_per_sample(is->audio_tgt.fmt);
} else {
// 未经重采样,则将指针指向frame中的音频数据
is->audio_buf = af->frame->data[0]; // s16交错模式data[0], fltp data[0] data[1]
resampled_data_size = data_size;
}
audio_clock0 = is->audio_clock;
/* update the audio clock with the pts */
if (!isnan(af->pts))
is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;
else
is->audio_clock = NAN;
is->audio_clock_serial = af->serial;
return resampled_data_size;
}
ここでは、wanted_nb_samples の値を計算し、synchronize_audio 関数を分析する方法に焦点を当てます。
static int synchronize_audio(VideoState *is, int nb_samples)
{
int wanted_nb_samples = nb_samples;
/* if not master, then we try to remove or add samples to correct the clock */
if (get_master_sync_type(is) != AV_SYNC_AUDIO_MASTER) {
//不是以音频为基准时
double diff, avg_diff;
int min_nb_samples, max_nb_samples;
diff = get_clock(&is->audclk) - get_master_clock(is);//过去音频和默认时钟的差值
if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD) {
//如果差值大于AV_NOSYNC_THRESHOLD则正常播放,不做任何处理,一般这种情况就出现错误了
is->audio_diff_cum = diff + is->audio_diff_avg_coef * is->audio_diff_cum;
if (is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {
/* not enough measures to have a correct estimate */
is->audio_diff_avg_count++; // 连续20次不同步才进行校正
} else {
/* estimate the A-V difference */
avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);
// avg_diff = diff;
if (fabs(avg_diff) >= is->audio_diff_threshold) {
wanted_nb_samples = nb_samples + (int)(diff * is->audio_src.freq);
min_nb_samples = ((nb_samples * (100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100));
max_nb_samples = ((nb_samples * (100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100));
// av_clip 用来限制wanted_nb_samples最终落在 min_nb_samples~max_nb_samples
// nb_samples *(90%~110%)
wanted_nb_samples = av_clip(wanted_nb_samples, min_nb_samples, max_nb_samples);
}
av_log(NULL, AV_LOG_INFO, "diff=%f adiff=%f sample_diff=%d apts=%0.3f %f\n",
diff, avg_diff, wanted_nb_samples - nb_samples,
is->audio_clock, is->audio_diff_threshold);
}
} else {
// > AV_NOSYNC_THRESHOLD 阈值,该干嘛就干嘛
/* too big difference : may be initial PTS errors, so
reset A-V filter */
is->audio_diff_avg_count = 0;
is->audio_diff_cum = 0; // 恢复正常后重置为0
}
}
return wanted_nb_samples;
}
コード内の audio_diff_cum は加重合計を計算するためのものです
まず、audio_diff_avg_coef とは何かを見てみましょう。
is->audio_diff_avg_coef = exp(log(0.01) / AUDIO_DIFF_AVG_NB)//exp 自然常数
この値は固定されており、加重合計を計算する際の固定比率となります。
注意深く分析してください: s->audio_diff_cum = diff + is->audio_diff_avg_coef * is->audio_diff_cum;
この数値は最終的に AUDIO_DIFF_AVG_NB 回循環して加重合計を取得します。
一方でavg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);
これは重み付け和/重み付け和によって得られた結果 avg_diff であり、avg_diff は後で差分比較として使用されます。
is->audio_diff_threshold = (double)(is->audio_hw_buf_size) / is->audio_tgt.bytes_per_sec;
この値は、差分を計算するためのしきい値時間です。この値より大きい場合は、同期を実行する必要があります~
同期アルゴリズムはシンプルです
diff*サンプリングレートを通じて差分のサンプル数を取得します。
wanted_nb_samples = nb_samples + (int)(diff * is->audio_src.freq);//获取想要的样本数
min_nb_samples = ((nb_samples * (100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100));//样本数减少10%的样本数
max_nb_samples = ((nb_samples * (100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100));//样本数增加10%的样本数
// av_clip 用来限制wanted_nb_samples最终落在 min_nb_samples~max_nb_samples
// nb_samples *(90%~110%)
wanted_nb_samples = av_clip(wanted_nb_samples, min_nb_samples, max_nb_samples);//av_clip就是取中间值,如果大于max就取max,小于min就取min,反正就是将wanted_nb_samples保证在nb_samples *(90%~110%)
そして、audio_decode_frame に達するとリサンプリングが実行され、サンプル補正関数である swr_set_compensation が直接呼び出されます。
4. 外部クロックに基づく
外部クロックは最初の 2 つのクロックに基づいています
sync_ Clock_to_slave 関数経由で設定
static void sync_clock_to_slave(Clock *c, Clock *slave)
{
double clock = get_clock(c);
double slave_clock = get_clock(slave);
if (!isnan(slave_clock) && (isnan(clock) || fabs(clock - slave_clock) > AV_NOSYNC_THRESHOLD))
set_clock(c, slave_clock, slave->serial);
}
ここで c は外部クロック、slave は他のクロックです。他のクロックが設定されていて外部クロックが設定されていない場合、または両方が設定されているがその差が AV_NOSYNC_THRESHOLD よりも大きい場合は、リセットする必要があることがわかります。
したがって、基本的に外部クロックは 1 回だけ設定されることがわかりますが、具体的な設定は最初のフレームがオーディオかビデオかによって異なり、それらのクロックを使用して設定できます。
要約する
具体的な基礎は、設定したパラメータを確認し、get_master_ Clock を通じてマスター クロックを取得することです。これにより、3 つの主要なクロックがすべて設定されます。
各時計設定位置:
オーディオクロックは次のとおりです。
set_clock_at(&is->audclk, is->audio_clock -
(double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size)
/ is->audio_tgt.bytes_per_sec,
is->audio_clock_serial,
audio_callback_time / 1000000.0);
ビデオクロックは次のとおりです。
if (!isnan(vp->pts))
update_video_pts(is, vp->pts, vp->pos, vp->serial);
外部クロックは次のとおりです。
オーディオクロック設定の次の部分:
set_clock_at(&is->audclk, is->audio_clock -
(double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size)
/ is->audio_tgt.bytes_per_sec,
is->audio_clock_serial,
audio_callback_time / 1000000.0);
sync_clock_to_slave(&is->extclk, &is->audclk);
ビデオクロック設定の update_video_pts 関数もあります。
static void update_video_pts(VideoState *is, double pts, int64_t pos, int serial) {
/* update current video pts */
set_clock(&is->vidclk, pts, serial);
sync_clock_to_slave(&is->extclk, &is->vidclk);
}