Android ilbc 语音对话示范(五)接收端处理【转】http://blog.youkuaiyun.com/ranxiedao/article/details/8080385...

本文详细介绍Android平台下使用ILBC编码实现语音通话的具体步骤,包括接收端处理流程及核心代码解析。

Android ilbc 语音对话示范(五)接收端处理

分类: Android 语音对话   483人阅读  评论(5)  收藏  举报

此系列文章拖了N久,有好多人发邮件来询问我第五次的文章为什么没有写,其实非常抱歉,本人学生一个,暑假一直

去公司实习,最近又忙着各种招聘找工作,没有时间好好写,现在抽空把最后一篇补上,水平有限,如过有不对的,请

各位指正~

    前四篇文章分别介绍了 “代码结构”,“程序流程”,以及”发送方的处理”,现在就把接收方的处理流程做个介绍;

           

    如上图所示,接收方的操作有三个类:AudioDecoder(负责解码),AudioPlayer(负责播放解码后的音频),

AudioReceiver(负责从服务器接收音频数据包),这三个类的流程在第三篇中有详细的介绍。

1.AudioReceiver代码:

   AudioReceiver使用UDP方式从服务端接收音频数据,其过程比较简单,直接上代码:

 

  1. package xmu.swordbearer.audio.receiver;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.DatagramPacket;  
  5. import java.net.DatagramSocket;  
  6. import java.net.SocketException;  
  7.   
  8. import xmu.swordbearer.audio.MyConfig;  
  9. import android.util.Log;  
  10.   
  11. public class AudioReceiver implements Runnable {  
  12.     String LOG = "NET Reciever ";  
  13.     int port = MyConfig.CLIENT_PORT;// 接收的端口  
  14.     DatagramSocket socket;  
  15.     DatagramPacket packet;  
  16.     boolean isRunning = false;  
  17.   
  18.     private byte[] packetBuf = new byte[1024];  
  19.     private int packetSize = 1024;  
  20.   
  21.     /* 
  22.      * 开始接收数据 
  23.      */  
  24.     public void startRecieving() {  
  25.         if (socket == null) {  
  26.             try {  
  27.                 socket = new DatagramSocket(port);  
  28.                 packet = new DatagramPacket(packetBuf, packetSize);  
  29.             } catch (SocketException e) {  
  30.             }  
  31.         }  
  32.         new Thread(this).start();  
  33.     }  
  34.   
  35.     /* 
  36.      * 停止接收数据 
  37.      */  
  38.     public void stopRecieving() {  
  39.         isRunning = false;  
  40.     }  
  41.   
  42.     /* 
  43.      * 释放资源 
  44.      */  
  45.     private void release() {  
  46.         if (packet != null) {  
  47.             packet = null;  
  48.         }  
  49.         if (socket != null) {  
  50.             socket.close();  
  51.             socket = null;  
  52.         }  
  53.     }  
  54.   
  55.     public void run() {  
  56.         // 在接收前,要先启动解码器  
  57.         AudioDecoder decoder = AudioDecoder.getInstance();  
  58.         decoder.startDecoding();  
  59.   
  60.         isRunning = true;  
  61.         try {  
  62.             while (isRunning) {  
  63.                 socket.receive(packet);  
  64.                 // 每接收一个UDP包,就交给解码器,等待解码  
  65.                 decoder.addData(packet.getData(), packet.getLength());  
  66.             }  
  67.   
  68.         } catch (IOException e) {  
  69.             Log.e(LOG, LOG + "RECIEVE ERROR!");  
  70.         }  
  71.         // 接收完成,停止解码器,释放资源  
  72.         decoder.stopDecoding();  
  73.         release();  
  74.         Log.e(LOG, LOG + "stop recieving");  
  75.     }  
  76.   
  77. }  

 

2.AudioDecoder代码:

解码的过程也很简单,由于接收端接收到了音频数据,然后就把数据交给解码器,所以解码的主要工作就是把接收端的数

