5f037ac015
Adds Nokia N900, N810 and N800 support. Features: - Introduce maemo specific platform defines - Play audio in silent mode - Stop playback on incoming calls - Battery level readout - Bluetooth headset support - Save CPU by disabling screen updates if the display is off or the app doesn't have input focus - N900: GStreamer audio backend Kudos to kugel for the code review. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29248 a1c6a512-1295-4272-9138-f99709370657
459 lines
12 KiB
C
459 lines
12 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2010 by Thomas Jarosch
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
|
|
#include "autoconf.h"
|
|
|
|
#include <stdbool.h>
|
|
#include "config.h"
|
|
#include "debug.h"
|
|
#include "sound.h"
|
|
#include "audiohw.h"
|
|
#include "system.h"
|
|
#include "settings.h"
|
|
#include "debug.h"
|
|
|
|
#include "playback.h"
|
|
#include "kernel.h"
|
|
|
|
#include <SDL.h>
|
|
#include <glib.h>
|
|
#include <gst/gst.h>
|
|
#include <gst/app/gstappsrc.h>
|
|
#include <linux/types.h>
|
|
|
|
/* Maemo5: N900 specific libplayback support */
|
|
#include <libplayback/playback.h>
|
|
#include <dbus/dbus-glib.h>
|
|
#include <dbus/dbus-glib-lowlevel.h>
|
|
#include "maemo-thread.h"
|
|
|
|
#ifdef HAVE_RECORDING
|
|
#include "audiohw.h"
|
|
#ifdef HAVE_SPDIF_IN
|
|
#include "spdif.h"
|
|
#endif
|
|
#endif
|
|
|
|
#include "pcm.h"
|
|
#include "pcm_sampr.h"
|
|
|
|
/*#define LOGF_ENABLE*/
|
|
#include "logf.h"
|
|
|
|
#ifdef DEBUG
|
|
#include <stdio.h>
|
|
extern bool debug_audio;
|
|
#endif
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
|
|
/* 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_volume = NULL;
|
|
GstElement *gst_pulsesink = NULL;
|
|
GstBus *gst_bus = NULL;
|
|
static int bus_watch_id = 0;
|
|
GMainLoop *pcm_loop = NULL;
|
|
|
|
static __u8* pcm_data = NULL;
|
|
static size_t pcm_data_size = 0;
|
|
|
|
static int inside_feed_data = 0;
|
|
|
|
void pcm_play_lock(void)
|
|
{
|
|
}
|
|
|
|
void pcm_play_unlock(void)
|
|
{
|
|
}
|
|
|
|
void pcm_dma_apply_settings(void)
|
|
{
|
|
}
|
|
|
|
void pcm_play_dma_start(const void *addr, size_t size)
|
|
{
|
|
pcm_data = (__u8 *) addr;
|
|
pcm_data_size = size;
|
|
|
|
if (playback_granted)
|
|
{
|
|
/* Start playing now */
|
|
if (!inside_feed_data)
|
|
gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_PLAYING);
|
|
else
|
|
DEBUGF("ERROR: dma_start called while inside feed_data\n");
|
|
} else
|
|
{
|
|
/* N900: Request change to playing state */
|
|
pb_playback_req_state (playback,
|
|
PB_STATE_PLAY,
|
|
playback_state_req_callback,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
void pcm_play_dma_stop(void)
|
|
{
|
|
if (inside_feed_data)
|
|
g_signal_emit_by_name (gst_appsrc, "end-of-stream", NULL);
|
|
else
|
|
gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_NULL);
|
|
}
|
|
|
|
void pcm_play_dma_pause(bool pause)
|
|
{
|
|
if (inside_feed_data)
|
|
{
|
|
if (pause)
|
|
g_signal_emit_by_name (gst_appsrc, "end-of-stream", NULL);
|
|
else
|
|
DEBUGF("ERROR: Called dma_pause(0) while inside feed_data\n");
|
|
} else
|
|
{
|
|
if (pause)
|
|
gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_NULL);
|
|
else
|
|
gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_PLAYING);
|
|
}
|
|
}
|
|
|
|
size_t pcm_get_bytes_waiting(void)
|
|
{
|
|
return pcm_data_size;
|
|
}
|
|
|
|
static void feed_data(GstElement * appsrc, guint size_hint, void *unused)
|
|
{
|
|
(void)size_hint;
|
|
(void)unused;
|
|
|
|
/* Make sure we don't trigger a gst_element_set_state() call
|
|
from inside gstreamer's stream thread as it will deadlock */
|
|
inside_feed_data = 1;
|
|
|
|
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;
|
|
|
|
g_signal_emit_by_name (appsrc, "push-buffer", buffer, &ret);
|
|
gst_buffer_unref (buffer);
|
|
|
|
if (ret != 0)
|
|
DEBUGF("push-buffer error result: %d\n", ret);
|
|
} else
|
|
{
|
|
DEBUGF("feed_data: No Data.\n");
|
|
g_signal_emit_by_name (appsrc, "end-of-stream", NULL);
|
|
}
|
|
|
|
inside_feed_data = 0;
|
|
}
|
|
|
|
const void * pcm_play_dma_get_peak_buffer(int *count)
|
|
{
|
|
uintptr_t addr = (uintptr_t)pcm_data;
|
|
*count = pcm_data_size / 4;
|
|
return (void *)((addr + 2) & ~3);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gst_bus_message (GstBus * bus, GstMessage * message, void *unused)
|
|
{
|
|
(void)bus;
|
|
(void)unused;
|
|
|
|
DEBUGF(" [gst] got BUS message %s\n",
|
|
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
|
|
|
switch (GST_MESSAGE_TYPE (message)) {
|
|
case GST_MESSAGE_ERROR:
|
|
{
|
|
GError *err;
|
|
gchar *debug;
|
|
gst_message_parse_error (message, &err, &debug);
|
|
|
|
DEBUGF("[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:
|
|
gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_NULL);
|
|
break;
|
|
case GST_MESSAGE_STATE_CHANGED:
|
|
{
|
|
GstState old_state, new_state;
|
|
|
|
gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
|
|
DEBUGF("[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;
|
|
}
|
|
|
|
void maemo_configure_appsrc(void)
|
|
{
|
|
/* 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 maemo_init_libplayback(void)
|
|
{
|
|
DBusConnection *session_bus_raw = (DBusConnection*)osso_get_dbus_connection(maemo_osso_ctx);
|
|
|
|
playback = pb_playback_new_2(session_bus_raw,
|
|
PB_CLASS_MEDIA,
|
|
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.
|
|
*/
|
|
void maemo_tell_rockbox_to_stop_audio(void)
|
|
{
|
|
sim_enter_irq_handler();
|
|
queue_broadcast(SYS_CALL_INCOMING, 0);
|
|
sim_exit_irq_handler();
|
|
|
|
osso_system_note_infoprint(maemo_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)
|
|
{
|
|
(void)pb;
|
|
(void)ext_req;
|
|
(void)data;
|
|
|
|
DEBUGF("External state change request: state: %s, data: %p\n",
|
|
pb_state_to_string(req_state), data);
|
|
|
|
if (req_state == PB_STATE_STOP && playback_granted)
|
|
{
|
|
DEBUGF("Stopping playback, might be an incoming call\n");
|
|
|
|
playback_granted = false;
|
|
maemo_tell_rockbox_to_stop_audio();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
(void)data;
|
|
(void)reason;
|
|
|
|
DEBUGF("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)
|
|
{
|
|
DEBUGF("Playback granted. Start playing...\n");
|
|
playback_granted = true;
|
|
if (!inside_feed_data)
|
|
gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_PLAYING);
|
|
} else
|
|
{
|
|
DEBUGF("Can't start playing. Throwing away play request\n");
|
|
|
|
playback_granted = false;
|
|
maemo_tell_rockbox_to_stop_audio();
|
|
}
|
|
|
|
pb_playback_req_completed(pb, req);
|
|
}
|
|
|
|
void pcm_play_dma_init(void)
|
|
{
|
|
maemo_init_libplayback();
|
|
|
|
GMainContext *ctx = g_main_loop_get_context(maemo_main_loop);
|
|
pcm_loop = g_main_loop_new (ctx, true);
|
|
|
|
gst_init (NULL, NULL);
|
|
|
|
gst_pipeline = gst_pipeline_new ("rockbox");
|
|
|
|
gst_appsrc = gst_element_factory_make ("appsrc", NULL);
|
|
gst_volume = gst_element_factory_make ("volume", NULL);
|
|
gst_pulsesink = gst_element_factory_make ("pulsesink", NULL);
|
|
|
|
/* Connect elements */
|
|
gst_bin_add_many (GST_BIN (gst_pipeline),
|
|
gst_appsrc, gst_volume, gst_pulsesink, NULL);
|
|
gst_element_link_many (gst_appsrc, gst_volume, gst_pulsesink, NULL);
|
|
|
|
/* Connect to gstreamer bus of the pipeline */
|
|
gst_bus = gst_pipeline_get_bus (GST_PIPELINE (gst_pipeline));
|
|
bus_watch_id = gst_bus_add_watch (gst_bus, (GstBusFunc) gst_bus_message, NULL);
|
|
|
|
maemo_configure_appsrc();
|
|
}
|
|
|
|
void pcm_shutdown_gstreamer(void)
|
|
{
|
|
/* Try to stop playing */
|
|
gst_element_set_state (GST_ELEMENT(gst_pipeline), GST_STATE_NULL);
|
|
|
|
/* Make sure we are really stopped. This should return almost instantly,
|
|
so we wait up to ten seconds and just continue otherwise */
|
|
gst_element_get_state (GST_ELEMENT(gst_pipeline), NULL, NULL, GST_SECOND * 10);
|
|
|
|
g_source_remove (bus_watch_id);
|
|
g_object_unref(gst_bus);
|
|
gst_bus = NULL;
|
|
|
|
gst_object_unref (gst_pipeline);
|
|
gst_pipeline = NULL;
|
|
|
|
/* Shutdown libplayback and gstreamer */
|
|
pb_playback_destroy (playback);
|
|
gst_deinit();
|
|
|
|
g_main_loop_quit(pcm_loop);
|
|
g_main_loop_unref (pcm_loop);
|
|
}
|
|
|
|
void pcm_postinit(void)
|
|
{
|
|
}
|
|
|
|
void pcm_set_mixer_volume(int volume)
|
|
{
|
|
/* gstreamer volume range is from 0.00 to 1.00 */
|
|
gdouble gst_vol = (gdouble)(volume - VOLUME_MIN) / (gdouble)VOLUME_RANGE;
|
|
|
|
g_object_set (G_OBJECT(gst_volume), "volume", gst_vol, NULL);
|
|
}
|
|
|
|
|
|
#ifdef HAVE_RECORDING
|
|
void pcm_rec_lock(void)
|
|
{
|
|
}
|
|
|
|
void pcm_rec_unlock(void)
|
|
{
|
|
}
|
|
|
|
void pcm_rec_dma_init(void)
|
|
{
|
|
}
|
|
|
|
void pcm_rec_dma_close(void)
|
|
{
|
|
}
|
|
|
|
void pcm_rec_dma_start(void *start, size_t size)
|
|
{
|
|
(void)start;
|
|
(void)size;
|
|
}
|
|
|
|
void pcm_rec_dma_stop(void)
|
|
{
|
|
}
|
|
|
|
const void * pcm_rec_dma_get_peak_buffer(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void audiohw_set_recvol(int left, int right, int type)
|
|
{
|
|
(void)left;
|
|
(void)right;
|
|
(void)type;
|
|
}
|
|
|
|
#ifdef HAVE_SPDIF_IN
|
|
unsigned long spdif_measure_frequency(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#endif /* HAVE_RECORDING */
|
|
|
|
#endif /* CONFIG_CODEC == SWCODEC */
|