分享

Android通过JUV+Red5+Speex实现网络语音聊天(一)

 quasiceo 2016-03-07
2012-12-19 19:38 7928人阅读 评论(4) 收藏 举报
分类:

先说下实现原理,手机采集到语音后进过Speex编码,通过juv以直播形式发布自己的语音流到red5,也是通过juv播放对方的直播流,经过Speex解码后输出到扬声器,如下图:

Android  voip  流程图
Android端采集编码和解码播放Speex,参考android-recorder,至于他用的red5客户端,看了下,没看明白。。。
JUV这库吧 好上手,虽然是付费的,但是有30天的试用,可长期申请。暂时用下也不错。另外,国内有破解版,你懂得

核心代码如下:

  1. public class AudioCenter extends AbstractMicrophone  


首先,音频处理类继承自JUV库中的 AbstractMicrophone,便可以使用 fireOnAudioData方法向Red5服务端发布音频数据。

  1. public void encSpeexAudio() {  
  2.   new Thread(new Runnable() {  
  3.    
  4.     @Override  
  5.     public void run() {  
  6.       int bufferSize = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);  
  7.       short[] mAudioRecordBuffer = new short[bufferSize];  
  8.       AudioRecord mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);  
  9.       mAudioRecord.startRecording();  
  10.       int bufferRead = 0;  
  11.       int len;  
  12.       isEncoding = true;  
  13.    
  14.       while (isEncoding) {  
  15.         bufferRead = mAudioRecord.read(mAudioRecordBuffer, 0, frameSize);  
  16.    
  17.         if (bufferRead > 0) {  
  18.           try {  
  19.               len = speex.encode(mAudioRecordBuffer, 0, processedData, frameSize);  
  20.               // LogHelper.d(subTAG, "EncSpeexAudio "+ len);  
  21.             byte[] speexData = new byte[len + 1];  
  22.             System.arraycopy(SpeexRtmpHead, 0, speexData, 01);  
  23.             System.arraycopy(processedData, 0, speexData, 1, len);  
  24.             fireOnAudioData(new MediaDataByteArray(20new ByteArray(speexData)));  
  25.           } catch (Exception e) {  
  26.             e.printStackTrace();  
  27.           }  
  28.         }  
  29.       }  
  30.       mAudioRecord.stop();  
  31.       mAudioRecord.release();  
  32.       mAudioRecord = null;  
  33.     }  
  34.   }, "EncSpeexAudio Thread").start();  
  35. }  


