用ffmpeg+qt做点有趣的事(4)----- 简易视频播放器

来源:互联网 发布:咸鱼如何申请淘宝介入 编辑:程序博客网 时间:2024/06/05 17:06

前面讲解了如何用FFMPEG解码视频。

现在,我们就着手用FFMPEG+Qt写一个视频播放器吧:

由于现在我们需要显示图像了,因此现在开始需要使用Qt GUI工程了。

创建工程的时候记得选择Qt GUI应用。


引用FFMPEG请参考前面的文章,这里不再介绍。

做过图像界面开发的都知道,任何耗时的操作都不能放在主线程进行,一旦主线程阻塞了,那么体现出来的就是界面卡了。 而我们读取视频和解码视频是一个非常耗时的操作,因此需要另外开辟一个线程来专门做这件事。


Qt里面线程的用法 则是写一个类继承QThread, 然后重载其run函数,把耗时的操作全部放入run函数。

1
2
3
4
5
6
7
8
9
10
11
12
class VideoPlayer : public QThread
{
    Q_OBJECT
 
public:
    explicit VideoPlayer();
    ~VideoPlayer();
 
protected:
    void run();
 
};


这里run函数里面就是写我们读取视频和解码视频的代码了;

读取和解码还是和前面说的一样的方法:


改动的一点是:

由于我们需要用Qt的控件来显示,因此是把解码之后的YUV数据转换成了RGB32格式的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
///这里我们改成了 将解码后的YUV数据转换成RGB32    
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
            pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
            PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
 
    numBytes = avpicture_get_size(PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
 
    out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, PIX_FMT_RGB32,
            pCodecCtx->width, pCodecCtx->height);
             
             
....
...
..
 
sws_scale(img_convert_ctx,
                (uint8_t const const *) pFrame->data,
                pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                pFrameRGB->linesize);



同时将转换后的RGB32数据存入QImage对象:

1
2
//把这个RGB数据 用QImage加载
QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);



由于我们不能够在子线程中操作界面,(操作界面只能在主线程中进行,几乎所有的图形界面开发都是这样设定),因此我们只能给主线程发送信号,信号带上这个QIMage,让主线程帮忙把这个图像显示出来。


声明信号:

1
2
signals:    
    void sig_GetOneFrame(QImage); //没获取到一帧图像 就发送此信号


发送信号:

1
2
3
4
//把这个RGB数据 用QImage加载                
QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
emit sig_GetOneFrame(image);  //发送信号



主线程绑定并接收信号:

1
2
    mPlayer = new VideoPlayer;    
    connect(mPlayer,SIGNAL(sig_GetOneFrame(QImage)),this,SLOT(slotGetOneFrame(QImage)));


信号处理函数如下:

1
2
3
4
void MainWindow::slotGetOneFrame(QImage img){
    mImage = img;
    update(); //调用update将执行 paintEvent函数
}



主线程显示图像 则是通过QPainter直接绘制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void MainWindow::paintEvent(QPaintEvent *event){
    QPainter painter(this);
    painter.setBrush(Qt::black);
    painter.drawRect(0, 0, this->width(), this->height()); //先画成黑色
 
    if (mImage.size().width() <= 0) return;
 
    ///将图像按比例缩放成和窗口一样大小
    QImage img = mImage.scaled(this->size(),Qt::KeepAspectRatio);
 
    int x = this->width() - img.width();
    int y = this->height() - img.height();
 
    x /= 2;
    y /= 2;
 
    painter.drawImage(QPoint(x,y),img); //画出图像
 
}



运行之后的程序如下:



当然这个离播放器还有十万八千里的步伐,后面我们将会一步一步完善它。

古语有云:不积跬步,无以至千里;不积小流,无以成江海。

写代码也一样,得慢慢来,一步一步往目标走,总能成功。


主程序:

#include <QApplication>

#include <QTextCodec>

#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QTextCodec *codec = QTextCodec::codecForName("UTF-8"); //设置编码格式为UTF-8
    QTextCodec::setCodecForLocale(codec);
    //QTextCodec::setCodecForCString(codec);
    //QTextCodec::setCodecForTr(codec);

    MainWindow w;
    w.show();
    return a.exec();
}

