一个完整的音频文件播放例子

播放步骤:

一个音频文件播放的过程包括以下几个阶段:
a) 解协议 —— 当音频文件在远端时,需要通过网格协议的方式传输到本地,如HTTP、RTSP、RTMP,这里会执行一个解协议的过程,去除协议头数据。
b) 解封装 —— 通常流媒体格式还包含媒体头,它对媒体中包含的流信息、流格式等元数据进行了描述,指导相关的逻辑进行媒体数据的进一步拆解。
c) 解码 —— 通常媒体数据都是编码过的,这里的编码主要是压缩,经压缩后的数据大小可以减小到原来的1/10或更多。
d) 重采样 —— 由于媒体数据的采样率、样本格式、通道布局等参数可能与播放单元要求的不一致,需要进行相应的数据转换,称之为重采样。
e) 播放 —— 经重采样后的PCM数据,按播放单元如ALSA要求的规范,调用相应的接口进行播放。

各步封装:

因为上面的流程比较多,后面演示例子的时候,如果将所有代码以过程式平铺在main函数里,那简直是没法看了。所以我们还是适当的按步骤进行封装,后面main函数将好看很多。

解封装:
// 错误信息维护
class CAVError
{
    int m_nErrCode;
    std::string m_strError;

public:
    CAVError()
    {
        setError(0, "");
    }
    virtual ~CAVError()
    {
    }

    // 设置错误信息
    void setError(int nCode, const std::string& strError)
    {
        m_nErrCode = nCode;

        if (m_nErrCode != 0)
        {
            char sErr[128] = {0};
            av_strerror(m_nErrCode, sErr, sizeof(sErr));
            m_strError = sErr;
        }
        else
        {
            m_strError = strError;
        }
    }

    // 获取错误信息
    std::string getError(int& nErrCode) const
    {
        char sTmp[32] = {0};
        snprintf(sTmp, sizeof(sTmp), "%d", m_nErrCode);

        nErrCode = m_nErrCode;
        return std::string(sTmp) + " - " + m_strError;
    }
};

