本篇文章将介绍使用FFMpeg解码音频文件为PCM的数据。
使用FFMpeg获取想要的音频数据的步骤如下:
解封装(MP3文件)->解码(MP3编码)->PCM数据重采样
使用FFMpeg解封装的步骤如下:
- 使用函数 av_register_all() 注册所有的封装器和解封装器。
- 使用函数 avformat_open_input() 打开一个文件,可以为文件名也可以为一个URL。
- 使用函数 avformat_find_stream_info() 查找流信息,把它存入AVFormatContext中。
- 查找流信息,获取音频流的索引位置,获取解码器的codec_id。
- 根据codec_id,使用函数 avcodec_find_decoder() 获取解码器AVCodec*。
- 使用函数 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等)
- 使用函数 av_read_frame() 获取一包数据。
- 使用函数 avcodec_send_packet() 发送一包数据。
- 使用函数 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 就存储着解码后的数据。
这里使用的FFMpeg提供工具(SwrContext)对音频做重采样。使用方法如下:
- 使用方法 swr_alloc(); 创建一个 SwrContext* 类型的指针,并分配内存。
- 使用方法 swr_alloc_set_opts() 设置输入和输出的参数。
- 使用方法 swr_init() 初始化这个 SwrContext* 指针变量。
- 使用方法 swr_convert() 转换。
- 使用方法 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;
}