音視頻的同步-轉載

使用timeBeginPeriod設置Sleep精度php

    timeBeginPeriod(1);
    timeEndPeriod(1); linux

1.   音視頻同步的原理編程

 2.  音視頻同步的較正方法緩存

 3.  音視頻同步時遇到的問題安全

 

 

聲明:如下全部內容均爲轉載多線程

 

 

 1.原文轉自:http://bbs.chinavideo.org/viewthread.php?tid=1183&extra=page%3D1%26amp%3Bfilter%3Ddigest&page=1ide

 

下面貼出部分:函數

視頻同步這一塊,我一直不是很瞭解,很想嘗試一下,下面是在qq羣上向諸位前人請教的會話記錄,由於對我這樣的初學者頗有幫助,因此粘貼至此,以做備份,再次感謝熱心回答問題的人,尤爲是(不作好人!)^_^;

2006-10-25 10:21:06  dophin
有人有實時編碼時對音視頻進行錄製時,音視頻如何同步的相關資料麼?有相關網站也能夠哈,我實在找不到相關資料了。。。
2006-10-25 10:25:42  dophin
或者具體一點的也行,好比音視頻究竟是如何同步的,有書籍介紹也能夠啊,先謝了哈。。。。
(人衆人)  2006-10-25 10:21:45
經過時間戳
(人衆人)  2006-10-25 10:21:49
我也在研究這個問題
( 新左派)  2006-10-25 10:21:56
音頻時間爲主
(人衆人)  2006-10-25 10:22:01

(不作好人!)  2006-10-25 10:22:08
新左派講對了。
(不作好人!)  2006-10-25 10:22:14
聲音圖象交錯發送。
( 新左派)  2006-10-25 10:22:43
視頻解碼時,按當前播放時間找到對應的視頻幀
(不作好人!)  2006-10-25 10:22:44
能夠一個音頻包有N個圖象。就在這一個音頻包完成的過程當中按幀率顯示圖象。
(dophin)  2006-10-25 10:28:39
嗯,那麼音頻又是以什麼爲主呢?畢竟,音頻的播放也是和時間有關係的,音頻在播放時就是隻管本身播放就行麼?
(不作好人!)  2006-10-25 10:24:05
聲卡有時間同步處理機制的。
(不作好人!)  2006-10-25 10:24:32
我之前的電腦主板若是驅動沒有裝好。聲音就很是快。結果圖象也是一閃就過去了。
( 新左派)  2006-10-25 10:24:47
聲音正常解碼與播放啊
(不作好人!)  2006-10-25 10:25:05
是WINDOWS MEDIAPLAYER播放的。只有驅動裝好了。能正確驅動的時候聲卡有時鐘同步功能的。
( 新左派)  2006-10-25 10:25:14
只是視頻的解碼須要參照當前的聲音的時間
(不作好人!)  2006-10-25 10:25:16
因此聲音和圖象交錯,
( 新左派)  2006-10-25 10:25:36
聲音播放時,是不用管什麼的,就是單獨的聲音解碼
(dophin)  2006-10-25 10:32:16
哦,我如今大腦中大體有點模型了,就是音頻只管本身播放就行,視頻根據本身自己帶的時間戳與但前系統時間和音頻時間進行比較,而後解碼播放,如此實現同步,這麼理解對麼?
(不作好人!)  2006-10-25 10:28:00
你是用什麼系統?
(dophin)  2006-10-25 10:33:26
聲音和圖像交錯是什麼意思?這兩個不是開兩個線程完成的麼?不存在交錯問題啊,即便存在,也是操做系統級的吧 ?
(不作好人!)  2006-10-25 10:28:35
AVVVVVVVVVVVVVVVAVVVVVVVVVVVVVVV
(dophin)  2006-10-25 10:33:42
我得,linux。。。
(不作好人!)  2006-10-25 10:28:52
當你放A的時候,直接交給聲卡。
(不作好人!)  2006-10-25 10:29:30
中間的V就是先後兩個聲音包的相差時間,你就算出平均速度
(dophin)  2006-10-25 10:35:35
算出 平均速度後,得出每sec放幾幀,而後播放v,是這樣麼?
(不作好人!)  2006-10-25 10:30:54

