Hello, I'm working on a Nokia N900 port of rockbox. Attached you'll find my current code. It's working already quite nice :)
Some features: - Gstreamer based audio backend - Stop playback on incoming calls - Playback of music in "silent mode" (this was a tough one) - Battery levels and runtime estimation - Suspend screen updates if phone display is off or the application is not in focus - Adjust rockbox volume if the volume is modified from external - Assembler optimized playback The code is still a bit messy. I'm currently cleaning it up for flyspray submission. Things on my todo list: - Create something like a PLATFORM_MAEMO define and make the N900 support optional. - Use only one thread for N900 specific dbus communication - Move gstreamer backend from pcm-sdl.c to an own pcm-gstreamer.c - Remove "TOMJ" debug output messages The improved SDL screen update code is already in FS#11834. On that note: Happy X-Mas. Cheers, Thomas
diff --git a/firmware/SOURCES b/firmware/SOURCES index 72b0217..bb55a7f 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -39,6 +39,7 @@ target/hosted/sdl/lcd-sdl.c target/hosted/sdl/system-sdl.c target/hosted/sdl/thread-sdl.c target/hosted/sdl/timer-sdl.c +target/hosted/sdl/n900-osso.c #ifdef HAVE_TOUCHSCREEN target/hosted/sdl/key_to_touch-sdl.c #endif diff --git a/firmware/drivers/audio/sdl.c b/firmware/drivers/audio/sdl.c index 7d6d745..01d7827 100644 --- a/firmware/drivers/audio/sdl.c +++ b/firmware/drivers/audio/sdl.c @@ -34,8 +34,7 @@ extern void pcm_set_mixer_volume(int); void audiohw_set_volume(int volume) { #if CONFIG_CODEC == SWCODEC - pcm_set_mixer_volume( - SDL_MIX_MAXVOLUME * ((volume - VOLUME_MIN) / 10) / (VOLUME_RANGE / 10)); + pcm_set_mixer_volume(volume); #else (void)volume; #endif diff --git a/firmware/export/config.h b/firmware/export/config.h index 8e9afe5..363c21d 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -516,6 +516,10 @@ Lyre prototype 1 */ #define ARM_ARCH 4 /* ARMv4 */ #endif +/* N900 hack: Enable ASM optimizations */ +#define CPU_ARM +#define ARM_ARCH 8 + #if (CONFIG_CPU == JZ4732) #define CPU_MIPS 32 #endif diff --git a/firmware/target/hosted/sdl/button-sdl.c b/firmware/target/hosted/sdl/button-sdl.c index 3321a01..fe3f964 100644 --- a/firmware/target/hosted/sdl/button-sdl.c +++ b/firmware/target/hosted/sdl/button-sdl.c @@ -34,6 +34,7 @@ #include "sim_tasks.h" #include "buttonmap.h" #include "debug.h" +#include "n900-osso.h" #ifdef HAVE_TOUCHSCREEN #include "touchscreen.h" @@ -209,11 +210,34 @@ static void mouse_event(SDL_MouseButtonEvent *event, bool button_up) static bool event_handler(SDL_Event *event) { + SDLKey my_key; + switch(event->type) { + case SDL_ACTIVEEVENT: + if (event->active.state & SDL_APPINPUTFOCUS) + { + if (event->active.gain == 1) + n900_has_input_focus = 1; + else + n900_has_input_focus = 0; + + printf("TOMJ: N900 input focus: %d\n", n900_has_input_focus); + } + break; case SDL_KEYDOWN: case SDL_KEYUP: - button_event(event->key.keysym.sym, event->type == SDL_KEYDOWN); + /* Hack for german N900: Looks like SDL doesn't do the cursor mapping properly. Grrr */ + my_key = event->key.keysym.sym; + if (event->key.keysym.mod & KMOD_MODE) + { + if (my_key == SDLK_LEFT) + my_key = SDLK_UP; + else if (my_key == SDLK_RIGHT) + my_key = SDLK_DOWN; + } + + button_event(my_key, event->type == SDL_KEYDOWN); break; #ifdef HAVE_TOUCHSCREEN case SDL_MOUSEMOTION: diff --git a/firmware/target/hosted/sdl/lcd-bitmap.c b/firmware/target/hosted/sdl/lcd-bitmap.c index 7058b26..4da2d4d 100644 --- a/firmware/target/hosted/sdl/lcd-bitmap.c +++ b/firmware/target/hosted/sdl/lcd-bitmap.c @@ -22,11 +22,15 @@ #include "debug.h" #include "sim-ui-defines.h" #include "system.h" +#include "n900-osso.h" #include "lcd-sdl.h" #include "screendump.h" SDL_Surface* lcd_surface; +Uint32 fps_starttime = 0; +Uint32 framecount = 0; + #if LCD_DEPTH <= 8 #ifdef HAVE_BACKLIGHT SDL_Color lcd_bl_color_dark = {RED_CMP(LCD_BL_DARKCOLOR), @@ -123,6 +127,29 @@ void lcd_update(void) void lcd_update_rect(int x_start, int y_start, int width, int height) { +/* + Uint32 now = SDL_GetTicks(); + ++framecount; + + if (now - fps_starttime > 1000) + { + fps = framecount / (now-fps_starttime) * 1000 + + printf("TOMJ: FPS: ~%d\n", fps); + + framecount = 0; + fps_starttime = now; + } +*/ + // Don't update display if not shown + if (!n900_display_on) + return; + + // Don't update if we don't have the input focus + // TODO: Just slow down the update to once a second + if (!n900_has_input_focus) + return; + sdl_update_rect(lcd_surface, x_start, y_start, width, height, LCD_WIDTH, LCD_HEIGHT, get_lcd_pixel); sdl_gui_update(lcd_surface, x_start, y_start, width, diff --git a/firmware/target/hosted/sdl/lcd-sdl.c b/firmware/target/hosted/sdl/lcd-sdl.c index 96b1a04..19db68d 100644 --- a/firmware/target/hosted/sdl/lcd-sdl.c +++ b/firmware/target/hosted/sdl/lcd-sdl.c @@ -32,8 +32,36 @@ void sdl_update_rect(SDL_Surface *surface, int x_start, int y_start, int width, { int x, y; int xmax, ymax; - SDL_Rect dest; - + SDL_Rect src, dest; + +#if LCD_DEPTH >= 8 && LCD_PIXELFORMAT & RGB565 \ + && !(LCD_PIXELFORMAT & RGB565SWAPPED) \ + && !defined(LCD_STRIDEFORMAT) + /* Update complete screen via one blit operation */ + SDL_Surface *lcd = SDL_CreateRGBSurfaceFrom(lcd_framebuffer, LCD_FBWIDTH, LCD_FBHEIGHT, + LCD_DEPTH, LCD_FBWIDTH * (LCD_DEPTH/8), + 0, 0, 0, 0); + + src.x = x_start; + src.y = y_start; + src.w = width; + src.h = height; + + dest.x = x_start * display_zoom; + dest.y = y_start * display_zoom; + dest.w = width * display_zoom; + dest.h = height * display_zoom; + + if (display_zoom == 1) { + SDL_BlitSurface(lcd, &src, surface, &dest); + } else { + /* Note: SDL_SoftStretch is currently marked as DO NOT USE + but there are no real alternatives for efficent zooming. */ + SDL_SoftStretch(lcd, &src, surface, &dest); + } + SDL_FreeSurface(lcd); +#else + /* Very slow pixel-by-pixel drawing */ ymax = y_start + height; xmax = x_start + width; @@ -42,8 +70,6 @@ void sdl_update_rect(SDL_Surface *surface, int x_start, int y_start, int width, if(ymax >= max_y) ymax = max_y; - SDL_LockSurface(surface); - dest.w = display_zoom; dest.h = display_zoom; @@ -69,8 +95,7 @@ void sdl_update_rect(SDL_Surface *surface, int x_start, int y_start, int width, } #endif } - - SDL_UnlockSurface(surface); +#endif } void sdl_gui_update(SDL_Surface *surface, int x_start, int y_start, int width, diff --git a/firmware/target/hosted/sdl/n900-osso.c b/firmware/target/hosted/sdl/n900-osso.c new file mode 100644 index 0000000..c23fc5e --- /dev/null +++ b/firmware/target/hosted/sdl/n900-osso.c @@ -0,0 +1,168 @@ +#include <libhal.h> +#include <libosso.h> +#include <stdio.h> + +#include "config.h" +#include "system.h" +#include "kernel.h" +#include "thread.h" +#include "power.h" + +// Battery status information +#define BME_UDI "/org/freedesktop/Hal/devices/bme" +#define BATTERY_PERCENTAGE "battery.charge_level.percentage" +#define BATTER_REMAINING_TIME "battery.remaining_time" + +GMainLoop *osso_loop = NULL; +osso_context_t *osso_ctx = NULL; + +/* Volatile is not thread safe (no memory barriers) + It just make sure the compiler doesn't cache it + inside a register. This is good enough for us + as it's ok to loose a frame or two when the display is switched on/off. */ +volatile int n900_display_on = 1; +volatile int n900_has_input_focus = 1; +volatile int n900_battery_level = 0; +volatile int n900_remaining_time_sec = 0; + +extern void send_battery_level_event(void); +extern int last_sent_battery_level; +extern int battery_percent; + +void display_status_callback(osso_display_state_t state, gpointer data) +{ + if (state == OSSO_DISPLAY_OFF) + n900_display_on = 0; + else + n900_display_on = 1; +} + + +void get_battery_values(LibHalContext *ctx) +{ + // Get initial battery percentage and remaining time + n900_battery_level = libhal_device_get_property_int( + ctx, BME_UDI, + BATTERY_PERCENTAGE, NULL); + + n900_remaining_time_sec = libhal_device_get_property_int( + ctx, BME_UDI, + BATTER_REMAINING_TIME, NULL); + + printf("[BATTERY] Battery percentage: %d, remaining_time_sec: %d\n", n900_battery_level, n900_remaining_time_sec); +} + +static void on_battery_changed (LibHalContext *ctx, + const char *udi, + const char *key, + dbus_bool_t is_removed, + dbus_bool_t is_added) +{ + if (!g_str_equal (udi, BME_UDI)) + return; + + if (!g_str_equal (key, BATTERY_PERCENTAGE) && !g_str_equal (key, BATTER_REMAINING_TIME)) + return; + + get_battery_values(ctx); +} + + +int n900_osso_thread (void *unused) +{ + GMainContext *ctx = g_main_context_new(); + + osso_loop = g_main_loop_new (ctx, FALSE); + + // Register display callback + osso_ctx = osso_initialize ("rockbox", "666", FALSE, ctx); + osso_hw_set_display_event_cb(osso_ctx, display_status_callback, NULL); + + // Register battery status callback + LibHalContext *hal_ctx; + hal_ctx = libhal_ctx_new(); + + DBusConnection *system_bus = (DBusConnection*)osso_get_sys_dbus_connection(osso_ctx); + libhal_ctx_set_dbus_connection(hal_ctx, system_bus); + + libhal_ctx_init(hal_ctx, NULL); + libhal_ctx_set_device_property_modified (hal_ctx, on_battery_changed); + libhal_device_add_property_watch (hal_ctx, BME_UDI, NULL); + + get_battery_values(hal_ctx); + + printf("TOMJ: Osso thread up and running\n"); + g_main_loop_run (osso_loop); + printf("TOMJ: Osso main loop done\n"); + + // Cleanup + printf("TOMJ: Before osso_deinit\n"), + osso_deinitialize (osso_ctx); + printf("TOMJ: Before hal deinit\n"); + libhal_device_remove_property_watch (hal_ctx, BME_UDI, NULL); + libhal_ctx_shutdown (hal_ctx, NULL); + libhal_ctx_free(hal_ctx); + + printf("TOMJ: Before looup_unref\n"); + g_main_loop_unref (osso_loop); + g_main_context_unref (ctx); + + printf("TOMJ: Thread should exit\n"); + return 0; +} + +/// Rockbox battery related functions +void battery_status_update(void) +{ + battery_percent = n900_battery_level; + send_battery_level_event(); +} + +/* Returns true if any power input is connected - charging-capable + * or not. */ +bool power_input_present(void) +{ + return false; +} + +unsigned battery_voltage(void) +{ + return 0; +} + +/* Returns battery level in percent */ +int battery_level(void) +{ + battery_status_update(); + return n900_battery_level; +} + +/* Return remaining battery time in minutes */ +int battery_time(void) +{ + battery_status_update(); + return n900_remaining_time_sec / 60; +} + +bool battery_level_safe(void) +{ + return battery_level() >= 5; +} + +/// Rockbox stubs +void set_poweroff_timeout(int timeout) +{ + (void)timeout; +} + +void reset_poweroff_timer(void) +{ +} + +void shutdown_hw(void) +{ +} + +void cancel_shutdown(void) +{ +} diff --git a/firmware/target/hosted/sdl/n900-osso.h b/firmware/target/hosted/sdl/n900-osso.h new file mode 100644 index 0000000..4c0b13a --- /dev/null +++ b/firmware/target/hosted/sdl/n900-osso.h @@ -0,0 +1,15 @@ +#ifndef n900_osso_h +#define n900_osso_h + +#include <glib.h> +#include <libosso.h> + +extern osso_context_t *osso_ctx; +extern GMainLoop *osso_loop; + +extern volatile int n900_display_on; +extern volatile int n900_has_input_focus; + +int n900_osso_thread(void *unused); + +#endif diff --git a/firmware/target/hosted/sdl/pcm-sdl.c b/firmware/target/hosted/sdl/pcm-sdl.c index 7780083..71aa995 100644 --- a/firmware/target/hosted/sdl/pcm-sdl.c +++ b/firmware/target/hosted/sdl/pcm-sdl.c @@ -24,12 +24,28 @@ #include <stdlib.h> #include <stdbool.h> -#include <SDL.h> +#include <time.h> #include "config.h" #include "debug.h" #include "sound.h" #include "audiohw.h" #include "system.h" +#include "settings.h" + +#include "playback.h" +#include "kernel.h" + +#include <SDL.h> +#include <gst/gst.h> +#include <gst/app/gstappsrc.h> +#include <linux/types.h> + +// N900 specific libplayback support +#include <libplayback/playback.h> +#include <glib.h> +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> +#include "n900-osso.h" #ifdef HAVE_RECORDING #include "audiohw.h" @@ -49,221 +65,366 @@ extern bool debug_audio; #endif -static int sim_volume = 0; - #if CONFIG_CODEC == SWCODEC -static int cvt_status = -1; - -static Uint8* pcm_data; -static size_t pcm_data_size; -static size_t pcm_sample_bytes; -static size_t pcm_channel_bytes; - -static struct pcm_udata -{ - Uint8 *stream; - Uint32 num_in; - Uint32 num_out; -#ifdef DEBUG - FILE *debug; -#endif -} udata; -static SDL_AudioSpec obtained; -static SDL_AudioCVT cvt; +#define GST_VOLUME_UNKNOWN 2 // Two is out of range and thus means unintialized +gdouble gst_last_volume = GST_VOLUME_UNKNOWN; + +// Declarations for libplayblack +pb_playback_t *playback = NULL; +void playback_state_req_handler(pb_playback_t *pb, + enum pb_state_e req_state, + pb_req_t *ext_req, + void *data); +void playback_state_req_callback(pb_playback_t *pb, + enum pb_state_e granted_state, + const char *reason, + pb_req_t *req, + void *data); +bool playback_granted = false; + +// Gstreamer related vars +GstCaps *gst_audio_caps = NULL; +GstElement *gst_pipeline = NULL; +GstElement *gst_appsrc = NULL; +GstElement *gst_pulsesink = NULL; +GstBus *gst_bus = NULL; +GMainLoop *pcm_loop = NULL; + +static __u8* pcm_data = NULL; +static size_t pcm_data_size = 0; void pcm_play_lock(void) { - SDL_LockAudio(); } void pcm_play_unlock(void) { - SDL_UnlockAudio(); -} - -static void pcm_dma_apply_settings_nolock(void) -{ - cvt_status = SDL_BuildAudioCVT(&cvt, AUDIO_S16SYS, 2, pcm_sampr, - obtained.format, obtained.channels, obtained.freq); - - if (cvt_status < 0) { - cvt.len_ratio = (double)obtained.freq / (double)pcm_sampr; - } } void pcm_dma_apply_settings(void) { - pcm_play_lock(); - pcm_dma_apply_settings_nolock(); - pcm_play_unlock(); } void pcm_play_dma_start(const void *addr, size_t size) { - pcm_dma_apply_settings_nolock(); + printf("[TOMJ %d] called pcm_dma_start: addr: %u, size: %u\n", (unsigned int)time(NULL), addr, size); - pcm_data = (Uint8 *) addr; + pcm_data = (__u8 *) addr; pcm_data_size = size; - SDL_PauseAudio(0); + if (playback_granted) + { + // Start playing now + gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_PLAYING); + } else + { + // N900: Request change to playing state + pb_playback_req_state (playback, + PB_STATE_PLAY, // PLAY + playback_state_req_callback, + NULL); + } } void pcm_play_dma_stop(void) { - SDL_PauseAudio(1); -#ifdef DEBUG - if (udata.debug != NULL) { - fclose(udata.debug); - udata.debug = NULL; - DEBUGF("Audio debug file closed\n"); - } -#endif + printf("[TOMJ %d] called pcm_dma_stop\n", (unsigned int)time(NULL)); + + gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_NULL); } void pcm_play_dma_pause(bool pause) { + printf("[TOMJ %d] called pcm_dma_pause: %d\n", time(NULL), pause); + if (pause) - SDL_PauseAudio(1); + gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_NULL); else - SDL_PauseAudio(0); + gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_PLAYING); } size_t pcm_get_bytes_waiting(void) { + // printf("[TOMJ %d] called pcm_get_bytes_waiting: %d\n", time(NULL), pcm_data_size); return pcm_data_size; } -static void write_to_soundcard(struct pcm_udata *udata) +static void feed_data(GstElement * appsrc, guint size, void *unused) { -#ifdef DEBUG - if (debug_audio && (udata->debug == NULL)) { - udata->debug = fopen("audiodebug.raw", "ab"); - DEBUGF("Audio debug file open\n"); - } -#endif - if (cvt.needed) { - Uint32 rd = udata->num_in; - Uint32 wr = (double)rd * cvt.len_ratio; - - if (wr > udata->num_out) { - wr = udata->num_out; - rd = (double)wr / cvt.len_ratio; - - if (rd > udata->num_in) - { - rd = udata->num_in; - wr = (double)rd * cvt.len_ratio; - } - } + pcm_play_get_more_callback((void **)&pcm_data, &pcm_data_size); + + if (pcm_data_size != 0) + { + GstBuffer *buffer = gst_buffer_new (); + GstFlowReturn ret; + + GST_BUFFER_DATA (buffer) = pcm_data; + GST_BUFFER_SIZE (buffer) = pcm_data_size; + + // printf("feed gst_buffer %p, pcm_data: %p, len %d\n", buffer, pcm_data, pcm_data_size); + g_signal_emit_by_name (gst_appsrc, "push-buffer", buffer, &ret); + gst_buffer_unref (buffer); - if (wr == 0 || rd == 0) + if (ret != 0) + printf("push-buffer error result: %d\n", ret); + else { - udata->num_out = udata->num_in = 0; - return; + // We fed everything into the buffer + // pcm_data += pcm_data_size; + // pcm_data_size = 0; } + } else + { + printf("feed_data: No Data.\n"); + } +} - if (cvt_status > 0) { - cvt.len = rd * pcm_sample_bytes; - cvt.buf = (Uint8 *) malloc(cvt.len * cvt.len_mult); +const void * pcm_play_dma_get_peak_buffer(int *count) +{ + /* + printf("[TOMJ %d] called pcm_play_dma_get_peak_buffer: %p - %d\n", + time(NULL), pcm_data, pcm_data_size / 4); + */ - memcpy(cvt.buf, pcm_data, cvt.len); + uintptr_t addr = (uintptr_t)pcm_data; + *count = pcm_data_size / 4; + return (void *)((addr + 2) & ~3); +} - SDL_ConvertAudio(&cvt); - SDL_MixAudio(udata->stream, cvt.buf, cvt.len_cvt, sim_volume); - udata->num_in = cvt.len / pcm_sample_bytes; - udata->num_out = cvt.len_cvt / pcm_sample_bytes; +static gboolean +gst_bus_message (GstBus * bus, GstMessage * message, void *unused) +{ + printf(" [gst] got BUS message %s\n", + gst_message_type_get_name (GST_MESSAGE_TYPE (message))); -#ifdef DEBUG - if (udata->debug != NULL) { - fwrite(cvt.buf, sizeof(Uint8), cvt.len_cvt, udata->debug); - } -#endif - free(cvt.buf); - } - else { - /* Convert is bad, so do silence */ - Uint32 num = wr*obtained.channels; - udata->num_in = rd; - udata->num_out = wr; - - switch (pcm_channel_bytes) - { - case 1: - { - Uint8 *stream = udata->stream; - while (num-- > 0) - *stream++ = obtained.silence; - break; - } - case 2: - { - Uint16 *stream = (Uint16 *)udata->stream; - while (num-- > 0) - *stream++ = obtained.silence; - break; - } - } -#ifdef DEBUG - if (udata->debug != NULL) { - fwrite(udata->stream, sizeof(Uint8), wr, udata->debug); - } -#endif - } - } else { - udata->num_in = udata->num_out = MIN(udata->num_in, udata->num_out); - SDL_MixAudio(udata->stream, pcm_data, - udata->num_out * pcm_sample_bytes, sim_volume); -#ifdef DEBUG - if (udata->debug != NULL) { - fwrite(pcm_data, sizeof(Uint8), udata->num_out * pcm_sample_bytes, - udata->debug); - } -#endif + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + { + GError *err; + gchar *debug; + gst_message_parse_error (message, &err, &debug); + + printf("[gst] Received error: Src: %s, msg: %s\n", GST_MESSAGE_SRC_NAME(message), err->message); + + g_error_free (err); + g_free (debug); } + + g_main_loop_quit (pcm_loop); + break; + case GST_MESSAGE_EOS: + g_main_loop_quit (pcm_loop); + break; + case GST_MESSAGE_STATE_CHANGED: + { + GstState old_state, new_state; + + gst_message_parse_state_changed (message, &old_state, &new_state, NULL); + printf("[gst] Element %s changed state from %s to %s.\n", + GST_MESSAGE_SRC_NAME(message), + gst_element_state_get_name (old_state), + gst_element_state_get_name (new_state)); + break; + } + default: + break; + } + return TRUE; } -static void sdl_audio_callback(struct pcm_udata *udata, Uint8 *stream, int len) +void n900_configure_appsrc(void) { - logf("sdl_audio_callback: len %d, pcm %d\n", len, pcm_data_size); - udata->stream = stream; - - /* Write what we have in the PCM buffer */ - if (pcm_data_size > 0) - goto start; - - /* Audio card wants more? Get some more then. */ - while (len > 0) { - pcm_play_get_more_callback((void **)&pcm_data, &pcm_data_size); - start: - if (pcm_data_size != 0) { - udata->num_in = pcm_data_size / pcm_sample_bytes; - udata->num_out = len / pcm_sample_bytes; - - write_to_soundcard(udata); - - udata->num_in *= pcm_sample_bytes; - udata->num_out *= pcm_sample_bytes; - - pcm_data += udata->num_in; - pcm_data_size -= udata->num_in; - udata->stream += udata->num_out; - len -= udata->num_out; - } else { - DEBUGF("sdl_audio_callback: No Data.\n"); - break; - } + // Block push-buffer until there is enough room + g_object_set (G_OBJECT(gst_appsrc), "block", TRUE, NULL); + + g_object_set(G_OBJECT(gst_appsrc), "format", GST_FORMAT_BYTES, NULL); + + gst_audio_caps = gst_caps_new_simple("audio/x-raw-int", "width", G_TYPE_INT, (gint)16, "depth", G_TYPE_INT, (gint)16, "channels" ,G_TYPE_INT, (gint)2, + "signed",G_TYPE_BOOLEAN,1, + "rate",G_TYPE_INT,44100,"endianness",G_TYPE_INT,(gint)1234,NULL); + + g_object_set (G_OBJECT(gst_appsrc), "caps", gst_audio_caps, NULL); + + gst_app_src_set_stream_type(GST_APP_SRC(gst_appsrc), + GST_APP_STREAM_TYPE_STREAM); + + /* configure the appsrc, we will push data into the appsrc from the + * mainloop. */ + g_signal_connect (gst_appsrc, "need-data", G_CALLBACK (feed_data), NULL); +} + +// Init libplayback: Grant access rights to +// play audio while the phone is in silent mode +void n900_init_libplayback(void) +{ + DBusConnection *session_bus_raw = (DBusConnection*)osso_get_dbus_connection(osso_ctx); + + playback = pb_playback_new_2(session_bus_raw, + PB_CLASS_MEDIA, // TODO: Just for testing. Use MEDIA here + PB_FLAG_AUDIO, + PB_STATE_STOP, + playback_state_req_handler, + NULL); + + pb_playback_set_stream(playback, "Playback Stream"); +} + +/** + * Gets called by the policy framework if an important + * event arrives: Incoming calls etc. + */ +extern SDL_mutex *m; // HACK +extern struct event_queue audio_queue; // HACK + +void n900_tell_rockbox_to_stop_audio(void) +{ + // Tell rockbox to stop the audio the hard way: + // Acquire main SDL kernel lock and post a STOP event + // into the audio queue. If we call audio_stop() directly, + // we will get a deadlock! + // broken: audio_stop(); + printf("[TOMJ %d] Waiting for kernel mutex\n", time(NULL)); + SDL_LockMutex(m); + printf("[TOMJ %d] Got mutex\n", time(NULL)); + + // Stop audio playback and clear queue immediately + queue_post(&audio_queue, Q_AUDIO_STOP, 1); + + SDL_UnlockMutex(m); + printf("[TOMJ %d] Left mutex\n", time(NULL)); + + osso_system_note_infoprint(osso_ctx, "Stopping rockbox playback", NULL); +} + +void playback_state_req_handler(pb_playback_t *pb, + enum pb_state_e req_state, + pb_req_t *ext_req, + void *data) +{ + printf("External state change request: state: %s, data: %p\n", + pb_state_to_string(req_state), data); + + if (req_state == PB_STATE_STOP && playback_granted) + { + printf("Stopping playback, might be an incoming call\n"); + + playback_granted = false; + n900_tell_rockbox_to_stop_audio(); } } -const void * pcm_play_dma_get_peak_buffer(int *count) +/** + * Callback for our own state change request. + */ +void playback_state_req_callback(pb_playback_t *pb, enum pb_state_e granted_state, const char *reason, pb_req_t *req, void *data) { - uintptr_t addr = (uintptr_t)pcm_data; - *count = pcm_data_size / 4; - return (void *)((addr + 2) & ~3); + printf("State request callback: granted_state: %s, reason: %s\n", + pb_state_to_string(granted_state), reason); + + /* We are allowed to play audio */ + if (granted_state == PB_STATE_PLAY) + { + printf("Start playing...\n"); + playback_granted = true; + gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_PLAYING); + } else + { + printf("Can't start playing. Throwing away play request\n"); + + playback_granted = false; + n900_tell_rockbox_to_stop_audio(); + } + + pb_playback_req_completed(pb, req); +} + +void pcm_play_dma_init(void) +{ + printf("[TOMJ %d] called pcm_play_dma_init\n", time(NULL)); + + n900_init_libplayback(); + + GMainContext *ctx = g_main_loop_get_context(osso_loop); + pcm_loop = g_main_loop_new (ctx, true); + + gst_init (NULL, NULL); + + // pcm_loop = g_main_loop_new (NULL, false); + + gst_pipeline = gst_pipeline_new ("rockbox"); + + gst_appsrc = gst_element_factory_make ("appsrc", NULL); + + gst_pulsesink = gst_element_factory_make ("pulsesink", NULL); + + // Connect elements + gst_bin_add_many (GST_BIN (gst_pipeline), + gst_appsrc, gst_pulsesink, NULL); + gst_element_link_many (gst_appsrc, gst_pulsesink, NULL); + + // Connect to gstreamer bus of the pipeline + gst_bus = gst_pipeline_get_bus (GST_PIPELINE (gst_pipeline)); + gst_bus_add_watch (gst_bus, (GstBusFunc) gst_bus_message, NULL); + + n900_configure_appsrc(); } +void pcm_postinit(void) +{ +} + +void pcm_set_mixer_volume(int volume) +{ + printf("TOMJ: pcm_set_mixer_volume: %d\n", volume); + + // gstreamer / playbin2 volume range is from 0.00 to 1.00 + gdouble gst_vol = (gdouble)(volume - VOLUME_MIN) / (gdouble)VOLUME_RANGE; + + // Overwrite rockbox volume settings if the global systm volume was changed. + // But don't prevent initial volume setting and only do it we are playing music. + // (the pulsesink reports a volume of 1.00 if it's inactive) + GstState current_state = GST_STATE_NULL; + if (gst_last_volume != GST_VOLUME_UNKNOWN && + gst_element_get_state(GST_ELEMENT(gst_pipeline), ¤t_state, NULL, + GST_MSECOND) == GST_STATE_CHANGE_SUCCESS + && current_state == GST_STATE_PLAYING) + { + gdouble current_gst_volume = 0; + g_object_get (G_OBJECT(gst_pulsesink), "volume", ¤t_gst_volume, NULL); + + // Cut of the last digits for the external volume change detection comparison + // as the double values get()/set() from "playbin2" are unstable + printf("TOMJ: DEBUG: current_volume: %f, last_volume: %f\n", + current_gst_volume, gst_last_volume); + + int compare_current_volume = (int)(current_gst_volume * 1000); + int compare_last_volume = (int)(gst_last_volume * 1000); + + // Volume changed from outside over change tolerance? + const int change_tolerance = 15; + if (compare_current_volume - change_tolerance > compare_last_volume || + compare_current_volume + change_tolerance < compare_last_volume) + { + // Calculate new rockbox volume + int new_rockbox_volume = (int)(current_gst_volume * VOLUME_RANGE + VOLUME_MIN) / 10; + + printf("TOMJ: Volume changed too much on the outside (current_volume: %d, last_volume: %d -> Adapting rockbox: %d\n", + compare_current_volume, compare_last_volume, new_rockbox_volume); + + global_settings.volume = new_rockbox_volume; + global_status.last_volume_change = current_tick; + + gst_vol = current_gst_volume; + } + } + + g_object_set (G_OBJECT(gst_pulsesink), "volume", gst_vol, NULL); + gst_last_volume = gst_vol; +} + + #ifdef HAVE_RECORDING void pcm_rec_lock(void) { @@ -312,68 +473,4 @@ unsigned long spdif_measure_frequency(void) #endif /* HAVE_RECORDING */ -void pcm_play_dma_init(void) -{ - if (SDL_InitSubSystem(SDL_INIT_AUDIO)) - { - DEBUGF("Could not initialize SDL audio subsystem!\n"); - return; - } - - SDL_AudioSpec wanted_spec; -#ifdef DEBUG - udata.debug = NULL; - if (debug_audio) { - udata.debug = fopen("audiodebug.raw", "wb"); - DEBUGF("Audio debug file open\n"); - } -#endif - /* Set 16-bit stereo audio at 44Khz */ - wanted_spec.freq = 44100; - wanted_spec.format = AUDIO_S16SYS; - wanted_spec.channels = 2; - wanted_spec.samples = 2048; - wanted_spec.callback = - (void (SDLCALL *)(void *userdata, - Uint8 *stream, int len))sdl_audio_callback; - wanted_spec.userdata = &udata; - - /* Open the audio device and start playing sound! */ - if(SDL_OpenAudio(&wanted_spec, &obtained) < 0) { - DEBUGF("Unable to open audio: %s\n", SDL_GetError()); - return; - } - - switch (obtained.format) - { - case AUDIO_U8: - case AUDIO_S8: - pcm_channel_bytes = 1; - break; - case AUDIO_U16LSB: - case AUDIO_S16LSB: - case AUDIO_U16MSB: - case AUDIO_S16MSB: - pcm_channel_bytes = 2; - break; - default: - DEBUGF("Unknown sample format obtained: %u\n", - (unsigned)obtained.format); - return; - } - - pcm_sample_bytes = obtained.channels * pcm_channel_bytes; - - pcm_dma_apply_settings_nolock(); -} - -void pcm_postinit(void) -{ -} - -void pcm_set_mixer_volume(int volume) -{ - sim_volume = volume; -} - #endif /* CONFIG_CODEC == SWCODEC */ diff --git a/firmware/target/hosted/sdl/system-sdl.c b/firmware/target/hosted/sdl/system-sdl.c index 6937c37..68f7cd2 100644 --- a/firmware/target/hosted/sdl/system-sdl.c +++ b/firmware/target/hosted/sdl/system-sdl.c @@ -19,6 +19,7 @@ * ****************************************************************************/ +#include <glib.h> #include <SDL.h> #include <SDL_thread.h> #include <stdlib.h> @@ -28,6 +29,7 @@ #include "thread-sdl.h" #include "system-sdl.h" #include "sim-ui-defines.h" +#include "n900-osso.h" #include "lcd-sdl.h" #ifdef HAVE_LCD_BITMAP #include "lcd-bitmap.h" @@ -67,6 +69,7 @@ int wps_verbose_level = 3; void sys_poweroff(void) { + printf("TOMJ: Called sys_poweroff\n"); } /* @@ -84,6 +87,7 @@ static int sdl_event_thread(void * param) SDL_Surface *picture_surface = NULL; int width, height; + int depth; /* Try and load the background image. If it fails go without */ if (background) { @@ -115,12 +119,18 @@ static int sdl_event_thread(void * param) height = SIM_LCD_HEIGHT; } } - - - if ((gui_surface = SDL_SetVideoMode(width * display_zoom, height * display_zoom, 24, SDL_HWSURFACE|SDL_DOUBLEBUF)) == NULL) { + + depth = LCD_DEPTH; + if (depth < 8) + depth = 16; + + if ((gui_surface = SDL_SetVideoMode(width * display_zoom, height * display_zoom, depth, SDL_HWSURFACE|SDL_DOUBLEBUF|SDL_FULLSCREEN)) == NULL) { panicf("%s", SDL_GetError()); } + // Hide mouse cursor + SDL_ShowCursor(SDL_DISABLE); + SDL_WM_SetCaption(UI_TITLE, NULL); if (background && picture_surface != NULL) @@ -129,10 +139,20 @@ static int sdl_event_thread(void * param) /* let system_init proceed */ SDL_SemPost((SDL_sem *)param); + // N900: Start osso thread: Listen to display on/off events + SDL_Thread *osso_thread = SDL_CreateThread(n900_osso_thread, NULL); + /* * finally enter the button loop */ gui_message_loop(); + /* TODO: We should wait for the n900_osso_thread to actually + initialize the main loop. This is currently not thread safe + and could be a problem if we get stopped very, very fast -> Use mutex */ + g_main_loop_quit (osso_loop); + printf("TOMJ: Waiting for osso thread\n"); + SDL_WaitThread(osso_thread, NULL); + if(picture_surface) SDL_FreeSurface(picture_surface); @@ -157,6 +177,10 @@ void system_init(void) { SDL_sem *s; + // Make glib thread safe + g_thread_init(NULL); + g_type_init(); + if (SDL_Init(SDL_INIT_TIMER)) panicf("%s", SDL_GetError()); diff --git a/firmware/target/hosted/sdl/thread-sdl.c b/firmware/target/hosted/sdl/thread-sdl.c index 83f1d19..f32aa57 100644 --- a/firmware/target/hosted/sdl/thread-sdl.c +++ b/firmware/target/hosted/sdl/thread-sdl.c @@ -59,7 +59,7 @@ static jmp_buf thread_jmpbufs[MAXTHREADS]; /* this mutex locks out other Rockbox threads while one runs, * that enables us to simulate a cooperative environment even if * the host is preemptive */ -static SDL_mutex *m; +SDL_mutex *m; #define THREADS_RUN 0 #define THREADS_EXIT 1 #define THREADS_EXIT_COMMAND_DONE 2 diff --git a/tools/configure b/tools/configure index 1b57210..668b936 100755 --- a/tools/configure +++ b/tools/configure @@ -284,7 +284,25 @@ simcc () { LDOPTS="$LDOPTS `$sdl --libs`" fi fi - + + # N900 libosso support + GCCOPTS="$GCCOPTS `pkg-config --cflags libosso glib-2.0 gthread-2.0`" + LDOPTS="$LDOPTS `pkg-config --libs libosso glib-2.0 gthread-2.0`" + + # N900 gstreamer support + GCCOPTS="$GCCOPTS `pkg-config --cflags gstreamer-base-0.10 gstreamer-plugins-base-0.10 gstreamer-app-0.10`" + LDOPTS="$LDOPTS `pkg-config --libs gstreamer-base-0.10 gstreamer-plugins-base-0.10 gstreamer-app-0.10`" + + # N900 libplayback support + GCCOPTS="$GCCOPTS `pkg-config --cflags libplayback-1`" + LDOPTS="$LDOPTS `pkg-config --libs libplayback-1`" + + # N900 libhal support + GCCOPTS="$GCCOPTS `pkg-config --cflags hal`" + LDOPTS="$LDOPTS `pkg-config --libs hal`" + + # N900: Enable arm NEON support + GCCOPTS="$GCCOPTS -mfloat-abi=softfp -mfpu=neon" GCCOPTS="$GCCOPTS -I\$(SIMDIR)" diff --git a/uisimulator/common/SOURCES b/uisimulator/common/SOURCES index fd256c5..2374103 100644 --- a/uisimulator/common/SOURCES +++ b/uisimulator/common/SOURCES @@ -12,5 +12,5 @@ backlight-sim.c io.c sim_tasks.c /* this is still needed for application since it has some stubs */ -powermgmt-sim.c +/* DISABLED BY TOMJ powermgmt-sim.c */ stubs.c