资料
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数据
特点
不支持:
(1)不支持版本低于 Android 2.3 (API 9) 的设备
(2)没有全部实现 OpenSL ES 定义的特性和功能
(4)不支持直接播放 DRM 或者 加密的内容
我认为 openSL ES 主要的工作就是读取数组数据后直接调用驱动的程序,让音箱发出声音.
状态机制:
https://blog.csdn.net/brandon2015/article/details/51814657
三种状态:
举例: 正在播放音乐的时候 来电话了,音乐会中断播放手机铃声.
代码.
新建FFmpegMusic.cpp 把ffmpeg相关的操作都放在这里面. 主要是三个部分.
头文件FFmpegMusic.h
//
// Created by liuml on 2018/9/11.
//
#include
#include
#include
#include
#include
//extern "C" 主要作用就是为了能够正确实现C++代码调用其他C语言代码 加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
//用于android 绘制图像的
#include
#include
#include
}
#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
#include
#include
#include
#include
#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
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);
}
例如:
创建一个播放器接口
sLresult=(*bgPlayerObject)->GetInterface(bgPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
OpenSL ES 的开发流程主要有如下6个步骤:
1、 创建接口对象
2、设置混音器
3、创建播放器(录音器)
4、设置缓冲队列和回调函数
5、设置播放状态
6、启动回调函数
上代码:
#include
#include
#include
//extern "C" 主要作用就是为了能够正确实现C++代码调用其他C语言代码 加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
//用于android 绘制图像的
#include
#include
#include "libswresample/swresample.h"
#include
#include
}
#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);