Analyse du lecteur ffplay (6) ---- Analyse de synchronisation audio et vidéo

1. Bases de la synchronisation audio et vidéo

La vidéo et l'audio sont des fils différents, et les images audio et vidéo des mêmes points ne seront pas décodées en même temps, une synchronisation audio et vidéo est donc requise ;

1.1 Stratégie de synchronisation audio et vidéo

  1. Basé sur l'audio
    • Si la vidéo est lente, certaines images vidéo seront perdues (l'expérience visuelle perd des images)
    • Lorsque la vidéo est trop rapide, continuez le rendu de l'image précédente.
  2. Basé sur la vidéo
    • Si l'audio est lent, la lecture sera accélérée (ou des images seront supprimées. Si des images sont supprimées, le son sera interrompu et l'expérience sera particulièrement mauvaise)
    • Si l'audio est trop rapide, ralentissez le point de données (ou répétez l'image précédente)
    • Changer la vitesse audio implique un rééchantillonnage
  3. Basé sur une horloge externe
    • Complet 1 2, changez la vitesse de lecture en fonction de l'horloge externe
  4. Diverses sorties vidéo et audio ne sont pas synchronisées (généralement pas comme ça)

Puisque les gens sont plus sensibles aux changements auditifs qu’aux changements visuels, l’audio est souvent utilisé comme référence lors du choix d’une stratégie de synchronisation !

1.2 Concept de synchronisation audio et vidéo

  1. DTS (Decoding Time Stamp) : horodatage de décodage, qui indique au joueur quand décoder cette trame de données.
  2. PTS (Presentation Time Stamp) : affiche l'horodatage, qui indique au joueur quand lire cette image de données
  3. time_base base de temps : c'est l'unité utilisée par FFmpeg

S'il n'y a pas d'images B dans la vidéo, l'ordre du DTS et du PTS sera le même.

La structure time_base est :

typedef struct AVRational
    int num; ///< 分子
    int den; ///< 分母
} AVRational;

Calculer l'horodatage :

timestame(seconde)=pts*av_qtd(st->time_base)

Calculer la durée de la trame :

temps(seconde)=durée*av_qtd(st->time_base)

Conversion de base de temps différente :

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)

Le concept "d'horloge" est nécessaire lors de la synchronisation de l'audio et de la vidéo. Les horloges audio, vidéo et externes ont toutes leur propre horloge. Chacune règle sa propre horloge, et qui est la référence pour obtenir l'horloge ?

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;

Comment fonctionne l'horloge :

  1. Vous devez constamment appeler set_clock_at pour "corriger l'heure", ce qui nécessite pts, série, heure (heure système)

  2. Le temps obtenu est également une estimation, et l'estimation est estimée via pts_drift de "time ajuster".

La chronologie de la figure augmente en fonction du temps. Nous appelons set_Clock pour ajuster l'heure. Supposons que pts est en retard sur le temps, alors pts_drift=pts-time, et calculons la différence entre pts et le temps.

Après un certain temps, vous avez besoin de get_clock pour obtenir des pts, puis vous pouvez le calculer grâce à la différence pts_drift et time tout à l'heure, pts=time+pts_drift, le temps peut être obtenu via la fonction av_gettime_relative fournie par ffmpeg

1.3 Unités de temps dans FFmpeg

OFF_TIME_BASE

  • #définir AV_TIME_BASE 1000000
  • Unité de synchronisation interne FFmpeg

AV_TIME_BASE_Q

  • #define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}
  • La représentation fractionnaire de la base de temps interne de FFmpeg est l'inverse de AV_TIME_BASE

Formule de conversion de base de temps

  • horodatage (horodatage interne de FFmpeg) = AV_TIME_BASE*heure (secondes)
  • heure (seconde) = horodatage * AV_TIME_BASE_Q

1.4 Analyse base_temps/durée de différentes structures

FFmpeg possède de nombreuses bases de temps, correspondant à différentes structures, et la valeur de chaque base de temps est différente.

  • AVFormatContext

    • durée : représente la longueur de l'intégralité du flux de code. Pour obtenir la durée normale, vous devez la diviser par AV_TIME_BASE . Le résultat est en secondes.
  • AVStream

    • time_base : L'unité est la seconde, par exemple, l'audio AAC peut être {1,44100}, le flux TS peut être {1,90KHZ}
    • durée : indique la durée du flux de données, l'unité est AVStream->time_base
  • La base de temps d'AVStream est définie dans dumuxer ou multiplexeur

    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 Analyse des pts/dts de différentes structures

