Qt-FFmpeg开发-音频解码为PCM文件(9)
音视频/FFmpeg #Qt
Qt-FFmpeg开发-使用libavcodec API的音频解码示例(MP3转pcm)
更多精彩内容 |
---|
个人内容分类汇总 |
音视频开发 |
1、概述
- 最近研究了一下FFmpeg开发,功能实在是太强大了,网上ffmpeg3、4的文章还是很多的,但是学习嘛,最新的还是不能放过,就选了一个最新的ffmpeg n5.1.2版本,和3、4版本api变化还是挺大的;
- 这是一个libavcodec API示例;
- 这里主要是研究FFmpeg官方示例产生的一个程序,官方示例可以看Examples;
- 由于官方示例有一些小问题,编译没通过,并且是通过命令行执行,不方便,这里通过修改为使用Qt实现这个音频解码为PCM文件的示例。
开发环境说明
2、实现效果
- 将.mp3文件解码转换为.pcm文件;(PCM数据时最原始的音频数据);
- 使用Qt重新实现,方便操作,便于使用;
- 解决官方示例中解码失败程序会终止问题 ;
- 关键步骤加上详细注释,比官方示例更便于学习。
实现效果如下:
3、主要代码
啥也不说了,直接上代码,一切有注释
widget.h文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QFile>
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
struct AVCodecParserContext;
struct AVCodecContext;
struct AVCodec;
struct AVPacket;
struct AVFrame;
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_but_in_clicked();
void on_but_out_clicked();
void on_but_start_clicked();
private:
int initDecode();
int decode(QFile& fileOut);
void showError(int err);
void showLog(const QString& log);
private:
Ui::Widget *ui;
AVCodecParserContext* m_parserContex = nullptr; // 裸流解析器
AVCodecContext* m_context = nullptr; // 解码器上下文
const AVCodec* m_codec = nullptr; // 音频解码器
AVPacket* m_packet = nullptr; // 未解码的原始数据
AVFrame* m_frame = nullptr; // 解码后的数据帧
};
#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 <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
}
#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle(QString("使用libavcodec API的音频解码示例(mp3转pcm) V%1").arg(APP_VERSION));
}
Widget::~Widget()
{
delete ui;
}
/**
* @brief 自定义非阻塞延时
* @param ms
*/
void msleep(int ms)
{
QEventLoop loop;
QTimer::singleShot(ms, &loop, SLOT(quit()));
loop.exec();
}
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));
}
/**
* @brief 获取输入文件路径
*/
void Widget::on_but_in_clicked()
{
QString strName = QFileDialog::getOpenFileName(this, "选择用于解码的.mp3音频文件~!", "/", "音频 (*.mp3);");
if(strName.isEmpty())
{
return;
}
ui->line_fileIn->setText(strName);
}
/**
* @brief 获取解码后的原始音频文件保存路径
*/
void Widget::on_but_out_clicked()
{
QString strName = QFileDialog::getSaveFileName(this, "解码后数据保存到~!", "/", "原始音频 (*.pcm);");
if(strName.isEmpty())
{
return;
}
ui->line_fileOut->setText(strName);
}
void Widget::on_but_start_clicked()
{
int ret = initDecode();
if(ret < 0)
{
showError(ret);
}
avcodec_free_context(&m_context); // 释放编解码器上下文和与之相关的所有内容,并将NULL写入提供的指针。
av_parser_close(m_parserContex);
av_frame_free(&m_frame);
av_packet_free(&m_packet);
}
QString get_format_from_sample_fmt(int fmt)
{
typedef struct sample_fmt_entry {
enum AVSampleFormat sample_fmt;
QString fmt_be; // 大端模式指令
QString fmt_le; // 小端模式指令
}sample_fmt_entry;
sample_fmt_entry sample_fmt_entryes[] = {
{ AV_SAMPLE_FMT_U8, "u8", "u8" },
{ AV_SAMPLE_FMT_S16, "s16be", "s16le" },
{ AV_SAMPLE_FMT_S32, "s32be", "s32le" },
{ AV_SAMPLE_FMT_FLT, "f32be", "f32le" },
{ AV_SAMPLE_FMT_DBL, "f64be", "f64le" },
};
for(int i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entryes); i++)
{
sample_fmt_entry entry = sample_fmt_entryes[i];
if(fmt == entry.sample_fmt)
{
return AV_NE(entry.fmt_be, entry.fmt_le); // AV_NE:判断大小端
}
}
return QString();
}
/**
* @brief 开始解码
* @return
*/
int Widget::initDecode()
{
QString strIn = ui->line_fileIn->text();
QString strOut = ui->line_fileOut->text();
if(strIn.isEmpty() || strOut.isEmpty())
{
return AVERROR(ENOENT); // 返回文件不存在的错误码
}
m_packet = av_packet_alloc(); // 创建一个AVPacket
if(!m_packet)
{
return AVERROR(ENOMEM); // 返回无法分配内存的错误码
}
m_frame = av_frame_alloc(); // 创建一个AVFrame
if(!m_frame)
{
return AVERROR(ENOMEM); // 返回无法分配内存的错误码
}
// 通过ID查询MPEG音频解码器
m_codec = avcodec_find_decoder(AV_CODEC_ID_MP2);
if(!m_codec)
{
return AVERROR(ENXIO); // 找不到解码器
}
m_parserContex = av_parser_init(m_codec->id);
if(!m_parserContex)
{
return AVERROR(ENOMEM); // 解析器初始化失败
}
m_context = avcodec_alloc_context3(m_codec); // 分配AVCodecContext并将其字段设置为默认值
if(!m_context)
{
return AVERROR(ENOMEM); // 解码器上下文创建失败
}
// 使用给定的AVCodec初始化AVCodecContext。
int ret = avcodec_open2(m_context, m_codec, nullptr);
if(ret < 0)
{
return ret;
}
// 打开输入文件
QFile fileIn(strIn);
if(!fileIn.open(QIODevice::ReadOnly))
{
return AVERROR(ENOENT);
}
// 打开输出文件
QFile fileOut(strOut);
if(!fileOut.open(QIODevice::WriteOnly))
{
return AVERROR(ENOENT);
}
showLog("开始解码!");
msleep(1);
QByteArray buf = fileIn.readAll(); // 读取所有数据
char inbuf[AUDIO_INBUF_SIZE];
while(buf.count() > 0)
{
int len = (buf.count() <= AUDIO_INBUF_SIZE) ? buf.count() : AUDIO_INBUF_SIZE;
memcpy(inbuf, buf.data(), len);
// 解析数据包
ret = av_parser_parse2(m_parserContex, m_context, &m_packet->data, &m_packet->size,
reinterpret_cast<const uchar*>(inbuf), // 这里不能直接使用buf.data(),否则会出现[mp2 @ 000001c8dbd40b00] Multiple frames in a packet.
len,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if(ret < 0)
{
break;
}
buf.remove(0, ret); // 移除已解析的数据
if(m_packet->size)
{
ret = decode(fileOut);
if(ret < 0)
{
// return ret;
}
}
}
m_packet->data = nullptr;
m_packet->size = 0;
decode(fileOut); // 需要传入空的数据帧才可以将解码器中所有数据读取出来
enum AVSampleFormat sfmt = m_context->sample_fmt;
// 检查样本格式是否为平面
if(av_sample_fmt_is_planar(sfmt))
{
const char* name = av_get_sample_fmt_name(sfmt); // 获取音频样本格式名称
showLog(QString("警告:解码器生成的样本格式是平面格式(%1)。此示例将仅输出第一个通道。").arg(name));
sfmt = av_get_packed_sample_fmt(sfmt); // 获取样本格式的替代格式
}
// 音频通道数
#if FF_API_OLD_CHANNEL_LAYOUT
int channels = m_context->channels;
#else
int channels = m_context->ch_layout.nb_channels;
#endif
QString strFmt = get_format_from_sample_fmt(sfmt);
if(!strFmt.isEmpty())
{
showLog(QString("使用下列命令播放输出音频文件!\n"
"ffplay -f %1 -ac %2 -ar %3 %4\n")
.arg(strFmt).arg(channels)
.arg(m_context->sample_rate).arg(strOut));
}
return 0;
}
/**
* @brief 解码并写入文件
* @param fileOut
* @return
*/
int Widget::decode(QFile &fileOut)
{
// 将包含压缩数据的数据包发送到解码器
int ret = avcodec_send_packet(m_context, m_packet); // 注意:官方Demo中这里如果返回值<0则终止程序,由于数据中有mp3文件头,所以一开始会有返回值<0的情况
// 读取所有输出帧(通常可以有任意数量的输出帧
while (ret >= 0)
{
// 读取解码后的数据帧
int ret = avcodec_receive_frame(m_context, m_frame);
if(ret == AVERROR(EAGAIN) // 资源暂时不可用
|| ret == AVERROR_EOF) // 文件末尾
{
return 0;
}
else if(ret < 0)
{
return ret;
}
// 返回每个样本的字节数。例如格式为AV_SAMPLE_FMT_U8,则字节数为1字节
int size = av_get_bytes_per_sample(m_context->sample_fmt); // 返回值不会小于0
for(int i = 0; i < m_frame->nb_samples; ++i) // 音频样本数(采样率)
{
#if FF_API_OLD_CHANNEL_LAYOUT
for(int j = 0; j < m_context->channels; ++j) // 5.1.2以后版本会弃用channels
#else
for(int j = 0; j < m_context->ch_layout.nb_channels; ++j)
#endif
{
fileOut.write((const char*)(m_frame->data[j] + size * i), size);
}
}
}
return 0;
}
4、完整源代码
Qt-FFmpeg开发-音频解码为PCM文件(9)的更多相关文章
- 最简单的基于FFMPEG的音频编码器(PCM编码为AAC)
http://blog.csdn.net/leixiaohua1020/article/details/25430449 本文介绍一个最简单的基于FFMPEG的音频编码器.该编码器实现了PCM音频采样 ...
- 基于FFmpeg的音频编码(PCM数据编码成AAC android)
概述 在Android上实现录音,并利用 FFmpeg将PCM数据编码成AAC. 详细 代码下载:http://www.demodashi.com/demo/10512.html 之前做的一个demo ...
- FFmpeg 裁剪——音频解码
配置ffmpeg,只留下某些音频的配置: ./configure --enable-shared --disable-yasm --enable-memalign-hack --enable-gpl ...
- FFMPEG视音频解码【一】
多媒体的时代,得多了解点编解码的技术才行,而ffmpeg为我们提供了一系列多媒体编解码的接口,如何用好这些接口达到自己所需要的目的,这也是一门重要的学问. 要是了解得不够,总是会遇到一堆又一堆问题:网 ...
- 在64位的ubuntu 14.04 上开展32位Qt 程序开发环境配置(pro文件中增加 QMAKE_CXXFLAGS += -m32 命令)
为了能中一个系统上开发64或32位C++程序,费了些周折,现在终于能够开始干过了.在此记录此时针对Q5.4版本的32位开发环境配置过程. 1. 下载Qt 5.4 的32位版本,进行安装,安装过程中会发 ...
- Qt + FFmpeg 本地音频播放器
http://pan.baidu.com/s/1hqoYXrI
- [总结]FFMPEG视音频编解码零基础学习方法--转
ffmpeg编解码学习 目录(?)[-] ffmpeg程序的使用ffmpegexeffplayexeffprobeexe 1 ffmpegexe 2 ffplayexe 3 ffprobeexe ...
- FFMPEG视音频编解码零基础学习方法
在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...
- FFMPEG视音频编解码零基础学习方法-b
感谢大神分享,虽然现在还看不懂,留着大家一起看啦 PS:有不少人不清楚“FFmpeg”应该怎么读.它读作“ef ef em peg” 0. 背景知识 本章主要介绍一下FFMPEG都用在了哪里(在这里仅 ...
- [总结]FFMPEG视音频编解码零基础学习方法
在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...
随机推荐
- Matplotlib Installing an official release from resources 源码安装Matplotlib官方版本
Installation Installing an official release Matplotlib releases are available as wheel packages for ...
- 如何使用XSSFWorkbook读取文本薄?
[版权声明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://www.cnblogs.com/cnb-yuchen/p/18146625 出自[进步*于辰的博客] 1.文件兼容类 ...
- 阿里云 EventBridge 事件驱动架构实践
简介:我们认为 EventBridge 是云原生时代新的计算驱动力,这些数据可以驱动云的计算能力,创造更多业务价值. 作者:周新宇 本文内容整理自 中国开源年会 演讲 首先做一个自我介绍,我是 Ro ...
- [FE] iframe 相关选项 x-frame-options: 设置 meta 标签无效 & helmet
The X-Frame-Options HTTP 响应头是用来给浏览器 指示允许一个页面 可否在 <frame>, <iframe>, <embed> 或者 < ...
- [ML] 可视化编写运行 Python 脚本的工具 Jupyter
Jupyter 提供了可视化的编写和运行 python 程序的 Web 界面. https://jupyter.org/install 使用只需要两步: $ pip install jupyterla ...
- dotnet 6 通过 DOTNET_ROOT 让调起的应用的进程拿到共享的运行时文件夹
我的应用是独立发布的,在用户的设备上不需要额外去安装 .NET 运行时.但是我的应用有一个需求是下载另一个应用作为插件,由本应用调起插件进程.本文告诉大家如何解决调用插件的进程时,赋值给插件进程运行时 ...
- 「IT运维迷宫」那些让人头疼的常见问题与破局之道
在数字化浪潮汹涌的今天,IT运维如同一座错综复杂的迷宫,稍有不慎便可能迷失方向.作为企业运营的幕后英雄,运维团队常常面临着各种突如其来的挑战.本文将带你深入探索IT运维中的那些常见"坑&qu ...
- Django Admin后台管理:高效开发与实践
title: Django Admin后台管理:高效开发与实践 date: 2024/5/8 14:24:15 updated: 2024/5/8 14:24:15 categories: 后端开发 ...
- Spring6 的JdbcTemplate的JDBC模板类的详细使用说明
1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明 @ 目录 1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明 每博一文案 2. 环境准备 3 ...
- 2022最新的Dubbo-Admin各个版本打包方案
目录 前景提要 环境整合 构建工具(参考工具部署方式) 官网查阅 打包 一.编译器打包 二.命令行打包 前景提要 很简单的一个操作很多人还在那整各种收费,明明是个免费开源的,干嘛让他们挣二手钱. 环境 ...