草庐IT

Qt-FFmpeg开发-回调函数读取数据(8)

mahuifa 2023-04-12 原文

音视频/FFmpeg #Qt

Qt-FFmpeg开发-使libavformat解复用器通过自定义AVIOContext读取回调访问媒体内容

目录

更多精彩内容
?个人内容分类汇总 ?
?音视频开发 ?

1、概述

  • 最近研究了一下FFmpeg开发,功能实在是太强大了,网上ffmpeg3、4的文章还是很多的,但是学习嘛,最新的还是不能放过,就选了一个最新的ffmpeg n5.1.2版本,和3、4版本api变化还是挺大的;
  • 这是一个libavformat AVIOContext API示例;
  • 这里主要是研究FFmpeg官方示例产生的一个程序,官方示例可以看Examples
  • 但是官方示例一般有一些小问题,这里通过学习官方示例程序,加上自己的理解完成了这一个基于Qt的FFmpeg avio_reading.c(官方示例编译后是通过命令行执行)。

开发环境说明

  • 系统:Windows10、Ubuntu20.04
  • Qt版本:V5.12.5
  • 编译器:MSVC2017-64、GCC/G++64
  • FFmpeg版本:n5.1.2

2、实现效果

  1. 将一个视频文件中所有数据读取到buf中;
  2. 为AVIOContext创建一个回调函数;
  3. 创建一个长度为4096内存用于从buf中读取数据;
  4. 使用回调函数完成数据的读取;
  5. 关键步骤加上详细注释,比官方示例更便于学习。
  • 这个程序的原理如下:把视频文件中所有数据读取到内存中,再通过回调函数按照4096的长度去读取。

  • 实现结果如下:

3、主要代码

  • 啥也不说了,直接上代码,一切有注释

  • widget.h文件

    #ifndef WIDGET_H
    #define WIDGET_H
    
    #include <QWidget>
    
    QT_BEGIN_NAMESPACE
    namespace Ui { class Widget; }
    QT_END_NAMESPACE
    
    struct AVFormatContext;
    struct AVIOContext;
    
    class Widget : public QWidget
    {
        Q_OBJECT
    
    public:
        Widget(QWidget *parent = nullptr);
        ~Widget();
    
    private slots:
        void on_pushButton_clicked();
    
        void on_pushButton_2_clicked();
    
    
        static int read_packet(void *opaque, uint8_t *buf, int buf_size);
    
    private:
        void showError(int err);
        int  openAV();
        void showLog(const QString& log);
    
    private:
        Ui::Widget *ui;
    
        AVFormatContext* m_formatContext = nullptr;
        AVIOContext    * m_avioContext   = nullptr;
        uchar          * m_buffer        = nullptr;    // 保存打开的媒体文件的所有数据
        quint64          m_bufSize       = 0;          // 打开的文件的总大小
        uchar          * m_avioBuffer    = nullptr;    // 从m_buffer中一次读取的数据
        int              m_avioBufSize   = 4096;       // 从m_buffer中一次读取的数据长度
    };
    #endif // WIDGET_H
    
    
  • widget.cpp文件

    #include "widget.h"
    #include "ui_widget.h"
    #include <qfiledialog.h>
    #include <QDebug>
    #include <qthread.h>
    #include <qtimer.h>
    
    extern "C" {        // 用C规则编译指定的代码
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libavformat/avio.h"
    #include "libavutil/file.h"
    }
    
    typedef struct BufferData {
        uchar*  ptr;
        quint64 size;   // 缓冲区中剩余的大小
    }BufferData;
    
    Widget::Widget(QWidget *parent)
        : QWidget(parent)
        , ui(new Ui::Widget)
    {
        ui->setupUi(this);
    
        this->setWindowTitle(QString("AVIOContext访问的自定义缓冲区读取数据 V%1").arg(APP_VERSION));
    }
    
    Widget::~Widget()
    {
        delete ui;
    }
    
    /**
     * @brief 选择文件
     */
    void Widget::on_pushButton_clicked()
    {
        QString strName = QFileDialog::getOpenFileName(this, "选择播放视频~!", "/", "视频 (*.mp4 *.m4v *.mov *.avi *.flv);; 其它(*)");
        if(strName.isEmpty())
        {
            return;
        }
        ui->line_file->setText(strName);
    }
    
    void Widget::on_pushButton_2_clicked()
    {
        int ret = openAV();
        if(ret < 0)
        {
            showError(ret);
        }
        av_file_unmap(m_buffer, m_bufSize);           // 释放m_buffer
        avformat_free_context(m_formatContext);       // 释放m_formatContext
        if(m_avioContext)
        {
            av_freep(&m_avioContext->buffer);         // 释放m_avioBuffer
        }
        avio_context_free(&m_avioContext);            // 释放m_avioContext并置NULL
    
        m_avioBuffer = nullptr;
        m_buffer = nullptr;
        m_bufSize = 0;
        m_formatContext = nullptr;
    }
    
    /**
     * @brief    自定义非阻塞延时
     * @param ms
     */
    void msleep(int ms)
    {
        QEventLoop loop;
        QTimer::singleShot(ms, &loop, SLOT(quit()));
        loop.exec();
    
    }
    
    /**
     * @brief           回调读取数据
     * @param opaque
     * @param buf
     * @param buf_size
     * @return
     */
    int Widget::read_packet(void *opaque, uint8_t *buf, int buf_size)
    {
        BufferData *bd = static_cast<BufferData *>(opaque);    // bd指针指向了读取文件的所有数据
        buf_size = FFMIN(buf_size, int(bd->size));             // 获取最小值
    
        if (!buf_size)
        {
            return AVERROR_EOF;       // 文件结尾
        }
        qDebug() << QString("当前指向缓冲区位置ptr:0x%1     剩余数据长度size:%2").arg(quint64(bd->ptr), 0, 16).arg(bd->size);
    
        /* 将内部缓冲区数据复制到buf */
        memcpy(buf, bd->ptr, quint64(buf_size));
        bd->ptr  += buf_size;                     // 通过指针向后移动读取数据
        bd->size -= quint64(buf_size);            // 每读取一次则剩余长度减4096
    
        msleep(1);   // 加上延时,否则回调函数执行很快,不能用QThread延时
    
        return buf_size;
    }
    
    int Widget::openAV()
    {
        QString strName = ui->line_file->text();
        if(strName.isEmpty())
        {
            return AVERROR(ENOENT);     // 返回文件不存在的错误码
        }
    
        // 打开strName文件,将文件中所有数据读取到m_buffer中,读取的数据长度为m_bufSize,最后两个参数与日志相关,基本用不到
        int ret = av_file_map(strName.toStdString().data(), &m_buffer, &m_bufSize, 0, nullptr);
        if(ret < 0)
        {
            return ret;
        }
        showLog(QString("文件总buf:0x%1     文件总长度:%2").arg(quint64(m_buffer), 0, 16).arg(m_bufSize));
    
        m_formatContext = avformat_alloc_context();      // 分配一个解封装上下文,包含了媒体流的格式信息(.mp4 .avi)
        if(!m_formatContext)
        {
            return AVERROR(ENOMEM);        // 返回无法分配内存的错误码
        }
    
        m_avioBuffer = static_cast<uchar*>(av_malloc(quint64(m_avioBufSize))) ;  // 分配一个空间
        if(!m_avioBuffer)
        {
            return AVERROR(ENOMEM);        // 返回无法分配内存的错误码
        }
        showLog(QString("avioBuffer:0x%1     avioBufSize长度:%2").arg(quint64(m_avioBuffer), 0, 16).arg(m_avioBufSize));
    
        BufferData bufData;
        bufData.ptr = m_buffer;
        bufData.size = m_bufSize;
        m_avioContext = avio_alloc_context(m_avioBuffer,
                                           m_avioBufSize,
                                           0,
                                           &bufData,
                                           &read_packet,
                                           nullptr,
                                           nullptr);
        if(!m_avioContext)
        {
            return AVERROR(ENOMEM);        // 返回无法分配内存的错误码
        }
    
        m_formatContext->pb = m_avioContext;
        showLog(QString("缓冲区的开始:0x%1    缓冲区大小:%3").arg(quint64(m_avioContext->buffer), 0, 16).arg(m_avioContext->buffer_size));
    
        ret = avformat_open_input(&m_formatContext, nullptr, nullptr, nullptr);
        if(ret < 0)
        {
            return ret;
        }
        showLog("回调函数执行完成!");
    
        // 读取媒体文件的数据包以获取流信息。
        ret = avformat_find_stream_info(m_formatContext, nullptr);
        if(ret < 0)
        {
            return ret;
        }
    
        // 打印关于输入或输出格式的详细信息
        av_dump_format(m_formatContext,
                       0,                             // 要转储信息的流的索引
                       strName.toStdString().data(),  // 要打印的URL,例如源文件或目标文件
                       0);                            // 选择指定的上下文是输入(0)还是输出(1)
    
        return 0;
    }
    
    void Widget::showLog(const QString &log)
    {
        ui->textEdit->append(log);
    }
    
    /**
     * @brief        显示ffmpeg函数调用异常信息
     * @param err
     */
    void Widget::showError(int err)
    {
        static char m_error[1024];
        memset(m_error, 0, sizeof (m_error));        // 将数组置零
        av_strerror(err, m_error, sizeof (m_error));
        showLog(QString("Error:%1  %2").arg(err).arg(m_error));
    }
    
    
    

4、完整源代码

有关Qt-FFmpeg开发-回调函数读取数据(8)的更多相关文章

  1. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  4. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  5. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  6. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

    我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

  7. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  8. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  9. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  10. ruby - 有人可以帮助解释类创建的 post_initialize 回调吗 (Sandi Metz) - 2

    我正在阅读SandiMetz的POODR,并且遇到了一个我不太了解的编码原则。这是代码:classBicycleattr_reader:size,:chain,:tire_sizedefinitialize(args={})@size=args[:size]||1@chain=args[:chain]||2@tire_size=args[:tire_size]||3post_initialize(args)endendclassMountainBike此代码将为其各自的属性输出1,2,3,4,5。我不明白的是查找方法。当一辆山地自行车被实例化时,因为它没有自己的initialize方法

随机推荐