音视频基础学习之【05.添加播放控制】_存星的博客-程序员秘密

技术标签: c++  qt  c语言  网络播放器项目  sdl  ffmpeg  

 

目录

播放器播放控制添加

界面设计

暂停

停止

主线程具体操作

视频解码线程具体操作

音频解码函数具体操作

ui界面控制操作

添加进度条

分别获取当前时间和总时间

进度条功能采用QTimer定时器实现


播放器播放控制添加

  • 界面设计

仿优酷的界面设计,资源都是自己在优酷客户端上截取处理的,暂时没有对界面进行优化

  • 暂停

在decode类中添加播放控制状态,考虑到后续的复用性,可以拷贝到其它地方直接使用,在这个类中不对ui进行设置,只更改状态

在decode类中构造函数初始化所有播放状态为true,代表没有线程在工作,解码线程和主线程已经退出

使用枚举类型代表播放、暂停、停止三个状态

添加播放控制函数

不要忘记在构造函数中给状态赋初值为Stop,否则可能会是一个垃圾值

m_playerState = PlayerState::Stop;

在打开文件后,改变播放状态为Playing

//打开文件
void Youku::on_pb_openfile_clicked()
{
    //打开文件 弹出对话框 参数:父窗口, 标题, 默认路径, 筛选器
    QString path = QFileDialog::getOpenFileName(this,"选择要播放的文件" , "F:/",
                                                "视频文件 (*.flv *.rmvb *.avi *.MP4 *.mkv);; 所有文件(*.*);;");
    if(!path.isEmpty())
    {
        qDebug()<< path ;
        QFileInfo info(path);
        //如果取到了路径
        if( info.exists() )
        {
            //点击按钮开启线程 获取图片 显示到控件
            decode->start();
            //如果播放 你要先关闭
            decode->stop( true );
            //调用解码类打开文件
            decode->setFileName(path);
            //隐藏打开文件的按钮
            ui->pb_openfile->hide();
            //设置ui上的播放文件名
            ui->lb_filename->setText( info.baseName() );
            //改变播放状态
            slot_PlayerStateChanged(PlayerState::Playing);
        }
        else
        {
            //没取到路径,打开失败
            QMessageBox::information( this, "提示" , "打开文件失败");
        }
    }
}

点击UI界面的打开文件,调用解码类decode的设置文件名函数,把播放状态置为Playing,开启线程开始解码

void Decode::setFileName(const QString &fileName)
{
    m_fileName = fileName;
    m_playerState = PlayerState::Playing;
    this->start();
}

根据对应状态,设置控件和标志位的函数

void Youku::slot_PlayerStateChanged(int state)
{
    switch( state )
    {
    case PlayerState::Stop:
        qDebug()<< "VideoPlayer::Stop";
        ui->wdg_show->hide();
        ui->wdg_black->show();
        //m_Timer->stop();
        ui->slider_progress->setValue(0);
        ui->lb_totalTime->setText("00:00:00");
        ui->lb_currentTime->setText("00:00:00");
        ui->lb_filename->setText("");
        this->update();
        isStop = true;
        break;
    case PlayerState::Playing:
        qDebug()<< "VideoPlayer::Playing";
        ui->wdg_show->show();
        ui->wdg_black->hide();
        ui->pb_play->setIcon(QIcon(":/images/pause.png"));
        //m_Timer->start();
        this->update();
        isStop = false;
        break;
    }
}

点击播放按钮

若此时正在播放,状态为Playing,应该单独切换按钮的ui,转变状态为Pause

若此时已经暂停,状态为Pause,转变状态为Playing

//播放/暂停
void Youku::on_pb_play_clicked()
{
    if(isStop) return;

    if( decode->getPlayerState() == PlayerState::Pause)
    {
        ui->pb_play->setIcon(QIcon(":/images/pause.png"));
        this->update();
    }else if(decode->getPlayerState() == PlayerState::Playing)
    {
        ui->pb_play->setIcon(QIcon(":/images/play.png"));
        this->update();
    }
    decode->play();
}

在解码类中添加改变播放状态的函数

如果播放状态是Playing,则把isPause置为true,播放状态置为Pause

如果播放状态是Pause,则把isPause置为false,播放状态置为Playing

