音视频/FFmpeg #QtQt-FFmpeg开发-使libavformat解复用器通过自定义AVIOContext读取回调访问媒体内容目录

  • 音视频/FFmpeg #Qt
  • Qt-FFmpeg开发-使libavformat解复用器通过自定义AVIOContext读取回调访问媒体内容
    • 1、概述
    • 2、实现效果
    • 3、主要代码
    • 4、完整源代码
更多精彩内容
?个人内容分类汇总 ?
?音视频开发 ?

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 QT_BEGIN_NAMESPACEnamespace Ui { class Widget; }QT_END_NAMESPACEstruct AVFormatContext;struct AVIOContext;class Widget : public QWidget{    Q_OBJECTpublic:    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 #include #include #include 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 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(opaque);    // bd指针指向了读取文件的所有数据    buf_size = FFMIN(buf_size, int(bd->size));             // 获取最小值    if (!buf_size)    {        return AVERROR_EOF;       // 文件结尾    }    qDebug() <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(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 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、完整源代码

  • github
  • gitee