// 输入文件封装器
class CAVInputWrapper
        : public CAVError
{
    AVFormatContext* m_pInputCTX;

public:
    CAVInputWrapper()
        : m_pInputCTX(NULL)
    {
    }
    virtual ~CAVInputWrapper()
    {
        close();
    }

    // 打开指定文件
    bool openFile(const std::string& strFile)
    {
        int rc = avformat_open_input(&m_pInputCTX, strFile.c_str(), NULL, NULL);
        if (rc < 0)
        {
            setError(rc, "");
            return false;
        }

        rc = avformat_find_stream_info(m_pInputCTX, NULL);
        if (rc < 0)
        {
            avformat_close_input(&m_pInputCTX);

            setError(rc, "");
            return false;
        }

        return true;
    }

    // 关闭文件
    void close()
    {
        if (m_pInputCTX)
        {
            avformat_close_input(&m_pInputCTX);
            setError(0, "");
        }
    }

    // 读取流信息
    bool getStreamsInfo(std::vector<AVStream*>& vecPStream)
    {
        if (m_pInputCTX == NULL)
        {
            setError(0, "not open!");
            return false;
        }

        for (int i = 0; i < m_pInputCTX->nb_streams; ++i)
        {
            vecPStream.push_back(m_pInputCTX->streams[i]);
        }

        return true;
    }

    // 读取一帧
    bool getPacket(AVPacket* pPacket)
    {
        if (m_pInputCTX == NULL)
        {
            setError(0, "not open!");
            return false;
        }

        int rc = av_read_frame(m_pInputCTX, pPacket);
        if (rc < 0)
        {
            setError(rc, "");
            return false;
        }

        return true;
    }
};
解码:
// 解码封装器
class CAVDecodeWrapper
        : public CAVError
{
    const AVCodec* m_pCodec;
    AVCodecContext* m_pCodecCTX;

public:
    CAVDecodeWrapper()
        : m_pCodec(NULL)
        , m_pCodecCTX(NULL)
    {
    }
    virtual ~CAVDecodeWrapper()
    {
        close();
    }

    // 打开解码器
    bool openCodecByID(enum AVCodecID emCodecID, const AVCodecParameters* pCodecPar)
    {
        // 查找解码器
        m_pCodec = avcodec_find_decoder(emCodecID);
        if (m_pCodec == NULL)
        {
            setError(0, "can't find the codec!");
            return false;
        }

        // 根据解码器分配上下文
        m_pCodecCTX = avcodec_alloc_context3(m_pCodec);
        if (m_pCodecCTX == NULL)
        {
            setError(0, "can't alloc the codec context!");
            m_pCodec = NULL;
            return false;
        }

        // 拷贝流中的编码器信息到上下文
        if (pCodecPar)
        {
            int rc = avcodec_parameters_to_context(m_pCodecCTX, pCodecPar);
            if (rc < 0)
            {
                setError(rc, "");
                avcodec_free_context(&m_pCodecCTX);
                m_pCodec = NULL;
                return false;
            }
        }

        // 打开解码器
        int rc = avcodec_open2(m_pCodecCTX, m_pCodec, NULL);
        if (rc < 0)
        {
            setError(rc, "");
            avcodec_free_context(&m_pCodecCTX);
            m_pCodec = NULL;
            return false;
        }

        return true;
    }

    // 关闭解码器
    void close()
    {
        if (m_pCodecCTX)
        {
            avcodec_free_context(&m_pCodecCTX);
        }

        if (m_pCodec)
        {
            m_pCodec = NULL;
        }
    }

    // 发送编码数据
    bool sendPacket(const AVPacket* pPacket)
    {
        if (m_pCodecCTX == NULL)
        {
            setError(0, "not open!");
            return false;
        }

        int rc = avcodec_send_packet(m_pCodecCTX, pPacket);
        if (rc < 0)
        {
            setError(rc, "");
            return false;
        }

        return true;
    }

    // 接收一帧解码数据
    bool recvFrame(AVFrame* pFrame)
    {
        if (m_pCodecCTX == NULL)
        {
            setError(0, "not open!");
            return false;
        }

        int rc = avcodec_receive_frame(m_pCodecCTX, pFrame);
        if (rc < 0)
        {
            setError(rc, "");
            return false;
        }

        return true;
    }
};
重采样:
// 音频重采样封装器
class CSWRWrapper
        : public CAVError
{
    struct SwrContext* m_pSwrCTX;

public:
    CSWRWrapper()
        : m_pSwrCTX(NULL)
    {
    }
    virtual ~CSWRWrapper()
    {
        free();
    }

    struct SwrContext* handle()
    {
        return m_pSwrCTX;
    }

    // 初使化转换参数
    bool init(int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
              int64_t in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate)
    {
        // 分配重采样上下文,初使化参数
        m_pSwrCTX = swr_alloc_set_opts(NULL, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL);
        if (m_pSwrCTX == NULL)
        {
            setError(0, "swr_alloc_set_opts() failed!");
            return false;
        }

        // 初使化上下文
        int rc = swr_init(m_pSwrCTX);
        if (rc < 0)
        {
            swr_free(&m_pSwrCTX);

            setError(rc, "");
            return false;
        }

        return true;
    }

    // 释放资源
    void free()
    {
        if (m_pSwrCTX)
        {
            swr_free(&m_pSwrCTX);
        }
    }

    // 重采样转换
    int convert(uint8_t **out, int out_count, const uint8_t **in , int in_count)
    {
        if (m_pSwrCTX == NULL)
        {
            setError(0, "not init!");
            return -1;
        }

        int rc = swr_convert(m_pSwrCTX, out, out_count, in, in_count);
        if (rc < 0)
        {
            setError(rc, "");
            return -1;
        }

        return rc;
    }
};
ALSA播放:

ALSA是linux系统的音频播放架构,在windows下就不行了,windows请采用其他合适的方案吧,或者采用SDL库统一跨平台。

