Compare commits

..

6 Commits

Author SHA1 Message Date
Simon Chan
4ab1e60529 Create AudioRecord by reflection as a fallback
Some devices (Vivo phones) fail to create an AudioRecord from an
AudioRecord.Builder (which throw a NullPointerException).

In that case, create an AudioRecord instance directly by reflection.

The AOSP version of AudioRecord constructor code can be found at:
 - Android 11 (R):
   <https://cs.android.com/android/platform/superproject/+/android-11.0.0_r1:frameworks/base/media/java/android/media/AudioRecord.java;l=335;drc=64ed2ec38a511bbbd048985fe413268335e072f8>
 - Android 12 (S):
   <https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/media/java/android/media/AudioRecord.java;l=388;drc=2eebf929650e0d320a21f0d13677a27d7ab278e9>
 - Android 13 (T, functionally identical to Android 12):
   <https://cs.android.com/android/platform/superproject/+/android-13.0.0_r1:frameworks/base/media/java/android/media/AudioRecord.java;l=382;drc=ed242da52f975a1dd18671afb346b18853d729f2>
 - Android 14 (U): Not released, but expected to change

PR #3862 <https://github.com/Genymobile/scrcpy/pull/3862>
Fixes #3805 <https://github.com/Genymobile/scrcpy/issues/3805>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-05-25 15:50:52 +02:00
Romain Vimont
597d2ccc01 Rename FORMAT to ENCODING
The AudioFormat contains several properties. This specific value is
named "encoding".
2023-05-24 22:13:09 +02:00
Romain Vimont
38900d7730 Extract audio source to a static constant
For consistency with the other parameters.
2023-05-24 22:13:09 +02:00
Romain Vimont
e926bf1fe8 Delay window resize when minimized
On some window managers (e.g. on Windows), performing a resize while the
window is minimized does nothing (the restored window keeps its old
size).

Therefore, like for maximized and fullscreen states, wait for the window
to be restored to apply a resize.

Refs #3947 <https://github.com/Genymobile/scrcpy/issues/3947>
2023-05-22 18:22:45 +02:00
Romain Vimont
6298ef095f Accept texture failures
When the scrcpy window is minimized on Windows with D3D9, texture
creation and update fail.

In that case, do not terminate scrcpy. Instead, store the pending size
or frame to update, to attempt again during the next update or
rendering.

Fixes #3947 <https://github.com/Genymobile/scrcpy/issues/3947>
2023-05-22 18:21:10 +02:00
Romain Vimont
7d33798b40 Upgrade FFmpeg build to 6.0-scrcpy-4
Use FFmpeg DLLs which do not depend on zlib1.dll.
2023-05-15 21:55:22 +02:00
10 changed files with 331 additions and 45 deletions

View File

@@ -6,11 +6,11 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
VERSION=6.0-scrcpy-3
VERSION=6.0-scrcpy-4
DEP_DIR="ffmpeg-$VERSION"
FILENAME="$DEP_DIR".7z
SHA256SUM=36829d98ac4454d7092c72ddb92faa20b60450bc0fe8873076efb0858cdcbc2c
SHA256SUM=39274b321491ce83e76cab5d24e7cbe3f402d3ccf382f739b13be5651c146b60
if [[ -d "$DEP_DIR" ]]
then

View File

