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


    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 
      
      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
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
    • 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 < 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));
      }
      
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109
      • 110
      • 111
      • 112
      • 113
      • 114
      • 115
      • 116
      • 117
      • 118
      • 119
      • 120
      • 121
      • 122
      • 123
      • 124
      • 125
      • 126
      • 127
      • 128
      • 129
      • 130
      • 131
      • 132
      • 133
      • 134
      • 135
      • 136
      • 137
      • 138
      • 139
      • 140
      • 141
      • 142
      • 143
      • 144
      • 145
      • 146
      • 147
      • 148
      • 149
      • 150
      • 151
      • 152
      • 153
      • 154
      • 155
      • 156
      • 157
      • 158
      • 159
      • 160
      • 161
      • 162
      • 163
      • 164
      • 165
      • 166
      • 167
      • 168
      • 169
      • 170
      • 171
      • 172
      • 173
      • 174
      • 175
      • 176
      • 177
      • 178
      • 179
      • 180
      • 181
      • 182
      • 183
      • 184
      • 185
      • 186
      • 187
      • 188
      • 189
      • 190
      • 191
      • 192
      • 193
      • 194
      • 195

    4、完整源代码

  • 相关阅读:
    黑苹果之技嘉(GIGABYTE)主板BIOS设置篇
    【计算机网络】网络编程接口 Socket API 解读(11)
    腾讯云轻量服务器购买优惠,腾讯云轻量应用服务器优惠购买方法
    Windows系统解压zip文件之后乱码的问题的原因和解决方法
    不重复数字
    python爬虫request和BeautifulSoup使用
    03【保姆级】-GO语言变量和数据类型和相互转换
    开发工程师必备————【Day21】前端开发之jQuery
    Go:微服务架构下的单元测试(基于 Ginkgo、gomock 、Gomega)
    什么是数据库的读写分离?什么是主表从表,主库从库?
  • 原文地址:https://blog.csdn.net/qq_43627907/article/details/128201550