#if defined(__linux__)

#include <alsa/asoundlib.h>

// 错误信息维护
class CALSAError
{
    int m_nErrCode;
    std::string m_strError;

public:
    CALSAError()
    {
        setError(0, "");
    }
    virtual ~CALSAError()
    {
    }

    // 设置错误信息
    void setError(int nCode, const std::string& strError)
    {
        m_nErrCode = nCode;

        if (m_nErrCode != 0)
        {
            m_strError = snd_strerror(m_nErrCode);
        }
        else
        {
            m_strError = strError;
        }
    }

    // 获取错误信息
    std::string getError(int& nErrCode) const
    {
        char sTmp[32] = {0};
        snprintf(sTmp, sizeof(sTmp), "%d", m_nErrCode);

        nErrCode = m_nErrCode;
        return std::string(sTmp) + " - " + m_strError;
    }
};

// ALSA声卡封装器
class CALSAWrapper
    : public CALSAError
{
    snd_pcm_t* m_pPCM;

    // 声卡硬件参数
    snd_pcm_access_t m_xAccess;
    snd_pcm_format_t m_xSampleFmt;
    unsigned int m_nChannels;
    unsigned int m_nSampleRate;
    snd_pcm_uframes_t m_xPeriodFrames;

public:
    CALSAWrapper()
        : m_pPCM(NULL)
        , m_xAccess(SND_PCM_ACCESS_RW_INTERLEAVED)
        , m_xSampleFmt(SND_PCM_FORMAT_S16_LE)
        , m_nChannels(2)
        , m_nSampleRate(44100)
        , m_xPeriodFrames(1024)
    {
    }
    virtual ~CALSAWrapper()
    {
        close();
    }

    // 声卡硬件参数访问

    void setAccess(snd_pcm_access_t xAccess)
    {
        m_xAccess = xAccess;
    }
    snd_pcm_access_t getAccess()
    {
        return m_xAccess;
    }

    void setSampleFormat(snd_pcm_format_t xSampleFmt)
    {
        m_xSampleFmt = xSampleFmt;
    }
    snd_pcm_format_t getSampleFormat()
    {
        return m_xSampleFmt;
    }

    void setChannels(unsigned int nChannels)
    {
        m_nChannels = nChannels;
    }
    unsigned int getChannels()
    {
        return m_nChannels;
    }

    void setSampleRate(unsigned int nSampleRate)
    {
        m_nSampleRate = nSampleRate;
    }
    unsigned int getSampleRate()
    {
        return m_nSampleRate;
    }

    void setPeriodFrames(snd_pcm_uframes_t xPeriodFrames)
    {
        m_xPeriodFrames = xPeriodFrames;
    }
    snd_pcm_uframes_t getPeriodFrames()
    {
        return m_xPeriodFrames;
    }

    // 打开回放
    bool openPlayBack()
    {
        // 打开默认设备
        int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, 0);
        if (rc < 0)
        {
            setError(rc, "");
            return false;
        }

        // 分配硬件参数,填充默认值
        snd_pcm_hw_params_t *pHWParams = NULL;
        snd_pcm_hw_params_malloc(&pHWParams);
        snd_pcm_hw_params_any(m_pPCM, pHWParams);

        do
        {
            // 设置硬件参数

            rc = snd_pcm_hw_params_set_access(m_pPCM, pHWParams, m_xAccess);
            if (rc < 0)
                break;

            rc = snd_pcm_hw_params_set_format(m_pPCM, pHWParams, m_xSampleFmt);
            if (rc < 0)
                break;

            rc = snd_pcm_hw_params_set_channels(m_pPCM, pHWParams, m_nChannels);
            if (rc < 0)
                break;

            rc = snd_pcm_hw_params_set_rate_near(m_pPCM, pHWParams, &m_nSampleRate, NULL);
            if (rc < 0)
                break;

            rc = snd_pcm_hw_params_set_period_size_near(m_pPCM, pHWParams, &m_xPeriodFrames, NULL);
            if (rc < 0)
                break;

            rc = snd_pcm_hw_params(m_pPCM, pHWParams);
            if (rc < 0)
                break;

            // 释放硬件参数内存
            snd_pcm_hw_params_free(pHWParams);
            pHWParams = NULL;

            return true;
        } while (0);

        // 打开失败,执行清理工作

        if (pHWParams)
        {
            snd_pcm_hw_params_free(pHWParams);
            pHWParams = NULL;
        }

        snd_pcm_hw_free(m_pPCM);
        m_pPCM = NULL;

        setError(rc, "");
        return false;
    }

    // 关闭声卡,清理资源
    void close()
    {
        if (m_pPCM)
        {
            snd_pcm_drain(m_pPCM);
            snd_pcm_close(m_pPCM);
            snd_pcm_hw_free(m_pPCM);
            m_pPCM = NULL;
        }
    }

    // 向声卡写入一段PCM缓冲
    snd_pcm_sframes_t writePCM(const void *pBuffer, snd_pcm_uframes_t uSize)
    {
        if (m_pPCM == NULL)
        {
            setError(0, "not open!");
            return -1;
        }

        snd_pcm_sframes_t ret = snd_pcm_writei(m_pPCM, pBuffer, uSize);
        if (ret == -EPIPE)
        {
            snd_pcm_prepare(m_pPCM);
            ret = snd_pcm_writei(m_pPCM, pBuffer, uSize);
        }

        if (ret < 0)
        {
            setError(ret, "");
            return -1;
        }

        if (ret > 0 && (snd_pcm_uframes_t)ret < uSize)
        {
            setError(0, "short write!");
        }

        return ret;
    }
};