@@ -62,11 +62,17 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer");
}
display->pending.flags = 0;
display->pending.frame = NULL;
return true;
}
void
sc_display_destroy(struct sc_display *display) {
if (display->pending.frame) {
av_frame_free(&display->pending.frame);
}
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
SDL_GL_DeleteContext(display->gl_context);
#endif
@@ -84,7 +90,7 @@ sc_display_create_texture(struct sc_display *display,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
if (!texture) {
LOGE("Could not create texture: %s", SDL_GetError());
LOGD("Could not create texture: %s", SDL_GetError());
return NULL;
}
@@ -104,8 +110,66 @@ sc_display_create_texture(struct sc_display *display,
return texture;
}
bool
sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
static inline void
sc_display_set_pending_size(struct sc_display *display, struct sc_size size) {
assert(!display->texture);
display->pending.size = size;
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_SIZE;
}
static bool
sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) {
if (!display->pending.frame) {
display->pending.frame = av_frame_alloc();
if (!display->pending.frame) {
LOG_OOM();
return false;
}
}
int r = av_frame_ref(display->pending.frame, frame);
if (r) {
LOGE("Could not ref frame: %d", r);
return false;
}
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_FRAME;
return true;
}
static bool
sc_display_apply_pending(struct sc_display *display) {
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_SIZE) {
assert(!display->texture);
display->texture =
sc_display_create_texture(display, display->pending.size);
if (!display->texture) {
return false;
}
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_SIZE;
}
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) {
assert(display->pending.frame);
bool ok = sc_display_update_texture(display, display->pending.frame);
if (!ok) {
return false;
}
av_frame_unref(display->pending.frame);
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_FRAME;
}
return true;
}
static bool
sc_display_set_texture_size_internal(struct sc_display *display,
struct sc_size size) {
assert(size.width && size.height);
if (display->texture) {
SDL_DestroyTexture(display->texture);
}
@@ -119,14 +183,27 @@ sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
return true;
}
bool
sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
enum sc_display_result
sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
bool ok = sc_display_set_texture_size_internal(display, size);
if (!ok) {
sc_display_set_pending_size(display, size);
return SC_DISPLAY_RESULT_PENDING;
}
return SC_DISPLAY_RESULT_OK;
}
static bool
sc_display_update_texture_internal(struct sc_display *display,
const AVFrame *frame) {
int ret = SDL_UpdateYUVTexture(display->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (ret) {
LOGE("Could not update texture: %s", SDL_GetError());
LOGD("Could not update texture: %s", SDL_GetError());
return false;
}
@@ -139,11 +216,34 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
return true;
}
bool
enum sc_display_result
sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
bool ok = sc_display_update_texture_internal(display, frame);
if (!ok) {
ok = sc_display_set_pending_frame(display, frame);
if (!ok) {
LOGE("Could not set pending frame");
return SC_DISPLAY_RESULT_ERROR;
}
return SC_DISPLAY_RESULT_PENDING;
}
return SC_DISPLAY_RESULT_OK;
}
enum sc_display_result
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
unsigned rotation) {
SDL_RenderClear(display->renderer);
if (display->pending.flags) {
bool ok = sc_display_apply_pending(display);
if (!ok) {
return SC_DISPLAY_RESULT_PENDING;
}
}
SDL_Renderer *renderer = display->renderer;
SDL_Texture *texture = display->texture;
@@ -151,7 +251,7 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
int ret = SDL_RenderCopy(renderer, texture, NULL, geometry);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return false;
return SC_DISPLAY_RESULT_ERROR;
}
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
@@ -176,10 +276,10 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
NULL, 0);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return false;
return SC_DISPLAY_RESULT_ERROR;
}
}
SDL_RenderPresent(display->renderer);
return true;
return SC_DISPLAY_RESULT_OK;
}

View File

@@ -24,6 +24,20 @@ struct sc_display {
#endif
bool mipmaps;
struct {
#define SC_DISPLAY_PENDING_FLAG_SIZE 1
#define SC_DISPLAY_PENDING_FLAG_FRAME 2
int8_t flags;
struct sc_size size;
AVFrame *frame;
} pending;
};
enum sc_display_result {
SC_DISPLAY_RESULT_OK,
SC_DISPLAY_RESULT_PENDING,
SC_DISPLAY_RESULT_ERROR,
};
bool
@@ -32,13 +46,13 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps);
void
sc_display_destroy(struct sc_display *display);
bool
enum sc_display_result
sc_display_set_texture_size(struct sc_display *display, struct sc_size size);
bool
enum sc_display_result
sc_display_update_texture(struct sc_display *display, const AVFrame *frame);
bool
enum sc_display_result
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
unsigned rotation);

View File

