audio: Update the code to match the design

This patch reworks the unit test using the TestDriver audio driver.  I
also recode the audio layer to match the design and drastically clean up
the code.

Signed-off-by: Anna Schumaker <Anna@OcarinaProject.net>
This commit is contained in:
Anna Schumaker 2014-06-01 17:52:49 -04:00
parent b8ea2c989d
commit 1bfa299e08
13 changed files with 293 additions and 449 deletions

25
DESIGN
View File

@ -1305,6 +1305,9 @@ Audio Driver:
values are returned to the Audio layer. This layer will derive from
the Driver class to implement either the GSTDriver or the TestDriver.
- Seconds -> Nanoseconds conversion:
static const unsigned long O_SECOND = 1000000000;
- Driver:
class Driver {
protected:
@ -1382,9 +1385,6 @@ Audio:
File << current_track->id << endl;
- Seconds -> Nanoseconds conversion:
#define O_SECOND 1000000000
- API:
void audio :: init(int *argc, char ***argv);
Initialize the audio driver through argc and argv. Read in
@ -1396,6 +1396,10 @@ Audio:
Call the corresponding function from the audio driver, but only
if a track is loaded.
void audio :: stop();
pause()
seek_to(0)
long audio :: position();
long audio :: duration();
Call the corresponding function from the audio driver. Return
@ -1405,10 +1409,6 @@ Audio:
Return the current audio position in string form.
Return an empty string if there is no current track.
void audio :: stop();
pause()
seek_to(0)
void audio :: next();
Call the deck :: next() function to get the next track that
should be played and use the audio driver to load the track.
@ -1421,15 +1421,15 @@ Audio:
Save that track's ID to the cur_track file.
Track *audio :: current_track();
Return the currently playing Track.
Return NULL if there is no current track.
void audio :: load_track(Track *track);
Load the requested track.
Save that track's ID to the cur_track file.
Track *audio :: current_track();
Return the currently playing Track.
Return NULL if there is no current track.
void audio :: pause_after(bool enabled, unsigned int N);
If enabled == true:
Configure Ocarina to pause playback after N tracks
@ -1437,6 +1437,9 @@ Audio:
If enabled == false:
Do not automatically pause.
If N is greater than the current pause count then enabled should
be set to true.
bool audio :: pause_enabled();
unsigned int audio :: pause_count();
Use these functions to access the current "pause after N" state.

View File

@ -4,32 +4,27 @@
#ifndef OCARINA_AUDIO_H
#define OCARINA_AUDIO_H
#include <error.h>
#include <queue.h>
#include <tags.h>
#include <string>
namespace audio
{
void init(int *, char ***);
void load_state();
void quit();
void play();
void pause();
void toggle_play();
void stop();
void next();
void previous();
void load_trackid(unsigned int);
unsigned int current_trackid();
Queue *get_recent_pq();
void seek_to(long);
void stop();
long position();
std::string position_str();
long duration();
std::string position_str();
void next();
void prev();
void load_track(Track *track);
Track *current_track();
void pause_after(bool, unsigned int);
bool pause_enabled();

View File

@ -4,6 +4,9 @@
#include <string>
static const unsigned long O_SECOND = 1000000000;
class Driver {
protected:
void (*on_eos)();

View File

@ -5,103 +5,87 @@
#include <callback.h>
#include <deck.h>
#include <driver.h>
#include <library.h>
#include <sstream>
#include <string.h>
static bool player_playing = false;
static bool track_loaded = false;
static unsigned int cur_trackid = 0;
static bool o_pause_enabled = false;
static unsigned int o_pause_count = 0;
static bool o_should_pause = false;
static bool _pause_enabled = false;
static unsigned int _pause_count = 0;
static Track *cur_track = NULL;
static File f_cur_track("cur_track", 0);
static void handle_pause_count()
{
if (o_pause_enabled == false)
return;
else if (o_pause_count == 0) {
o_should_pause = true;
o_pause_enabled = false;
get_callbacks()->on_pause();
} else
o_pause_count--;
get_callbacks()->on_pause_count_changed(o_pause_enabled, o_pause_count);
}
static void on_error()
{
audio :: next();
audio :: play();
}
static void on_eos()
{
Track *track;
handle_pause_count();
track = tagdb :: lookup(cur_trackid);
if (track) {
track->played();
library :: get_queue()->updated(track);
}
audio :: next();
audio :: seek_to(0);
}
static void save_state()
{
f_cur_track.open(OPEN_WRITE);
f_cur_track << cur_trackid << std::endl;
f_cur_track << cur_track->id << std::endl;
f_cur_track.close();
}
static bool load_song(Track *track)
static void _load_track(Track *track, bool start_playback)
{
bool start_playback;
if (o_should_pause)
start_playback = false;
else
start_playback = driver :: get_driver()->is_playing();
cur_track = track;
if (!track)
return;
driver :: get_driver()->load(track->path());
get_callbacks()->on_track_loaded(track);
if (start_playback)
return driver :: get_driver()->play();
audio :: play();
else
return driver :: get_driver()->pause();
audio :: pause();
save_state();
}
static inline void _load_track_default(Track *track)
{
_load_track(track, driver :: get_driver()->is_playing());
}
static bool continue_playback()
{
bool ret = true;
if (_pause_enabled) {
if (_pause_count == 0) {
ret = false;
_pause_enabled = false;
} else
_pause_count--;
get_callbacks()->on_pause_count_changed(_pause_enabled, _pause_count);
}
return ret;
}
static void on_eos()
{
if (cur_track) {
cur_track->played();
library :: get_queue()->updated(cur_track);
}
_load_track(deck :: next(), continue_playback());
}
void audio :: init(int *argc, char ***argv)
{
driver :: get_driver()->init(argc, argv, on_eos, on_error);
}
void audio :: load_state()
{
unsigned int id;
driver :: get_driver()->init(argc, argv, on_eos, audio :: next);
if (f_cur_track.exists()) {
f_cur_track.open(OPEN_READ);
f_cur_track >> id;
f_cur_track.close();
audio :: load_trackid(id);
audio :: load_track(tagdb :: lookup(id));
}
}
void audio :: quit()
{
}
void audio :: play()
{
if (track_loaded == false)
if (!cur_track)
return;
if (driver :: get_driver()->play())
get_callbacks()->on_play();
@ -109,18 +93,17 @@ void audio :: play()
void audio :: pause()
{
if (track_loaded == false)
if (!cur_track)
return;
if (driver :: get_driver()->pause())
get_callbacks()->on_pause();
}
void audio :: toggle_play()
void audio :: seek_to(long pos)
{
if (player_playing == true)
pause();
else
play();
if (!cur_track)
return;
driver :: get_driver()->seek_to(pos);
}
void audio :: stop()
@ -129,76 +112,24 @@ void audio :: stop()
seek_to(0);
}
void audio :: seek_to(long pos)
{
if (track_loaded == false)
return;
driver :: get_driver()->seek_to(pos);
}
void audio :: next()
{
Track *track;
track_loaded = false;
track = deck :: next();
load_song(track);
track_loaded = true;
cur_trackid = track->id;
save_state();
}
void audio :: previous()
{
Track *track = deck :: prev();
if (track->id == cur_trackid)
return;
load_song(track);
cur_trackid = track->id;
save_state();
}
void audio :: load_trackid(unsigned int track_id)
{
Track *track;
if ((track_id == cur_trackid) && (track_loaded == true))
return;
track_loaded = false;
try {
track = tagdb :: lookup(track_id);
} catch (int err) {
return;
}
load_song(track);
track_loaded = true;
cur_trackid = track_id;
save_state();
deck :: get_queue()->add(track);
}
unsigned int audio :: current_trackid()
{
if (track_loaded == false)
throw -E_EXIST;
return cur_trackid;
}
long audio :: position()
{
if (track_loaded == false)
if (!cur_track)
return 0;
return driver :: get_driver()->position();
}
long audio :: duration()
{
if (!cur_track)
return 0;
return driver :: get_driver()->duration();
}
std::string audio :: position_str()
{
std::stringstream ss;
long cur = position() / GST_SECOND;
long cur = position() / O_SECOND;
unsigned int minutes = cur / 60;
unsigned int seconds = cur % 60;
@ -209,29 +140,46 @@ std::string audio :: position_str()
return ss.str();
}
long audio :: duration()
void audio :: next()
{
if (track_loaded == false)
return 0;
return driver :: get_driver()->duration();
_load_track_default(deck :: next());
}
void audio :: prev()
{
_load_track_default(deck :: prev());
}
void audio :: load_track(Track *track)
{
if (!track || track == cur_track)
return;
_load_track(track, driver :: get_driver()->is_playing());
deck :: get_queue()->add(cur_track);
}
Track *audio :: current_track()
{
return cur_track;
}
void audio :: pause_after(bool enabled, unsigned int n)
{
if (n > o_pause_count)
if (n > _pause_count)
enabled = true;
o_pause_enabled = enabled;
o_pause_count = n;
_pause_enabled = enabled;
_pause_count = n;
get_callbacks()->on_pause_count_changed(enabled, n);
}
bool audio :: pause_enabled()
{
return o_pause_enabled;
return _pause_enabled;
}
unsigned int audio :: pause_count()
{
return o_pause_count;
return _pause_count;
}

View File

@ -15,7 +15,8 @@ TestDriver :: ~TestDriver() {}
void TestDriver :: init(int *argc, char ***argv, void (*eos_cb)(), void (*error_cb)())
{ on_eos = eos_cb; on_error = error_cb; }
void TestDriver :: load(const std::string &file) { cur_file = file; }
void TestDriver :: load(const std::string &file)
{ cur_file = file; playing = false; cur_pos = 0; }
bool TestDriver :: play() { playing = true; return true; }
bool TestDriver :: pause() { playing = false; return true; }
bool TestDriver :: is_playing() { return playing; }

1
tests/.gitignore vendored
View File

@ -11,3 +11,4 @@ library
playlist
deck
driver
audio

1
tests/Data/cur_track Normal file
View File

@ -0,0 +1 @@
3

View File

@ -22,6 +22,7 @@ tests = [
("playlist.cpp", True, [], []),
("deck.cpp", True, [], []),
("driver.cpp", False, [ "driver.cpp" ], []),
("audio.cpp", True, [ "driver.cpp" ], []),
]

170
tests/audio.cpp Normal file
View File

@ -0,0 +1,170 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <audio.h>
#include <driver.h>
#include <library.h>
#include "test.h"
Track *TRACK_NULL = NULL;
void test_pre_init()
{
TestDriver *driver = (TestDriver *)driver :: get_driver();
test_equal(audio :: current_track(), TRACK_NULL);
audio :: play();
test_equal(driver->playing, false);
driver->playing = true;
audio :: pause();
test_equal(driver->playing, true);
audio :: stop();
test_equal(driver->playing, true);
driver->playing = false;
audio :: seek_to(4242);
test_equal(driver->position(), (long)0);
driver->cur_pos = 4242;
test_equal(audio :: position(), (long)0);
driver->cur_pos = 0;
driver->cur_duration = 4242;
test_equal(audio :: duration(), (long)0);
driver->cur_duration = 0;
audio :: next();
test_equal(audio :: current_track(), TRACK_NULL);
audio :: prev();
test_equal(audio :: current_track(), TRACK_NULL);
}
void test_init(int argc, char **argv)
{
Track *track;
test :: cp_data_dir();
audio :: init(&argc, &argv);
track = audio :: current_track();
test_equal(track, TRACK_NULL);
tagdb :: init();
library :: init();
audio :: init(&argc, &argv);
track = audio :: current_track();
test_not_equal(track, TRACK_NULL);
}
void test_playback_controls()
{
TestDriver *driver = (TestDriver *)driver :: get_driver();
audio :: play();
test_equal(driver->playing, true);
audio :: pause();
test_equal(driver->playing, false);
audio :: seek_to(4242);
test_equal(driver->cur_pos, (long)4242);
test_equal(audio :: position(), (long)4242);
audio :: play();
audio :: stop();
test_equal(driver->playing, false);
test_equal(driver->cur_pos, (long)0);
audio :: seek_to(4242);
driver->cur_duration = 424242;
test_equal(audio :: position(), (long)4242);
test_equal(audio :: duration(), (long)424242);
audio :: seek_to(83 * O_SECOND);
test_equal(audio :: position_str(), (std::string)"1:23");
}
void test_track_controls()
{
Track *track = NULL;
TestDriver *driver = (TestDriver *)driver :: get_driver();
library :: get_queue()->unset_flag(Q_RANDOM);
audio :: pause();
audio :: next();
test_not_equal(audio :: current_track()->id, (unsigned)2);
test_equal(driver->is_playing(), false);
audio :: play();
audio :: next();
test_equal(driver->is_playing(), true);
audio :: load_track(track);
test_not_equal(audio :: current_track(), track);
track = tagdb :: lookup(0);
audio :: seek_to(4242);
audio :: load_track(track);
test_equal(driver->is_playing(), true);
test_equal(audio :: position(), (long)0);
audio :: seek_to(4242);
audio :: load_track(track);
test_equal(driver->is_playing(), true);
test_equal(audio :: position(), (long)4242);
driver->error();
test_not_equal(audio :: current_track(), track);
track = audio :: current_track();
driver->eos();
test_not_equal(audio :: current_track(), track);
}
void test_autopause()
{
TestDriver *driver = (TestDriver *)driver :: get_driver();
audio :: play();
test_equal(audio :: pause_enabled(), false);
test_equal(audio :: pause_count(), (unsigned)0);
audio :: pause_after(true, 3);
test_equal(audio :: pause_enabled(), true);
test_equal(audio :: pause_count(), (unsigned)3);
audio :: pause_after(false, 3);
test_equal(audio :: pause_enabled(), false);
test_equal(audio :: pause_count(), (unsigned)3);
audio :: pause_after(false, 5);
test_equal(audio :: pause_enabled(), true);
test_equal(audio :: pause_count(), (unsigned)5);
for (int i = 4; i >= 0; i--) {
driver->eos();
test_equal(audio :: pause_enabled(), true);
test_equal(audio :: pause_count(), (unsigned)i);
test_equal(driver->is_playing(), true);
}
driver->eos();
test_equal(audio :: pause_enabled(), false);
test_equal(audio :: pause_count(), (unsigned)0);
test_equal(driver->is_playing(), false);
}
int main(int argc, char **argv)
{
run_test("Test Audio Pre-Init", test_pre_init);
run_test("Test Audio Init", test_init, argc, argv);
run_test("Test Audio Playback Controls", test_playback_controls);
run_test("Test Audio Track Controls", test_track_controls);
run_test("Test Audio Automatic Pausing", test_autopause);
return 0;
}

View File

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

View File

@ -1,226 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <audio.h>
#include <deck.h>
#include <idle.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");
}
template <class T>
void check_ret(const std :: string &test, long ret, long expected)
{
print("Test %s: ", test.c_str());
if (ret == expected)
print("Success!\n");
else
print("Failed (Expected %ld but got %ld)\n", expected, ret);
}
void check_error(int error, int expected)
{
if (expected == 0) {
if (error == 0)
print("Success!\n");
else
print("Failed with error: %d\n", error);
} else {
if (error == 0)
print("Failed (expected error: %d)\n", expected);
else
print("Success!\n");
}
}
void call_func(const std :: string &test, void (*func)(), int expected)
{
print("Test %s: ", test.c_str());
try {
func();
check_error(0, expected);
} catch (int error) {
check_error(error, expected);
}
}
void call_func(const std :: string &test, void (*func)(long), long arg, int expected)
{
print("Test %s: ", test.c_str());
try {
func(arg);
check_error(0, expected);
} catch (int error) {
check_error(error, expected);
}
}
/* Call various functions without a track loaded */
void test_0()
{
call_func("0a", audio :: play, 0);
call_func("0b", audio :: pause, 0);
call_func("0c", audio :: seek_to, 10, 0);
call_func("0d", audio :: next, -E_EXIST);
call_func("0e", audio :: stop, 0);
check_ret("0f", audio :: position(), 0);
check_ret("0g", audio :: duration(), 0);
try {
print("Test 0h: ");
audio :: current_trackid();
check_error(0, -E_EXIST);
} catch (int error) {
check_error(error, -E_EXIST);
}
call_func("0i", audio :: previous, -E_EXIST);
print("\n");
}
void test_1()
{
library :: Song song;
library :: lookup(0, &song);
call_func("1a", audio :: next, 0);
call_func("1b", audio :: play, 0);
call_func("1c", audio :: pause, 0);
call_func("1d", audio :: seek_to, 1 * GST_SECOND, 0);
call_func("1e", audio :: stop, 0);
check_ret("1f", audio :: current_trackid() == 0, true);
check_ret("1g", audio :: position(), 0);
call_func("1h", audio :: previous, 0);
check_ret("1i", audio :: current_trackid() == 0, true);
audio :: next();
audio :: next();
call_func("1j", audio :: previous, 0);
check_ret("1k", audio :: current_trackid() == 1, true);
call_func("1l", audio :: previous, 0);
check_ret("1m", audio :: current_trackid() == 0, true);
print("\n");
}
/* Test pause_after() */
unsigned int test_2_count = 0;
int test_2_cb(gpointer data)
{
long seek_pos, pos, max;
library :: Song song;
GMainLoop *loop = (GMainLoop *)data;
library :: lookup(audio :: current_trackid(), &song);
pos = audio :: position();
switch (test_2_count) {
case 0:
break;
case 1:
check_ret("2g", audio :: duration(), song.track->length * GST_SECOND);
seek_pos = (song.track->length * GST_SECOND) - GST_SECOND;
call_func("2h", audio :: seek_to, seek_pos, 0);
break;
case 2:
max = (song.track->length * GST_SECOND) - GST_SECOND + (501 * GST_MSECOND);
check_ret("2i", pos <= max, true);
call_func("2j", audio :: stop, 0);
break;
case 3:
check_ret("2k", pos, 0);
call_func("2l", audio :: play, 0);
call_func("2m", audio :: seek_to, audio :: duration() - 1, 0);
break;
case 4:
check_ret("2n", audio :: pause_count(), (long)2);
call_func("2o", audio :: seek_to, audio :: duration() - 1, 0);
break;
case 5:
check_ret("2p", audio :: pause_count(), (long)1);
call_func("2q", audio :: seek_to, audio :: duration() - 1, 0);
break;
case 6:
check_ret("2r", audio :: pause_count(), (long)0);
call_func("2s", audio :: seek_to, audio :: duration() - 1, 0);
break;
case 7:
check_ret("2t", audio :: pause_enabled(), false);
check_ret("2u", audio :: pause_count(), (long)0);
break;
case 8:
pos = audio :: position();
check_ret("2v", (0 <= pos) && (pos <= GST_MSECOND), true);
break;
case 9:
pos = audio :: position();
check_ret("2w", (0 <= pos) && (pos <= GST_MSECOND), true);
default:
g_main_quit(loop);
}
test_2_count++;
return true;
}
void test_2()
{
GMainLoop *loop;
check_ret("2a", audio :: pause_enabled(), false);
check_ret("2b", audio :: pause_count(), (long)0);
audio :: pause_after(true, 3);
check_ret("2c", audio :: pause_enabled(), true);
check_ret("2d", audio :: pause_count(), (long)3);
audio :: next();
check_ret("2e", audio :: pause_enabled(), true);
check_ret("2f", audio :: pause_count(), (long)3);
audio :: play();
loop = g_main_loop_new(NULL, FALSE);
g_timeout_add(500, test_2_cb, loop);
g_main_loop_run(loop);
}
int main(int argc, char **argv)
{
Playqueue *pqueue;
gen_library();
/* Initialize before testing */
audio :: init(&argc, &argv);
test_0();
/* Read in library, set up a playlist */
library::init();
deck::init();
library :: reset();
library :: add_path("/tmp/library/0");
while (idle :: run_task());
pqueue = deck :: create(false);
for (unsigned int i = 0; i < 150; i++)
pqueue->add(i);
test_1();
test_2();
audio :: quit();
return 0;
}

View File

@ -1,53 +0,0 @@
Generating library: 0
Generating library: 1
Generating library: 2
Generating library: 3
Generating library: 4
Test 0a: Success!
Test 0b: Success!
Test 0c: Success!
Test 0d: Success!
Test 0e: Success!
Test 0f: Success!
Test 0g: Success!
Test 0h: Success!
Test 0i: Success!
Test 1a: Success!
Test 1b: Success!
Test 1c: Success!
Test 1d: Success!
Test 1e: Success!
Test 1f: Success!
Test 1g: Success!
Test 1h: Success!
Test 1i: Success!
Test 1j: Success!
Test 1k: Success!
Test 1l: Success!
Test 1m: Success!
Test 2a: Success!
Test 2b: Success!
Test 2c: Success!
Test 2d: Success!
Test 2e: Success!
Test 2f: Success!
Test 2g: Success!
Test 2h: Success!
Test 2i: Success!
Test 2j: Success!
Test 2k: Success!
Test 2l: Success!
Test 2m: Success!
Test 2n: Success!
Test 2o: Success!
Test 2p: Success!
Test 2q: Success!
Test 2r: Success!
Test 2s: Success!
Test 2t: Success!
Test 2u: Success!
Test 2v: Success!
Test 2w: Success!

View File

@ -50,6 +50,12 @@ void test_driver()
driver->error();
test_equal(error_count, (unsigned)1);
driver->play();
driver->seek_to(4242);
driver->load(file);
test_equal(driver->is_playing(), false);
test_equal(driver->position(), (long)0);
}
int main(int argc, char **argv)