编写一个视频播放器
一、开发环境
VS2015+Qt5.12+ffmpeg
二、概要说明
利用ffmpeg对本地视频进行解析,将每一帧读出来转为一个图片,在主界面的label控件中挨个显示图片。
核心功能
-
本地视频播放
-
网络视频播放
锦上添花
-
播放、暂停、停止。
-
读取视频的总时长、比特率。
-
保存视频的每一帧到指定图片文件夹。
三、详细实现
-
日志模块
-
spdlog
日志模块使用第三方的spdlog库,详细配置见使用C++日志spdlog的记录
-
-
播放器类
-
头文件说明
#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
-
代码实现
#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); }
-
-
界面显示
//播放 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);//保存图片 } }