void Decode::play()
{

    if(getPlayerState() == PlayerState::Playing)
    {
        //点击一下转为播放状态
        m_videoState.isPause = true;
        m_playerState = PlayerState::Pause;

    }
    else if(getPlayerState() == PlayerState::Pause)
    {
        m_videoState.isPause = false;
        m_playerState = PlayerState::Playing;
    }

}

在视频解码线程和音频解码函数中添加判断,当前暂停标志位是否被置为TRUE

如果是暂停状态,调用SDL_Delay延迟解码,直到标志位被置为FALSE时继续解码播放

  • 停止

这个部分添加停止的思路是:

主线程run()函数,视频解码线程,音频解码线程

主线程读文件的标志是isreadFinished,读线程退出的标志是isreadThreadFinished

视频线程退出的标志是isvideoThreadFinished

通过这三个标志位来控制停止——线程退出

音频线程需要调用SDL库函数将其停止

主线程具体操作

在主线程run函数中添加下面一段代码

读取文件到packet中时,如果一次读取失败不认为读取完毕,而是用一个DelayCount判断

一次延时10ms,300次延时3秒钟没有读到内容,说明文件流读取完了,将readFinished置为true

if (av_read_frame(m_videoState.pFormatCtx, packet) < 0)
{
    DelayCount++;
    if( DelayCount>= 300)
    {
        m_videoState.readFinished = true;
        DelayCount = 0 ;
    }
  
    SDL_Delay(10);
    continue;
}

读取完毕文件流后,等待音视频解码线程退出

音视频解码线程退出后,回收音视频队列

//音视频队列回收
if( m_videoState.videoStream != -1)
    m_videoState.videoq->packet_queue_flush();
if( m_videoState.audioStream != -1)
    m_videoState.audioq->packet_queue_flush();

 

视频解码线程具体操作

从队列取包解码的操作

//从队列取 如果没取到
if (is->videoq->packet_queue_get(packet, 0) <= 0)
{
    //判断读线程是否完毕,音频队列中包是不是已经没有了
    if( is->isreadFinished && is->audioq->nb_packets == 0)
    {
         //如果是 跳出视频解码线程
         break;
    }else
    {
         //如果不是,则只是队列里面暂时没有数据而已
         SDL_Delay(10);
         continue;
    }
}

音视频同步的操作

//视频同步到音频上
while(1)
{
     //如果已经退出,不再延时
     if( is->isquit) break;
     //如果音频队列已经回收,退出视频解码线程,不然可能永远等不到音频队列的包中的音频时钟
     if( is ->audioq->size == 0 ) break;
     //获取音、视频时钟,当 视频时钟 > 音频时钟时 循环等待
     audio_pts = is->audio_clock;
     video_pts = is->video_clock;
     if (video_pts <= audio_pts) break;
     int delayTime = (video_pts - audio_pts) * 1000;
     delayTime = delayTime > 5 ? 5:delayTime;
     SDL_Delay(delayTime);
}

视频解码线程推出时的操作

//视频线程退出时,quit要置为true
if( !is->isquit)
{
   is->isquit = true;
}

最后置视频解码线程退出标志为1

is->isvideoThreadFinished = true;

音频解码函数具体操作

//从音频队列取包解码
if(is->audioq->packet_queue_get(&pkt, 0) <= 0)
{
     //读文件已经完毕,并且音频队列没有包了,把quit标志置为1 退出音频解码函数
     if( is->isreadFinished && is->audioq->nb_packets == 0 )
         is->isquit = true;

     return -1;
}

ui界面控制操作

在解码类decode中添加stop函数具体内容

因为SDL音频解码线程不能自己退出,需要在这个地方加一段关闭设备的代码退出

退出后将标志位置为Stop,并通过信号传递给UI界面

void Decode::stop( bool isWait)
{
    //按下stop,退出标志位置为1
    m_videoState.isquit = 1;
    if( isWait )
    {
        //阻塞一直等待主线程——读取线程退出
        while(!m_videoState.isreadThreadFinished )
        {
            SDL_Delay(10);
        }
    }
    //关闭 SDL 音频设备
    if (m_videoState.audioID != 0)
    {
        SDL_LockAudio();
        SDL_PauseAudioDevice(m_videoState.audioID,1);//停止播放,即停止音频回调函数
        SDL_UnlockAudio();
        m_videoState.audioID = 0;
    }
    //播放状态变为Stop
    m_playerState = PlayerState::Stop;
    Q_EMIT SIG_PlayerStateChanged(PlayerState::Stop);
}

