龙空技术网

ffmpeg音视频同步,seek策略总结。

端庄优雅青山NAl 128

前言:

眼前看官们对“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