使用FFMpeg 解碼音訊檔案
阿新 • • 發佈:2018-12-13
本篇文章將介紹使用FFMpeg解碼音訊檔案為PCM的資料。 使用FFMpeg獲取想要的音訊資料的步驟如下: 解封裝(MP3檔案)->解碼(MP3編碼)->PCM資料重取樣
1. 解封裝
使用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等)
2. 解碼
- 使用函式 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 就儲存著解碼後的資料。
3. 對解碼後的資料重取樣
這裡使用的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 <QThread>
#include <QObject>
#include <QMap>
#include <QImage>
#include <QMutex>
#include <QMutexLocker>
#include "AudioPlayThread.h"
extern "C"{
#include <stdio.h>
#include <stdlib.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/frame.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavutil/opt.h>
#include <libavutil/error.h>
}
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 <QDebug>
#include <QTime>
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;
}