diff --git a/README.md b/README.md index ca2ba2d..9ffac18 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,10 @@ ### Sonic扩展功能 > 1. 获取app列表,包括app图标 > 2. 远程音频采集 -> 3. PCM格式音频进行ACC转码 +> 3. PCM格式音频进行WAV转码(doing) > 4. 横竖屏实时监听 +> 5. 剪切板管理优化(doing) +> 6. 其他扩展 ### 开源协议 diff --git a/app/src/main/java/org/cloud/sonic/android/AudioService.java b/app/src/main/java/org/cloud/sonic/android/AudioService.java index dc5ac3e..06c3271 100644 --- a/app/src/main/java/org/cloud/sonic/android/AudioService.java +++ b/app/src/main/java/org/cloud/sonic/android/AudioService.java @@ -266,30 +266,30 @@ private static AudioRecord createAudioRecord(MediaProjection mediaProjection) { // packet[6] = (byte) 0xFC; // } + //try wav + //record audio private void startRecording() { final AudioRecord recorder = createAudioRecord(mediaProjection); - recorderThread = new Thread(new Runnable() { - @Override - public void run() { - try (LocalSocket socket = connect()) { - handler.sendEmptyMessage(MSG_CONNECTION_ESTABLISHED); - - recorder.startRecording(); - int BUFFER_MS = 15; // do not buffer more than BUFFER_MS milliseconds - byte[] buf = new byte[SAMPLE_RATE * CHANNELS * BUFFER_MS / 1000]; - while (true) { - int r = recorder.read(buf, 0, buf.length); + //try not thread + recorderThread = new Thread(() -> { + try (LocalSocket socket = connect()) { + handler.sendEmptyMessage(MSG_CONNECTION_ESTABLISHED); + + recorder.startRecording(); + int BUFFER_MS = 15; // do not buffer more than BUFFER_MS milliseconds + byte[] buf = new byte[SAMPLE_RATE * CHANNELS * BUFFER_MS / 1000]; + while (true) { + int r = recorder.read(buf, 0, buf.length); // encodePCMToAAC(buf,socket.getOutputStream()); - socket.getOutputStream().write(buf, 0, r); - } - } catch (IOException e) { - // ignore - } finally { - recorder.stop(); - stopSelf(); + socket.getOutputStream().write(buf, 0, r); } + } catch (IOException e) { + // ignore + } finally { + recorder.stop(); + stopSelf(); } }); recorderThread.start(); diff --git a/app/src/main/java/org/cloud/sonic/android/models/WavHeader.java b/app/src/main/java/org/cloud/sonic/android/models/WavHeader.java new file mode 100644 index 0000000..d6bef20 --- /dev/null +++ b/app/src/main/java/org/cloud/sonic/android/models/WavHeader.java @@ -0,0 +1,60 @@ +package org.cloud.sonic.android.models; + +import org.cloud.sonic.android.util.ByteUtils; + +/** + * + */ +public class WavHeader { + /** + * RIFF数据块 + */ + final String riffChunkId = "RIFF"; + int riffChunkSize; + final String riffType = "WAVE"; + + /** + * FORMAT 数据块 + */ + final String formatChunkId = "fmt "; + final int formatChunkSize = 16; + final short audioFormat = 1; + short channels; + int sampleRate; + int byteRate; + short blockAlign; + short sampleBits; + + /** + * FORMAT 数据块 + */ + final String dataChunkId = "data"; + int dataChunkSize; + + public WavHeader(int totalAudioLen, int sampleRate, short channels, short sampleBits) { + this.riffChunkSize = totalAudioLen; + this.channels = channels; + this.sampleRate = sampleRate; + this.byteRate = sampleRate * sampleBits / 8 * channels; + this.blockAlign = (short) (channels * sampleBits / 8); + this.sampleBits = sampleBits; + this.dataChunkSize = totalAudioLen - 44; + } + + public byte[] getHeader() { + byte[] result; + result = ByteUtils.merger(ByteUtils.toBytes(riffChunkId), ByteUtils.toBytes(riffChunkSize)); + result = ByteUtils.merger(result, ByteUtils.toBytes(riffType)); + result = ByteUtils.merger(result, ByteUtils.toBytes(formatChunkId)); + result = ByteUtils.merger(result, ByteUtils.toBytes(formatChunkSize)); + result = ByteUtils.merger(result, ByteUtils.toBytes(audioFormat)); + result = ByteUtils.merger(result, ByteUtils.toBytes(channels)); + result = ByteUtils.merger(result, ByteUtils.toBytes(sampleRate)); + result = ByteUtils.merger(result, ByteUtils.toBytes(byteRate)); + result = ByteUtils.merger(result, ByteUtils.toBytes(blockAlign)); + result = ByteUtils.merger(result, ByteUtils.toBytes(sampleBits)); + result = ByteUtils.merger(result, ByteUtils.toBytes(dataChunkId)); + result = ByteUtils.merger(result, ByteUtils.toBytes(dataChunkSize)); + return result; + } +} diff --git a/app/src/main/java/org/cloud/sonic/android/util/ByteUtils.java b/app/src/main/java/org/cloud/sonic/android/util/ByteUtils.java new file mode 100644 index 0000000..1c252cc --- /dev/null +++ b/app/src/main/java/org/cloud/sonic/android/util/ByteUtils.java @@ -0,0 +1,105 @@ +package org.cloud.sonic.android.util; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +public class ByteUtils { + /** + * short[] 转 byte[] + */ + public static byte[] toBytes(short[] src) { + int count = src.length; + byte[] dest = new byte[count << 1]; + for (int i = 0; i < count; i++) { + dest[i * 2] = (byte) (src[i]); + dest[i * 2 + 1] = (byte) (src[i] >> 8); + } + + return dest; + } + + + /** + * short[] 转 byte[] + */ + public static byte[] toBytes(short src) { + byte[] dest = new byte[2]; + dest[0] = (byte) (src); + dest[1] = (byte) (src >> 8); + + return dest; + } + + /** + * int 转 byte[] + */ + public static byte[] toBytes(int i) { + byte[] b = new byte[4]; + b[0] = (byte) (i & 0xff); + b[1] = (byte) ((i >> 8) & 0xff); + b[2] = (byte) ((i >> 16) & 0xff); + b[3] = (byte) ((i >> 24) & 0xff); + return b; + } + + + /** + * String 转 byte[] + */ + public static byte[] toBytes(String str) { + return str.getBytes(); + } + + /** + * long类型转成byte数组 + */ + public static byte[] toBytes(long number) { + ByteBuffer buffer = ByteBuffer.allocate(8); + buffer.putLong(0, number); + return buffer.array(); + } + + public static int toInt(byte[] src, int offset) { + return ((src[offset] & 0xFF) + | ((src[offset + 1] & 0xFF) << 8) + | ((src[offset + 2] & 0xFF) << 16) + | ((src[offset + 3] & 0xFF) << 24)); + } + + public static int toInt(byte[] src) { + return toInt(src, 0); + } + + /** + * 字节数组到long的转换. + */ + public static long toLong(byte[] b) { + ByteBuffer buffer = ByteBuffer.allocate(8); + buffer.put(b, 0, b.length); + return buffer.getLong(); + } + + /** + * byte[] 转 short[] + * short: 2字节 + */ + public static short[] toShorts(byte[] src) { + int count = src.length >> 1; + short[] dest = new short[count]; + for (int i = 0; i < count; i++) { + dest[i] = (short) ((src[i * 2] & 0xff) | ((src[2 * i + 1] & 0xff) << 8)); + } + return dest; + } + + public static byte[] merger(byte[] bt1, byte[] bt2) { + byte[] bt3 = new byte[bt1.length + bt2.length]; + System.arraycopy(bt1, 0, bt3, 0, bt1.length); + System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length); + return bt3; + } + + public static String toString(byte[] b) { + return Arrays.toString(b); + } +} diff --git a/app/src/main/java/org/cloud/sonic/android/util/WavUtils.java b/app/src/main/java/org/cloud/sonic/android/util/WavUtils.java new file mode 100644 index 0000000..4048d7f --- /dev/null +++ b/app/src/main/java/org/cloud/sonic/android/util/WavUtils.java @@ -0,0 +1,63 @@ +package org.cloud.sonic.android.util; + +import android.os.FileUtils; +import android.util.Log; + +import org.cloud.sonic.android.models.WavHeader; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.logging.Logger; + +/** + * 生成头字节 + */ +public class WavUtils { + private static final String TAG = WavUtils.class.getSimpleName(); + + /** + * 生成wav格式的Header + * wave是RIFF文件结构,每一部分为一个chunk,其中有RIFF WAVE chunk, + * FMT Chunk,Fact chunk(可选),Data chunk + * + * @param totalAudioLen 不包括header的音频数据总长度 + * @param sampleRate 采样率,也就是录制时使用的频率 + * @param channels audioRecord的频道数量 + * @param sampleBits 位宽 + */ + public static byte[] generateWavFileHeader(int totalAudioLen, int sampleRate, int channels, int sampleBits) { + WavHeader wavHeader = new WavHeader(totalAudioLen, sampleRate, (short) channels, (short) sampleBits); + return wavHeader.getHeader(); + } + + /** + * 将header写入到pcm文件中 不修改文件名 + * + * @param file 写入的pcm文件 + * @param header wav头数据 + */ + public static void writeHeader(File file, byte[] header) { + if (!file.exists()) { + return; + } + + RandomAccessFile wavRaf = null; + try { + wavRaf = new RandomAccessFile(file, "rw"); + wavRaf.seek(0); + wavRaf.write(header); + wavRaf.close(); + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } finally { + try { + if (wavRaf != null) { + wavRaf.close(); + } + } catch (IOException e) { + Log.e(TAG, e.getMessage()); + } + } + } +}