L'heure AVPacket et AVFrame proviennent d'AVStream-> time_base

Le point principal ici est que la base de temps de chaque flux lors de l'encodage est définie après avformat_write_header. Le code source spécifique ne sera pas analysé pour le moment, mais ne vous laissez pas entraîner dans l'encodage et ne savez pas d'où vient time_base !

1.6 Analyse de la structure du Frame dans 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;

Pourquoi les pts et la durée dans la structure Frame sont-ils de double type ?

Parce que nous avons converti les pts et la durée lors de l'écriture de la trame, qui est implémentée dans la fonction queue_picture, les unités de pts et de durée à ce moment doivent être des secondes !

1.7 Acquisition et correction du PTS Vidoe Frame

correction de points

frame->pts=frame->best_effort_timestamp;

/**
*使用各种启发式估计的帧时间戳,以流时基为单位
*-编码:未使用
*-解码:由libavcodec设置,由用户读取。
*/
int64_t best_effort_timestamp;

Corrigez les points de la trame. L'algorithme de correction spécifique est défini par libavcodec. L'horodatage de la trame est fondamentalement le même que celui des points. Si les points actuels ont une valeur déraisonnable, une série d'étalonnages sera tentée pour obtenir cette valeur plus raisonnable.

1.8 Obtention du PTS de la trame audio

ffplay a 3 pts de conversions

  1. Converti de AVStream->time_base en {1, taux d'échantillonnage}

    Je pense que c'est le rééchantillonnage qui fait changer la fréquence d'échantillonnage. Si vous utilisez également AVStream->time_base, une erreur se produira !

    frame->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);
    
  2. Convertissez le taux d'échantillonnage en secondes, ce qui est nécessaire lorsque queue_picture est placé dans la file d'attente d'images.

    af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
    
  3. Ajustements estimés en fonction de la quantité de données copiées vers 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. Basé sur l'audio

ffplay adopte le mode de synchronisation audio par défaut et le réglage de l'horloge du mode de synchronisation audio est défini dans 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);
    }
}

C'est le même que le fil de lecture vidéo que j'ai analysé précédemment. Vous pouvez y jeter un oeil.

Le processus suivant est le processus principal de lecture vidéo. Vous pouvez également lire l'explication précédente.

]

Le fait est qu'il est plus important de calculer la durée d'affichage de l'image précédente. Jetez un œil au code :

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; //回到函数开始位置,继续重试
                }
            }
........
}

Concentrez-vous sur le délai = calculate_target_delay(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;
}

Cette fonction détermine la valeur du retard en comparant la différence calculée entre l'horloge vidéo et l'horloge audio avec le retard.

étape:

  1. Pour calculer la différence, utilisez la valeur de l'horloge vidéo-horloge audio

  2. sync_threshold est la valeur du délai comprise entre AV_SYNC_THRESHOLD_MIN et AV_SYNC_THRESHOLD_MIN. La méthode de calcul est la suivante :

     sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN,
                                   FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
    
  3. Si la différence de diff est supérieure à la valeur max_frame_duration définie, le délai sera directement renvoyé sans aucun ajustement. Il y aura une décision externe d'abandonner la trame ou de continuer avec la trame précédente.

  4. Si diff est inférieur à max_frame_duration, alors nous devons ajuster le délai, car le temps de lecture ne peut pas toujours être fixé comme durée, mais est déterminé par des erreurs plus pratiques. Par exemple, si l'audio est une demi-image plus rapide que la vidéo, alors le temps de lecture de la vidéo ne peut pas être trop long. , il doit être raccourci. S'il est d'une demi-image plus lent, le temps de lecture vidéo devra être prolongé.

  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和当前时间进行对比
                }
    
    • Le premier if (diff <= -sync_threshold)

      Cela signifie que la vidéo est en retard. Le diff à ce moment est un nombre négatif, donc le retard devrait devenir plus petit. La méthode adoptée est delay = FFMAX (0, delay + diff); après tout, il ne peut pas être 0.

    • 第二个if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD)

      Cela signifie que la vidéo est plus rapide que l'audio et que le temps d'affichage est supérieur à AV_SYNC_FRAMEDUP_THRESHOLD, alors la mesure à prendre est d'étendre delay = delay + diff ;

    • Le troisième si (diff >= sync_threshold) est plus rapide que l'audio, et que le temps d'affichage est inférieur à AV_SYNC_FRAMEDUP_THRESHOLD, la mesure prise est delay = 2 * delay

