前言:
眼前看官们对“python文件seek csdn”大致比较看重,朋友们都需要知道一些“python文件seek csdn”的相关内容。那么小编同时在网络上汇集了一些对于“python文件seek csdn””的相关内容,希望大家能喜欢,咱们快快来了解一下吧!上一篇音视频同步策略和视频seek策略讲过一些方法,但是总视存在一些小问题,这里花费了近三天的时间对整个 音视频同步,以及seek测率进行较大的调整,使得整个程序更健壮,用户在界面胡乱操作,seek和pause都不会引起程序卡顿和崩溃了。
音视频seek策略最简单的方法,就是一个大锁,将音频解码 和 视频解码播放 各用同一个锁锁住,然后,将seek部分用同一个锁锁住,这样seek的时候清空数据就不会导致缓冲区有数据,或者死锁问题,但是这样效率很低,且看似音视频各 一个线程,其实同时只有一个线程能跑。这里将自己的心血总结一些。大致是对上一篇的优化。
结构图
如图:有四个线程,橙色为条件判断和赋值。
demux 解封装出来,分别为videoPacket 和 audioPacket,分别存入一个 list里面。
audioThread 不停的从audioPacket list 取audioPacket 进行decode 和resample,并且将frame的pts和 重采样数据data存储在 audio data list 和 pts list中
videoThread 不停从 videoPacket list 取出 videoPacket 进行decode,然后于当前播放音频 pts比较,小于就进行显示
音频播放线程,openSELS进行播放,在回调函数中取 audio data 和 pts 进行播放,并且将当前pts(curAudioPts) 设置为播放的pts
node: 为了方便播放器资源的管理,图中其实还有个dataManager类没有画出,这个类是所有对象的成员变量,并且一个播放器只能有一个,所有的数据都在dataManager 对象。一些 list 数据和播放器状态都在 这个类对象当中,包括ffmpeg的一些解码器 和 上下文 都存储在里面,当对播放器操作,seek 和 pause 和close的时候对数据的清理和通信,都是通过公用的dataManager来进行的
音视频同步策略
同步测率没有什么变化,同上一篇一样:因为视频解码后很大,不建议缓存,只能缓存packet,然后与当前音频比较,如果小于音频的pts就显示和播放。没有多大的变化。
Pause策略
通过将音频 线程 和 demux线程分开,现在音频 、 视频 、demux这三个线程都是完全独立的,除了 同步那里会阻塞其他地方都不会堵塞了。并且,在decode 和 resample的时候 不要用while(!isExit)没取到数据就睡2ms然后继续取数据。因为在过程中尽量不要堵塞,方便再后面暂停播放器。
pause:当我们每个线程的一个周期执行完毕后,再进行暂停,因为每个线程都是一秒至少30次,因此人是感觉不到这个暂停的延迟的,即在线程开通进行沉睡(2ms),然后看播放器状态,选择是否继续睡眠。
node
如果用glsurfaceView的时候,可能绘制视频的那个线程是主线程,可能不能堵塞哦。
demux的packet 必须存入后才能暂停,因此,需要用到while(!isExit),可能暂停会堵塞在这里,因此 需要在堵塞的时候判断是否isPauing (正在进行暂停操作),若是,则直接返回不等待,在暂停和seek的时候,丢一帧是感觉不到的。
openSELS有数据就播放,没数据就没声音,因此主要是在获取音频data的时候去控制播放于否。
相关学习资料推荐,点击下方链接免费报名,先码住不迷路~】
音视频免费学习地址:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~
下面给出 三个线程的:
demux线程:
void LammyOpenglVideoPlayer::demuxThreadMain(){ while(!dataManager->isExit) { while(dataManager->demuxPauseReady) { LSleep(2); continue; } /********************* 解封装部分****************************/ int mode = ffmDemux->demux(); /********************* 解封装部分结束****************************/ if(dataManager->isPausing) { LOGI("pause demuxPauseReady .open...."); dataManager->demuxPauseReady = true; } } }
因为demux中要等待存入到packet list当中,才能进行下一次循环,因此暂停的时候,会卡在这里,因此如果在暂停的时候就不存,直接返回:
if(dataManager->isPausing){ dataManager->videoLock.unlock(); return 0;}
视频线程
视频的播放在主线程,因此开头没有用while而是if:
void LammyOpenglVideoPlayer::videoThreadMain(){ if(!dataManager->isExit) { if(dataManager->videoPauseReady){ LSleep(2); LOGI("pause videoPauseReady ....."); return; } AVFrame * avFrame = ffMdecode->decode(0); if(avFrame != 0 && avFrame != nullptr){ LSleep(2); LOGI("avFrame video show ....."); openglVideoShow->show(avFrame); } else if(avFrame == 0){ LSleep(2); } if(dataManager->isPausing)//&&dataManager->demuxPauseReady { LOGI("pause videoPauseReady .open...."); dataManager->videoPauseReady = true; } }else{ dataManager->isVideoRunning = false; } }
因为decode的时候取不到数据也不会堵塞,show的时候也不会堵塞,整个过程很快。 videoPauseReady 很快就会true,然后在开头的地方为了不显示并且不堵塞主线程,堵塞2ms后只能直接返回。
音频线程
void LammyOpenglVideoPlayer::audioThreadMain(){ while(!dataManager->isExit) { while(dataManager->audioPauseReady) { LSleep(2); //continue; } /********************* 解码重采样部分****************************/ AVFrame * avFrame = ffMdecode->decode(1); if(avFrame != 0 && avFrame != nullptr) {// LOGI("pause resample .........."); ffmResample->resample(avFrame); }else{ LSleep(2); } /********************* 解码重采样部分结束****************************/ if(dataManager->isPausing ) { LOGI("pause audioPauseReady .open...."); dataManager->audioPauseReady = true; } }
音频不在主线程,因此后台不停的解码 和重采样,存入到缓冲区
openSELS获得数据
void OpenSLESAudioPlayer::getAudioData(){// 当暂停后,就等待 while ((dataManager->isPause)&&!dataManager->isExit){ LOGE("音频暂停中。。。。。。。。"); LSleep(10); continue; } char *data = nullptr; while (!dataManager->isExit) { dataManager->audioLock.lock(); if (dataManager->audioData.size() > 0 && dataManager->audioPts.size()>0) { data = (char *) (dataManager->audioData.front()); dataManager->currentAudioPts = dataManager->audioPts.front(); dataManager->audioPts.pop_front(); dataManager->audioData.pop_front(); memcpy(buf,data,dataManager->audioDateSize); free(data); dataManager->audioLock.unlock(); return ; } LOGE("没有数据了,等等"); dataManager->audioLock.unlock(); LSleep(2); continue; } }
pause函数:
void LammyOpenglVideoPlayer::pauseOrContinue(){ if(!dataManager->isPause) { dataManager->isPausing = true; while(true) { if( dataManager->videoPauseReady &&dataManager->audioPauseReady&&dataManager->demuxPauseReady ) { dataManager->isPause =true; dataManager->isPausing =false; // 只有 取消暂停的时候才能 将下面置为true// dataManager->videoPauseReady =false;// dataManager->audioPauseReady =false;// dataManager->demuxPauseReady=false; LOGE("pause success"); return; }else{ LSleep(20); continue; } } } else { dataManager->isPause =false; dataManager->isPausing =false; dataManager->videoPauseReady =false; dataManager->audioPauseReady =false; dataManager->demuxPauseReady=false; LOGE("un pause success"); return; } }
只有当三个线程都准备完毕后,isPausing 完毕置为false,isPause为true。
这样pause的策略就完成了,这个策略这样设计主要是方便后面的seek操作。
seek策略
上面pause策略可以看出,暂停后,线程都会停留在线程的开头,不会对解码器或者重采样等ffmpeg的数据进行操作,这样可以省去不进行pause 和 seek的时候 大量的锁操作,大大减少了开销,并且 音频 和 视频的解码完全独立开来,不会解码音频的时候视频就无法进行解码。
seek策略:seek操作是在主线程,上一篇中讲到无法快速点击seek,这会seek操作延迟会很严重,因此这里进行了改进:
将seek操作放入子线程进行操作,防止堵塞主线程。
为了减小开销和延迟,当用户进行seek操作时,如果清理数据等一切操作完毕,而没有进行 ffmpeg的seek操作时候,我们只需要将seek的seekPos修改为最新的用户点击的seekPos,前面的seekPos就不执行了。
增加的seekLock只在 ffmpeg的seek的时候锁住 和 点击seek键的时候判断是否正在seeking当中这2步同步,这2个操作都很短,并且保证了进程中只有一个seek线程。用户点击seek键存在2种情况 :1、 一旦 进入了seekTo函数,下面的ffmpeg线程就无法seek操作,等修改好了seePos,直接seek到新点击的pos点,不创建线程。2、无法进入seekTo函数,等待 seek完毕,再创建线程进行seek。
先给出seek的函数:
float progress = 0;void LammyOpenglVideoPlayer::seekTo(float seekPos){ LOGE("seekPos = %f", seekPos); dataManager->seekLock.lock(); if (dataManager->isSeeking){ progress = seekPos; LOGE(" progress = seekPos = %f", seekPos); dataManager->seekLock.unlock(); return; }else{ progress = seekPos; std::thread seek_th(&LammyOpenglVideoPlayer::seekThreadMain,this); seek_th.detach(); } dataManager->seekLock.unlock(); } void LammyOpenglVideoPlayer::seekThreadMain(){ dataManager->isSeeking = true; if(!dataManager-> isPause) { pauseOrContinue(); } dataManager->clearData(); dataManager->seekLock.lock(); long long pos2 = dataManager->avFormatContext->streams[dataManager->videoStreamIndex]->duration* progress; av_seek_frame(dataManager->avFormatContext, dataManager->videoStreamIndex, pos2, AVSEEK_FLAG_FRAME|AVSEEK_FLAG_BACKWARD); ffmDemux->seekTo(progress); dataManager->currentAudioPts =LLONG_MAX; pauseOrContinue(); dataManager->isSeeking = false; dataManager->seekLock.unlock(); }
新的seek操作是异步的,并且只会执行最新的seek操作,不会感觉到延迟,还减少了开销和主线程卡死的情况。
原文 ffmpeg音视频同步,seek策略总结。_ffmpeg seek_Lammyzp的博客-CSDN博客
标签: #python文件seek csdn