#endif
PCM缓冲:

需要注意的是,很多播放单元如ALSA,要求上层提供给播放单元的样本个数是有严格要求的,否则将不能很好的利用播放单元的内部缓冲,造成播放时的噪音或者沙沙声。解决办法就是,我们可以设置一个PCM缓冲,将解码或者重采样后的音频数据先排队在缓冲里,然后再需要的块大小交给播放单元。

// PCM缓冲
class CPCMBuffer
{
    char* m_pData;
    int m_nSize;
    int m_nCapacity;

public:
    CPCMBuffer()
        : m_pData(NULL)
        , m_nSize(0)
        , m_nCapacity(0)
    {
    }
    virtual ~CPCMBuffer()
    {
        __clear();
    }

    // 添加数据
    void append(const char* pData, int nSize)
    {
        __tryIncrement(nSize);
        __append(pData, nSize);
    }

    // 直接添加AVFrame
    void append(AVFrame* pFrame)
    {
        // 计算音频数据的大小
        int nSampleBytes = av_get_bytes_per_sample((AVSampleFormat)pFrame->format);
        int nPCMSize = nSampleBytes * pFrame->channels * pFrame->nb_samples;
        char* pPCMBuffer = new char[nPCMSize];

        int nOffset = 0;
        for (int i = 0; i < pFrame->nb_samples; ++i)
        {
            for (int c = 0; c < pFrame->channels; ++c)
            {
                memcpy(pPCMBuffer + nOffset, pFrame->data[c] + nSampleBytes * i, nSampleBytes);
                nOffset += nSampleBytes;
            }
        }

        append(pPCMBuffer, nPCMSize);
        delete[] pPCMBuffer;
    }

    // 提供外部缓冲区,获取指定大小的数据
    bool fetch(char* pData, int nSize)
    {
        return __fetch(pData, nSize);
    }

    // 不提供外部缓冲区,获取指定大小的数据
    bool fetch(char** ppData, int nSize)
    {
        char* pData = new char[nSize];
        if (!__fetch(pData, nSize))
        {
            delete[] pData;
            return false;
        }

        *ppData = pData;
        return true;
    }

