因为之前在原公司上班的时候,用过openAL来播放PCM实时数据流.现在换了新公司又要求我用自己的方法写个音频播放器.我就选用这openAL
H-,RzL/
但是这次使用openAL的时候,就没之前那么顺了,哎,说多了都是泪啊~~~附上自己写的部分算是重要的代码,给大家分享一下,也好帮助
:J}@*>c
新手少走些误区,或者是能够快速定位究竟是错在哪里。代码如下:
pQ Y.MZSA
IFY,j8~q
//openAL.h
61J01(+|
#import <OpenAL/al.h>
VoJelyzh
#import <OpenAL/alc.h>
P2Ja*!K]
@interface OpenAlDecode : NSObject
1=t\|Th-
{
,1YnWy *
ALCcontext *m_Context; //内容,相当于给音频播放器提供一个环境描述
\k|ZbCWg
ALCdevice *m_Device; //硬件,获取电脑或者ios设备上的硬件,提供支持
N|ut^X+|\
ALuint m_sourceID; //音源,相当于一个ID,用来标识音源
J dDP
Vhww- A
NSCondition *m_DecodeLock;
k'BLos 1W
&:` 7
}
IPl>bD~=p
nV0"q|0K;
-(BOOL)initOpenAl;
M#qZ0JT4
-(void)playSound;
gi7As$+E
-(void)stopSound;
|j i}LWcD
-(void)openAudio:(unsigned char*)pBuffer length:(UInt32)pLength;
X~Li`
-(void)clearOpenAL;
N+?kFob
@end
4>gMe3]0
WbS2w @8
//openAL.m
eD, 7gC-
15r<n
-(BOOL)initOpenAl
1px:(8]{
{
.JpYZ |
if (m_Device ==nil)
gtHk1 9
{
NdQXQa?,
m_Device = alcOpenDevice(NULL); //参数为NULL , 让ALC 使用默认设备
x c-= ;|s
}
f#l/N%VoBZ
&Sc}3UI/F
if (m_Device==nil)
I@ch 5vl4
{
~`{HWmah
return NO;
,h1r6&MEY
}
Ox&g#,@h
if (m_Context==nil)
~ S R:,R
{
%t\`20-1<
if (m_Device)
?#\?&uFJ}
{
0=s+bo1
m_Context =alcCreateContext(m_Device, NULL); //与初始化device是同样的道理
oj<.axA,
alcMakeContextCurrent(m_Context);
KTk%N p
}
_-Aw`<_*-
}
{P(Z{9u%
LIVVb"V|,
alGenSources(1, &m_sourceID); //初始化音源ID
HF5aU :M
alSourcei(m_sourceID, AL_LOOPING, AL_FALSE); // 设置音频播放是否为循环播放,AL_FALSE是不循环
'&3Sl?E
alSourcef(m_sourceID, AL_SOURCE_TYPE, AL_STREAMING); // 设置声音数据为流试,(openAL 针对PCM格式数据流)
;'pEzz?k"
alSourcef(m_sourceID, AL_GAIN, 1.0f); //设置音量大小,1.0f表示最大音量。openAL动态调节音量大小就用这个方法
wLU w'Ai
// alDopplerVelocity(1.0); //多普勒效应,这属于高级范畴,不是做游戏开发,对音质没有苛刻要求的话,一般无需设置
[/*85 4
// alDopplerFactor(1.0); //同上
qrNW\ME
alSpeedOfSound(1.0); //设置声音的播放速度
qB7.LR* '
1(!QutEb
m_DecodeLock =[[NSCondition alloc] init];
'mug,jM
if (m_Context==nil)
m5zP|s1`['
{
Qkk~{OuC
return NO;
J C1T033 r
}
Os8]iNvW\
counts =0;
S_6`.@B}
t3Q;1#Zf
R8\y|p#c
VPn #O
/*这里有我注释掉的监测方法,alGetError()用来监测环境搭建过程中是否有错误
t22BO@gt74
在这里,可以说是是否出错都可以,为什么这样说呢? 因为运行到这里之前,
^=D77 jS
如果加上了alSourcef(m_sourceID, AL_SOURCE_TYPE, AL_STREAMING);
c1CP1 2
这个方法,这里就会监测到错误,注释掉这个方法就不会有错误。(具体为什么,我
/43DR;4
也不知道~~~,知道的大神麻烦说下~~~),加上这个方法,在这里监测出错误
N(}7M~m>
对之后播放声音无影响,所以,这里可以注释掉下面的alGetError()。
\9i.dF
*/
?GD{}f33
// ALenum error;
cPI #XPM=
// if ((error=alGetError())!=AL_NO_ERROR)
R`ZU'|
// {
aiw~4ix
// return NO;
VuJth
// }
[CRy>hfV
return YES;
Si*Pi
}
p4Vw`i+DnH
=[t([DG
//清楚已存在的buffer,这个函数其实没什么的,就只是用来清空缓存而已,我只是多一步将播放声音放到这个函数里。
w*#k&N[X
-(BOOL)updataQueueBuffer
.XURI#b
{
MY9?957F
ALint state;
qJ"dkT*
int processed ,queued;
*-_` xe
{ j&|Em]
alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
>V87#E
if (state !=AL_PLAYING)
DWk'6;e4j
{
vT%rg r
[self playSound];
}%$9nq3
return NO;
x gaN0!
}
)/bt/,M&}
;5"r)F+P
alGetSourcei(m_sourceID, AL_BUFFERS_PROCESSED, &processed);
TDtk'=;
alGetSourcei(m_sourceID, AL_BUFFERS_QUEUED, &queued);
4=F~^Xc`
"H%TOk7l
'D\(p,(Mt
NSLog(@"Processed = %d\n", processed);
L V33vy
NSLog(@"Queued = %d\n", queued);
O%(:8nIgZ
while (processed--)
[ "J
{
xE;fM\7pu
ALuint buffer;
a2Q9tt>Q
alSourceUnqueueBuffers(m_sourceID, 1, &buffer);
)d C%g=dtc
alDeleteBuffers(1, &buffer);
A{9Hm:)
}
NqhRJa63
return YES;
6n%^ U2H/-
}
q* Ns]f'a
{U^mL6=&v
//这个函数就是比较重要的函数了, 将收到的pcm数据放到缓存器中,再拿出来播放
,a^_ ~(C
-(void)openAudio:(unsigned char*)pBuffer length:(UInt32)pLength
HEjV7g0E
{
zMtK_ccQ
k[Uc _=
[m_DecodeLock lock];
`j4ukOnG
<po(7XB
ALenum error =AL_NO_ERROR;
YaSwn3i/@S
if ((error =alGetError())!=AL_NO_ERROR)
|4X:> Ut]
{
x*BfR j
[m_DecodeLock unlock];
rCYNdfdpp
return ;
)F4er '
}
1vl~[
if (pBuffer ==NULL)
a5Xr"-
{
QnaMj Dh$6
return ;
fcJ#\-+E
}
cQ3Dk<GZ
DNki xE*
[self updataQueueBuffer]; //在这里调用了刚才说的清除缓存buffer函数,也附加声音播放
.o|Gk 5)
UvQxtT]
if ((error =alGetError())!=AL_NO_ERROR)
VD36ce9
{
CzNSJ VE5
[m_DecodeLock unlock];
ih ,8'D4
return ;
8.Y6r
}
/Pg66H#RUf
N1#*~/sXh
ALuint bufferID =0; //存储声音数据,建立一个pcm数据存储器,初始化一块区域用来保存声音数据
!WVF{L,/I
alGenBuffers(1, &bufferID);
c/T]=S[
er l_Gg
if ((error = alGetError())!=AL_NO_ERROR)
A[oi?.D
{
fKrOz! b
NSLog(@"Create buffer failed");
4~G9._
[m_DecodeLock unlock];
oh& P Q{
return;
z${B|
}
w? !@fu
#FuOTBNvB
NSData *data =[NSData dataWithBytes:pBuffer length:pLength]; //将PCM格式数据转换成NSData ,
\8_&@uLm
alBufferData(bufferID, AL_FORMAT_MONO16, (char *)[data bytes] , (ALsizei)[data length], 8000 ); //将转好的NSData存放到之前初始化好的一块buffer区域中并设置好相应的播放格式 ,(本人使用的播放格式: 单声道16bit(AL_FORMAT_MONO16) , 采样率 8000HZ)
TmzEZ<} &7
P_&2HA,I
if ((error =alGetError())!=AL_NO_ERROR)
3TtnLay.k
{
/:U\U_j
NSLog(@"create bufferData failed");
p_P'2mf
[m_DecodeLock unlock];
YdE$G>&em
return;
dLQ!hKD~
}
-fG;`N5U
l)XzU&Sc~
//添加到缓冲区
xQaN\):^8
alSourceQueueBuffers(m_sourceID, 1, &bufferID);
r%_)7Wk*
eT}c_h)
if ((error =alGetError())!=AL_NO_ERROR)
G'{4ec0<{
{
RE *UIh*O
NSLog(@"add buffer to queue failed");
G.T}^ xHmL
[m_DecodeLock unlock];
Q3z-v&^E9
return;
T-|z18|!
}
,pf<"^li
if ((error=alGetError())!=AL_NO_ERROR)
"MK:y[+*
{
)ALf!E%{
NSLog(@"play failed");
.D)'ZY
alDeleteBuffers(1, &bufferID);
Ej6vGC.,
[m_DecodeLock unlock];
H$1R\rE`
return;
oqUtW3y
}
4HkOg)a
Z4E:Z}~''
[m_DecodeLock unlock];
10}\7p8
&#;UKk~)Of
}
bnUd !/;
-(void)playSound
;'R{b$B;|
{
)ZNH/9e/
ALint state;
{!'AR`|
alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
0Ba-VY.H
if (state != AL_PLAYING)
oD~VK,.
{
2hmV 1gj
alSourcePlay(m_sourceID);
] hL 1qS
}
Td"f(&Hk&
}
X`^9a5<"
HPr5mWs:
-(void)stopSound
K.b-8NIUW
{
b_\aSEaTT
ALint state;
rDUNA@r
alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
H%Q@DW8~@
if (state != AL_STOPPED)
>9Ub=tZm
{
>JrQS"[u
x1Z?x,-D"
alSourceStop(m_sourceID);
cKFzn+
}
i<)c4
}
h"]v+u`!SM
/`}C~
-(void)clearOpenAL
F~{yqY5]n
{
Zp l?zI
alDeleteSources(1, &m_sourceID);
RK"dPr
if (m_Context != nil)
XSn^$$S
{
9(ANhG
alcDestroyContext(m_Context);
*sZ Ows<
m_Context=nil;
O<."C=1~E
}
,MuLu,$/
if (m_Device !=nil)
p24sWDf
{
4N*Fq!k~
alcCloseDevice(m_Device);
0@^YxU[YN
m_Device=nil;
c,X\1yLy
}
U#d&#",s
}
8WfF: R;
Y -o*d@
sAC1Pda
其实这些代码很简单,而且很容易在网上找到几乎差不多的代码,但是这整个代码本身并没有什么我值得说的,值得说的地方有两点:
JU/K\S2%,
一、方法alSourcePlay(m_sourceID);
:=UeYm @
很多网上的例子都在问为什么程序运行的时候一直听不到声音或者声音一直卡着,其实是在调用alSourcePlay(m_sourceID); 方法
li%=<?%T
之前,没有对整个环境进行一个判断,就是没有判断当前播放器是否已经是播放状态了(AL_PLAYING) ,所以每次来一帧数据,都会
ZMy7z|
调用一次开启播放方法,这样,声音就会卡着。这一点,算不上是难点,但是比较容易忽略,相信很多用openAL的朋友都会注意到
V5qvH"^
这个问题。
s1:UCv-%
二、其二就是,很多人在网上发帖问为什么播放不了声音或者是有杂音,杂音很大,其实这个问题跟这个方法 alBufferData 有直接的关系,
+cQ4u4
能否播放出正常的声音,全看你的参数怎么给了。 我相信很多人在调用 alBufferData这个方法的时候,对于里面的参数很迷惑,不知道
a?M<r>
该怎么填,我看的最多的参数就是 alBufferData(bufferID,AL_FORMAT_STETERO16,(char *)[data bytes] ,(ALsizei)[data length], 44100),
I zM=?,`
我也相信,很多人对参数AL_FORMAT_STETERO16,和44100不解,不知道为什么这么填,而且根据网上也是这样填的,怎么自己就
=TwV_Dro~
是播放不出来声音或者, 很大杂音之类的。这里我就简单的说一下,AL_FORMAT_STETERO16 表示的是双声道 16bit, 44100表示的是
}!lLA4XRr
音频采样率。PCM格式的数据,一般有单声道16bit(AL_FORMAT_MONO16),采样率为22050 和双声道16bit,采样率为44100. 不过
R%8nR6iG"
也有单声道8bit.和双声道8bit。 所以这就不难看出为什么网上别人的参数为什么会这样写了。
hTf]t
但是,还有一点,也就是为什么你的声音出不来的重要一点,就是,你的数据或者你的设备,不适合网上的AL_FORMAT_STETERO16,
:7@"E W
44100。你需要自己来为自己的设备匹配适合的参数,这也是最难 部分,自己的设备,哪一套参数才适合呢,这个,我也说不准。我记得
=p N?h<dc
在我原来公司用openAL的时候,我用的貌似是一套AL_FORMAT_MONO8,8000。 现在新公司用的是AL_FORMAT_MONO16,8000.
dkLc"$( O
openAL 提供了4种声道选择,AL_FORMAT_MONO8,AL_FORMAT_MONO16,AL_FORMAT_STETERO8,AL_FORMAT_STETERO16,
b4S7 Q"g
对于采样率的选择却没有明确给出,一般是不大于人类的识别频率就行(<48000)。 而常用的采样率也就是8000,22050,44100 这三种了,
dxUq5`#G,
当然也有些奇葩的值,这个需要你自己来定了。
(s,Nq~O
-`&4>\o2Lx
说了这么多,其实重要的也就下面的一条而已,难也只是难在这里,因为我个人也在网上找了好多例子,但是就是没有人讲声道跟采样率的适配,
Xe:B*
我就来这里多嘴一句,由于很少发帖, 所以文笔不好,各位不要介意,个人觉得应该能看得懂了。 再有如果其他地方的原因,应该是传入的数据
s80:.B
的问题了, 可能你装pcm数据的数组大于你实际pcm数据长度,这样可能会导致播放器停止播放,一般做法是初始化数组的时候也将整个数组填充
ofj7$se
有效字符,避免播放器自动停止播放的情况。
aq0J }4U
M)V z9,
本人在写 openAL的时候,由于同事在测试别的功能,把音频给停了(不知道怎么弄的,也就是无法播放音频),然后我调试了一天半都没个声音
it D%sKo
,然后我说去听听他做的程序(他是ios,我是mac)的音频效果如何,结果他才说他把音频拔了,音频没有接上,需要接上才有声音,当时听了之后,
( y'i{:B
我只有一砖头拍死他的冲动~~~ - -!
qs\& C
UVxE~801Y