FFmpeg 開發(03):FFmpeg + OpenSLES 實現音頻解碼播放


FFmpeg + OpenSLES 實現音頻解碼播放

該文章首發於微信公眾號:字節流動

FFmpeg 開發系列連載:
FFmpeg 開發(01):FFmpeg 編譯和集成
FFmpeg 開發(02):FFmpeg + ANativeWindow 實現視頻解碼播放

本文將利用 FFmpeg 對一個 Mp4 文件的音頻流進行解碼,然后使用 libswresample 將解碼后的 PCM 音頻數據轉換為目標格式的數據,最后利用 OpenSLES 進行播放。

FFmpeg 音頻解碼

舊文中,我們已經對視頻解碼流程進行了詳細的介紹,一個多媒體文件(Mp4)一般包含一個音頻流和一個視頻流,而FFmpeg 對音頻流和視頻流的解碼流程一致。因此,本節將不再對音頻解碼流程進行贅述。

類似於視頻流的處理,音頻流的處理流程為:(Mp4文件)解協議->解封裝->音頻解碼->重采樣->播放。

音頻解碼播放流程圖.png音頻解碼播放流程圖.png

這里面有反復提到重采樣,類似於視頻圖像的轉碼,因為顯示器最終顯示的是 RGB 數據,這個一點比較好理解。那么為什么要對解碼的音頻數據進行重采樣呢?

一般錄音(采集音頻)時,可能有多種采樣率可以選擇,當該采樣率與音頻設備驅動的固定采樣率不符時,就會導致變聲或者音頻出現快放慢放效果,此時就需要用到重采樣來確保音頻采樣率和設備驅動采樣率一致,使音頻正確播放。

利用 libswresample 庫將對音頻進行重采樣,有如下幾個步驟:

//1. 生成 resample 上下文,設置輸入和輸出的通道數、采樣率以及采樣格式,初始化上下文
m_SwrContext = swr_alloc();

av_opt_set_int(m_SwrContext, "in_channel_layout", codeCtx->channel_layout, 0);
av_opt_set_int(m_SwrContext, "out_channel_layout", AUDIO_DST_CHANNEL_LAYOUT, 0);
av_opt_set_int(m_SwrContext, "in_sample_rate", codeCtx->sample_rate, 0);
av_opt_set_int(m_SwrContext, "out_sample_rate", AUDIO_DST_SAMPLE_RATE, 0);
av_opt_set_sample_fmt(m_SwrContext, "in_sample_fmt", codeCtx->sample_fmt, 0);
av_opt_set_sample_fmt(m_SwrContext, "out_sample_fmt", DST_SAMPLT_FORMAT,  0);

swr_init(m_SwrContext);

//2. 申請輸出 Buffer
m_nbSamples = (int)av_rescale_rnd(NB_SAMPLES, AUDIO_DST_SAMPLE_RATE, codeCtx->sample_rate, AV_ROUND_UP);
m_BufferSize = av_samples_get_buffer_size(NULL, AUDIO_DST_CHANNEL_COUNTS,m_nbSamples, DST_SAMPLT_FORMAT, 1);
m_AudioOutBuffer = (uint8_t *) malloc(m_BufferSize);

//3. 重采樣,frame 為解碼幀
int result = swr_convert(m_SwrContext, &m_AudioOutBuffer, m_BufferSize / 2, (const uint8_t **) frame->data, frame->nb_samples);
if (result > 0 ) {
    //play
}

//4. 釋放資源
if(m_AudioOutBuffer) {
    free(m_AudioOutBuffer);
    m_AudioOutBuffer = nullptr;
}

if(m_SwrContext) {
    swr_free(&m_SwrContext);
    m_SwrContext = nullptr;
}

OpenSL ES 播放音頻

OpenSL ES 全稱為: Open Sound Library for Embedded Systems,是一個針對嵌入式系統的開放硬件音頻加速庫,支持音頻的采集和播放,它提供了一套高性能、低延遲的音頻功能實現方法,並且實現了軟硬件音頻性能的跨平台部署,大大降低了上層處理音頻應用的開發難度。

OpenSL ES 是基於 c 語言實現的,但其提供的接口是采用面向對象的方式實現,OpenSL ES 的大多數 API 是通過對象來調用的。

Object 和 Interface OpenSL ES 中的兩大基本概念,可以類比為 Java 中的對象和接口。在 OpenSL ES 中, 每個 Object 可以存在一系列的 Interface ,並且為每個對象都提供了一系列的基本操作,如 Realize,GetState,Destroy 等。

