Capture device audio WIP
Co-authored-by: Romain Vimont <rom@rom1v.com> Signed-off-by: Romain Vimont <rom@rom1v.com>
This commit is contained in:
committed by
Romain Vimont
parent
18082bd7ab
commit
de95b61f81
74
server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java
Normal file
74
server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
|
||||
public final class AudioEncoder {
|
||||
|
||||
private static final int SAMPLE_RATE = 48000;
|
||||
private static final int CHANNELS = 2;
|
||||
|
||||
private Thread thread;
|
||||
|
||||
private static AudioFormat createAudioFormat() {
|
||||
AudioFormat.Builder builder = new AudioFormat.Builder();
|
||||
builder.setEncoding(AudioFormat.ENCODING_PCM_16BIT);
|
||||
builder.setSampleRate(SAMPLE_RATE);
|
||||
builder.setChannelMask(CHANNELS == 2 ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@SuppressLint({"WrongConstant", "MissingPermission"})
|
||||
private static AudioRecord createAudioRecord() {
|
||||
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(MediaRecorder.AudioSource.REMOTE_SUBMIX);
|
||||
builder.setAudioFormat(createAudioFormat());
|
||||
builder.setBufferSizeInBytes(1024 * 1024);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
AudioRecord recorder = createAudioRecord();
|
||||
|
||||
thread = new Thread(() -> {
|
||||
recorder.startRecording();
|
||||
try {
|
||||
int BUFFER_MS = 15; // do not buffer more than BUFFER_MS milliseconds
|
||||
byte[] buf = new byte[SAMPLE_RATE * CHANNELS * BUFFER_MS / 1000];
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
int r = recorder.read(buf, 0, buf.length);
|
||||
if (r > 0) {
|
||||
Ln.i("Audio captured: " + r + " bytes");
|
||||
}
|
||||
if (r < 0) {
|
||||
Ln.e("Audio capture error: " + r);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
recorder.stop();
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (thread != null) {
|
||||
thread.interrupt();
|
||||
thread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,15 +73,23 @@ public final class Server {
|
||||
boolean sendDummyByte = options.getSendDummyByte();
|
||||
|
||||
Workarounds.prepareMainLooper();
|
||||
if (Build.BRAND.equalsIgnoreCase("meizu")) {
|
||||
// Workarounds must be applied for Meizu phones:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/240>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/365>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/2656>
|
||||
//
|
||||
// But only apply when strictly necessary, since workarounds can cause other issues:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/940>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/994>
|
||||
|
||||
// Workarounds must be applied for Meizu phones:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/240>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/365>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/2656>
|
||||
//
|
||||
// But only apply when strictly necessary, since workarounds can cause other issues:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/940>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/994>
|
||||
boolean mustFillAppInfo = Build.BRAND.equalsIgnoreCase("meizu");
|
||||
|
||||
// Before Android 11, audio is not supported.
|
||||
// Since Android 12, we can properly set a context on the AudioRecord.
|
||||
// Only on Android 11 we must fill app info for the AudioRecord to work.
|
||||
mustFillAppInfo |= audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R;
|
||||
|
||||
if (mustFillAppInfo) {
|
||||
Workarounds.fillAppInfo();
|
||||
}
|
||||
|
||||
@@ -103,6 +111,12 @@ public final class Server {
|
||||
device.setClipboardListener(text -> controllerRef.getSender().pushClipboardText(text));
|
||||
}
|
||||
|
||||
AudioEncoder audioEncoder = null;
|
||||
if (audio) {
|
||||
audioEncoder = new AudioEncoder();
|
||||
audioEncoder.start();
|
||||
}
|
||||
|
||||
try {
|
||||
// synchronous
|
||||
VideoStreamer videoStreamer = new VideoStreamer(connection.getVideoFd(), options.getSendFrameMeta());
|
||||
@@ -118,6 +132,9 @@ public final class Server {
|
||||
if (controller != null) {
|
||||
controller.stop();
|
||||
}
|
||||
if (audioEncoder != null) {
|
||||
audioEncoder.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user