(不作好人!)  2006-10-25 10:31:01
你聲音有一個採樣率吧。
(dophin)  2006-10-25 10:36:02
對,有,
(不作好人!)  2006-10-25 10:31:23
這就能夠算出先後一個聲音的時間了對不?
(不作好人!)  2006-10-25 10:31:36
好比44.1K/S
(dophin)  2006-10-25 10:36:36
對,
(不作好人!)  2006-10-25 10:31:56
那你從DSP中讀取22.05K的數據是否是0.5?
(dophin)  2006-10-25 10:37:05
是,
(不作好人!)  2006-10-25 10:32:29
那在這段0.5秒鐘的時間內你獲取了15幀的數據。那你是否是0.5/15=0.03333秒鐘就刷新一副圖。
(dophin)  2006-10-25 10:37:49
沒錯。
(不作好人!)  2006-10-25 10:32:55
而後你再讀取下一個聲音和圖象包。再這樣搞。就能夠了啦。不過前提條件你採集必須是同步的。
(不作好人!)  2006-10-25 10:33:11
對了。你是用嵌入式的嗎?
(不作好人!)  2006-10-25 10:33:25
這個是以聲音爲基礎的。
(不作好人!)  2006-10-25 10:34:04
還有一種是設置時鐘,計算你的幀率來設置先後幀的時間,中間有偏差就延時或者是跳躍一下。聲音就另外單獨管理。
(不作好人!)  2006-10-25 10:34:21
看看ffmpeg 的fplay.c,裏面的源程序講得很詳細了。
(dophin)  2006-10-25 10:40:12
個人不是embedded
(dophin)  2006-10-25 10:40:24
就是普通pc機,
(不作好人!)  2006-10-25 10:36:08
哦。那你看fplay.c吧。裏面很詳細的有聲音和圖象的同步。雖然簡單了一點。但麻雀雖小五臟俱全。
(dophin)  2006-10-25 10:41:54
哦,太好了,採集這一塊,我基本明白了,播放我想也應該差很少,我本身試試看看,太感謝了你了哈。。。^_^
(不作好人!)  2006-10-25 10:37:14
別謝謝我。
(不作好人!)  2006-10-25 10:37:24
不過我作的方法是按照fplay.c裏面的作的。
(dophin)  2006-10-25 10:42:23
好的,我立刻就看看fplay。c,之前沒有看是由於感受他的播放效果不如mplayer,
(不作好人!)  2006-10-25 10:38:06
這個是簡化了的啦。固然比不上mplayer性能

 
 
2.
mplayer播放時的大循環過程爲:
while(!mpctx->eof){

  fill_audio_out_buffers();//音頻stream的讀取,解碼,播放
  update_video(&blit_frame);//視頻stream的讀取,解碼,過濾處理
  sleep_until_update(&time_frame, &aq_sleep_time);//計算延遲時間並睡眠等待
  mpctx->video_out->flip_page();//視頻的播放
  adjust_sync_and_print_status(frame_time_remaining, time_frame);//根據音視頻的PTS作同步矯正處理

}

音視頻同步方法爲
1)音頻播放playsize = mpctx->audio_out->play(sh_audio->a_out_buffer, playsize,  playflags);  後,根據數據大小算出時間並累計
mpctx->delay += playback_speed*playsize/(double)ao_data.bps;
2)視頻解碼前,用累計延遲時間剪掉本禎視頻的時間mpctx->delay -= frame_time;
3)計算聲音延遲時間*time_frame = delay - mpctx->delay / playback_speed;
其中float delay = mpctx->audio_out->get_delay();爲距當前聲音OUTPUT BUF裏數據被所有播放完爲止所需的時間。
4)播放視頻同步完成,因此視頻的播放是徹底根據聲卡最後的數據輸出來同步的。
5)計算出當前音視頻PTS差double AV_delay = a_pts - audio_delay - v_pts;再算出矯正值x = (AV_delay + timing_error * playback_speed) * 0.1f;最後把矯正的時間加到延遲累計中mpctx->delay+=x;。
 
3.

