简易版WoMic(二)

在阅读这篇文章之前,最好可以看一下这篇文章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() {
            @Override
            public 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;

    @Override
    protected 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.92
        final 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() {
            @Override
            public void onClick(View v) {
                if (socket == null) {
                    startClient(ip, port);
                }
                if (socket.isConnected()) {
                    mAudioTrackUtil.startRecord();
                    mAudioTrackUtil.setIAudioCallBackListener(new AudioTrackUtil.IAudioCallBack() {
                        @Override
                        public void callBack(byte[] o) {
                            sendTcpMessage(o);
                        }
                    });
                }
            }
        });

        findViewById(R.id.btn_stop_send_message).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (socket != null) {
                    try {
                        sendTcpMessage("我要结束了".getBytes());
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
        });
    }

    @Override
    protected 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() {
                @Override
                public 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() {
                @Override
                public void run() {
                    try {
                        socket.getOutputStream().write(msg);
                        socket.getOutputStream().flush();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

    @Override
    protected 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() {
                @Override
                public 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端为服务端。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
ESP32简易版SQLite是一种针对ESP32芯片开发的轻量级关系型数据库。SQLite是一款嵌入式数据库管理系统,相比于传统的客户端-服务器模式的数据库系统,其特点是不需要独立的服务器进程,与应用程序集成在一起,可以直接在设备上进行数据存储和查询操作。 ESP32简易版SQLite具有以下特点: 1. 轻量级:ESP32具有较小的内存和存储容量,简易版SQLite采用了精简的设计,使得其不占用过多的资源,适合在资源有限的嵌入式设备上使用。 2. 支持基本的SQL语法:SQLite支持常见的SQL语句,如创建表、插入数据、查询数据等。开发者可以使用SQL语句对数据进行操作,方便简洁。 3. 数据安全:ESP32简易版SQLite支持事务处理,可以确保数据的一致性和安全性。在多线程或多任务环境下,多个操作可以在一个事务中进行,避免数据冲突和损坏。 4. 快速响应:ESP32简易版SQLite具有高性能的特点,可以快速响应查询请求。相比于传统的数据库系统,其无需与远程服务器通信,减少了网络延迟,提高了查询的效率。 5. 易于集成和开发:ESP32简易版SQLite的API简单易用,开发者可以轻松地集成到ESP32的应用程序中。同时,SQLite提供了丰富的开发工具和支持文档,方便开发者进行调试和开发。 尽管ESP32简易版SQLite功能相对有限,但对于嵌入式设备而言,其提供了一种可行的数据存储和管理方案。通过合理使用ESP32简易版SQLite,开发者可以在资源受限的环境中进行高效的数据处理,满足应用需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术人Howzit

钱不钱的无所谓,这是一种鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值