MainWindow类的cpp文件:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPainter>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    mPlayer = new VideoPlayer;
    connect(mPlayer,SIGNAL(sig_GetOneFrame(QImage)),this,SLOT(slotGetOneFrame(QImage)));
    mPlayer->setFileName("./1111.mp4");
 
    mPlayer->startPlay();
}
MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setBrush(Qt::black);//设置画刷颜色
    painter.drawRect(0, 0, this->width(), this->height()); //先画成黑色
    if (mImage.size().width() <= 0)
 return;
    ///将图像按比例缩放成和窗口一样大小
    QImage img = mImage.scaled(this->size(),Qt::KeepAspectRatio);
    int x = this->width() - img.width();
    int y = this->height() - img.height();
    x /= 2;
    y /= 2;
    painter.drawImage(QPoint(x,y),img); //画出图像
}
void MainWindow::slotGetOneFrame(QImage img)
{
    mImage = img;
    update(); //调用update将执行 paintEvent函数
}
MainWindow类的h文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QImage>
#include <QPaintEvent>
#include "videoplayer/videoplayer.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
protected:
    void paintEvent(QPaintEvent *event);
private:
    Ui::MainWindow *ui;
    VideoPlayer *mPlayer; //播放线程
    QImage mImage; //记录当前的图像
private slots:
    void slotGetOneFrame(QImage img);
};
#endif // MAINWINDOW_H

videoplayer类:

#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H
#include <QThread>
#include <QImage>
class VideoPlayer : public QThread
{
    Q_OBJECT
public:
    explicit VideoPlayer();
    ~VideoPlayer();
    void setFileName(char* path)
    {
        int len = strlen(path);
        int i;
        for(i=0;i<len;i++)
        mFileName[i] = path[i];
        mFileName[i] = '\0';
    }
    void startPlay();
signals:
    void sig_GetOneFrame(QImage); //没获取到一帧图像 就发送此信号
protected:
    void run();
private:
    char mFileName[25];
};
#endif // VIDEOPLAYER_H

#include "videoplayer.h"
#include <QDebug>
extern "C"
{
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libavutil/pixfmt.h"
    #include "libswscale/swscale.h"
}
#include <stdio.h>
VideoPlayer::VideoPlayer()
{
}
VideoPlayer::~VideoPlayer()
{
}
void VideoPlayer::startPlay()
{
    ///调用 QThread 的start函数 将会自动执行下面的run函数 run函数是一个新的线程
    this->start();
}
void VideoPlayer::run()
{
   // char *file_path = mFileName.toUtf8().data();
    //qDebug()<<*file_path;
    AVFormatContext *pFormatCtx;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    AVFrame *pFrame, *pFrameRGB;
    AVPacket *packet;
    uint8_t *out_buffer;
    static struct SwsContext *img_convert_ctx;
    int videoStream, i, numBytes;
    int ret, got_picture;
    av_register_all(); //初始化FFMPEG  调用了这个才能正常适用编码器和解码器
    //Allocate an AVFormatContext.
    pFormatCtx = avformat_alloc_context();
    if (avformat_open_input(&pFormatCtx, mFileName, NULL, NULL) != 0) {
        printf("can't open the file11. \n");
        return;
    }
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        printf("Could't find stream infomation.\n");
        return;
    }
    videoStream = -1;
    ///循环查找视频中包含的流信息,直到找到视频类型的流
    ///便将其记录下来 保存到videoStream变量中
    ///这里我们现在只处理视频流  音频流先不管他
    for (i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
        }
    }
    ///如果videoStream为-1 说明没有找到视频流
    if (videoStream == -1) {
        printf("Didn't find a video stream.\n");
        return;
    }
    ///查找解码器
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL) {
        printf("Codec not found.\n");
        return;
    }
    ///打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        printf("Could not open codec.\n");
        return;
    }
    pFrame = av_frame_alloc();
    pFrameRGB = av_frame_alloc();
    ///这里我们改成了 将解码后的YUV数据转换成RGB32
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
            pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
            PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
    numBytes = avpicture_get_size(PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
    out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, PIX_FMT_RGB32,
            pCodecCtx->width, pCodecCtx->height);
    int y_size = pCodecCtx->width * pCodecCtx->height;
    packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
    av_new_packet(packet, y_size); //分配packet的数据
    av_dump_format(pFormatCtx, 0, mFileName, 0); //输出视频信息
    while (1)
    {
        if (av_read_frame(pFormatCtx, packet) < 0)
        {
            break; //这里认为视频读取完了
        }
        if (packet->stream_index == videoStream) {
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);
            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);  //发送信号
            }
        }
        av_free_packet(packet);
        msleep(5); //停一停  不然放的太快了
    }
    av_free(out_buffer);
    av_free(pFrameRGB);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
}





阅读全文
0 0
原创粉丝点击