mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-12-17 21:44:20 +01:00
Add option to select audio source
Pass --audio-source=mic to capture the microphone instead of the device audio output.
This commit is contained in:
@@ -10,7 +10,6 @@ import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.AudioTimestamp;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
|
||||
@@ -18,7 +17,6 @@ import java.nio.ByteBuffer;
|
||||
|
||||
public final class AudioCapture {
|
||||
|
||||
public static final int SOURCE = MediaRecorder.AudioSource.REMOTE_SUBMIX;
|
||||
public static final int SAMPLE_RATE = 48000;
|
||||
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
|
||||
public static final int CHANNELS = 2;
|
||||
@@ -26,12 +24,18 @@ public final class AudioCapture {
|
||||
public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
|
||||
public static final int BYTES_PER_SAMPLE = 2;
|
||||
|
||||
private final int audioSource;
|
||||
|
||||
private AudioRecord recorder;
|
||||
|
||||
private final AudioTimestamp timestamp = new AudioTimestamp();
|
||||
private long previousPts = 0;
|
||||
private long nextPts = 0;
|
||||
|
||||
public AudioCapture(AudioSource audioSource) {
|
||||
this.audioSource = audioSource.value();
|
||||
}
|
||||
|
||||
public static int millisToBytes(int millis) {
|
||||
return SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * millis / 1000;
|
||||
}
|
||||
@@ -46,13 +50,13 @@ public final class AudioCapture {
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@SuppressLint({"WrongConstant", "MissingPermission"})
|
||||
private static AudioRecord createAudioRecord() {
|
||||
private static AudioRecord createAudioRecord(int audioSource) {
|
||||
AudioRecord.Builder builder = new AudioRecord.Builder();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
// On older APIs, Workarounds.fillAppInfo() must be called beforehand
|
||||
builder.setContext(FakeContext.get());
|
||||
}
|
||||
builder.setAudioSource(SOURCE);
|
||||
builder.setAudioSource(audioSource);
|
||||
builder.setAudioFormat(createAudioFormat());
|
||||
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING);
|
||||
// This buffer size does not impact latency
|
||||
@@ -100,12 +104,12 @@ public final class AudioCapture {
|
||||
|
||||
private void startRecording() {
|
||||
try {
|
||||
recorder = createAudioRecord();
|
||||
recorder = createAudioRecord(audioSource);
|
||||
} catch (NullPointerException e) {
|
||||
// Creating an AudioRecord using an AudioRecord.Builder does not work on Vivo phones:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/3805>
|
||||
// - <https://github.com/Genymobile/scrcpy/pull/3862>
|
||||
recorder = Workarounds.createAudioRecord(SOURCE, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING);
|
||||
recorder = Workarounds.createAudioRecord(audioSource, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING);
|
||||
}
|
||||
recorder.startRecording();
|
||||
}
|
||||
|
||||
30
server/src/main/java/com/genymobile/scrcpy/AudioSource.java
Normal file
30
server/src/main/java/com/genymobile/scrcpy/AudioSource.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.media.MediaRecorder;
|
||||
|
||||
public enum AudioSource {
|
||||
OUTPUT("output", MediaRecorder.AudioSource.REMOTE_SUBMIX),
|
||||
MIC("mic", MediaRecorder.AudioSource.MIC);
|
||||
|
||||
private final String name;
|
||||
private final int value;
|
||||
|
||||
AudioSource(String name, int value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
int value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
static AudioSource findByName(String name) {
|
||||
for (AudioSource audioSource : AudioSource.values()) {
|
||||
if (name.equals(audioSource.name)) {
|
||||
return audioSource;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ public class Options {
|
||||
private int maxSize;
|
||||
private VideoCodec videoCodec = VideoCodec.H264;
|
||||
private AudioCodec audioCodec = AudioCodec.OPUS;
|
||||
private AudioSource audioSource = AudioSource.OUTPUT;
|
||||
private int videoBitRate = 8000000;
|
||||
private int audioBitRate = 128000;
|
||||
private int maxFps;
|
||||
@@ -72,6 +73,10 @@ public class Options {
|
||||
return audioCodec;
|
||||
}
|
||||
|
||||
public AudioSource getAudioSource() {
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
public int getVideoBitRate() {
|
||||
return videoBitRate;
|
||||
}
|
||||
@@ -225,6 +230,13 @@ public class Options {
|
||||
}
|
||||
options.audioCodec = audioCodec;
|
||||
break;
|
||||
case "audio_source":
|
||||
AudioSource audioSource = AudioSource.findByName(value);
|
||||
if (audioSource == null) {
|
||||
throw new IllegalArgumentException("Audio source " + value + " not supported");
|
||||
}
|
||||
options.audioSource = audioSource;
|
||||
break;
|
||||
case "max_size":
|
||||
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
||||
break;
|
||||
|
||||
@@ -136,7 +136,7 @@ public final class Server {
|
||||
|
||||
if (audio) {
|
||||
AudioCodec audioCodec = options.getAudioCodec();
|
||||
AudioCapture audioCapture = new AudioCapture();
|
||||
AudioCapture audioCapture = new AudioCapture(options.getAudioSource());
|
||||
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(),
|
||||
options.getSendFrameMeta());
|
||||
AsyncProcessor audioRecorder;
|
||||
|
||||
Reference in New Issue
Block a user