Android音频实时传输与播放(三):AMR硬编码与硬解码
Android音频实时传输与播放(三):AMR硬编码与硬解码
分类:Android流媒体 2012-07-16 22:17 12684人阅读 评论(23) 收藏 举报 android exception buffer null socket转载请注明出处!
原文链接:http://blog.csdn.net/zgyulongfei/article/details/7753163
在Android中我所知道的音频编解码有两种方式:
(一)使用AudioRecord采集音频,用这种方式采集的是未经压缩的音频流;用AudioTrack播放实时音频流。用这两个类的话,如果需要对音频进行编解码,就需要自己移植编解码库了,比如可以移植ilbc,speex等开源编解码库。
ilbc的编解码实现可以查看这个专栏:http://blog.csdn.net/column/details/media.html
(二)使用MediaRecorder获取编码后的AMR音频,但由于MediaRecorder的特点,只能将流保存到文件中,但通过其他方式是可以获取到实时音频流的,这篇文章将介绍用LocalSocket的方法来实现;使用MediaPlayer来播放AMR音频流,但同样MediaPlayer也只能播放文件流,因此我用缓存的方式来播放音频。
以上两种方式各有利弊,使用方法(一)需移植编解码库,但可以播放实时音频流;使用方法(二)直接硬编硬解码效率高,但是需要对文件进行操作。
PS:这篇文章只是给大家一个参考,仅供学习之用,如果真正用到项目中还有很多地方需要优化。
我强烈推荐播放音频时候用方法(一),方法(二)虽然能够实现功能,但是实现方式不太好。
接下来看代码:
编码器:
[java] view plain copy
- packagecn.edu.xmu.zgy.audio.encoder;
- importjava.io.DataInputStream;
- importjava.io.IOException;
- importjava.net.DatagramPacket;
- importjava.net.DatagramSocket;
- importjava.net.InetAddress;
- importcn.edu.xmu.zgy.config.CommonConfig;
- importandroid.app.Activity;
- importandroid.media.MediaRecorder;
- importandroid.net.LocalServerSocket;
- importandroid.net.LocalSocket;
- importandroid.net.LocalSocketAddress;
- importandroid.util.Log;
- importandroid.widget.Toast;
- //blog.csdn.net/zgyulongfei
- //Email:zgyulongfei@gmail.com
- publicclassAmrAudioEncoder{
- privatestaticfinalStringTAG="ArmAudioEncoder";
- privatestaticAmrAudioEncoderamrAudioEncoder=null;
- privateActivityactivity;
- privateMediaRecorderaudioRecorder;
- privatebooleanisAudioRecording;
- privateLocalServerSocketlss;
- privateLocalSocketsender,receiver;
- privateAmrAudioEncoder(){
- }
- publicstaticAmrAudioEncodergetArmAudioEncoderInstance(){
- if(amrAudioEncoder==null){
- synchronized(AmrAudioEncoder.class){
- if(amrAudioEncoder==null){
- amrAudioEncoder=newAmrAudioEncoder();
- }
- }
- }
- returnamrAudioEncoder;
- }
- publicvoidinitArmAudioEncoder(Activityactivity){
- this.activity=activity;
- isAudioRecording=false;
- }
- publicvoidstart(){
- if(activity==null){
- showToastText("音频编码器未初始化,请先执行init方法");
- return;
- }
- if(isAudioRecording){
- showToastText("音频已经开始编码,无需再次编码");
- return;
- }
- if(!initLocalSocket()){
- showToastText("本地服务开启失败");
- releaseAll();
- return;
- }
- if(!initAudioRecorder()){
- showToastText("音频编码器初始化失败");
- releaseAll();
- return;
- }
- this.isAudioRecording=true;
- startAudioRecording();
- }
- privatebooleaninitLocalSocket(){
- booleanret=true;
- try{
- releaseLocalSocket();
- StringserverName="armAudioServer";
- finalintbufSize=1024;
- lss=newLocalServerSocket(serverName);
- receiver=newLocalSocket();
- receiver.connect(newLocalSocketAddress(serverName));
- receiver.setReceiveBufferSize(bufSize);
- receiver.setSendBufferSize(bufSize);
- sender=lss.accept();
- sender.setReceiveBufferSize(bufSize);
- sender.setSendBufferSize(bufSize);
- }catch(IOExceptione){
- ret=false;
- }
- returnret;
- }
- privatebooleaninitAudioRecorder(){
- if(audioRecorder!=null){
- audioRecorder.reset();
- audioRecorder.release();
- }
- audioRecorder=newMediaRecorder();
- audioRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
- audioRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);
- finalintmono=1;
- audioRecorder.setAudioChannels(mono);
- audioRecorder.setAudioSamplingRate(8000);
- audioRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
- audioRecorder.setOutputFile(sender.getFileDescriptor());
- booleanret=true;
- try{
- audioRecorder.prepare();
- audioRecorder.start();
- }catch(Exceptione){
- releaseMediaRecorder();
- showToastText("手机不支持录音此功能");
- ret=false;
- }
- returnret;
- }
- privatevoidstartAudioRecording(){
- newThread(newAudioCaptureAndSendThread()).start();
- }
- publicvoidstop(){
- if(isAudioRecording){
- isAudioRecording=false;
- }
- releaseAll();
- }
- privatevoidreleaseAll(){
- releaseMediaRecorder();
- releaseLocalSocket();
- amrAudioEncoder=null;
- }
- privatevoidreleaseMediaRecorder(){
- try{
- if(audioRecorder==null){
- return;
- }
- if(isAudioRecording){
- audioRecorder.stop();
- isAudioRecording=false;
- }
- audioRecorder.reset();
- audioRecorder.release();
- audioRecorder=null;
- }catch(Exceptionerr){
- Log.d(TAG,err.toString());
- }
- }
- privatevoidreleaseLocalSocket(){
- try{
- if(sender!=null){
- sender.close();
- }
- if(receiver!=null){
- receiver.close();
- }
- if(lss!=null){
- lss.close();
- }
- }catch(IOExceptione){
- e.printStackTrace();
- }
- sender=null;
- receiver=null;
- lss=null;
- }
- privatebooleanisAudioRecording(){
- returnisAudioRecording;
- }
- privatevoidshowToastText(Stringmsg){
- Toast.makeText(activity,msg,Toast.LENGTH_SHORT).show();
- }
- privateclassAudioCaptureAndSendThreadimplementsRunnable{
- publicvoidrun(){
- try{
- sendAmrAudio();
- }catch(Exceptione){
- Log.e(TAG,"sendAmrAudio()出错");
- }
- }
- privatevoidsendAmrAudio()throwsException{
- DatagramSocketudpSocket=newDatagramSocket();
- DataInputStreamdataInput=newDataInputStream(receiver.getInputStream());
- skipAmrHead(dataInput);
- finalintSEND_FRAME_COUNT_ONE_TIME=10;//每次发送10帧的数据,1帧大约32B
- //AMR格式见博客:http://blog.csdn.net/dinggo/article/details/1966444
- finalintBLOCK_SIZE[]={12,13,15,17,19,20,26,31,5,0,0,0,0,0,0,0};
- byte[]sendBuffer=newbyte[1024];
- while(isAudioRecording()){
- intoffset=0;
- for(intindex=0;index<SEND_FRAME_COUNT_ONE_TIME;++index){
- if(!isAudioRecording()){
- break;
- }
- dataInput.read(sendBuffer,offset,1);
- intblockIndex=(int)(sendBuffer[offset]>>3)&0x0F;
- intframeLength=BLOCK_SIZE[blockIndex];
- readSomeData(sendBuffer,offset+1,frameLength,dataInput);
- offset+=frameLength+1;
- }
- udpSend(udpSocket,sendBuffer,offset);
- }
- udpSocket.close();
- dataInput.close();
- releaseAll();
- }
- privatevoidskipAmrHead(DataInputStreamdataInput){
- finalbyte[]AMR_HEAD=newbyte[]{0x23,0x21,0x41,0x4D,0x52,0x0A};
- intresult=-1;
- intstate=0;
- try{
- while(-1!=(result=dataInput.readByte())){
- if(AMR_HEAD[0]==result){
- state=(0==state)?1:0;
- }elseif(AMR_HEAD[1]==result){
- state=(1==state)?2:0;
- }elseif(AMR_HEAD[2]==result){
- state=(2==state)?3:0;
- }elseif(AMR_HEAD[3]==result){
- state=(3==state)?4:0;
- }elseif(AMR_HEAD[4]==result){
- state=(4==state)?5:0;
- }elseif(AMR_HEAD[5]==result){
- state=(5==state)?6:0;
- }
- if(6==state){
- break;
- }
- }
- }catch(Exceptione){
- Log.e(TAG,"readmdaterror...");
- }
- }
- privatevoidreadSomeData(byte[]buffer,intoffset,intlength,DataInputStreamdataInput){
- intnumOfRead=-1;
- while(true){
- try{
- numOfRead=dataInput.read(buffer,offset,length);
- if(numOfRead==-1){
- Log.d(TAG,"amr...nodatagetwaitfordatacoming.....");
- Thread.sleep(100);
- }else{
- offset+=numOfRead;
- length-=numOfRead;
- if(length<=0){
- break;
- }
- }
- }catch(Exceptione){
- Log.e(TAG,"amr..errorreadSomeData");
- break;
- }
- }
- }
- privatevoidudpSend(DatagramSocketudpSocket,byte[]buffer,intsendLength){
- try{
- InetAddressip=InetAddress.getByName(CommonConfig.SERVER_IP_ADDRESS.trim());
- intport=CommonConfig.AUDIO_SERVER_UP_PORT;
- byte[]sendBuffer=newbyte[sendLength];
- System.arraycopy(buffer,0,sendBuffer,0,sendLength);
- DatagramPacketpacket=newDatagramPacket(sendBuffer,sendLength);
- packet.setAddress(ip);
- packet.setPort(port);
- udpSocket.send(packet);
- }catch(IOExceptione){
- e.printStackTrace();
- }
- }
- }
- }
关于编码器:前面提到了,MediaRecorder的硬编码的方式只能将码流保存到文件中,这里用了LocalSocket的方式将流保存到内存中,然后从缓冲中读取码流。由于保存的格式RAW_AMR格式的,因此需要对读取到的数据进行解析,从而获得真正的音频流。想了解AMR音频码流格式的,可以查看代码中附上的网页链接。由于压缩过的码流很小,因此我在实现的时候,组合了int SEND_FRAME_COUNT_ONE_TIME = 10帧的码流后才往外发送,这样的方式造成的延迟会加重,大家可以根据自己的需要进行修改。造成延迟的另一因素是LocalSocket缓冲的大小,在这里我设置的大小是final int bufSize = 1024;代码写的很清楚详细,有疑问的可以提出。
播放器:
[java] view plain copy
- packagecn.edu.xmu.zgy.audio.player;
- importjava.io.BufferedInputStream;
- importjava.io.BufferedOutputStream;
- importjava.io.File;
- importjava.io.FileInputStream;
- importjava.io.FileOutputStream;
- importjava.io.IOException;
- importjava.io.InputStream;
- importjava.net.InetAddress;
- importjava.net.Socket;
- importcn.edu.xmu.zgy.config.CommonConfig;
- importandroid.app.Activity;
- importandroid.media.MediaPlayer;
- importandroid.os.Handler;
- importandroid.util.Log;
- //blog.csdn.net/zgyulongfei
- //Email:zgyulongfei@gmail.com
- publicclassAmrAudioPlayer{
- privatestaticfinalStringTAG="AmrAudioPlayer";
- privatestaticAmrAudioPlayerplayerInstance=null;
- privatelongalreadyReadByteCount=0;
- privateMediaPlayeraudioPlayer;
- privateHandlerhandler=newHandler();
- privatefinalStringcacheFileName="audioCacheFile";
- privateFilecacheFile;
- privateintcacheFileCount=0;
- //用来记录是否已经从cacheFile中复制数据到另一个cache中
- privatebooleanhasMovedTheCacheFlag;
- privatebooleanisPlaying;
- privateActivityactivity;
- privatebooleanisChaingCacheToAnother;
- privateAmrAudioPlayer(){
- }
- publicstaticAmrAudioPlayergetAmrAudioPlayerInstance(){
- if(playerInstance==null){
- synchronized(AmrAudioPlayer.class){
- if(playerInstance==null){
- playerInstance=newAmrAudioPlayer();
- }
- }
- }
- returnplayerInstance;
- }
- publicvoidinitAmrAudioPlayer(Activityactivity){
- this.activity=activity;
- deleteExistCacheFile();
- initCacheFile();
- }
- privatevoiddeleteExistCacheFile(){
- FilecacheDir=activity.getCacheDir();
- File[]needDeleteCacheFiles=cacheDir.listFiles();
- for(intindex=0;index<needDeleteCacheFiles.length;++index){
- Filecache=needDeleteCacheFiles[index];
- if(cache.isFile()){
- if(cache.getName().contains(cacheFileName.trim())){
- Log.e(TAG,"deletecachefile:"+cache.getName());
- cache.delete();
- }
- }
- }
- needDeleteCacheFiles=null;
- }
- privatevoidinitCacheFile(){
- cacheFile=null;
- cacheFile=newFile(activity.getCacheDir(),cacheFileName);
- }
- publicvoidstart(){
- isPlaying=true;
- isChaingCacheToAnother=false;
- setHasMovedTheCacheToAnotherCache(false);
- newThread(newNetAudioPlayerThread()).start();
- }
- publicvoidstop(){
- isPlaying=false;
- isChaingCacheToAnother=false;
- setHasMovedTheCacheToAnotherCache(false);
- releaseAudioPlayer();
- deleteExistCacheFile();
- cacheFile=null;
- handler=null;
- }
- privatevoidreleaseAudioPlayer(){
- playerInstance=null;
- if(audioPlayer!=null){
- try{
- if(audioPlayer.isPlaying()){
- audioPlayer.pause();
- }
- audioPlayer.release();
- audioPlayer=null;
- }catch(Exceptione){
- }
- }
- }
- privatebooleanhasMovedTheCacheToAnotherCache(){
- returnhasMovedTheCacheFlag;
- }
- privatevoidsetHasMovedTheCacheToAnotherCache(booleanresult){
- hasMovedTheCacheFlag=result;
- }
- privateclassNetAudioPlayerThreadimplementsRunnable{
- //从接受数据开始计算,当缓存大于INIT_BUFFER_SIZE时候开始播放
- privatefinalintINIT_AUDIO_BUFFER=2*1024;
- //剩1秒的时候播放新的缓存的音乐
- privatefinalintCHANGE_CACHE_TIME=1000;
- publicvoidrun(){
- try{
- Socketsocket=createSocketConnectToServer();
- receiveNetAudioThenPlay(socket);
- }catch(Exceptione){
- Log.e(TAG,e.getMessage()+"从服务端接受音频失败。。。");
- }
- }
- privateSocketcreateSocketConnectToServer()throwsException{
- StringhostName=CommonConfig.SERVER_IP_ADDRESS;
- InetAddressipAddress=InetAddress.getByName(hostName);
- intport=CommonConfig.AUDIO_SERVER_DOWN_PORT;
- Socketsocket=newSocket(ipAddress,port);
- returnsocket;
- }
- privatevoidreceiveNetAudioThenPlay(Socketsocket)throwsException{
- InputStreaminputStream=socket.getInputStream();
- FileOutputStreamoutputStream=newFileOutputStream(cacheFile);
- finalintBUFFER_SIZE=100*1024;//100kbbuffersize
- byte[]buffer=newbyte[BUFFER_SIZE];
- //收集了10*350b了之后才开始更换缓存
- inttestTime=10;
- try{
- alreadyReadByteCount=0;
- while(isPlaying){
- intnumOfRead=inputStream.read(buffer);
- if(numOfRead<=0){
- break;
- }
- alreadyReadByteCount+=numOfRead;
- outputStream.write(buffer,0,numOfRead);
- outputStream.flush();
- try{
- if(testTime++>=10){
- Log.e(TAG,"cacheFile="+cacheFile.length());
- testWhetherToChangeCache();
- testTime=0;
- }
- }catch(Exceptione){
- //TODO:handleexception
- }
- //如果复制了接收网络流的cache,则执行此操作
- if(hasMovedTheCacheToAnotherCache()&&!isChaingCacheToAnother){
- if(outputStream!=null){
- outputStream.close();
- outputStream=null;
- }
- //将接收网络流的cache删除,然后重0开始存储
- //initCacheFile();
- outputStream=newFileOutputStream(cacheFile);
- setHasMovedTheCacheToAnotherCache(false);
- alreadyReadByteCount=0;
- }
- }
- }catch(Exceptione){
- errorOperator();
- e.printStackTrace();
- Log.e(TAG,"socketdisconnect...:"+e.getMessage());
- thrownewException("socketdisconnect....");
- }finally{
- buffer=null;
- if(socket!=null){
- socket.close();
- }
- if(inputStream!=null){
- inputStream.close();
- inputStream=null;
- }
- if(outputStream!=null){
- outputStream.close();
- outputStream=null;
- }
- stop();
- }
- }
- privatevoidtestWhetherToChangeCache()throwsException{
- if(audioPlayer==null){
- firstTimeStartPlayer();
- }else{
- changeAnotherCacheWhenEndOfCurrentCache();
- }
- }
- privatevoidfirstTimeStartPlayer()throwsException{
- //当缓存已经大于INIT_AUDIO_BUFFER则开始播放
- if(alreadyReadByteCount>=INIT_AUDIO_BUFFER){
- Runnabler=newRunnable(){
- publicvoidrun(){
- try{
- FilefirstCacheFile=createFirstCacheFile();
- //设置已经从cache中复制数据,然后会删除这个cache
- setHasMovedTheCacheToAnotherCache(true);
- audioPlayer=createAudioPlayer(firstCacheFile);
- audioPlayer.start();
- }catch(Exceptione){
- Log.e(TAG,e.getMessage()+":infirstTimeStartPlayer()fun");
- }finally{
- }
- }
- };
- handler.post(r);
- }
- }
- privateFilecreateFirstCacheFile()throwsException{
- StringfirstCacheFileName=cacheFileName+(cacheFileCount++);
- FilefirstCacheFile=newFile(activity.getCacheDir(),firstCacheFileName);
- //为什么不直接播放cacheFile,而要复制cacheFile到一个新的cache,然后播放此新的cache?
- //是为了防止潜在的读/写错误,可能在写入cacheFile的时候,
- //MediaPlayer正试图读数据,这样可以防止死锁的发生。
- moveFile(cacheFile,firstCacheFile);
- returnfirstCacheFile;
- }
- privatevoidmoveFile(FileoldFile,FilenewFile)throwsIOException{
- if(!oldFile.exists()){
- thrownewIOException("oldFileisnotexists.inmoveFile()fun");
- }
- if(oldFile.length()<=0){
- thrownewIOException("oldFilesize=0.inmoveFile()fun");
- }
- BufferedInputStreamreader=newBufferedInputStream(newFileInputStream(oldFile));
- BufferedOutputStreamwriter=newBufferedOutputStream(newFileOutputStream(newFile,
- false));
- finalbyte[]AMR_HEAD=newbyte[]{0x23,0x21,0x41,0x4D,0x52,0x0A};
- writer.write(AMR_HEAD,0,AMR_HEAD.length);
- writer.flush();
- try{
- byte[]buffer=newbyte[1024];
- intnumOfRead=0;
- Log.d(TAG,"POS...newFile.length="+newFile.length()+"old="+oldFile.length());
- while((numOfRead=reader.read(buffer,0,buffer.length))!=-1){
- writer.write(buffer,0,numOfRead);
- writer.flush();
- }
- Log.d(TAG,"POS..AFTER...newFile.length="+newFile.length());
- }catch(IOExceptione){
- Log.e(TAG,"moveFileerror..inmoveFile()fun."+e.getMessage());
- thrownewIOException("moveFileerror..inmoveFile()fun.");
- }finally{
- if(reader!=null){
- reader.close();
- reader=null;
- }
- if(writer!=null){
- writer.close();
- writer=null;
- }
- }
- }
- privateMediaPlayercreateAudioPlayer(FileaudioFile)throwsIOException{
- MediaPlayermPlayer=newMediaPlayer();
- //Itappearsthatforsecurity/permissionreasons,itisbetterto
- //pass
- //aFileDescriptorratherthanadirectpathtotheFile.
- //AlsoIhaveseenerrorssuchas"PVMFErrNotSupported"and
- //"Preparefailed.:status=0x1"ifafilepathStringispassedto
- //setDataSource().Sounlessotherwisenoted,weusea
- //FileDescriptorhere.
- FileInputStreamfis=newFileInputStream(audioFile);
- mPlayer.reset();
- mPlayer.setDataSource(fis.getFD());
- mPlayer.prepare();
- returnmPlayer;
- }
- privatevoidchangeAnotherCacheWhenEndOfCurrentCache()throwsIOException{
- //检查当前cache剩余时间
- longtheRestTime=audioPlayer.getDuration()-audioPlayer.getCurrentPosition();
- Log.e(TAG,"theRestTime="+theRestTime+"isChaingCacheToAnother="
- +isChaingCacheToAnother);
- if(!isChaingCacheToAnother&&theRestTime<=CHANGE_CACHE_TIME){
- isChaingCacheToAnother=true;
- Runnabler=newRunnable(){
- publicvoidrun(){
- try{
- FilenewCacheFile=createNewCache();
- //设置已经从cache中复制数据,然后会删除这个cache
- setHasMovedTheCacheToAnotherCache(true);
- transferNewCacheToAudioPlayer(newCacheFile);
- }catch(Exceptione){
- Log.e(TAG,e.getMessage()
- +":changeAnotherCacheWhenEndOfCurrentCache()fun");
- }finally{
- deleteOldCache();
- isChaingCacheToAnother=false;
- }
- }
- };
- handler.post(r);
- }
- }
- privateFilecreateNewCache()throwsException{
- //将保存网络数据的cache复制到newCache中进行播放
- StringnewCacheFileName=cacheFileName+(cacheFileCount++);
- FilenewCacheFile=newFile(activity.getCacheDir(),newCacheFileName);
- Log.e(TAG,"beforemoveFile............thesize="+cacheFile.length());
- moveFile(cacheFile,newCacheFile);
- returnnewCacheFile;
- }
- privatevoidtransferNewCacheToAudioPlayer(FilenewCacheFile)throwsException{
- MediaPlayeroldPlayer=audioPlayer;
- try{
- audioPlayer=createAudioPlayer(newCacheFile);
- audioPlayer.start();
- }catch(Exceptione){
- Log.e(TAG,"filename="+newCacheFile.getName()+"size="+newCacheFile.length());
- Log.e(TAG,e.getMessage()+""+e.getCause()+"errorstart..intransfanNer..");
- }
- try{
- oldPlayer.pause();
- oldPlayer.reset();
- oldPlayer.release();
- }catch(Exceptione){
- Log.e(TAG,"ERRORreleaseoldPlayer.");
- }finally{
- oldPlayer=null;
- }
- }
- privatevoiddeleteOldCache(){
- intoldCacheFileCount=cacheFileCount-1;
- StringoldCacheFileName=cacheFileName+oldCacheFileCount;
- FileoldCacheFile=newFile(activity.getCacheDir(),oldCacheFileName);
- if(oldCacheFile.exists()){
- oldCacheFile.delete();
- }
- }
- privatevoiderrorOperator(){
- }
- }
- }
关于播放器:由于MediaPlayer的限制,我用了cache的方式来实现音频的实时播放。即把获取到的音频流首先保存到文件中,然后当保存到一定大小的时候就播放之,类似于QQ播放器那样有缓存的,只不过我这里的处理得挺粗糙。代码写的也挺详细了,如果有疑问也可以提出来。
注:编码器和播放器的编写,我都是站在巨人的肩膀上完成的,参考了一些其他资料。
在后面一篇文章中,我将附上服务器和客户端的所有代码。
希望朋友们看完能提出意见和建议,也希望看完能有所收获 ^_^
更多相关文章
- Android(安卓)Service AIDL 远程调用服务 【简单音乐播放实例】
- 优秀的Android音频播放器
- android listview继承BaseAdapter,自定义的适配器,getView方法执
- Android播放器MediaPlayer与MediaRecorder:录制音频并播放
- Android(安卓)Activity四种启动方式
- Android中通过耳机按键控制音乐播放的实现
- Android使用JDBC连接mysql数据库
- Android(安卓)3.0 r1 API中文文档(107) ―― AsyncPlayer
- android 开发包的离线安装方式