635. Design Log Storage System

本文介绍了一种高效日志系统的设计与实现方法,通过将时间戳转换为秒数进行存储,利用TreeMap自动排序特性,实现了快速检索指定时间段内的日志记录。系统支持批量插入与按时间粒度查询功能。

You are given several logs that each log contains a unique id and timestamp. Timestamp is a string that has the following format: Year:Month:Day:Hour:Minute:Second, for example, 2017:01:01:23:59:59. All domains are zero-padded decimal numbers.

Design a log storage system to implement the following functions:

 

void Put(int id, string timestamp):

Given a log's unique id and timestamp, store the log in your storage system.

 

int[] Retrieve(String start, String end, String granularity):

Return the id of logs whose timestamps are within the range from start to end. Start and end all have the same format as timestamp. However, granularity means the time level for consideration. For example, start = "2017:01:01:23:59:59", end = "2017:01:02:23:59:59", granularity = "Day", it means that we need to find the logs within the range from Jan. 1st 2017 to Jan. 2nd 2017.

 

Example 1:

put(1, "2017:01:01:23:59:59");
put(2, "2017:01:01:22:59:59");
put(3, "2016:01:01:00:00:00");
retrieve("2016:01:01:01:01:01","2017:01:01:23:00:00","Year"); // return [1,2,3], because you need to return all logs within 2016 and 2017.
retrieve("2016:01:01:01:01:01","2017:01:01:23:00:00","Hour"); // return [1,2], because you need to return all logs start from 2016:01:01:01 to 2017:01:01:23, where log 3 is left outside the range.

Note:

  1. There will be at most 300 operations of Put or Retrieve.
  2. Year ranges from [2000,2017]. Hour ranges from [00,23].
  3. Output for Retrieve has no order required.

 

设计一个日志系统,包含放置和获取两个功能。放的时候是(id, timestamp),取的时候是(start, end, gra),表示要把timestamp在start和end之间的日志id取出来。

一刷就出来了,思路基本一致,主要有两点:

1.timestamp转数字,这样才方便比较。

对于每个timestamp,转为秒数存就可以了。我的稍显复杂,是把每个timestamp存了多个维度。

比如2016:01:01:01:01:01

存成了

Year, 2016

Month, 2016:01

...

2.用treemap,自动排序,不用遍历所有timestamp。

 

public class LogSystem {
    TreeMap < Long, Integer > map;
    public LogSystem() {
        map = new TreeMap < Long, Integer > ();
    }

    public void put(int id, String timestamp) {
        int[] st = Arrays.stream(timestamp.split(":")).mapToInt(Integer::parseInt).toArray();
        map.put(convert(st), id);
    }
    public long convert(int[] st) {
        st[1] = st[1] - (st[1] == 0 ? 0 : 1);
        st[2] = st[2] - (st[2] == 0 ? 0 : 1);
        return (st[0] - 1999L) * (31 * 12) * 24 * 60 * 60 + st[1] * 31 * 24 * 60 * 60 + st[2] * 24 * 60 * 60 + st[3] * 60 * 60 + st[4] * 60 + st[5];
    }
    public List < Integer > retrieve(String s, String e, String gra) {
        ArrayList < Integer > res = new ArrayList();
        long start = granularity(s, gra, false);
        long end = granularity(e, gra, true);
        for (long key: map.tailMap(start).keySet()) {
            if (key >= start && key < end)
                res.add(map.get(key));
        }
        return res;
    }

    public long granularity(String s, String gra, boolean end) {
        HashMap < String, Integer > h = new HashMap();
        h.put("Year", 0);
        h.put("Month", 1);
        h.put("Day", 2);
        h.put("Hour", 3);
        h.put("Minute", 4);
        h.put("Second", 5);
        String[] res = new String[] {"1999", "00", "00", "00", "00", "00"};
        String[] st = s.split(":");
        for (int i = 0; i <= h.get(gra); i++) {
            res[i] = st[i];
        }
        int[] t = Arrays.stream(res).mapToInt(Integer::parseInt).toArray();
        if (end)//这里,作者是想把end包含进来,实际上可以用treemap.tailMap(toKey, true)
            t[h.get(gra)]++;
        return convert(t);
    }
}

 

