diff options
-rw-r--r-- | Makefile.am | 21 | ||||
-rw-r--r-- | NEWS | 9 | ||||
-rw-r--r-- | src/directory.c | 10 | ||||
-rw-r--r-- | src/encoder/flac_encoder.c | 1 | ||||
-rw-r--r-- | src/encoder/twolame_encoder.c | 1 | ||||
-rw-r--r-- | src/encoder/vorbis_encoder.c | 3 | ||||
-rw-r--r-- | src/encoder_plugin.h | 98 | ||||
-rw-r--r-- | src/output/jack_output_plugin.c | 16 | ||||
-rw-r--r-- | src/output/osx_output_plugin.c | 94 | ||||
-rw-r--r-- | src/output/recorder_output_plugin.c | 2 | ||||
-rw-r--r-- | src/output/shout_output_plugin.c | 2 | ||||
-rw-r--r-- | src/update_remove.c | 2 | ||||
-rw-r--r-- | src/update_walk.c | 6 | ||||
-rw-r--r-- | src/uri.c | 4 | ||||
-rw-r--r-- | test/run_encoder.c | 2 | ||||
-rw-r--r-- | test/test_vorbis_encoder.c | 109 |
16 files changed, 311 insertions, 69 deletions
diff --git a/Makefile.am b/Makefile.am index 75da99698..38d08f098 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1170,6 +1170,27 @@ test_run_encoder_LDADD = \ $(GLIB_LIBS) endif +if ENABLE_VORBIS_ENCODER +noinst_PROGRAMS += test/test_vorbis_encoder +test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.c \ + test/stdbin.h \ + src/conf.c src/tokenizer.c \ + src/utils.c \ + src/string_util.c \ + src/tag.c src/tag_pool.c \ + src/audio_check.c \ + src/audio_format.c \ + src/audio_parser.c \ + src/pcm_buffer.c \ + src/fifo_buffer.c src/growing_fifo.c \ + $(ENCODER_SRC) +test_test_vorbis_encoder_CPPFLAGS = $(AM_CPPFLAGS) \ + $(ENCODER_CFLAGS) +test_test_vorbis_encoder_LDADD = $(MPD_LIBS) \ + $(ENCODER_LIBS) \ + $(GLIB_LIBS) +endif + test_software_volume_SOURCES = test/software_volume.c \ test/stdbin.h \ src/audio_check.c \ @@ -44,6 +44,15 @@ ver 0.16.8 (2012/??/??) * decoder: - vorbis (and others): fix seeking at startup - ffmpeg: read the "year" tag +* encoder: + - vorbis: generate end-of-stream packet before tag + - vorbis: generate end-of-stream packet when playback ends +* output: + - jack: check for connection failure before starting playback + - jack: workaround for libjack1 crash bug + - osx: fix stuttering due to buffering bug +* fix endless loop in text file reader +* update: skip symlinks in path that is to be updated ver 0.16.7 (2012/02/04) diff --git a/src/directory.c b/src/directory.c index 662b8907f..930881129 100644 --- a/src/directory.c +++ b/src/directory.c @@ -86,7 +86,15 @@ directory_delete(struct directory *directory) const char * directory_get_name(const struct directory *directory) { - return g_basename(directory->path); + assert(!directory_is_root(directory)); + assert(directory->path != NULL); + + const char *slash = strrchr(directory->path, '/'); + assert((slash == NULL) == directory_is_root(directory->parent)); + + return slash != NULL + ? slash + 1 + : directory->path; } struct directory * diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c index 9f60bdae5..e32588e29 100644 --- a/src/encoder/flac_encoder.c +++ b/src/encoder/flac_encoder.c @@ -354,6 +354,7 @@ const struct encoder_plugin flac_encoder_plugin = { .finish = flac_encoder_finish, .open = flac_encoder_open, .close = flac_encoder_close, + .end = flac_encoder_flush, .flush = flac_encoder_flush, .write = flac_encoder_write, .read = flac_encoder_read, diff --git a/src/encoder/twolame_encoder.c b/src/encoder/twolame_encoder.c index 00ebcc0d7..934b2ab24 100644 --- a/src/encoder/twolame_encoder.c +++ b/src/encoder/twolame_encoder.c @@ -300,6 +300,7 @@ const struct encoder_plugin twolame_encoder_plugin = { .finish = twolame_encoder_finish, .open = twolame_encoder_open, .close = twolame_encoder_close, + .end = twolame_encoder_flush, .flush = twolame_encoder_flush, .write = twolame_encoder_write, .read = twolame_encoder_read, diff --git a/src/encoder/vorbis_encoder.c b/src/encoder/vorbis_encoder.c index 3e9d486b6..fcf2b5135 100644 --- a/src/encoder/vorbis_encoder.c +++ b/src/encoder/vorbis_encoder.c @@ -285,8 +285,6 @@ vorbis_encoder_pre_tag(struct encoder *_encoder, G_GNUC_UNUSED GError **error) vorbis_analysis_init(&encoder->vd, &encoder->vi); vorbis_block_init(&encoder->vd, &encoder->vb); - ogg_stream_reset(&encoder->os); - encoder->flush = true; return true; } @@ -407,6 +405,7 @@ const struct encoder_plugin vorbis_encoder_plugin = { .finish = vorbis_encoder_finish, .open = vorbis_encoder_open, .close = vorbis_encoder_close, + .end = vorbis_encoder_pre_tag, .flush = vorbis_encoder_flush, .pre_tag = vorbis_encoder_pre_tag, .tag = vorbis_encoder_tag, diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h index 95b3da016..33a379115 100644 --- a/src/encoder_plugin.h +++ b/src/encoder_plugin.h @@ -22,6 +22,7 @@ #include <glib.h> +#include <assert.h> #include <stdbool.h> #include <stddef.h> @@ -32,6 +33,10 @@ struct tag; struct encoder { const struct encoder_plugin *plugin; + +#ifndef NDEBUG + bool open, pre_tag, tag, end; +#endif }; struct encoder_plugin { @@ -48,6 +53,8 @@ struct encoder_plugin { void (*close)(struct encoder *encoder); + bool (*end)(struct encoder *encoder, GError **error); + bool (*flush)(struct encoder *encoder, GError **error); bool (*pre_tag)(struct encoder *encoder, GError **error); @@ -73,6 +80,10 @@ encoder_struct_init(struct encoder *encoder, const struct encoder_plugin *plugin) { encoder->plugin = plugin; + +#ifndef NDEBUG + encoder->open = false; +#endif } /** @@ -98,6 +109,8 @@ encoder_init(const struct encoder_plugin *plugin, static inline void encoder_finish(struct encoder *encoder) { + assert(!encoder->open); + encoder->plugin->finish(encoder); } @@ -116,7 +129,14 @@ static inline bool encoder_open(struct encoder *encoder, struct audio_format *audio_format, GError **error) { - return encoder->plugin->open(encoder, audio_format, error); + assert(!encoder->open); + + bool success = encoder->plugin->open(encoder, audio_format, error); +#ifndef NDEBUG + encoder->open = success; + encoder->pre_tag = encoder->tag = encoder->end = false; +#endif + return success; } /** @@ -128,8 +148,43 @@ encoder_open(struct encoder *encoder, struct audio_format *audio_format, static inline void encoder_close(struct encoder *encoder) { + assert(encoder->open); + if (encoder->plugin->close != NULL) encoder->plugin->close(encoder); + +#ifndef NDEBUG + encoder->open = false; +#endif +} + +/** + * Ends the stream: flushes the encoder object, generate an + * end-of-stream marker (if applicable), make everything which might + * currently be buffered available by encoder_read(). + * + * After this function has been called, the encoder may not be usable + * for more data, and only encoder_read() and encoder_close() can be + * called. + * + * @param encoder the encoder + * @param error location to store the error occuring, or NULL to ignore errors. + * @return true on success + */ +static inline bool +encoder_end(struct encoder *encoder, GError **error) +{ + assert(encoder->open); + assert(!encoder->end); + +#ifndef NDEBUG + encoder->end = true; +#endif + + /* this method is optional */ + return encoder->plugin->end != NULL + ? encoder->plugin->end(encoder, error) + : true; } /** @@ -143,6 +198,11 @@ encoder_close(struct encoder *encoder) static inline bool encoder_flush(struct encoder *encoder, GError **error) { + assert(encoder->open); + assert(!encoder->pre_tag); + assert(!encoder->tag); + assert(!encoder->end); + /* this method is optional */ return encoder->plugin->flush != NULL ? encoder->plugin->flush(encoder, error) @@ -162,10 +222,20 @@ encoder_flush(struct encoder *encoder, GError **error) static inline bool encoder_pre_tag(struct encoder *encoder, GError **error) { + assert(encoder->open); + assert(!encoder->pre_tag); + assert(!encoder->tag); + assert(!encoder->end); + /* this method is optional */ - return encoder->plugin->pre_tag != NULL + bool success = encoder->plugin->pre_tag != NULL ? encoder->plugin->pre_tag(encoder, error) : true; + +#ifndef NDEBUG + encoder->pre_tag = success; +#endif + return success; } /** @@ -182,6 +252,15 @@ encoder_pre_tag(struct encoder *encoder, GError **error) static inline bool encoder_tag(struct encoder *encoder, const struct tag *tag, GError **error) { + assert(encoder->open); + assert(!encoder->pre_tag); + assert(encoder->tag); + assert(!encoder->end); + +#ifndef NDEBUG + encoder->tag = false; +#endif + /* this method is optional */ return encoder->plugin->tag != NULL ? encoder->plugin->tag(encoder, tag, error) @@ -201,6 +280,11 @@ static inline bool encoder_write(struct encoder *encoder, const void *data, size_t length, GError **error) { + assert(encoder->open); + assert(!encoder->pre_tag); + assert(!encoder->tag); + assert(!encoder->end); + return encoder->plugin->write(encoder, data, length, error); } @@ -215,6 +299,16 @@ encoder_write(struct encoder *encoder, const void *data, size_t length, static inline size_t encoder_read(struct encoder *encoder, void *dest, size_t length) { + assert(encoder->open); + assert(!encoder->pre_tag || !encoder->tag); + +#ifndef NDEBUG + if (encoder->pre_tag) { + encoder->pre_tag = false; + encoder->tag = true; + } +#endif + return encoder->plugin->read(encoder, dest, length); } diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c index cd769088b..a24cb8557 100644 --- a/src/output/jack_output_plugin.c +++ b/src/output/jack_output_plugin.c @@ -146,6 +146,13 @@ mpd_jack_process(jack_nframes_t nframes, void *arg) for (unsigned i = 0; i < jd->audio_format.channels; ++i) { out = jack_port_get_buffer(jd->ports[i], nframes); + if (out == NULL) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is + invoked anyway, but unable to get a + buffer */ + continue; + jack_ringbuffer_read(jd->ringbuffer[i], (char *)out, available * jack_sample_size); @@ -159,6 +166,12 @@ mpd_jack_process(jack_nframes_t nframes, void *arg) for (unsigned i = jd->audio_format.channels; i < jd->num_source_ports; ++i) { out = jack_port_get_buffer(jd->ports[i], nframes); + if (out == NULL) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is + invoked anyway, but unable to get a + buffer */ + continue; for (jack_nframes_t f = 0; f < nframes; ++f) out[f] = 0.0; @@ -572,6 +585,9 @@ mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format, jd->pause = false; + if (jd->client != NULL && jd->shutdown) + mpd_jack_disconnect(jd); + if (jd->client == NULL && !mpd_jack_connect(jd, error_r)) return false; diff --git a/src/output/osx_output_plugin.c b/src/output/osx_output_plugin.c index 3f1af821b..fbba81749 100644 --- a/src/output/osx_output_plugin.c +++ b/src/output/osx_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "osx_output_plugin.h" #include "output_api.h" +#include "fifo_buffer.h" #include <glib.h> #include <CoreAudio/AudioHardware.h> @@ -40,10 +41,8 @@ struct osx_output { AudioUnit au; GMutex *mutex; GCond *condition; - char *buffer; - size_t buffer_size; - size_t pos; - size_t len; + + struct fifo_buffer *buffer; }; /** @@ -96,11 +95,6 @@ osx_output_init(const struct config_param *param, GError **error_r) oo->mutex = g_mutex_new(); oo->condition = g_cond_new(); - oo->pos = 0; - oo->len = 0; - oo->buffer = NULL; - oo->buffer_size = 0; - return &oo->base; } @@ -109,7 +103,6 @@ osx_output_finish(struct audio_output *ao) { struct osx_output *od = (struct osx_output *)ao; - g_free(od->buffer); g_mutex_free(od->mutex); g_cond_free(od->condition); g_free(od); @@ -215,37 +208,29 @@ osx_render(void *vdata, struct osx_output *od = (struct osx_output *) vdata; AudioBuffer *buffer = &buffer_list->mBuffers[0]; size_t buffer_size = buffer->mDataByteSize; - size_t bytes_to_copy; - size_t trailer_length; - size_t dest_pos = 0; - g_mutex_lock(od->mutex); + assert(od->buffer != NULL); - bytes_to_copy = MIN(od->len, buffer_size); - od->len -= bytes_to_copy; + g_mutex_lock(od->mutex); - trailer_length = od->buffer_size - od->pos; - if (bytes_to_copy > trailer_length) { - memcpy((unsigned char*)buffer->mData + dest_pos, - od->buffer + od->pos, trailer_length); - od->pos = 0; - dest_pos += trailer_length; - bytes_to_copy -= trailer_length; - } + size_t nbytes; + const void *src = fifo_buffer_read(od->buffer, &nbytes); - memcpy((unsigned char*)buffer->mData + dest_pos, - od->buffer + od->pos, bytes_to_copy); - od->pos += bytes_to_copy; + if (src != NULL) { + if (nbytes > buffer_size) + nbytes = buffer_size; - if (od->pos >= od->buffer_size) - od->pos = 0; + memcpy(buffer->mData, src, nbytes); + fifo_buffer_consume(od->buffer, nbytes); + } else + nbytes = 0; g_cond_signal(od->condition); g_mutex_unlock(od->mutex); - if (bytes_to_copy < buffer_size) - memset((unsigned char*)buffer->mData + bytes_to_copy, 0, - buffer_size - bytes_to_copy); + if (nbytes < buffer_size) + memset((unsigned char*)buffer->mData + nbytes, 0, + buffer_size - nbytes); return 0; } @@ -315,7 +300,7 @@ osx_output_cancel(struct audio_output *ao) struct osx_output *od = (struct osx_output *)ao; g_mutex_lock(od->mutex); - od->len = 0; + fifo_buffer_clear(od->buffer); g_mutex_unlock(od->mutex); } @@ -326,6 +311,8 @@ osx_output_close(struct audio_output *ao) AudioOutputUnitStop(od->au); AudioUnitUninitialize(od->au); + + fifo_buffer_free(od->buffer); } static bool @@ -387,12 +374,8 @@ osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GErr } /* create a buffer of 1s */ - od->buffer_size = (audio_format->sample_rate) * - audio_format_frame_size(audio_format); - od->buffer = g_realloc(od->buffer, od->buffer_size); - - od->pos = 0; - od->len = 0; + od->buffer = fifo_buffer_new(audio_format->sample_rate * + audio_format_frame_size(audio_format)); status = AudioOutputUnitStart(od->au); if (status != 0) { @@ -411,33 +394,30 @@ osx_output_play(struct audio_output *ao, const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { struct osx_output *od = (struct osx_output *)ao; - size_t start, nbytes; g_mutex_lock(od->mutex); - while (od->len >= od->buffer_size) - /* wait for some free space in the buffer */ - g_cond_wait(od->condition, od->mutex); + void *dest; + size_t max_length; - start = od->pos + od->len; - if (start >= od->buffer_size) - start -= od->buffer_size; - - nbytes = start < od->pos - ? od->pos - start - : od->buffer_size - start; + while (true) { + dest = fifo_buffer_write(od->buffer, &max_length); + if (dest != NULL) + break; - assert(nbytes > 0); + /* wait for some free space in the buffer */ + g_cond_wait(od->condition, od->mutex); + } - if (nbytes > size) - nbytes = size; + if (size > max_length) + size = max_length; - memcpy(od->buffer + start, chunk, nbytes); - od->len += nbytes; + memcpy(dest, chunk, size); + fifo_buffer_append(od->buffer, size); g_mutex_unlock(od->mutex); - return nbytes; + return size; } const struct audio_output_plugin osx_output_plugin = { diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c index 00adc5d19..ea299468b 100644 --- a/src/output/recorder_output_plugin.c +++ b/src/output/recorder_output_plugin.c @@ -202,7 +202,7 @@ recorder_output_close(struct audio_output *ao) /* flush the encoder and write the rest to the file */ - if (encoder_flush(recorder->encoder, NULL)) + if (encoder_end(recorder->encoder, NULL)) recorder_output_encoder_to_file(recorder, NULL); /* now really close everything */ diff --git a/src/output/shout_output_plugin.c b/src/output/shout_output_plugin.c index 35356d659..7867ae63c 100644 --- a/src/output/shout_output_plugin.c +++ b/src/output/shout_output_plugin.c @@ -376,7 +376,7 @@ static void close_shout_conn(struct shout_data * sd) sd->buf.len = 0; if (sd->encoder != NULL) { - if (encoder_flush(sd->encoder, NULL)) + if (encoder_end(sd->encoder, NULL)) write_page(sd, NULL); encoder_close(sd->encoder); diff --git a/src/update_remove.c b/src/update_remove.c index ef697e44e..f443f5eb2 100644 --- a/src/update_remove.c +++ b/src/update_remove.c @@ -50,7 +50,7 @@ song_remove_event(void) assert(removed_song != NULL); uri = song_get_uri(removed_song); - g_debug("removing: %s", uri); + g_message("removing %s", uri); g_free(uri); #ifdef ENABLE_SQLITE diff --git a/src/update_walk.c b/src/update_walk.c index 003807da6..9ca9115bd 100644 --- a/src/update_walk.c +++ b/src/update_walk.c @@ -676,6 +676,9 @@ directory_make_child_checked(struct directory *parent, const char *name_utf8) inodeFoundInParent(parent, st.st_ino, st.st_dev)) return NULL; + if (skip_symlink(parent, name_utf8)) + return NULL; + /* if we're adding directory paths, make sure to delete filenames with potentially the same name */ db_lock(); @@ -728,7 +731,8 @@ updatePath(const char *path) name = g_path_get_basename(path); - if (stat_directory_child(parent, name, &st) == 0) + if (!skip_symlink(parent, name) && + stat_directory_child(parent, name, &st) == 0) updateInDirectory(parent, name, &st); else modified |= delete_name_in(parent, name); @@ -34,13 +34,13 @@ bool uri_has_scheme(const char *uri) const char * uri_get_suffix(const char *uri) { - const char *suffix = strrchr(g_basename(uri), '.'); + const char *suffix = strrchr(uri, '.'); if (suffix == NULL) return NULL; ++suffix; - if (strchr(suffix, '/') != NULL) + if (strpbrk(suffix, "/\\") != NULL) return NULL; return suffix; diff --git a/test/run_encoder.c b/test/run_encoder.c index 3b3ec7240..6a4108620 100644 --- a/test/run_encoder.c +++ b/test/run_encoder.c @@ -121,7 +121,7 @@ int main(int argc, char **argv) encoder_to_stdout(encoder); } - ret = encoder_flush(encoder, &error); + ret = encoder_end(encoder, &error); if (!ret) { g_printerr("encoder_flush() failed: %s\n", error->message); diff --git a/test/test_vorbis_encoder.c b/test/test_vorbis_encoder.c new file mode 100644 index 000000000..048d26f0a --- /dev/null +++ b/test/test_vorbis_encoder.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "encoder_list.h" +#include "encoder_plugin.h" +#include "audio_format.h" +#include "conf.h" +#include "stdbin.h" +#include "tag.h" + +#include <glib.h> + +#include <stddef.h> +#include <unistd.h> + +static uint8_t zero[256]; + +static void +encoder_to_stdout(struct encoder *encoder) +{ + size_t length; + static char buffer[32768]; + + while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0) { + G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length); + } +} + +int +main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) +{ + G_GNUC_UNUSED bool success; + + /* create the encoder */ + + const struct encoder_plugin *plugin = encoder_plugin_get("vorbis"); + assert(plugin != NULL); + + struct config_param *param = config_new_param(NULL, -1); + config_add_block_param(param, "quality", "5.0", -1); + + struct encoder *encoder = encoder_init(plugin, param, NULL); + assert(encoder != NULL); + + /* open the encoder */ + + struct audio_format audio_format; + + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); + success = encoder_open(encoder, &audio_format, NULL); + assert(success); + + /* write a block of data */ + + success = encoder_write(encoder, zero, sizeof(zero), NULL); + assert(success); + + encoder_to_stdout(encoder); + + /* write a tag */ + + success = encoder_pre_tag(encoder, NULL); + assert(success); + + encoder_to_stdout(encoder); + + struct tag *tag = tag_new(); + tag_add_item(tag, TAG_ARTIST, "Foo"); + tag_add_item(tag, TAG_TITLE, "Bar"); + + success = encoder_tag(encoder, tag, NULL); + assert(success); + + tag_free(tag); + + encoder_to_stdout(encoder); + + /* write another block of data */ + + success = encoder_write(encoder, zero, sizeof(zero), NULL); + assert(success); + + /* finish */ + + success = encoder_end(encoder, NULL); + assert(success); + + encoder_to_stdout(encoder); + + encoder_close(encoder); + encoder_finish(encoder); +} |