    // 释放之前fetch()的内存
    void free(char* pData)
    {
        delete[] pData;
    }

private:
    void __clear()
    {
        if (m_pData)
        {
            delete[] m_pData;
            m_pData = NULL;
            m_nSize = 0;
            m_nCapacity = 0;
        }
    }

    void __tryIncrement(int nSize)
    {
        if (m_nCapacity - m_nSize >= nSize)
            return;

        int nAdd = nSize - (m_nCapacity - m_nSize);
        int nNewCapacity = m_nCapacity + nAdd;

        char* pNewData = new char[nNewCapacity];
        memcpy(pNewData, m_pData, m_nSize);
        delete[] m_pData;

        m_pData = pNewData;
        m_nCapacity = nNewCapacity;
    }

    void __append(const char* pData, int nSize)
    {
        memcpy(m_pData + m_nSize, pData, nSize);
        m_nSize += nSize;
    }

    bool __fetch(char* pData, int nSize)
    {
        if (m_nSize < nSize)
            return false;

        memcpy(pData, m_pData, nSize);
        memcpy(m_pData, m_pData + nSize, m_nSize - nSize);
        m_nSize -= nSize;

        return true;
    }
};

执行调用:

请将上面的代码收集到一起,然后编写下面的main()调用:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include <string>
#include <vector>

extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
}

