目前BPlay1.0的功能已经全部完成,但是在使用时还存在许多缺陷,后面会继续优化这些点,使体验更加舒适。
花屏
在播放视频时,如果不拖动滑块变更播放进度,视频基本可以流畅的放完,但是一旦使用进度条后,大概率会出现花屏的现象:
这个主要是因为我们在根据进度条位置跳转视频时,使用av_seek_frame(FormatContext, -1, (int64_t)Time * AV_TIME_BASE, AVSEEK_FLAG_ANY),它最后一个入参是 AVSEEK_FLAG_ANY,即跳转到指定帧,假如跳转处是视频I帧,则不会出现花屏现象,但是视频绝大部分帧都是B帧和P帧,跳转到这些位置时其预测关键帧就会出现问题,此处需要优化。
ffmpeg对av_seek_frame最后一个参数提供了下面这些选择:
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward
#define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes
#define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes
#define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number
其中 AVSEEK_FLAG_BACKWARD 和 AVSEEK_FLAG_FRAME 都是用来找I帧的,前者是从往当前找,而后者是往后找,如果我们之间使用这两个参数来替代 AVSEEK_FLAG_ANY 的话,视频每次跳转的位置可能会和我们指定的实际位置有区别,这样显然是不可取的。但是我们却可以借助这两个参数来解决花屏问题,重新实现的 Bffmpeg::ResetTime(int Time) 如下:
/********************************
* double Bffmpeg::GetTimeAll()
* 功能:重设视频播放时间(进度)
* *****************************/
void Bffmpeg::ResetTime(int Time)
{
StopPlay();
Bffmpeg::GetInstance()->GetAudioQue().que.clear();
Bffmpeg::GetInstance()->GetVideoQue().que.clear();
if (!FormatContext)
{
BLOG("FormatContext NULL");
return;
}
/* 1、找到前面的关键帧 */
av_seek_frame(FormatContext, -1, (int64_t)Time * AV_TIME_BASE, AVSEEK_FLAG_BACKWARD);
/* 2、向后找播放点 */
AVPacket pkt;
while (1) {
av_read_frame(FormatContext, &pkt);
if (pkt.stream_index == Video_index) {
if (Time < pkt.pts * av_q2d(FormatContext->streams[Video_index]->time_base)) {
break;
}
/* 解码 */
AVFrame* frame = Decode(&pkt);
av_packet_unref(&pkt);
}
}
StartPlay();
return;
}
我们将 av_seek_frame 的最后一个参数改成了 AVSEEK_FLAG_BACKWARD,即每次先定位到预期位置前面的一个I帧,然后根据 pts 和定位处的差距,解码到定位处在继续播放。此时播放出来的视频不再有花屏现象,但是每次跳转后瞬间都会有一点卡顿的感觉,这个主要是I帧到目标帧解码的消耗造成的。
我后来尝试用 VLC 去验证视频跳转的效果,发现其不同的播放点跳转时会有不同程度的卡顿,卡顿程度和距离前一个I帧距离成正比(个人体验),但是整体丝滑程度比BPlay1.0好太多了,这么看 VLC 有可能也是利用了类似向前找关键帧的方法来解决花屏,但是在解码时应该做了优化,以后可以看看源码研究一下。
进度条跳转
当前视频进度改变只能通过拖动滑块来实现,点击进度条其他位置无法实现滑块的跳转,在使用时极其的不方便。
对于这个问题,我们只要为进度条注册事件过滤器(鼠标点击进度条任意处可准确修改滑块位置):
ui->BPlaySlider->installEventFilter(this);
对 BPlaySlider 鼠标事件重新处理:
/********************************
* bool MainWindow::eventFilter(QObject *obj, QEvent *event)
* 功能:事件过滤器
* *****************************/
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->BPlaySlider) {
if (event->type() == QEvent::MouseButtonPress) {
/* 滑动条事件过滤 */
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
int value = QStyle::sliderValueFromPosition(ui->BPlaySlider->minimum(), ui->BPlaySlider->maximum(), mouseEvent->pos().x(), ui->BPlaySlider->width());
ui->BPlaySlider->setValue(value);
Bffmpeg::GetInstance()->ResetTime(ui->BPlaySlider->value());
}
}
return QObject::eventFilter(obj,event);
}
最后,BPlay1.0的功能已经全部开发,项目开源到了GitHub上: BPlay1.0
欢迎大家提出宝贵的优化意见,敬请期待BPlay2.0!