這幾天搞文件回放,視頻格式是 H264,音頻是PCM,使用FFMPEG來讀取音視頻,而後用ffmpeg來解碼顯示,全部的一切還算順利,但音視頻同步花了我不少時間,總也搞不清楚 爲何會差不少。音視頻同步的原理固然是根據音頻的pts來控制視頻的播放,也就是說在視頻解碼一幀後,是否顯示以及顯示多長時間是經過該幀的PTS與同 時正在播放的音頻的PTS比較而來的,若是音頻的PTS較大,則視頻顯示完畢準備下一幀的解碼顯示,不然等待。
        具體實現時遇到的問題一:沒辦法獲得正在播放的音頻幀的PTS,由於進行音頻播放使用的DirectSound,而對於DirectSound我只能獲得 當前拷入DirectSound的緩存的幀的PTS,而沒法獲得正在播放的PTS,若是得不到正在播放的幀的PTS的話,那同步確定是不可能的了。在網上 找資料好象也沒找到有用的,最後忽然想到因爲音頻幀的大小與時間成正比,那麼DirectSound的緩存中的數據所須要的播放時間就能夠計算得出,再根 據當前正在拷入的音頻幀的PTS,就能夠獲得正在播放的幀的PTS,再用這個就能夠正確同步視頻幀的顯示了。
        問題二:根據上面的方法處理後仍是出現不一樣步的現象,爲何這樣我也是百思不得其解,後來才發現是等待機制有問題,原來我是用Sleep()來作等待的, 但實際上Sleep()的偏差很大的,網上有說有15MS,作音視頻同步確定是不行的了,通過不斷的google,找到一份代碼:
void MySleep(int interval)
{
LARGE_INTEGER litmp; 
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim; 
QueryPerformanceFrequency(&litmp);
dfFreq = (double)litmp.QuadPart;// 得到計數器的時鐘頻率
QueryPerformanceCounter(&litmp);
QPart1 = litmp.QuadPart;// 得到初始值
網站

do
{
   QueryPerformanceCounter(&litmp);
   QPart2 = litmp.QuadPart;//得到停止值
   dfMinus = (double)(QPart2-QPart1);
   dfTim = dfMinus / dfFreq;// 得到對應的時間值,單位爲秒
}while(dfTim<0.001 * interval);
}
       能夠達到精度比較高的等待,從效果看,也能夠達到音視頻同步。
       本覺得問題到這就算結束了,但程序運行的時候怎麼發現機器這麼慢呀,看了下CPU佔用率,達到100%。很顯然使用這個作等待是不行的了。
       因而繼續google,網上有說timesetevent什麼的,我沒有試。感受麻煩了些。後來想到之前看過的一篇用 WaitForSingleObject來作定時讓某段代碼執行的,因而試了試,一試之下當即發現效果明顯,CPU佔用率一會兒回到了個位數。更改後的代 碼以下:
void MySleep(int interval)
{
HANDLE evt;
evt = CreateEvent(NULL, TRUE, FALSE, NULL);
WaitForSingleObject(evt, interval);
CloseHandle(evt);
}

 

4.如下轉至 http://blog.csdn.net/stone_kingnet/article/details/3111171

 

媒 體內容在播放時,最使人頭痛的就是音視頻不一樣步。從技術上來講,解決音視頻同步問題的最佳方案就是時間戳:首先選擇一個參考時鐘(要求參考時鐘上的時間是 線性遞增的);生成數據流時依據參考時鐘上的時間給每一個數據塊都打上時間戳(通常包括開始時間和結束時間);在播放時,讀取數據塊上的時間戳,同時參考當 前參考時鐘上的時間來安排播放(若是數據塊的開始時間大於當前參考時鐘上的時間,則不急於播放該數據塊,直到參考時鐘達到數據塊的開始時間;若是數據塊的 開始時間小於當前參考時鐘上的時間,則「儘快」播放這塊數據或者索性將這塊數據「丟棄」,以使播放進度追上參考時鐘)。

 

 

 

圖2.8 解決音視頻同步問題的時間戳方案

 