据取出来进行解码,如果解码正确,就将解码后的数据再转交给AudioPlayer去播放,这三个类之间是依次传递的 :

    AudioReceiver---->AudioDecoder--->AudioPlayer

下面代码中有个List变量 private List<AudioData> dataList = null;这个就是用来存放数据的,每次解码时,dataList.remove(0),

从最前端取出数据进行解码:

 

  1. package xmu.swordbearer.audio.receiver;  
  2.   
  3. import java.util.Collections;  
  4. import java.util.LinkedList;  
  5. import java.util.List;  
  6.   
  7. import xmu.swordbearer.audio.AudioCodec;  
  8. import xmu.swordbearer.audio.data.AudioData;  
  9. import android.util.Log;  
  10.   
  11. public class AudioDecoder implements Runnable {  
  12.   
  13.     String LOG = "CODEC Decoder ";  
  14.     private static AudioDecoder decoder;  
  15.   
  16.     private static final int MAX_BUFFER_SIZE = 2048;  
  17.   
  18.     private byte[] decodedData = new byte[1024];// data of decoded  
  19.     private boolean isDecoding = false;  
  20.     private List<AudioData> dataList = null;  
  21.   
  22.     public static AudioDecoder getInstance() {  
  23.         if (decoder == null) {  
  24.             decoder = new AudioDecoder();  
  25.         }  
  26.         return decoder;  
  27.     }  
  28.   
  29.     private AudioDecoder() {  
  30.         this.dataList = Collections  
  31.                 .synchronizedList(new LinkedList<AudioData>());  
  32.     }  
  33.   
  34.     /* 
  35.      * add Data to be decoded 
  36.      *  
  37.      * @ data:the data recieved from server 
  38.      *  
  39.      * @ size:data size 
  40.      */  
  41.     public void addData(byte[] data, int size) {  
  42.         AudioData adata = new AudioData();  
  43.         adata.setSize(size);  
  44.         byte[] tempData = new byte[size];  
  45.         System.arraycopy(data, 0, tempData, 0, size);  
  46.         adata.setRealData(tempData);  
  47.         dataList.add(adata);  
  48.         System.out.println(LOG + "add data once");  
  49.   
  50.     }  
  51.   
  52.     /* 
  53.      * start decode AMR data 
  54.      */  
  55.     public void startDecoding() {  
  56.         System.out.println(LOG + "start decoder");  
  57.         if (isDecoding) {  
  58.             return;  
  59.         }  
  60.         new Thread(this).start();  
  61.     }  
  62.   
  63.     public void run() {  
  64.         // start player first  
  65.         AudioPlayer player = AudioPlayer.getInstance();  
  66.         player.startPlaying();  
  67.         //  
  68.         this.isDecoding = true;  
  69.         // init ILBC parameter:30 ,20, 15  
  70.         AudioCodec.audio_codec_init(30);  
  71.   
  72.         Log.d(LOG, LOG + "initialized decoder");  
  73.         int decodeSize = 0;  
  74.         while (isDecoding) {  
  75.             while (dataList.size() > 0) {  
  76.                 AudioData encodedData = dataList.remove(0);  
  77.                 decodedData = new byte[MAX_BUFFER_SIZE];  
  78.   
  79.                 byte[] data = encodedData.getRealData();  
  80.                 //  
  81.                 decodeSize = AudioCodec.audio_decode(data, 0,  
  82.                         encodedData.getSize(), decodedData, 0);  
  83.                 if (decodeSize > 0) {  
  84.                     // add decoded audio to player  
  85.                     player.addData(decodedData, decodeSize);  
  86.                     // clear data  
  87.                     decodedData = new byte[decodedData.length];  
  88.                 }  
  89.             }  
  90.         }  
  91.         System.out.println(LOG + "stop decoder");  
  92.         // stop playback audio  
  93.         player.stopPlaying();  
  94.     }  
  95.   
  96.     public void stopDecoding() {  
  97.         this.isDecoding = false;  
  98.     }  
  99. }  

3.AudioPlayer代码:

 

播放器的工作流程其实和解码器一模一样,都是启动一个线程,然后不断从自己的 dataList中提取数据。

