1. 程式人生 > >使用FFMpeg 解碼音訊檔案

使用FFMpeg 解碼音訊檔案

本篇文章將介紹使用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 <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;
}