音視頻/FFmpeg #Qt
Qt-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、實作效果
- 將一個視頻檔案中所有資料讀取到buf中;
- 為AVIOContext創建一個回呼函式;
- 創建一個長度為4096記憶體用于從buf中讀取資料;
- 使用回呼函式完成資料的讀取;
- 關鍵步驟加上詳細注釋,比官方示例更便于學習,
-
這個程式的原理如下:把視頻檔案中所有資料讀取到記憶體中,再通過回呼函式按照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、完整源代碼
- github
- gitee
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/549529.html
標籤:其他
