scrcpy: Audio not working on Vivo(Iqoo) Android 13 device

scrcpy 2.0 <https://github.com/Genymobile/scrcpy>
/usr/local/share/scrcpy/scrcpy-server: 1 file pushed. 4.8 MB/s (52867 bytes in 0.010s)
[server] INFO: Device: vivo I2202 (Android 13)
INFO: Renderer: opengl
INFO: OpenGL version: 3.1 Mesa 21.2.6
INFO: Trilinear filtering enabled
INFO: Initial texture: 1080x2400
WARN: Demuxer 'audio': stream explicitly disabled by the device
[server] ERROR: Exception on thread Thread[Thread-4,5,main]
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getOpPackageName()' on a null object reference
        at android.media.VivoAudioRecordImpl.isSupportSubMixRecording(VivoAudioRecordImpl.java:133)
        at android.media.AudioRecord.<init>(AudioRecord.java:493)
        at android.media.AudioRecord.<init>(Unknown Source:0)
        at android.media.AudioRecord$Builder.build(AudioRecord.java:999)
        at com.genymobile.scrcpy.AudioCapture.createAudioRecord(AudioCapture.java:58)
        at com.genymobile.scrcpy.AudioCapture.start(AudioCapture.java:90)
        at com.genymobile.scrcpy.AudioEncoder.encode(AudioEncoder.java:183)
        at com.genymobile.scrcpy.AudioEncoder.lambda$start$0$com-genymobile-scrcpy-AudioEncoder(AudioEncoder.java:120)
        at com.genymobile.scrcpy.AudioEncoder$$ExternalSyntheticLambda0.run(Unknown Source:2)
        at java.lang.Thread.run(Thread.java:1012)

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 1
  • Comments: 35 (7 by maintainers)

Commits related to this issue

Most upvoted comments

Please try this: scrcpy-server.zip

diff
diff --git a/server/build.gradle b/server/build.gradle
index ce234d10..644045af 100644
--- a/server/build.gradle
+++ b/server/build.gradle
@@ -20,6 +20,9 @@ android {
 }
 
 dependencies {
+    // https://mvnrepository.com/artifact/org.jooq/joor
+    implementation 'org.jooq:joor:0.9.14'
+
     testImplementation 'junit:junit:4.13.2'
 }
 
diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java
index 6bb3ce23..eb65dedf 100644
--- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java
+++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java
@@ -4,16 +4,23 @@ import com.genymobile.scrcpy.wrappers.ServiceManager;
 
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
+import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.media.AudioAttributes;
 import android.media.AudioFormat;
+import android.media.AudioManager;
 import android.media.AudioRecord;
 import android.media.AudioTimestamp;
 import android.media.MediaCodec;
 import android.media.MediaRecorder;
 import android.os.Build;
+import android.os.Looper;
 import android.os.SystemClock;
 
