音视频/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. java—将查询的结果封装成List<Map>与用回调函数实现数据的动态封装(44)

    手工的开始QueryRunner类.实现数据封装: MapListHandler MapHandler BeanListHandler BeanHandler 第一步:基本的封装测试 写一个类,Que ...

  2. ffmpeg 从内存中读取数据(或将数据输出到内存)

    更新记录(2014.7.24): 1.为了使本文更通俗易懂,更新了部分内容,将例子改为从内存中打开. 2.增加了将数据输出到内存的方法. 从内存中读取数据 ffmpeg一般情况下支持打开一个本地文件, ...

  3. ffmpeg 从内存中读取数据(或将数据输出到内存)(转)

    更新记录(2014.7.24): 1.为了使本文更通俗易懂,更新了部分内容,将例子改为从内存中打开. 2.增加了将数据输出到内存的方法. 从内存中读取数据 ffmpeg一般情况下支持打开一个本地文件, ...

  4. ffmpeg 从内存中读取数据 .

    http://blog.csdn.net/leixiaohua1020/article/details/12980423 ——————————————————————————————————————— ...

  5. Winpcap笔记4之不用回调函数捕获数据包

    函数1: pcap_next_ex(pcap_t*                       p, struct pcap_pkthdr**   pkt_header, const u_char*  ...

  6. Linux应用开发之【多线程开发-回调函数】

    原来我一直都不懂回调函数是什么 ... Callback Function 定义:通过函数指针调用的函数 在理解这个回调函数之前我们需要先了解回调机制 回调机制在编程中体现在:特定的情况发生后,返回并 ...

  7. 复制文件时,如何显示进度条(使用BlockRead函数读取数据,并插入application.ProcessMessages)

    procedure mycopyfile(sourcef,targetf:string;i:integer); var FromF,ToF:file; NumRead,NumWritten:Integ ...

  8. FFMPEG内存操作(一) avio_reading.c 回调读取数据到内存解析

    相关博客列表 : FFMPEG内存操作(一) avio_reading.c 回调读取数据到内存解析 FFMPEG内存操作(二)从内存中读取数及数据格式的转换 FFmpeg内存操作(三)内存转码器 在F ...

  9. 使用进程池模拟多进程爬取url获取数据,使用进程绑定的回调函数去处理数据

    1 # 使用requests请求网页,爬取网页的内容 2 3 # 模拟使用进程池模拟多进程爬取网页获取数据,使用进程绑定的回调函数去处理数据 4 5 import requests 6 from mu ...

  10. ajax执行成功后,在success回调函数中把后台返回的list还原到html的table中

    需求描述:前台通过onclick触发ajax,到后台返回一个list(json格式的),把list插入到html的table中. 思路简介: ̄□ ̄|| 刚开始的时候,是没有思路的,就卡在了,怎么把 a ...

随机推荐

  1. js es6系列——map函数

    正文 map,必要解释就是map不是地图的意思,而是映射的意思. 这里就简单的介绍了这个map了. array.map(callback,[ thisObject]); 看下这个案例后,我们发现了就发 ...

  2. ImageJ软件使用教程(三):目标计数

    目录 多点工具法 阀值分割法 二值化 填充分割 自动计数 显示结果 总结 参考资料 本文以钢筋计数为例,讲解一下如何使用ImageJ软件进行计数,这里只介绍两种方法: 多点工具法 阀值分割法 钢筋计数 ...

  3. 第 4章 用 CSV 和 Excel 存储数据

    第4章 用 CSV 和 Excel 存储数据 4.1 用 CSV 文件存储数据 CSV(Comma-Separated Values)其实就是纯文本,用逗号分隔值,可以分隔成多个单元格.CSV 文件除 ...

  4. P10160 [DTCPC 2024] Ultra 题解

    [题目描述] 给你一个 \(01\) 序列,你可以进行如下操作若干次(或零次): 将序列中形如 \(101\cdots01\) 的一个子串(即 \(1(01)^k\),\(k\ge 1\))替换成等长 ...

  5. Spring Boot参数校验以及分组校验的使用

    简介: 做web开发基本上每个接口都要对参数进行校验,如果参数比较少,还比较容易处理,一但参数比较多了的话代码中就会出现大量的if-else语句.虽然这种方式简单直接,但会大大降低开发效率和代码可读性 ...

  6. [FAQ] pdf 无法导入 adobe AI, 分辨率 or 颜色缺失 or 字体缺失

    属于Adoge软件不支持问题, 可能是分辨率.字体等多种原因. https://www.codebye.com/adobe-reader-or-acrobat-opens-pdf-file-drawi ...

  7. [ELK] 生产环境中 Elasticsearch 的重要配置项

    配置 Elasticsearch https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html 重要的 E ...

  8. dotnet OpenXML 聊聊文本段落对齐方式

    本文来和大家聊聊在 OpenXML 里面,文本段落对齐方式.在 Word 和 PPT 的文本段落对齐规则是相同的,对齐的规则比较多,本文将一一告诉大家 文本的段落对齐,需要设置给段落属性上,在 Ope ...

  9. Windows 窗口样式 什么是 WS_EX_NOREDIRECTIONBITMAP 样式

    我觉得我可以加入历史博物馆了,加入微软历史博物馆,本文也是和大家吹历史的博客 简单说这个 WS_EX_NOREDIRECTIONBITMAP 样式是 Win8 提供的,用来做画面图层混合的功能.什么是 ...

  10. 习题8 #第8章 Verilog有限状态机设计-4 #Verilog #Quartus #modelsim

    4. 用状态机设计交通灯控制器,设计要求:A路和B路,每路都有红.黄.绿三种灯,持续时间为:红灯45s,黄灯5s,绿灯40秒. A路和B路灯的状态转换是: (1) A红,B绿(持续时间40s): (2 ...