重要的一點,只有通過 GetInterface 方法拿到 Object 的 Interface ,才能使用 Object 提供的功能。

Audio 引擎對象和接口

Audio 引擎對象和接口,即 Engine Object 和 SLEngineItf Interface 。Engine Object 的主要功能是管理 Audio Engine 的生命周期,提供引擎對象的管理接口。引擎對象的使用方法如下:

SLresult result;
// 創建引擎對象
result = slCreateEngine(&engineObject, 0NULL0NULLNULL);
assert(SL_RESULT_SUCCESS == result);
(void)result;

// 實例化
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
(void)result;

// 獲取引擎對象接口
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
assert(SL_RESULT_SUCCESS == result);
(void)result;

// 釋放引擎對象的資源
result = (*engineObject)->Destroy(engineObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
(void)result;

SLRecordItf 和 SLPlayItf

SLRecordItf 和 SLPlayItf 分別抽象多媒體功能 recorder 和 player ,通過 SLEngineItf 的 CreateAudioPlayer 和 CreateAudioRecorder 方法分別創建 player 和 recorder 對象實例。

// 創建 audio recorder 對象
result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject , &recSource, &dataSink,
                                                  NUM_RECORDER_EXPLICIT_INTERFACES, iids, required);

// 創建 audio player 對象
SLresult result = (*engineEngine)->CreateAudioPlayer(
        engineEngine,
        &audioPlayerObject,
        &dataSource,
        &dataSink,
        1,
        interfaceIDs,
        requiredInterfaces
);

SLDataSource 和 SLDataSink

OpenSL ES 中的 SLDataSource 和 SLDataSink 結構體,主要用於構建 audio player 和 recorder 對象,其中 SLDataSource 表示音頻數據來源的信息,SLDataSink 表示音頻數據輸出信息。

// 數據源簡單緩沖隊列定位器
SLDataLocator_AndroidSimpleBufferQueue dataSou
        SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEU
        1
};
// PCM 數據源格式
SLDataFormat_PCM dataSourceFormat = {
        SL_DATAFORMAT_PCM, // 格式類型
        wav_get_channels(wav), // 通道數
        wav_get_rate(wav) * 1000//采樣率
        wav_get_bits(wav), // 位寬
        wav_get_bits(wav),
        SL_SPEAKER_FRONT_CENTER, // 通道屏蔽
        SL_BYTEORDER_LITTLEENDIAN // 字節順序(大小端序)
};


// 數據源
SLDataSource dataSource = {
        &dataSourceLocator,
        &dataSourceFormat
};
// 針對數據接收器的輸出混合定位器(混音器)
SLDataLocator_OutputMix dataSinkLocator = {
        SL_DATALOCATOR_OUTPUTMIX, // 定位器類型
        outputMixObject // 輸出混合
};
// 輸出
SLDataSink dataSink = {
        &dataSinkLocator, // 定位器
        0,
};

OpenSL ES Recorder 和 Player 功能構建

Audio Recorder

Audio RecorderAudio Recorder

Audio Player

Audio PlayerAudio Player

Audio Player 的 Data Source 也可以是本地存儲或緩存的音頻數據,以上圖片來自於 Jhuster 的博客。

由於本文只介紹音頻的解碼播放,下面的代碼僅展示 OpenSLES Audio Player 播放音頻的過程。

//OpenSLES 渲染器初始化
void OpenSLRender::Init() {
    LOGCATE("OpenSLRender::Init");

    int result = -1;
    do {
        //創建並初始化引擎對象
        result = CreateEngine();
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::Init CreateEngine fail. result=%d", result);
            break;
        }

        //創建並初始化混音器
        result = CreateOutputMixer();
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::Init CreateOutputMixer fail. result=%d", result);
            break;
        }

        //創建並初始化播放器
        result = CreateAudioPlayer();
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::Init CreateAudioPlayer fail. result=%d", result);
            break;
        }

        //設置播放狀態
        (*m_AudioPlayerPlay)->SetPlayState(m_AudioPlayerPlay, SL_PLAYSTATE_PLAYING);

        //激活回調接口
        AudioPlayerCallback(m_BufferQueue, this);

    } while (false);

    if(result != SL_RESULT_SUCCESS) {
        LOGCATE("OpenSLRender::Init fail. result=%d", result);
        UnInit();
    }

}