+import org.joor.Reflect;
+
+import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
 
 public final class AudioCapture {
@@ -34,28 +41,65 @@ public final class AudioCapture {
         return SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * millis / 1000;
     }
 
-    private static AudioFormat createAudioFormat() {
-        AudioFormat.Builder builder = new AudioFormat.Builder();
-        builder.setEncoding(FORMAT);
-        builder.setSampleRate(SAMPLE_RATE);
-        builder.setChannelMask(CHANNEL_CONFIG);
-        return builder.build();
-    }
-
     @TargetApi(Build.VERSION_CODES.M)
-    @SuppressLint({"WrongConstant", "MissingPermission"})
+    @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());
+        try {
+            Reflect audioRecord = Reflect.onClass(AudioRecord.class).create(0L);
+
+            audioRecord.set("mRecordingState", AudioRecord.RECORDSTATE_STOPPED);
+
+            Looper looper = Looper.myLooper();
+            if (looper == null) {
+                looper = Looper.getMainLooper();
+            }
+            audioRecord.set("mInitializationLooper", looper);
+
+            audioRecord.set("mIsSubmixFullVolume", true);
+
+            AudioAttributes.Builder audioAttributesBuilder = new AudioAttributes.Builder();
+            Reflect.on(audioAttributesBuilder).call("setInternalCapturePreset",
+                    MediaRecorder.AudioSource.REMOTE_SUBMIX);
+            AudioAttributes audioAttributes = audioAttributesBuilder.build();
+            audioRecord.set("mAudioAttributes", audioAttributes);
+
+            audioRecord.call("audioParamCheck", MediaRecorder.AudioSource.REMOTE_SUBMIX, SAMPLE_RATE, FORMAT);
+
+            audioRecord.set("mChannelMask", CHANNEL_CONFIG);
+            audioRecord.set("mChannelCount", CHANNELS);
+
+            int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, FORMAT);
+            // This buffer size does not impact latency
+            audioRecord.call("audioBuffSizeCheck", 8 * minBufferSize);
+
+            int[] sampleRate = new int[] { 0 };
+            int[] session = new int[] { AudioManager.AUDIO_SESSION_ID_GENERATE };
+
+            AttributionSource attributionSource = FakeContext.get().getAttributionSource();
+            Reflect attributionSourceState = Reflect.on(attributionSource).call("asScopedParcelState");
+            try (AutoCloseable closeable = attributionSourceState.as(AutoCloseable.class)) {
+                int initResult = audioRecord
+                        .call("native_setup", (Object) new WeakReference<AudioRecord>(audioRecord.get()),
+                                (Object) audioAttributes, sampleRate, CHANNEL_CONFIG, 0, FORMAT,
+                                audioRecord.get("mNativeBufferSizeInBytes"), session,
+                                attributionSourceState.call("getParcel").get(), 0L, 0)
+                        .get();
+                if (initResult != AudioRecord.SUCCESS) {
+                    Ln.e("Error code " + initResult + " when initializing native AudioRecord object.");
+                    return null;
+                }
+            }
+
+            audioRecord.set("mSampleRate", sampleRate[0]);
+            audioRecord.set("mSessionId", session[0]);
+
+            audioRecord.set("mState", AudioRecord.STATE_INITIALIZED);
+
+            return audioRecord.get();
+        } catch (Throwable e) {
+            Ln.e("createAudioRecord", e);
+            return null;
         }
