热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

使用FFMpeg解码音频文件

文章目录1.解封装2.解码3.对解码后的数据重采样本篇文章将介绍使用FFMpeg解码音频文件为PCM的数据。使用FFMpeg获取想要的音频数据的步骤如下:解封装(MP3文件)-解

文章目录

  • 1. 解封装
  • 2. 解码
  • 3. 对解码后的数据重采样

本篇文章将介绍使用FFMpeg解码音频文件为PCM的数据。
使用FFMpeg获取想要的音频数据的步骤如下:
解封装(MP3文件)->解码(MP3编码)->PCM数据重采样

1. 解封装

使用FFMpeg解封装的步骤如下:

  1. 使用函数 av_register_all() 注册所有的封装器和解封装器。
  2. 使用函数 avformat_open_input() 打开一个文件,可以为文件名也可以为一个URL。
  3. 使用函数 avformat_find_stream_info() 查找流信息,把它存入AVFormatContext中。
  4. 查找流信息,获取音频流的索引位置,获取解码器的codec_id
  5. 根据codec_id,使用函数 avcodec_find_decoder() 获取解码器AVCodec*
  6. 使用函数 avcodec_open2() 打开解码器。

下面是关键部分代码:

bool MusicDecodecThread::openAudioFile(QString fileName)
{
av_register_all();
m_AvFrame = av_frame_alloc();

// 打开文件
int result = avformat_open_input(&m_AVFormatContext, fileName.toLocal8Bit().data(), nullptr, nullptr);
if (result != 0 || m_AVFormatContext == nullptr)
return false;
// 查找流信息,把它存入AVFormatContext中
if (avformat_find_stream_info(m_AVFormatContext, nullptr) < 0)
return false;
int streamsCount = m_AVFormatContext->nb_streams;
// 读取详细信息
AVDictionaryEntry *tag = nullptr;
while (tag = av_dict_get(m_AVFormatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))
{
QString keyString = tag->key;
QString valueString = QString::fromUtf8(tag->value);
m_InfoMap.insert(keyString, valueString);
}
// 查找音频流索引
for (int i=0; i<streamsCount; ++i)
{
if (m_AVFormatContext->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC)
{
AVPacket pkt = m_AVFormatContext->streams[i]->attached_pic;
m_InfoImage = QImage::fromData((uchar*)pkt.data, pkt.size);
}
if (m_AVFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
m_AudioIndex = i;
continue;
}
}
if (m_AudioIndex == -1)
return false;
// 获取总时间
m_TotalTime = m_AVFormatContext->duration / AV_TIME_BASE * 1000;
// 查找解码器
m_AudioCodec = m_AVFormatContext->streams[m_AudioIndex]->codec;
AVCodec *codec = avcodec_find_decoder(m_AudioCodec->codec_id);
if (codec == nullptr)
return false;
// 打开音频解码器
if (avcodec_open2(m_AudioCodec, codec, nullptr) != 0)
return false;
int rate = m_AudioCodec->sample_rate;
int channel = m_AudioCodec->channels;
m_AudioCodec->channel_layout = av_get_default_channel_layout(m_AudioCodec->channels);
return true;
}

其中:
AVFormatContext *m_AVFormatCOntext= nullptr;
AVCodecContext *m_AudioCodec = nullptr;

AVFormatContext中存储文件封装的信息,比如我们可以使用这个方法获取音频的总时间长度:
m_TotalTime = m_AVFormatContext->duration / AV_TIME_BASE * 1000;

AVCodecContext 中存储了与解码器相关的信息,如
sample_rate: 表示采样率
channels: 表示通道数
sample_fmt: 表示采样的格式。(如AV_SAMPLE_FMT_S16、AV_SAMPLE_FMT_S32等)

2. 解码
  1. 使用函数 av_read_frame() 获取一包数据。
  2. 使用函数 avcodec_send_packet() 发送一包数据。
  3. 使用函数 avcodec_receive_frame() 获取一帧数据。

下面是关键部分代码:

while (!this->isInterruptionRequested())
{
QMutexLocker locker(&m_Mutex);
AVPacket pkt;
int result = av_read_frame(m_AVFormatContext, &pkt);
if (result != 0)
{
QThread::msleep(10);
continue;
}
if (pkt.stream_index != m_AudioIndex)
continue;
// 解码音频帧, 发送音频包
if (avcodec_send_packet(m_AudioCodec, &pkt))
continue;
// 解码音频帧,接收音频解码帧
if (avcodec_receive_frame(m_AudioCodec, m_AvFrame))
continue;
// 释放包的内存
av_packet_unref(&pkt);
}