3. Basé sur la vidéo

Lorsque le flux média ne contient que des composants vidéo, la vidéo servira de base !

Lorsque vous utilisez l'audio comme référence, la stratégie consistant à supprimer des images ou à attendre est utilisée, mais lorsque vous utilisez la vidéo comme référence, vous ne pouvez pas simplement supprimer des images, car les gens sont très sensibles au son, et il est très facile de le ressentir une fois que le son est entendu. est interrompu !

3.1 Processus principal audio

Insérer la description de l'image ici

La stratégie de synchronisation audio consiste à rééchantillonner, en modifiant le nombre d'échantillons dans une image pour obtenir un effet de vitesse variable. Si l'audio est lent, réduisez le nombre d'échantillons. Si l'audio est rapide, augmentez le nombre d'échantillons.

Voir audio_decode_frame pour une implémentation spécifique :

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;
}

Ici, nous nous concentrons sur la façon de calculer la valeur de wanted_nb_samples et d'analyser la fonction synchroniser_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 dans le code consiste à calculer la somme pondérée

Voyons d’abord ce qu’est audio_diff_avg_coef ?

is->audio_diff_avg_coef  = exp(log(0.01) / AUDIO_DIFF_AVG_NB)//exp 自然常数

Cette valeur est fixe et constitue un ratio fixe lors du calcul de la somme pondérée.

Analysez-le attentivement : s->audio_diff_cum = diff + is->audio_diff_avg_coef * is->audio_diff_cum ;

Ce nombre finira par parcourir AUDIO_DIFF_AVG_NB fois pour obtenir la somme pondérée.

而avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);

C'est le résultat avg_diff obtenu par la somme pondérée/somme pondérée, et avg_diff est utilisé plus tard comme comparaison de différence.

is->audio_diff_threshold = (double)(is->audio_hw_buf_size) / is->audio_tgt.bytes_per_sec;

Cette valeur est le temps seuil de calcul de l'écart. Si elle est supérieure à cette valeur, une synchronisation doit être effectuée ~

L'algorithme de synchronisation est simple

Obtenez le nombre d'échantillons de la différence grâce au taux d'échantillonnage 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%)

Ensuite, lorsque audio_decode_frame est atteint, un rééchantillonnage sera effectué. Le rééchantillonnage appellera directement swr_set_compensation, qui est la fonction de compensation d'échantillon !

4. Basé sur une horloge externe

L'horloge externe est basée sur les deux premières horloges

Réglé via la fonction 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);
}

Ici c est l'horloge externe et l'esclave est une autre horloge. On peut voir que lorsque d'autres horloges sont réglées et que l'horloge externe n'est pas réglée, ou qu'elles sont toutes les deux réglées mais que la différence est supérieure à AV_NOSYNC_THRESHOLD, elle doit être réinitialisée.

Par conséquent, on peut voir que l'horloge externe ne sera réglée qu'une seule fois. Le réglage spécifique dépend du fait que la première image soit audio ou vidéo et peut être réglé à l'aide de leur horloge.

Résumer

La base spécifique est de regarder les paramètres que vous définissez, puis d'obtenir l'horloge principale via get_master_clock, donc les trois horloges principales seront réglées !!!

Chaque position de réglage de l'horloge :

L'horloge audio est :

 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);

L'horloge vidéo est :

 if (!isnan(vp->pts))
                update_video_pts(is, vp->pts, vp->pos, vp->serial);

L'horloge externe est :

La partie suivante des paramètres de l'horloge audio :

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);

Il existe également la fonction update_video_pts du réglage de l'horloge vidéo :

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);
}

Je suppose que tu aimes

Origine blog.csdn.net/m0_60565784/article/details/131896496
conseillé
Classement