@@ -56,6 +56,7 @@ static void
set_window_size(struct sc_screen *screen, struct sc_size new_size) {
assert(!screen->fullscreen);
assert(!screen->maximized);
assert(!screen->minimized);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
}
@@ -249,9 +250,9 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
sc_screen_update_content_rect(screen);
}
bool ok = sc_display_render(&screen->display, &screen->rect,
screen->rotation);
(void) ok; // error already logged
enum sc_display_result res =
sc_display_render(&screen->display, &screen->rect, screen->rotation);
(void) res; // any error already logged
}
#if defined(__APPLE__) || defined(__WINDOWS__)
@@ -359,6 +360,7 @@ sc_screen_init(struct sc_screen *screen,
screen->has_frame = false;
screen->fullscreen = false;
screen->maximized = false;
screen->minimized = false;
screen->mouse_capture_key_pressed = 0;
screen->req.x = params->window_x;
@@ -531,11 +533,11 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
static void
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
if (!screen->fullscreen && !screen->maximized) {
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
// Store the windowed size to be able to compute the optimal size once
// fullscreen and maximized are disabled
// fullscreen/maximized/minimized are disabled
screen->windowed_content_size = screen->content_size;
screen->resize_pending = true;
}
@@ -547,6 +549,7 @@ static void
apply_pending_resize(struct sc_screen *screen) {
assert(!screen->fullscreen);
assert(!screen->maximized);
assert(!screen->minimized);
if (screen->resize_pending) {
resize_for_content(screen, screen->windowed_content_size,
screen->content_size);
@@ -583,15 +586,17 @@ sc_screen_init_size(struct sc_screen *screen) {
get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size;
return sc_display_set_texture_size(&screen->display, screen->frame_size);
enum sc_display_result res =
sc_display_set_texture_size(&screen->display, screen->frame_size);
return res != SC_DISPLAY_RESULT_ERROR;
}
// recreate the texture and resize the window if the frame size has changed
static bool
static enum sc_display_result
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
if (screen->frame_size.width == new_frame_size.width
&& screen->frame_size.height == new_frame_size.height) {
return true;
return SC_DISPLAY_RESULT_OK;
}
// frame dimension changed
@@ -615,13 +620,23 @@ sc_screen_update_frame(struct sc_screen *screen) {
sc_fps_counter_add_rendered_frame(&screen->fps_counter);
struct sc_size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
enum sc_display_result res = prepare_for_frame(screen, new_frame_size);
if (res == SC_DISPLAY_RESULT_ERROR) {
return false;
}
if (res == SC_DISPLAY_RESULT_PENDING) {
// Not an error, but do not continue
return true;
}
if (!sc_display_update_texture(&screen->display, frame)) {
res = sc_display_update_texture(&screen->display, frame);
if (res == SC_DISPLAY_RESULT_ERROR) {
return false;
}
if (res == SC_DISPLAY_RESULT_PENDING) {
// Not an error, but do not continue
return true;
}
if (!screen->has_frame) {
screen->has_frame = true;
@@ -647,7 +662,7 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) {
}
screen->fullscreen = !screen->fullscreen;
if (!screen->fullscreen && !screen->maximized) {
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
apply_pending_resize(screen);
}
@@ -657,7 +672,7 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) {
void
sc_screen_resize_to_fit(struct sc_screen *screen) {
if (screen->fullscreen || screen->maximized) {
if (screen->fullscreen || screen->maximized || screen->minimized) {
return;
}
@@ -681,7 +696,7 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
if (screen->fullscreen) {
if (screen->fullscreen || screen->minimized) {
return;
}
@@ -738,6 +753,9 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_MINIMIZED:
screen->minimized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling
@@ -748,6 +766,7 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
break;
}
screen->maximized = false;
screen->minimized = false;
apply_pending_resize(screen);
sc_screen_render(screen, true);
break;

View File

@@ -56,6 +56,7 @@ struct sc_screen {
bool has_frame;
bool fullscreen;
bool maximized;
bool minimized;
// To enable/disable mouse capture, a mouse capture key (LALT, LGUI or
// RGUI) must be pressed. This variable tracks the pressed capture key.

View File

@@ -16,6 +16,6 @@ cpu = 'i686'
endian = 'little'
[properties]
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-3/win32'
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win32'
prebuilt_sdl2 = 'SDL2-2.26.4/i686-w64-mingw32'
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32'

View File

@@ -16,6 +16,6 @@ cpu = 'x86_64'
endian = 'little'
[properties]
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-3/win64'
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win64'
prebuilt_sdl2 = 'SDL2-2.26.4/x86_64-w64-mingw32'
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64'

View File

@@ -94,11 +94,10 @@ dist-win32: build-server build-win32
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@@ -113,11 +112,10 @@ dist-win64: build-server build-win64
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"

View File

@@ -18,10 +18,12 @@ 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;
public static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT;
public static final int CHANNEL_MASK = AudioFormat.CHANNEL_IN_LEFT | AudioFormat.CHANNEL_IN_RIGHT;
public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
public static final int BYTES_PER_SAMPLE = 2;
private AudioRecord recorder;
@@ -36,7 +38,7 @@ public final class AudioCapture {
private static AudioFormat createAudioFormat() {
AudioFormat.Builder builder = new AudioFormat.Builder();
builder.setEncoding(FORMAT);
builder.setEncoding(ENCODING);
builder.setSampleRate(SAMPLE_RATE);
builder.setChannelMask(CHANNEL_CONFIG);
return builder.build();
@@ -50,9 +52,9 @@ public final class AudioCapture {
// On older APIs, Workarounds.fillAppInfo() must be called beforehand
builder.setContext(FakeContext.get());
}
builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX);
builder.setAudioSource(SOURCE);
builder.setAudioFormat(createAudioFormat());
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, FORMAT);
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING);
// This buffer size does not impact latency
builder.setBufferSizeInBytes(8 * minBufferSize);
return builder.build();
@@ -97,7 +99,14 @@ public final class AudioCapture {
}
private void startRecording() {
recorder = createAudioRecord();
try {
recorder = createAudioRecord();
} 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.startRecording();
}

View File

@@ -1,13 +1,22 @@
package com.genymobile.scrcpy;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Application;
import android.content.AttributionSource;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.os.Build;
import android.os.Looper;
import android.os.Parcel;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public final class Workarounds {
@@ -95,4 +104,140 @@ public final class Workarounds {
Ln.d("Could not fill app context: " + throwable.getMessage());
}
}
@TargetApi(Build.VERSION_CODES.R)
@SuppressLint({"WrongConstant", "MissingPermission", "BlockedPrivateApi", "SoonBlockedPrivateApi"})
public static AudioRecord createAudioRecord(int source, int sampleRate, int channelConfig, int channels, int channelMask, int encoding) {
// Vivo (and maybe some other third-party ROMs) modified `AudioRecord`'s constructor, requiring `Context`s from real App environment.
//
// This method invokes the `AudioRecord(long nativeRecordInJavaObj)` constructor to create an empty `AudioRecord` instance, then uses
// reflections to initialize it like the normal constructor do (or the `AudioRecord.Builder.build()` method do).
// As a result, the modified code was not executed.
try {
// AudioRecord audioRecord = new AudioRecord(0L);
Constructor<AudioRecord> audioRecordConstructor = AudioRecord.class.getDeclaredConstructor(long.class);
audioRecordConstructor.setAccessible(true);
AudioRecord audioRecord = audioRecordConstructor.newInstance(0L);
// audioRecord.mRecordingState = RECORDSTATE_STOPPED;
Field mRecordingStateField = AudioRecord.class.getDeclaredField("mRecordingState");
mRecordingStateField.setAccessible(true);
mRecordingStateField.set(audioRecord, AudioRecord.RECORDSTATE_STOPPED);
Looper looper = Looper.myLooper();
if (looper == null) {
looper = Looper.getMainLooper();
}
// audioRecord.mInitializationLooper = looper;
Field mInitializationLooperField = AudioRecord.class.getDeclaredField("mInitializationLooper");
mInitializationLooperField.setAccessible(true);
mInitializationLooperField.set(audioRecord, looper);
// Create `AudioAttributes` with fixed capture preset
int capturePreset = source;
AudioAttributes.Builder audioAttributesBuilder = new AudioAttributes.Builder();
Method setInternalCapturePresetMethod = AudioAttributes.Builder.class.getMethod("setInternalCapturePreset", int.class);
setInternalCapturePresetMethod.invoke(audioAttributesBuilder, capturePreset);
AudioAttributes attributes = audioAttributesBuilder.build();
// audioRecord.mAudioAttributes = attributes;
Field mAudioAttributesField = AudioRecord.class.getDeclaredField("mAudioAttributes");
mAudioAttributesField.setAccessible(true);
mAudioAttributesField.set(audioRecord, attributes);
// audioRecord.audioParamCheck(capturePreset, sampleRate, encoding);
Method audioParamCheckMethod = AudioRecord.class.getDeclaredMethod("audioParamCheck", int.class, int.class, int.class);
audioParamCheckMethod.setAccessible(true);
audioParamCheckMethod.invoke(audioRecord, capturePreset, sampleRate, encoding);
// audioRecord.mChannelCount = channels
Field mChannelCountField = AudioRecord.class.getDeclaredField("mChannelCount");
mChannelCountField.setAccessible(true);
mChannelCountField.set(audioRecord, channels);
// audioRecord.mChannelMask = channelMask
Field mChannelMaskField = AudioRecord.class.getDeclaredField("mChannelMask");
mChannelMaskField.setAccessible(true);
mChannelMaskField.set(audioRecord, channelMask);
int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, encoding);
int bufferSizeInBytes = minBufferSize * 8;
// audioRecord.audioBuffSizeCheck(bufferSizeInBytes)
Method audioBuffSizeCheckMethod = AudioRecord.class.getDeclaredMethod("audioBuffSizeCheck", int.class);
audioBuffSizeCheckMethod.setAccessible(true);
audioBuffSizeCheckMethod.invoke(audioRecord, bufferSizeInBytes);
final int channelIndexMask = 0;
int[] sampleRateArray = new int[]{sampleRate};
int[] session = new int[]{AudioManager.AUDIO_SESSION_ID_GENERATE};
int initResult;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
// private native final int native_setup(Object audiorecord_this,
// Object /*AudioAttributes*/ attributes,
// int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
// int buffSizeInBytes, int[] sessionId, String opPackageName,
// long nativeRecordInJavaObj);
Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, int.class,
int.class, int.class, int.class, int[].class, String.class, long.class);
nativeSetupMethod.setAccessible(true);
initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference<AudioRecord>(audioRecord), attributes, sampleRateArray,
channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, FakeContext.get().getOpPackageName(),
0L);
} else {
// Assume `context` is never `null`
AttributionSource attributionSource = FakeContext.get().getAttributionSource();
// Assume `attributionSource.getPackageName()` is never null
// ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()
Method asScopedParcelStateMethod = AttributionSource.class.getDeclaredMethod("asScopedParcelState");
asScopedParcelStateMethod.setAccessible(true);
try (AutoCloseable attributionSourceState = (AutoCloseable) asScopedParcelStateMethod.invoke(attributionSource)) {
Method getParcelMethod = attributionSourceState.getClass().getDeclaredMethod("getParcel");
Parcel attributionSourceParcel = (Parcel) getParcelMethod.invoke(attributionSourceState);
// private native int native_setup(Object audiorecordThis,
// Object /*AudioAttributes*/ attributes,
// int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
// int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource,
// long nativeRecordInJavaObj, int maxSharedAudioHistoryMs);
Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, int.class,
int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class);
nativeSetupMethod.setAccessible(true);
initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference<AudioRecord>(audioRecord), attributes, sampleRateArray,
channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, attributionSourceParcel, 0L, 0);
}
}
if (initResult != AudioRecord.SUCCESS) {
Ln.e("Error code " + initResult + " when initializing native AudioRecord object.");
throw new RuntimeException("Cannot create AudioRecord");
}
// mSampleRate = sampleRate[0]
Field mSampleRateField = AudioRecord.class.getDeclaredField("mSampleRate");
mSampleRateField.setAccessible(true);
mSampleRateField.set(audioRecord, sampleRateArray[0]);
// audioRecord.mSessionId = session[0]
Field mSessionIdField = AudioRecord.class.getDeclaredField("mSessionId");
mSessionIdField.setAccessible(true);
mSessionIdField.set(audioRecord, session[0]);
// audioRecord.mState = AudioRecord.STATE_INITIALIZED
Field mStateField = AudioRecord.class.getDeclaredField("mState");
mStateField.setAccessible(true);
mStateField.set(audioRecord, AudioRecord.STATE_INITIALIZED);
return audioRecord;
} catch (Exception e) {
Ln.e("Failed to invoke AudioRecord.<init>.", e);
throw new RuntimeException("Cannot create AudioRecord");
}
}
}