Qt-FFmpeg开发-回调函数读取数据(8)
音视频/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(官方示例编译后是通过命令行执行)。
开发环境说明
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_Hwidget.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)的更多相关文章
- java—将查询的结果封装成List<Map>与用回调函数实现数据的动态封装(44)
手工的开始QueryRunner类.实现数据封装: MapListHandler MapHandler BeanListHandler BeanHandler 第一步:基本的封装测试 写一个类,Que ...
- ffmpeg 从内存中读取数据(或将数据输出到内存)
更新记录(2014.7.24): 1.为了使本文更通俗易懂,更新了部分内容,将例子改为从内存中打开. 2.增加了将数据输出到内存的方法. 从内存中读取数据 ffmpeg一般情况下支持打开一个本地文件, ...
- ffmpeg 从内存中读取数据(或将数据输出到内存)(转)
更新记录(2014.7.24): 1.为了使本文更通俗易懂,更新了部分内容,将例子改为从内存中打开. 2.增加了将数据输出到内存的方法. 从内存中读取数据 ffmpeg一般情况下支持打开一个本地文件, ...
- ffmpeg 从内存中读取数据 .
http://blog.csdn.net/leixiaohua1020/article/details/12980423 ——————————————————————————————————————— ...
- Winpcap笔记4之不用回调函数捕获数据包
函数1: pcap_next_ex(pcap_t* p, struct pcap_pkthdr** pkt_header, const u_char* ...
- Linux应用开发之【多线程开发-回调函数】
原来我一直都不懂回调函数是什么 ... Callback Function 定义:通过函数指针调用的函数 在理解这个回调函数之前我们需要先了解回调机制 回调机制在编程中体现在:特定的情况发生后,返回并 ...
- 复制文件时,如何显示进度条(使用BlockRead函数读取数据,并插入application.ProcessMessages)
procedure mycopyfile(sourcef,targetf:string;i:integer); var FromF,ToF:file; NumRead,NumWritten:Integ ...
- FFMPEG内存操作(一) avio_reading.c 回调读取数据到内存解析
相关博客列表 : FFMPEG内存操作(一) avio_reading.c 回调读取数据到内存解析 FFMPEG内存操作(二)从内存中读取数及数据格式的转换 FFmpeg内存操作(三)内存转码器 在F ...
- 使用进程池模拟多进程爬取url获取数据,使用进程绑定的回调函数去处理数据
1 # 使用requests请求网页,爬取网页的内容 2 3 # 模拟使用进程池模拟多进程爬取网页获取数据,使用进程绑定的回调函数去处理数据 4 5 import requests 6 from mu ...
- ajax执行成功后,在success回调函数中把后台返回的list还原到html的table中
需求描述:前台通过onclick触发ajax,到后台返回一个list(json格式的),把list插入到html的table中. 思路简介: ̄□ ̄|| 刚开始的时候,是没有思路的,就卡在了,怎么把 a ...
随机推荐
- SpringCloud整体架构概览
什么是SpringCloud #目标 协调任何服务,简化分布式系统开发. #简介 构建分布式系统不应该是复杂的,SpringCloud对常见的分布式系统模式提供了简单易用的编程模型,帮助开发者构建弹性 ...
- Java:得到指定年份、月份、周次的最后一天
使用Java的工具类Calendar 通过Calendar可以进行很多的日期操作 /** * 得到指定年份的最后一天 * @param year */ public static void getLa ...
- CentOS+Django+uWSGI+Celery+Supervisor配置
目录 背景 目录 安装 配置Supervisor 1.生成配置文件 2. 修改配置文件 3. 创建进程文件 创建 uwsgi.conf 进程文件 创建celery进程文件 启动supervisor 启 ...
- idea引用lombok一直不成功
idea引用lombok一直不成功 比如想使用lombok的@data注解一直报错,尽管已经声明了import lombok.Data,且在pom里面也增加了lombok依赖,但是就是不成功的时候 建 ...
- 力扣571(MySQL)-给定数字的频率查询中位数(困难)
题目: Numbers 表保存数字的值及其频率. 在此表中,数字为 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 3,所以中位数是 (0 + 0) / 2 = 0. 请编写一个查询 ...
- 深入理解高级加密标准(Advanced Encryption Standard)
title: 深入理解高级加密标准(Advanced Encryption Standard) date: 2024/4/23 20:04:36 updated: 2024/4/23 20:04:36 ...
- 从MVC到云原生:CBU研发体系演进之路
简介: 本文对过去十年 CBU 在研发方式和技术架构上的探索做一个简要的回顾总结,以及对未来的展望. 前言 CBU作为集团内最早成立的几个BU之一,有着多年丰富的业务沉淀,而CBU的技术也伴随着业务一 ...
- WPF 调试依赖属性变更方法
本文告诉大家如何调试 WPF 的某个依赖属性被变更的方法 在 WPF 里面,所有的依赖属性都有带通知的功能,通过带通知的功能,可以在通知里加上断点,通过调用堆栈了解是哪个模块调用的 对依赖属性添加通知 ...
- 开发日志:Kylin麒麟操作系统部署ASP.NET CORE
需求场景: 我需要部署的项目是在Windows上开发的,目标框架为.net core 6.0 因此我们需要先在kylin上部署项目运行所需要的环境. 借助百度词条,先看看Kylin是什么: 服务器资源 ...
- C#的基于.net framework的Dll模块编程(四) - 编程手把手系列文章
这次继续这个系列的介绍: 一.命名空间的起名: 对于C#来说,一般命名空间的建议是:公司名(或个人名称).产品名.分类名,比如我这边是用的这个:Lzhdim.LPF.Helper,意思是个人名Lzhd ...