音视频/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文件的示例。

开发环境说明

  • 系统:Windows10、Ubuntu20.04
  • Qt版本:V5.12.5
  • 编译器:MSVC2017-64、GCC/G++64
  • FFmpeg版本:n5.1.2

2、实现效果

  1. 将.mp3文件解码转换为.pcm文件;(PCM数据时最原始的音频数据);
  2. 使用Qt重新实现,方便操作,便于使用;
  3. 解决官方示例中解码失败程序会终止问题 ;
  4. 关键步骤加上详细注释,比官方示例更便于学习。
  • 实现效果如下:

3、主要代码

  • 啥也不说了,直接上代码,一切有注释

  • widget.h文件

    1. #ifndef WIDGET_H
    2. #define WIDGET_H
    3. #include <QFile>
    4. #include <QWidget>
    5. QT_BEGIN_NAMESPACE
    6. namespace Ui { class Widget; }
    7. QT_END_NAMESPACE
    8. struct AVCodecParserContext;
    9. struct AVCodecContext;
    10. struct AVCodec;
    11. struct AVPacket;
    12. struct AVFrame;
    13. class Widget : public QWidget
    14. {
    15. Q_OBJECT
    16. public:
    17. Widget(QWidget *parent = nullptr);
    18. ~Widget();
    19. private slots:
    20. void on_but_in_clicked();
    21. void on_but_out_clicked();
    22. void on_but_start_clicked();
    23. private:
    24. int initDecode();
    25. int decode(QFile& fileOut);
    26. void showError(int err);
    27. void showLog(const QString& log);
    28. private:
    29. Ui::Widget *ui;
    30. AVCodecParserContext* m_parserContex = nullptr; // 裸流解析器
    31. AVCodecContext* m_context = nullptr; // 解码器上下文
    32. const AVCodec* m_codec = nullptr; // 音频解码器
    33. AVPacket* m_packet = nullptr; // 未解码的原始数据
    34. AVFrame* m_frame = nullptr; // 解码后的数据帧
    35. };
    36. #endif // WIDGET_H
  • widget.cpp文件

    1. #include "widget.h"
    2. #include "ui_widget.h"
    3. #include <qfiledialog.h>
    4. #include <QDebug>
    5. #include <qthread.h>
    6. #include <qtimer.h>
    7. extern "C" { // 用C规则编译指定的代码
    8. #include <libavutil/frame.h>
    9. #include <libavutil/mem.h>
    10. #include <libavcodec/avcodec.h>
    11. }
    12. #define AUDIO_INBUF_SIZE 20480
    13. #define AUDIO_REFILL_THRESH 4096
    14. Widget::Widget(QWidget *parent)
    15. : QWidget(parent)
    16. , ui(new Ui::Widget)
    17. {
    18. ui->setupUi(this);
    19. this->setWindowTitle(QString("使用libavcodec API的音频解码示例(mp3转pcm) V%1").arg(APP_VERSION));
    20. }
    21. Widget::~Widget()
    22. {
    23. delete ui;
    24. }
    25. /**
    26. * @brief 自定义非阻塞延时
    27. * @param ms
    28. */
    29. void msleep(int ms)
    30. {
    31. QEventLoop loop;
    32. QTimer::singleShot(ms, &loop, SLOT(quit()));
    33. loop.exec();
    34. }
    35. void Widget::showLog(const QString &log)
    36. {
    37. ui->textEdit->append(log);
    38. }
    39. /**
    40. * @brief 显示ffmpeg函数调用异常信息
    41. * @param err
    42. */
    43. void Widget::showError(int err)
    44. {
    45. static char m_error[1024];
    46. memset(m_error, 0, sizeof (m_error)); // 将数组置零
    47. av_strerror(err, m_error, sizeof (m_error));
    48. showLog(QString("Error:%1 %2").arg(err).arg(m_error));
    49. }
    50. /**
    51. * @brief 获取输入文件路径
    52. */
    53. void Widget::on_but_in_clicked()
    54. {
    55. QString strName = QFileDialog::getOpenFileName(this, "选择用于解码的.mp3音频文件~!", "/", "音频 (*.mp3);");
    56. if(strName.isEmpty())
    57. {
    58. return;
    59. }
    60. ui->line_fileIn->setText(strName);
    61. }
    62. /**
    63. * @brief 获取解码后的原始音频文件保存路径
    64. */
    65. void Widget::on_but_out_clicked()
    66. {
    67. QString strName = QFileDialog::getSaveFileName(this, "解码后数据保存到~!", "/", "原始音频 (*.pcm);");
    68. if(strName.isEmpty())
    69. {
    70. return;
    71. }
    72. ui->line_fileOut->setText(strName);
    73. }
    74. void Widget::on_but_start_clicked()
    75. {
    76. int ret = initDecode();
    77. if(ret < 0)
    78. {
    79. showError(ret);
    80. }
    81. avcodec_free_context(&m_context); // 释放编解码器上下文和与之相关的所有内容,并将NULL写入提供的指针。
    82. av_parser_close(m_parserContex);
    83. av_frame_free(&m_frame);
    84. av_packet_free(&m_packet);
    85. }
    86. QString get_format_from_sample_fmt(int fmt)
    87. {
    88. typedef struct sample_fmt_entry {
    89. enum AVSampleFormat sample_fmt;
    90. QString fmt_be; // 大端模式指令
    91. QString fmt_le; // 小端模式指令
    92. }sample_fmt_entry;
    93. sample_fmt_entry sample_fmt_entryes[] = {
    94. { AV_SAMPLE_FMT_U8, "u8", "u8" },
    95. { AV_SAMPLE_FMT_S16, "s16be", "s16le" },
    96. { AV_SAMPLE_FMT_S32, "s32be", "s32le" },
    97. { AV_SAMPLE_FMT_FLT, "f32be", "f32le" },
    98. { AV_SAMPLE_FMT_DBL, "f64be", "f64le" },
    99. };
    100. for(int i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entryes); i++)
    101. {
    102. sample_fmt_entry entry = sample_fmt_entryes[i];
    103. if(fmt == entry.sample_fmt)
    104. {
    105. return AV_NE(entry.fmt_be, entry.fmt_le); // AV_NE:判断大小端
    106. }
    107. }
    108. return QString();
    109. }
    110. /**
    111. * @brief 开始解码
    112. * @return
    113. */
    114. int Widget::initDecode()
    115. {
    116. QString strIn = ui->line_fileIn->text();
    117. QString strOut = ui->line_fileOut->text();
    118. if(strIn.isEmpty() || strOut.isEmpty())
    119. {
    120. return AVERROR(ENOENT); // 返回文件不存在的错误码
    121. }
    122. m_packet = av_packet_alloc(); // 创建一个AVPacket
    123. if(!m_packet)
    124. {
    125. return AVERROR(ENOMEM); // 返回无法分配内存的错误码
    126. }
    127. m_frame = av_frame_alloc(); // 创建一个AVFrame
    128. if(!m_frame)
    129. {
    130. return AVERROR(ENOMEM); // 返回无法分配内存的错误码
    131. }
    132. // 通过ID查询MPEG音频解码器
    133. m_codec = avcodec_find_decoder(AV_CODEC_ID_MP2);
    134. if(!m_codec)
    135. {
    136. return AVERROR(ENXIO); // 找不到解码器
    137. }
    138. m_parserContex = av_parser_init(m_codec->id);
    139. if(!m_parserContex)
    140. {
    141. return AVERROR(ENOMEM); // 解析器初始化失败
    142. }
    143. m_context = avcodec_alloc_context3(m_codec); // 分配AVCodecContext并将其字段设置为默认值
    144. if(!m_context)
    145. {
    146. return AVERROR(ENOMEM); // 解码器上下文创建失败
    147. }
    148. // 使用给定的AVCodec初始化AVCodecContext。
    149. int ret = avcodec_open2(m_context, m_codec, nullptr);
    150. if(ret < 0)
    151. {
    152. return ret;
    153. }
    154. // 打开输入文件
    155. QFile fileIn(strIn);
    156. if(!fileIn.open(QIODevice::ReadOnly))
    157. {
    158. return AVERROR(ENOENT);
    159. }
    160. // 打开输出文件
    161. QFile fileOut(strOut);
    162. if(!fileOut.open(QIODevice::WriteOnly))
    163. {
    164. return AVERROR(ENOENT);
    165. }
    166. showLog("开始解码!");
    167. msleep(1);
    168. QByteArray buf = fileIn.readAll(); // 读取所有数据
    169. char inbuf[AUDIO_INBUF_SIZE];
    170. while(buf.count() > 0)
    171. {
    172. int len = (buf.count() <= AUDIO_INBUF_SIZE) ? buf.count() : AUDIO_INBUF_SIZE;
    173. memcpy(inbuf, buf.data(), len);
    174. // 解析数据包
    175. ret = av_parser_parse2(m_parserContex, m_context, &m_packet->data, &m_packet->size,
    176. reinterpret_cast<const uchar*>(inbuf), // 这里不能直接使用buf.data(),否则会出现[mp2 @ 000001c8dbd40b00] Multiple frames in a packet.
    177. len,
    178. AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
    179. if(ret < 0)
    180. {
    181. break;
    182. }
    183. buf.remove(0, ret); // 移除已解析的数据
    184. if(m_packet->size)
    185. {
    186. ret = decode(fileOut);
    187. if(ret < 0)
    188. {
    189. // return ret;
    190. }
    191. }
    192. }
    193. m_packet->data = nullptr;
    194. m_packet->size = 0;
    195. decode(fileOut); // 需要传入空的数据帧才可以将解码器中所有数据读取出来
    196. enum AVSampleFormat sfmt = m_context->sample_fmt;
    197. // 检查样本格式是否为平面
    198. if(av_sample_fmt_is_planar(sfmt))
    199. {
    200. const char* name = av_get_sample_fmt_name(sfmt); // 获取音频样本格式名称
    201. showLog(QString("警告:解码器生成的样本格式是平面格式(%1)。此示例将仅输出第一个通道。").arg(name));
    202. sfmt = av_get_packed_sample_fmt(sfmt); // 获取样本格式的替代格式
    203. }
    204. // 音频通道数
    205. #if FF_API_OLD_CHANNEL_LAYOUT
    206. int channels = m_context->channels;
    207. #else
    208. int channels = m_context->ch_layout.nb_channels;
    209. #endif
    210. QString strFmt = get_format_from_sample_fmt(sfmt);
    211. if(!strFmt.isEmpty())
    212. {
    213. showLog(QString("使用下列命令播放输出音频文件!\n"
    214. "ffplay -f %1 -ac %2 -ar %3 %4\n")
    215. .arg(strFmt).arg(channels)
    216. .arg(m_context->sample_rate).arg(strOut));
    217. }
    218. return 0;
    219. }
    220. /**
    221. * @brief 解码并写入文件
    222. * @param fileOut
    223. * @return
    224. */
    225. int Widget::decode(QFile &fileOut)
    226. {
    227. // 将包含压缩数据的数据包发送到解码器
    228. int ret = avcodec_send_packet(m_context, m_packet); // 注意:官方Demo中这里如果返回值<0则终止程序,由于数据中有mp3文件头,所以一开始会有返回值<0的情况
    229. // 读取所有输出帧(通常可以有任意数量的输出帧
    230. while (ret >= 0)
    231. {
    232. // 读取解码后的数据帧
    233. int ret = avcodec_receive_frame(m_context, m_frame);
    234. if(ret == AVERROR(EAGAIN) // 资源暂时不可用
    235. || ret == AVERROR_EOF) // 文件末尾
    236. {
    237. return 0;
    238. }
    239. else if(ret < 0)
    240. {
    241. return ret;
    242. }
    243. // 返回每个样本的字节数。例如格式为AV_SAMPLE_FMT_U8,则字节数为1字节
    244. int size = av_get_bytes_per_sample(m_context->sample_fmt); // 返回值不会小于0
    245. for(int i = 0; i < m_frame->nb_samples; ++i) // 音频样本数(采样率)
    246. {
    247. #if FF_API_OLD_CHANNEL_LAYOUT
    248. for(int j = 0; j < m_context->channels; ++j) // 5.1.2以后版本会弃用channels
    249. #else
    250. for(int j = 0; j < m_context->ch_layout.nb_channels; ++j)
    251. #endif
    252. {
    253. fileOut.write((const char*)(m_frame->data[j] + size * i), size);
    254. }
    255. }
    256. }
    257. return 0;
    258. }

4、完整源代码

Qt-FFmpeg开发-音频解码为PCM文件(9)的更多相关文章

  1. 最简单的基于FFMPEG的音频编码器(PCM编码为AAC)

    http://blog.csdn.net/leixiaohua1020/article/details/25430449 本文介绍一个最简单的基于FFMPEG的音频编码器.该编码器实现了PCM音频采样 ...

  2. 基于FFmpeg的音频编码(PCM数据编码成AAC android)

    概述 在Android上实现录音,并利用 FFmpeg将PCM数据编码成AAC. 详细 代码下载:http://www.demodashi.com/demo/10512.html 之前做的一个demo ...

  3. FFmpeg 裁剪——音频解码

    配置ffmpeg,只留下某些音频的配置: ./configure --enable-shared --disable-yasm --enable-memalign-hack --enable-gpl ...

  4. FFMPEG视音频解码【一】

    多媒体的时代,得多了解点编解码的技术才行,而ffmpeg为我们提供了一系列多媒体编解码的接口,如何用好这些接口达到自己所需要的目的,这也是一门重要的学问. 要是了解得不够,总是会遇到一堆又一堆问题:网 ...

  5. 在64位的ubuntu 14.04 上开展32位Qt 程序开发环境配置(pro文件中增加 QMAKE_CXXFLAGS += -m32 命令)

    为了能中一个系统上开发64或32位C++程序,费了些周折,现在终于能够开始干过了.在此记录此时针对Q5.4版本的32位开发环境配置过程. 1. 下载Qt 5.4 的32位版本,进行安装,安装过程中会发 ...

  6. Qt + FFmpeg 本地音频播放器

    http://pan.baidu.com/s/1hqoYXrI

  7. [总结]FFMPEG视音频编解码零基础学习方法--转

    ffmpeg编解码学习   目录(?)[-] ffmpeg程序的使用ffmpegexeffplayexeffprobeexe 1 ffmpegexe 2 ffplayexe 3 ffprobeexe ...

  8. FFMPEG视音频编解码零基础学习方法

    在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...

  9. FFMPEG视音频编解码零基础学习方法-b

    感谢大神分享,虽然现在还看不懂,留着大家一起看啦 PS:有不少人不清楚“FFmpeg”应该怎么读.它读作“ef ef em peg” 0. 背景知识 本章主要介绍一下FFMPEG都用在了哪里(在这里仅 ...

  10. [总结]FFMPEG视音频编解码零基础学习方法

    在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...

随机推荐

  1. Matplotlib Installing an official release from resources 源码安装Matplotlib官方版本

    Installation Installing an official release Matplotlib releases are available as wheel packages for ...

  2. 如何使用XSSFWorkbook读取文本薄?

    [版权声明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://www.cnblogs.com/cnb-yuchen/p/18146625 出自[进步*于辰的博客] 1.文件兼容类 ...

  3. 阿里云 EventBridge 事件驱动架构实践

    ​简介:我们认为 EventBridge 是云原生时代新的计算驱动力,这些数据可以驱动云的计算能力,创造更多业务价值. 作者:周新宇 本文内容整理自 中国开源年会 演讲 首先做一个自我介绍,我是 Ro ...

  4. [FE] iframe 相关选项 x-frame-options: 设置 meta 标签无效 & helmet

    The X-Frame-Options HTTP 响应头是用来给浏览器 指示允许一个页面 可否在 <frame>, <iframe>, <embed> 或者 < ...

  5. [ML] 可视化编写运行 Python 脚本的工具 Jupyter

    Jupyter 提供了可视化的编写和运行 python 程序的 Web 界面. https://jupyter.org/install 使用只需要两步: $ pip install jupyterla ...

  6. dotnet 6 通过 DOTNET_ROOT 让调起的应用的进程拿到共享的运行时文件夹

    我的应用是独立发布的,在用户的设备上不需要额外去安装 .NET 运行时.但是我的应用有一个需求是下载另一个应用作为插件,由本应用调起插件进程.本文告诉大家如何解决调用插件的进程时,赋值给插件进程运行时 ...

  7. 「IT运维迷宫」那些让人头疼的常见问题与破局之道

    在数字化浪潮汹涌的今天,IT运维如同一座错综复杂的迷宫,稍有不慎便可能迷失方向.作为企业运营的幕后英雄,运维团队常常面临着各种突如其来的挑战.本文将带你深入探索IT运维中的那些常见"坑&qu ...

  8. Django Admin后台管理:高效开发与实践

    title: Django Admin后台管理:高效开发与实践 date: 2024/5/8 14:24:15 updated: 2024/5/8 14:24:15 categories: 后端开发 ...

  9. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明

    1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明 @ 目录 1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明 每博一文案 2. 环境准备 3 ...

  10. 2022最新的Dubbo-Admin各个版本打包方案

    目录 前景提要 环境整合 构建工具(参考工具部署方式) 官网查阅 打包 一.编译器打包 二.命令行打包 前景提要 很简单的一个操作很多人还在那整各种收费,明明是个免费开源的,干嘛让他们挣二手钱. 环境 ...