Skip to content

Optimize ram performance 126302852423521117#29

Merged
b1ume merged 6 commits into
mainfrom
optimize-ram-performance-126302852423521117
Feb 15, 2026
Merged

Optimize ram performance 126302852423521117#29
b1ume merged 6 commits into
mainfrom
optimize-ram-performance-126302852423521117

Conversation

@b1ume

@b1ume b1ume commented Feb 15, 2026

Copy link
Copy Markdown
Owner

No description provided.

Copilot AI and others added 6 commits February 15, 2026 21:59
Changes:
- Extracted RGB_565 decoding to `getPixel` helper in `BorderProcessor`.
- Added try-catch for `ImageReader` creation in `HyperionScreenEncoder` (init and setOrientation) with fallback to `RGBA_8888`.
- Restored optimized fast-path for `RGBA_8888` in `HyperionScreenEncoder.extractRgb`.
- Restored `mRowBuffer` field for optimized RGBA path.
…RGBA fast-path

- Extract RGB_565 decoding into reusable decodePixel() helper method in BorderProcessor to eliminate duplication
- Add robust error handling with RGBA_8888 fallback for ImageReader creation in init() and setOrientation()
- Restore optimized RGBA_8888 fast-path with bulk row reads to avoid performance regression
- Track pixel format in mPixelFormat field for proper format-aware processing

Co-authored-by: haumlab <222520099+haumlab@users.noreply.github.com>
- Remove trailing whitespace in BorderProcessor for consistent formatting
- Move rowBuffer allocation to class level in HyperionScreenEncoder to reduce GC pressure during continuous capture
- Clear mRowBuffer in stopRecording() and setOrientation() cleanup paths

Co-authored-by: haumlab <222520099+haumlab@users.noreply.github.com>
Address code review feedback: extract RGB_565 helper, add error handling, restore RGBA fast-path
Copilot AI review requested due to automatic review settings February 15, 2026 23:05
@b1ume
b1ume merged commit 91775a9 into main Feb 15, 2026
2 of 3 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR primarily targets runtime performance/behavior changes across the capture/network pipeline and app UI helpers, while also removing several test/benchmark artifacts.

Changes:

  • Reworks Hyperion server discovery: replaces mDNS-based scanning with a subnet TCP-connect scanner and updates the TV scan UI flow accordingly.
  • Adjusts screen capture pixel handling: adds an RGBA row-bulk fast path and introduces RGB_565→RGBA_8888 fallback logic for ImageReader.
  • Refactors/cleans up: removes helper/test/benchmark files and updates update-download/install logic and some Activity utilities.

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
test_output.txt Deletes committed build output/log file.
common/src/test/java/com/hyperion/grabber/common/util/LedDataExtractorBenchmark.java Removes benchmark test.
common/src/test/java/com/hyperion/grabber/common/util/HyperionGrabberOptionsTest.java Removes unit tests for divisor logic.
common/src/test/java/com/hyperion/grabber/common/util/BorderProcessorTest.java Removes unit tests for border parsing.
common/src/main/java/com/hyperion/grabber/util/UpdateManager.kt Switches update download/install to public Downloads path + public DownloadManager destination.
common/src/main/java/com/hyperion/grabber/util/HyperionScannerTask.java Replaces async scan implementation with an executor-based runner using new NetworkScanner.
common/src/main/java/com/hyperion/grabber/util/BorderProcessor.java Refactors border pixel reads to support RGB_565 and adds pixel decode helpers.
common/src/main/java/com/hyperion/grabber/network/WLEDClient.java Changes LED extraction/count retrieval and refactors color-order conversion in packet building.
common/src/main/java/com/hyperion/grabber/network/NetworkScanner.java Replaces NSD/mDNS scanning with subnet IP connect attempts + progress reporting.
common/src/main/java/com/hyperion/grabber/network/HyperionThread.java Extends thread construction/preferences with smoothing-related parameters.
common/src/main/java/com/hyperion/grabber/network/AdalightClient.java Changes LED extraction/count retrieval and removes packet buffer reuse.
common/src/main/java/com/hyperion/grabber/common/util/LedDataExtractor.java Removes the “pass x/y counts directly” fast API and uses Context+Preferences lookup.
common/src/main/java/com/hyperion/grabber/HyperionScreenService.java Threads smoothing settings through and inlines start/prepare logic.
common/src/main/java/com/hyperion/grabber/HyperionScreenEncoder.java Adds RGB_565 fallback handling and RGBA row-buffer fast path.
common/build.gradle Removes Robolectric/Mockito test dependencies.
app/src/main/java/com/hyperion/grabber/tv/activities/ScanResultActivity.java Replaces structured logging with printStackTrace() on error path.
app/src/main/java/com/hyperion/grabber/tv/activities/NetworkScanActivity.java Updates to new scanner task + progress bar behavior changes.
app/src/main/java/com/hyperion/grabber/tv/activities/MainActivity.java Inlines helper logic (locale, fade, service running checks, etc.).
app/src/main/java/com/hyperion/grabber/tv/activities/LeanbackActivity.java Inlines locale update logic into attachBaseContext.
app/src/main/java/com/hyperion/grabber/UpdateDialog.kt Replaces structured logging with printStackTrace() on error path.
app/src/main/java/com/hyperion/grabber/MainActivity.java Inlines helper logic (locale, fade, service running checks, etc.).
common/src/main/java/com/hyperion/grabber/HyperionActivityHelper.java Removes centralized helper class and duplicates logic into Activities.
Comments suppressed due to low confidence (1)

