如何使用OpenSL ES在c4droid中进行声音编程
c4droid吧
全部回复
仅看楼主
level 2
叜駣 楼主
现在我们来研究如何使用OpenSL ES在c4droid中进行声音编程
阅读本文的前提是阅读并理解了native activity 教程:https://tieba.baidu.com/p/2650541001
或者能改动并编译c4droid自带的native activity实例
2013年10月21日 02点10分 1
level 2
叜駣 楼主
使用opensl es 的前提条件是在源代码中:
#include <SLES/OpenSLES.h>, 包含 opensl es的头文件
在gcc中:
-lOpenSLES 链接 opensl es的库文件
大休步骤如下:
1.创建声音引擎
2.创建声音播放器
3.设置播放缓冲
2013年10月21日 02点10分 2
就是 gcc参数要加入-IOpenSLES
2013年12月09日 12点12分
level 9
前排

2013年10月21日 02点10分 3
level 2
叜駣 楼主
opensl es 主要操作的是 介面(interface),大体来说有如下几个介面:
SLObjectItf : 对象介面,
SLEngineItf :引擎介面
SLPlayItf:播放介面
SLBufferQueueItf :缓冲队列介面
SLEffectSendItf:音效发送介面
SLMuteSoloItf : 声道关闭 /单声道介面
SLVolumeItf : 声量介面
除了 slCreateEngine 这一条函数是用来创建声音引擎对象介面以外,其它的所有操作都是通过介面的成员函数完成的,现在我们来研究如何初始化声音引擎
2013年10月21日 02点10分 4
interface 翻译成接口,不是界面啊,大神
2015年03月24日 04点03分
level 2
叜駣 楼主
初始化声音引擎需要3个介面,我们要将它们声明为全局变量:
SLObjectItf _aud;/* 声音引擎的对象介面 */
SLEngineItf _aud_eng;/* 声音引擎 */
SLObjectItf _aud_mix;/* 混音器对象介面 */
首先我们创建声音引擎的对象介面 :
slCreateEngine(&_aud, 0, NULL, 0, NULL, NULL);
创建之后的介面并不能立即使用,我们首先要通过介面的子函数实现(Realize)它:
(*_aud)->Realize(_aud, SL_BOOLEAN_FALSE);/* 通过_aud的Realize子函数实现声音引擎的对象介面 */
2013年10月21日 02点10分 5
level 2
叜駣 楼主
实现之后,我们从声音引擎的对象中抓取声音引擎,在这里我们通过介面的子函数抓取介面 (GetInterface),抓取对像是 _aud, 抓取类型是引擎,抓取后存放的内存位置是我们先前声明的引擎介面_aud_eng的指针位置。指令如下:
(*_aud)->GetInterface(_aud, SL_IID_ENGINE, &_aud_eng);
这样声音引擎就初始化了
2013年10月21日 02点10分 6
level 2
叜駣 楼主
第三步我们要做的是从声音引擎中创建 "输出混音器" 对象,这样我们才可以将声音播放出来。
注意,同 声音引擎的对象一样, 由于 "输出混音器" 是对象,我们必须创建后在实现(Realize)它。
由于"输出混音器"创建的同时需要两个音效参数,所以我们先准备好这两个参数:第一个参数类型是声音介面编号(SLInterfaceID),是一个数组,我们可以将其看成一个音效配置列表,在其中放置不同的音效编号。第二个参数是一个逻辑数组:是否强制实现声音介面编号中对应音效,如果某个音效的逻辑为真(SL_BOOLEAN_TRUE),则在无法实现该音效时视为"输出混音器"创建失败,否则将忽略该音效。因为安卓的声硬件对音效的支持不同,所以最好不要强制实现,所以我们在第二个数组中全部填写SL_BOOLEAN_FALSE 。
现在我们开始创建"输出混音器",环境回响(SL_IID_ENVIRONMENTALREVERB )是比较常见的音效,我们将非强制性的使用该音效。
SLInterfaceID effect[1] = {SL_IID_ENVIRONMENTALREVERB}; /*环境回响音效 */
SLboolean effect_bool[1] = {SL_BOOLEAN_FALSE}; /* 回响音效强制实现逻辑 */
(*_aud_eng)->CreateOutputMix(_aud_eng, &_aud_mix, 1, effect, effect_bool);/* 从声音引擎中创建“输出混音器” */
(*_aud_mix)->Realize(_aud_mix, SL_BOOLEAN_FALSE); /* 实现刚创建的“输出混音器” */
2013年10月21日 03点10分 7
level 9
好帖,顶 …
2013年10月21日 03点10分 8
level 2
叜駣 楼主
这样一来,所有的初始化工作就全部完成了。我们获得如下完整代码:
SLObjectItf _aud;/* 声音引擎对象 */
SLEngineItf _aud_eng;/* 声音引擎 */
SLObjectItf _aud_mix;/* 输出混音器对象 */
/* audio_init: 初始化opensl es */
int audio_init ()
{
SLInterfaceID effect[1] = {SL_IID_ENVIRONMENTALREVERB}; /* 音效 */
SLboolean effect_bool[1] = {SL_BOOLEAN_FALSE}; /*音效强制实现逻辑 */
slCreateEngine(&_aud, 0, NULL, 0, NULL, NULL);/* 创建声音引擎对象 */
(*_aud)->Realize(_aud, SL_BOOLEAN_FALSE);/* 实现声音引擎对象 */
(*_aud)->GetInterface(_aud, SL_IID_ENGINE, &_aud_eng);/* 从声音引擎对象中抓取声音引擎 */
(*_aud_eng)->CreateOutputMix(_aud_eng, &_aud_mix, 1, effect, effect_bool);/* 通过声音引擎创建输出混音器对象,并且非强制性的开启环境混响效果 */
(*_aud_mix)->Realize(_aud_mix, SL_BOOLEAN_FALSE);/* 实现混音器对象 */
}
2013年10月21日 03点10分 9
忘了写返回了[委屈]
2013年10月21日 04点10分
level 2
叜駣 楼主
初始化引擎之后, 我们还要创建一个播放器对象,就可以在播放器中输出声音了
播放器对象和输出混音器一样,是对象,创建后需要进行实现:
SLObjectItf _aud_plyobj; /*播放器对象 */
(*_aud_eng)->CreateAudioPlayer(_aud_eng, &_aud_plyobj, &sndsrc, &sndsnk, 3, ids, req);
/* 这是创建声音播放器对象aud_plyobj的函数 */
前两个参数分别把 声音引擎 和 播放器引擎的指针位置填上就可以了
我们真正需要填写的是后面4个参数 :
sndsrc: 声音数据源
sndsnk:声音输出池(data sink),也就是声音输出设备
3:功能清单数目,我们要让播放器支持3种功能
ids: 功能清单,我们要让播放器支持的功能
req:功能强制实现逻辑,类似9楼的强制逻辑
下面我们逐一来分析这5个参数
2013年10月21日 03点10分 10
“我们真正需要填写的是后面4个参数 :” 这里改成五个[委屈]
2013年10月21日 04点10分
level 2
叜駣 楼主
sndsrc是声源, 类型是SLDataSource, 是一个有两个成员的结构,两 个成员分别是:
pLocator: 数据定位器(DataLocator)的指针位置
PFormat:数据定位器指向数据的格式指针位置, 在这里我们使用pcm 声音格式
那么首先看这两种结构:
1:数据定位器 就是定位要播放声音数据的存放位置,分为4种:内存位置,输入/输出设备位置,缓冲区队列位置,和midi缓冲区队列位置。由于 我们可以灵活的对缓冲区队列进行安排并设置播放后回传函数,而且我们使用的是pcm 声音格式, 所以我们采取第三种数据定位器:
SLDataLocator_BufferQueue loc_bufq; /* 缓冲区队列定位器 */
2:数据格式,由于我们采用的是pcm 声音格式,所以我们使用:
SLDataFormat_PCM format_pcm; /* pcm 数据格式 */
接下来,我们要填写 "缓冲区队列定位器" 与 "pcm数据格式" 这两种结构的内容
2013年10月21日 04点10分 11
level 2
叜駣 楼主
我们要在SLDataLocator_BufferQueue类型变量 loc_bufq中填上的是
SLDataLocator_BufferQueue loc_bufq = { /* 缓冲区队列定位器 */
SL_DATALOCATOR_BUFFERQUEUE, /* 必须填成左边这样,表示数据定位器将定位缓冲区队列 */
2 /* 缓冲队列里的缓冲数目,这里我们填写2个缓冲 */
};
我们要在SLDataFormat_PCM类型变量 format_pcm中填上的是
SLDataFormat_PCM format_pcm = { /* pcm 数据格式 */
SL_DATAFORMAT_PCM, /* 必须填成左边这样,表示pcm 数据格式 */
1, /* 这里是声道数目,多声道数目将交错存放,而不是整体存放,我们使用单声道 */
SL_SAMPLINGRATE_8, /*采样率,我们使用 8kHz的采样率,质量不是很好 */
SL_PCMSAMPLEFORMAT_FIXED_16, /*采样大小,我们使用固定的 16bit采样 */
SL_PCMSAMPLEFORMAT_FIXED_16,/*容器大小,和采样大小保持一致就不会失真,我们采用 16bit容器*/ SL_SPEAKER_FRONT_CENTER, /* 声道映射掩码,在这里,我们将不用的声道映射到不同的扩音器中,在这里,由于我们的声音是单声道,所以我们将其映射到安卓设备的主扩音器, 也就是前中扩音器:SPEAKER_FRONT_CENTER */
SL_BYTEORDER_LITTLEENDIAN /* 尾序,我们用小尾序 */
};
2013年10月21日 04点10分 12
level 2
叜駣 楼主
这样,我们这完成了11楼中提到的 声源参数:
sndsrc
我们还要完成剩下的4个参数,也就是:
sndsnk:声音输出池(data sink),也就是声音输出设备
3:功能清单数目,我们要让播放器支持3种功能
ids: 功能清单,我们要让播放器支持的功能
req:功能强制实现逻辑,类似9楼的强制逻辑
从声音输出池开始,声音输出池和声源的成员差不多:
pLocator: 数据定位器(DataLocator)的指针位置
PFormat:数据定位器指向数据的格式指针位置
但声音输出池需要的是SLDataLocator_OutputMix 输出混音器数据定位器,而且不需要交代数据格式,所以只要填写输出混音器数据定位器就可以了:
SLDataLocator_OutputMix loc_outmix = { /* 输出混音器数据定位器 */
SL_DATALOCATOR_OUTPUTMIX, /* 必须填成左边这样,表示数据定位器将定位输出混音器 */
NULL /* 不需要交代数据格式 */
};
2013年10月21日 04点10分 13
SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, outputMixObject }; SLDataSink audioSnk = { &loc_outmix, NULL };
2013年12月09日 07点12分
level 2
叜駣 楼主
最后只有播放器功能清单要完成了,我们要求播放器具备3种功能 ,它们的编号如下:
SL_IID_BUFFERQUEUE:缓冲区队列功能(循环播放,随机播放...)
SL_IID_EFFECTSEND:音效功能(环境回响,均衡器...)
SL_IID_VOLUME:音量功能(音量调节,3d声源...)
这样来填写
SLInterfaceID ids[] = { /* 播放器功能列表 */
SL_IID_BUFFERQUEUE, /* 缓冲区队列功能 */
SL_IID_EFFECTSEND,/* 音效功能 */
SL_IID_VOLUME /* 音量功能 */
};
而强制逻辑和7楼提到的完全一样,但这3种功能是常用功能,一般的安卓设备都应具备,所以我们强制要求实现,实现不了则视为错误:
SLboolean req[] = {
SL_BOOLEAN_TRUE, /* 强制实现id中对应的 缓冲区队列功能 */
SL_BOOLEAN_TRUE, /* 强制实现id中对应的 音效功能 */
SL_BOOLEAN_TRUE /* 强制实现id中对应的 音量功能 */
};
2013年10月21日 05点10分 14
level 2
叜駣 楼主
整合 10楼到14楼的步骤,我们终于得到了一个声音播放器对象 _aud_plyobj[狂汗],最终指令如下:
(*_aud_eng)->CreateAudioPlayer(_aud_eng, &_aud_plyobj, &sndsrc, &sndsnk, 3, ids, req);
/* 从声音引擎_aud_eng中创建 声音播放器对象_aud_plyobj
同其它对象一样,我们来实现声音播放器对象_aud_plyobj:
(*_aud_plyobj)->Realize(_aud_plyobj, SL_BOOLEAN_FALSE); /*实现声音播放器对象 */
这样一来,声音播放器对象就创建好了。
在4楼,我们知道opensl es 主要操作的是介面(interface),所以我们要从播放器中获得一些介面,这样在对介面进行操作时,就能控制声音播放器。在这里,我们从声音播放器中获得4种不同类型的介面,首先进行全局变量声明:
SLPlayItf _aud_ply; /* 播放介面(播放,暂停...) */
SLBufferQueueItf _aud_buf; /*缓冲区介面 */
SLEffectSendItf _aud_bufefx; /*音效介面,在本播放器中将用于缓冲区 */
SLVolumeItf _aud_vol; /*音量介面 */
然后通过GetInterface成员函数从播放器对象中获得介面:
(*_aud_plyobj)->GetInterface(_aud_plyobj, SL_IID_PLAY, &_aud_ply); /*获得播放介面, 存至_aud_ply*/
(*_aud_plyobj)->GetInterface(_aud_plyobj, SL_IID_BUFFERQUEUE, &_aud_buf); /*获得缓冲区介面,存至 _aud_buf */
(*_aud_plyobj)->GetInterface(_aud_plyobj, SL_IID_EFFECTSEND, &_aud_bufefx); /*获得音效介面,存至 _aud_bufefx */
(*_aud_plyobj)->GetInterface(_aud_plyobj, SL_IID_VOLUME, &_aud_vol); /*获得音量介面,存至 _aud_vol */
从现在起,所有对上述4个介面的操作都将影响到播放器
2013年10月21日 05点10分 15
level 2
叜駣 楼主
为了真正实现缓冲区连续播放的功能,我们要为缓冲区介面_aud_buf注册一个回调函数:
(*_aud_buf)->RegisterCallback(_aud_buf, _newsnd_cb, NULL);
_newsnd_cb函数将在缓冲区播放完毕时自动执行,接下来我们研究这个函数怎么实现
2013年10月21日 05点10分 16
level 11
虽然没有用过,但是顶[真棒]
2013年10月21日 05点10分 17
level 2
叜駣 楼主
前面我们创建了引擎和播放器,如果我们命令播放器播放,会不会马上有声音呢?答案是否定的,因为我们根本没有提供数据[委屈]。。。。
其实我们自行设计_newsnd_cb回调函数将要实现的功能只有一个,就是为播放器提供将要播放的数据,这样播放器就会自动对其进行播放了
short *snd; /* 16 bit, 8kHz单声道 pcm 声音采样 */
unsigned sndlen; /*声音采样数据的大小*/
int num_snd; /* 该采样的剩余播放的次数 */
/* _newsnd_cb: 我们自己创建的缓冲区队列播放回调函数,第一项填写目标缓冲区介面,第二项填NULL即可 */
void _newsnd_cb(SLBufferQueueItf bq, void *context)
{
if (--num_snd > 0 && snd && sndlen > 0) /* 如果采样的剩余播放的次数大于0,声音数据有效,而且 声音数据的长度大于0 */
(*bq)->Enqueue(bq, snd, sndlen); /* 操作缓冲区队列介面,将长度为 sndlen的声音数据snd排进队列 */
}
这样,缓冲区队列没有声音可以播放时,将呼出上述回调函数,而该回调函数将检查剩余播放次数,并在有播放次数的情况下将 声音 排进队列,从而让缓冲区队列可以播放队列中的第一个声音,也就是刚排进去的声音。当然,由于这个函数是自定义的,灵活度很高,所以大家可以自由发挥,借助它实现随机播放,或是互联网 流媒体播放 。
2013年10月21日 06点10分 18
level 2
叜駣 楼主
现在 我们完成了 opensl es声音引擎和播放器创建,只需要操作_aud_ply 播放介面,即可启动播放器:
(*_aud_ply)->SetPlayState(_aud_ply, SL_PLAYSTATE_PLAYING); /* 将播放状态设为“正在播放” */
为了测试我们的播放器是否正常,我们尝试通过函数合成一个 时长为一秒种的 正弦波采样,将其播放5次:
#include "math.h"
short snd_sin[8000]; /* 时长为1秒的 16bit 单声道 正弦波采样 */
void create_sine_wave(void)
{
int i;
for (i = 0; i < 8000; ++i) /* 声音时长1秒,所以我们创建8000个单声道 采样 */
snd_sin[i] = sinf(i*180/M_PI)*32767; /* 通过正弦函数创建单个采样值 */
}
这样我们就能通过输出声音是否为正弦波来判断播放器是否正常了
2013年10月21日 06点10分 19
另外还要将num_snd,设为5
2013年10月21日 06点10分
level 2
叜駣 楼主
最后是 将正弦波播放5次的 演示程序:
[无效] http://pan.baidu.com/s/1uw672
谢谢大家的支持
2013年10月21日 06点10分 20
1 2 尾页