不过要注意,播放器的一些参数配置非常的关键;

播放声音时,使用了Android自带的 AudioTrack 这个类,它有这个方法:

public int write(byte[] audioData,int offsetInBytes, int sizeInBytes)可以直接播放;

所有播放器的代码如下:

 

  1. package xmu.swordbearer.audio.receiver;  
  2.   
  3. import java.util.Collections;  
  4. import java.util.LinkedList;  
  5. import java.util.List;  
  6.   
  7. import xmu.swordbearer.audio.data.AudioData;  
  8. import android.media.AudioFormat;  
  9. import android.media.AudioManager;  
  10. import android.media.AudioRecord;  
  11. import android.media.AudioTrack;  
  12. import android.util.Log;  
  13.   
  14. public class AudioPlayer implements Runnable {  
  15.     String LOG = "AudioPlayer ";  
  16.     private static AudioPlayer player;  
  17.   
  18.     private List<AudioData> dataList = null;  
  19.     private AudioData playData;  
  20.     private boolean isPlaying = false;  
  21.   
  22.     private AudioTrack audioTrack;  
  23.   
  24.     private static final int sampleRate = 8000;  
  25.     // 注意:参数配置  
  26.     private static final int channelConfig = AudioFormat.CHANNEL_IN_MONO;  
  27.     private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;  
  28.   
  29.     private AudioPlayer() {  
  30.         dataList = Collections.synchronizedList(new LinkedList<AudioData>());  
  31.     }  
  32.   
  33.     public static AudioPlayer getInstance() {  
  34.         if (player == null) {  
  35.             player = new AudioPlayer();  
  36.         }  
  37.         return player;  
  38.     }  
  39.   
  40.     public void addData(byte[] rawData, int size) {  
  41.         AudioData decodedData = new AudioData();  
  42.         decodedData.setSize(size);  
  43.   
  44.         byte[] tempData = new byte[size];  
  45.         System.arraycopy(rawData, 0, tempData, 0, size);  
  46.         decodedData.setRealData(tempData);  
  47.         dataList.add(decodedData);  
  48.     }  
  49.   
  50.     /* 
  51.      * init Player parameters 
  52.      */  
  53.     private boolean initAudioTrack() {  
  54.         int bufferSize = AudioRecord.getMinBufferSize(sampleRate,  
  55.                 channelConfig, audioFormat);  
  56.         if (bufferSize < 0) {  
  57.             Log.e(LOG, LOG + "initialize error!");  
  58.             return false;  
  59.         }  
  60.         audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,  
  61.                 channelConfig, audioFormat, bufferSize, AudioTrack.MODE_STREAM);  
  62.         // set volume:设置播放音量  
  63.         audioTrack.setStereoVolume(1.0f, 1.0f);  
  64.         audioTrack.play();  
  65.         return true;  
  66.     }  
  67.   
  68.     private void playFromList() {  
  69.         while (dataList.size() > 0 && isPlaying) {  
  70.             playData = dataList.remove(0);  
  71.             audioTrack.write(playData.getRealData(), 0, playData.getSize());  
  72.         }  
  73.     }  
  74.   
  75.     public void startPlaying() {  
  76.         if (isPlaying) {  
  77.             return;  
  78.         }  
  79.         new Thread(this).start();  
  80.     }  
  81.   
  82.     public void run() {  
  83.         this.isPlaying = true;  
  84.           
  85.         if (!initAudioTrack()) {  
  86.             Log.e(LOG, LOG + "initialized player error!");  
  87.             return;  
  88.         }  
  89.         while (isPlaying) {  
  90.             if (dataList.size() > 0) {  
  91.                 playFromList();  
  92.             } else {  
  93.                 try {  
  94.                     Thread.sleep(20);  
  95.                 } catch (InterruptedException e) {  
  96.                 }  
  97.             }  
  98.         }  
  99.         if (this.audioTrack != null) {  
  100.             if (this.audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {  
  101.                 this.audioTrack.stop();  
  102.                 this.audioTrack.release();  
  103.             }  
  104.         }  
  105.         Log.d(LOG, LOG + "end playing");  
  106.     }  
  107.   
  108.     public void stopPlaying() {  
  109.         this.isPlaying = false;  
  110.     }  
  111. }  

 

 

