Kotlin 写的环形 ByteBuffer,理论上是线程安全的。

本文介绍了一种环形缓冲区的实现方法,通过一个自定义的CircleByteBuffer类,支持数组的字节查看、写入和读取操作。该缓冲区在单线程写入和读取场景下表现良好,但多线程环境下需自行加锁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

class CircleByteBuffer(val size:Int) {
    private val datas=ByteArray(size)

    private var start=0
    private var end=0


    fun getLen():Int{
        if (start==end)
            return 0
        else if (start<end)
            return end-start
        else
            return start-end
    }

    fun getFree():Int{
        return size-getLen()
    }

    fun put(e:Byte){
        datas[end]=e
        val pos=end+1
        if (pos==size)
            end=0
        else if (pos==start)
            throw Exception("out buffer")
        else
            end=pos
    }
    fun get():Byte{
        if(getLen()==0)
            throw Exception("out buffer")
        val ret=datas[start]
        if (++start==size)
            start=0
        return ret
    }
    fun get(i:Int):Byte{
        if(i>=getLen())
            throw Exception("out buffer")
        var pos=start+i
        if (pos>=size)
            pos-=size
        return datas[pos]
    }

    fun puts(bts:ByteArray,ind:Int,len:Int){
        for (i in ind until len){
            put(bts[i])
        }
    }
    fun gets(len:Int):ByteArray{
        val bts=ByteArray(len)
        for (i in 0 until len){
            bts[i]=get()
        }
        return bts
    }
}

    环形缓冲区,可查看数组的某个byte,写入,读取。

理论上是线程安全的(使用多线程没碰到问题,但是不是很确定)

    只要一个线程只写,一个线程只读,是没问题的,如果多个线程同时写或者读需要自己加锁。

package com.example.demoapplication; import android.Manifest; // 录音权限相关 import android.content.pm.PackageManager; // 权限检查相关 import android.media.AudioFormat; // 音频格式定义 import android.media.AudioRecord; // 音频录制功能 import android.media.MediaRecorder; // 媒体录制配置 import android.os.Bundle; // Activity生命周期数据 import android.os.Handler; // 线程通信机制 import android.os.Looper; // 主线程消息循环 import android.os.Message; // 消息传递对象 import android.widget.Button; // UI按钮控件 import android.widget.Toast; // 短时提示信息 import androidx.annotation.NonNull; // 非空注解 import androidx.appcompat.app.AppCompatActivity; // 兼容Activity基类 import androidx.core.app.ActivityCompat; // 动态权限请求 import androidx.core.content.ContextCompat; // 权限状态查询 import java.io.BufferedReader; // 文本流读取 import java.io.BufferedWriter; // 文本流入 import java.io.IOException; // IO异常处理 import java.io.InputStreamReader; // 字节转字符流 import java.io.OutputStreamWriter; // 字节转字符流 import java.net.ServerSocket; // 服务端监听套接字 import java.net.Socket; // 客户端连接套接字 import java.util.concurrent.ExecutorService; // 线程池管理 import java.util.concurrent.Executors; // 线程池工厂 import java.util.concurrent.ScheduledExecutorService; // 定时任务调度 import java.util.concurrent.TimeUnit; // 时间单位定义 import android.util.Base64; // Base64编码 import org.json.JSONException; import org.json.JSONObject; // JSON对象 public class MainActivity extends AppCompatActivity { private Button startRecordButton; // 开始录音按钮 private Button stopRecordButton; // 停止录音按钮 private Button uploadButton; // 上传文件按钮 private AudioRecord audioRecord; // 音频录制对象 private static final int SAMPLE_RATE = 44100; // 采样率:44.1kHz private static final int BUFFER_SIZE; // 缓冲区大小 static { // 使用更安全的缓冲区大小计算方式 int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); // 确保缓冲区大小是帧大小的整数倍(2通道*2字节) BUFFER_SIZE = ((minBufferSize / (2 * 2)) + 1) * (2 * 2); } private ScheduledExecutorService scheduler; // 录音定时器 private boolean isRecording = false; // 录音状态标志 private static final int PERMISSION_REQUEST_CODE = 1; // 权限请求码 private final ExecutorService executorService = Executors.newCachedThreadPool(); // 通用线程池 private ServerSocket serverSocket; // TCP服务端Socket private volatile boolean isServerRunning = true; // 服务运行状态 private Socket clientSocket; // 当前客户端连接 // 主线程消息处理器 private Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case 0x11: // 客户端连接成功 Toast.makeText(MainActivity.this, "客户端已连接", Toast.LENGTH_SHORT).show(); break; case 0x12: // 数据接收完成 Toast.makeText(MainActivity.this, "开始录音", Toast.LENGTH_SHORT).show(); break; case 0x13: // 上传错误 Toast.makeText(MainActivity.this, "录音数据已发送", Toast.LENGTH_SHORT).show(); break; case 0x14: // 自定义消息类型,用于停止录音提示 Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_SHORT).show(); break; case 0x15: // 接收到控制指令 Toast.makeText(MainActivity.this, "收到控制指令:" + msg.obj.toString(), Toast.LENGTH_SHORT).show(); break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); // 初始化视图组件 checkPermissions(); // 检查权限状态 setupClickListeners(); // 设置点击事件监听 startServer(30000); // 启动TCP服务器,监听30000端口 } /** * 视图初始化方法 * 绑定布局中的UI组件并设置初始状态 */ private void initViews() { startRecordButton = findViewById(R.id.startRecordButton); stopRecordButton = findViewById(R.id.stopRecordButton); uploadButton = findViewById(R.id.uploadButton); stopRecordButton.setEnabled(false); // 初始禁用停止按钮 uploadButton.setEnabled(false); // 初始禁用上传按钮 } /** * 权限检查方法 * 如果未授予录音权限则发起请求 */ private void checkPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE); } } /** * 按钮点击事件绑定 * 设置各按钮的响应逻辑 */ private void setupClickListeners() { startRecordButton.setOnClickListener(v -> startRecording()); // 开始录音 stopRecordButton.setOnClickListener(v -> stopRecording()); // 停止录音 uploadButton.setOnClickListener(v -> uploadRecording()); // 上传录音 } /** * 开始录音方法 * 初始化AudioRecord并启动录制 */ private void startRecording() { // 添加状态检查和异常处理 if (isRecording || audioRecord != null) { return; // 防止重复启动 } try { audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, BUFFER_SIZE); if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { throw new IllegalStateException("AudioRecord初始化失败"); } audioRecord.startRecording(); isRecording = true; startRecordButton.setEnabled(false); stopRecordButton.setEnabled(true); uploadButton.setEnabled(false); // 创建定时任务发送音频数据 scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::uploadAudioData, 0, 160, TimeUnit.MILLISECONDS); handler.sendEmptyMessage(0x12); // 发送开始录音的消息 // 添加发送 { "type": "startRecorder", "data": null } 的逻辑 if (clientSocket != null && !clientSocket.isClosed()) { try { JSONObject startPacket = new JSONObject(); startPacket.put("type", "startRecorder"); startPacket.put("data", JSONObject.NULL); // 设置 data 为 null BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8")); writer.write(startPacket.toString()); writer.write("\n\n"); // 双换行作为结束标识 writer.flush(); } catch (IOException | JSONException e) { e.printStackTrace(); Message msg = handler.obtainMessage(0x13, e.getMessage()); handler.sendMessage(msg); } } else { Toast.makeText(this, "客户端未连接", Toast.LENGTH_SHORT).show(); } } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, "录音启动失败: " + e.getMessage(), Toast.LENGTH_LONG).show(); releaseAudioResources(); } } /** * 停止录音方法 * 释放录音资源并清理状态 */ private void stopRecording() { isRecording = false; releaseAudioResources(); stopRecordButton.setEnabled(false); uploadButton.setEnabled(true); handler.sendEmptyMessage(0x14); // 发送停止录音的消息 // 添加发送 { "type": "stopRecor", "data": null } 的逻辑 if (clientSocket != null && !clientSocket.isClosed()) { try { JSONObject stopPacket = new JSONObject(); stopPacket.put("type", "stopRecor"); stopPacket.put("data", JSONObject.NULL); // 设置 data 为 null BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8")); writer.write(stopPacket.toString()); writer.write("\n\n"); // 双换行作为结束标识 writer.flush(); } catch (IOException | JSONException e) { e.printStackTrace(); Message msg = handler.obtainMessage(0x13, e.getMessage()); handler.sendMessage(msg); } } } /** * 释放音频资源 */ private void releaseAudioResources() { if (audioRecord != null) { try { audioRecord.stop(); } catch (IllegalStateException e) { // 忽略可能的非法状态异常 } try { audioRecord.release(); } finally { audioRecord = null; } } if (scheduler != null) { try { scheduler.shutdownNow(); } finally { scheduler = null; } } } /** * 实时上传音频数据 * 将当前缓冲区数据通过Socket发送给客户端,并添加Base64编码的JSON格式数据 */ private void uploadAudioData() { if (!isRecording || clientSocket == null || clientSocket.isClosed()) return; byte[] buffer = new byte[BUFFER_SIZE]; try { int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE); if (bytesRead > 0) { // 使用固定大小缓冲区确保完整性 byte[] validData = new byte[bytesRead]; System.arraycopy(buffer, 0, validData, 0, bytesRead); // 将音频数据转换为Base64编码字符串 String base64Data = Base64.encodeToString(validData, Base64.DEFAULT); // 构建JSON格式字符串 JSONObject json = new JSONObject(); json.put("type", "recording"); json.put("data", base64Data); // 获取输出流并发送数据 BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8")); writer.write(json.toString()); writer.write("\n\n"); // 添加双换行作为结束标识 writer.flush(); handler.sendEmptyMessage(0x13); // 发送录音数据的消息 } } catch (IOException | JSONException e) { Message msg = handler.obtainMessage(0x13, e.getMessage()); handler.sendMessage(msg); } catch (Exception e) { e.printStackTrace(); Message msg = handler.obtainMessage(0x13, "音频读取异常: " + e.getMessage()); handler.sendMessage(msg); } } /** * 上传完整录音文件(当前未使用) * 提示该模式下为实时传输无需手动上传 */ private void uploadRecording() { // 可选:上传录音完整文件逻辑,如有需要可添加实现 Toast.makeText(this, "该模式下无需上传文件,已实时发送", Toast.LENGTH_SHORT).show(); } /** * 启动TCP服务器 * 在指定端口监听客户端连接 * * @param port 监听端口号 */ private void startServer(int port) { executorService.execute(() -> { try { serverSocket = new ServerSocket(port); while (isServerRunning) { Socket socket = serverSocket.accept(); clientSocket = socket; handler.sendEmptyMessage(0x11); // 发送客户端连接成功的消息 // 启动双向通信处理 executorService.execute(() -> startCommunication(socket)); } } catch (IOException e) { e.printStackTrace(); runOnUiThread(() -> Toast.makeText(MainActivity.this, "服务器启动失败: " + e.getMessage(), Toast.LENGTH_LONG).show()); } }); } /** * 启动双向通信 * 处理客户端的连接和数据交互 * * @param socket 客户端Socket连接 */ private void startCommunication(Socket socket) { try { BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8")); StringBuilder packetBuilder = new StringBuilder(); int c; while ((c = reader.read()) != -1) { char ch = (char) c; packetBuilder.append(ch); // 检测到连续两个换行符,表示一个完整的数据包结束 if (packetBuilder.length() >= 2 && packetBuilder.charAt(packetBuilder.length() - 2) == '\n' && packetBuilder.charAt(packetBuilder.length() - 1) == '\n') { String packet = packetBuilder.toString().trim(); // 去除首尾空白字符 packetBuilder.setLength(0); // 清空构建器 if (!packet.isEmpty()) { try { JSONObject jsonObject = new JSONObject(packet); handleReceivedPacket(jsonObject); // 处理接收到的数据包 } catch (JSONException e) { e.printStackTrace(); } } } } } catch (IOException e) { e.printStackTrace(); runOnUiThread(() -> Toast.makeText(MainActivity.this, "通信中断: " + e.getMessage(), Toast.LENGTH_SHORT).show()); } finally { try { socket.close(); } catch (IOException ignored) {} // 重置客户端socket if (socket == clientSocket) { clientSocket = null; } } } /** * 处理接收到的数据包 * 根据数据包类型执行相应操作 * * @param jsonObject 接收到的JSON数据包 */ private void handleReceivedPacket(JSONObject jsonObject) { try { String type = jsonObject.getString("type"); Object data = jsonObject.opt("data"); // 发送消息到主线程进行Toast显示 Message msg = handler.obtainMessage(0x15, type + ": " + data.toString()); handler.sendMessage(msg); // 根据不同类型执行不同操作 switch (type) { case "start_recording": if (!isRecording) { runOnUiThread(this::startRecording); } break; case "stop_recording": if (isRecording) { runOnUiThread(this::stopRecording); } break; case "ping": sendResponse("pong"); // 发送pong响应 break; // 可以添加更多类型的处理 } } catch (JSONException e) { e.printStackTrace(); } } /** * 发送响应数据包 * 使用统一的JSON格式并通过双换行结尾 * * @param responseType 响应类型 */ private void sendResponse(String responseType) { if (clientSocket == null || clientSocket.isClosed()) return; try { JSONObject response = new JSONObject(); response.put("type", responseType); response.put("data", ""); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8")); writer.write(response.toString()); writer.write("\n\n"); writer.flush(); } catch (JSONException | IOException e) { e.printStackTrace(); } } /** * 发送特定类型的数据包 * 用于发送控制指令或其他消息 * * @param type 数据包类型 * @param data 数据内容 */ private void sendDataPacket(String type, Object data) { if (clientSocket == null || clientSocket.isClosed()) return; try { JSONObject packet = new JSONObject(); packet.put("type", type); packet.put("data", data); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8")); writer.write(packet.toString()); writer.write("\n\n"); writer.flush(); } catch (JSONException | IOException e) { e.printStackTrace(); } } @Override protected void onDestroy() { super.onDestroy(); isServerRunning = false; try { if (serverSocket != null) serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } executorService.shutdownNow(); if (audioRecord != null) { audioRecord.release(); audioRecord = null; } if (scheduler != null) { scheduler.shutdownNow(); } try { if (clientSocket != null && !clientSocket.isClosed()) clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "权限已授予", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "录音权限被拒绝", Toast.LENGTH_SHORT).show(); } } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } import socket import subprocess import threading import time import json import base64 import numpy as np import queue import pyaudio # 配置参数 CHUNK = 1024 * 2 # 缓冲区大小 FORMAT = pyaudio.paInt16 # 16位PCM格式 CHANNELS = 1 # 单声道 RATE = 44100 # 采样率 SERVER_PORT = 30000 # TCP监听端口 class ADBExecutor: """ADB命令执行器""" ADB_PATH = "adb" HOST_PORT = 35000 ANDROID_PORT = 30000 def __init__(self, adb_path="adb"): self.adb_path = adb_path def get_connected_device(self): """获取当前连接的第一个设备""" try: result = subprocess.check_output([self.adb_path, "devices"], text=True) lines = result.strip().split("\n")[1:] devices = [line.split("\t")[0] for line in lines if "device" in line] return devices[0] if devices else None except Exception as e: print(f"ADB命令执行错误: {e}") return None def setup_port_forwarding(self, device_id): """设置端口转发""" try: subprocess.run( [self.adb_path, "-s", device_id, "forward", f"tcp:{self.HOST_PORT}", f"tcp:{self.ANDROID_PORT}"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) print(f"端口转发已设置: {self.HOST_PORT} <-> {self.ANDROID_PORT}") return True except Exception as e: print(f"端口转发失败: {e}") return False class AdbDeviceListener: """ADB设备监听器""" def __init__(self, adb_executor, interval=2, on_device_callback=None): self.adb_executor = adb_executor self.interval = interval self.on_device_callback = on_device_callback self.running = False self.current_device = None def start(self): """启动设备监听线程""" self.running = True threading.Thread(target=self._listen_loop, daemon=True).start() print("设备监听已启动") def stop(self): """停止设备监听""" self.running = False print("设备监听已停止") def _listen_loop(self): """设备监听主循环""" while self.running: try: device = self.adb_executor.get_connected_device() # 检测设备连接变化 if device and device != self.current_device: if self.current_device: self._handle_event("disconnected", self.current_device) self.current_device = device self._handle_event("connected", device) elif not device and self.current_device: self._handle_event("disconnected", self.current_device) self.current_device = None time.sleep(self.interval) except Exception as e: print(f"设备监听错误: {e}") time.sleep(5) def _handle_event(self, event_type, device_id): """处理设备事件""" if self.on_device_callback: self.on_device_callback(event_type, device_id) print(f"[设备事件] {event_type}: {device_id}") class AudioReceiver: """音频接收器,处理JSON格式的音频数据包""" def __init__(self, host_port): self.host_port = host_port self.socket = None self.running = False self.buffer = bytearray() # 接收缓冲区 self.audio_callback = None self.command_callback = None def connect(self): """连接到设备""" try: self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect(("localhost", self.host_port)) print(f"已连接到端口 {self.host_port}") return True except Exception as e: print(f"连接失败: {e}") self.socket = None return False def disconnect(self): """断开连接""" if self.socket: try: self.socket.close() except: pass self.socket = None def start_receiving(self, audio_callback=None, command_callback=None): """开始接收数据""" self.running = True self.audio_callback = audio_callback self.command_callback = command_callback threading.Thread(target=self._receive_loop, daemon=True).start() print("音频接收已启动") def stop_receiving(self): """停止接收数据""" self.running = False self.disconnect() print("音频接收已停止") def _receive_loop(self): """接收主循环""" while self.running: if not self.socket or not self._is_connected(): if not self.connect(): time.sleep(2) continue try: data = self.socket.recv(4096) if not data: print("连接断开,尝试重连...") self.disconnect() continue self.buffer.extend(data) self._parse_packets() except Exception as e: print(f"接收错误: {e}") self.disconnect() time.sleep(2) def _parse_packets(self): """解析接收到的数据包""" end_marker = b'\n\n' while True: marker_pos = self.buffer.find(end_marker) if marker_pos == -1: break # 没有完整数据包 # 提取完整数据包 packet_bytes = self.buffer[:marker_pos + len(end_marker)] self.buffer = self.buffer[marker_pos + len(end_marker):] try: # 解析JSON json_bytes = packet_bytes[:-len(end_marker)] json_str = json_bytes.decode('utf-8') packet = json.loads(json_str) # 处理不同类型的数据包 self._handle_packet(packet) except json.JSONDecodeError as e: print(f"JSON解析错误: {e}") except Exception as e: print(f"数据包处理错误: {e}") def _handle_packet(self, packet): """处理解析后的数据包""" packet_type = packet.get("type") if packet_type == "recording": # 处理录音数据 if self.audio_callback: audio_data = base64.b64decode(packet.get("data", "")) self.audio_callback(audio_data) elif packet_type in ["startRecorder", "stopRecord"]: # 处理命令 if self.command_callback: self.command_callback(packet_type) else: print(f"未知数据包类型: {packet_type}") def _is_connected(self): """检查连接状态""" if not self.socket: return False try: self.socket.sendall(b'') return True except: return False # 发送命令到安卓设备 def send_command(self, command_type, data=None): """发送命令到安卓设备""" if not self.socket or not self._is_connected(): print(f"发送失败: 未连接到设备") return False try: # 构建JSON数据包 packet = { "type": command_type, "data": base64.b64encode(data).decode('ascii') if data else None } # 转换为JSON字符串并添加结束标志 json_str = json.dumps(packet) packet_bytes = (json_str + "\n\n").encode('utf-8') # 发送数据包 self.socket.sendall(packet_bytes) print(f"已发送命令: {command_type}") return True except Exception as e: print(f"发送错误: {e}") return False def pcm_to_base64_str(pcm_data: bytes) -> str: """将PCM转为Base64编码字符串""" return base64.b64encode(pcm_data).decode('ascii') def pcm_to_utf8(pcm_data: bytearray) -> str: """将16位PCM音频数据转为UTF-8字符串""" def validate_pcm(data: bytearray) -> bool: """验证PCM数据有效性""" return len(data) % 2 == 0 # 16位PCM需为偶数长度 if not validate_pcm(pcm_data): raise ValueError("无效的PCM数据长度,16位PCM需为偶数长度") try: # 转为16位有符号整数数组(小端序) samples = np.frombuffer(pcm_data, dtype='<i2') # 标准化到0-255范围 normalized = ((samples - samples.min()) * (255 / (samples.max() - samples.min()))).astype(np.uint8) # 转换为UTF-8字符串 return bytes(normalized).decode('utf-8', errors='replace') except Exception as e: raise RuntimeError(f"转换失败: {str(e)}") def audio_data_handler(audio_data): """音频数据处理回调""" print(f"收到音频数据: {len(audio_data)} 字节") # 打印前32字节的十六进制表示 if len(audio_data) > 0: hex_preview = ' '.join(f'{b:02x}' for b in audio_data[:32]) print(f"前32字节: {hex_preview}...") # 调试用:将PCM转为UTF-8 if len(audio_data) < 1024: try: utf8_data = pcm_to_utf8(audio_data) print(f"UTF-8预览: {utf8_data[:30]}...") except: pass def command_handler(command_type): """命令处理回调""" if command_type == "startRecorder": print("[命令] 开始录音") elif command_type == "stopRecord": print("[命令] 停止录音") def on_device_callback(event, device_name): """设备事件回调""" if event == "connected": print(f"[设备已连接] {device_name}") # 设置端口转发 adb_executor = ADBExecutor() if adb_executor.setup_port_forwarding(device_name): # 连接并开始接收音频 global audio_receiver audio_receiver = AudioReceiver(ADBExecutor.HOST_PORT) if audio_receiver.connect(): audio_receiver.start_receiving( audio_callback=audio_data_handler, command_callback=command_handler ) print("已开始接收音频数据") else: print("连接失败") else: print("端口转发设置失败") elif event == "disconnected": print(f"[设备已断开连接] {device_name}") if 'audio_receiver' in globals(): audio_receiver.stop_receiving() # 主函数 def run_listener(): adb_executor = ADBExecutor() listener = AdbDeviceListener(adb_executor, interval=2, on_device_callback=on_device_callback) listener.start() try: while True: time.sleep(1) except KeyboardInterrupt: print("程序关闭,停止监听") listener.stop() if 'audio_receiver' in globals(): audio_receiver.stop_receiving() if __name__ == '__main__': run_listener() 把脚本和安卓端优化下 让安卓当客户端 python 当服务端去接收,现在是相反
最新发布
06-27
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值