播放器实战27 完成seek到指定位置
1.添加进度条的滑动与释放
将信号sliderPress()与槽SliderPress()连接
将信号sliderRelease与槽SliderRelease()连接
2.完成进度条跳转
,并在xplay2.h将这两个槽放在public slots:中:
void Xplay2::SliderPress()
{isSliderPress = true;
}
seek会造成极大的开销,假如seek到一个P帧,其参考的I帧与其距离50帧,则要解码50帧,若每seek一点都进行一次解码,这是不现实的,因此我们选择当seek结束时进行解码`
void Xplay2::SliderRelease()
{isSliderPress = false;double pos = 0.0;pos = (double)ui.playPos->value() / (double)ui.playPos->maximum();dt.seek(pos);
}
在解封装线程添加seek来直接访问解封装类的接口:
void xdemuxthread::seek(double pos)
{mux.lock();if (demux)demux->seek(pos);mux.unlock();
}
seek时代码调用逻辑:
01.当用户拖动进度条的行为结束时调用Xplay2的SliderRelease()函数,该函数中通过在qt中进度条拖动的值除以进度条的最大值获得一个比例pos:
pos = (double)ui.playPos->value() / (double)ui.playPos->maximum();
然后通过dt.seek(pos)将pos传给解封装线程
02.在解封装线程中将传进来的比例值pos乘上解封装上下文中的视频流的总时长,获得要seek到的pts,再使用ffmpeg提供的接口av_seek_frame进行跳转:
long long seek_pos = ic->streams[videoStream]->duration * pos;
int re = av_seek_frame(ic, videoStream, seek_pos, AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME);
注意:1.这是跳到关键帧,而不是实际的帧,这是解封装的函数,根据单一职责将跳转到实际帧(涉及解码)的任务交给到外部业务
2.为什么使用视频来seek?因为若使用音频来seek的话对应的视频可能seek到非关键帧,比如P帧,P帧的解码需要I帧,此时将会使用错误的I帧进行P帧解码从而导致花屏
3.使用AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME向后找
03.此时在解封装线程中用个while(true)循环进行seek的等待,在这个循环等待中,会不断拿到解封装的pkt,当拿到的pkt中的pts大于等于想要seek到的pts时才开始播放:
if (decode->pts >= seekpts){if (call)call->Repaint(frame);vmux.unlock();return true;}
3.解决卡顿
此时拖动进度条发现过一段时间画面才切换过来,这是因为还有缓冲还未用完,因此在seek使调用解封装的clear,在这个clear中再调解封装的clear和视频音频线程的clear:
void XDemuxThread::Clear()
{mux.lock();if (demux)demux->clear();if (vt) vt->Clear();if (at) at->Clear();mux.unlock();
}
解封装的clear:
void xdemux::clear()
{mux.lock();if (!ic){mux.unlock();return;}avformat_flush(ic);mux.unlock();
}
音频线程的clear要比视频线程的clear多清理一个audioplay,因此视频线程的clear调用其基类xdecodethread的clear:
void xdecodethread::Clear()
{mux.lock();if(decode)decode->clear();while (!packs.empty()){AVPacket* pkt = packs.front();XFreePacket(&pkt);packs.pop_front();}mux.unlock();
}
音频线程调用自己重载的clear:
void xaudiothread::Clear()
{xdecodethread::Clear();mux.lock();if (audioplay) audioplay->Clear();mux.unlock();
}
其先调用其基类的clear,再清理其audioplay:
void Clear(){mux.lock();if (io){io->reset();}mux.unlock();}
同样,这是工厂模式产生的类,需要在其工厂中将其声明为纯虚函数:
virtual void Clear()=0;
现在seek后没有卡顿,但是不会在seek位置准确播放
4.解决进度条的跳转
位置不精确是因为demux里的seek策略导致的:
av_seek_frame(ic, videoStream, seek_pos, AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME);
将会导致实际seek到的位置在想要seek的位置偏左
因此我们需要在seek之后用循环来判断当前解码到的pts与想要seek到的pts的关系,当解码到的pts大于等于想要seek的pts时才会将音视频播放:
seekpts为想要seek的pts,decode->pts为实际的pts
void xdemuxthread::seek(double pos)
{Clear();mux.lock();bool statuspause = this->isPause;mux.unlock();SetPause(true);mux.lock();if (demux)demux->seek(pos);long long seekPts = pos * (demux->totalMs);while (!isexit){AVPacket* pkt = demux->readfz();if (demux->isvideo(pkt)){if (!pkt) break;if (vt->RepaintPts(pkt, seekPts)){this->pts = seekPts;break;}}else{XFreePacket(&pkt);}}mux.unlock();if(!statuspause)SetPause(false);
}
关于if (vt->RepaintPts(pkt, seekPts)):
根据seek到的pts与实际想要的pts来判断是等待还是直接绘制
等待时RepaintPts返回false,然后进入下次while
绘制后RepaintPts返回true,然后更新当前的pts
关于暂停:
不管seek前视频是否为播放状态,则seek前统一暂停,不然如果正在播放的话解封装线程在seek,其他线程仍然在做解码,会出问题。如果seek前为播放状态,则seek结束后恢复播放,如果seek前为暂停状态,则seek结束后仍暂停。
关于XFreePacket(&pkt):
在demux的seek策略中选择的是使用视频来做seek,因此当读到的packet为音频时直接清空这个packet,如果是视频的话在send之后完成了对pkt的清理。
本模块包含了decode模块,decode中包含了ffmpeg库,av_packet_free(pkt)需要使用ffmpeg库,这里想减少与ffmpeg库的耦合,便在decoder中将av_packet_free(pkt)封装起来,在本模块中调用封装后的函数即可
void XFreePacket(AVPacket** pkt)
{if (!pkt || !(*pkt))return;av_packet_free(pkt);
}
bool xvideothread::RepaintPts(AVPacket* pkt, long long seekpts)
{vmux.lock();bool re = decode->send(pkt);if (!re){vmux.unlock();return true;}AVFrame* frame = decode->receive();if (!frame){vmux.unlock();return false;}//到达位置if (decode->pts >= seekpts){if (call)call->Repaint(frame);vmux.unlock();return true;}XFreeFrame(&frame);vmux.unlock();return false;
}
该函数一直到decode->pts >= seekpts都返回false,即seekpts之前的音视频都不会播放,并且使用XFreeFrame(&frame);/释放掉指向位置之前的frame。
此时完成了移动滑动条到指定位置播放
播放器实战27 完成seek到指定位置相关推荐
- 播放器实战28 总结
至此,播放器的基本功能已经实现,进行一个总结: 一,仅进行播放时的函数调用流程 只做一个大致的梳理且不涉及seek等操作): 01.QT中的整个控件为QWidget类,Xplay2类为其继承,在mai ...
- 基于 Vue 的直播播放器实战
原文地址:点击进入 前言 时下直播的盛行让很多人对直播技术产生浓厚的兴趣,orange 本人也不例外,本文借着实战的目的完成一个 demo,并没有深入的讲解直播技术的实现原理以及推流和拉流的实现,为什 ...
- vue 判断同一数组内的值是否一直_前端代码+后端API,值得一学的Vue高仿音乐播放器实战项目
项目名称:vue-fds_music 项目作者:符道胜 开源许可协议:Apache-2.0 项目地址:https://gitee.com/fudaosheng/vue-fds_music 项目简介 V ...
- vue实现音乐播放器实战笔记
一.项目说明 该播放器的是基于学习vue的实战练习,不用于其他途径.应用中的全部数据来自于 QQ音乐 移动端(https://m.y.qq.com/),利用 jsonp 以及 axios 代理后端请求 ...
- 后端实体类接收数组_前端代码+后端API,值得一学的Vue高仿音乐播放器实战项目...
项目名称:vue-fds_music 项目作者:符道胜 开源许可协议:Apache-2.0 项目地址:https://gitee.com/fudaosheng/vue-fds_music 项目简介 V ...
- 前端代码+后端API,值得一学的Vue高仿音乐播放器实战项目
项目名称:vue-fds_music 项目作者:符道胜 开源许可协议:Apache-2.0 项目地址:https://gitee.com/fudaosheng/vue-fds_music 项目简介 V ...
- 播放器实战07 av_read_frame与av_seek_frame
一.了解结构体AVPacket与AVstream AVPacket: AVBufferRef *buf :buf指向一个空间,该空间里存放引用计数,packet每次copy的时候引用计数+1,每次删除 ...
- 妙味课堂H5音乐播放器实战视频课程 ajax实战教程
课程介绍: 本次课程涉及的知识点包括移动端H5.CSS3.JS.滑屏.HTTP协议.AJAX.跨域.前后端交互.PHP.mySql.jQuery--配合这些知识点,讲师写了一个H5播放器demo,用来 ...
- 播放器实战19 Xaudio打开音频
1.xaudio.h #pragma once class xaudioplay {public:static xaudioplay* get();xaudioplay();//一定得是虚析构函数,d ...
最新文章
- 软件测试培训 高级测试/测试开发基本技能列表
- EdgeGallery — OpenStack VIM
- 负载均衡下ajax第二次请求,会话清除第二个AJAX电话
- Linux疑难杂症解决方案100篇(十一)-ubuntu crontab 详细规则及不执行时的解决方法
- less的一些用法整理
- Elasticsearch实战:给博客打造全文检索
- 文件操作(上传,下载,限制)
- java 打印ascii字符串_简单使用JAVA打印纯ASCII字符构成的酷图效果
- DMA—直接存储区访问
- elementary os java,吐槽ELEMENTARY OS系统/ELEMENTARY OS系列文章汇总
- 【matlab】ode45求解二阶微分方程,绘制曲线图 | 使用函数句柄的方法
- 关于Angular样式封装
- Python学习笔记(正则表达式)
- ArcGIS Engine开发之旅01---产品组成、逻辑体系结构
- C语言开发环境搭建及调试
- JSchException: Algorithm negotiation fail
- 证券业上云内参: 深圳证券信息
- 零死角玩转stm32初级篇5-流水灯的前后今生
- 神经网络模型文件后缀名,神经网络模型文件格式
- [创投新闻] [中英双语] FACEBOOK为美竞选上线电子投票功能并开通选民信息中心