m_AvFrame->data 就存储着解码后的数据。

3. 对解码后的数据重采样

这里使用的FFMpeg提供工具(SwrContext)对音频做重采样。使用方法如下:

  1. 使用方法 swr_alloc(); 创建一个 SwrContext* 类型的指针,并分配内存。
  2. 使用方法 swr_alloc_set_opts() 设置输入和输出的参数。
  3. 使用方法 swr_init() 初始化这个 SwrContext* 指针变量。
  4. 使用方法 swr_convert() 转换。
  5. 使用方法 swr_free() 释放内存。

下面是主要部分代码:

SwrContext *m_SWRtx = swr_alloc();
swr_alloc_set_opts(m_SWRtx, m_AudioCodec->channel_layout, AV_SAMPLE_FMT_S16, \
m_AudioCodec->sample_rate, m_AudioCodec->channels, m_AudioCodec->sample_fmt, \
m_AudioCodec->sample_rate, 0, 0);
swr_init(m_SWRtx);
uint8_t *array[1];
uint8_t arrays[10000] = { 0};
array[0] = arrays;
int len = swr_convert(m_SWRtx, array, 10000, (const uint8_t **)m_AvFrame->data, \
m_AvFrame->nb_samples);
swr_free(&m_SWRtx);

我这里使用线程解码,完整代码如下:
MusicDecodecThread.h

#ifndef MUSCI_DECODEC_THREAD_H
#define MUSCI_DECODEC_THREAD_H
#include
#include
#include
#include
#include
#include
#include "AudioPlayThread.h"
extern "C"{
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
}
class MusicDecodecThread : public QThread
{
Q_OBJECT
public:
MusicDecodecThread(QObject *parent = nullptr);
~MusicDecodecThread();
// 打开文件
bool openAudioFile(QString fileName);
void run(void) override;
// 获取信息列表中的内容
QMap<QString, QString> getInfoMap(void);
// 获取音乐的头像
QImage getMusicIcon(void);
// 获取音乐的总时长
int getTotalTime(void);
private:
AVFormatContext *m_AVFormatContext = nullptr;
AVCodecContext *m_AudioCodec = nullptr;
AVFrame *m_AvFrame;
int m_AudioIndex = -1;
int m_TotalTime = 0;
QMap<QString, QString> m_InfoMap;
QImage m_InfoImage;
QMutex m_Mutex;
};
#endif

MusicDecodecThread.cpp

