diff --git a/config b/config index 2b52c65e..4162743a 100644 --- a/config +++ b/config @@ -34,6 +34,7 @@ class Config: if self.TEST: env.Append( CCFLAGS = [ "-DCONFIG_TEST" ]) def reset(self, TEST = False): + self.AUDIO = False self.DATABASE = False self.DECK = False self.FILE = False diff --git a/design/audio.txt b/design/audio.txt index 150fa080..c6384654 100644 --- a/design/audio.txt +++ b/design/audio.txt @@ -13,11 +13,12 @@ Audio: (lib/audio.cpp) namespace that will be easier to work with than using raw gstreamer functions. - The audio layer will also control the "pause after N tracks" feature - so songs can be loaded without neeting to pass in a "begin playback" - flag every time. The remaining tracks counter will only be decremented - when a song finishes through the end-of-stream message passed by the - gst pipeline. + The audio layer is meant to be an interface used by the front end to + control most features of the backend library. This means implementing + next track, previous track, and so on here. + + Gstreamer options passed to audio :: init() can be found by running + `gst-inspect-1.0 --help-gst` on the command line. - Internal: Set up a message bus to look for end-of-stream and error messages so @@ -28,27 +29,30 @@ Audio: (lib/audio.cpp) - API: void audio :: init(argc, argv) Initialize the gstreamer layer and reload the track that was - last loaded before shutdown. Please only pass --gst-* options - for argv. + last loaded before shutdown. Gstreamer is supposed to remove + options from the argv array as they are processed, so pass + pointers to argc and argv to this function. - void audio :: play() - Begin or resume playback. + bool audio :: play() + bool audio :: pause() + Change the gstreamer state to either GST_STATE_PLAYING or + GST_STATE_PAUSED. Return true on success and false otherwise. - void audio :: pause() - Pause playback. - - void audio :: seek_to(int) - Seek to a position X seconds into the track + bool audio :: seek_to(int) + Seek to a position X seconds into the track. Return true if + a track is loaded and the seek isn't out of bounds. False + otherwise. void audio :: stop() pause() seek_to(0) - void audio :: next() - Call the playlist :: next() function to get the next trackid, + bool audio :: next() + Call the deck :: next() function to get the next trackid, and load that file into the gstreamer pipeline. Do not change the state of the pipeline (if nothing is playing yet, don't - call play()). + call play()). Return true if a track has been loaded into the + pipeline, false otherwise. void audio :: previous() Call the playlist :: previous() function to iterate backwards @@ -65,7 +69,9 @@ Audio: (lib/audio.cpp) Return the duration of the current song in seconds. void audio :: pause_after(unsigned int) - Pause after N tracks, pass a negative number to disable. + Pause after N tracks, pass a negative number to disable. The + count will only be decremented when an end-of-stream message + is received by the gstreamer pipeline. unsigned int audio :: pause_count() Return the number of tracks that will be played before diff --git a/include/audio.h b/include/audio.h new file mode 100644 index 00000000..6f8f04f9 --- /dev/null +++ b/include/audio.h @@ -0,0 +1,23 @@ +/* + * Copyright 2013 (c) Anna Schumaker. + */ +#ifndef OCARINA_AUDIO_H +#define OCARINA_AUDIO_H + +extern "C" { + #include +} + +namespace audio +{ + + void init(int *, char ***); + + bool play(); + bool pause(); + bool seek_to(double); + bool next(); + +}; + +#endif /* OCARINA_AUDIO_H */ diff --git a/lib/Sconscript b/lib/Sconscript index f0344536..564aa329 100644 --- a/lib/Sconscript +++ b/lib/Sconscript @@ -15,6 +15,7 @@ modules = { # # ########################### + "AUDIO" : Module("audio.cpp", package = "gstreamer-1.0", depends = [ "DECK", "LIBRARY" ]), "DATABASE" : Module("database.cpp", depends = [ "FILE" ]), "DECK" : Module("deck.cpp", depends = [ "PLAYLIST" ]), "FILE" : Module("file.cpp", package = "glib-2.0"), diff --git a/lib/audio.cpp b/lib/audio.cpp new file mode 100644 index 00000000..a69eeca9 --- /dev/null +++ b/lib/audio.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2013 (c) Anna Schumaker. + */ +#include +#include +#include + +#include + +static GstElement *ocarina_player; +static bool track_loaded = false; + +static gboolean on_message(GstBus *bus, GstMessage *message, gpointer data) +{ + return TRUE; +} + +static bool change_state(GstState state) +{ + GstStateChangeReturn ret; + + ret = gst_element_set_state(GST_ELEMENT(ocarina_player), state); + switch (ret) { + case GST_STATE_CHANGE_SUCCESS: + case GST_STATE_CHANGE_ASYNC: + return true; + default: + return false; + } +} + +static bool load_song(library :: Song &song) +{ + GstState state; + gchar *escaped; + std::string filepath = song.library->root_path + "/" + song.track->filepath; + + gst_element_get_state(GST_ELEMENT(ocarina_player), &state, + NULL, GST_CLOCK_TIME_NONE); + + escaped = gst_filename_to_uri(filepath.c_str(), NULL); + g_object_set(G_OBJECT(ocarina_player), "uri", escaped, NULL); + g_free(escaped); + + return change_state(state); +} + +void audio :: init(int *argc, char ***argv) +{ + GstBus *bus; + + gst_init(argc, argv); + + ocarina_player = gst_element_factory_make("playbin", "ocarina_player"); + bus = gst_pipeline_get_bus(GST_PIPELINE(ocarina_player)); + + gst_bus_add_watch(bus, on_message, NULL); +} + +bool audio :: play() +{ + if (track_loaded == false) + return false; + return change_state(GST_STATE_PLAYING); +} + +bool audio :: pause() +{ + if (track_loaded == false) + return false; + return change_state(GST_STATE_PAUSED); +} + +bool audio :: seek_to(double pos) +{ + if (track_loaded == false) + return false; + return gst_element_seek_simple(GST_ELEMENT(ocarina_player), + GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, + pos); + return true; +} + +bool audio :: next() +{ + unsigned int id; + library :: Song song; + + try { + id = deck :: next(); + if (library :: lookup(id, &song) == false) + track_loaded = false; + else + track_loaded = load_song(song); + } catch (int) { + track_loaded = false; + } + return track_loaded; +} diff --git a/tests/Sconscript b/tests/Sconscript index cbff13b2..9280f150 100644 --- a/tests/Sconscript +++ b/tests/Sconscript @@ -57,8 +57,8 @@ rm_test_dir(xdg.BaseDirectory.xdg_data_home); # # Read SConscript files # -scripts = [ "database", "deck", "file", "filter", "group", "idle", "index", - "library", "playlist", "print" ] +scripts = [ "audio", "database", "deck", "file", "filter", "group", "idle", + "index", "library", "playlist", "print" ] for s in scripts: CONFIG.reset(TEST = True) SConscript("%s/Sconscript" % s) diff --git a/tests/audio/Sconscript b/tests/audio/Sconscript new file mode 100644 index 00000000..4cc17946 --- /dev/null +++ b/tests/audio/Sconscript @@ -0,0 +1,6 @@ +#!/usr/bin/python +Import("Test", "CONFIG") + +CONFIG.AUDIO = True + +Test("audio", "audio.cpp") diff --git a/tests/audio/audio.cpp b/tests/audio/audio.cpp new file mode 100644 index 00000000..d0c7000b --- /dev/null +++ b/tests/audio/audio.cpp @@ -0,0 +1,65 @@ +/* + * Copyright 2013 (c) Anna Schumaker. + */ +#include +#include +#include +#include + +#include +#include +#include + +void gen_library() +{ + system("tests/library/gen_library.sh"); + print("\n"); +} + +void check_ret(const std :: string &test, bool ret, bool expected) +{ + print("Test %s: ", test.c_str()); + if (ret == expected) + print("Success!\n"); + else + print("Failed.\n"); +} + +/* Call various functions without a track loaded */ +void test_0() +{ + check_ret("0a", audio :: play(), false); + check_ret("0b", audio :: pause(), false); + check_ret("0c", audio :: seek_to(10), false); + check_ret("0d", audio :: next(), false); + print("\n"); +} + +void test_1() +{ + check_ret("1a", audio :: next(), true); + check_ret("1b", audio :: play(), true); + check_ret("1c", audio :: pause(), true); + check_ret("1c", audio :: seek_to(10), true); +} + +int main(int argc, char **argv) +{ + Playlist *plist; + + gen_library(); + + /* Initialize before testing */ + audio :: init(&argc, &argv); + test_0(); + + /* Read in library, set up a playlist */ + library :: add_path("/tmp/library/0"); + plist = deck :: create(); + for (unsigned int i = 0; i < 150; i++) + plist->add(i); + + test_1(); + + return 0; +}