zoukankan      html  css  js  c++  java
  • Qt+FFmpeg 简单实现视频播放

    这里使用 Qt + FFmpeg 实现了一个简单播放视频的例子。先看下按下按钮播放视频时的效果图:


    完整工程下载链接:Github-FFmpeg_demo

    注意:一定要将 bin 目录下的 dll 文件拷贝到编译生成的 exe 所在的目录下,否则会提示:程序异常结束,无法运行。


    一、开发环境的准备

    我所用的开发环境是qt-opensource-windows-x86-msvc2015-5.11.2.exe,这里使用 Qt 自带的 Qt Creator 编译器,也可以使用微软的 VS,安装 Qt 插件即可,具体可以参考:如何在 VS2015 上开发 Qt 程序


    (一)下载工具

    现在准备 FFmpeg 的开发环境:

    FFmpeg 下载地址:https://ffmpeg.zeranoe.com/builds/

    点击上面地址后弹出界面如下图 1 所示,然后选择 Windows 32-bit 的 FFmpeg,当然你也可以选择 64 位的,不过我选择的是 32 位。之后我们需要将它右侧 Linking 下的 Shared 和 Dev 下载下来,解压后 Dev 的 include 里是它的头文件、lib 里是他的静态链接库,Shared 里的 bin 是它的 dll 和 .exe 程序。之后我们将它 Dev 里的 include、lib 和 Shared 里的 bin 拷贝出来形成如下图 2 所示。



    (二)添加库

    然后新建一个 Qt Widgets Application 工程,在 .pro 添加 include目录 和 lib 目录的路径。

    INCLUDEPATH +="./include"
    LIBS += -L$$PWD/lib -lavutil -lavformat -lavcodec -lavdevice -lavfilter -lpostproc -lswresample -lswscale
    

    -L 后面不用加空格,直接跟着地址,然后空格,-l 后面是去掉 lib 之后的文件名",这样 qmake 编译器就可以通过路径+文件名正确的定位到库文件了,另外 $PWD 表示当前目录。

    然后要将 bin 目录下的 dll 文件拷贝到编译生成的 exe 所在的目录下,否则会提示:程序异常结束,无法运行。原因是缺少库文件。编译时,我们可能会把库文件放在与源代码相同的地方或其他地方,只要在 pro 文件中设置路径即可,但运行时的路径和编译时的路径往往不一样,这样就导致运行时找不到库文件,需要将库文件拷贝至运行路径下才行。


    二、代码实现播放功能

    在界面上放置一个 QLabel 和 QPushButton 控件,当点击按钮时播放视频。程序主要分为以下几方面:

    1、打开音视频流并获取音视频流信息;

    2、查找视频流位置以及查找并打开视频解码器;

    3、视频解码的同时处理图片像素数据;

    4、最后要释放申请的内存空间。


    完整代码如下:

    #include "widget.h"
    #include "ui_widget.h"
    #include <QTime>
    
    // 调用FFmpeg的头文件
    extern "C"{
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswscale/swscale.h>
    #include <libavutil/imgutils.h>
    }
    
    Widget::Widget(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::Widget)
    {
        ui->setupUi(this);
    }
    
    Widget::~Widget()
    {
        delete ui;
    }
    
    // 延时函数
    void delay(int msec)
    {
        QTime dieTime = QTime::currentTime().addMSecs(msec);
        while( QTime::currentTime() < dieTime )
            QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
    }
    
    void Widget::on_pushButton_clicked()
    {
        AVFormatContext *pFormatCtx; // 存储音视频封装格式中包含的信息
        int videoIndex = -1; // 视频帧索引,初始化为-1
        AVCodecContext *pCodecCtx; // 视频流编码结构
        AVCodec *pCodec; // 视频解码器
        AVFrame *pFrame, *pFrameRGB;
        unsigned char *out_buffer;
        AVPacket *packet;
        int ret, got_picture;
        struct SwsContext *img_convert_ctx; // 主要用于视频图像的转换
    
        char filepath[] = "../FFmpeg_demo/test.mp4"; // 当前目录为构建目录
    
        // 注册FFMpeg的库
        av_register_all();
    
        /*** (一)打开音视频流并获取音视频流信息 ***/
        // 初始化AVFormatContext
        pFormatCtx = avformat_alloc_context();
        // 打开音视频流
        if (avformat_open_input(&pFormatCtx, filepath, nullptr, nullptr) != 0)
        {
            printf("Couldn't open input stream.
    ");
            return;
        }
        // 获取音视频流数据信息
        if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)
        {
            printf("Couldn't find stream information.
    ");
            return;
        }
    
        /*** (二)查找视频流位置以及查找并打开视频解码器 ***/
        // 查找视频流的起始索引位置(nb_streams表示视音频流的个数)
        for (int i = 0; i < (int)pFormatCtx->nb_streams; i++)
        {
            // 查找到视频流时退出循环
            if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) // 判断是否为视频流
            {
                videoIndex = i;
                break;
            }
        }
        if (videoIndex == -1)
        {
            printf("Didn't find a video stream.
    ");
            return ;
        }
        // 查找视频解码器
        pCodecCtx = pFormatCtx->streams[videoIndex]->codec; // 获取视频流编码结构
        pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
        if (pCodec == nullptr)
        {
            printf("Codec not found.
    ");
            return ;
        }
        // 打开解码器
        if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0)
        {
            printf("Could not open codec.
    ");
            return ;
        }
        // 打印视频信息
        printf("--------------- File Information ----------------
    ");
        av_dump_format(pFormatCtx, 0, filepath, 0); // 此函数打印输入或输出的详细信息
        printf("-------------------------------------------------
    ");
    
        /*** (三)视频解码的同时处理图片像素数据 ***/
        // 创建帧结构,此函数仅分配基本结构空间,图像数据空间需通过av_malloc分配
        pFrame = av_frame_alloc();
        pFrameRGB = av_frame_alloc();
        // 创建动态内存,创建存储图像数据的空间(av_image_get_buffer_size获取一帧图像需要的大小)
        out_buffer = (unsigned char *)av_malloc((size_t)av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1));
        // 存储一帧像素数据缓冲区
        av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, out_buffer,
            AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1);
        packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    
        // 初始化img_convert_ctx结构
        img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
            pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr);
        // av_read_frame读取一帧未解码的数据
        while (av_read_frame(pFormatCtx, packet) >= 0)
        {
            // 如果是视频数据
            if (packet->stream_index == videoIndex)
            {
                // 解码一帧视频数据
                ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
                if (ret < 0)
                {
                    printf("Decode Error.
    ");
                    return ;
                }
                if (got_picture)
                {
                    sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
                                        pFrameRGB->data, pFrameRGB->linesize);
                    QImage img((uchar*)pFrameRGB->data[0],pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
                    ui->label->setPixmap(QPixmap::fromImage(img)); // 在label上播放视频图片
                    delay(40);
                }
            }
            av_free_packet(packet);
        }
    
        /*** (四)最后要释放申请的内存空间 ***/
        sws_freeContext(img_convert_ctx); // 释放一个SwsContext
        av_frame_free(&pFrameRGB);
        av_frame_free(&pFrame);
        avcodec_close(pCodecCtx);
        avformat_close_input(&pFormatCtx);
    }
    

    打印出的音视频流信息如下:

    --------------- File Information ----------------
    -------------------------------------------------
    Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '../FFmpeg_demo/test.mp4':
      Metadata:
        major_brand     : isom
        minor_version   : 1
        compatible_brands: isom
        creation_time   : 2015-06-30T08:50:41.000000Z
        copyright       : 
        copyright-eng   : 
      Duration: 00:00:15.06, start: 0.000000, bitrate: 470 kb/s
        Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 640x360 [SAR 1:1 DAR 16:9], 418 kb/s, 24 fps, 24 tbr, 24k tbn, 48 tbc (default)
        Metadata:
          creation_time   : 2015-06-30T08:50:40.000000Z
          handler_name    : TrackHandler
        Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 49 kb/s (default)
        Metadata:
          creation_time   : 2015-06-30T08:50:40.000000Z
          handler_name    : Sound Media Handler
    

    参考:

    基于Qt、FFMpeg的音视频播放器设计一(准备环境)

    QT+FFMPEG实现视频播放


  • 相关阅读:
    require的特点
    require和load的不同之处
    关于“load”方法
    puts方法要点
    用类解释对象的由来
    以方法调用的原理解释Ruby中“puts ‘Hello‘”
    Ruby中方法的设计理念
    Ruby中puts,print,p的区别
    Ubuntu16.04安装MongoDB的Ruby驱动
    使用spring框架,用xml方式进行bean装配出现“The fully qualified name of the bean's class, except if it serves...”
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/12046600.html
Copyright © 2011-2022 走看看