使用timeBeginPeriod设置Sleep精度
timeBeginPeriod(1);
timeEndPeriod(1);
1. 音视频同步的原理
2. 音视频同步的较正方法
3. 音视频同步时遇到的问题
声明:以下所有内容均为转载
1.原文转自:http://bbs.chinavideo.org/viewthread.php?tid=1183&extra=page%3D1%26amp%3Bfilter%3Ddigest&page=1
下面贴出部分:
音视频同步这一块,我一直不是很了解,很想尝试一下,下面是在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
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;。
这几天搞文件回放,视频格式是 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可以是线程的句柄,文件的句柄等。