在阅读这篇文章之前,最好可以看一下这篇文章WoMic虚拟麦克风技术剖析。
这篇文章介绍Womic的技术原理,因此这篇文章按照这个技术路线实现简单的WoMic。

WoMic由三个部分进行介绍:
1.虚拟声卡
2.PC端
3.Android 端

虚拟声卡

Womic采用自己开发的虚拟声卡,但是这里我们采用开源的虚拟声卡 Virtual Audio Cables,它是一款免费个人使用的虚拟声卡,它在虚拟麦克风中主要作用就是输入数据并从虚拟麦克中输出声音,应用就可以自由从虚拟声卡中读取数据。

自行从网站上下载并安装,我相信这一点不会难到你,若是安装有问题你回复评论。

Android 端

Android端是用于录制声音的,也就是作为麦克风。Android 采用的AudioTrack获取声音的pcm流,采用的是tcp方式进行传输pcm流,不了解网络传输的可以参考这篇文章Android中socket(tcp|udp),websocket基本使用。

设置AudioTrack 频率,声道,码率等等,这些必须也pc端保持一致,不然出来的声音会是噪音。

  • 声音采集
    下面是声音采集类AudioTrackUtil.class:
public class AudioTrackUtil {AudioRecord mAudioRecord;private Integer mRecordBufferSize;private boolean mWhetherRecord;private void initMinBufferSize() {//获取每一帧的字节流大小mRecordBufferSize = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_8BIT);}public AudioTrackUtil() {initMinBufferSize();if (mAudioRecord == null) {mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_8BIT, mRecordBufferSize);}}public void startRecord() {mWhetherRecord = true;new Thread(new Runnable() {@Overridepublic void run() {mAudioRecord.startRecording();//开始录制try {byte[] bytes = new byte[mRecordBufferSize];while (mWhetherRecord) {mAudioRecord.read(bytes, 0, bytes.length);//读取流mIAudioCallBack.callBack(bytes);}Log.e("test", "run: 暂停录制");mAudioRecord.stop();//停止录制} catch (Exception e) {e.printStackTrace();mWhetherRecord = false;mAudioRecord.stop();}}}).start();}void stopRecord() {if (mAudioRecord != null) {mAudioRecord.stop();mAudioRecord.release();}}public IAudioCallBack mIAudioCallBack;public interface IAudioCallBack {void callBack(byte[] o);}public void setIAudioCallBackListener(IAudioCallBack audioCallBack) {mIAudioCallBack = audioCallBack;}
}
  • 通过TCP方式发送pcm流
    通过Socket类,设置目标IP和Port,通过
    socket.getOutputStream().write(msg);
    socket.getOutputStream().flush();

