scrcpy
scrcpy copied to clipboard
Create AudioRecord using reflection as a fallback
Fixes #3805
It duplicated all code from AudioRecord
's constructor and AudioRecord.Builder.build()
for both Android 11 and Android 12.
Android 13 uses AudioAttributes.Builder.replaceTags()
instead of AudioAttributes.Builder.addTag()
. The former may copy more fields from the source attributes
, but not using it (in this PR) doesn't seem to matter.
It uses native reflections, but the code is much harder to read with that. Most methods/fields are only used once so performance shouldn't change even if joor was used. Jar package size grows from 52KB to 57KB (previous one using joor was 61KB).
~~The linter doesn't like it using private APIs, so a lint-baseline.xml
file was created according to the docs. To update it, simply delete it and rebuild the project. Doesn't know why it can still access those APIs even if they are blocked, maybe Android Shell process doesn't have that restriction. Using joor won't cause those linting errors (and don't need the lint-baseline.xml
file) maybe because the linter can't recognize the usage.~~
Tested (by using this build()
method forcefully) on Android 11 and Android 13 emulator.
Expected to break in Android 14 since the addition of deviceId
parameter.
Thank you for your work :+1:
It uses native reflections, but the code is much harder to read with that. Most methods/fields are only used once so performance shouldn't change even if joor was used. Jar package size grows from 52KB to 57KB (previous one using joor was 61KB).
Sure, but I want to avoid adding dependencies for only minor convenience gains. This adds complexity at other places (e.g. when the project is not built with gradle
or to make offline builds), without mentioning maintenance, upgrade…
The linter doesn't like it using private APIs, so a
lint-baseline.xml
file was created according to the docs. To update it, simply delete it and rebuild the project. Doesn't know why it can still access those APIs even if they are blocked, maybe Android Shell process doesn't have that restriction. Using joor won't cause those linting errors (and don't need thelint-baseline.xml
file) maybe because the linter can't recognize the usage.
Adding the following annotation seems sufficient:
@SuppressLint("SoonBlockedPrivateApi")
public class AudioRecordWrapper {
It duplicated all code from AudioRecord's constructor and AudioRecord.Builder.build() for both Android 11 and Android 12.
Thank you for taking care of all the difference for each version! That's a lot of painful work :)
IMO, we should minimize the duplicated code from AOSP, this is just a workaround with known values. For example, in the implementation of AudioRecordWrapper.build()
, the tests relative to format or "privacy sensitive" are probably not necessary.
I think we don't even need a reimplementation of AudioRecord.Builder.build()
, but just initialize an AudioRecord
with the expected values, like you did here. Something like AudioRecordWorkarounds.createAudioRecord(MediaFormat format, int minBufferSize, …)
.
This should probably remove the need of most of AudioFormatWrapper
and AudioAttributesWrapper
(and the remaining methods, if any, could be inlined). What do you think?
Also, I noticed that this fallback code is executed on Android 11 when the screen is locked (even on non Vivo devices), IMO it should not (and it interacts with the retrying mechanism introduced by 02f4ff7534649153d6f87b05a0757431a2d0ee5f). I don't know how we should detect that a failure is due to screen lock or because the AudioRecord
itself is buggy/modified by the vendor on the device. Maybe we should limit this to vivo
devices? In the past, I had so many problems introduced on other devices due to a workaround specific to meizu
phones…
I removed some checks, but I don't want to deviate it from AOSP too much, so others can read it more easily.
Maybe we should limit this to
vivo
devices?
Not sure how to do this. What's the Build.Brand
value for those devices? vivo
and iqoo
?
Thank you @yume-chan for your latest version :+1:
I move your code to Workarounds.createAudioRecord()
, and removed even more unnecessary parts: vivo_workaround
. What do you think?
Here is a binary:
-
scrcpy-win64-vivo_workaround.zip
SHA-256: 15ff62aee34e60fccd97e678151de20411da4ccbc7268f38081908963a5f8531
cc @A-viral-dev (#3805)
EDIT: I also moved the URLs pointing to AOSP AudioRecord implementation from code comments to the commit message. I could invent a reason, but the real one is that I did not manage to silent the linter errors about the lines greater than 150 chars due to the long URLs :smile:
Here is a binary:
scrcpy-win64-vivo_workaround.zip
SHA-256: 15ff62aee34e60fccd97e678151de20411da4ccbc7268f38081908963a5f8531
I tried this and got this error:
INFO: Renderer: direct3d
INFO: Initial texture: 720x1600
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 'android.content.pm.PackageManager android.content.Context.getPackageManager()' on a null object reference
at android.content.ContextWrapper.getPackageManager(ContextWrapper.java:102)
at android.content.ContextWrapper.getPackageManager(ContextWrapper.java:102)
at android.media.VivoAudioRecordImpl.getSignatures(VivoAudioRecordImpl.java:100)
at android.media.VivoAudioRecordImpl.getSignInfo(VivoAudioRecordImpl.java:68)
at android.media.VivoAudioRecordImpl.isSupportSubMixRecording(VivoAudioRecordImpl.java:133)
at android.media.AudioRecord.<init>(AudioRecord.java:433)
at android.media.AudioRecord$Builder.build(AudioRecord.java:798)
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:923)```
@rahaaatul Your stacktrace (line numbers) does not match the binary I posted, but matches v2.0, so you're probably not using the binary posted in the previous command.
How did you run it?
@rahaaatul Your stacktrace (line numbers) does not match the binary I posted, but match v2.0, so you're probably not using the binary posted in the previous command.
How did you run it?
Oh shoot! My bad! I forgot that I added the released version in the PATH.
So after I downloaded your new binary, I extracted and opened the terminal and ran scrcpy there. Most likely it ran the other one in the PATH.
Sorry to bother you like that, I will try again. Please accept my apology.
Okay, just tested it works fine. But the screen must be on first.
But the screen must be on first.
Android 11?
But the screen must be on first.
Android 11?
Yes
I move your code to Workarounds.createAudioRecord(), and removed even more unnecessary parts: vivo_workaround. What do you think?
I think those are good simplifications. One thing I noticed is that the mChannelMask
field wasn't being set in your code, but if it still works, maybe it's not used by Android.
One thing I noticed is that the
mChannelMask
field wasn't being set in your code
Indeed, it is better to set it to the correct value (even if it works without). I updated the branch.
Merged into dev
: cab354102d722c27660e83817ecff6fd3cb9cfc7
On my device, this doesn't seem to be a scrcpy bug,i dont't know. because I turned off selinux permissions, which is permissive. When I set "setenforce 1" in the shell again, it worked fine