Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Android: Rewrite lcd subsystem to use the SurfaceView API.

That enables drawing from outside the apps UI thread, i.e. from within the Rockbox native thread, without needing synchronization means, and adds determinism as to when the draw happens.
It simplifies the drawing routines and adds a convinient way of detecting whether drawing should happen or not (surfaceCreated/Destroyed).
It also restores max. fps on my phone which went down drastically with the gingerbread(CM7) update.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29333 a1c6a512-1295-4272-9138-f99709370657

+157 -177
+20 -25
android/src/org/rockbox/RockboxActivity.java
··· 66 66 protected void onReceiveResult(final int resultCode, final Bundle resultData) 67 67 { 68 68 switch (resultCode) { 69 - case RockboxService.RESULT_LIB_LOADED: 70 - rbservice = RockboxService.get_instance(); 69 + case RockboxService.RESULT_INVOKING_MAIN: 71 70 if (loadingdialog != null) 72 - loadingdialog.setIndeterminate(true); 71 + loadingdialog.dismiss(); 73 72 break; 74 73 case RockboxService.RESULT_LIB_LOAD_PROGRESS: 75 74 if (loadingdialog == null) ··· 79 78 loadingdialog.setMax(resultData.getInt("max", 100)); 80 79 loadingdialog.setProgress(resultData.getInt("value", 0)); 81 80 break; 82 - case RockboxService.RESULT_FB_INITIALIZED: 81 + case RockboxService.RESULT_SERVICE_RUNNING: 82 + rbservice = RockboxService.get_instance(); 83 + setServiceActivity(true); 83 84 attachFramebuffer(); 84 - if (loadingdialog != null) 85 - loadingdialog.dismiss(); 86 85 break; 87 86 case RockboxService.RESULT_ERROR_OCCURED: 88 87 Toast.makeText(RockboxActivity.this, resultData.getString("error"), Toast.LENGTH_LONG); ··· 93 92 startService(intent); 94 93 } 95 94 96 - private boolean isRockboxRunning() 95 + private void setServiceActivity(boolean set) 97 96 { 98 - if (rbservice == null) 99 - rbservice = RockboxService.get_instance(); 100 - return (rbservice!= null && rbservice.isRockboxRunning() == true); 97 + if (rbservice != null) 98 + rbservice.set_activity(this); 101 99 } 102 100 103 101 private void attachFramebuffer() 104 102 { 105 - View rbFramebuffer = rbservice.get_fb(); 103 + View rbFramebuffer = null; 106 104 try { 105 + rbFramebuffer = rbservice.get_fb(); 107 106 setContentView(rbFramebuffer); 108 107 } catch (IllegalStateException e) { 109 108 /* we are already using the View, ··· 111 110 ViewGroup g = (ViewGroup) rbFramebuffer.getParent(); 112 111 g.removeView(rbFramebuffer); 113 112 setContentView(rbFramebuffer); 114 - } finally { 115 - rbFramebuffer.requestFocus(); 116 - rbservice.set_activity(this); 113 + } catch (NullPointerException e) { 114 + return; 117 115 } 116 + rbFramebuffer.requestFocus(); 118 117 } 119 118 120 119 public void onResume() 121 120 { 122 121 super.onResume(); 123 - if (isRockboxRunning()) 124 - attachFramebuffer(); 122 + setVisible(true); 123 + attachFramebuffer(); 125 124 } 126 125 127 126 /* this is also called when the backlight goes off, ··· 131 130 protected void onPause() 132 131 { 133 132 super.onPause(); 134 - if (rbservice != null) 135 - { 136 - rbservice.set_activity(null); 137 - rbservice.get_fb().dispatchWindowVisibilityChanged(View.INVISIBLE); 138 - } 133 + /* this will cause the framebuffer's Surface to be destroyed, enabling 134 + * us to disable drawing */ 135 + setVisible(false); 139 136 } 140 137 141 138 @Override 142 139 protected void onStop() 143 140 { 144 141 super.onStop(); 145 - if (rbservice != null) 146 - rbservice.set_activity(null); 142 + setServiceActivity(false); 147 143 } 148 144 149 145 @Override 150 146 protected void onDestroy() 151 147 { 152 148 super.onDestroy(); 153 - if (rbservice != null) 154 - rbservice.set_activity(null); 149 + setServiceActivity(false); 155 150 } 156 151 157 152 private void LOG(CharSequence text)
+54 -47
android/src/org/rockbox/RockboxFramebuffer.java
··· 23 23 24 24 import java.nio.ByteBuffer; 25 25 26 - import org.rockbox.Helper.MediaButtonReceiver; 27 - 28 26 import android.content.Context; 29 27 import android.graphics.Bitmap; 30 28 import android.graphics.Canvas; ··· 33 31 import android.util.Log; 34 32 import android.view.KeyEvent; 35 33 import android.view.MotionEvent; 36 - import android.view.View; 34 + import android.view.SurfaceHolder; 35 + import android.view.SurfaceView; 37 36 import android.view.ViewConfiguration; 38 37 39 - public class RockboxFramebuffer extends View 38 + public class RockboxFramebuffer extends SurfaceView 39 + implements SurfaceHolder.Callback 40 40 { 41 - private Bitmap btm; 42 - private Rect rect; 43 - private ByteBuffer native_buf; 44 - private MediaButtonReceiver media_monitor; 45 41 private final DisplayMetrics metrics; 46 42 private final ViewConfiguration view_config; 43 + private ByteBuffer native_buf; 44 + private Bitmap btm; 47 45 48 - public RockboxFramebuffer(Context c, int lcd_width, 49 - int lcd_height, ByteBuffer native_fb) 46 + /* first stage init; needs to run from a thread that has a Looper 47 + * setup stuff that needs a Context */ 48 + public RockboxFramebuffer(Context c) 50 49 { 51 50 super(c); 51 + 52 + metrics = c.getResources().getDisplayMetrics(); 53 + view_config = ViewConfiguration.get(c); 54 + getHolder().addCallback(this); 52 55 /* Needed so we can catch KeyEvents */ 53 56 setFocusable(true); 54 57 setFocusableInTouchMode(true); 55 58 setClickable(true); 59 + /* don't draw until native is ready (2nd stage) */ 60 + setEnabled(false); 61 + } 62 + 63 + /* second stage init; called from Rockbox with information about the 64 + * display framebuffer */ 65 + @SuppressWarnings("unused") 66 + private void java_lcd_init(int lcd_width, int lcd_height, ByteBuffer native_fb) 67 + { 56 68 btm = Bitmap.createBitmap(lcd_width, lcd_height, Bitmap.Config.RGB_565); 57 - rect = new Rect(); 58 69 native_buf = native_fb; 59 - media_monitor = new MediaButtonReceiver(c); 60 - media_monitor.register(); 61 - /* the service needs to know the about us */ 62 - ((RockboxService)c).set_fb(this); 63 - 64 - metrics = c.getResources().getDisplayMetrics(); 65 - view_config = ViewConfiguration.get(c); 70 + setEnabled(true); 66 71 } 67 72 68 - public void onDraw(Canvas c) 73 + @SuppressWarnings("unused") 74 + private void java_lcd_update() 75 + { 76 + SurfaceHolder holder = getHolder(); 77 + Canvas c = holder.lockCanvas(null); 78 + btm.copyPixelsFromBuffer(native_buf); 79 + synchronized (holder) 80 + { /* draw */ 81 + c.drawBitmap(btm, 0.0f, 0.0f, null); 82 + } 83 + holder.unlockCanvasAndPost(c); 84 + } 85 + 86 + @SuppressWarnings("unused") 87 + private void java_lcd_update_rect(int x, int y, int width, int height) 69 88 { 70 - /* can't copy a partial buffer :( */ 89 + SurfaceHolder holder = getHolder(); 90 + Rect dirty = new Rect(x, y, x+width, y+height); 91 + Canvas c = holder.lockCanvas(dirty); 92 + /* can't copy a partial buffer, 93 + * but it doesn't make a noticeable difference anyway */ 71 94 btm.copyPixelsFromBuffer(native_buf); 72 - c.getClipBounds(rect); 73 - c.drawBitmap(btm, rect, rect, null); 74 - post_update_done(); 95 + synchronized (holder) 96 + { /* draw */ 97 + c.drawBitmap(btm, dirty, dirty, null); 98 + } 99 + holder.unlockCanvasAndPost(c); 75 100 } 76 101 77 102 @SuppressWarnings("unused") ··· 109 134 { 110 135 return buttonHandler(keyCode, false); 111 136 } 112 - 113 - public void destroy() 114 - { 115 - set_lcd_active(0); 116 - media_monitor.unregister(); 117 - } 118 - 119 - @Override 120 - protected void onWindowVisibilityChanged(int visibility) 121 - { 122 - super.onWindowVisibilityChanged(visibility); 123 - 124 - switch (visibility) { 125 - case VISIBLE: 126 - set_lcd_active(1); 127 - break; 128 - case GONE: 129 - case INVISIBLE: 130 - set_lcd_active(0); 131 - break; 132 - } 133 - } 134 137 135 138 @SuppressWarnings("unused") 136 139 private int getDpi() ··· 144 147 return view_config.getScaledTouchSlop(); 145 148 } 146 149 147 - private native void post_update_done(); 148 - private native void set_lcd_active(int active); 149 150 private native void touchHandler(boolean down, int x, int y); 150 151 private native static boolean buttonHandler(int keycode, boolean state); 152 + 153 + public native void surfaceCreated(SurfaceHolder holder); 154 + public native void surfaceDestroyed(SurfaceHolder holder); 155 + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 156 + { 157 + } 151 158 }
+20 -25
android/src/org/rockbox/RockboxService.java
··· 31 31 import java.util.zip.ZipEntry; 32 32 import java.util.zip.ZipFile; 33 33 34 + import org.rockbox.Helper.MediaButtonReceiver; 34 35 import org.rockbox.Helper.RunForegroundManager; 35 36 36 37 import android.app.Activity; ··· 59 60 60 61 /* locals needed for the c code and rockbox state */ 61 62 private RockboxFramebuffer fb = null; 62 - private boolean mRockboxRunning = false; 63 - private volatile boolean rbLibLoaded; 63 + private volatile boolean rockbox_running; 64 64 private Activity current_activity = null; 65 65 private IntentFilter itf; 66 66 private BroadcastReceiver batt_monitor; 67 67 private RunForegroundManager fg_runner; 68 + private MediaButtonReceiver mMediaButtonReceiver; 68 69 @SuppressWarnings("unused") 69 70 private int battery_level; 70 71 private ResultReceiver resultReceiver; 71 72 72 - public static final int RESULT_LIB_LOADED = 0; 73 + public static final int RESULT_INVOKING_MAIN = 0; 73 74 public static final int RESULT_LIB_LOAD_PROGRESS = 1; 74 75 public static final int RESULT_FB_INITIALIZED = 2; 75 - public static final int RESULT_ERROR_OCCURED = 3; 76 + public static final int RESULT_SERVICE_RUNNING = 3; 77 + public static final int RESULT_ERROR_OCCURED = 4; 76 78 77 79 @Override 78 80 public void onCreate() ··· 89 91 { 90 92 return fb; 91 93 } 92 - /* framebuffer is initialised by the native code(!) so this is needed */ 93 - public void set_fb(RockboxFramebuffer newfb) 94 - { 95 - fb = newfb; 96 - mRockboxRunning = true; 97 - if (resultReceiver != null) 98 - resultReceiver.send(RESULT_FB_INITIALIZED, null); 99 - } 100 94 101 95 public Activity get_activity() 102 96 { ··· 113 107 114 108 if (intent != null && intent.hasExtra("callback")) 115 109 resultReceiver = (ResultReceiver) intent.getParcelableExtra("callback"); 116 - if (!rbLibLoaded) 110 + if (!rockbox_running) 117 111 startservice(); 118 112 119 113 if (intent != null && intent.getAction() != null) ··· 151 145 e.printStackTrace(); 152 146 } 153 147 } 148 + if (resultReceiver != null) 149 + resultReceiver.send(RESULT_SERVICE_RUNNING, null); 154 150 } 155 151 156 152 private void LOG(CharSequence text) ··· 176 172 private void startservice() 177 173 { 178 174 final int BUFFER = 8*1024; 175 + fb = new RockboxFramebuffer(this); 176 + if (resultReceiver != null) 177 + resultReceiver.send(RESULT_FB_INITIALIZED, null); 178 + mMediaButtonReceiver = new MediaButtonReceiver(this); 179 + mMediaButtonReceiver.register(); 179 180 Thread rb = new Thread(new Runnable() 180 181 { 181 182 public void run() ··· 247 248 } 248 249 } 249 250 250 - System.loadLibrary("rockbox"); 251 - rbLibLoaded = true; 251 + System.loadLibrary("rockbox"); 252 + 253 + rockbox_running = true; 252 254 if (resultReceiver != null) 253 - resultReceiver.send(RESULT_LIB_LOADED, null); 255 + resultReceiver.send(RESULT_INVOKING_MAIN, null); 254 256 255 257 main(); 256 258 throw new IllegalStateException("native main() returned!"); ··· 259 261 rb.setDaemon(false); 260 262 rb.start(); 261 263 } 264 + 262 265 private native void main(); 263 - 264 - /* returns true once rockbox is up and running. 265 - * This is considered done once the framebuffer is initialised 266 - */ 267 - public boolean isRockboxRunning() 268 - { 269 - return mRockboxRunning; 270 - } 271 266 272 267 @Override 273 268 public IBinder onBind(Intent intent) ··· 329 324 public void onDestroy() 330 325 { 331 326 super.onDestroy(); 332 - fb.destroy(); 327 + mMediaButtonReceiver.unregister(); 333 328 /* Make sure our notification is gone. */ 334 329 stopForeground(); 335 330 }
+3
apps/keymaps/keymap-android.c
··· 50 50 { ACTION_STD_MENU, BUTTON_MENU|BUTTON_REL, BUTTON_MENU }, 51 51 { ACTION_STD_CONTEXT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU }, 52 52 53 + /* special hack to get a redraw on activity resume, see lcd-android.c */ 54 + { ACTION_REDRAW, BUTTON_FORCE_REDRAW, BUTTON_NONE }, 55 + 53 56 LAST_ITEM_IN_LIST 54 57 }; /* button_context_standard */ 55 58
+2
firmware/target/hosted/android/app/button-target.h
··· 58 58 #define BUTTON_BOTTOMMIDDLE 0x00080000 59 59 #define BUTTON_BOTTOMRIGHT 0x00100000 60 60 61 + #define BUTTON_FORCE_REDRAW 0x00200000 62 + 61 63 /* No remote */ 62 64 #define BUTTON_REMOTE 0 63 65
+58 -80
firmware/target/hosted/android/lcd-android.c
··· 21 21 22 22 23 23 #include <jni.h> 24 + #include <string.h> 24 25 #include "config.h" 25 26 #include "system.h" 26 27 #include "kernel.h" 27 28 #include "lcd.h" 29 + #include "button.h" 28 30 29 31 extern JNIEnv *env_ptr; 30 32 extern jclass RockboxService_class; ··· 32 34 33 35 static jclass RockboxFramebuffer_class; 34 36 static jobject RockboxFramebuffer_instance; 35 - static jmethodID postInvalidate1; 36 - static jmethodID postInvalidate2; 37 + static jmethodID java_lcd_update; 38 + static jmethodID java_lcd_update_rect; 37 39 38 - static bool display_on; 39 40 static int dpi; 40 41 static int scroll_threshold; 41 - static struct wakeup lcd_wakeup; 42 - static struct mutex lcd_mtx; 42 + static bool display_on; 43 43 44 44 void lcd_init_device(void) 45 45 { 46 46 JNIEnv e = *env_ptr; 47 - wakeup_init(&lcd_wakeup); 48 - mutex_init(&lcd_mtx); 49 - RockboxFramebuffer_class = e->FindClass(env_ptr, 50 - "org/rockbox/RockboxFramebuffer"); 51 - /* instantiate a RockboxFramebuffer instance 52 - * 53 - * Pass lcd width and height and our framebuffer so the java layer 54 - * can create a Bitmap which directly maps to it 55 - **/ 47 + /* get existing instance from the Service */ 48 + jmethodID get_fb = e->GetMethodID(env_ptr, RockboxService_class, "get_fb", 49 + "()Lorg/rockbox/RockboxFramebuffer;"); 50 + RockboxFramebuffer_instance = e->CallObjectMethod(env_ptr, 51 + RockboxService_instance, 52 + get_fb); 53 + RockboxFramebuffer_class = (*env_ptr)->GetObjectClass(env_ptr, 54 + RockboxFramebuffer_instance); 55 + 56 + /* Get init function and set up what's left from the constructor */ 57 + jmethodID java_lcd_init = (*env_ptr)->GetMethodID(env_ptr, 58 + RockboxFramebuffer_class, 59 + "java_lcd_init", 60 + "(IILjava/nio/ByteBuffer;)V"); 56 61 57 - /* map the framebuffer to a ByteBuffer, this way lcd updates will 58 - * be directly feched from the framebuffer */ 59 62 jobject buf = e->NewDirectByteBuffer(env_ptr, 60 63 lcd_framebuffer, 61 64 (jlong)sizeof(lcd_framebuffer)); 62 65 63 - jmethodID constructor = e->GetMethodID(env_ptr, 64 - RockboxFramebuffer_class, 65 - "<init>", 66 - "(Landroid/content/Context;" /* Service */ 67 - "II" /* lcd width/height */ 68 - "Ljava/nio/ByteBuffer;)V"); /* ByteBuffer */ 69 - 70 - RockboxFramebuffer_instance = e->NewObject(env_ptr, 71 - RockboxFramebuffer_class, 72 - constructor, 73 - RockboxService_instance, 66 + e->CallVoidMethod(env_ptr, RockboxFramebuffer_instance, java_lcd_init, 74 67 (jint)LCD_WIDTH, 75 68 (jint)LCD_HEIGHT, 76 69 buf); 77 70 78 71 /* cache update functions */ 79 - postInvalidate1 = (*env_ptr)->GetMethodID(env_ptr, 72 + java_lcd_update = (*env_ptr)->GetMethodID(env_ptr, 80 73 RockboxFramebuffer_class, 81 - "postInvalidate", 74 + "java_lcd_update", 82 75 "()V"); 83 - postInvalidate2 = (*env_ptr)->GetMethodID(env_ptr, 76 + java_lcd_update_rect = (*env_ptr)->GetMethodID(env_ptr, 84 77 RockboxFramebuffer_class, 85 - "postInvalidate", 78 + "java_lcd_update_rect", 86 79 "(IIII)V"); 87 80 88 81 jmethodID get_dpi = e->GetMethodID(env_ptr, ··· 98 91 get_dpi); 99 92 scroll_threshold = e->CallIntMethod(env_ptr, RockboxFramebuffer_instance, 100 93 get_scroll_threshold); 101 - display_on = true; 94 + /* must not draw until surface is created */ 95 + display_on = false; 102 96 } 103 97 104 - /* the update mechanism is asynchronous since 105 - * onDraw() must be called from the UI thread 106 - * 107 - * The Rockbox thread calling lcd_update() has to wait 108 - * for the update to complete, so that it's synchronous, 109 - * and we need to notify it (we could wait in the java layer, but 110 - * that'd block the other Rockbox threads too) 111 - * 112 - * That should give more smoonth animations 113 - */ 114 98 void lcd_update(void) 115 99 { 116 - /* tell the system we're ready for drawing */ 117 100 if (display_on) 118 - { 119 - mutex_lock(&lcd_mtx); 120 - (*env_ptr)->CallVoidMethod(env_ptr, RockboxFramebuffer_instance, postInvalidate1); 121 - wakeup_wait(&lcd_wakeup, TIMEOUT_BLOCK); 122 - mutex_unlock(&lcd_mtx); 123 - } 101 + (*env_ptr)->CallVoidMethod(env_ptr, RockboxFramebuffer_instance, 102 + java_lcd_update); 124 103 } 125 104 126 105 void lcd_update_rect(int x, int y, int width, int height) 127 106 { 128 107 if (display_on) 129 - { 130 - mutex_lock(&lcd_mtx); 131 - (*env_ptr)->CallVoidMethod(env_ptr, RockboxFramebuffer_instance, postInvalidate2, 132 - (jint)x, (jint)y, (jint)x+width, (jint)y+height); 133 - wakeup_wait(&lcd_wakeup, TIMEOUT_BLOCK); 134 - mutex_unlock(&lcd_mtx); 135 - } 108 + (*env_ptr)->CallVoidMethod(env_ptr, RockboxFramebuffer_instance, 109 + java_lcd_update_rect, x, y, width, height); 110 + } 111 + 112 + /* 113 + * this is called when the surface is created, which called is everytime 114 + * the activity is brought in front and the RockboxFramebuffer gains focus 115 + * 116 + * Note this is considered interrupt context 117 + */ 118 + JNIEXPORT void JNICALL 119 + Java_org_rockbox_RockboxFramebuffer_surfaceCreated(JNIEnv *e, jobject this, 120 + jobject surfaceholder) 121 + { 122 + (void)e; (void)this; (void)surfaceholder; 123 + 124 + display_on = true; 125 + send_event(LCD_EVENT_ACTIVATION, NULL); 126 + /* Force an update, since the newly created surface is initially black 127 + * waiting for the next normal update results in a longish black screen */ 128 + queue_post(&button_queue, BUTTON_FORCE_REDRAW, 0); 136 129 } 137 130 131 + /* 132 + * the surface is destroyed everytime the RockboxFramebuffer loses focus and 133 + * goes invisible 134 + */ 138 135 JNIEXPORT void JNICALL 139 - Java_org_rockbox_RockboxFramebuffer_post_1update_1done(JNIEnv *e, jobject this) 136 + Java_org_rockbox_RockboxFramebuffer_surfaceDestroyed(JNIEnv *e, jobject this, 137 + jobject surfaceholder) 140 138 { 141 - (void)e; 142 - (void)this; 143 - wakeup_signal(&lcd_wakeup); 139 + (void)e; (void)this; (void)surfaceholder; 140 + 141 + display_on = false; 144 142 } 145 143 146 144 bool lcd_active(void) ··· 158 156 return scroll_threshold; 159 157 } 160 158 161 - /* 162 - * (un)block lcd updates. 163 - * 164 - * Notice: This is called from the activity thread, so take it 165 - * as interrupt context and take care what the event callback does 166 - * (it shouldn't block in particular 167 - * 168 - * the 1s are needed due to strange naming conventions... 169 - **/ 170 - JNIEXPORT void JNICALL 171 - Java_org_rockbox_RockboxFramebuffer_set_1lcd_1active(JNIEnv *e, 172 - jobject this, 173 - jint active) 174 - { 175 - (void)e; 176 - (void)this; 177 - display_on = active != 0; 178 - if (active) 179 - send_event(LCD_EVENT_ACTIVATION, NULL); 180 - } 181 159 /* below is a plain copy from lcd-sdl.c */ 182 160 183 161 /**