编写一个视频播放器

编写一个视频播放器

 

一、开发环境

VS2015+Qt5.12+ffmpeg

二、概要说明

利用ffmpeg对本地视频进行解析,将每一帧读出来转为一个图片,在主界面的label控件中挨个显示图片。

核心功能

  1. 本地视频播放

  2. 网络视频播放

锦上添花

  1. 播放、暂停、停止。

  2. 读取视频的总时长、比特率。

  3. 保存视频的每一帧到指定图片文件夹。

三、详细实现

  1. 日志模块

    1. spdlog

      日志模块使用第三方的spdlog库,详细配置见使用C++日志spdlog的记录

  2. 播放器类

    1. 头文件说明

      #ifndef VIDEOPLAYER_H
      #define VIDEOPLAYER_H
      ​
      //#include <QObject>
      #include <QThread>
      #include <qimage.h>
      ​
      ​
      class videoPlayer : public QThread
      {
          Q_OBJECT
      public:
          explicit videoPlayer();
          ~videoPlayer ();
          void initPlay(QString path);    //初始化路径
          void startPlay();   //播放
          void stopPlay();    //停止
          void pausePlay(bool pause); //暂停
          bool isPause;
          bool isStop;
      private:
          QString qFilePath;  
          const char* filePath;
      ​
      signals:
          void sig_GetInfo(QString totalTime, int totalFrames, int bps, int w, int h);//总时长,总帧数,比特率,宽,高
          void sig_GetOneFrame(QImage);   //获得一帧
      ​
      protected:
          void run();
      ​
      };
      ​
      #endif // VIDEOPLAYER_H
    2. 代码实现

      #include "videoplayer.h"
      #include <iostream>
      #include <qdebug.h>
      #include <thrPart/include/spdlog/spdlog.h>
      using namespace std;
      ​
      extern "C"
      {
      #include <libavcodec/avcodec.h>
      #include <libavformat/avformat.h>
      #include <libswscale/swscale.h>
      #include <libavutil/imgutils.h>
      };
      ​
      //videoPlayer::sFilePath ;
      videoPlayer::videoPlayer()
      {
          isStop = false;
          isPause = false;
      }
      ​
      videoPlayer::~videoPlayer()
      {
      }
      ​
      //初始化
      void videoPlayer::initPlay(QString path)
      {
          //videoPlayer::sFilePath = path;// str.c_str();
          //this->filePath = path.toStdString().c_str();
          //qDebug() << "const char*" << sFilePath.c_str();//QString(sFilePath);
          this->qFilePath = path;
          qDebug() << "QString" << qFilePath;
      }
      //播放
      void videoPlayer::startPlay()
      {
          spdlog::get("mylog")->critical("videoPlayer::start \n");
          isPause = false;
          isStop = false;
          this->start();
      }
      //停止
      void videoPlayer::stopPlay()
      {
          isStop = true;
      }
      //暂停
      void videoPlayer::pausePlay(bool pause)
      {
          isPause = pause;
      }
      ​
      //开始
      void videoPlayer::run()
      {
          AVFormatContext *pFormatCtx;
          int i, videoIndex;
          AVCodecContext *pCodecCtx;
          AVCodec *pCodec;
          AVFrame *pFrame, *pFrameYUV, *pFrameRGB;
          unsigned char *out_buffer;
          AVPacket *pkt;
          int ret, got_picture;
          SwsContext *img_convert_ctx;
          spdlog::get("mylog")->critical("here is videoplayer's init.\n");
          //spdlog::get("mylog")->critical("path is {0}.\n",pth);
          /*
          QString filePath;
          AVFormatContext *pFormatCtx;
          int i,videoIndex;
          AVCodecContext *pCodecCtx;
          AVCodec *pCodec;
          AVFrame *pFrame,*pFrameYUV;
          unsigned char *out_buffer;
          AVPacket *pkt;
          int ret,got_picture;
          */
          //const char* filePath =  "D:\\myImages\\001.mp4"; //this->filePath;     pth;  //文件路径
                                                     //char filePath[]="D:\\myImages\\0002.mp4";
          //const char* filePath = sFilePath.c_str();
          string str = qFilePath.toStdString();;
          filePath = str.c_str();
      ​
          spdlog::get("mylog")->critical("av_register. \n");
          av_register_all();
          avformat_network_init();    //网络初始化
          spdlog::get("mylog")->critical("avformat_alloc_context. \n");
          pFormatCtx = avformat_alloc_context();
      ​
          if (avformat_open_input(&pFormatCtx, (filePath), NULL, NULL)<0) {
              spdlog::get("mylog")->critical("Couldn't open input stream.\n");
          }
          if (avformat_find_stream_info(pFormatCtx, NULL)<0) {
              spdlog::get("mylog")->critical("Couldn't find stream information.\n");
          }
          videoIndex = -1;
          for (int i = 0; i<pFormatCtx->nb_streams; i++) {
              if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
                  videoIndex = i;
              }
          }
          if (videoIndex == -1) {
              spdlog::get("mylog")->critical("Didn't find a video stream.\n");
          }
          pCodecCtx = pFormatCtx->streams[videoIndex]->codec;
          pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
          if (pCodec == NULL) {
              spdlog::get("mylog")->critical("Code not found.\n");
          }
          if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) {
              spdlog::get("mylog")->critical("Coundn't open codec.\n");
          }
          pFrame = av_frame_alloc();
          pFrameYUV = av_frame_alloc();
          pFrameRGB = av_frame_alloc();
      ​
          //    out_buffer=(unsigned char *)av_malloc(
          //                av_image_get_buffer_size(AV_PIX_FMT_YUV420P
          //                                         ,pCodecCtx->width,
          //                                         pCodecCtx->height,1));
      ​
          //    av_image_fill_arrays(pFrameYUV->data,pFrameYUV->linesize,
          //                         out_buffer,AV_PIX_FMT_YUV420P,
          //                         pCodecCtx->width,pCodecCtx->height,1);
      ​
          spdlog::get("mylog")->critical("------------File Information----------\n");
          av_dump_format(pFormatCtx, 0, (filePath), 0);  //.toStdString().c_str()
          long time=pFormatCtx->duration;//时间长度;
          QString totalTime = QString("%1h %2m %3s").arg(QString::number((time/1000000)/60  /60))
              .arg(QString::number((time / 1000000) / 60 % 60)).arg(QString::number((time / 1000000) % 60));
          int totalFrames = 0;
          int bps = pFormatCtx->bit_rate;
          int w = pFormatCtx->streams[videoIndex]->codec->width;
          int h = pFormatCtx->streams[videoIndex]->codec->height;
          //emit sig_GetInfo(totalTime, totalFrames,  bps,  w,  h);
          emit sig_GetInfo(totalTime, totalFrames, bps, w, h);
          
          spdlog::get("mylog")->critical("---------------------------------------\n");
      ​
          //将解码后的YUV数据转换成RGB32
          img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
              pCodecCtx->pix_fmt, pCodecCtx->width
              , pCodecCtx->height,
              AV_PIX_FMT_RGB32
              , SWS_BICUBIC, NULL, NULL, NULL);
          int size = avpicture_get_size(AV_PIX_FMT_RGB32,
              pCodecCtx->width,
              pCodecCtx->height);
          out_buffer = (unsigned char *)av_malloc(size*sizeof(unsigned char));
          avpicture_fill((AVPicture*)pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
              pCodecCtx->width, pCodecCtx->height);
          int y_size = pCodecCtx->width* pCodecCtx->height;
          pkt = (AVPacket*)malloc(sizeof(AVPacket));
          av_new_packet(pkt, y_size);
      ​
      ​
          spdlog::get("mylog")->critical("videoPlayer::run \n");
          while (1)
          {
              //添加暂停停止控制
              if (isStop) {
                  break;
              }
              if(isPause){
                  this_thread::sleep_for(std::chrono::seconds(1));    //等待一秒
                  continue;
              }
      ​
              if (av_read_frame(pFormatCtx, pkt) < 0)
              {
                  break; //这里认为视频读取完了
              }
      ​
              if (pkt->stream_index == videoIndex) {
                  ret = avcodec_decode_video2(
                              pCodecCtx, pFrame,
                              &got_picture,pkt);
      ​
                  if (ret < 0) {
                      printf("decode error.\n");
                      return;
                  }
      ​
                  if (got_picture) {
                      sws_scale(img_convert_ctx,
                              (uint8_t const * const *) pFrame->data,
                              pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                              pFrameRGB->linesize);
                      //把这个RGB数据 用QImage加载
                      QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
                      QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
                      emit sig_GetOneFrame(image);  //发送信号
                      //emit sig_SaveFrame(image);        //发送信号,保存图片使用
             ///2017.8.11---lizhen
                      //提取出图像中的R数据
                      for(int i=0;i<pCodecCtx->width;i++)
                      {
                          for(int j=0;j<pCodecCtx->height;j++)
                          {
                              QRgb rgb=image.pixel(i,j);
                              int r=qRed(rgb);
                              image.setPixel(i,j,qRgb(r,0,0));
                          }
                      }
                      //emit sig_GetOneFrame(image);
                  }
              }
              av_free_packet(pkt); //释放资源,否则内存会一直上升
      ​
           ///2017.8.7---lizhen
              //msleep(0.02); //停一停  不然放的太快了
          }
          //释放资源,否则第二次播放不了
          av_frame_free(&pFrame);
          av_frame_free(&pFrameYUV);
          av_frame_free(&pFrameRGB);
          avcodec_close(pCodecCtx);
          avformat_close_input(&pFormatCtx);
          sws_freeContext(img_convert_ctx);
      }

       

  3. 界面显示

    //播放
    void Widget::on_btn_play_clicked()
    {
        shouldStop = false;     //可以保存图片
        spdlog::get("mylog")->warn("on_btn_play_clicked \n");
        //qfilePath = ui->lineEdit->text(); //从界面取得地址,防止直接输入?
        //this->setUpdatesEnabled(false);
        vPlayer->initPlay(qfilePath);
    ​
        //创建一个线程专门用来保存图片:
        std::thread([&](Widget *p) {
            //开始保存图片
            Widget::savePics();
        }, this).detach();
    ​
        vPlayer->startPlay();
    ​
        ui->btn_Open->setEnabled(false);
        ui->btn_play->setEnabled(false);
        ui->btn_pause->setEnabled(true);
        ui->btn_stop->setEnabled(true);
    }
    //停止
    void Widget::on_btn_stop_clicked()
    {
        vPlayer->stopPlay();//停止
        shouldStop = true;  //停止保存图片线程
    ​
        ui->btn_Open->setEnabled(true);
        ui->btn_play->setEnabled(true);
        ui->btn_pause->setEnabled(false);
        ui->btn_stop->setEnabled(false);
        
    }
    //暂停
    void Widget::on_btn_pause_clicked()
    {
        if (ui->btn_pause->text()=="暂停")
        {
            vPlayer->pausePlay(true);
            ui->btn_pause->setText("继续");
        }
        else
        {
            vPlayer->pausePlay(false);
            ui->btn_pause->setText("暂停");
        }
    }
    ​
    void Widget::paintEvent(QPaintEvent *event)
    {
        //QPainter painter(this);
        //painter.s
    }
    //勾选保存图片
    void Widget::on_checkBoxChange(int i)
    {
        if (ui->checkBoxConvert->checkState() == Qt::CheckState::Checked)
        {
            savePic = true;
        }
        else
        {
            savePic = false;
        }
    }
    //更新界面:回调函数:可选择是否保存图片
    void Widget::slotGetOneFrame(QImage img)
    {
        spdlog::get("mylog")->info("read one image!");
        videoLabel->setPixmap(QPixmap::fromImage(img.scaled(videoLabel->size())));
        spdlog::get("mylog")->info("get one image and save!");
    ​
        //只有勾选的时候才入队列
        if (savePic)    {
            mutex.lock();
            picQueue.push(img);
            mutex.unlock();
        }
    }
    ​
    //设置视频的宽和高
    void Widget::slotGetInfo(QString totalTime, int totalFrames, int bps, int w, int h)
    {
        ui->labelToalTime->setText("totalTime/"+totalTime);
        //ui->labelTotalFrames->setText("totalFrames/"+QString::number(totalFrames));
        ui->labelBPS->setText("BPS/"+QString::number(bps));
        
    }
    ​
    ​
    ​
    void Widget::savePics()
    {
        while (true)
        {
            if (shouldStop) {
                break;  //停止线程!
            }
            if (picQueue.empty())
            {
                continue;
            }
    ​
            QImage img;
            mutex.lock();
            img = picQueue.front();
            picQueue.pop();
            mutex.unlock();
            img.save(savePicPath +"\\"+QString::number(picCnt++) + ".jpg", "JPG", 100);//保存图片
        }
    }

     

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
当使用Qt Quick和QML编写一个视频播放器时,可以使用信号与槽机制来实现各种功能。以下是一个简单的示例,展示了如何使用信号与槽机制来控制视频播放器: 首先,我们需要导入`QtMultimedia`模块,以便使用相关的视频播放功能。在QML文件的开头添加以下代码: ```qml import QtQuick 2.0 import QtMultimedia 5.0 ``` 接下来,我们创建一个视频播放器组件并定义一些属性和信号。在QML文件中添加以下代码: ```qml Item { width: 800 height: 600 Video { id: videoPlayer source: "path/to/video/file.mp4" autoPlay: false onStatusChanged: { if (status === MediaPlayer.EndOfMedia) { videoPlayer.stop() videoFinished() } } } function play() { videoPlayer.play() } function pause() { videoPlayer.pause() } function stop() { videoPlayer.stop() } signal videoFinished } ``` 在上面的示例代码中,我们创建了一个名为`videoPlayer`的视频组件,并设置了视频文件的路径。我们还定义了三个函数`play()`,`pause()`和`stop()`,用于控制视频的播放、暂停和停止。当视频播放结束时,我们发出了一个名为`videoFinished`的信号。 现在,我们可以在其他地方使用这个视频播放器组件,并连接它的信号与槽来实现视频播放器的控制。以下是一个示例: ```qml Rectangle { width: 200 height: 50 color: "blue" MouseArea { anchors.fill: parent onClicked: { videoPlayer.play() } } Connections { target: videoPlayer onVideoFinished: { console.log("Video finished!") } } } ``` 在上面的示例代码中,我们创建了一个蓝色的矩形作为播放按钮,并将其与视频播放器的`play()`函数连接起来。我们还使用`Connections`组件将视频播放器的`videoFinished`信号与一个匿名函数连接起来,在视频播放结束时打印一条消息。 这只是一个简单的示例,你可以根据自己的需求扩展和修改代码以实现更复杂的功能。希望对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值