音视频基础学习之【05.添加播放控制】

 

目录

播放器播放控制添加

界面设计

暂停

停止

主线程具体操作

视频解码线程具体操作

音频解码函数具体操作

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);
     }
}

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

最后的效果展示图

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值