在ui界面的构造函数中添加connect处理发来的状态转换信号

connect(decode,SIGNAL(SIG_PlayerStateChanged(int)),
            this,SLOT(slot_PlayerStateChanged(int)));

通过ui界面上的停止按钮调用decode的stop函数

decode->stop(true);

 

添加进度条

  • 分别获取当前时间和总时间

首先要读取视频的长度,在解码类decode中添加获取时间的代码,因为时钟是同步到音频时钟上的,所以当前时间以音频时间为基准

 

//获取当前时间
double VideoPlayer::getCurrentTime()
{
     return m_videoState.audio_clock;
}

 

//获取总时间
int64_t VideoPlayer::getTotalTime()
{
    if( m_videoState.pFormatCtx )
        return m_videoState.pFormatCtx->duration;
    return -1;
}

获取的时间单位都是微秒,需要将获取的总时间通过信号在文件开始读取之前发出去

添加信号

void SIG_TotalTime(qint64 uSec); 

 

在ui界面添加槽函数处理

connect( decode, SIGNAL( SIG_TotalTime(qint64)) ,
         this ,SLOT( slot_getTotalTime(qint64)) );

添加槽函数处理控件 

//获取视频全部时间
void Youku::slot_getTotalTime(qint64 uSec)
{
    //计算秒
    qint64 Sec = uSec/1000000;
    ui->slider_progress->setRange(0,Sec);
    QString hStr = QString("00%1").arg(Sec/3600);
    QString mStr = QString("00%1").arg(Sec/60%60);
    QString sStr = QString("00%1").arg(Sec%60);
    QString str =     
    QString("%1:%2:%3").arg(hStr.right(2)).arg(mStr.right(2)).arg(sStr.right(2));
    ui->lb_totalTime->setText(str);
}

 

  • 进度条功能采用QTimer定时器实现

包含定时器头文件QTimer

#include <QTimer>

首先在ui类中定义定时器对象

 QTimer* m_Timer;

在ui类的构造函数中添加定时事件处理connect函数

//定时器 进度条切换 设置间隔500ms
m_Timer = new QTimer;
connect(m_Timer,SIGNAL(timeout()),this,SLOT(slot_TimerTimeout()));
m_Timer->setInterval(500);

添加处理定时器事件的槽函数,具体见注释

void VideoShow::slot_TimerTimeOut()
{
     if (QObject::sender() == m_Timer)
     {
         //获取到的时间是微秒 换算为秒
         qint64 Sec = m_Player->getCurrentTime()/1000000;
         //设置进度条
         ui->slider_progress->setValue(Sec);
         //通过秒换算小时
         QString hStr = QString("00%1").arg(Sec/3600);
         //通过秒换算分钟
         QString mStr = QString("00%1").arg(Sec/60%60);
         //通过秒换算秒
         QString sStr = QString("00%1").arg(Sec%60); 
         //在时间label上显示换算后的时间
         QString str = 
         QString("%1:%2:%3").arg(hStr.right(2)).arg(mStr.right(2)).arg(sStr.right(2));
         ui->lb_currentTime->setText(str);
     }
}

在状态切换中打开与关闭定时器

最后的效果展示图

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_37348221/article/details/115556737

智能推荐

html打开方式解锁,解锁bootloader_他们迂回误会的博客-程序员秘密

如何解锁Bootloader下面我以小米手机解锁Boot Loader为例 进入小米手机申请解锁官网:申请解锁小米手机 点击立即解锁进行解锁申请 获得资格之后点击“解锁工具下载” 下载完毕后安装解锁工具并登录小米账号 让手机进入Fastboot模式并连接电脑。解锁bootloader有什么用?通过进入Bootloader,我们可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合...

IAR使用方法建立工程文件超详细操作步骤_程序员-虎哥的博客-程序员秘密_iar怎么新建工程

第一步:第二步:第三步:第四步:点击OK后选择文件夹保存,名字随意第五步:点击保存第六步:第七步:第八步:右键箭头位置按图示添加第九步:右键箭头位置按图示点击第十步:点击箭头位置,并选择文件夹打开,选择CC2530F256.i51文件第十一步:按图示修改,这样写完代码后编译就会生成Hex文件,将他烧写进单片机就好了也可以不用第十一步,接着第十步并按下面两张图操作也可以直接将代码烧写进板...

