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

View File

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

View File

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

View File

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

1
tests/.gitignore vendored
View File

@ -11,3 +11,4 @@ library
playlist playlist
deck deck
driver 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, [], []), ("playlist.cpp", True, [], []),
("deck.cpp", True, [], []), ("deck.cpp", True, [], []),
("driver.cpp", False, [ "driver.cpp" ], []), ("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(); driver->error();
test_equal(error_count, (unsigned)1); 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) int main(int argc, char **argv)