Skip to content

Commit

Permalink
player improvements
Browse files Browse the repository at this point in the history
 * build: fix GNUInstallDirs usage
 * metadata example: fix improper cleanup
 * sinks now support any number of sample rates, sample formats,
   channel layouts, and support planar and/or interleaved
 * introduce `groove_sink_set_only_format', a convenience function
   which does the old behavior
 * player: exact mode is now the only mode, except that if a device
   does not support the input format, a substitute will be used
   instead.
 * playlist example: don't exit until last song finished playing and
   the device was closed.
 * sink now has a method called `filled` which is called when
   buffers are put into the sink.
 * player: `GROOVE_EVENT_DEVICEREOPENED` is now
   `GROOVE_EVENT_DEVICE_CLOSED` and `GROOVE_EVENT_DEVICE_OPENED`.
 * player: `GROOVE_EVENT_DEVICE_REOPEN_ERROR` is now
   `GROOVE_EVENT_DEVICE_OPEN_ERROR`
 * Progress toward #115
 * Better exact mode. Closes #114
 * No more buffer underrun when first starting player.
   Closes #113
 * player: emit now playing event takes latency into account.
   Closes #109
 * player: get rid of panics. closes #108
 * fix playlist example not terminating after songs finished.
   Closes #107
  • Loading branch information
andrewrk committed Sep 15, 2015
1 parent 4b63776 commit 40abf55
Show file tree
Hide file tree
Showing 20 changed files with 1,506 additions and 538 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@
* Instead of `groove_file_open` now use `groove_file_create` and then
`groove_file_open`. Then `groove_file_destroy` when done.
* Instead of `groove_file_close` use `groove_file_destroy`.
* `GroovePlayer::target_audio_format`, `GroovePlayer::actual_audio_format`, and
`GroovePlayer::use_exact_audio_format` no longer exist. Exact mode is always
on now. When the device does not support audio parameters, the highest
quality substitute is used.
* `GrooveSink::audio_format` no longer exists. See
`groove_sink_set_only_format` instead.
* `GrooveSink::disable_resample` no longer exists. Instead, this is now the
default and you must set the allowed formats in order to get conversions.
* `GrooveSink::bytes_per_sec` no longer exists. API users should compute this
value themselves if they want to use it.
* player: `GROOVE_EVENT_DEVICEREOPENED` is now
`GROOVE_EVENT_DEVICE_CLOSED` and `GROOVE_EVENT_DEVICE_OPENED`.
* player: `GROOVE_EVENT_DEVICE_REOPEN_ERROR` is now
`GROOVE_EVENT_DEVICE_OPEN_ERROR`


### Version 4.3.0 (2015-05-25)
Expand Down
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
cmake_minimum_required(VERSION 2.8.5)
project(libgroove C CXX)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})

if(CMAKE_VERSION VERSION_LESS 3.0.0)
set(CMAKE_INSTALL_LIBDIR "lib" CACHE PATH "library install dir (lib)")
Expand All @@ -15,9 +17,6 @@ if(NOT CMAKE_BUILD_TYPE)
"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
endif()

project(libgroove C CXX)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})

set(LIBGROOVE_VERSION_MAJOR 5)
set(LIBGROOVE_VERSION_MINOR 0)
set(LIBGROOVE_VERSION_PATCH 0)
Expand Down Expand Up @@ -101,6 +100,7 @@ set(LIBGROOVE_SOURCES
"${CMAKE_SOURCE_DIR}/src/waveform.cpp"
"${CMAKE_SOURCE_DIR}/src/playlist.cpp"
"${CMAKE_SOURCE_DIR}/src/util.cpp"
"${CMAKE_SOURCE_DIR}/src/os.cpp"
)

set(CONFIGURE_OUT_FILE "${CMAKE_BINARY_DIR}/config.h")
Expand Down
2 changes: 1 addition & 1 deletion example/metadata.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ int main(int argc, char * argv[]) {
printf("%s=%s\n", groove_tag_key(tag), groove_tag_value(tag));
if (file->dirty && (err = groove_file_save(file)))
fprintf(stderr, "error saving file: %s\n", groove_strerror(err));
groove_file_close(file);
groove_file_destroy(file);

groove_destroy(groove);
return 0;
Expand Down
19 changes: 11 additions & 8 deletions example/metadata_checksum.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,12 @@ int main(int argc, char * argv[]) {
}

struct GrooveSink *sink = groove_sink_create(groove);
sink->audio_format.sample_rate = 44100;
sink->audio_format.layout = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono);
sink->audio_format.format = SoundIoFormatS16NE;
sink->audio_format.is_planar = false;
struct GrooveAudioFormat audio_format;
audio_format.sample_rate = 44100;
audio_format.layout = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono);
audio_format.format = SoundIoFormatS16NE;
audio_format.is_planar = false;
groove_sink_set_only_format(sink, &audio_format);

if (groove_sink_attach(sink, playlist) < 0)
panic("error attaching sink");
Expand Down Expand Up @@ -206,10 +208,11 @@ int main(int argc, char * argv[]) {
playlist = groove_playlist_create(groove);

sink = groove_sink_create(groove);
sink->audio_format.sample_rate = 44100;
sink->audio_format.layout = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono);
sink->audio_format.format = SoundIoFormatS16NE;
sink->audio_format.is_planar = false;
audio_format.sample_rate = 44100;
audio_format.layout = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono);
audio_format.format = SoundIoFormatS16NE;
audio_format.is_planar = false;
groove_sink_set_only_format(sink, &audio_format);

if (groove_sink_attach(sink, playlist) < 0)
panic("error attaching sink");
Expand Down
43 changes: 27 additions & 16 deletions example/playlist.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ static int usage(const char *exe) {
fprintf(stderr, "Usage: %s [options] file1 file2 ...\n"
"Options:\n"
" [--volume 1.0]\n"
" [--exact]\n"
" [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n"
" [--device id]\n"
" [--raw]\n", exe);
Expand Down Expand Up @@ -59,9 +58,7 @@ int main(int argc, char * argv[]) {
char *arg = argv[i];
if (arg[0] == '-' && arg[1] == '-') {
arg += 2;
if (strcmp(arg, "exact") == 0) {
player->use_exact_audio_format = 1;
} else if (strcmp(arg, "raw") == 0) {
if (strcmp(arg, "raw") == 0) {
is_raw = true;
} else if (i + 1 >= argc) {
return usage(exe);
Expand Down Expand Up @@ -148,22 +145,27 @@ int main(int argc, char * argv[]) {

union GroovePlayerEvent event;
struct GroovePlaylistItem *item;
bool end_of_playlist = false;
while (groove_player_event_get(player, &event, 1) >= 0) {
switch (event.type) {
case GROOVE_EVENT_BUFFERUNDERRUN:
fprintf(stderr, "buffer underrun\n");
break;
case GROOVE_EVENT_DEVICEREOPENED:
fprintf(stderr, "device re-opened\n");
break;
case GROOVE_EVENT_DEVICE_REOPEN_ERROR:
panic("error re-opening device");
case GROOVE_EVENT_WAKEUP:
case GROOVE_EVENT_DEVICE_OPENED:
{
struct GrooveAudioFormat audio_format;
groove_player_get_device_audio_format(player, &audio_format);

fprintf(stderr, "device opened: %d channels %dHz %s\n", audio_format.layout.channel_count,
audio_format.sample_rate, soundio_format_string(audio_format.format));
break;
}
case GROOVE_EVENT_DEVICE_OPEN_ERROR:
panic("error opening device");
break;
case GROOVE_EVENT_NOWPLAYING:
groove_player_position(player, &item, NULL);
if (!item) {
printf("done\n");
case GROOVE_EVENT_DEVICE_CLOSED:
if (end_of_playlist) {
fprintf(stderr, "done\n");
item = playlist->head;
while (item) {
struct GrooveFile *file = item->file;
Expand All @@ -178,13 +180,22 @@ int main(int argc, char * argv[]) {
soundio_destroy(soundio);
return 0;
}
break;
case GROOVE_EVENT_WAKEUP:
break;
case GROOVE_EVENT_NOWPLAYING:
groove_player_position(player, &item, NULL);
if (!item) {
end_of_playlist = true;
break;
}
struct GrooveTag *artist_tag = groove_file_metadata_get(item->file, "artist", NULL, 0);
struct GrooveTag *title_tag = groove_file_metadata_get(item->file, "title", NULL, 0);
if (artist_tag && title_tag) {
printf("Now playing: %s - %s\n", groove_tag_value(artist_tag),
fprintf(stderr, "Now playing: %s - %s\n", groove_tag_value(artist_tag),
groove_tag_value(title_tag));
} else {
printf("Now playing: %s\n", item->file->filename);
fprintf(stderr, "Now playing: %s\n", item->file->filename);
}
break;
}
Expand Down
89 changes: 77 additions & 12 deletions groove/groove.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ enum GrooveFillMode {
GrooveFillModeEverySinkFull,
};

enum GrooveSinkFlags {
GrooveSinkFlagPlanarOk = 0x1,
GrooveSinkFlagInterleavedOk = 0x2,
};

#define GROOVE_LOG_QUIET -8
#define GROOVE_LOG_ERROR 16
#define GROOVE_LOG_WARNING 24
Expand All @@ -122,15 +127,15 @@ enum GrooveFillMode {
#define GROOVE_BUFFER_YES 1
#define GROOVE_BUFFER_END 2

struct Groove;

struct GrooveAudioFormat {
int sample_rate;
struct SoundIoChannelLayout layout;
enum SoundIoFormat format;
bool is_planar;
};

struct Groove;

struct GrooveFile {
/// read-only
int dirty;
Expand Down Expand Up @@ -235,11 +240,57 @@ struct GrooveBuffer {
/// for example you could use it to draw a waveform or other visualization
/// GroovePlayer uses this internally to get the audio buffer for playback
struct GrooveSink {
/// set this to the audio format you want the sink to output
struct GrooveAudioFormat audio_format;
/// Set this flag to ignore audio_format. If you set this flag, the
/// buffers you pull from this sink could have any audio format.
int disable_resample;
/// Set the allowed sample rates buffers in this sink will have. You may
/// leave this to the default `NULL` to allow any sample rate.
/// If the source audio format does not match any of these sample rates,
/// sample rate conversion will be performed to convert the sample rate to
/// GrooveSink::default_sample_rate.
/// See also ::groove_sink_set_only_format
struct SoundIoSampleRateRange *sample_rates;
/// See also ::groove_sink_set_only_format
int sample_rate_count;
/// If the source audio format does not match, sample rate conversion will
/// be performed to convert the sample rate to this value.
/// See also ::groove_sink_set_only_format
int sample_rate_default;

/// Set the allowed channel layouts buffers in this sink will have. You may
/// leave this to the default `NULL` to allow any channel layouts.
/// If the source channel layout does not match any of these channel
/// layouts, the audio will be remapped to fit
/// GrooveSink::channel_layout_default
/// See also ::groove_sink_set_only_format
struct SoundIoChannelLayout *channel_layouts;
/// See also ::groove_sink_set_only_format
int channel_layout_count;
/// If the source channel layout does not match, audio will be remapped to
/// fit this channel layout. This should point to one of
/// GrooveSink::channel_layouts.
/// See also ::groove_sink_set_only_format
struct SoundIoChannelLayout channel_layout_default;

/// Set the allowed sample formats buffers in this sink will have. You may
/// leave this to the default `NULL` to allow any sample format.
/// If the source sample format does not match any of these sample formats,
/// sample format conversion will be performed to convert the sample format
/// to GrooveSink::sample_format_default.
/// See also ::groove_sink_set_only_format
enum SoundIoFormat *sample_formats;
/// See also ::groove_sink_set_only_format
int sample_format_count;
/// If the source sample format does not match, sample format conversion
/// will be performed to convert the sample format to this.
/// See also ::groove_sink_set_only_format
enum SoundIoFormat sample_format_default;

/// See #GrooveSinkFlags
/// * If #GrooveSinkFlagPlanarOk is set, buffers in this sink may be planar.
/// * If #GrooveSinkFlagInterleavedOk is set, buffers in this sink may be
/// interleaved.
/// Leaving both of these flags unset is the same as having them both set.
/// See also ::groove_sink_set_only_format
uint32_t flags;

/// If you leave this to its default of 0, frames pulled from the sink
/// will have sample count determined by efficiency.
/// If you set this to a positive number, frames pulled from the sink
Expand Down Expand Up @@ -269,15 +320,13 @@ struct GrooveSink {
void (*pause)(struct GrooveSink *);
/// called when the playlist is played
void (*play)(struct GrooveSink *);
/// Called when a buffer is put into the sink. This is called from a thread
/// context in which it is undefined behavior to call any libgroove functions.
void (*filled)(struct GrooveSink *);

/// read-only. set when you call ::groove_sink_attach. cleared when you call
/// ::groove_sink_detach
struct GroovePlaylist *playlist;

/// read-only. automatically computed from audio_format when you call
/// ::groove_sink_attach
/// if `disable_resample` is 1 then this is unknown and set to 0.
int bytes_per_sec;
};


Expand Down Expand Up @@ -429,6 +478,22 @@ GROOVE_EXPORT void groove_buffer_unref(struct GrooveBuffer *buffer);
GROOVE_EXPORT struct GrooveSink *groove_sink_create(struct Groove *);
GROOVE_EXPORT void groove_sink_destroy(struct GrooveSink *sink);

/// If the sink should always convert to a single audio format, this function
/// is convenient (and avoids allocating memory).
/// This function sets the following fields based on `audio_format`:
/// * GrooveSink::sample_rates
/// * GrooveSink::sample_rate_count
/// * GrooveSink::sample_rate_default
/// * GrooveSink::channel_layouts
/// * GrooveSink::channel_layout_count
/// * GrooveSink::channel_layout_default
/// * GrooveSink::sample_formats
/// * GrooveSink::sample_format_count
/// * GrooveSink::sample_format_default
/// * GrooveSink::flags
GROOVE_EXPORT void groove_sink_set_only_format(struct GrooveSink *sink,
const struct GrooveAudioFormat *audio_format);

/// before calling this, set audio_format
/// returns 0 on success, < 0 on error
GROOVE_EXPORT int groove_sink_attach(struct GrooveSink *sink, struct GroovePlaylist *playlist);
Expand Down
35 changes: 10 additions & 25 deletions groove/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ enum GroovePlayerEventType {
/// when something tries to read from an empty buffer
GROOVE_EVENT_BUFFERUNDERRUN,

/// when the audio device is re-opened due to audio format changing
GROOVE_EVENT_DEVICEREOPENED,
/// when the audio device is closed
GROOVE_EVENT_DEVICE_CLOSED,

/// when the audio device is opened
GROOVE_EVENT_DEVICE_OPENED,

/// when the audio device gets an error re-opening
GROOVE_EVENT_DEVICE_REOPEN_ERROR,
GROOVE_EVENT_DEVICE_OPEN_ERROR,

/// user requested wakeup
GROOVE_EVENT_WAKEUP
Expand All @@ -33,18 +36,9 @@ union GroovePlayerEvent {
};

struct GroovePlayer {
/// Set this to the device you want to open. You may use NULL for default.
/// If you set `use_dummy_device` to 1, this field is ignored.
/// Set this to the device you want to open.
struct SoundIoDevice *device;

/// The desired audio format settings with which to open the device.
/// groove_player_create defaults these to 48000 Hz,
/// signed 32-bit native endian integer, stereo.
/// These are preferences; if a setting cannot be used, a substitute will be
/// used instead. actual_audio_format is set to the actual values.
/// If you set `use_exact_audio_format` to 1, this field is ignored.
struct GrooveAudioFormat target_audio_format;

/// Volume adjustment to make to this player.
/// It is recommended that you leave this at 1.0 and instead adjust the
/// gain of the underlying playlist.
Expand All @@ -53,19 +47,9 @@ struct GroovePlayer {
/// float format. Defaults to 1.0
double gain;

/// read-only. set when you call ::groove_player_attach and cleared when
/// Read-only. Set when you call ::groove_player_attach and cleared when
/// you call ::groove_player_detach
struct GroovePlaylist *playlist;

/// read-only. set to the actual format you get when you open the device.
/// ideally will be the same as target_audio_format but might not be.
struct GrooveAudioFormat actual_audio_format;

/// If you set this to 1, target_audio_format and actual_audio_format are
/// ignored and no resampling, channel layout remapping, or sample format
/// conversion will occur. The audio device will be reopened with exact
/// parameters whenever necessary.
int use_exact_audio_format;
};

GROOVE_EXPORT struct GroovePlayer *groove_player_create(struct Groove *groove);
Expand Down Expand Up @@ -109,6 +93,7 @@ GROOVE_EXPORT int groove_player_set_gain(struct GroovePlayer *player, double gai
/// closed and re-opened as necessary. When this happens, a
/// #GROOVE_EVENT_DEVICEREOPENED event is emitted, and you can use this function
/// to discover the audio format of the device.
GROOVE_EXPORT struct GrooveAudioFormat groove_player_get_device_audio_format(struct GroovePlayer *player);
GROOVE_EXPORT void groove_player_get_device_audio_format(struct GroovePlayer *player,
struct GrooveAudioFormat *out_audio_format);

#endif
Loading

0 comments on commit 40abf55

Please sign in to comment.