mpd/src/decoder_api.c
Avuton Olrich 0aee49bdf8 all: Update copyright header.
This updates the copyright header to all be the same, which is
pretty much an update of where to mail request for a copy of the GPL
and the years of the MPD project. This also puts all committers under
'The Music Player Project' umbrella. These entries should go
individually in the AUTHORS file, for consistancy.
2009-03-13 11:51:55 -07:00

346 lines
8.3 KiB
C

/*
* Copyright (C) 2003-2009 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 "decoder_api.h"
#include "decoder_internal.h"
#include "decoder_control.h"
#include "player_control.h"
#include "audio.h"
#include "song.h"
#include "buffer.h"
#include "normalize.h"
#include "pipe.h"
#include "chunk.h"
#include <glib.h>
#include <assert.h>
#include <stdlib.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "decoder"
void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder,
const struct audio_format *audio_format,
bool seekable, float total_time)
{
assert(dc.state == DECODE_STATE_START);
assert(decoder != NULL);
assert(decoder->stream_tag == NULL);
assert(decoder->decoder_tag == NULL);
assert(!decoder->seeking);
assert(audio_format != NULL);
assert(audio_format_defined(audio_format));
assert(audio_format_valid(audio_format));
dc.in_audio_format = *audio_format;
getOutputAudioFormat(audio_format, &dc.out_audio_format);
dc.seekable = seekable;
dc.total_time = total_time;
dc.state = DECODE_STATE_DECODE;
notify_signal(&pc.notify);
g_debug("audio_format=%u:%u:%u, seekable=%s",
dc.in_audio_format.sample_rate, dc.in_audio_format.bits,
dc.in_audio_format.channels,
seekable ? "true" : "false");
if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format))
g_debug("converting to %u:%u:%u",
dc.out_audio_format.sample_rate,
dc.out_audio_format.bits,
dc.out_audio_format.channels);
}
char *decoder_get_uri(G_GNUC_UNUSED struct decoder *decoder)
{
return song_get_uri(dc.current_song);
}
enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder)
{
return dc.command;
}
void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
{
assert(dc.command != DECODE_COMMAND_NONE);
assert(dc.command != DECODE_COMMAND_SEEK ||
dc.seek_error || decoder->seeking);
if (dc.command == DECODE_COMMAND_SEEK) {
/* delete frames from the old song position */
if (decoder->chunk != NULL) {
music_buffer_return(dc.buffer, decoder->chunk);
decoder->chunk = NULL;
}
music_pipe_clear(dc.pipe, dc.buffer);
}
dc.command = DECODE_COMMAND_NONE;
notify_signal(&pc.notify);
}
double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder)
{
assert(dc.command == DECODE_COMMAND_SEEK);
decoder->seeking = true;
return dc.seek_where;
}
void decoder_seek_error(struct decoder * decoder)
{
assert(dc.command == DECODE_COMMAND_SEEK);
dc.seek_error = true;
decoder_command_finished(decoder);
}
size_t decoder_read(struct decoder *decoder,
struct input_stream *is,
void *buffer, size_t length)
{
size_t nbytes;
assert(decoder == NULL ||
dc.state == DECODE_STATE_START ||
dc.state == DECODE_STATE_DECODE);
assert(is != NULL);
assert(buffer != NULL);
if (length == 0)
return 0;
while (true) {
/* XXX don't allow decoder==NULL */
if (decoder != NULL &&
/* ignore the SEEK command during initialization,
the plugin should handle that after it has
initialized successfully */
(dc.command != DECODE_COMMAND_SEEK ||
(dc.state != DECODE_STATE_START && !decoder->seeking)) &&
dc.command != DECODE_COMMAND_NONE)
return 0;
nbytes = input_stream_read(is, buffer, length);
if (nbytes > 0 || input_stream_eof(is))
return nbytes;
/* sleep for a fraction of a second! */
/* XXX don't sleep, wait for an event instead */
g_usleep(10000);
}
}
/**
* Sends a #tag as-is to the music pipe. Flushes the current chunk
* (decoder.chunk) if there is one.
*/
static enum decoder_command
do_send_tag(struct decoder *decoder, struct input_stream *is,
const struct tag *tag)
{
struct music_chunk *chunk;
if (decoder->chunk != NULL) {
/* there is a partial chunk - flush it, we want the
tag in a new chunk */
decoder_flush_chunk(decoder);
notify_signal(&pc.notify);
}
assert(decoder->chunk == NULL);
chunk = decoder_get_chunk(decoder, is);
if (chunk == NULL) {
assert(dc.command != DECODE_COMMAND_NONE);
return dc.command;
}
chunk->tag = tag_dup(tag);
return DECODE_COMMAND_NONE;
}
static bool
update_stream_tag(struct decoder *decoder, struct input_stream *is)
{
struct tag *tag;
if (is == NULL)
return false;
tag = input_stream_tag(is);
if (tag == NULL)
return false;
if (decoder->stream_tag != NULL)
tag_free(decoder->stream_tag);
decoder->stream_tag = tag;
return true;
}
enum decoder_command
decoder_data(struct decoder *decoder,
struct input_stream *is,
const void *_data, size_t length,
float data_time, uint16_t bitRate,
struct replay_gain_info *replay_gain_info)
{
const char *data = _data;
assert(dc.state == DECODE_STATE_DECODE);
assert(length % audio_format_frame_size(&dc.in_audio_format) == 0);
if (dc.command == DECODE_COMMAND_STOP ||
dc.command == DECODE_COMMAND_SEEK ||
length == 0)
return dc.command;
/* send stream tags */
if (update_stream_tag(decoder, is)) {
enum decoder_command cmd;
if (decoder->decoder_tag != NULL) {
/* merge with tag from decoder plugin */
struct tag *tag;
tag = tag_merge(decoder->stream_tag,
decoder->decoder_tag);
cmd = do_send_tag(decoder, is, tag);
tag_free(tag);
} else
/* send only the stream tag */
cmd = do_send_tag(decoder, is, decoder->stream_tag);
if (cmd != DECODE_COMMAND_NONE)
return cmd;
}
if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format)) {
data = pcm_convert(&decoder->conv_state,
&dc.in_audio_format, data, length,
&dc.out_audio_format, &length);
/* under certain circumstances, pcm_convert() may
return an empty buffer - this condition should be
investigated further, but for now, do this check as
a workaround: */
if (data == NULL)
return DECODE_COMMAND_NONE;
}
while (length > 0) {
struct music_chunk *chunk;
char *dest;
size_t nbytes;
bool full;
chunk = decoder_get_chunk(decoder, is);
if (chunk == NULL) {
assert(dc.command != DECODE_COMMAND_NONE);
return dc.command;
}
dest = music_chunk_write(chunk, &dc.out_audio_format,
data_time, bitRate, &nbytes);
if (dest == NULL) {
/* the chunk is full, flush it */
decoder_flush_chunk(decoder);
notify_signal(&pc.notify);
continue;
}
assert(nbytes > 0);
if (nbytes > length)
nbytes = length;
/* copy the buffer */
memcpy(dest, data, nbytes);
/* apply replay gain or normalization */
if (replay_gain_info != NULL &&
replay_gain_mode != REPLAY_GAIN_OFF)
replay_gain_apply(replay_gain_info, dest, nbytes,
&dc.out_audio_format);
else if (normalizationEnabled)
normalizeData(dest, nbytes, &dc.out_audio_format);
/* expand the music pipe chunk */
full = music_chunk_expand(chunk, &dc.out_audio_format, nbytes);
if (full) {
/* the chunk is full, flush it */
decoder_flush_chunk(decoder);
notify_signal(&pc.notify);
}
data += nbytes;
length -= nbytes;
}
return DECODE_COMMAND_NONE;
}
enum decoder_command
decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
const struct tag *tag)
{
enum decoder_command cmd;
assert(dc.state == DECODE_STATE_DECODE);
assert(tag != NULL);
/* save the tag */
if (decoder->decoder_tag != NULL)
tag_free(decoder->decoder_tag);
decoder->decoder_tag = tag_dup(tag);
/* check for a new stream tag */
update_stream_tag(decoder, is);
/* send tag to music pipe */
if (decoder->stream_tag != NULL) {
/* merge with tag from input stream */
struct tag *merged;
merged = tag_merge(decoder->stream_tag, decoder->decoder_tag);
cmd = do_send_tag(decoder, is, merged);
tag_free(merged);
} else
/* send only the decoder tag */
cmd = do_send_tag(decoder, is, tag);
return cmd;
}