audio: Begin implementing audio code

I have written play(), pause(), next() and seek_to() so far.

Signed-off-by: Anna Schumaker <schumaker.anna@gmail.com>
This commit is contained in:
Anna Schumaker 2013-12-24 20:17:44 -05:00 committed by Anna Schumaker
parent b8b215be35
commit edbdd1c66f
8 changed files with 222 additions and 20 deletions

1
config
View File

@ -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

View File

@ -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

23
include/audio.h Normal file
View File

@ -0,0 +1,23 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#ifndef OCARINA_AUDIO_H
#define OCARINA_AUDIO_H
extern "C" {
#include <gst/gst.h>
}
namespace audio
{
void init(int *, char ***);
bool play();
bool pause();
bool seek_to(double);
bool next();
};
#endif /* OCARINA_AUDIO_H */

View File

@ -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"),

100
lib/audio.cpp Normal file
View File

@ -0,0 +1,100 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <audio.h>
#include <deck.h>
#include <library.h>
#include <string.h>
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;
}

View File

@ -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)

6
tests/audio/Sconscript Normal file
View File

@ -0,0 +1,6 @@
#!/usr/bin/python
Import("Test", "CONFIG")
CONFIG.AUDIO = True
Test("audio", "audio.cpp")

65
tests/audio/audio.cpp Normal file
View File

@ -0,0 +1,65 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <audio.h>
#include <deck.h>
#include <library.h>
#include <print.h>
#include <stdlib.h>
#include <string>
#include <unistd.h>
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;
}