diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java index dc000e98dc..4937b34632 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java @@ -3,6 +3,7 @@ import android.media.MediaFormat; public enum AudioCodec implements Codec { + RAW(0x00_72_61_77, "raw", MediaFormat.MIMETYPE_AUDIO_RAW), OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS), AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC); 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 0000000000..b0dbedaa74 --- /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 AudioRecorder { + + private final Streamer streamer; + + private Thread thread; + + private static final int BUFFER_MS = 5; // milliseconds + private static final int BUFFER_SIZE = AudioCapture.millisToBytes(BUFFER_MS); + + public AudioRawRecorder(Streamer streamer) { + this.streamer = streamer; + } + + private void record() throws IOException, AudioCaptureForegroundException { + final ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_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, BUFFER_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 b034e451f4..a80a8cd3af 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -109,9 +109,14 @@ private static void scrcpy(Options options) throws IOException, ConfigurationExc } if (audio) { - Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(), - options.getSendFrameMeta()); - audioRecorder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), options.getAudioEncoder()); + AudioCodec audioCodec = options.getAudioCodec(); + Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecId(), options.getSendFrameMeta()); + if (audioCodec == AudioCodec.RAW) { + audioRecorder = new AudioRawRecorder(audioStreamer); + } else { + audioRecorder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), + options.getAudioEncoder()); + } audioRecorder.start(); }