天天看點

FFmpeg開發(三):音頻播放器的實作

作者:音視訊流媒體技術

OpenSL基礎

OpenSLES接口可以直接載Native層處理音頻資料,減少了Java層到Native層在采集、播放和編解碼過程中的資料拷貝。

OpenSLES文檔:OpenSL_ES_Specification1.1.pdf

優點

  1. C語言接口,使用NDK,翻邊深度優化,例如NEON優化。
  2. 沒有垃圾回收機制,需要自己實作垃圾回收
  3. 支援PCM資料采集
  4. 支援PCM資料的播放

Objects 和 Interfaces

  1. 官方為每一種Objects都定義了一系列Interface
  2. 先通過GetInterface拿到接口,再通過Interface通路功能函數
  3. 通過Interface ID可以擷取對應的功能接口

狀态機制

文檔中狀态機制圖:

FFmpeg開發(三):音頻播放器的實作

每個對象都有一些最基礎的方法:Realize、Resume、GetState、Destory

如上圖所示,隻有對象realize之後系統才會正确得配置設定資源。

常用結構體

1.Engine Object 和 SLEngineltf(管理接口,用于建立其他對象)

2.Media Object(多媒體抽象功能)

3.Data Source 和Data Sink(輸入源和輸出源)

相關學習資料推薦,點選下方連結免費報名,先碼住不迷路~】

【免費分享】音視訊學習資料包、大廠面試題、技術視訊和學習路線圖,資料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以點選加群免費領取~

FFmpeg開發(三):音頻播放器的實作

FFmpeg解碼音頻資料

FFmpeg開發(三):音頻播放器的實作

一般的處理方式為:利用 FFmpeg 對一個 Mp4 檔案的音頻流進行解碼,然後使用 libswresample 将解碼後的 PCM 音頻資料轉換為目标格式的資料,重采樣來確定音頻采樣率和裝置驅動采樣率一緻,使音頻正确播放。最後利用 OpenSLES 進行播放。

播放源代碼:

//建立引擎對象
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    if(result != SL_RESULT_SUCCESS)
    {
        return false;
    }

    //初始化引擎對象
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    if(result != SL_RESULT_SUCCESS)
    {
        return false;
    }

    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
    if(result != SL_RESULT_SUCCESS)
    {
        return false;
    }

    //建立并初始化混音器
    SLInterfaceID ids1[1] = {SL_IID_OUTPUTMIX};
    SLboolean reqs1[1] = {SL_BOOLEAN_FALSE};
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, ids1, reqs1);
    if(result != SL_RESULT_SUCCESS)
    {
        return false;
    }

    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    if(result != SL_RESULT_SUCCESS)
    {
        return false;
    }

    // 建立并初始化播放器
    SLDataLocator_AndroidSimpleBufferQueue bufferQueue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
    SLDataFormat_PCM pcmFormat = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
                                  SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN};

    SLDataSource audioSrc = {&bufferQueue, &pcmFormat};

    SLDataLocator_OutputMix locOutputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSink = {&locOutputMix, NULL};

    SLInterfaceID ids2[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
    SLboolean reqs2[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE};

    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &audioSrc, &audioSink, 2, ids2, reqs2);
    if(result != SL_RESULT_SUCCESS)
    {
        return false;
    }

    result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
    if(result != SL_RESULT_SUCCESS)
    {
        return false;
    }

    result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
    if(result != SL_RESULT_SUCCESS)
    {
        return false;
    }

    result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueue);
    if(result != SL_RESULT_SUCCESS)
    {
        return false;
    }

    result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, audioCallback, this);
    if(result != SL_RESULT_SUCCESS)
    {
        return false;
    }

//    result = (*playerObject)->GetInterface(playerObject, SL_IID_VOLUME, &bqPlayerVolume);
//    if(result != SL_RESULT_SUCCESS)
//    {
//        return false;
//    }

    result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PAUSED);
    if(result != SL_RESULT_SUCCESS)
    {
        return false;
    }

    (*playerBufferQueue)->Enqueue(playerBufferQueue, emptyBuffer, EMPTY_BUFFER_SAMPLES * 2 * sizeof(int16_t));

    return true;
           

RegisterCallback(playerBufferQueue, audioCallback, this);用來注冊回調函數,回調函數原理:

void OpenSLESPlayer::processAudio() {
    //注意這個是另外一條采集線程回調,
    unique_lock<mutex> providerLock(providerMu);
    if(provider != NULL)
    {
        AudioFrame *data = provider->getAudioFrame();
        if(data == NULL)
        {
            LOGE("get a NULL audio frame");
            (*playerBufferQueue)->Enqueue(playerBufferQueue, emptyBuffer, EMPTY_BUFFER_SAMPLES * 2 * sizeof(int16_t));
        } else
        {
//            LOGD("audio frame sample count = %d, pts = %ld", data->sampleCount, data->pts);
            memcpy(emptyBuffer, data->data, data->sampleCount * 2 * sizeof(int16_t));
            // 取完資料,需要調用Enqueue觸發下一次資料回調
            (*playerBufferQueue)->Enqueue(playerBufferQueue, emptyBuffer, data->sampleCount * 2 * sizeof(int16_t));
            provider->putBackUsed(data);
        }
    }
    providerLock.unlock();
}
           

原文連結:FFmpeg開發(三):音頻播放器的實作_comochris的部落格-CSDN部落格