-        builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX);
-        builder.setAudioFormat(createAudioFormat());
-        int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, FORMAT);
-        // This buffer size does not impact latency
-        builder.setBufferSizeInBytes(8 * minBufferSize);
-        return builder.build();
     }
 
     private static void startWorkaroundAndroid11() {

不幸的是,上面的版本不适用于荣耀设备,所以这里有一个确认适用于荣耀设备的新版本:#4015(评论)

您能否确认它也适用于Vivo设备?

@rom1v It works fine.

C:\Users\mr.song\Downloads\scrcpy-win64-honor_6\scrcpy-win64-v2.0-94-g9f9b852ae>scrcpy -Vdebug scrcpy 2.0 https://github.com/Genymobile/scrcpy DEBUG: ADB device found: DEBUG: --> (usb) 3468348101003D0 device V2157A DEBUG: Device serial: 3468348101003D0 DEBUG: Using server (portable): C:\Users\mr.song\Downloads\scrcpy-win64-honor_6\scrcpy-win64-v2.0-94-g9f9b852ae\scrcpy-server C:\Users\mr.song\Downloads\scrcpy-win64-honor_6\scrcpy-win…file pushed, 0 skipped. 125.7 MB/s (56935 bytes in 0.000s) [server] INFO: Device: [vivo] vivo V2157A (Android 13) DEBUG: Server connected DEBUG: Starting controller thread DEBUG: Starting receiver thread [server] DEBUG: Using audio encoder: ‘c2.android.opus.encoder’ [server] DEBUG: Using video encoder: ‘OMX.qcom.video.encoder.avc’ INFO: Renderer: direct3d DEBUG: Trilinear filtering disabled (not an OpenGL renderer DEBUG: Using icon (portable): C:\Users\mr.song\Downloads\scrcpy-win64-honor_6\scrcpy-win64-v2.0-94-g9f9b852ae\icon.png DEBUG: Demuxer ‘video’: starting thread DEBUG: Demuxer ‘audio’: starting thread INFO: Texture: 1080x2400 DEBUG: [Audio] Buffering threshold exceeded, skipping 240 samples DEBUG: [Audio] Buffering threshold exceeded, skipping 480 samples DEBUG: [Audio] Buffer underflow, inserting silence: 87 samples DEBUG: [Audio] Buffer underflow, inserting silence: 240 samples DEBUG: [Audio] Buffer underflow, inserting silence: 240 samples

@rom1v

C:\Users\mr.song\Downloads\scrcpy-win64-issue3805\scrcpy-win64-v2.0-95-g9bff8ccad>scrcpy -Vdebug
scrcpy 2.0 <https://github.com/Genymobile/scrcpy>
DEBUG: ADB device found:
DEBUG:     -->   (usb)       3468348101003D0            device  V2157A
DEBUG: Device serial: 3468348101003D0
DEBUG: Using server (portable): C:\Users\mr.song\Downloads\scrcpy-win64-issue3805\scrcpy-win64-v2.0-95-g9bff8ccad\scrcpy-server
C:\Users\mr.song\Downloads\scrcpy-win64-issue3805\scrcpy-w... file pushed, 0 skipped. 34.7 MB/s (57051 bytes in 0.002s)
[server] INFO: Device: [vivo] vivo V2157A (Android 13)
DEBUG: Server connected
DEBUG: Starting controller thread
DEBUG: Starting receiver thread
[server] DEBUG: Using video encoder: 'OMX.qcom.video.encoder.avc'
[server] DEBUG: Using audio encoder: 'c2.android.opus.encoder'
INFO: Renderer: direct3d
DEBUG: Trilinear filtering disable[server] DEBUG: Audio encoder stopped
d (not an OpenGL renderer
DEBUG: Using icon (portable): C:\Users\mr.song\Downloads\scrcpy-win64-issue3805\scrcpy-win64-v2.0-95-g9bff8ccad\icon.pngDEBUG: Demuxer 'video': starting thread
DEBUG: Demuxer 'audi[server] ERROR: Exception on thread Thread[audio-encoder,5,main]
o': starting thread
WARN: Demuxer 'java.lang.AssertionError: java.lang.reflect.InvocationTargetExceptiona
u       at com.genymobile.scrcpy.wrappers.ActivityThread.<clinit>(ActivityThread.java:17)d
i       at com.genymobile.scrcpy.wrappers.ActivityThread.getActivityThreadClass(ActivityThread.java:30)o
'       at com.genymobile.scrcpy.FakeContext.retrieveSystemContext(FakeContext.java:23):
        at com.genymobile.scrcpy.FakeContext.<init>(FakeContext.java:39)s
t       at com.genymobile.scrcpy.FakeContext.<clinit>(FakeContext.java:19)r
e       at com.genymobile.scrcpy.FakeContext.get(FakeContext.java:35)am exp
l       at com.genymobile.scrcpy.AudioCapture.createAudioRecord(AudioCapture.java:57)i
c       at com.genymobile.scrcpy.AudioCapture.startRecording(AudioCapture.java:107)i
t       at com.genymobile.scrcpy.AudioCapture.start(AudioCapture.java:128)l
y       at com.genymobile.scrcpy.AudioEncoder.encode(AudioEncoder.java:193)
d       at com.genymobile.scrcpy.AudioEncoder.lambda$start$0$com-genymobile-scrcpy-AudioEncoder(AudioEncoder.java:124)i
s       at com.genymobile.scrcpy.AudioEncoder$$ExternalSyntheticLambda0.run(Unknown Source:4)a
b       at java.lang.Thread.run(Thread.java:1015)l
eCaused by: java.lang.reflect.InvocationTargetExceptiond
        at java.lang.reflect.Constructor.newInstance0(Native Method)b
y       at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
t       at com.genymobile.scrcpy.wrappers.ActivityThread.<clinit>(ActivityThread.java:15)h
e       ... 12 more
deCaused by: java.lang.RuntimeException: Can't create handler inside thread Thread[audio-encoder,5,main] that has not called Looper.prepare()v
i       at android.os.Handler.<init>(Handler.java:227)c
e       at android.os.Handler.<init>(Handler.java:129)

I       at android.app.ActivityThread$H.<init>(ActivityThread.java:2262)N
F       at android.app.ActivityThread.<init>(ActivityThread.java:420)O
:       ... 15 more
Texture: 1080x2400

I can create a PR this weekend.