linux的strtoul函数什么意思,strtoul_写小说的闲鱼牧云的博客-程序员秘密

NAMEstrtoul, strtoull, strtouq - convert a string to an unsignedlong integerSYNOPSIS#include unsigned long intstrtoul(const char *nptr, char **endptr, int base);unsigned long long intstrtoull(cons...

RUBY AND RAILS COMMAND_bee100的博客-程序员秘密

Rails2.0 命令大全2008-05-21 22:11一.铁道1.1 创建一个Rails应用程序$ rails app_name可选项:-d, database=xxx 指定安装一个数据库(mysql oracle postgresql sqlite2 sqlite3 ), 默认情况下是数据库-r, ruby-path= 指定Ruby的安装路径,如果没有指...

Python入门-pycharm安装(编译器)_Finghting!的博客-程序员秘密_pyth arm安装编译器

最近准备学习Python,为下学期学习Python做准备下面是安装编译器,环境安装在下一篇写。一、打开浏览器搜索网址http://www.jetbrains.com/pycharm/download/#section=windows(如下图)二、点击Next进行下一步。三、选择合适的路径继续点击Next。四、选择如下图,然后点击Next。五、点击Install进行下一步等待安装。六...

elementui el-image组件 点击按钮 预览图片_明媚始初晴 。的博客-程序员秘密_elementui el-image

今天遇到一个新的需求,点击按钮查看图片,且图片能放大缩小。如下图:想到了使用element-ui的el-image组件,官网示例:&lt;div class="demo-image__preview"&gt; &lt;el-image style="width: 100px; height: 100px" :src="url" :preview-src-list="srcList"&gt; &lt;/el-image&gt;&lt;/div&gt;&lt;sc

随便推点

特征提取+BP神经网络,实现对图像的分类_做个好男人!的博客-程序员秘密_bp神经网络图像分类

1.前言这里是使用“特征提取+BP神经网络,实现对图像的分类”,需要知道的是:1.对二维图像做完标签,制作数据集后,可以用CNN(一般输入是而惟独图片)来对图像做分类的。当前,在这之前要经过对模型的训练过程。2.也可以通过特征提取先把二维图片变成一位特征,输入到BP神经网络中,进行模型训练,然后做分类。今天,讲到的,就是第2种方式。2.数据集制作这里使用的是对工业金属件表面缺陷图像(有三类缺陷图像,分别是:裂纹crack,夹杂inclusion,麻点pitted )。使用Hu不变矩等方法手动提

Win32 常用API函数_大树学长的博客-程序员秘密

1.获取客户区矩形区域RECT cliRect;GetClientRect(hWnd, &amp;amp;cliRect);2.获取窗口上下文句柄HDC hdc = GetDC(hWnd);//....ReleaseDC(hWnd, hdc);  3.LPWSTR   与 char * 互转int32_t WToChar(LPWSTR szWstr, char szCstr[], const int32...

python下载文件的11种方式_python 下载文件的多种方法汇总_weixin_35526110的博客-程序员秘密

本文档介绍了 Python 下载文件的各种方式,从下载简单的小文件到用断点续传的方式下载大文件。Requests使用 Requests 模块的 get 方法从一个 url 上下载文件,在 python 爬虫中经常使用它下载简单的网页内容import requests# 图片来自bing.comurl = 'https://cn.bing.com/th?id=OHR.DerwentIsle_EN-C...

Ubuntu caffe训练留log_非文艺小燕儿_Vivien的博客-程序员秘密_ubuntu caffe log重定向

通过重定向如下:#!/usr/bin/env shTOOLS=/home/caffe/build/toolsLOG=Train_Log_finetune.log$TOOLS/caffe train \--solver=solver.prototxt \--weights=iter_6000.caffemodel \--gpu 2 \2>&1 | tee $LOG

nodejs连接mysql报错throw err; // Rethrow non-MySQL errors解决方法_南隅笙箫的博客-程序员秘密

环境:mysql: 8.0+ ;macos; 64位;报错主要信息:/Users/edisonhuang/CodingSpace/VScodeProjects/WebProjects/HTMLTest/web_final/node_modules/mysql/lib/protocol/Parser.js:437 throw err; // Rethrow non-MySQL err...

推荐文章

热门文章

相关标签