音视频/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文件

    #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)的更多相关文章

  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. shell脚本中的运算符和条件判断

    shell脚本中的运算符和条件判断: 一.算术运算符 在Shell脚本中,你可以使用各种运算符来执行数学运算.比较和逻辑操作. 计算方式: $[ ] $(( )) 例: a=$[(9+5)90] 打印 ...

  2. EPLAN电气绘图笔记

    EPLAN的背景由来发展意义 使用软件的一些思维上规则的东西. 引入一些新的概念性名词术语及区分介绍. 如何完成项目式交付初级标准电气图纸. 如何高效简化. eplan安装后数据库问题. 6.安装后无 ...

  3. 深入分析C++对象模型之移动构造函数

    接下来我将持续更新"深度解读<深度探索C++对象模型>"系列,敬请期待,欢迎关注!也可以关注公众号:iShare爱分享,自动获得推文和全部的文章列表. C++11新标准 ...

  4. 《Effective C#》系列之(六)——提高多线程的性能

    一.综述 <Effective C#>中提高多线程性能的方法主要有以下几点: 避免锁竞争:锁的使用会导致线程阻塞,从而影响程序的性能.为了避免锁竞争,可以采用无锁编程技术,如CAS(Com ...

  5. 剑指 Offer II 018(Java). 有效的回文(简单)

    题目: 给定一个字符串 s ,验证 s 是否是 回文串 ,只考虑字母和数字字符,可以忽略字母的大小写. 本题中,将空字符串定义为有效的 回文串 . 示例 1: 输入: s = "A man, ...

  6. word文档怎么让封面没有页码,页码从正文开始(word 2019)

    1.打开需要插入页码的文档,光标放在正文处的开头,然后点击word窗口中的 [布局]  ---> 选择[分隔符] -->选择 [分节符] 下面的   [连续]; 2.然后选择word功能区 ...

  7. 大屏小程序探索实践 | Cube 技术解读

    简介: 支付宝客户端有极强的动态化诉求,不论 iOS 还是 Android 平台,重新分发软件包从时间上,效率上难以满足产品运营的要求,因此客户端动态化技术应运而生. Cube 起源于 Native ...

  8. Flink 和 Iceberg 如何解决数据入湖面临的挑战

    简介: 4.17 上海站 Meetup 胡争老师分享内容:数据入湖的挑战有哪些,以及如何用 Flink + Iceberg 解决此类问题. 一.数据入湖的核心挑战 数据实时入湖可以分成三个部分,分别是 ...

  9. Flink 在爱奇艺广告业务的实践

    简介: 5 月 22 日北京站 Flink Meetup 分享的议题. 本文整理自爱奇艺技术经理韩红根在 5 月 22 日北京站 Flink Meetup 分享的议题<Flink 在爱奇艺广告业 ...

  10. Metasploit 实现木马生成、捆绑及免杀

    ​简介: 在渗透测试的过程中,避免不了使用到社会工程学的方式来诱骗对方运行我们的木马或者点击我们准备好的恶意链接.木马的捆绑在社会工程学中是我们经常使用的手段,而为了躲避杀毒软件的查杀,我们又不得不对 ...