以上是编码上传线程,一个关键技术点:SpeexRtmpHead,曾经困扰我很长一段时间,也是因为自己刚开始解除对RTMP协议只停留在api调用层面。

  1. private byte[] SpeexRtmpHead = new byte[] { (byte0xB2 };  



我们首先要知道“RTMP Packet中封装的音视频数据流,其实和FLV封装音频和视频数据的方式是相同的。”

Audio tag 数据区
audio信息 1byte 前四位bits表示音频格式:
0 – 未压缩
1 = ADPCM
2 = MP3
3 = Linear PCM, little endian
4 = Nellymoser 16-kHz mono
5 = Nellymoser 8-kHz mono
6 = Nellymoser
7 = G.711 A-law logarithmic PCM
8 = G.711 mu-law logarithmic PCM
9 = reserved
10 = AAC
11 = Speex
14 = MP3 8-Khz
15 = Device-specific sound
下面两位bits表示samplerate:
0 – 5.5kHz
1 – 11kHz
2 – 22kHz
3 – 44kHz
下面一位bit表示每个采样的长度:
0 – snd8Bit
1 – snd16Bit
下面一位bit表示类型:
0 – sndMomo
1 – sndStereo
audio数据区 不定   if SoundFormat == 10    AACAUDIODATAelse    Sound data—varies by format

由Flv协议的AudioTag数据区可查得,在数据区前有一个字节的audio信息,我们采用speex编码,8KHz采样,每个采样16bit,单声道。那么 得出的数据为10110010 十六进制:0xB2,将它拼装到每个数据区前,通过fireOnAudioData发布,则为标准的Rtmp数据上传到服务器。这时候可以使用red5-publisher测试,已经能听到声音了。不清楚red5-publisher使用的朋友,可以看我上篇关于Red5的配置

  1. public void playSpeexAudio() {  
  2.   new Thread(new Runnable() {  
  3.     @Override  
  4.     public void run() {  
  5.       short[] decData = new short[256];  
  6.       AudioTrack audioTrack;  
  7.       int bufferSizeInBytes = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);  
  8.       audioTrack = new AudioTrack(AudioManager.STREAM_VOICE_CALL, 8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, 2 * bufferSizeInBytes, AudioTrack.MODE_STREAM);  
  9.       audioTrack.play();  
  10.       isPlaying = true;  
  11.       while (isPlaying) {  
  12.         while (encData.size() > 0) {  
  13.           byte[] data = encData.elementAt(0);  
  14.           encData.removeElementAt(0);  
  15.           int dec;  
  16.             dec = speex.decode(data, decData, data.length);  
  17.             // LogHelper.d(subTAG, "playSpeexAudio "+ dec);  
  18.           if (dec > 0) {  
  19.             audioTrack.write(decData, 0, dec);  
  20.           }  
  21.         }  
  22.         try {  
  23.           Thread.sleep(10);  
  24.         } catch (InterruptedException e) {  
  25.           e.printStackTrace();  
  26.         }  
  27.    
  28.       }  
  29.       audioTrack.stop();  
  30.       audioTrack.release();  
  31.       audioTrack = null;  
  32.    
  33.     }  
  34.   }, "PlaySpeexAudio Thread").start();  
  35. }  


以上为音频解码线程,没什么难点,只需注意我使用了一个Vector 来缓存juv拉下来的speex语音数据

private Vector encData = new Vector();

采集编码解码播放先谈到这里,下篇讲下juv的连接和数据传输相关。
如有疑问,欢迎与我交流。

原创文章,转载请注明: 转载自贝壳博客

本文链接地址: Android通过JUV+Red5+Speex实现网络语音聊天(一)

0
0

我的同类文章

猜你在找
查看评论
3楼 LLz. 2014-01-10 11:19发表 [回复]
您好方便把代码连接发一下学习学习吗
Re: LLz. 2014-01-13 13:56发表 [回复]
回复kongbaidepao:这个不知道回音怎么解决
2楼 whhpc19891120 2013-04-16 10:34发表 [回复]
while(!isRecording){
//从bufferSize中读取字节,返回读取的short个数
int tmp = record.read(buffer, 0, framesize);
Log.d("tmp:",""+tmp);
if(tmp >0){
final long ctime = System.currentTimeMillis();
//对data进行speex编码
int len = speex.encode(buffer, 0, data, framesize);
Log.d("len:",len+"");
byte[] speexData = new byte[len + 1];
byte[] SpeexRtmpHead = new byte[] { (byte) 0xB6 };

System.arraycopy(SpeexRtmpHead, 0, speexData, 0, 1);
System.arraycopy(data, 0, speexData, 1, len);

fireOnAudioData(new MediaDataByteArray(20 , new ByteArray(speexData)));
final int spent = (int) (System.currentTimeMillis() - ctime);
Log.d("花费时间"," "+spent);
}
}
1楼 whhpc19891120 2013-04-16 10:34发表 [回复]
您好,我最近也在学习流媒体服务,根据您的方法没有声音,android 发送到fms服务器, 转发给flex播放。
//根据定义好的几个配置,来获取合适的缓冲大小
int bufferSize = AudioRecord.getMinBufferSize(frequence, channelConfig, audioEncoding);
Log.d("bufferSize","" + bufferSize);
//实例化AudioRecord
AudioRecord record = new AudioRecord(source, frequence, channelConfig, audioEncoding, bufferSize);
//定义缓冲
buffer = new short[bufferSize];
data = new byte[size_bufferencode];
//开始录制
Log.d(AUDIO,"开始录制");
record.startRecording();

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多