ocarina/core/audio.c

330 lines
7.3 KiB
C

/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/idle.h>
#include <core/playlist.h>
#include <core/settings.h>
static const char *SETTINGS_TRACK = "core.audio.cur";
static const char *SETTINGS_VOLUME = "core.audio.volume";
static struct file audio_file = FILE_INIT("cur_track", 0);
static struct track *audio_track = NULL;
static int audio_pause_count = -1;
static GstElement *audio_pipeline = NULL;
static GstElement *audio_source = NULL;
static GstElement *audio_decoder = NULL;
static GstElement *audio_converter = NULL;
static GstElement *audio_volume = NULL;
static GstElement *audio_sink = NULL;
static guint audio_bus_id = 0;
static struct audio_callbacks *audio_cb = NULL;
static bool __audio_change_state(GstState state)
{
GstStateChangeReturn ret = GST_STATE_CHANGE_FAILURE;
if (audio_cur_state() != state)
ret = gst_element_set_state(audio_pipeline, state);
if (ret == GST_STATE_CHANGE_FAILURE)
return false;
if (audio_cb)
audio_cb->audio_cb_state_change(state);
return true;
}
static void __audio_gst_load(struct track *track, GstState state)
{
gchar *path = track_path(track);
audio_track = track;
gst_element_set_state(audio_pipeline, GST_STATE_READY);
g_object_set(G_OBJECT(audio_source), "location", path, NULL);
__audio_change_state(state);
if (audio_cb)
audio_cb->audio_cb_load(track);
audio_save();
g_free(path);
}
/* Load a track, but don't add it to the history. */
static struct track *__audio_do_load(struct track *track, GstState state)
{
struct track *prev = audio_track;
if (track) {
__audio_gst_load(track, state);
playlist_selected(track);
}
playlist_played(prev);
return audio_track;
}
static struct track *__audio_load(struct track *track, GstState state)
{
struct track *ret = __audio_do_load(track, state);
if (ret == track)
playlist_add(playlist_lookup(PL_SYSTEM, "History"), ret);
return ret;
}
static struct track *__audio_next(GstState state)
{
return __audio_load(playlist_next(), state);
}
static void __audio_pad_added(GstElement *element, GstPad *pad, gpointer data)
{
GstPad *sink = gst_element_get_static_pad(audio_decoder, "sink");
gst_element_link(element, audio_converter);
gst_pad_link(pad, sink);
gst_object_unref(sink);
}
static gboolean __audio_message(GstBus *bus, GstMessage *message, gpointer data)
{
switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_ERROR:
audio_error(message);
break;
case GST_MESSAGE_EOS:
audio_eos();
default:
break;
}
return true;
}
static bool __audio_init_idle(void *data)
{
unsigned int track;
if (settings_has(SETTINGS_TRACK)) {
track = settings_get(SETTINGS_TRACK);
__audio_load(track_get(track), GST_STATE_PAUSED);
} else if (file_open(&audio_file, OPEN_READ)) {
file_readf(&audio_file, "%u", &track);
file_close(&audio_file);
file_remove(&audio_file);
__audio_load(track_get(track), GST_STATE_PAUSED);
}
return true;
}
void audio_init(int *argc, char ***argv, struct audio_callbacks *callbacks)
{
unsigned int volume = 100;
GstBus *bus;
gst_init(argc, argv);
audio_cb = callbacks;
audio_pipeline = gst_pipeline_new("pipeline");
audio_source = gst_element_factory_make("filesrc", "source");
audio_decoder = gst_element_factory_make("decodebin", "decoder");
audio_converter = gst_element_factory_make("audioconvert", "converter");
audio_volume = gst_element_factory_make("volume", "volume");
audio_sink = gst_element_factory_make("autoaudiosink", "sink");
bus = gst_pipeline_get_bus(GST_PIPELINE(audio_pipeline));
audio_bus_id = gst_bus_add_watch(bus, __audio_message, NULL);
gst_bin_add_many(GST_BIN(audio_pipeline), audio_source, audio_decoder,
audio_converter, audio_volume,
audio_sink, NULL);
gst_element_link(audio_source, audio_decoder);
gst_element_link_many(audio_converter, audio_volume, audio_sink, NULL);
g_signal_connect(audio_decoder, "pad-added", G_CALLBACK(__audio_pad_added), NULL);
gst_object_unref(bus);
if (settings_has(SETTINGS_VOLUME))
volume = settings_get(SETTINGS_VOLUME);
audio_set_volume(volume);
idle_schedule(IDLE_SYNC, __audio_init_idle, NULL);
}
void audio_deinit()
{
gst_element_set_state(audio_pipeline, GST_STATE_NULL);
gst_object_unref(GST_ELEMENT(audio_pipeline));
g_source_remove(audio_bus_id);
audio_pipeline = NULL;
audio_source = NULL;
audio_decoder = NULL;
audio_converter = NULL;
audio_volume = NULL;
audio_sink = NULL;
audio_track = NULL;
gst_deinit();
}
void audio_save()
{
if (audio_track)
settings_set(SETTINGS_TRACK, track_index(audio_track));
}
bool audio_load(struct track *track)
{
if (track == audio_track)
return false;
return __audio_load(track, GST_STATE_PLAYING) == track;
}
struct track *audio_cur_track()
{
return audio_track;
}
GstState audio_cur_state()
{
GstState cur = GST_STATE_NULL;
if (audio_pipeline)
gst_element_get_state(audio_pipeline,
&cur, NULL,
GST_CLOCK_TIME_NONE);
return cur;
}
void audio_set_volume(unsigned int volume)
{
gdouble vol;
if (volume > 100)
volume = 100;
vol = (gdouble)volume / 100;
settings_set(SETTINGS_VOLUME, volume);
g_object_set(G_OBJECT(audio_volume), "volume", vol, NULL);
}
unsigned int audio_get_volume()
{
gdouble volume;
g_object_get(G_OBJECT(audio_volume), "volume", &volume, NULL);
return volume * 100;
}
bool audio_play()
{
if (!audio_track)
return false;
return __audio_change_state(GST_STATE_PLAYING);
}
bool audio_pause()
{
if (!audio_track)
return false;
return __audio_change_state(GST_STATE_PAUSED);
}
bool audio_seek(gint64 offset)
{
if (!audio_track)
return false;
return gst_element_seek_simple(audio_pipeline,
GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH,
offset);
}
gint64 audio_position()
{
gint64 position;
if (gst_element_query_position(audio_pipeline,
GST_FORMAT_TIME,
&position))
return position;
return 0;
}
gint64 audio_duration()
{
gint64 duration;
if (gst_element_query_duration(audio_pipeline,
GST_FORMAT_TIME,
&duration))
return duration;
if (audio_track)
return audio_track->tr_length * GST_SECOND;
return 0;
}
struct track *audio_next()
{
return __audio_next(GST_STATE_PLAYING);
}
struct track *audio_prev()
{
return __audio_do_load(playlist_prev(), GST_STATE_PLAYING);
}
struct track *audio_eos()
{
/* Mark current track as played */
if (audio_track)
track_played(audio_track);
/* Check pause count and pick the next track */
if (audio_pause_count >= 0) {
audio_pause_after(audio_pause_count - 1);
if (audio_pause_count == -1)
return __audio_next(GST_STATE_PAUSED);
}
return __audio_next(GST_STATE_PLAYING);
}
void audio_error(GstMessage *error)
{
gchar *path = NULL, *debug = NULL;
GError *err = NULL;
if (audio_track)
path = track_path(audio_track);
if (error)
gst_message_parse_error(error, &err, &debug);
g_print("Error: %s (%s)\n", err->message, path);
if (debug)
g_print("Debug details: %s\n", debug);
__audio_next(audio_cur_state());
g_error_free(err);
g_free(debug);
g_free(path);
}
void audio_pause_after(int n)
{
if (n != audio_pause_count) {
audio_pause_count = n;
if (audio_cb)
audio_cb->audio_cb_config_pause(audio_pause_count);
}
}
#ifdef CONFIG_TESTING
GstElement *test_audio_pipeline()
{
return audio_pipeline;
}
#endif /* CONFIG_TESTING */