int main(int argc, char* argv[])
{
    CAVInputWrapper avInput;

    // 打开媒体文件
    bool bRet = avInput.openFile("1.flv");
    if (!bRet)
    {
        int nErrCode = 0;
        printf("open file failed, err:[%s] \n", avInput.getError(nErrCode).c_str());
        return -1;
    }

    // 读取流信息
    std::vector<AVStream*> vecPStream;
    avInput.getStreamsInfo(vecPStream);

    // 选择第一路音频流
    int nAudioIndex = -1;
    for (std::vector<AVStream*>::iterator iter = vecPStream.begin(); iter != vecPStream.end(); ++iter)
    {
        if ((*iter)->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            nAudioIndex = (*iter)->index;
        }
    }

    if (nAudioIndex < 0)
    {
        printf("no audio stream! \n");
        return -1;
    }

    // 打印所选音频的格式信息
    AVCodecParameters* pCodecPar = vecPStream[nAudioIndex]->codecpar;
    printf("codec:[%d - %s] \n", pCodecPar->codec_id, avcodec_get_name(pCodecPar->codec_id));
    printf("format:[%d - %s] \n", pCodecPar->format, av_get_sample_fmt_name((AVSampleFormat)pCodecPar->format));
    printf("channel_layout:[%lld - %s] \n", pCodecPar->channel_layout, av_get_channel_name(pCodecPar->channel_layout));
    printf("channels:[%d] \n", pCodecPar->channels);
    printf("sample_rate:[%d] \n", pCodecPar->sample_rate);

    // 计划向ALSA输出AV_SAMPLE_FMT_S16格式
    bool bRequireSWR = false;
    if (pCodecPar->format != AV_SAMPLE_FMT_S16)
    {
        bRequireSWR = true;
        printf("stream format not [%d - %s], must resample! \n", AV_SAMPLE_FMT_S16, av_get_sample_fmt_name((AVSampleFormat)AV_SAMPLE_FMT_S16));
    }

    // 打开解码器
    CAVDecodeWrapper avDecodec;
    if (!avDecodec.openCodecByID(vecPStream[nAudioIndex]->codecpar->codec_id, vecPStream[nAudioIndex]->codecpar))
    {
        int nErrCode = 0;
        printf("open decodec failed, err:[%s] \n", avDecodec.getError(nErrCode).c_str());
        return -1;
    }

    unsigned int nRealSampleRate = pCodecPar->sample_rate;
    int xRealPeriodFrames = 1024;

#if defined(__linux__)

    CALSAWrapper alsa;

    // 设置ALSA相关参数
    alsa.setAccess(SND_PCM_ACCESS_RW_INTERLEAVED);
    alsa.setSampleFormat(SND_PCM_FORMAT_S16_LE);
    alsa.setChannels(pCodecPar->channels);
    alsa.setSampleRate(pCodecPar->sample_rate);
    alsa.setPeriodFrames(1024);

    // 打开ALSA声卡
    if (!alsa.openPlayBack())
    {
        int nErrCode = 0;
        printf("open alsa failed, err:[%s] \n", alsa.getError(nErrCode).c_str());
        return -1;
    }

    // 获取ALSA实际采纳的采样率和周期帧数
    nRealSampleRate = alsa.getSampleRate();
    xRealPeriodFrames = alsa.getPeriodFrames();

    // 检查ALSA实际接受的采样率是否与媒体不一致,则需要重采样
    if (nRealSampleRate != pCodecPar->sample_rate)
    {
        bRequireSWR = true;
        printf("stream sample rate:[%d], but alsa support is:[%d], must resample! \n", pCodecPar->sample_rate, nRealSampleRate);
    }

#endif

    CSWRWrapper swr;

    // 初使化音频重采样
    if (bRequireSWR
            && !swr.init(pCodecPar->channel_layout, AV_SAMPLE_FMT_S16, nRealSampleRate,
                         pCodecPar->channel_layout, (enum AVSampleFormat)pCodecPar->format, pCodecPar->sample_rate) )
    {
        int nErrCode = 0;
        printf("SWR init failed, err:[%s] \n", swr.getError(nErrCode).c_str());
        return -1;
    }

    // PCM缓冲
    CPCMBuffer pcmBuffer;

    AVPacket* pPacket = av_packet_alloc();
    AVFrame* pFrame = av_frame_alloc();

    // 读取媒体帧
    while (true)
    {
        if (!avInput.getPacket(pPacket))
        {
            int nErrCode = 0;
            std::string strError = avInput.getError(nErrCode);
            if (nErrCode != AVERROR_EOF)
            {
                printf("read frame failed, err:[%s] \n", strError.c_str());
            }

            break;
        }

        // 只关注选择的音频流
        if (pPacket->stream_index != nAudioIndex)
            continue;

        printf("packet pts:[%lld] dts:[%lld] duration:[%lld] size:[%d] \n", pPacket->pts, pPacket->dts, pPacket->duration, pPacket->size);

        // 发送编码数据
        avDecodec.sendPacket(pPacket);

        // 持续接收解码数据
        while (true)
        {
            if (!avDecodec.recvFrame(pFrame))
            {
                int nErrCode = 0;
                std::string strError = avDecodec.getError(nErrCode);
                if (nErrCode != AVERROR(EAGAIN) && nErrCode != AVERROR_EOF)
                {
                    printf("read frame failed, err:[%s] \n", strError.c_str());
                }

                break;
            }

            printf("frame format:[%d:%s] channels:[%d] sample_rate:[%d] nb_samples:[%d] pkt_size:[%d] linesize:[%d] \n",
                   pFrame->format, av_get_sample_fmt_name((AVSampleFormat)pFrame->format), pFrame->channels, pFrame->sample_rate, pFrame->nb_samples, pFrame->pkt_size, pFrame->linesize[0]);

            // 需要音频重采样
            if (bRequireSWR)
            {
                // 计算重采样样本数
                int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr.handle(), pCodecPar->sample_rate) +
                                                pFrame->nb_samples, nRealSampleRate, pCodecPar->sample_rate, AV_ROUND_UP);

                // 分配重采样空间
                uint8_t **pDstData = NULL;
                av_samples_alloc_array_and_samples(&pDstData, NULL, pFrame->channels, dst_nb_samples, AV_SAMPLE_FMT_S16, 0);

                // 重采样转换
                int nConvertSamples = 0;
                if ((nConvertSamples = swr.convert(pDstData, dst_nb_samples, (const uint8_t**)pFrame->data, pFrame->nb_samples)) > 0)
                {
                    int nConvertSize = av_samples_get_buffer_size(NULL, pFrame->channels, nConvertSamples, AV_SAMPLE_FMT_S16, 1);

                    // 添加重采样数据到PCM缓冲
                    pcmBuffer.append((const char*)pDstData[0], nConvertSize);
                }
                else
                {
                    int nErrCode = 0;
                    printf("SWR convert failed, err:[%s] \n", swr.getError(nErrCode).c_str());
                }

                // 释放重采样空间
                if (pDstData)
                {
                    av_freep(&pDstData[0]);
                }
                av_freep(&pDstData);
            }
            else
            {
                // 添加原始数据到PCM缓冲
                pcmBuffer.append(pFrame);
            }

            // 从PCM缓冲取周期数据
            int nSampleBytes = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
            int nPeriodBytes = nSampleBytes * pFrame->channels * xRealPeriodFrames;
            char* pPeriodBuffer = NULL;

            while (pcmBuffer.fetch(&pPeriodBuffer, nPeriodBytes))
            {
#if defined(__linux__)
                // 交给ALSA声卡播放
                int nWriteFrames = alsa.writePCM(pPeriodBuffer, xRealPeriodFrames);
                printf("pcm write:[%d] ret:[%d] \n", xRealPeriodFrames, nWriteFrames);
#endif
                pcmBuffer.free(pPeriodBuffer);
            }
        }

        // 释放帧引用的内存
        av_packet_unref(pPacket);
    }

    // 刷新解码缓冲区
    avDecodec.sendPacket(NULL);

    // 接收尾包
    if (avDecodec.recvFrame(pFrame))
    {
        printf("tail frame format:[%d:%s] channels:[%d] sample_rate:[%d] nb_samples:[%d] pkt_size:[%d] linesize:[%d] \n",
               pFrame->format, av_get_sample_fmt_name((AVSampleFormat)pFrame->format), pFrame->channels, pFrame->sample_rate, pFrame->nb_samples, pFrame->pkt_size, pFrame->linesize[0]);

        // 简单起见,忽略尾包处理
    }

    av_packet_free(&pPacket);
    av_frame_free(&pFrame);

    swr.free();

