OpenSL基礎
OpenSLES接口可以直接載Native層處理音頻資料,減少了Java層到Native層在采集、播放和編解碼過程中的資料拷貝。
OpenSLES文檔:OpenSL_ES_Specification1.1.pdf
優點
- C語言接口,使用NDK,翻邊深度優化,例如NEON優化。
- 沒有垃圾回收機制,需要自己實作垃圾回收
- 支援PCM資料采集
- 支援PCM資料的播放
Objects 和 Interfaces
- 官方為每一種Objects都定義了一系列Interface
- 先通過GetInterface拿到接口,再通過Interface通路功能函數
- 通過Interface ID可以擷取對應的功能接口
狀态機制
文檔中狀态機制圖:
每個對象都有一些最基礎的方法: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 對一個 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部落格