資料
https://blog.csdn.net/ywl5320/article/details/78503768
http://www.cnblogs.com/renhui/p/9567332.html
AndroidStudio使用OpenSL ES官方配置文檔
https://developer.android.com/ndk/guides/audio/getting-started.html
Open SL ES 和Open GL ES
一個是音頻 一個是視頻
OpenSLES官網
https://www.khronos.org/opensles/
官方文檔
https://www.khronos.org/files/opensl-es-1-1-quick-reference.pdf
原生播放
OpenSL ES
OpenSL ES 全稱是:Open Sound Library for Embedded Systems,
是一套無授權費、跨平臺、針對嵌入式系統精心優化的硬件音頻加速API
它爲嵌入式移動多媒體設備上的本地應用程序開發者提供標準化, 高性能, 低響應時間的音頻功能實現方法
,並實現軟硬件音頻性能的直接跨平臺部署,降低執行難度,促進高級音頻市場的發展
OpenSL ES 可以錄音、播放音頻URI和PCM數據,後面將編寫 OpenSL ES 相關代碼:
1、OpenSL ES 音頻URI 播放
2、OpenSL ES 音頻PCM數據 播放
3、OpenSL ES 錄音 PCM數據
特點
- C 語言接口,兼容 C++,需要在 NDK 下開發,能更好地集成在 native 應用中
- 運行於 native 層,需要自己管理資源的申請與釋放,沒有 Dalvik 虛擬機的垃圾回收機制
- 支持 PCM 數據的播放,支持的配置:8bit/16bit 位寬,單通道/雙通道,小端模式,採樣率(8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 Hz)
- 支持播放的音頻數據來源:res 文件夾下的音頻、assets 文件夾下的音頻、sdcard 目錄下的音頻、在線網絡音頻、代碼中定義的音頻二進制數據等等
不支持:
(1)不支持版本低於 Android 2.3 (API 9) 的設備
(2)沒有全部實現 OpenSL ES 定義的特性和功能
(4)不支持直接播放 DRM 或者 加密的內容
我認爲 openSL ES 主要的工作就是讀取數組數據後直接調用驅動的程序,讓音箱發出聲音.
- openSL ES 的so 庫在 sdk\ndk-bundle\platforms\android-9\arch-arm\usr\lib\libOpenSLES.so
狀態機制:
https://blog.csdn.net/brandon2015/article/details/51814657
三種狀態:
- unrealized初始化狀態
- realized 實現狀態. 能夠用的只能是這種狀態
- suspended 暫停狀態
舉例: 正在播放音樂的時候 來電話了,音樂會中斷播放手機鈴聲.
代碼.
新建FFmpegMusic.cpp 把ffmpeg相關的操作都放在這裏面. 主要是三個部分.
- 初始化 包括註冊組件,初始化AVFormatContext,解碼器AVCodecContext上下文,解碼器AVCodec等等.
- 獲取pcm數據
- 釋放指針 回收內存
頭文件FFmpegMusic.h
//
// Created by liuml on 2018/9/11.
//
#include <jni.h>
#include <string>
#include <jni.h>
#include <string>
#include <android/log.h>
//extern "C" 主要作用就是爲了能夠正確實現C++代碼調用其他C語言代碼 加上extern "C"後,會指示編譯器這部分代碼按C語言的進行編譯,而不是C++的。
extern "C" {
//編碼
#include "libavcodec/avcodec.h"
//封裝格式處理
#include "libavformat/avformat.h"
//像素處理
#include "libswscale/swscale.h"
//用於android 繪製圖像的
#include <android/native_window_jni.h>
#include <unistd.h>
#include <libswresample/swresample.h>
}
#ifndef FFMPEGDEMO_FFMPEGMUSIC_H
#define FFMPEGDEMO_FFMPEGMUSIC_H
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"jnilib",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"jnilib",FORMAT,##__VA_ARGS__);
#endif //FFMPEGDEMO_FFMPEGMUSIC_H
int createFFmpge();
int getPcm();
void realseFFmpeg();
實現 FFmpegMusic.cpp
//
// Created by liuml on 2018/9/11.
//
#include <jni.h>
#include <string>
#include <jni.h>
#include <string>
#include <android/log.h>
#include "FFmpegMusic.h"
AVFormatContext *pContext;
AVCodecContext *pCodecCtx;
AVCodec *pCodex;
AVPacket *packet;
AVFrame *frame;
SwrContext *swrContext;
uint8_t *out_buffer;
int out_sample_rate;
int out_channer_nb = -1;
//找到視頻流
int audio_stream_ids = -1;
/**
* 初始化. openSL ES 調用這個函數
* @param rate 採樣率
* @param channel 通道數
* @return
*/
int createFFmpeg(int *rate, int *channel) {
//前面和之前一樣 獲取輸入的信息 但是現在是找到音頻的AVMEDIA_TYPE_AUDIO
// TODO
//無論編碼還是解碼 都要調用這個 註冊各大組件
av_register_all();
//獲取AVFormatContext 比特率 時長 文件路徑 流的信息(nustream) 都封裝在這裏面
pContext = avformat_alloc_context();
char *input = "/sdcard/input.mp3";
//AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options
//上下文 文件名 打開文件格式 獲取信息(AVDictionary) 凡是AVDictionary字典 都是獲取視頻文件信息
if (avformat_open_input(&pContext, input, NULL, NULL) < 0) {
LOGE("打開失敗");
return -1;
}
//給nbstram填充信息
if (avformat_find_stream_info(pContext, NULL) < 0) {
LOGE("獲取信息失敗");
return -1;
}
for (int i = 0; i < pContext->nb_streams; ++i) {
LOGE("循環 %d", i);
//如果填充的視頻流信息 -> 編解碼,解碼器 -> 流的類型 == 視頻的類型
//codec 每一個流 對應的解碼上下文 codec_type 流的類型
if (pContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
audio_stream_ids = i;
LOGE("找到音頻id %d", i);
break;
}
}
//解碼器. mp3的解碼器 解碼器
// ---------- 獲取到解碼器上下文 獲取視音頻解碼器
pCodecCtx = pContext->streams[audio_stream_ids]->codec;
LOGE("獲取解碼器上下文");
//----------解碼器
pCodex = avcodec_find_decoder(pCodecCtx->codec_id);
LOGE("獲取解碼器");
//打開解碼器 爲什麼avcodec_open2 版本升級的原因
if (avcodec_open2(pCodecCtx, pCodex, NULL) < 0) {
LOGE("解碼失敗");
return -1;
}
//得到解封裝 讀取 解封每一幀 讀取每一幀的壓縮數據
//初始化avpacket 分配內存 FFMpeg 沒有自動分配內存 必須手動分匹配手動釋放 不過有分配函數
packet = (AVPacket *) av_malloc(sizeof(AVPacket));
//初始化AVFrame
frame = av_frame_alloc();
//mp3 裏面所包含的編碼格式 轉化成pcm
//#include <libswresample/swresample.h>
swrContext = swr_alloc();//獲取轉換的上下文
int frame_count = 0;
//AVFormatContext *s, AVPacket *pkt 上下文,avpacket 是數據包的意思
//packet 入參
int got_frame;
int length = 0;
//定義緩衝區輸出的 需要多大的採樣率 採樣 44100 多少個字節. 雙通道需要乘以2
out_buffer = static_cast<uint8_t *>(av_malloc(44100 * 2));//一秒的緩衝區數量
/**
* struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
* //輸出的
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,
//偏移量
int log_offset, void *log_ctx);
*/
//輸出聲道佈局
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;//立體聲
//輸出採樣位數 16位 現在基本都是16位 位數越高 聲音越清晰
enum AVSampleFormat out_formart = AV_SAMPLE_FMT_S16;
//輸出的採樣率 必須與輸入的相同
out_sample_rate = pCodecCtx->sample_rate;
//https://blog.csdn.net/explorer_day/article/details/76332556 文檔
//swr_alloc_set_opts將PCM源文件的採樣格式轉換爲自己希望的採樣格式
swr_alloc_set_opts(swrContext, out_ch_layout, out_formart, out_sample_rate,
pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate,
0, NULL);
//初始化轉換器
swr_init(swrContext);
//求出通道數
out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
*rate = pCodecCtx->sample_rate;//通過解析出來的 採樣率 賦值給調用的openSL ES 的採樣率
*channel = pCodecCtx->channels;//同理賦值 通道數.
return 0 ;
}
/**
* 獲取pcm數據
* @param pcm 二級指針 可以修改(緩衝區數組的地址)
* @param size 大小
* @return
*/
int getPcm(void **pcm,size_t *pcm_size) {
int got_frame;
int count = 0;
//讀取frame
while (av_read_frame(pContext, packet) >= 0) {
if (packet->stream_index == audio_stream_ids) {
//解碼 現在編碼格式frame 需要轉化成pcm
int ret = avcodec_decode_audio4(pCodecCtx, frame, &got_frame, packet);
LOGE("正在解碼 %d", count++);
if (ret < 0) {
LOGE("解碼完成");
}
//解碼一幀
if (got_frame > 0) {
//真正的解碼
LOGE("開始解碼");
//轉換得到out_buffer
swr_convert(swrContext, &out_buffer, 44100 * 2,
(const uint8_t **) (frame->data), frame->nb_samples);
LOGE("轉換得到out_buffer");
//求緩衝區實際的大小 通道數 frame->nb_samples 採樣的點
int size = av_samples_get_buffer_size(NULL, out_channer_nb, frame->nb_samples,
AV_SAMPLE_FMT_S16, 1);
LOGE("求緩衝區實際的大小 %d", size);
*pcm = out_buffer;
*pcm_size = size;
break;
}
}
}
LOGE("完成")
}
/**
* 釋放
*/
void releaseFFmpeg() {
//回收
av_free_packet(packet);
av_free(out_buffer);
av_frame_free(&frame);
swr_free(&swrContext);
avcodec_close(pCodecCtx);
avformat_free_context(pContext);
}
- 這裏有個點 都是通過入參從而改變傳遞過來的參數.
一個概念 調用OpenSL ES 的方法 最終調用的是他的一個接口
例如:
創建一個播放器接口
sLresult=(*bgPlayerObject)->GetInterface(bgPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
OpenSL ES 的開發流程主要有如下6個步驟:
1、 創建接口對象
2、設置混音器
3、創建播放器(錄音器)
4、設置緩衝隊列和回調函數
5、設置播放狀態
6、啓動回調函數
上代碼:
#include <jni.h>
#include <string>
#include <android/log.h>
//extern "C" 主要作用就是爲了能夠正確實現C++代碼調用其他C語言代碼 加上extern "C"後,會指示編譯器這部分代碼按C語言的進行編譯,而不是C++的。
extern "C" {
//編碼
#include "libavcodec/avcodec.h"
//封裝格式處理
#include "libavformat/avformat.h"
//像素處理
#include "libswscale/swscale.h"
//用於android 繪製圖像的
#include <android/native_window_jni.h>
#include <unistd.h>
#include "libswresample/swresample.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
}
#include "FFmpegMusic.h"
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"jnilib",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"jnilib",FORMAT,##__VA_ARGS__);
SLObjectItf engineObject = NULL;//用SLObjectItf聲明引擎接口對象
SLEngineItf engineEngine = NULL;//聲明具體的引擎對象實例
//混音器
SLObjectItf outputMixObject = NULL; //聲明混音器接口對象
SLEnvironmentalReverbItf outputMixEnvironmentalReverbItf = NULL;//環境混響接口
SLEnvironmentalReverbSettings settings = SL_I3DL2_ENVIRONMENT_PRESET_DEFAULT;//設置默認環境
//播放器
SLObjectItf bqPlayerObject;
//播放器接口
SLPlayItf bqPlayerPlay;
//緩衝器隊列
SLAndroidSimpleBufferQueueItf bqPalyerQueue;
//音量對象
SLVolumeItf bqPlayerVolume;
//buffer數據
size_t bufferSize = 0;
void *buffer;
//當喇叭播放完聲音回調此函數,添加pcm數據到緩衝區
void bqPlayerCallBack(SLAndroidSimpleBufferQueueItf bq, void *context) {
bufferSize = 0;
getPcm(&buffer, &bufferSize);//獲取pcm數據
if (NULL != buffer && 0 != bufferSize) {
SLresult result;//結果
//播放幀
result = (*bqPalyerQueue)->Enqueue(bqPalyerQueue, buffer, bufferSize);
LOGE("回調 bqPlayerCallback : %d", result);
} else {
LOGE("獲取PCM失敗")
}
}
//openSL ES 播放音頻
extern "C"
JNIEXPORT void JNICALL
Java_androidrn_ffmpegdemo_AudioPlayer_OpenSLEsPlay(JNIEnv *env, jobject instance) {
SLresult sLresult;
//初始化一個引擎
sLresult = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
LOGE("初始化引擎 %d", sLresult);
//改變成Realize狀態,參數false 代表非異步,就是同步
sLresult = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
LOGE("引擎改變成Realize狀態 %d", sLresult);
//獲取到引擎接口 利用GetInterface 調用函數
sLresult = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
LOGE("獲取到引擎接口 %d", sLresult);
// LOGE("引擎地址 &p", engineEngine);
// ====混音器 設置 開始=====
sLresult = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
LOGE("混音器 設置 %d", sLresult);
//同樣切換狀態 和上面同樣的套路
sLresult = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
LOGE("混音器 同樣切換狀態 %d", sLresult);
//設置環境混響
sLresult = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
&outputMixEnvironmentalReverbItf);
LOGE("設置環境混響 %d", sLresult);
//每個函數都會返回sLresult 用於判斷是否調用成功
if (SL_RESULT_SUCCESS == sLresult) {
LOGE("環境混響成功");
//設置環境
(*outputMixEnvironmentalReverbItf)->SetEnvironmentalReverbProperties(
outputMixEnvironmentalReverbItf, &settings);
} else {
LOGE("環境混響設置不成功 %d", sLresult);
// return;
}
//===混音器設置結束 ====
//初始化ffmpeg
int rate;
int channers;
createFFmpeg(&rate, &channers);
LOGE("初始化ffmpeg");
//命名規則 都是SL 開頭 比如像這個 把前面SLDataLocator 打出來了即可
//pLocator -> SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE 這個是讀取本地的 如果是網絡的 則是 SL_DATALOCATOR_IODEVICE
SLDataLocator_AndroidBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
2};
//SLDataSource的參數 *pAudioSrc
/**
* void *pLocator; 緩衝區隊列 SLDataLocator_BufferQueue
void *pFormat; 緩衝區隊列裝載什麼數據格式 比如PCM 採樣率 通道數 等
*/
//pFormat 參數
/*
* SLuint32 formatType; 源數據類型 PCM
SLuint32 numChannels; 通道數
SLuint32 samplesPerSec; 開始的採樣率
SLuint32 bitsPerSample; 採樣位數
SLuint32 containerSize; 包含位數
SLuint32 channelMask; 聲道類型 立體聲 左聲道 右聲道 環繞聲
SLuint32 endianness; end結束標誌位
*/
SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM, channers, SL_SAMPLINGRATE_44_1,
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
SL_BYTEORDER_LITTLEENDIAN};
// SLDataLocator_AndroidBufferQueue
// SLDataLocator_AndroidSimpleBufferQueue
//==CreateAudioPlayer 參數3
SLDataSource slDataSource = {&android_queue, &pcm};
// 輸出管道
SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
//混音器和播放器關聯起來 鏈接方式
// void *pLocator; 參數
// void *pFormat;
//和上面一樣的參數
SLDataSink audioSnk = {&outputMix, NULL};
//聲音增大減小 音量 調節輸出
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};
LOGE("引擎 %p",engineEngine);
/**
* SLEngineItf self, 引擎
SLObjectItf * pPlayer, 播放器
SLDataSource *pAudioSrc, 數據源
SLDataSink *pAudioSnk, 混音器和播放器關聯起來 鏈接方式
SLuint32 numInterfaces, 數量
const SLInterfaceID * pInterfaceIds, 用於聲音增大減小
const SLboolean * pInterfaceRequired 和上面對應 如果是true 證明要被實體化 這裏是3個boolean值
*/
//得到播放器
sLresult = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &slDataSource,
&audioSnk, 3,
ids, req);
LOGE(" 播放器 sLresult %d ", sLresult);
//bqPlayerObject 修改狀態
sLresult = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
LOGE(" 播放器 修改狀態 sLresult %d ", sLresult);
// 得到接口後調用 獲取Player接口 bqPlayerPlay
sLresult = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
LOGE(" 得到接口後調用 獲取Player接口 bqPlayerPlay sLresult %d ", sLresult);
// 註冊回調緩衝區 //獲取緩衝隊列接口
sLresult = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
&bqPalyerQueue);
LOGE(" 註冊回調緩衝區 //獲取緩衝隊列接口 sLresult %d ", sLresult);
//緩衝區接口回調 第二個參數是個函數
sLresult = (*bqPalyerQueue)->RegisterCallback(bqPalyerQueue, bqPlayerCallBack,
NULL);
LOGE(" 緩衝區接口回調 sLresult %d ", sLresult);
//獲取音量接口
sLresult = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
LOGE(" 獲取音量接口 sLresult %d ", sLresult);
//獲取播放狀態接口 設置播放狀態
sLresult = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
LOGE(" 獲取播放狀態接口 sLresult %d ", sLresult);
//播放第一幀
bqPlayerCallBack(bqPalyerQueue, NULL);
}
//openSL ES 停止音頻
extern "C"
JNIEXPORT void JNICALL
Java_androidrn_ffmpegdemo_AudioPlayer_OpenSlESStop(JNIEnv *env, jobject instance) {
// destroy buffer queue audio player object, and invalidate all associated interfaces
if (bqPlayerObject != NULL) {
(*bqPlayerObject)->Destroy(bqPlayerObject);
bqPlayerObject = NULL;
bqPlayerPlay = NULL;
bqPalyerQueue = NULL;
bqPlayerVolume = NULL;
}
// destroy output mix object, and invalidate all associated interfaces
if (outputMixObject != NULL) {
(*outputMixObject)->Destroy(outputMixObject);
outputMixObject = NULL;
outputMixEnvironmentalReverbItf = NULL;
}
// destroy engine object, and invalidate all associated interfaces
if (engineObject != NULL) {
(*engineObject)->Destroy(engineObject);
engineObject = NULL;
engineEngine = NULL;
}
// 釋放FFmpeg解碼器相關資源
releaseFFmpeg();
}
發現個問題 混響設置失敗了一樣能播放
解決:
// ====混音器 設置 開始=====
const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};
const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};
sLresult =(*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);