1. 程式人生 > >錄影音視訊同步原理分析及PTS計算公式

錄影音視訊同步原理分析及PTS計算公式

圖解分析

音視訊同步要分別保證開始的PTS一樣,PTS是控制幀的顯示時間的,所以要實現音視訊同步必須分別設定音視訊的PTS。


注:音、視訊最後一幀的PTS時刻不一定相同。

1. 視訊時間戳計算

pts = count++ *(1000/fps);  //其中count初始值為0,每次打完時間戳count加1.

//在ffmpeg,中的程式碼為
pkt.pts= count++ * (Ctx->time_base.num * 1000 / Ctx->time_base.den);

2. 音訊時間戳

pts = count++ * (frame_size * 1000 / sample_rate)

//在ffmpeg中的程式碼為
pkt.pts
= count++ * (Ctx->frame_size * 1000 / Ctx->sample_rate);

FFmpeg例子

視訊:

 // 視訊幀的播放時間依賴pts欄位,音訊和視訊都有自己單獨的pts
            // 音視訊同步要以視訊或音訊為參考標準,然後控制延時來保證音視訊的同步,最簡單的是以音訊為準同步視訊
            // 錄音編碼時pts是一定的,由編碼器以相同頻率寫入,音視訊錄製時總時長是一定的,都在同一個時間座標序列上,但長度不一定一樣
            // 計算音視訊pts,都是根據每秒算出有多少個音視訊幀, 然後根據time_base,算出每幀佔有多少個時間單位,即為pts差值
            long pts = 0;

            // 計算視訊公式: pkt.pts = count++ * (Ctx->time_base.num * 1000 / Ctx->time_base.den);
            //pts = (codec.Framecnt - 1) * (codec.GetCodecCtx()->pkt_timebase.num * 1000 / codec.GetCodecCtx()->pkt_timebase.den);
            pts = (codec.Framecnt - 1) * (codec.GetCodecCtx()->pkt_timebase.num * 1000 / 25);
            Console.WriteLine("...........video...pts:" + pts);
            // 初始化音視訊共同的timer
            CvNetVideo.Play.AVPtsTimer.Init();
            // 調整時間戳與時間軸跑過的timer之間的延時
            while (CvNetVideo.Play.AVPtsTimer.Timer<pts)
            {
                Thread.Sleep(1);
                CvNetVideo.Play.AVPtsTimer.Sleep_Count++;
                Console.WriteLine("...........video...sleep count:" + CvNetVideo.Play.AVPtsTimer.Sleep_Count);
            }

音訊:

 #region 設定音訊的pts
                // 計算音訊PTS公式:pkt.pts= count++ * (Ctx->frame_size * 1000 / Ctx->sample_rate);
               
                long pts = frame_count++ * (output_codec_context->frame_size * 1000 / output_codec_context->sample_rate);
                // 初始化音視訊共同的timer
                CvNetVideo.Play.AVPtsTimer.Init();
                // 調整時間戳與時間軸跑過的timer之間的延時
                while (CvNetVideo.Play.AVPtsTimer.Timer < pts)
                {
                    Thread.Sleep(1);
                    CvNetVideo.Play.AVPtsTimer.Sleep_Count++;
                    Console.WriteLine("...........audio...sleep count:" + CvNetVideo.Play.AVPtsTimer.Sleep_Count);
                }
                frame->pts = pts;
                frame->pkt_dts = pts;
                #endregion
  
                Console.WriteLine("...........audio...pts:"  + pts);

Timer時間戳計算:

 /// <summary>
    /// PTS-Timer
    /// </summary>
    public class AVPtsTimer
    {
        /// <summary>
        /// 初始化時的時間戳
        /// </summary>
        private static long Start_Stamp { get; set; }

        public static bool IsInit = false;

        /// <summary>
        /// 休眠次數
        /// </summary>
        public static long Sleep_Count { get; set; }

        /// <summary>
        /// 時間軸跑過的時間
        /// </summary>
        public static long Timer { get { return ConvertDataTimeToLong(DateTime.Now) - Start_Stamp; } }

        public static void Init()
        {
            if (IsInit)
            {
                return ;
            }
            Start_Stamp = ConvertDataTimeToLong(DateTime.Now);
            IsInit = true;
        }

        /// <summary>
        /// 計算時間戳
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static long ConvertDataTimeToLong(DateTime dt)
        {
            DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            TimeSpan toNow = dt.Subtract(dtStart);
            long timeStamp = toNow.Ticks;
            timeStamp = long.Parse(timeStamp.ToString().Substring(0, timeStamp.ToString().Length - 4));
            return timeStamp;
        }
    }

注:音視訊同步仍然存在瑕疵,時間戳和延時已處理,但視訊還是存在不同步的情況,應該是計算過程中直接使用的每秒25幀的值的問題,具體是多少還得再看。

上文中的程式碼是可以執行的但是中間的值是有問題的,程式碼如下:

pts = (codec.Framecnt - 1) * (codec.GetCodecCtx()->pkt_timebase.num * 1000 / codec.GetCodecCtx()->pkt_timebase.den);

int den=codec.GetCodecCtx()->pkt_timebase.den;

den=12800;//導致執行的時候pts一直為0,所以才改為的25幀每秒(25幀是我程式碼中視訊錄製的預設值)。