2012-12-19 19:38
7928人阅读
评论(4)
收藏
举报
分类:
先说下实现原理,手机采集到语音后进过Speex编码,通过juv以直播形式发布自己的语音流到red5,也是通过juv播放对方的直播流,经过Speex解码后输出到扬声器,如下图:

Android端采集编码和解码播放Speex,参考android-recorder,至于他用的red5客户端,看了下,没看明白。。。
JUV这库吧 好上手,虽然是付费的,但是有30天的试用,可长期申请。暂时用下也不错。另外,国内有破解版,你懂得。
核心代码如下:
- public class AudioCenter extends AbstractMicrophone
首先,音频处理类继承自JUV库中的 AbstractMicrophone,便可以使用 fireOnAudioData方法向Red5服务端发布音频数据。
- public void encSpeexAudio() {
- new Thread(new Runnable() {
-
- @Override
- public void run() {
- int bufferSize = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
- short[] mAudioRecordBuffer = new short[bufferSize];
- AudioRecord mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
- mAudioRecord.startRecording();
- int bufferRead = 0;
- int len;
- isEncoding = true;
-
- while (isEncoding) {
- bufferRead = mAudioRecord.read(mAudioRecordBuffer, 0, frameSize);
-
- if (bufferRead > 0) {
- try {
- len = speex.encode(mAudioRecordBuffer, 0, processedData, frameSize);
- // LogHelper.d(subTAG, "EncSpeexAudio "+ len);
- byte[] speexData = new byte[len + 1];
- System.arraycopy(SpeexRtmpHead, 0, speexData, 0, 1);
- System.arraycopy(processedData, 0, speexData, 1, len);
- fireOnAudioData(new MediaDataByteArray(20, new ByteArray(speexData)));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- mAudioRecord.stop();
- mAudioRecord.release();
- mAudioRecord = null;
- }
- }, "EncSpeexAudio Thread").start();
- }
以上是编码上传线程,一个关键技术点:SpeexRtmpHead,曾经困扰我很长一段时间,也是因为自己刚开始解除对RTMP协议只停留在api调用层面。
- private byte[] SpeexRtmpHead = new byte[] { (byte) 0xB2 };
我们首先要知道“RTMP Packet中封装的音视频数据流,其实和FLV封装音频和视频数据的方式是相同的。”
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的配置。
- public void playSpeexAudio() {
- new Thread(new Runnable() {
- @Override
- public void run() {
- short[] decData = new short[256];
- AudioTrack audioTrack;
- int bufferSizeInBytes = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
- audioTrack = new AudioTrack(AudioManager.STREAM_VOICE_CALL, 8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, 2 * bufferSizeInBytes, AudioTrack.MODE_STREAM);
- audioTrack.play();
- isPlaying = true;
- while (isPlaying) {
- while (encData.size() > 0) {
- byte[] data = encData.elementAt(0);
- encData.removeElementAt(0);
- int dec;
- dec = speex.decode(data, decData, data.length);
- // LogHelper.d(subTAG, "playSpeexAudio "+ dec);
- if (dec > 0) {
- audioTrack.write(decData, 0, dec);
- }
- }
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- }
- audioTrack.stop();
- audioTrack.release();
- audioTrack = null;
-
- }
- }, "PlaySpeexAudio Thread").start();
- }
以上为音频解码线程,没什么难点,只需注意我使用了一个Vector 来缓存juv拉下来的speex语音数据
private Vector encData = new Vector();
采集编码解码播放先谈到这里,下篇讲下juv的连接和数据传输相关。
如有疑问,欢迎与我交流。
- 猜你在找
- 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();
|