4.简易服务端:

为了方便测试,我自己用Java 写了一个UDP的服务器,其功能非常的弱,就是接收,然后转发给另一方:

 

 

  1. import java.io.IOException;  
  2. import java.net.DatagramPacket;  
  3. import java.net.DatagramSocket;  
  4. import java.net.InetAddress;  
  5. import java.net.SocketException;  
  6. import java.net.UnknownHostException;  
  7.   
  8. public class AudioServer implements Runnable {  
  9.   
  10.     DatagramSocket socket;  
  11.     DatagramPacket packet;// 从客户端接收到的UDP包  
  12.     DatagramPacket sendPkt;// 转发给另一个客户端的UDP包  
  13.   
  14.     byte[] pktBuffer = new byte[1024];  
  15.     int bufferSize = 1024;  
  16.     boolean isRunning = false;  
  17.     int myport = 5656;  
  18.   
  19.     // ///////////  
  20.     String clientIpStr = "192.168.1.104";  
  21.     InetAddress clientIp;  
  22.     int clientPort = 5757;  
  23.   
  24.     public AudioServer() {  
  25.         try {  
  26.             clientIp = InetAddress.getByName(clientIpStr);  
  27.         } catch (UnknownHostException e1) {  
  28.             e1.printStackTrace();  
  29.         }  
  30.         try {  
  31.             socket = new DatagramSocket(myport);  
  32.             packet = new DatagramPacket(pktBuffer, bufferSize);  
  33.         } catch (SocketException e) {  
  34.             e.printStackTrace();  
  35.         }  
  36.         System.out.println("服务器初始化完成");  
  37.     }  
  38.   
  39.     public void startServer() {  
  40.         this.isRunning = true;  
  41.         new Thread(this).start();  
  42.     }  
  43.   
  44.     public void run() {  
  45.         try {  
  46.             while (isRunning) {  
  47.                 socket.receive(packet);  
  48.                 sendPkt = new DatagramPacket(packet.getData(),  
  49.                         packet.getLength(), packet.getAddress(), clientPort);  
  50.                 socket.send(sendPkt);  
  51.                 try {  
  52.                     Thread.sleep(20);  
  53.                 } catch (InterruptedException e) {  
  54.                     e.printStackTrace();  
  55.                 }  
  56.             }  
  57.         } catch (IOException e) {  
  58.         }  
  59.     }  
  60.   
  61.     // main  
  62.     public static void main(String[] args) {  
  63.         new AudioServer().startServer();  
  64.     }  
  65. }  


 

5.结语:

Android使用 ILBC 进行语音通话的大致过程就讲述完了,此系列只是做一个ILBC 使用原理的介绍,距离真正的语音

通话还有很多工作要做,缺点还是很多的:

   1. 文章中介绍的只是单方通话,如果要做成双方互相通话或者一对多的通话,就需要增加更多的流程处理,其服务端

也要做很多工作;

   2. 实时性:本程序在局域网中使用时,实时性还是较高的,但是再广域网中,效果可能会有所下降,除此之外,本

程序还缺少时间戳的处理,如果网络状况不理想,或者数据延迟,就会导致语音播放前后混乱;

   3. 服务器很弱:真正的流媒体服务器,需要很强的功能,来对数据进行处理,我是为了方便,就写了一个简单的,

最近打算移植live555,用来做专门的流媒体服务器,用RTP协议对数据进行封装,这样效果应该会好很多。

 

现在,整个工程的代码都完成了,全部源代码可以在这里下载: http://download.youkuaiyun.com/detail/ranxiedao/4923759

BY:http://blog.youkuaiyun.com/ranxiedao

 

转载于:https://www.cnblogs.com/songtzu/archive/2013/02/04/2891982.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值