#include "MusicDecodecThread.h"
#include
#include
MusicDecodecThread::MusicDecodecThread(QObject *parent)
:QThread(parent)
{
av_register_all();
avfilter_register_all();
m_AvFrame = av_frame_alloc();
g_AudioPlayThread->start();
}
MusicDecodecThread::~MusicDecodecThread()
{
}
bool MusicDecodecThread::openAudioFile(QString fileName)
{
QMutexLocker locker(&m_Mutex);
if (m_AVFormatContext)
avformat_close_input(&m_AVFormatContext);
// 打开文件
int result = avformat_open_input(&m_AVFormatContext, fileName.toLocal8Bit().data(), nullptr, nullptr);
if (result != 0 || m_AVFormatContext == nullptr)
return false;
// 查找流信息,把它存入AVFormatContext中
if (avformat_find_stream_info(m_AVFormatContext, nullptr) < 0)
return false;
int streamsCount = m_AVFormatContext->nb_streams;
// 读取详细信息
AVDictionaryEntry *tag = nullptr;
while (tag = av_dict_get(m_AVFormatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))
{
QString keyString = tag->key;
QString valueString = QString::fromUtf8(tag->value);
m_InfoMap.insert(keyString, valueString);
}
// 查找音频流索引
for (int i=0; i<streamsCount; ++i)
{
if (m_AVFormatContext->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC)
{
AVPacket pkt = m_AVFormatContext->streams[i]->attached_pic;
m_InfoImage = QImage::fromData((uchar*)pkt.data, pkt.size);
}
if (m_AVFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
m_AudioIndex = i;
continue;
}
}
if (m_AudioIndex == -1)
return false;
// 获取总时间
m_TotalTime = m_AVFormatContext->duration / AV_TIME_BASE * 1000;
// 查找解码器
m_AudioCodec = m_AVFormatContext->streams[m_AudioIndex]->codec;
AVCodec *codec = avcodec_find_decoder(m_AudioCodec->codec_id);
if (codec == nullptr)
return false;
// 打开音频解码器
if (avcodec_open2(m_AudioCodec, codec, nullptr) != 0)
return false;
int rate = m_AudioCodec->sample_rate;
int channel = m_AudioCodec->channels;
m_AudioCodec->channel_layout = av_get_default_channel_layout(m_AudioCodec->channels);
g_AudioPlayThread->cleanAllAudioBuffer();
g_AudioPlayThread->setCurrentSampleInfo(rate, 16, channel);
return true;
}
void MusicDecodecThread::run(void)
{
QTime time;
int count = 0;
while (!this->isInterruptionRequested())
{
QMutexLocker locker(&m_Mutex);
AVPacket pkt;
int result = av_read_frame(m_AVFormatContext, &pkt);
if (result != 0)
{
QThread::msleep(10);
continue;
}
if (pkt.stream_index != m_AudioIndex)
continue;
// 解码视频帧, 发送视频包
if (avcodec_send_packet(m_AudioCodec, &pkt))
continue;
// 解码视频帧,接收视频解码帧
if (avcodec_receive_frame(m_AudioCodec, m_AvFrame))
continue;
SwrContext *m_SWRtx = swr_alloc();
swr_alloc_set_opts(m_SWRtx, m_AudioCodec->channel_layout, AV_SAMPLE_FMT_S16, \
m_AudioCodec->sample_rate, m_AudioCodec->channels, m_AudioCodec->sample_fmt, \
m_AudioCodec->sample_rate, 0, 0);
swr_init(m_SWRtx);
uint8_t *array[1];
uint8_t arrays[10000] = { 0};
array[0] = arrays;
int len = swr_convert(m_SWRtx, array, 10000, (const uint8_t **)m_AvFrame->data, m_AvFrame->nb_samples);
g_AudioPlayThread->addAudioBuffer((char*)arrays, m_AvFrame->linesize[0]);
swr_free(&m_SWRtx);
av_packet_unref(&pkt);
}
}
QMap<QString, QString> MusicDecodecThread::getInfoMap(void)
{
QMutexLocker locker(&m_Mutex);
return m_InfoMap;
}
QImage MusicDecodecThread::getMusicIcon(void)
{
QMutexLocker locker(&m_Mutex);
return m_InfoImage;
}
int MusicDecodecThread::getTotalTime(void)
{
return m_TotalTime;
}

推荐阅读
  • 使用R包提供的数据是学习数据科学工具的好方法,但是在某个时候,您希望停止学习,开始使用自己的数据。在本章中,您将学习如何将纯文本矩形文件读入r。在这里,我们只讨论数据导入的皮毛,但 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • WhenIusepythontoapplythepymysqlmoduletoaddafieldtoatableinthemysqldatabase,itdo ... [详细]
  • IOS开发之短信发送与拨打电话的方法详解
    本文详细介绍了在IOS开发中实现短信发送和拨打电话的两种方式,一种是使用系统底层发送,虽然无法自定义短信内容和返回原应用,但是简单方便;另一种是使用第三方框架发送,需要导入MessageUI头文件,并遵守MFMessageComposeViewControllerDelegate协议,可以实现自定义短信内容和返回原应用的功能。 ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • 本文讨论了在ASP中创建RazorFunctions.cshtml文件时出现的问题,即ASP.global_asax不存在于命名空间ASP中。文章提供了解决该问题的代码示例,并详细解释了代码中涉及的关键概念,如HttpContext、Request和RouteData等。通过阅读本文,读者可以了解如何解决该问题并理解相关的ASP概念。 ... [详细]
  • 2019独角兽企业重金招聘Python工程师标准
    本文介绍了2019独角兽企业对Python工程师的招聘标准,包括在AndroidManifest中定义meta-data的方法和获取meta-data值的代码。同时提供了获取meta-data值的具体实现方法。转载文章链接:https://my.oschina.net/u/244918/blog/685127 ... [详细]
  • 开发笔记:Spark Java API 之 CountVectorizer
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了SparkJavaAPI之CountVectorizer相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 目标使用echarts绘制饼状图,并在此基础上绘制南丁格尔饼图,示例如下搭建环境新建文件夹note02,目录结构如下.note02|---index.html|---index.j ... [详细]
  • Html5第一章
    Html5第一章 ... [详细]
  • linux设备驱动子系统终极弹 usb,Linux usb子系统(二):USB设备驱动usbskeleton.c
    usb驱动分为通过usbfs操作设备的用户空间驱动,内核空间的内核驱动。两者不能同时进行,否则容易引发对共享资源访问的问题,死锁ÿ ... [详细]
author-avatar
Min2502857657_377
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有