可見,避免音視頻不一樣步現象有兩個關鍵——一是在生成數據流時要打上正確的時間戳。若是數據塊上打的時間戳自己就有問題,那麼播放時再怎麼調整也於事無補。如圖2.8,視頻流內容是從0s開始的,假設10s時有人開始說話,要求配上音頻流,那麼音頻流的起始時間應該是10s,若是時間戳從0s或 其它時間開始打,則這個混合的音視頻流在時間同步上自己就出了問題。打時間戳時,視頻流和音頻流都是參考參考時鐘的時間,而數據流之間不會發生參考關係; 也就是說,視頻流和音頻流是經過一箇中立的第三方(也就是參考時鐘)來實現同步的。第二個關鍵的地方,就是在播放時基於時間戳對數據流的控制,也就是對數 據塊早到或晚到採起不一樣的處理方法。圖2.8中,參考時鐘時間在0-10s內播放視頻流內容過程當中,即便收到了音頻流數據塊也不能當即播放它,而必須等到參考時鐘的時間達到10s以後才能夠,不然就會引發音視頻不一樣步問題。

基於時間戳的播放過程當中,僅僅對早到的或晚到的數據塊進行等待或快速處理,有時候是不夠的。若是想要更加主動而且有效地調節播放性能,須要引入一個反饋機制,也就是要將當前數據流速度太快或太慢的狀態反饋給「源」,讓源去放慢或加快數據流的速度。熟悉DirectShow的讀者必定知道,DirectShow中的質量控制(Quality Control)就是這麼一個反饋機制。DirectShow對於音視頻同步的解決方案是至關出色的。但WMF SDK在播放時只負責將ASF數據流讀出並解碼,而並不負責音視頻內容的最終呈現,因此它也缺乏這樣的一個反饋機制。

爲了更好地理解基於時間戳的音視頻同步方案,下面舉一個生活中的例子。假設你和你的一個朋友約好了今天18:00在滬上廣場見面,而後一塊兒吃飯,再去打遊戲。實際上,這個18:00就是你和你朋友保持同步的一個時間點。結果你17:50就到了滬上廣場,那麼你必須等你的朋友。10分 鍾事後,你的朋友尚未到,這時他打來電話說有事耽擱了,要晚一點才能到。你沒辦法,由於你已經在旁邊的餐廳預訂了位置,若是不立刻趕過去,預訂就會被取 消,因而你告訴你的朋友直接到餐廳碰頭吧,要他加快點。因而在餐廳未來的某個時間點就成爲你和你朋友的又一個同步點。雖然具體時間不定(要看你朋友趕過來 的速度),但這樣努力的方向是對的,你和你朋友確定能在餐廳見到面。結果呢?你朋友終於在18:30趕過來了,大家最終「同步」了。吃完飯19:30了,你臨時有事要處理一下,因而跟你朋友再約好了20:00在附近的一家遊戲廳碰頭。大家又不一樣步了,但在遊戲廳未來的某個時間點大家仍是會再次同步的。

其實,同步是一個動態的過程,是一個有人等待、有人追趕的過程。同步只是暫時的,而不一樣步纔是常態。人們老是在同步的水平線上振盪波動,但不會偏離這條基線太遠。

 

 在發送方:
對於相同時刻的音頻/視頻幀,打上相同的時間戳(系統時間)
接收方:
保存兩個隊列,audio/video分別用來存放還未播放的音頻和視頻
1。當每接收到音頻幀的時候,遍歷此時的video隊列,將此音頻幀的時間戳跟每一個視頻幀的時間戳進行比較:
1)若是音頻幀的時間在這個視頻幀的前面,幀播放該音頻
2)若是音頻跟視頻的時間戳相差在某個能夠接受的偏差內,則同時播放該音頻/視頻(並將視頻幀從video隊列中刪除)
3)若是視頻時間在前,則播放視頻幀(並將視頻幀從video隊列中刪除)
若是video隊列中的最後一幀的時間都在這個audio幀以前,在此時會把整個video隊列中的幀播放完,此時video隊列將爲空,那麼將這個音頻放入audio隊列。
2.對接收到視頻幀的時候,也作相似的處理。

 