app/src/main/java/com/hyperion/grabber/tv/activities/NetworkScanActivity.java:138

  • onScannerCompleted() no longer resets the progress bar state. If the scan is retried, the UI may start with a stale progress value. Consider resetting progress (and indeterminate mode, if used) when the scan completes or restarts.
    public void onScannerCompleted(@Nullable String foundIpAddress) {
        isScanning = false;

        if (foundIpAddress == null){
            startScanButton.setText(com.hyperion.grabber.common.R.string.scanner_retry_button);
            manualSetupButton.requestFocus();
            descriptionText.setText(getString(com.hyperion.grabber.common.R.string.scanner_no_results, "\uD83D\uDE29")); // 😩

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +87 to 92
final int[] rgb = new int[3];

// Optimization: Pre-calculate invariant multiplications outside the loops
int height33PercentRowStride = height33percent * rowStride;
int height66PercentRowStride = height66percent * rowStride;
int widthPixelStride = width * pixelStride;
int yCenterRowStride = yCenter * rowStride;
// centerBaseX = (yCenter * rowStride) + ((width - 1) * pixelStride)
// posCentered = centerBaseX - (x * pixelStride)
int centerBaseX = yCenterRowStride + widthPixelStride - pixelStride;
buffer.position(0).mark();

int width33PercentPixelStride = width33percent * pixelStride;
int[] rgb = new int[3]; // Reusable array for RGB values

Copilot AI Feb 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rgb is declared twice in the same scope (final int[] rgb and int[] rgb), which will not compile. Remove one declaration and reuse a single buffer for decoded RGB values.

Copilot uses AI. Check for mistakes.
try {
startScreenRecord(intent);
} catch (SecurityException e) {
Log.e(TAG, "Failed to start screen recording: " + e.getMessage());

Copilot AI Feb 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SecurityException logging drops the stack trace by concatenating e.getMessage(). Use Log.e(TAG, "Failed to start screen recording", e) so the root cause is preserved in logs.

Suggested change
Log.e(TAG, "Failed to start screen recording: " + e.getMessage());
Log.e(TAG, "Failed to start screen recording", e);

Copilot uses AI. Check for mistakes.
Comment on lines 217 to 219
// Extract LED data reusing buffer
mLedDataBuffer = LedDataExtractor.extractLEDData(data, width, height, mXLed, mYLed, mLedDataBuffer);
mLedDataBuffer = LedDataExtractor.extractLEDData(mContext, data, width, height, mLedDataBuffer);
if (mLedDataBuffer.length == 0) return;

Copilot AI Feb 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LedDataExtractor.extractLEDData(mContext, ...) reads preferences on every frame. Since setImage(...) is called frequently, this can regress performance and increase allocations. Consider passing cached LED counts into the extractor or caching them in the client and refreshing only when preferences change.

Copilot uses AI. Check for mistakes.
Comment on lines 273 to 279
int idx = 2;
for (ColorRgb led : leds) {
convertColorOrder(led, mColorOrder, packet, idx);
idx += 3;
byte[] ordered = convertColorOrder(led, mColorOrder);
packet[idx++] = ordered[0];
packet[idx++] = ordered[1];
packet[idx++] = ordered[2];
}

Copilot AI Feb 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convertColorOrder(...) now allocates a new byte[3] for every LED while building packets, which creates significant GC pressure in the hot path. Prefer writing the ordered bytes directly into the destination packet buffer (or reuse a small scratch array) to avoid per-LED allocations.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +61
Socket socket = new Socket();
String ip = ipsToTry[++lastTriedIndex];
try {
socket.connect(new InetSocketAddress(ip, PORT), ATTEMPT_TIMEOUT_MS);

if (socket.isConnected()){
socket.close();
return ip;
}

@Override
public void onServiceFound(NsdServiceInfo serviceInfo) {
Log.d(TAG, "Service found: " + serviceInfo);
// Check if the service type matches (handling potential trailing dot differences)
if (serviceInfo.getServiceType().contains("_hyperiond-flatbuf._tcp")) {
nsdManager.resolveService(serviceInfo, new NsdManager.ResolveListener() {
@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
Log.e(TAG, "Resolve failed: " + errorCode);
}
} catch(Exception e) {
return null;
}

Copilot AI Feb 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tryNext() returns early on exceptions without closing the Socket, leaking file descriptors across many attempts. Use try-with-resources or a finally block to always close the socket, even on connect failure.

Copilot uses AI. Check for mistakes.
isScanning = false;
}
};
return lastTriedIndex / (float)ipsToTry.length;

Copilot AI Feb 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getProgress() uses lastTriedIndex / ipsToTry.length, so progress never reaches 1.0 (and is 0.0 after the first attempt). Consider returning (lastTriedIndex + 1) / (float) ipsToTry.length and clamping to [0,1] so the UI can reach 100%.

Suggested change
return lastTriedIndex / (float)ipsToTry.length;
float progress = (lastTriedIndex + 1) / (float) ipsToTry.length;
if (progress < 0f) {
progress = 0f;
} else if (progress > 1f) {
progress = 1f;
}
return progress;

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +33
public class HyperionScannerTask {
private WeakReference<Listener> weakListener;
private Context context;
private NetworkScanner scanner;
private ExecutorService executor = Executors.newSingleThreadExecutor();
private Handler mainHandler = new Handler(Looper.getMainLooper());
private boolean found = false;

public HyperionScannerTask(@NonNull Context context, Listener listener){
this.context = context;
public HyperionScannerTask(Listener listener){
weakListener = new WeakReference<>(listener);
}

public void execute() {
Log.d(TAG, "Starting scan");
scanner = new NetworkScanner(context);
executor.execute(() -> {
String result = doInBackground();
mainHandler.post(() -> onPostExecute(result));
});
}

Copilot AI Feb 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HyperionScannerTask creates a new single-thread executor per scan but never shuts it down. This can leave threads around after the scan completes. Consider calling executor.shutdown()/shutdownNow() in onPostExecute() (and add cancellation support if the Activity is destroyed).

Copilot uses AI. Check for mistakes.
Comment on lines 34 to 36
} catch (e: Exception) {
Log.e(TAG, "Error showing update dialog", e)
e.printStackTrace()
onDismiss()

Copilot AI Feb 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

printStackTrace() should be avoided here; use Log.e(..., e) so the failure is properly captured in logcat and consistent with the rest of the app's error logging.

Copilot uses AI. Check for mistakes.
Comment on lines +166 to +202
/**
* Creates an ImageReader with the best available pixel format.
* Tries RGB_565 first for memory efficiency, falls back to RGBA_8888 if unsupported.
*
* @param width Image width
* @param height Image height
* @return ImageReader instance or null if both formats fail
*/
private ImageReader createImageReader(int width, int height) {
// Try RGB_565 first (uses 50% less memory than RGBA_8888)
try {
mPixelFormat = PixelFormat.RGB_565;
ImageReader reader = ImageReader.newInstance(
width, height,
mPixelFormat,
IMAGE_READER_IMAGES);
if (DEBUG) Log.d(TAG, "ImageReader created with RGB_565 format");
return reader;
} catch (IllegalArgumentException | RuntimeException e) {
Log.w(TAG, "RGB_565 format not supported, falling back to RGBA_8888", e);
}

// Fallback to RGBA_8888
try {
mPixelFormat = PixelFormat.RGBA_8888;
ImageReader reader = ImageReader.newInstance(
width, height,
mPixelFormat,
IMAGE_READER_IMAGES);
if (DEBUG) Log.d(TAG, "ImageReader created with RGBA_8888 format");
return reader;
} catch (IllegalArgumentException | RuntimeException e) {
Log.e(TAG, "Failed to create ImageReader with RGBA_8888 format", e);
return null;
}
}

Copilot AI Feb 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createImageReader(...) and mPixelFormat are currently unused (init/rotation create the ImageReader directly). This dead code makes it unclear which path is authoritative and risks drift. Either use createImageReader(...) consistently (and rely on mPixelFormat), or remove these additions.

Suggested change
/**
* Creates an ImageReader with the best available pixel format.
* Tries RGB_565 first for memory efficiency, falls back to RGBA_8888 if unsupported.
*
* @param width Image width
* @param height Image height
* @return ImageReader instance or null if both formats fail
*/
private ImageReader createImageReader(int width, int height) {
// Try RGB_565 first (uses 50% less memory than RGBA_8888)
try {
mPixelFormat = PixelFormat.RGB_565;
ImageReader reader = ImageReader.newInstance(
width, height,
mPixelFormat,
IMAGE_READER_IMAGES);
if (DEBUG) Log.d(TAG, "ImageReader created with RGB_565 format");
return reader;
} catch (IllegalArgumentException | RuntimeException e) {
Log.w(TAG, "RGB_565 format not supported, falling back to RGBA_8888", e);
}
// Fallback to RGBA_8888
try {
mPixelFormat = PixelFormat.RGBA_8888;
ImageReader reader = ImageReader.newInstance(
width, height,
mPixelFormat,
IMAGE_READER_IMAGES);
if (DEBUG) Log.d(TAG, "ImageReader created with RGBA_8888 format");
return reader;
} catch (IllegalArgumentException | RuntimeException e) {
Log.e(TAG, "Failed to create ImageReader with RGBA_8888 format", e);
return null;
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +417 to +425
private boolean isServiceRunning() {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
assert manager != null;
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (HyperionScreenService.class.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;

Copilot AI Feb 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isServiceRunning() uses assert manager != null; which may be ignored in production, leading to an NPE. Prefer a proper null-check and return false if ActivityManager is unavailable.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants