基于Android手机的语音数据采集系统(语音数据自动上传至电脑端)

一、服务器端程序设计-->server.py

python 复制代码
import socket
import os

SAVE_DIR = "voice_uploads"
if not os.path.exists(SAVE_DIR):
    os.makedirs(SAVE_DIR)

def start_server(host, port):
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((host, port))
    server_socket.listen(5)
    print(f"服务已启动:{host}:{port}")

    while True:
        client_socket, addr = server_socket.accept()
        print(f"\n手机连接:{addr}")
        try:
            name_len = int.from_bytes(client_socket.recv(2), 'big')
            file_name = client_socket.recv(name_len).decode('utf-8')
            file_size = int.from_bytes(client_socket.recv(4), 'big')
            save_path = os.path.join(SAVE_DIR, file_name)

            received = 0
            with open(save_path, 'wb') as f:
                while received < file_size:
                    buffer = client_socket.recv(min(4096, file_size - received))
                    if not buffer: break
                    f.write(buffer)
                    received += len(buffer)

            print(f"接收完成:{save_path}")
        except Exception as e:
            print(f"错误:{e}")
        finally:
            client_socket.close()

if __name__ == "__main__":
    start_server(host = "192.168.1.10", port=8092)

二、Android手机端程序设计

1、界面程序设计-->res/layout/activity_main.xml

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="20dp">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="60dp"
        android:text="语音数据采集APP"
        android:textSize="12pt"
        android:textStyle="bold"
        android:textColor="#505050"
        android:layout_gravity="center"
        android:gravity="center_vertical">
    </TextView>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="80dp"
        android:layout_marginTop="30dp"
        android:layout_gravity="center_vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:text="服务器地址:"
            android:textSize="25sp"
            android:gravity="center_vertical">
        </TextView>
        <EditText
            android:id="@+id/et_ip"
            android:layout_width="240dp"
            android:layout_height="60dp"
            android:hint="请输入服务器ip地址"
            android:text="192.168.1.10"
            android:layout_marginLeft="5dp"
            android:textSize="25sp"
            android:textColor="#0000ff">
        </EditText>
    </LinearLayout>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="80dp"
        android:layout_marginTop="10dp"
        android:layout_gravity="center_vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:text="服务器端口:"
            android:textSize="25sp"
            android:gravity="center_vertical">
        </TextView>
        <EditText
            android:id="@+id/et_port"
            android:layout_width="240dp"
            android:layout_height="60dp"
            android:text="8092"
            android:layout_marginLeft="5dp"
            android:textSize="25sp"
            android:textColor="#0000ff">
        </EditText>
    </LinearLayout>
    <EditText
        android:id="@+id/et_label"
        android:layout_width="240dp"
        android:layout_height="60dp"
        android:hint="请输入数据标签"
        android:layout_gravity="center"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:textSize="30sp"
        android:textColor="#00ff00">
    </EditText>

    <Button
        android:id="@+id/btn_record"
        android:layout_width="240dp"
        android:layout_height="60dp"
        android:text="长按录音"
        android:textSize="25sp"
        android:layout_marginTop="15dp"
        android:layout_gravity="center"/>

    <Button
        android:id="@+id/btn_reset"
        android:layout_width="240dp"
        android:layout_height="60dp"
        android:text="重录"
        android:layout_marginTop="15dp"
        android:textSize="25sp"
        android:layout_gravity="center"/>

    <Button
        android:id="@+id/btn_upload"
        android:layout_width="240dp"
        android:layout_height="60dp"
        android:text="确认上传"
        android:layout_marginTop="15dp"
        android:textSize="25sp"
        android:layout_gravity="center"/>

    <TextView
        android:id="@+id/tv_status"
        android:layout_marginTop="30dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="状态:等待操作"
        android:textSize="25sp"
        android:layout_gravity="center"/>

</LinearLayout>

2、Java程序设计-->java/com.example.voicecollection/MainActivity.java

java 复制代码
package com.example.voicecollection;

import android.Manifest;
import android.content.pm.PackageManager;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.FileInputStream;
import java.net.Socket;

public class MainActivity extends AppCompatActivity {
    private String SERVER_IP;
    private int SERVER_PORT;
    private String label;
    private static final int REQUEST_AUDIO_PERMISSION = 1001;
    private EditText etIP,etPort,etLabel;
    private Button btnRecord, btnReset, btnUpload;
    private TextView tvStatus;
    private MediaRecorder mediaRecorder;
    private String currentFilePath;
    private boolean isRecording = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        etIP = findViewById(R.id.et_ip);
        etPort = findViewById(R.id.et_port);
        etLabel = findViewById(R.id.et_label);
        btnRecord = findViewById(R.id.btn_record);
        btnReset = findViewById(R.id.btn_reset);
        btnUpload = findViewById(R.id.btn_upload);
        tvStatus = findViewById(R.id.tv_status);

        requestAudioPermission();

        // 长按录音,松开停止 ------ 严格加状态判断,防止重复触发
        btnRecord.setOnTouchListener((v, event) -> {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                if (!isRecording) { // 只有没在录音,才开始
                    startRecord();
                }
            } else if (event.getAction() == MotionEvent.ACTION_UP) {
                if (isRecording) { // 只有正在录音,才停止
                    stopRecord();
                }
            }
            return true;
        });

        btnReset.setOnClickListener(v -> resetRecord());
        btnUpload.setOnClickListener(v -> uploadFile());
    }

    private void requestAudioPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_AUDIO_PERMISSION);
        }
    }

    private void startRecord() {
        if (isRecording) return;

        releaseRecorder(); // 先彻底释放之前的录音器

        label = etLabel.getText().toString().trim();
        if (label.isEmpty()) {
            tvStatus.setText("请输入标签");
            return;
        }

        try {
            File labelFolder = getExternalFilesDir(null);
            currentFilePath = labelFolder.getAbsolutePath() + "/"+label+"_" + System.currentTimeMillis() + ".wav";
            mediaRecorder = new MediaRecorder();
            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
            mediaRecorder.setAudioSamplingRate(16000);
            mediaRecorder.setOutputFile(currentFilePath);
            mediaRecorder.prepare();
            mediaRecorder.start();
            isRecording = true;
            btnRecord.setText("松开结束");
            tvStatus.setText("状态:录音中...");
        } catch (Exception e) {
            tvStatus.setText("录音失败");
            releaseRecorder();
        }
    }

    private void stopRecord() {
        if (!isRecording || mediaRecorder == null) {
            return;
        }

        // 先停止,再释放,顺序绝对不能乱
        try {
            mediaRecorder.stop();
        } catch (Exception ignored) {}

        releaseRecorder();
        btnRecord.setText("长按录音");
        tvStatus.setText("状态:录音完成,可上传或重录");
    }

    private void resetRecord() {
        releaseRecorder();
        if (currentFilePath != null) {
            new File(currentFilePath).delete();
            currentFilePath = null;
        }
        tvStatus.setText("状态:已重录,可以再次录音");
    }

    private void uploadFile() {
        if (currentFilePath == null) {
            tvStatus.setText("请先录音");
            return;
        }
        SERVER_IP = etIP.getText().toString().trim();
        SERVER_PORT = safeParseInt(etPort.getText().toString());

        if (SERVER_IP.isEmpty() || SERVER_PORT <= 0) {
            tvStatus.setText("请填写正确的IP和端口");
            return;
        }

        new Thread(() -> {
            try {
                runOnUiThread(() -> tvStatus.setText("状态:上传中..."));
                Socket socket = new Socket(SERVER_IP, SERVER_PORT);
                File file = new File(currentFilePath);
                String name = file.getName();
                long size = file.length();

                socket.getOutputStream().write(new byte[]{(byte) (name.length() >> 8), (byte) name.length()});
                socket.getOutputStream().write(name.getBytes());
                socket.getOutputStream().write(new byte[]{(byte) (size >> 24), (byte) (size >> 16), (byte) (size >> 8), (byte) size});

                FileInputStream fis = new FileInputStream(file);
                byte[] buffer = new byte[4096];
                int len;
                while ((len = fis.read(buffer)) != -1) {
                    socket.getOutputStream().write(buffer, 0, len);
                }
                fis.close();
                socket.close();
                runOnUiThread(() -> tvStatus.setText("状态:上传成功!"));
            } catch (Exception e) {
                runOnUiThread(() -> tvStatus.setText("上传失败"));
            }
        }).start();
    }

    private void releaseRecorder() {
        if (mediaRecorder != null) {
            try {
                mediaRecorder.reset();
                mediaRecorder.release();
            } catch (Exception ignored) {}
            mediaRecorder = null;
        }
        isRecording = false;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        releaseRecorder();
    }

    private int safeParseInt(String str) {
        try {
            return Integer.parseInt(str.trim());
        } catch (NumberFormatException e) {
            return 0;
        }
    }
}

3、权限设置-->AndroidManifest.xml

java 复制代码
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />

4、编译生成APK文件,在手机端安装

三、测试

1、运行server.py启动服务器

2、打开手机APP采集音频数据

相关推荐
不止二进制2 小时前
从 0 到 1 理解 LinearLayout:Android 布局入门实战
android
不止二进制3 小时前
Android |FrameLayout 帧布局实战 ——NeonLamp 霓虹灯效果详解
android
天行健,君子而铎3 小时前
联动闭环、精确、动态:医疗行业数据库审计与风险监测实践方案
网络·数据库
C++chaofan3 小时前
RPC框架SPI机制深度解析
java·网络·后端·网络协议·rpc·spi·序列化器
码云社区3 小时前
2026 年充电桩协议新趋势:云快充协议 3.0 有哪些升级亮点?
网络·充电桩·云快充·充电系统
stevenzqzq3 小时前
Kotlin 进阶指南:中缀函数 (Infix Function)
android·kotlin·compose
●VON3 小时前
Flutter组件深度解析:从基础到高级的完整指南
android·javascript·flutter·harmonyos·von
锋风Fengfeng3 小时前
安卓屏保调试2
android