    进行写数据,并发送到服务端,这里就是pc端。代码如下:
public class MainActivity extends AppCompatActivity {private Button mBtnSendMessage;public Socket socket;private AudioTrackUtil mAudioTrackUtil;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mBtnSendMessage = findViewById(R.id.btn_start_send_message);mAudioTrackUtil = new AudioTrackUtil();final String ip = "192.168.40.57";//10.5.169.16服务端的IP地址;10.5.206.92final int port = 5678;//自己定义个端口号要与服务端匹配。startClient(ip, port);if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED ||ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE}, 1);}mBtnSendMessage.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (socket == null) {startClient(ip, port);}if (socket.isConnected()) {mAudioTrackUtil.startRecord();mAudioTrackUtil.setIAudioCallBackListener(new AudioTrackUtil.IAudioCallBack() {@Overridepublic void callBack(byte[] o) {sendTcpMessage(o);}});}}});findViewById(R.id.btn_stop_send_message).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (socket != null) {try {sendTcpMessage("我要结束了".getBytes());socket.close();} catch (IOException e) {e.printStackTrace();}}}});}@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);mAudioTrackUtil.startRecord();}public void startClient(final String address, final int port) {if (address == null) {return;}if (socket == null) {new Thread(new Runnable() {@Overridepublic void run() {try {Log.i("tcp", "启动客户端");socket = new Socket(address, port);Log.i("tcp", "客户端连接成功");InputStream inputStream = socket.getInputStream();byte[] buffer = new byte[1024];int len = -1;while ((len = inputStream.read(buffer)) != -1) {String data = new String(buffer, 0, len);Log.i("tcp", "收到服务器的数据-------------------:" + data);}Log.i("tcp", "客户端断开连接");} catch (Exception EE) {EE.printStackTrace();Log.i("tcp", "客户端无法连接服务器" + EE.getMessage());} finally {try {if (socket != null) {socket.close();}} catch (IOException e) {e.printStackTrace();}socket = null;}}}).start();}}public void sendTcpMessage(final byte[] msg) {if (socket != null && socket.isConnected()) {new Thread(new Runnable() {@Overridepublic void run() {try {socket.getOutputStream().write(msg);socket.getOutputStream().flush();} catch (IOException e) {e.printStackTrace();}}}).start();}}@Overrideprotected void onDestroy() {super.onDestroy();try {if (socket != null) {socket.close();}} catch (IOException e) {e.printStackTrace();}mAudioTrackUtil.stopRecord();}}

pc端

第一步,获取虚拟声卡,需要设置采样频率,样本位数,声道等,需要和Android的参数一致,不然是噪音。

public class AudioVirtualCableUtile {SourceDataLine auline = null;public AudioVirtualCableUtile() {AudioFormat.Encoding encoding = new AudioFormat.Encoding("PCM_SIGNED");AudioFormat format = new AudioFormat(encoding,8000,16,2,2,8000,true);//编码格式,采样率,每个样本的位数,声道,帧长(字节),帧数,是否按big-endian字节顺序存储DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);try {Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo();Mixer mixer = null;for (int i = 0; i < mixerInfos.length; i++) {System.out.println(mixerInfos[i]);if (mixerInfos[i].getName().equals("CABLE Input (VB-Audio Virtual Cable)")) {mixer = AudioSystem.getMixer(mixerInfos[i]);System.out.println("get successful");}}auline = (SourceDataLine) mixer.getLine(info);auline.open(format);} catch (LineUnavailableException e) {e.printStackTrace();return;} catch (Exception e) {e.printStackTrace();return;}}SourceDataLine getAudioLine() {return auline;}

第二步,获取从Android过来的pcm流,并写入到虚拟声卡中。

private static ServerSocket mServerSocket;private static Socket mSocket;private static AudioVirtualCableUtile pcmUtile = new AudioVirtualCableUtile();public static void main(String args[]) {startServer();}static void startServer() {if (mServerSocket == null) {new Thread(new Runnable() {@Overridepublic void run() {try {mServerSocket = new ServerSocket(5678);System.out.println("tcp connecting");mSocket = mServerSocket.accept();System.out.println("tcp connected");InputStream inputStream = mSocket.getInputStream();byte[] buffer = new byte[1024];int len = -1;
//pcmUtile.getAudioLine().start();while ((len = inputStream.read(buffer)) != -1) {pcmUtile.getAudioLine().write(buffer, 0, len);//写入到虚拟声卡中System.out.println("----" + System.currentTimeMillis());}} catch (IOException e) {e.printStackTrace();try {mSocket.close();mServerSocket.close();} catch (IOException e1) {e1.printStackTrace();}}}}).start();}}
}

运行pc和Android代码,你会发现麦克有音量变化

到此代码基本已经完成,还需要持续优化,本篇文章中的代码纯demo,没有过多考虑,只要功能满足即可。

备注:
需要手动切换虚拟声卡。需要先启动pc端,在启动Android,因为pc端为服务端。

简易版WoMic(二)相关推荐

  1. 传统公司部署OpenStack(t版)简易介绍(二)——Keystone组件部署

    传统公司部署OpenStack(t版)简易介绍(二)--Keystone组件部署 一.OpenStack组件安装的顺序 二.创建数据库实例和数据库用户(ct控制节点) 三.安装.配置keystone. ...

  2. c语言实现学生二科成绩的单链表,c++链表实现学生成绩管理系统(简易版)

    #include using namespace std; typedef struct student{ int id;//学号 string sex; string name; int cpp;/ ...

  3. 从封装函数到实现简易版自用jQuery (一)

    温馨提示 本文阅读对象: 对 JavaScript 有一定的了解,如果你没有学过或者忘记 JavaScript 某些操作,请看 阮一峰 JavaScript 教程 . 导语 DOM 有许多 API , ...

  4. 肝一波 ~ 手写一个简易版的Mybatis,带你深入领略它的魅力!

    零.准备工作 <dependencies><dependency><groupId>mysql</groupId><artifactId>m ...

  5. emmc固件开发_UP2开发板简易开箱(二)

    在 @老狼 的x86开源硬件群里面抽中了一个UP2开发板,时间已经过去半年多了,实在惭愧,其中一部分原因是菜,菜是原罪.现接上一位抽中UP2的大佬 @Jiaao Bai 的文章,本文就叫UP2开发板简 ...

  6. 基于Zookeeper实现简易版服务的注册与发现机制

    一.功能要求 基于Zookeeper实现简易版服务的注册与发现机制 启动2个服务端 将服务端IP和端口信息注册到Zookeeper上 启动1个客户端 从Zookeeper中获取2个服务端节点信息 客户 ...

  7. 实战:自定义简易版SpringBoot

    实战:自定义简易版SpringBoot 一.功能要求 自定义简易版SpringBoot,实现SpringBoot MVC及内嵌Tomcat启动.DispatcherServlet注册和组件扫描功能 程 ...

  8. 依赖注入[5]: 创建一个简易版的DI框架[下篇]

    为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]> ...

  9. 【C语言初阶】——简易版·扫雷(9*9)【运行逻辑思维导图+细节讲解+源码】【初级】

    目录 一.扫雷游戏的运行逻辑 二.代码逻辑讲解+源码 1.打印一个简易的游戏开始菜单 2.创建数组储存数据并初始化数组 代码逻辑讲解 源码 3.布置雷 代码逻辑讲解 源码 4.排雷 代码逻辑 源码 三 ...

  10. php mailer altbody,PHP_phpmailer 中文使用说明(简易版),phpmailer v5.1下载 A开头: $AltBody - phpStudy...

    phpmailer 中文使用说明(简易版) phpmailer v5.1下载 A开头: $AltBody--属性 出自:PHPMailer::$AltBody 文件:class.phpmailer.p ...

最新文章

  1. BZOJ 4584 [Apio2016]赛艇
  2. 《研磨设计模式》chap20 享元模式 Flyweight (4)总结
  3. Apollo2.5摄像头安装
  4. 手动打开和关闭windows的相关服务
  5. [react] react非父子组件如何通信?
  6. 对症下药,方能药到病除——如何修复drv?
  7. egg风格 什么意思_egg框架学习笔记
  8. C++ 函数参数中和区别
  9. for循环中包含跨服务查询优化
  10. 什么软件画er图方便_ER模型怎么画?必备入门级模型图绘制软件
  11. JavaScript 调用 Windows Win32 API
  12. AutoCAD 命令定义
  13. 打开JMeter报错:Could not reserve enough space for 1048576KB object heap
  14. QT Creator 5.1.2中英文切换
  15. 分享:git push 时报错 Permission to username/My_python.git denied to deploy key 解决方法
  16. 史上最全运放运算放大器知识讲解(附主流厂商)
  17. HTML中span标签使用详解含多种实例(转)
  18. 用Java解决牛客网题目BC30kiki和酸奶
  19. 纸牌游戏洗牌发牌排序算法设计
  20. 利用python绘制简易词云图(使用jieba进行中文分词)

热门文章

  1. 掌握这些插件,分分钟提高你的办公效率90%!
  2. 2020.01.18【NOIP提高组】模拟B 组——总结——探险者拉罗
  3. redis集群 原理
  4. 联想电脑硬盘保护系统EDU8.0.1iso安装
  5. 什么是自然语言处理技术
  6. html制作清明上河图
  7. 微信企业号开发接口文档
  8. C++学习(四六六)Multiple parse contexts are available for this file
  9. win10自带抓包工具_Win10商店抓包工具(UWP挖掘机)下载 v1.1
  10. 两分钟教你玩转千图成像Part1~