同步的意思是,保證一個程序在被不適宜的切換時,不會出現問題。

    對Window3.1來說,雖然有多任務,可是沒有同步基層。由於這些多任務的協做是經過調用API函數,好比(GetMessage和 PeekMessage)來實現。若是一個程序調用了GetMessage或PeekMessage,則意思就是說,我如今處於可中斷狀態。

    Win32程序沒有這樣的協做多任務,他們必須作好隨時被CPU切換掉的準備。一個真正的Win32程序不該該耗盡CPU時間去等待某些事情的發生。

    Win32API有四個主要的同步對象:(1)Event 事件;(2)Semaphore 信號量;(3)Mutexes 互斥;(4)Critical Section 臨界區。

    除Critical Setion外,其他是系統全局對象,而且與不一樣進程及相同進程中的線程一塊兒工做,這樣同步機制也能夠用於進程同步。

    1。事件(Event)

    這是同步對象的一種類型類型,正如其名字含義,在這個中心周圍是一些發生在另外一個進程或線程中的特殊活動。當你但願線程暫時掛起時,不會消 耗CPU的工做週期。事件相似咱們經常使用的消息概念。若是咱們剖析消息的內核,確定能發現,它就是用事件來實現的。這裏解釋一下事件與消息的區別:

    事件實際上就是消息的到達,也就是說一個消息通過一系列的過程到達了,它就會觸發一個事件處理器,事件處理器就會調用你寫的事件處理函數。 而消息就是發消息給系統,系統會有消息隊列。而後系統根據必定的調度會取到等待處理的消息(固然有可能丟失)來調用消息相應函數。雖然效果同樣,可是事件 系統顯然跟安全,由於不會丟失消息。

    程序可用CreateEvent或OpenEvent對事件得到一個句柄:

    HANDLE CreateEvent (
                      LPSECURITY_ATTRIBUTES
lpEventAttributes, // SD
                      BOOL bManualReset,                       // reset type
                      BOOL bInitialState,                      // initial state
                      LPCTSTR lpName                           // object name
                    );

 

     HANDLE OpenEvent(
                      DWORD
dwDesiredAccess// access
                      BOOL bInheritHandle,    // inheritance option
                      LPCTSTR lpName          // object name
                    );

    函數參數和返回值解釋請參考MSDN,如下相同。

 

    而後,該程序再調用WaitForSingleObject,選定事件句柄和等待超時時間。那麼線程就會被掛起,一直到其餘線程調用下面API函數,給出事件有關信號後纔再次被激活。

    BOOL SetEvent(
                  HANDLE
hEvent   // handle to event

                );

 

    BOOL PulseEvent(
                  HANDLE
hEvent   // handle to event object
                );

    好比,當一個線程要使用另外一個線程的排序結果時,你或許但願去使用一個事件。比較糟糕的方法是執行這個線程,並在結束時設置全局變量標誌, 另外一個線程循環檢查這個標誌是否已設置,這就浪費不少CPU時間。用事件做一樣的事情則很簡單,排序線程在結束時產生一個事件,其餘線程調用 WaitForSingleObject。這就使得線程被掛起。當排序線程完成時,調用SetEvent喚醒另外一個線程繼續執行。

    除了WaitForSingleObject外,還有WaitForMultipleObjects,容許一個線程被掛起,直到知足多個Event條件。

    舉例說明。

    音視頻通訊過程當中,咱們用一個TCP Socket,m_hDataSock接收數據,在沒有數據到達時,接收線程會被掛起,直到有數據到達或者Socket超時,來進行相應處理,示例方法以下:

UINT RecvDataThread(LPVOID pPara)
{
     WSAEVENT  =  WSACreateEvent();
     WSAEventSelect(m_hDataSock,m_hEvent,FD_READ | FD_CLOSE);
     while(!m_bThreadEnd)
     {
          DWORD dwWait = WSAWaitForMultipleEvents(1,&m_hEvent,FALSE,18000,FALSE);
          if (WSA_WAIT_TIMEOUT == dwWait)
          {
              //超時處理
               break;
          }
          if(WAIT_OBJECT_0 == dwWait)
          {
               WSANETWORKEVENTS netEvents;
               if(SOCKET_ERROR == WSAEnumNetworkEvents(m_hDataSock,m_hEvent,&netEvents))
               {
                    continue;
               }
               if((netEvents.lNetworkEvents & FD_READ) && (0 == netEvents.iErrorCode[FD_READ_BIT]))
               {
                    //接收數據
               }
               else if(netEvents.lNetworkEvents & FD_CLOSE)
               {
                      //處理通道關閉

                        break;
               }
          }
     }
     WSACloseEvent(m_hEvent);
     _endthreadex(0);
     return 0;
}

    

    2。信號量

    當須要限制訪問特殊資源或限制一段代碼到某些線程是,Semaphores很是有用。好比說,同樣資源有十個,當你須要用時,已經被其餘十我的佔用了。這樣就必須等待,直到有人不用了,歸還了資源。

    在Win32編程中得到Semaphores就好像得到該資源的一次控制。

    爲了利用Semaphores,一個線程調用CreateSemaphore去得到一個HANDLE給Semaphores。也就是將Semaphores與資源綁定,並初始化該Semaphores,並返回該Semaphores的句柄。

    函數原型以下:

    HANDLE CreateSemaphore(
                      LPSECURITY_ATTRIBUTES
lpSemaphoreAttributes, // SD
                      LONG lInitialCount,                          // initial count
                      LONG lMaximumCount,                          // maximum count
                      LPCTSTR lpName                               // object name
                      );

    若是Semaphores在其餘進程中建立,能夠用OpenSemaphore去獲取其句柄。

    HANDLE OpenSemaphore(
                      DWORD
dwDesiredAccess// access
                      BOOL bInheritHandle,    // inheritance option
                      LPCTSTR lpName          // object name
                      );

    接下來固然是利用等待函數來阻塞線程。若是這個Semaphore計數大於0,這等待功能只是簡單處理Semaphores的使用數,線程 繼續執行,換句話說,若是Semaphores使用數超出最大值,則等待線程被掛起。固然也能夠利用ReleaseSemaphore來釋放資源。

    BOOL ReleaseSemaphore(
                  HANDLE
hSemaphore,       // handle to semaphore
                  LONG lReleaseCount,      // count increment amount
                  LPLONG lpPreviousCount   // previous count
                  );

    也就是用信號量這個對象來管理某個資源的分配與回收。

    

    3。互斥(Mutexes)

    Mutex是「mutual exclusion」的縮寫。但願一次只有一個線程去訪問一個資源或一段代碼時可使用互斥。使用方法與信號量相似。建立和釋放Mutex的函數原型以下:

    HANDLE CreateMutex(
                      LPSECURITY_ATTRIBUTES
lpMutexAttributes,  // SD
                      BOOL bInitialOwner,                       // initial owner
                      LPCTSTR lpName                            // object name
                    );

    BOOL ReleaseMutex(
                      HANDLE
hMutex   // handle to mutex
                    );

    能夠將使用方法封裝成類,以下:

    class CMutex  
    {
        public:
             CMutex(HANDLE hMutex){m_hMutex = hMutex; WaitForSingleObject(m_hMutex,INFINITE);}
             virtual ~CMutex(){ReleaseMutex(m_hMutex);}
        private:
             HANDLE m_hMutex;
    };

    使用的時候首先聲明一個HANDLE  m_hMutex;調用接口建立Mutex,m_hMutex = CreateMutex(NULL,FALSE,NULL);而後再任何須要互斥的代碼前構造這樣一個類就能夠了。好比,CMutex mutex(m_hMutex);

 

    4。臨界區(Critical Sections)

    臨界段至關於一個微型的互斥,只能被同一個進程中的線程使用。臨界區是爲了防止多線程同時執行一段代碼。相對其餘同步機而言,臨界區相對簡單和易用。通常先聲明一個CRITICAL_SECTION類型的全局變量,而後調用下面三個函數來使用它。

    VOID InitializeCriticalSection(
               LPCRITICAL_SECTION
lpCriticalSection  // critical section
                );

 

    VOID EnterCriticalSection(
              LPCRITICAL_SECTION
lpCriticalSection  // critical section
                );

 

    VOID LeaveCriticalSection(
              LPCRITICAL_SECTION
lpCriticalSection   // critical section
                );

    5。WaitForSingleObject/WaitForMultipleObjects函數

    其實,線程同步,除了上面四種方法外,還可使用WaitForSingleObject/WaitForMultipleObjects函數。等待的HANDLE能夠是線程的句柄,文件的句柄等。

相關文章
相關標籤/搜索