Merge branch 'v0.16.x'
Conflicts: src/player_thread.c src/playlist_control.c
This commit is contained in:
commit
0ea4c970d7
7
NEWS
7
NEWS
@ -21,9 +21,16 @@ ver 0.17 (2011/??/??)
|
|||||||
|
|
||||||
ver 0.16.4 (2011/??/??)
|
ver 0.16.4 (2011/??/??)
|
||||||
* fix memory leaks
|
* fix memory leaks
|
||||||
|
* don't resume playback when seeking to another song while paused
|
||||||
|
* apply follow_inside_symlinks to absolute symlinks
|
||||||
* decoder:
|
* decoder:
|
||||||
- ffmpeg: workaround for semantic API change in recent ffmpeg versions
|
- ffmpeg: workaround for semantic API change in recent ffmpeg versions
|
||||||
- flac: validate the sample rate when scanning the tag
|
- flac: validate the sample rate when scanning the tag
|
||||||
|
- wavpack: obey all decoder commands, stop at CUE track border
|
||||||
|
* encoder:
|
||||||
|
- vorbis: don't send end-of-stream on flush
|
||||||
|
* output:
|
||||||
|
- alsa: fix SIGFPE when alsa announces a period size of 0
|
||||||
|
|
||||||
|
|
||||||
ver 0.16.3 (2011/06/04)
|
ver 0.16.3 (2011/06/04)
|
||||||
|
@ -34,9 +34,6 @@
|
|||||||
#undef G_LOG_DOMAIN
|
#undef G_LOG_DOMAIN
|
||||||
#define G_LOG_DOMAIN "wavpack"
|
#define G_LOG_DOMAIN "wavpack"
|
||||||
|
|
||||||
/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
|
|
||||||
#define CHUNK_SIZE 1020
|
|
||||||
|
|
||||||
#define ERRORLEN 80
|
#define ERRORLEN 80
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
@ -162,8 +159,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
|
|||||||
enum sample_format sample_format;
|
enum sample_format sample_format;
|
||||||
struct audio_format audio_format;
|
struct audio_format audio_format;
|
||||||
format_samples_t format_samples;
|
format_samples_t format_samples;
|
||||||
char chunk[CHUNK_SIZE];
|
|
||||||
int samples_requested, samples_got;
|
|
||||||
float total_time;
|
float total_time;
|
||||||
int bytes_per_sample, output_sample_size;
|
int bytes_per_sample, output_sample_size;
|
||||||
|
|
||||||
@ -193,12 +188,15 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
|
|||||||
output_sample_size = audio_format_frame_size(&audio_format);
|
output_sample_size = audio_format_frame_size(&audio_format);
|
||||||
|
|
||||||
/* wavpack gives us all kind of samples in a 32-bit space */
|
/* wavpack gives us all kind of samples in a 32-bit space */
|
||||||
samples_requested = sizeof(chunk) / (4 * audio_format.channels);
|
int32_t chunk[1024];
|
||||||
|
const uint32_t samples_requested = G_N_ELEMENTS(chunk) /
|
||||||
|
audio_format.channels;
|
||||||
|
|
||||||
decoder_initialized(decoder, &audio_format, can_seek, total_time);
|
decoder_initialized(decoder, &audio_format, can_seek, total_time);
|
||||||
|
|
||||||
do {
|
enum decoder_command cmd = decoder_get_command(decoder);
|
||||||
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
|
while (cmd != DECODE_COMMAND_STOP) {
|
||||||
|
if (cmd == DECODE_COMMAND_SEEK) {
|
||||||
if (can_seek) {
|
if (can_seek) {
|
||||||
unsigned where = decoder_seek_where(decoder) *
|
unsigned where = decoder_seek_where(decoder) *
|
||||||
audio_format.sample_rate;
|
audio_format.sample_rate;
|
||||||
@ -213,29 +211,20 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decoder_get_command(decoder) == DECODE_COMMAND_STOP) {
|
uint32_t samples_got = WavpackUnpackSamples(wpc, chunk,
|
||||||
|
samples_requested);
|
||||||
|
if (samples_got == 0)
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
samples_got = WavpackUnpackSamples(
|
int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 +
|
||||||
wpc, (int32_t *)chunk, samples_requested
|
0.5);
|
||||||
);
|
format_samples(bytes_per_sample, chunk,
|
||||||
if (samples_got > 0) {
|
samples_got * audio_format.channels);
|
||||||
int bitrate = (int)(WavpackGetInstantBitrate(wpc) /
|
|
||||||
1000 + 0.5);
|
|
||||||
|
|
||||||
format_samples(
|
cmd = decoder_data(decoder, NULL, chunk,
|
||||||
bytes_per_sample, chunk,
|
samples_got * output_sample_size,
|
||||||
samples_got * audio_format.channels
|
bitrate);
|
||||||
);
|
}
|
||||||
|
|
||||||
decoder_data(
|
|
||||||
decoder, NULL, chunk,
|
|
||||||
samples_got * output_sample_size,
|
|
||||||
bitrate
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} while (samples_got > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -266,6 +266,15 @@ vorbis_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
|
|||||||
{
|
{
|
||||||
struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
|
struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
|
||||||
|
|
||||||
|
encoder->flush = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
vorbis_encoder_pre_tag(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
|
||||||
|
{
|
||||||
|
struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
|
||||||
|
|
||||||
vorbis_analysis_wrote(&encoder->vd, 0);
|
vorbis_analysis_wrote(&encoder->vd, 0);
|
||||||
vorbis_encoder_blockout(encoder);
|
vorbis_encoder_blockout(encoder);
|
||||||
|
|
||||||
@ -366,6 +375,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
|
|||||||
if (ret == 0 && encoder->flush) {
|
if (ret == 0 && encoder->flush) {
|
||||||
encoder->flush = false;
|
encoder->flush = false;
|
||||||
ret = ogg_stream_flush(&encoder->os, &page);
|
ret = ogg_stream_flush(&encoder->os, &page);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
@ -398,6 +408,7 @@ const struct encoder_plugin vorbis_encoder_plugin = {
|
|||||||
.open = vorbis_encoder_open,
|
.open = vorbis_encoder_open,
|
||||||
.close = vorbis_encoder_close,
|
.close = vorbis_encoder_close,
|
||||||
.flush = vorbis_encoder_flush,
|
.flush = vorbis_encoder_flush,
|
||||||
|
.pre_tag = vorbis_encoder_pre_tag,
|
||||||
.tag = vorbis_encoder_tag,
|
.tag = vorbis_encoder_tag,
|
||||||
.write = vorbis_encoder_write,
|
.write = vorbis_encoder_write,
|
||||||
.read = vorbis_encoder_read,
|
.read = vorbis_encoder_read,
|
||||||
|
@ -50,6 +50,8 @@ struct encoder_plugin {
|
|||||||
|
|
||||||
bool (*flush)(struct encoder *encoder, GError **error);
|
bool (*flush)(struct encoder *encoder, GError **error);
|
||||||
|
|
||||||
|
bool (*pre_tag)(struct encoder *encoder, GError **error);
|
||||||
|
|
||||||
bool (*tag)(struct encoder *encoder, const struct tag *tag,
|
bool (*tag)(struct encoder *encoder, const struct tag *tag,
|
||||||
GError **error);
|
GError **error);
|
||||||
|
|
||||||
@ -147,9 +149,31 @@ encoder_flush(struct encoder *encoder, GError **error)
|
|||||||
: true;
|
: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare for sending a tag to the encoder. This is used by some
|
||||||
|
* encoders to flush the previous sub-stream, in preparation to begin
|
||||||
|
* a new one.
|
||||||
|
*
|
||||||
|
* @param encoder the encoder
|
||||||
|
* @param tag the tag object
|
||||||
|
* @param error location to store the error occuring, or NULL to ignore errors.
|
||||||
|
* @return true on success
|
||||||
|
*/
|
||||||
|
static inline bool
|
||||||
|
encoder_pre_tag(struct encoder *encoder, GError **error)
|
||||||
|
{
|
||||||
|
/* this method is optional */
|
||||||
|
return encoder->plugin->pre_tag != NULL
|
||||||
|
? encoder->plugin->pre_tag(encoder, error)
|
||||||
|
: true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a tag to the encoder.
|
* Sends a tag to the encoder.
|
||||||
*
|
*
|
||||||
|
* Instructions: call encoder_pre_tag(); then obtain flushed data with
|
||||||
|
* encoder_read(); finally call encoder_tag().
|
||||||
|
*
|
||||||
* @param encoder the encoder
|
* @param encoder the encoder
|
||||||
* @param tag the tag object
|
* @param tag the tag object
|
||||||
* @param error location to store the error occurring, or NULL to ignore errors.
|
* @param error location to store the error occurring, or NULL to ignore errors.
|
||||||
|
@ -508,6 +508,14 @@ configure_hw:
|
|||||||
g_debug("buffer_size=%u period_size=%u",
|
g_debug("buffer_size=%u period_size=%u",
|
||||||
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
|
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
|
||||||
|
|
||||||
|
if (alsa_period_size == 0)
|
||||||
|
/* this works around a SIGFPE bug that occurred when
|
||||||
|
an ALSA driver indicated period_size==0; this
|
||||||
|
caused a division by zero in alsa_play(). By using
|
||||||
|
the fallback "1", we make sure that this won't
|
||||||
|
happen again. */
|
||||||
|
alsa_period_size = 1;
|
||||||
|
|
||||||
ad->period_frames = alsa_period_size;
|
ad->period_frames = alsa_period_size;
|
||||||
ad->period_position = 0;
|
ad->period_position = 0;
|
||||||
|
|
||||||
|
@ -493,7 +493,8 @@ httpd_output_pause(void *data)
|
|||||||
|
|
||||||
if (has_clients) {
|
if (has_clients) {
|
||||||
static const char silence[1020];
|
static const char silence[1020];
|
||||||
return httpd_output_play(data, silence, sizeof(silence), NULL);
|
return httpd_output_play(data, silence, sizeof(silence),
|
||||||
|
NULL) > 0;
|
||||||
} else {
|
} else {
|
||||||
g_usleep(100000);
|
g_usleep(100000);
|
||||||
return true;
|
return true;
|
||||||
@ -522,7 +523,7 @@ httpd_output_tag(void *data, const struct tag *tag)
|
|||||||
|
|
||||||
/* flush the current stream, and end it */
|
/* flush the current stream, and end it */
|
||||||
|
|
||||||
encoder_flush(httpd->encoder, NULL);
|
encoder_pre_tag(httpd->encoder, NULL);
|
||||||
httpd_output_encoder_to_clients(httpd);
|
httpd_output_encoder_to_clients(httpd);
|
||||||
|
|
||||||
/* send the tag to the encoder - which starts a new
|
/* send the tag to the encoder - which starts a new
|
||||||
|
@ -518,7 +518,7 @@ static void my_shout_set_tag(void *data,
|
|||||||
if (sd->encoder->plugin->tag != NULL) {
|
if (sd->encoder->plugin->tag != NULL) {
|
||||||
/* encoder plugin supports stream tags */
|
/* encoder plugin supports stream tags */
|
||||||
|
|
||||||
ret = encoder_flush(sd->encoder, &error);
|
ret = encoder_pre_tag(sd->encoder, &error);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
g_warning("%s", error->message);
|
g_warning("%s", error->message);
|
||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
|
@ -641,8 +641,13 @@ static gpointer audio_output_task(gpointer arg)
|
|||||||
|
|
||||||
case AO_COMMAND_CANCEL:
|
case AO_COMMAND_CANCEL:
|
||||||
ao->chunk = NULL;
|
ao->chunk = NULL;
|
||||||
if (ao->open)
|
|
||||||
|
if (ao->open) {
|
||||||
|
g_mutex_unlock(ao->mutex);
|
||||||
ao_plugin_cancel(ao->plugin, ao->data);
|
ao_plugin_cancel(ao->plugin, ao->data);
|
||||||
|
g_mutex_lock(ao->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
ao_command_finished(ao);
|
ao_command_finished(ao);
|
||||||
|
|
||||||
/* the player thread will now clear our music
|
/* the player thread will now clear our music
|
||||||
|
@ -187,5 +187,8 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk)
|
|||||||
unsigned
|
unsigned
|
||||||
music_pipe_size(const struct music_pipe *mp)
|
music_pipe_size(const struct music_pipe *mp)
|
||||||
{
|
{
|
||||||
return mp->size;
|
g_mutex_lock(mp->mutex);
|
||||||
|
unsigned size = mp->size;
|
||||||
|
g_mutex_unlock(mp->mutex);
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#ifndef MPD_PIPE_H
|
#ifndef MPD_PIPE_H
|
||||||
#define MPD_PIPE_H
|
#define MPD_PIPE_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
@ -38,6 +39,7 @@ struct music_pipe;
|
|||||||
/**
|
/**
|
||||||
* Creates a new #music_pipe object. It is empty.
|
* Creates a new #music_pipe object. It is empty.
|
||||||
*/
|
*/
|
||||||
|
G_GNUC_MALLOC
|
||||||
struct music_pipe *
|
struct music_pipe *
|
||||||
music_pipe_new(void);
|
music_pipe_new(void);
|
||||||
|
|
||||||
@ -70,6 +72,7 @@ music_pipe_contains(const struct music_pipe *mp,
|
|||||||
* Returns the first #music_chunk from the pipe. Returns NULL if the
|
* Returns the first #music_chunk from the pipe. Returns NULL if the
|
||||||
* pipe is empty.
|
* pipe is empty.
|
||||||
*/
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
const struct music_chunk *
|
const struct music_chunk *
|
||||||
music_pipe_peek(const struct music_pipe *mp);
|
music_pipe_peek(const struct music_pipe *mp);
|
||||||
|
|
||||||
@ -96,9 +99,11 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk);
|
|||||||
/**
|
/**
|
||||||
* Returns the number of chunks currently in this pipe.
|
* Returns the number of chunks currently in this pipe.
|
||||||
*/
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
unsigned
|
unsigned
|
||||||
music_pipe_size(const struct music_pipe *mp);
|
music_pipe_size(const struct music_pipe *mp);
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
static inline bool
|
static inline bool
|
||||||
music_pipe_empty(const struct music_pipe *mp)
|
music_pipe_empty(const struct music_pipe *mp)
|
||||||
{
|
{
|
||||||
|
@ -628,7 +628,9 @@ play_chunk(struct player_control *pc,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
player_lock(pc);
|
||||||
pc->bit_rate = chunk->bit_rate;
|
pc->bit_rate = chunk->bit_rate;
|
||||||
|
player_unlock(pc);
|
||||||
|
|
||||||
/* send the chunk to the audio outputs */
|
/* send the chunk to the audio outputs */
|
||||||
|
|
||||||
|
@ -230,10 +230,12 @@ playlist_seek_song(struct playlist *playlist, struct player_control *pc,
|
|||||||
playlist->error_count = 0;
|
playlist->error_count = 0;
|
||||||
|
|
||||||
if (!playlist->playing || (unsigned)playlist->current != i) {
|
if (!playlist->playing || (unsigned)playlist->current != i) {
|
||||||
/* seeking is not within the current song - first
|
/* seeking is not within the current song - prepare
|
||||||
start playing the new song */
|
song change */
|
||||||
|
|
||||||
|
playlist->playing = true;
|
||||||
|
playlist->current = i;
|
||||||
|
|
||||||
playlist_play_order(playlist, pc, i);
|
|
||||||
queued = NULL;
|
queued = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -714,8 +714,14 @@ skip_symlink(const struct directory *directory, const char *utf8_name)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer[0] == '/')
|
if (g_path_is_absolute(buffer)) {
|
||||||
return !follow_outside_symlinks;
|
/* if the symlink points to an absolute path, see if
|
||||||
|
that path is inside the music directory */
|
||||||
|
const char *relative = map_to_relative_path(buffer);
|
||||||
|
return relative > buffer
|
||||||
|
? !follow_inside_symlinks
|
||||||
|
: !follow_outside_symlinks;
|
||||||
|
}
|
||||||
|
|
||||||
p = buffer;
|
p = buffer;
|
||||||
while (*p == '.') {
|
while (*p == '.') {
|
||||||
|
Loading…
Reference in New Issue
Block a user