diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java index dc000e98..1f3b07a0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java @@ -4,7 +4,8 @@ import android.media.MediaFormat; public enum AudioCodec implements Codec { OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS), - AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC); + AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC), + RAW(0x00_72_61_77, "raw", MediaFormat.MIMETYPE_AUDIO_RAW); private final int id; // 4-byte ASCII representation of the name private final String name; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java new file mode 100644 index 00000000..2e483daa --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -0,0 +1,75 @@ +package com.genymobile.scrcpy; + +import android.media.MediaCodec; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public final class AudioRawRecorder implements AsyncProcessor { + + private final Streamer streamer; + + private Thread thread; + + private static final int READ_MS = 5; // milliseconds + private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS); + + public AudioRawRecorder(Streamer streamer) { + this.streamer = streamer; + } + + private void record() throws IOException, AudioCaptureForegroundException { + final ByteBuffer buffer = ByteBuffer.allocateDirect(READ_SIZE); + final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + + AudioCapture capture = new AudioCapture(); + try { + capture.start(); + + streamer.writeHeader(); + while (!Thread.currentThread().isInterrupted()) { + buffer.position(0); + int r = capture.read(buffer, READ_SIZE, bufferInfo); + if (r < 0) { + throw new IOException("Could not read audio: " + r); + } + buffer.limit(r); + + streamer.writePacket(buffer, bufferInfo); + } + } catch (Throwable e) { + // Notify the client that the audio could not be captured + streamer.writeDisableStream(false); + throw e; + } finally { + capture.stop(); + } + } + + public void start() { + thread = new Thread(() -> { + try { + record(); + } catch (AudioCaptureForegroundException e) { + // Do not print stack trace, a user-friendly error-message has already been logged + } catch (IOException e) { + Ln.e("Audio recording error", e); + } finally { + Ln.d("Audio recorder stopped"); + } + }); + thread.start(); + } + + public void stop() { + if (thread != null) { + thread.interrupt(); + } + } + + public void join() throws InterruptedException { + if (thread != null) { + thread.join(); + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 3d3e02fd..86555e3b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -107,9 +107,16 @@ public final class Server { } if (audio) { - Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(), + AudioCodec audioCodec = options.getAudioCodec(); + Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecId(), options.getSendFrameMeta()); - AudioEncoder audioRecorder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), options.getAudioEncoder()); + AsyncProcessor audioRecorder; + if (audioCodec == AudioCodec.RAW) { + audioRecorder = new AudioRawRecorder(audioStreamer); + } else { + audioRecorder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), + options.getAudioEncoder()); + } asyncProcessors.add(audioRecorder); }