#if defined(__linux__)

    alsa.close();

#endif

    // 关闭解码器
    avDecodec.close();

    // 关闭媒体文件
    avInput.close();

    return 0;
}

编译,只能在linux上进行了:
g++ -o testaudio testaudio.cpp -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -lavformat -lavcodec -lavutil -lswscale -lswresample -lva -lasound -pthread -lz -lmp3lame -lX11 -lva-drm -lva-x11 -lm -ggdb

执行前,代码要求同目标下有1.flv的文件,当然你可以改成其他的媒体文件,理论上应该各种格式都支持,输出:

codec:[86017 - mp3] 
format:[8 - fltp] 
channel_layout:[4 - FC] 
channels:[1] 
sample_rate:[44100] 
stream format not [1 - s16], must resample! 
packet pts:[0] dts:[0] duration:[26] size:[208] 
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[208] linesize:[4608] 
pcm write:[940] ret:[940] 
packet pts:[26] dts:[26] duration:[26] size:[209] 
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608] 
pcm write:[940] ret:[940] 
packet pts:[52] dts:[52] duration:[26] size:[209] 
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608] 
pcm write:[940] ret:[940] 
packet pts:[78] dts:[78] duration:[26] size:[209] 
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608] 
pcm write:[940] ret:[940] 
packet pts:[104] dts:[104] duration:[26] size:[209] 
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608] 
pcm write:[940] ret:[940] 
pcm write:[940] ret:[940] 
packet pts:[131] dts:[131] duration:[26] size:[209] 
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608] 
pcm write:[940] ret:[940] 
packet pts:[157] dts:[157] duration:[26] size:[209] 
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608] 
pcm write:[940] ret:[940] 
packet pts:[183] dts:[183] duration:[26] size:[209] 
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608] 
pcm write:[940] ret:[940] 
packet pts:[209] dts:[209] duration:[26] size:[209] 
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608] 
pcm write:[940] ret:[940] 
pcm write:[940] ret:[940] 
......

留意下喇叭输出。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容