int OpenSLRender::CreateEngine() {
    SLresult result = SL_RESULT_SUCCESS;
    do {
        result = slCreateEngine(&m_EngineObj, 0nullptr0nullptrnullptr);
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::CreateEngine slCreateEngine fail. result=%d", result);
            break;
        }

        result = (*m_EngineObj)->Realize(m_EngineObj, SL_BOOLEAN_FALSE);
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::CreateEngine Realize fail. result=%d", result);
            break;
        }

        result = (*m_EngineObj)->GetInterface(m_EngineObj, SL_IID_ENGINE, &m_EngineEngine);
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::CreateEngine GetInterface fail. result=%d", result);
            break;
        }

    } while (false);
    return result;
}

int OpenSLRender::CreateOutputMixer() {
    SLresult result = SL_RESULT_SUCCESS;
    do {
        const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};
        const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};

        result = (*m_EngineEngine)->CreateOutputMix(m_EngineEngine, &m_OutputMixObj, 1, mids, mreq);
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::CreateOutputMixer CreateOutputMix fail. result=%d", result);
            break;
        }

        result = (*m_OutputMixObj)->Realize(m_OutputMixObj, SL_BOOLEAN_FALSE);
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::CreateOutputMixer CreateOutputMix fail. result=%d", result);
            break;
        }

    } while (false);

    return result;
}

int OpenSLRender::CreateAudioPlayer() {
    SLDataLocator_AndroidSimpleBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
    SLDataFormat_PCM pcm = {
            SL_DATAFORMAT_PCM,//format type
            (SLuint32)2,//channel count
            SL_SAMPLINGRATE_44_1,//44100hz
            SL_PCMSAMPLEFORMAT_FIXED_16,// bits per sample
            SL_PCMSAMPLEFORMAT_FIXED_16,// container size
            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,// channel mask
            SL_BYTEORDER_LITTLEENDIAN // endianness
    };
    SLDataSource slDataSource = {&android_queue, &pcm};

    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, m_OutputMixObj};
    SLDataSink slDataSink = {&outputMix, nullptr};

    const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME};
    const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};

    SLresult result;

    do {

        result = (*m_EngineEngine)->CreateAudioPlayer(m_EngineEngine, &m_AudioPlayerObj, &slDataSource, &slDataSink, 3, ids, req);
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::CreateAudioPlayer CreateAudioPlayer fail. result=%d", result);
            break;
        }

        result = (*m_AudioPlayerObj)->Realize(m_AudioPlayerObj, SL_BOOLEAN_FALSE);
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::CreateAudioPlayer Realize fail. result=%d", result);
            break;
        }

        result = (*m_AudioPlayerObj)->GetInterface(m_AudioPlayerObj, SL_IID_PLAY, &m_AudioPlayerPlay);
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::CreateAudioPlayer GetInterface fail. result=%d", result);
            break;
        }

        result = (*m_AudioPlayerObj)->GetInterface(m_AudioPlayerObj, SL_IID_BUFFERQUEUE, &m_BufferQueue);
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::CreateAudioPlayer GetInterface fail. result=%d", result);
            break;
        }

        result = (*m_BufferQueue)->RegisterCallback(m_BufferQueue, AudioPlayerCallback, this);
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::CreateAudioPlayer RegisterCallback fail. result=%d", result);
            break;
        }

        result = (*m_AudioPlayerObj)->GetInterface(m_AudioPlayerObj, SL_IID_VOLUME, &m_AudioPlayerVolume);
        if(result != SL_RESULT_SUCCESS)
        {
            LOGCATE("OpenSLRender::CreateAudioPlayer GetInterface fail. result=%d", result);
            break;
        }

    } while (false);

    return result;
}

//播放器的 callback
void OpenSLRender::AudioPlayerCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *context) {
    OpenSLRender *openSlRender = static_cast<OpenSLRender *>(context);
    openSlRender->HandleAudioFrameQueue();
}

void OpenSLRender::HandleAudioFrameQueue() {
    LOGCATE("OpenSLRender::HandleAudioFrameQueue QueueSize=%d", m_AudioFrameQueue.size());
    if (m_AudioPlayerPlay == nullptrreturn;

    //播放存放在音頻幀隊列中的數據
    AudioFrame *audioFrame = m_AudioFrameQueue.front();
    if (nullptr != audioFrame && m_AudioPlayerPlay) {
        SLresult result = (*m_BufferQueue)->Enqueue(m_BufferQueue, audioFrame->data, (SLuint32) audioFrame->dataSize);
        if (result == SL_RESULT_SUCCESS) {
            m_AudioFrameQueue.pop();
            delete audioFrame;
        }

    }
}

下一篇文章將會在本篇的基礎上,利用 OpenGL ES 增加音頻的可視化功能。

聯系與交流

技術交流獲取源碼可以添加我的微信:Byte-Flow

聯系我聯系我


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM