AudioRecord和AudioTrack类是Android获取和播放音频流的重要类,放置在android.media包中。与该包中 的MediaRecorder和MediaPlayer类不同,AudioRecord和AudioTrack类在获取和播放音频数据流时无需通过文件保 存和文件读取,可以动态地直接获取和播放音频流,在实时处理音频数据流时非常有用。

当然,如果用户只想录音后写入文件或从文件中取得音频流进行播放,那么直接使用MediaRecorder和MediaPlayer类是首选方案,因为这 两个类使用非常方便,而且成功率很高。而AudioRecord和AudioTrack类的使用却比较复杂,我们发现很多人都不能成功地使用这两个类,甚 至认为Android的这两个类是不能工作的。

其实,AudioRecord和AudioTrack类的使用虽然比较复杂,但是可以工作,我们不仅可以很好地使用了这两个类,而且还通过套接字 (Socket)实现了音频数据的网络传输,做到了一端使用AudioRecord获取音频流然后通过套接字传输出去,而另一端通过套接字接收后使用 AudioTrack类播放。

下面是我们对AudioRecord和AudioTrack类在使用方面的经验总结:

(1)创建AudioRecord和AudioTrack类对象:创建这两个类的对象比较复杂,通过对文档的反复和仔细理解,并通过多次失败的尝试,并在 北理工的某个Android大牛的网上的文章启发下,我们也最终成功地创建了这两个类的对象。

创建AudioRecord和AudioTrack类对象的 代码如下:

AudioRecord类:m_in_buf_size =AudioRecord.getMinBufferSize(8000,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT);m_in_rec = new AudioRecord(MediaRecorder.AudioSource.MIC,8000,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT,m_in_buf_size) ;

AudioTrack类:m_out_buf_size = android.media.AudioTrack.getMinBufferSize(8000,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT);m_out_trk = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT,m_out_buf_size,AudioTrack.MODE_STREAM);

(2)关于AudioRecord和AudioTrack类的监听函数,不用也行。

(3)调试方面,包括初始化后看logcat信息,以确定类的工作状态,初始化是否成功等。

编写好代码,没有语法错误,调用模拟器运行、调试代码时,logcat发挥了很好的功用。刚调试时,经常会出现模拟器显示出现异常,这时我们可以在代码的 一些关键语句后添加如Log.d("test1","OK");这样的语句进行标识,出现异常时我们就可以在logcat窗口观察代码执行到哪里出现异 常,然后进行相应的修改、调试。模拟器不会出现异常时,又遇到了录放音的问题。录音方面,刚开始选择将语音编码数据存放在多个固定大小的文件中进行传送, 但是这种情况下会出现声音断续的现象,而且要反复的建立文件,比较麻烦,后来想到要进行网上传输,直接将语音编码数据以数据流的形式传送,经过验证,这种 方法可行并且使代码更加简洁。放音方面,将接收到的数据流存放在一个数组中,然后将数组中数据写到AudioTrack中。刚开始只是“嘟”几声,经过检 查发现只是把数据写一次,加入循环,让数据反复写到AudioTrack中,就可以听到正常的语音了。接下来的工作主要是改善话音质量与话音延迟,在进行 通话的过程中,观察logcat窗口,发现向数组中写数据时会出现Bufferflow的情况,于是把重心转移到数组大小的影响上,经过试验,发现 AudioRecord一次会读640个数据,然后就对录音和放音中有数组的地方进行实验修改。AudioRecord和AudioTrack进行实例化 时,参数中各有一个数组大小,经过试验这个数组大小和AudioRecord和AudioTrack能正常实例化所需的最小Buffer大小(即上面实例 化时的m_in_buf_size和m_out_buf_size参数)相等且服务器方进行缓存数据的数组尺寸是上述数值的2倍时,语音质量最好。由于录 音和放音的速度不一致,受到北理工大牛的启发,在录音方面,将存放录音数据的数组放到LinkedList中,当LinkedList中数组个数达到 2(这个也是经过试验验证话音质量最好时的数据)时,将先录好的数组中数据传送出去。经过上述反复试验和修改,最终使双方通话质量较好,且延时较短(大概 有2秒钟)。

(4)通过套接字传输和接收数据

数据传送部分,使用的是套接字。通信双方,通过不同的端口向服务器发送请求,与服务器连接上后,开始通话向服务器发送数据,服务器通过一个套接字接收到一 方的数据后,先存在一个数组中,然后将该数组中数据以数据流的形式再通过另一个套接字传送到另一方。这样就实现了双方数据的传送。

(5)代码架构

为避免反复录入和读取数据占用较多资源,使程序在进行录放音时不能执行其他命令,故将录音和放音各写成一个线程类,然后在主程序中,通过MENU控制通话的开始、停止、结束。

最后说明,AudioRecord和AudioTrack类可以用,只是稍微复杂些。以下贴出双方通信的源码,希望对大家有所帮助:

主程序Daudioclient:package cn.Daudioclient;import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;public class Daudioclient extends Activity {public static final int MENU_START_ID = Menu.FIRST ;public static final int MENU_STOP_ID = Menu.FIRST + 1 ;public static final int MENU_EXIT_ID = Menu.FIRST + 2 ;protected Saudioserver     m_player ;protected Saudioclient     m_recorder ;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);}public boolean onCreateOptionsMenu(Menu aMenu){boolean res = super.onCreateOptionsMenu(aMenu) ;aMenu.add(0, MENU_START_ID, 0, "START") ;aMenu.add(0, MENU_STOP_ID, 0, "STOP") ;aMenu.add(0, MENU_EXIT_ID, 0, "EXIT") ;return res ;}public boolean onOptionsItemSelected(MenuItem aMenuItem){switch (aMenuItem.getItemId()) {case MENU_START_ID:{m_player = new Saudioserver() ;m_recorder = new Saudioclient() ;m_player.init() ;m_recorder.init() ;m_recorder.start() ;m_player.start() ;}break ;case MENU_STOP_ID:{ m_recorder.free() ;m_player.free() ;m_player = null ;m_recorder = null ;}break ;case MENU_EXIT_ID:{int pid = android.os.Process.myPid() ;android.os.Process.killProcess(pid) ;}break ;default:break ;}return super.onOptionsItemSelected(aMenuItem);}
}

录音程序Saudioclient:package cn.Daudioclient;import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.LinkedList;import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;public class Saudioclient extends Thread
{protected AudioRecord m_in_rec ;protected int         m_in_buf_size ;protected byte []     m_in_bytes ;protected boolean     m_keep_running ;protected Socket      s;protected DataOutputStream dout;protected LinkedList<byte[]>  m_in_q ;public void run(){try{byte [] bytes_pkg ;m_in_rec.startRecording() ;while(m_keep_running){m_in_rec.read(m_in_bytes, 0, m_in_buf_size) ;bytes_pkg = m_in_bytes.clone() ;if(m_in_q.size() >= 2){dout.write(m_in_q.removeFirst() , 0, m_in_q.removeFirst() .length);}m_in_q.add(bytes_pkg) ;}m_in_rec.stop() ;m_in_rec = null ;m_in_bytes = null ;dout.close();}catch(Exception e){e.printStackTrace();}}public void init(){m_in_buf_size =  AudioRecord.getMinBufferSize(8000,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT);m_in_rec = new AudioRecord(MediaRecorder.AudioSource.MIC,8000,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT,m_in_buf_size) ;m_in_bytes = new byte [m_in_buf_size] ;m_keep_running = true ;m_in_q=new LinkedList<byte[]>();try{s=new Socket("192.168.1.100",4332);dout=new DataOutputStream(s.getOutputStream());//new Thread(R1).start();
  }catch (UnknownHostException e){// TODO Auto-generated catch block
   e.printStackTrace();}catch (IOException e){// TODO Auto-generated catch block
   e.printStackTrace();}}public void free(){m_keep_running = false ;try {Thread.sleep(1000) ;} catch(Exception e) {Log.d("sleep exceptions...\n","") ;}}
}

放音程序Saudioserver:package cn.Daudioclient;import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;public class Saudioserver extends Thread
{ protected AudioTrack m_out_trk ;protected int        m_out_buf_size ;protected byte []    m_out_bytes ;protected boolean    m_keep_running ;private Socket s;private DataInputStream din;public void init(){try{s=new Socket("192.168.1.100",4331);din=new DataInputStream(s.getInputStream());m_keep_running = true ;m_out_buf_size = AudioTrack.getMinBufferSize(8000,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT);m_out_trk = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT,m_out_buf_size,AudioTrack.MODE_STREAM);m_out_bytes=new byte[m_out_buf_size];// new Thread(R1).start();
          }catch(Exception e){e.printStackTrace();}}public void free(){m_keep_running = false ;try {Thread.sleep(1000) ;} catch(Exception e) {Log.d("sleep exceptions...\n","") ;}}public void run(){byte [] bytes_pkg = null ;m_out_trk.play() ;while(m_keep_running) {try{din.read(m_out_bytes);bytes_pkg = m_out_bytes.clone() ;m_out_trk.write(bytes_pkg, 0, bytes_pkg.length) ;}catch(Exception e){e.printStackTrace();}}m_out_trk.stop() ;m_out_trk = null ;try {din.close();} catch (IOException e) {// TODO Auto-generated catch block
    e.printStackTrace();}}
}

AudioRecord结构继承关系public class AudioRecord extends Objectjava.lang.Objectandroid.media.AudioRecord类概述AudioRecord类在Java应用程序中管理音频资源,用来记录从平台音频输入设备产生的数据。 通过AudioRecord对象来完成"pulling"(读取)数据。 应用通过以下几个方法负责立即从AudioRecord对象读取: read(byte[], int, int), read(short[], int, int)或read(ByteBuffer, int). 无论使用哪种音频格式,使用AudioRecord是最方便的。在创建AudioRecord对象时,AudioRecord会初始化,并和音频缓冲区连接,用来缓冲新的音频数据。 根据构造时指定的缓冲区大小,来决定AudioRecord能够记录多长的数据。 从硬件设备读取的数据,应小于整个记录缓冲区。内部类interface          AudioRecord.OnRecordPositionUpdateListener接口定义为:当AudioRecord 收到一个由setNotificationMarkerPosition(int)设置的通知标志,或由 setPositionNotificationPeriod(int)设置的周期更新记录的进度状态时,回调此接口。常量public static final int ERROR表示操作失败。常量值: -1 (0xffffffff)public static final int ERROR_BAD_VALUE表示使用了一个不合理的值导致的失败。常量值: -2 (0xfffffffe)public static final int ERROR_INVALID_OPERATION表示不恰当的方法导致的失败。常量值: -3 (0xfffffffd)public static final int RECORDSTATE_RECORDING指示AudioRecord录制状态为“正在录制”。常量值: 3 (0x00000003)public static final int RECORDSTATE_STOPPED指示AudioRecord录制状态为“不在录制”。常量值: 1 (0x00000001)public static final int STATE_INITIALIZED指示AudioRecord准备就绪。常量值: 1 (0x00000001)public static final int STATE_UNINITIALIZED指示AudioRecord状态没有初始化成功。常量值: 0 (0x00000000)public static final int SUCCESS表示操作成功。常量值: 0 (0x00000000)构造函数public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)类构造函数。参数audioSource     录制源。 请见MediaRecorder.AudioSource录制源定义。sampleRateInHz      默认采样率,单位Hz。 44100Hz是当前唯一能保证在所有设备上工作的采样率,在一些设备上还有22050, 16000或11025。channelConfig          描述音频通道设置。 请见CHANNEL_IN_MONO 和 CHANNEL_IN_STEREO。 CHANNEL_IN_MONO保证能在所有设备上工作。audioFormat             音频数据保证支持此格式。 请见ENCODING_PCM_16BIT 和ENCODING_PCM_8BIT。bufferSizeInBytes    在录制过程中,音频数据写入缓冲区的总数(字节)。 从缓冲区读取的新音频数据总会小于此值。 getMinBufferSize(int, int, int)返回AudioRecord 实例创建成功后的最小缓冲区。 设置的值比getMinBufferSize()还小则会导致初始化失败。异常IllegalArgumentException公共方法public int getAudioFormat ()返回设置的音频数据格式。 请见ENCODING_PCM_16BIT 和ENCODING_PCM_8BIT。public int getAudioSource ()返回音频录制源。参见MediaRecorder.AudioSourcepublic int getChannelConfiguration ()返回设置的频道设置。 请见CHANNEL_IN_MONO和CHANNEL_IN_STEREO。public int getChannelCount ()返回设置的频道数目。public static int getMinBufferSize (int sampleRateInHz, int channelConfig, int audioFormat)返回成功创建AudioRecord对象所需要的最小缓冲区大小。 注意:这个大小并不保证在负荷下的流畅录制,应根据预期的频率来选择更高的值,AudioRecord实例在推送新数据时使用此值。参数sampleRateInHz      默认采样率,单位Hz。channelConfig           描述音频通道设置。请见CHANNEL_IN_MONO和CHANNEL_IN_STEREO。audioFormat             音频数据保证支持此格式。参见ENCODING_PCM_16BIT。返回值如果硬件不支持录制参数,或输入了一个无效的参数,则返回ERROR_BAD_VALUE,如果硬件查询到输出属性没有实现,或最小缓冲区用byte表示,则返回ERROR。参见更多信息请见有效的设置参数public int getNotificationMarkerPosition ()返回通知,标记框架中的位置。public int getPositionNotificationPeriod ()返回通知,更新框架中的时间位置。public int getRecordingState ()返回AudioRecord实例的录制状态。参见RECORDSTATE_STOPPEDRECORDSTATE_RECORDINGpublic int getSampleRate ()返回设置的音频数据样本采样率,单位Hz。public int getState ()返回AudioRecord实例的状态。 这点非常有用,用在AudioRecord 实例创建成功后,检查初始化属性。 它能肯定请求到了合适的硬件资源。参见STATE_INITIALIZEDSTATE_UNINITIALIZEDpublic int read (short[] audioData, int offsetInShorts, int sizeInShorts)从音频硬件录制缓冲区读取数据。参数audioData        写入的音频录制数据。offsetInShorts           目标数组 audioData 的起始偏移量。sizeInShorts              请求读取的数据大小。返回值返回short型数据,表示读取到的数据,如果对象属性没有初始化,则返回ERROR_INVALID_OPERATION,如果参数不能解析成有效的数据或索引,则返回ERROR_BAD_VALUE。 返回数值不会超过sizeInShorts。public int read (byte[] audioData, int offsetInBytes, int sizeInBytes)从音频硬件录制缓冲区读取数据。参数audioData        写入的音频录制数据。offsetInBytes            audioData的起始偏移值,单位byte。sizeInBytes                读取的最大字节数。返回值读入缓冲区的总byte数,如果对象属性没有初始化,则返回ERROR_INVALID_OPERATION,如果参数不能解析成有效的数据或索引,则返回ERROR_BAD_VALUE。 读取的总byte数不会超过sizeInBytes。public int read (ByteBuffer audioBuffer, int sizeInBytes)从音频硬件录制缓冲区读取数据,直接复制到指定缓冲区。 如果audioBuffer不是直接的缓冲区,此方法总是返回0。参数audioBuffer               存储写入音频录制数据的缓冲区。sizeInBytes                请求的最大字节数。返回值读入缓冲区的总byte数,如果对象属性没有初始化,则返回ERROR_INVALID_OPERATION,如果参数不能解析成有效的数据或索引,则返回ERROR_BAD_VALUE。 读取的总byte数不会超过sizeInBytes。public void release ()释放本地AudioRecord资源。 对象不能经常使用此方法,而且在调用release()后,必须设置引用为null。public int setNotificationMarkerPosition (int markerInFrames)如果设置了 setRecordPositionUpdateListener(OnRecordPositionUpdateListener)或 setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler),则通知监听者设置位置标记。参数markerInFrames      在框架中快速标记位置。返回值返回错误或成功代码,请见SUCCESS、ERROR_BAD_VALUE、ERROR_INVALID_OPERATION。public int setPositionNotificationPeriod (int periodInFrames)如果设置了 setRecordPositionUpdateListener(OnRecordPositionUpdateListener)或 setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler),则通知监听者设置时间标记。参数markerInFrames      在框架中快速更新时间标记。返回值返回错误或成功代码,请见SUCCESS、ERROR_INVALID_OPERATION。public void setRecordPositionUpdateListener (AudioRecord.OnRecordPositionUpdateListener listener, Handler handler)当之前设置的标志已经成立,或者周期录制位置更新时,设置处理监听者。 使用此方法来将Handler 和别的线程联系起来,来接收AudioRecord 事件,比创建AudioTrack 实例更好一些。参数handler    用来接收事件通知消息。public void setRecordPositionUpdateListener (AudioRecord.OnRecordPositionUpdateListener listener)当之前设置的标志已经成立,或者周期录制位置更新时,设置处理监听者。public void startRecording ()AudioRecord实例开始进行录制。异常IllegalStateException受保护方法protected void finalize ()通知VM回收此对象内存。 此方法只能用在运行的应用程序没有任何线程再使用此对象,来告诉垃圾回收器回收此对象。此方法用于释放系统资源,由垃圾回收器清除此对象。 默认没有实现,由VM来决定,但子类根据需要可重写finalize()。 在执行期间,调用此方法可能会立即抛出未定义异常,但是可以忽略。注意:VM保证对象可以一次或多次调用finalize(),但并不保证finalize()会马上执行。 例如,对象B的finalize()可能延迟执行,等待对象A的finalize()延迟回收A的内存。 为了安全起见,请看ReferenceQueue,它提供了更多地控制VM的垃圾回收。

转载于:https://www.cnblogs.com/niray/p/4251406.html

android通过数组,流播放声音的方法,音频实时传输相关推荐

  1. Android音频实时传输与播放(四):源码下载(问题更新)【转】

    Android音频实时传输与播放(四):源码下载(问题更新) 激动人心的时刻到了有木有 ^_^ 服务端下载请点击这里,客户端下载请点击这里! 最近有朋友在下载源码使用之后,说播放出来的声音噪声很大.其 ...

  2. 【局域网音频实时传输、屏幕单播及广播】

    局域网音频实时传输.屏幕单播及广播 程序源代码及可执行程序链接: https://gitee.com/azhezhezhezhe/DesktopSharing/tree/master/ 设计思路 教师 ...

  3. java音频实时传输_会议室智能系统建设方案,实时远程视频协作

    2019年,预计会议协作需求将持续增长,创建多功能会议室促进本地.异地协作仍然是一个强大的趋势.无论空间大小或距离远近,政府部门.企业单位以及团体组织为了实现决策指令畅通.管理层次分明,需要通过对会议 ...

  4. 知识点2:js(javascript)中检测是否为数组的两种方法【翻转数组案例】

    javascript基础知识 文章目录 javascript基础知识 前言 一.翻转数组案例 二.检测数组的两种方法 1.如果传输的参数不是数组 2.instanceof 运算符 可以用来检测是否满足 ...

  5. android实时传输视频Socket

    android实时传输视频Socket https://download.csdn.net/download/u012560682/7780979?spm=1001.2101.3001.5697 An ...

  6. 树莓派实时音频采集并实时传输至ubuntu

    前言 想通过usb音频采集卡连接树莓派后实时采集音频,并通过音频流的方式将采集到的音频实时传输到另外一台电脑. 什么是树莓派? 树莓派是一款基于ARM的微型电脑主板,以SD/MicroSD卡为内存硬盘 ...

  7. android给数组添加新元素_重磅!超详细的 JS 数组方法整理出来了

    作者:Yushiahttps://juejin.cn/post/6907109642917117965 数组是 js 中最常用到的数据集合,其内置的方法有很多,熟练掌握这些方法,可以有效的提高我们的工 ...

  8. Android Multimedia框架总结(十七)音频开发基础知识

    原文链接:http://blog.csdn.net/hejjunlin/article/details/53078828 近年来,唱吧,全民K歌,QQ音乐,等成为音频软件的主流力量,音频开发一直是多媒 ...

  9. Android音视频学习系列(五) — 掌握音频基础知识并使用AudioTrack、OpenSL ES渲染PCM数据

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

最新文章

  1. 文件还原工具Foremost
  2. 【Android 应用开发】Android中使用ViewPager制作广告栏效果 - 解决ViewPager占满全屏页面适配问题
  3. 根据信号灯状态解决网络故障
  4. /MD, /MDD, /ML, /MT,/MTD(使用运行时库)
  5. JavaScrip有哪些优点
  6. 设置Java EE 6开发环境
  7. 【学习Android NDK开发】Java通过JNI调用native方法
  8. 一般技术书籍出版版税多少_如何为您的技术书籍寻找出版商
  9. linux的创建线程池,Linux下通用线程池的创建与使用(上) (3)
  10. Raspbian 源替换
  11. loadrunner中定义数组
  12. 遗传算法python
  13. jdk和cglib动态代理
  14. java 前后端分离教程,Java web前后端分离
  15. ESXI 7.0 版本配置N卡显卡直通
  16. 【专题3:电子工程师 之 上位机】 之 【47.使用QT Opengl显示YUV图像】
  17. 计算机辅助小学数学教学的研究,计算机辅助小学数学教学研究.doc
  18. 矩池云 | Tony老师解读Kaggle Twitter情感分析案例
  19. 人工生命全景图:如何创造出超越人工智能的生命系统
  20. 【JS】Math对象随机数方法

热门文章

  1. OCP读书笔记(16) - 管理资源
  2. freebsd 运维人员
  3. 动态规划-装配线调度
  4. where is lingang city in shanghai?
  5. 无需predetermine一条路
  6. 剑桥管理学老哥的研究生申请和人生道路规划观念
  7. Python cv2 摄像头
  8. mysql varchar 225 和 varchar 60 区别
  9. 《云栖精选》第8期:科技,改变世界
  10. 内存分配与数据格式化(malloc与new)