package com.example.demoapplication; import android.Manifest; import android.content.pm.PackageManager; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.AudioTrack; import android.media.MediaRecorder; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.speech.tts.TextToSpeech; import android.util.Base64; import android.util.Log; import android.view.View; import android.view.animation.Animation; import android.view.animation.ScaleAnimation; import android.widget.Button; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.LinkedList; import java.util.Locale; import java.util.Queue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * 主活动类,实现录音、播放、网络通信和TTS功能 */ public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener { // 日志标签 private static final String TAG = "AudioRecorder"; // UI控件 private Button startRecordButton, stopRecordButton; private Button playSoundButton, pauseSoundButton, stopSoundButton, resumeSoundButton, clearSoundsButton; private AudioRecord audioRecord; // 音频配置常量 private static final int SAMPLE_RATE = 16000; private static final int BUFFER_SIZE; // 静态代码块计算缓冲区大小 static { int minBufferSize = AudioRecord.getMinBufferSize( SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT ); BUFFER_SIZE = Math.max(minBufferSize, 4096); } // 线程和状态管理 private ScheduledExecutorService scheduler; private AtomicBoolean isRecording = new AtomicBoolean(false); private static final int PERMISSION_REQUEST_CODE = 1; private final ExecutorService executorService = Executors.newCachedThreadPool(); // 网络通信相关变量 private ServerSocket serverSocket; private volatile boolean isServerRunning = true; private volatile Socket clientSocket; private volatile BufferedWriter socketWriter; // TTS和音频播放控制 private TextToSpeech ttsEngine; private boolean isTtsInitialized = false; private AudioTrack audioTrack; // 队列用于存储录音数据 private final Queue<byte[]> recordingQueue = new LinkedList<>(); private final Queue<byte[]> pausedQueue = new LinkedList<>(); private final Queue<byte[]> playbackQueue = new LinkedList<>(); // 原子变量确保线程安全的状态管理 private final AtomicBoolean isPlaying = new AtomicBoolean(false); private final AtomicBoolean isPaused = new AtomicBoolean(false); private volatile boolean isPlaybackThreadActive = false; // 锁对象用于同步访问共享资源 private final Object audioTrackLock = new Object(); private final Object playbackQueueLock = new Object(); private final Object recordingQueueLock = new Object(); // 动画资源 private Animation buttonPressAnim; // 主线程Handler用于更新UI private final Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case 0x11: showAnimatedToast("客户端已连接", Toast.LENGTH_SHORT); break; case 0x12: showAnimatedToast("开始录音", Toast.LENGTH_SHORT); sendJsonPacket("startRecorder", null); playTts("开始录音"); startButtonPulseAnimation(startRecordButton); break; case 0x14: showAnimatedToast("停止录音", Toast.LENGTH_SHORT); sendJsonPacket("stopRecorder", null); playTts("停止录音"); stopButtonPulseAnimation(startRecordButton); break; case 0x16: showAnimatedToast("错误: " + msg.obj, Toast.LENGTH_LONG); break; case 0x17: showAnimatedToast("播放完成", Toast.LENGTH_SHORT); isPlaying.set(false); isPlaybackThreadActive = false; updatePlayButtonsState(); stopPlaybackAnimations(); break; case 0x18: showAnimatedToast("已添加到播放队列", Toast.LENGTH_SHORT); break; case 0x19: updatePlayButtonsState(); break; case 0x20: sendJsonPacket("pauseSound", null); playTts("播放暂停"); pausePlaybackAnimations(); break; case 0x21: sendJsonPacket("stopSound", null); playTts("播放停止"); stopPlaybackAnimations(); break; case 0x22: sendJsonPacket("resumeSound", null); playTts("继续播放"); resumePlaybackAnimations(); break; case 0x23: sendJsonPacket("clearSounds", null); playTts("清空所有录音"); break; case 0x24: String base64Data = (String) msg.obj; try { byte[] decodedData = Base64.decode(base64Data, Base64.DEFAULT); addBase64ToPlaybackQueue(decodedData); } catch (IllegalArgumentException e) { Log.e(TAG, "Base64解码失败", e); sendErrorMessage("无效的Base64数据"); } break; } } }; /** * Activity创建方法 * @param savedInstanceState 保存的状态 */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化按钮脉冲动画 initButtonAnimations(); // 初始化TTS引擎 ttsEngine = new TextToSpeech(this, this); initViews(); setupClickListeners(); checkPermissions(); startServer(30000); startSocketListener(); } /** * 初始化按钮动画 */ private void initButtonAnimations() { // 创建按钮点击动画 buttonPressAnim = new ScaleAnimation( 1.0f, 0.9f, 1.0f, 0.9f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f ); buttonPressAnim.setDuration(150); buttonPressAnim.setRepeatCount(0); buttonPressAnim.setFillAfter(false); } /** * 初始化视图组件 */ private void initViews() { // 绑定按钮 startRecordButton = findViewById(R.id.startRecordButton); stopRecordButton = findViewById(R.id.stopRecordButton); playSoundButton = findViewById(R.id.playSoundButton); pauseSoundButton = findViewById(R.id.pauseSoundButton); stopSoundButton = findViewById(R.id.stopSoundButton); resumeSoundButton = findViewById(R.id.resumeSoundButton); clearSoundsButton = findViewById(R.id.clearSoundsButton); // 初始按钮状态设置 stopRecordButton.setEnabled(false); pauseSoundButton.setEnabled(false); stopSoundButton.setEnabled(false); resumeSoundButton.setEnabled(false); } /** * 设置按钮点击监听器 */ private void setupClickListeners() { startRecordButton.setOnClickListener(v -> { animateButtonClick(v); startRecording(); }); stopRecordButton.setOnClickListener(v -> { animateButtonClick(v); stopRecording(); }); playSoundButton.setOnClickListener(v -> { animateButtonClick(v); addToPlaybackQueue(); }); pauseSoundButton.setOnClickListener(v -> { animateButtonClick(v); pausePlayback(); handler.sendEmptyMessage(0x20); }); stopSoundButton.setOnClickListener(v -> { animateButtonClick(v); stopPlayback(); handler.sendEmptyMessage(0x21); }); resumeSoundButton.setOnClickListener(v -> { animateButtonClick(v); if (isPaused.get() && !playbackQueue.isEmpty()) { resumePlayback(); handler.sendEmptyMessage(0x22); } }); clearSoundsButton.setOnClickListener(v -> { animateButtonClick(v); clearAllRecordings(); handler.sendEmptyMessage(0x23); }); } /** * 按钮点击动画 */ private void animateButtonClick(View view) { view.startAnimation(buttonPressAnim); } /** * 开始录音时启动脉冲动画 */ private void startButtonPulseAnimation(Button button) { stopButtonPulseAnimation(button); // 先停止之前的动画 Animation pulseAnim = new ScaleAnimation( 1.0f, 1.1f, 1.0f, 1.1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f ); pulseAnim.setDuration(500); pulseAnim.setRepeatCount(Animation.INFINITE); pulseAnim.setRepeatMode(Animation.REVERSE); button.startAnimation(pulseAnim); } /** * 停止录音时停止脉冲动画 */ private void stopButtonPulseAnimation(Button button) { button.clearAnimation(); } /** * 播放暂停时的动画效果 */ private void pausePlaybackAnimations() { stopButtonPulseAnimation(playSoundButton); stopButtonPulseAnimation(resumeSoundButton); } /** * 播放恢复时的动画效果 */ private void resumePlaybackAnimations() { startButtonPulseAnimation(resumeSoundButton); } /** * 播放停止时的动画效果 */ private void stopPlaybackAnimations() { stopButtonPulseAnimation(playSoundButton); stopButtonPulseAnimation(resumeSoundButton); } /** * 显示带缩放动画的Toast */ private void showAnimatedToast(String message, int duration) { Toast toast = Toast.makeText(this, message, duration); View view = toast.getView(); Animation anim = new ScaleAnimation( 0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f ); anim.setDuration(300); view.startAnimation(anim); toast.show(); } // ==================== 录音功能实现 ==================== /** * 开始录音 */ private void startRecording() { // 检查录音权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { sendErrorMessage("没有录音权限"); return; } // 如果已经在录音,先释放资源 if (isRecording.get()) { releaseAudioResources(); } 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.set(true); // 更新UI状态 startRecordButton.setEnabled(false); stopRecordButton.setEnabled(true); updatePlayButtonsState(); // 启动录音数据采集线程 if (scheduler != null && !scheduler.isShutdown()) { scheduler.shutdownNow(); } scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::captureAudioData, 0, 100, TimeUnit.MILLISECONDS); // 发送开始录音通知 handler.sendEmptyMessage(0x12); } catch (Exception e) { Log.e(TAG, "录音启动失败", e); sendErrorMessage("录音启动失败: " + e.getMessage()); releaseAudioResources(); } } /** * 停止录音 */ private void stopRecording() { if (!isRecording.get()) return; isRecording.set(false); releaseAudioResources(); // 更新UI状态 stopRecordButton.setEnabled(false); startRecordButton.setEnabled(true); updatePlayButtonsState(); // 发送停止录音通知 handler.sendEmptyMessage(0x14); } /** * 采集音频数据并保存到队列 */ private void captureAudioData() { if (!isRecording.get() || audioRecord == null) return; byte[] buffer = new byte[BUFFER_SIZE]; try { int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE); if (bytesRead > 0) { // 将录制的音频数据保存到队列 synchronized (recordingQueueLock) { recordingQueue.offer(buffer.clone()); } // 发送录音数据包 String base64Data = Base64.encodeToString(buffer, Base64.DEFAULT); sendJsonPacket("recording", base64Data); } } catch (Exception e) { Log.e(TAG, "音频采集失败", e); } } // ==================== 录音功能结束 ==================== // ==================== 播放功能实现 ==================== /** * 添加当前录音到播放队列 */ private void addToPlaybackQueue() { if (recordingQueue.isEmpty()) { showAnimatedToast("没有可播放的录音", Toast.LENGTH_SHORT); return; } // 创建录音数据副本 Queue<byte[]> recordingCopy = new LinkedList<>(); synchronized (recordingQueueLock) { for (byte[] data : recordingQueue) { recordingCopy.offer(data.clone()); } } // 添加到播放队列 synchronized (playbackQueueLock) { playbackQueue.addAll(recordingCopy); } // 如果当前没有播放,立即开始播放 if (!isPlaybackThreadActive && !isPlaying.get()) { executorService.execute(this::playRecordingQueue); startButtonPulseAnimation(playSoundButton); } else { handler.sendEmptyMessage(0x18); } } /** * 将Base64编码的音频数据添加到播放队列 * @param decodedData 解码后的音频数据 */ private void addBase64ToPlaybackQueue(byte[] decodedData) { if (decodedData == null || decodedData.length == 0) { Log.w(TAG, "无效的音频数据"); return; } ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedData); byte[] buffer; int bytesRead; // 使用固定大小的缓冲区读取数据 buffer = new byte[4096]; try { while ((bytesRead = inputStream.read(buffer)) != -1) { if (bytesRead > 0) { byte[] dataChunk = new byte[bytesRead]; System.arraycopy(buffer, 0, dataChunk, 0, bytesRead); synchronized (playbackQueueLock) { playbackQueue.offer(dataChunk); } } } } catch (IOException e) { Log.e(TAG, "读取音频数据失败", e); } finally { try { inputStream.close(); } catch (IOException e) { Log.e(TAG, "关闭输入流失败", e); } } // 如果当前没有播放,立即开始播放 if (!isPlaybackThreadActive && !isPlaying.get()) { executorService.execute(this::playRecordingQueue); startButtonPulseAnimation(playSoundButton); } else { handler.sendEmptyMessage(0x18); } } /** * 播放录音队列 */ private void playRecordingQueue() { isPlaybackThreadActive = true; isPlaying.set(true); isPaused.set(false); handler.sendEmptyMessage(0x19); // 配置音频播放器 int bufferSize = AudioTrack.getMinBufferSize( SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT ); // 创建新的AudioTrack synchronized (audioTrackLock) { if (audioTrack != null) { try { audioTrack.stop(); audioTrack.release(); } catch (Exception e) { Log.e(TAG, "释放AudioTrack失败", e); } } try { audioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM ); audioTrack.play(); } catch (IllegalStateException e) { Log.e(TAG, "创建AudioTrack失败", e); stopPlayback(); return; } } // 播放队列中的所有录音数据 while (isPlaying.get() && !playbackQueue.isEmpty()) { if (isPaused.get()) { // 暂停状态,等待恢复 try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } continue; } byte[] audioData; synchronized (playbackQueueLock) { audioData = playbackQueue.poll(); } if (audioData != null) { synchronized (audioTrackLock) { if (audioTrack != null && audioTrack.getState() == AudioTrack.STATE_INITIALIZED) { try { audioTrack.write(audioData, 0, audioData.length); } catch (IllegalStateException e) { Log.e(TAG, "音频写入失败: " + e.getMessage()); break; } } else { Log.w(TAG, "AudioTrack不可用,停止播放"); break; } } } } // 确保播放完成时正确释放资源 try { synchronized (audioTrackLock) { if (audioTrack != null) { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { audioTrack.stop(); } audioTrack.release(); audioTrack = null; } } } catch (Exception e) { Log.e(TAG, "播放完成后释放资源失败", e); } // 播放完成 stopPlayback(); handler.sendEmptyMessage(0x17); } /** * 暂停播放 */ private void pausePlayback() { if (!isPlaying.get() || isPaused.get()) return; isPaused.set(true); // 保存当前播放位置 synchronized (playbackQueueLock) { pausedQueue.clear(); pausedQueue.addAll(playbackQueue); playbackQueue.clear(); } // 暂停音频播放 synchronized (audioTrackLock) { if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { try { audioTrack.pause(); } catch (IllegalStateException e) { Log.e(TAG, "暂停播放失败: " + e.getMessage()); } } } handler.sendEmptyMessage(0x19); runOnUiThread(() -> showAnimatedToast("播放已暂停", Toast.LENGTH_SHORT) ); } /** * 继续播放 */ private void resumePlayback() { if (!isPaused.get() || pausedQueue.isEmpty()) { return; } isPaused.set(false); isPlaying.set(true); // 恢复播放位置 synchronized (playbackQueueLock) { playbackQueue.clear(); playbackQueue.addAll(pausedQueue); pausedQueue.clear(); } // 恢复音频播放 synchronized (audioTrackLock) { if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) { try { audioTrack.play(); } catch (IllegalStateException e) { Log.e(TAG, "恢复播放失败: " + e.getMessage()); } } } handler.sendEmptyMessage(0x19); runOnUiThread(() -> showAnimatedToast("继续播放", Toast.LENGTH_SHORT) ); } /** * 停止播放 */ private void stopPlayback() { isPlaying.set(false); isPaused.set(false); synchronized (audioTrackLock) { if (audioTrack != null) { try { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING || audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) { audioTrack.stop(); } audioTrack.release(); } catch (IllegalStateException e) { Log.e(TAG, "停止播放失败: " + e.getMessage()); } finally { audioTrack = null; } } } synchronized (playbackQueueLock) { playbackQueue.clear(); } pausedQueue.clear(); runOnUiThread(() -> { handler.sendEmptyMessage(0x19); showAnimatedToast("播放已停止", Toast.LENGTH_SHORT); }); } private void clearAllRecordings() { stopPlayback(); synchronized (recordingQueueLock) { recordingQueue.clear(); } pausedQueue.clear(); synchronized (playbackQueueLock) { playbackQueue.clear(); } handler.sendEmptyMessage(0x19); runOnUiThread(() -> showAnimatedToast("所有录音已清除", Toast.LENGTH_SHORT) ); } // ==================== 播放功能结束 ==================== // ==================== 辅助方法 ==================== /** * 更新播放按钮状态 */ private void updatePlayButtonsState() { runOnUiThread(() -> { boolean hasRecordings = !recordingQueue.isEmpty() || !pausedQueue.isEmpty(); boolean isPlayingState = isPlaying.get() && !isPaused.get(); playSoundButton.setEnabled(hasRecordings && !isPlayingState); pauseSoundButton.setEnabled(isPlayingState); stopSoundButton.setEnabled(isPlaying.get() || isPaused.get()); resumeSoundButton.setEnabled(!playbackQueue.isEmpty() && isPaused.get()); clearSoundsButton.setEnabled(hasRecordings); }); } /** * 播放TTS语音 * @param text 要播放的文本 */ private void playTts(String text) { if (isTtsInitialized) { ttsEngine.speak(text, TextToSpeech.QUEUE_FLUSH, null); } } /** * 释放音频资源 */ private void releaseAudioResources() { if (audioRecord != null) { try { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { audioRecord.stop(); } audioRecord.release(); } catch (IllegalStateException e) { Log.e(TAG, "停止录音失败", e); } finally { audioRecord = null; } } if (scheduler != null) { try { scheduler.shutdownNow(); if (!scheduler.awaitTermination(500, TimeUnit.MILLISECONDS)) { Log.w(TAG, "录音线程池未正常关闭"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { scheduler = null; } } } /** * 发送JSON格式的数据包 * @param type 数据包类型 * @param data 数据内容(可以为null) */ private void sendJsonPacket(String type, Object data) { if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { return; } try { JSONObject packet = new JSONObject(); packet.put("type", type); if (data != null) { packet.put("data", data); } synchronized (this) { if (socketWriter != null) { socketWriter.write(packet.toString()); socketWriter.write("\n\n"); socketWriter.flush(); } } } catch (Exception e) { Log.e(TAG, "发送数据包失败: " + type, e); } } /** * 发送错误消息 * @param message 错误信息 */ private void sendErrorMessage(String message) { handler.obtainMessage(0x16, message).sendToTarget(); } /** * 检查权限 */ 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); } } /** * 启动服务器 * @param port 监听端口 */ private void startServer(int port) { executorService.execute(() -> { try { serverSocket = new ServerSocket(port); Log.i(TAG, "服务器启动: " + port); while (isServerRunning) { try { Socket socket = serverSocket.accept(); clientSocket = socket; synchronized (this) { socketWriter = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); } handler.sendEmptyMessage(0x11); } catch (IOException e) { if (isServerRunning) Log.e(TAG, "接受连接失败", e); } } } catch (IOException e) { Log.e(TAG, "服务器启动失败", e); runOnUiThread(() -> Toast.makeText(this, "服务器启动失败: " + e.getMessage(), Toast.LENGTH_LONG).show()); } finally { closeServerSocket(); } }); } /** * 启动Socket监听 */ private void startSocketListener() { executorService.execute(() -> { while (true) { if (clientSocket != null && !clientSocket.isClosed()) { try { BufferedReader reader = new BufferedReader( new InputStreamReader(clientSocket.getInputStream(), "UTF-8")); StringBuilder packetBuilder = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { if (line.isEmpty()) { // 收到两个换行符,表示一个数据包结束 if (packetBuilder.length() > 0) { String packet = packetBuilder.toString(); Log.d(TAG, "收到数据包: " + packet); try { JSONObject command = new JSONObject(packet); String type = command.getString("type"); switch (type) { case "playSound": String base64Data = command.getString("data"); Message msg = handler.obtainMessage(0x24, base64Data); handler.sendMessage(msg); break; case "pauseSound": handler.sendEmptyMessage(0x20); break; case "stopSound": handler.sendEmptyMessage(0x21); break; case "resumeSound": handler.sendEmptyMessage(0x22); break; case "clearSounds": handler.sendEmptyMessage(0x23); break; default: Log.w(TAG, "未知指令类型: " + type); } } catch (JSONException e) { Log.e(TAG, "JSON解析失败", e); } // 重置包构建器 packetBuilder.setLength(0); } } else { // 添加数据到当前包 packetBuilder.append(line); } } } catch (IOException e) { Log.e(TAG, "Socket读取失败", e); } } else { try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } }); } /** * 关闭服务器Socket */ private void closeServerSocket() { try { if (serverSocket != null && !serverSocket.isClosed()) { serverSocket.close(); } } catch (IOException e) { Log.w(TAG, "关闭ServerSocket失败", e); } } /** * TTS初始化回调 */ @Override public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { int result = ttsEngine.setLanguage(Locale.CHINESE); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Log.e(TAG, "TTS语言不支持中文"); } else { isTtsInitialized = true; } } } /** * 活动销毁时调用 */ @Override protected void onDestroy() { super.onDestroy(); isServerRunning = false; if (ttsEngine != null) { ttsEngine.stop(); ttsEngine.shutdown(); } closeServerSocket(); closeSocket(clientSocket); // 停止所有录音和播放 stopRecording(); stopPlayback(); // 优雅关闭线程池 executorService.shutdown(); try { if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException e) { executorService.shutdownNow(); Thread.currentThread().interrupt(); } // 确保所有资源释放 releaseAudioResources(); } /** * 关闭Socket * @param socket 要关闭的Socket */ private void closeSocket(Socket socket) { try { if (socket != null && !socket.isClosed()) { socket.close(); } } catch (IOException e) { Log.w(TAG, "关闭Socket失败", e); } if (socket == clientSocket) { clientSocket = null; synchronized (this) { socketWriter = null; } } } } 在这个页面添加点样式。录音时候带些波浪回馈,美化页面 然后把完整的安卓代码给我
最新发布
07-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值