view src/flacng/tools.c @ 2991:2cdf6400594c

flacng: enable replaygain
author Teemu Torma <teemu@torma.org>
date Tue, 10 Feb 2009 23:56:41 +0100
parents f1b6f1b2cdb3
children 3b200cf6d1b7
line wrap: on
line source

/*
 *  A FLAC decoder plugin for the Audacious Media Player
 *  Copyright (C) 2005 Ralf Ertzinger
 *
 *  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 <string.h>
#include <strings.h>
#include "tools.h"
#include "debug.h"

/* === */

callback_info* init_callback_info(gchar* name) {

    callback_info* info;

    _ENTER;

    if (NULL == name) {
        _ERROR("Can not allocate callback structure without a name");
        _LEAVE NULL;
    }

    if (NULL == (info = malloc(sizeof(callback_info)))) {
        _ERROR("Could not allocate memory for callback structure!");
        _LEAVE NULL;
    }

    if (NULL == (info->output_buffer = malloc(BUFFER_SIZE_BYTE))) {
        _ERROR("Could not allocate memory for output buffer!");
        _LEAVE NULL;
    }

    /*
     * We need to set this manually to NULL because
     * reset_info will try to close the file pointed to
     * by input_stream if it is non-NULL.
     * Same for info->comment.x
     */
    info->name = name;
    info->input_stream = NULL;
    info->comment.artist = NULL;
    info->comment.album = NULL;
    info->comment.title = NULL;
    info->comment.tracknumber = NULL;
    info->comment.genre = NULL;
    info->comment.date = NULL;
    info->replaygain.ref_loud = NULL;
    info->replaygain.track_gain = NULL;
    info->replaygain.track_peak = NULL;
    info->replaygain.album_gain = NULL;
    info->replaygain.album_peak = NULL;
    reset_info(info, FALSE);

    info->mutex = g_mutex_new();

    _DEBUG("Playback buffer allocated for %d samples, %d bytes", BUFFER_SIZE_SAMP, BUFFER_SIZE_BYTE);

    _LEAVE info;
}

/* --- */

void clean_callback_info(callback_info* info)
{
    g_mutex_free(info->mutex);
    free(info->output_buffer);
    free(info);
}

/* --- */

void reset_info(callback_info* info, gboolean close_fd) {

    _ENTER;

    _DEBUG("Using callback_info %s", info->name);

    if (close_fd && (NULL != info->input_stream)) {
        _DEBUG("Closing fd");
        aud_vfs_fclose(info->input_stream);
    }
    info->input_stream = NULL;

    // memset(info->output_buffer, 0, BUFFER_SIZE * sizeof(int16_t));
    info->stream.samplerate = 0;
    info->stream.bits_per_sample = 0;
    info->stream.channels = 0;
    info->stream.samples = 0;
    info->stream.has_seektable = FALSE;
    info->buffer_free = BUFFER_SIZE_SAMP;
    info->buffer_used = 0;
    info->write_pointer = info->output_buffer;
    info->read_max = -1;
    info->testing = FALSE;

    /*
     * Clear the stream and frame information
     */
    info->stream.bits_per_sample = 0;
    info->stream.samplerate = 0;
    info->stream.channels = 0;
    info->stream.samples = 0;

    if (NULL != info->comment.artist) {
        free(info->comment.artist);
        info->comment.artist = NULL;
    }

    if (NULL != info->comment.album) {
        free(info->comment.album);
        info->comment.album = NULL;
    }

    if (NULL != info->comment.title) {
        free(info->comment.title);
        info->comment.title = NULL;
    }

    if (NULL != info->comment.tracknumber) {
        free(info->comment.tracknumber);
        info->comment.tracknumber = NULL;
    }

    if (NULL != info->comment.genre) {
        free(info->comment.genre);
        info->comment.genre = NULL;
    }

    if (NULL != info->replaygain.ref_loud) {
        free(info->replaygain.ref_loud);
        info->replaygain.ref_loud = NULL;
    }

    if (NULL != info->replaygain.track_gain) {
        free(info->replaygain.track_gain);
        info->replaygain.track_gain = NULL;
    }

    if (NULL != info->replaygain.track_peak) {
        free(info->replaygain.track_peak);
        info->replaygain.track_peak = NULL;
    }

    if (NULL != info->replaygain.album_gain) {
        free(info->replaygain.album_gain);
        info->replaygain.album_gain = NULL;
    }

    if (NULL != info->replaygain.album_peak) {
        free(info->replaygain.album_peak);
        info->replaygain.album_peak = NULL;
    }

    info->replaygain.has_rg = FALSE;

    if (NULL != info->comment.date) {
        free(info->comment.date);
        info->comment.date = NULL;
    }

    info->frame.bits_per_sample = 0;
    info->frame.samplerate = 0;
    info->frame.channels = 0;

    info->metadata_changed = FALSE;

    _LEAVE;
}

/* --- */

gboolean read_metadata(VFSFile* fd, FLAC__StreamDecoder* decoder, callback_info* info) {

    FLAC__StreamDecoderState ret;

    _ENTER;

    _DEBUG("Using callback_info %s", info->name);

    reset_info(info, FALSE);

    info->input_stream = fd;

    /*
     * Reset the decoder
     */
    if (false == FLAC__stream_decoder_reset(decoder)) {
        _ERROR("Could not reset the decoder!");
        _LEAVE FALSE;
    }

    /*
     * Just scan the first 8k for the start of metadata
     */

    info->read_max = 8192;

    /*
     * We are not sure if this is an actual flac file, so do not
     * complain too much about errors in the stream
     */
    info->testing = TRUE;

    /*
     * Try to decode the metadata
     */
    if (false == FLAC__stream_decoder_process_until_end_of_metadata(decoder)) {
        ret = FLAC__stream_decoder_get_state(decoder);
        _DEBUG("Could not read the metadata: %s(%d)!",
                FLAC__StreamDecoderStateString[ret], ret);
        /* Do not close the filehandle, it was passed to us */
        reset_info(info, FALSE);
        _LEAVE FALSE;
    }

    /*
     * Seems to be a FLAC stream. Allow further reading
     */

    info->read_max = -1;
    info->testing = FALSE;

    _LEAVE TRUE;
}

/* --- */

Tuple* get_tuple(const gchar* filename, callback_info* info) {

    Tuple *out;

    _ENTER;

    _DEBUG("Using callback_info %s", info->name);

    out = aud_tuple_new_from_filename(filename);

    aud_tuple_associate_string(out, FIELD_CODEC, NULL, "Free Lossless Audio Codec (FLAC)");
    aud_tuple_associate_string(out, FIELD_QUALITY, NULL, "lossless");

    aud_tuple_associate_string(out, FIELD_ARTIST, NULL, info->comment.artist);
    aud_tuple_associate_string(out, FIELD_TITLE, NULL, info->comment.title);
    aud_tuple_associate_string(out, FIELD_ALBUM, NULL, info->comment.album);
    aud_tuple_associate_string(out, FIELD_GENRE, NULL, info->comment.genre);

    if (info->comment.tracknumber != NULL)
        aud_tuple_associate_int(out, FIELD_TRACK_NUMBER, NULL, atoi(info->comment.tracknumber));

    if (info->comment.date != NULL)
        aud_tuple_associate_int(out, FIELD_YEAR, NULL, atoi(info->comment.date));

    /*
     * Calculate the stream length (milliseconds)
     */
    if (0 == info->stream.samplerate) {
        _ERROR("Invalid sample rate for stream!");
        aud_tuple_associate_int(out, FIELD_LENGTH, NULL, -1);
    } else {
        aud_tuple_associate_int(out, FIELD_LENGTH, NULL, (info->stream.samples / info->stream.samplerate) * 1000);
        _DEBUG("Stream length: %d seconds", aud_tuple_get_int(out, FIELD_LENGTH, NULL));
    }

    _DEBUG("Tuple created: [%p]", out);

    _LEAVE out;
}

/* --- */

gchar* get_title(const gchar* filename, callback_info* info) {

    Tuple *input;
    gchar *title;

    _ENTER;

    _DEBUG("Using callback_info %s", info->name);

    input = get_tuple(filename, info);

    title = aud_tuple_formatter_make_title_string(input, aud_get_gentitle_format());

    aud_tuple_free(input);

    _DEBUG("Title created: <%s>", title);

    _LEAVE title;
}

/* --- */

void add_comment(callback_info* info, gchar* key, gchar* value) {

    /*
     * We are currently interested in comments of type
     * - ARTIST
     * - ALBUM
     * - TITLE
     * - TRACKNUMBER
     *
     * We have to create copies of the value string.
     */

    gchar** destination = NULL;
    gboolean rg = FALSE;

     _ENTER;

     _DEBUG("Using callback_info %s", info->name);

    if (0 == strcasecmp(key, "ARTIST")) {
        _DEBUG("Found key ARTIST");
        destination = &(info->comment.artist);
    }

    if (0 == strcasecmp(key, "ALBUM")) {
        _DEBUG("Found key ALBUM");
        destination = &(info->comment.album);
    }

    if (0 == strcasecmp(key, "TITLE")) {
        _DEBUG("Found key TITLE");
        destination = &(info->comment.title);
    }

    if (0 == strcasecmp(key, "TRACKNUMBER")) {
        _DEBUG("Found key TRACKNUMBER");
        destination = &(info->comment.tracknumber);
    }

    if (0 == strcasecmp(key, "REPLAYGAIN_REFERENCE_LOUDNESS")) {
        _DEBUG("Found key REPLAYGAIN_REFERENCE_LOUDNESS");
        destination = &(info->replaygain.ref_loud);
        rg = TRUE;
    }

    if (0 == strcasecmp(key, "REPLAYGAIN_TRACK_GAIN")) {
        _DEBUG("Found key REPLAYGAIN_TRACK_GAIN");
        destination = &(info->replaygain.track_gain);
        rg = TRUE;
    }

    if (0 == strcasecmp(key, "REPLAYGAIN_TRACK_PEAK")) {
        _DEBUG("Found key REPLAYGAIN_TRACK_PEAK");
        destination = &(info->replaygain.track_peak);
        rg = TRUE;
    }

    if (0 == strcasecmp(key, "REPLAYGAIN_ALBUM_GAIN")) {
        _DEBUG("Found key REPLAYGAIN_ALBUM_GAIN");
        destination = &(info->replaygain.album_gain);
        rg = TRUE;
    }

    if (0 == strcasecmp(key, "REPLAYGAIN_ALBUM_PEAK")) {
        _DEBUG("Found key REPLAYGAIN_ALBUM_PEAK");
        destination = &(info->replaygain.album_peak);
        rg = TRUE;
    }

    if (0 == strcasecmp(key, "DATE")) {
        _DEBUG("Found key DATE");
        destination = &(info->comment.date);
    }

    if (0 == strcasecmp(key, "GENRE")) {
        _DEBUG("Found key GENRE");
        destination = &(info->comment.genre);
    }

    if (NULL != destination) {
        if (NULL != *destination) {
            g_free(*destination);
        }

        if (NULL == (*destination = g_strdup(value))) {
            _ERROR("Could not allocate memory for comment!");
            _LEAVE;
        }
    }

    if (rg) {
        info->replaygain.has_rg = TRUE;
    }

    _LEAVE;
}

/* --- */

ReplayGainInfo get_replay_gain(callback_info *info) {

    ReplayGainInfo rg;
    
    if (info->replaygain.has_rg) {
	rg.track_gain = (info->replaygain.track_gain
			 ? atof(info->replaygain.track_gain)
			 : 0.0);
	rg.track_peak = (info->replaygain.track_peak
			 ? atof(info->replaygain.track_peak)
			 : 0.0);
	rg.album_gain = (info->replaygain.album_gain
			 ? atof(info->replaygain.album_gain)
			 : 0.0);
	rg.album_peak = (info->replaygain.album_peak
			 ? atof(info->replaygain.album_peak)
			 : 0.0);
    } else {
	rg.track_gain = 0.0;
	rg.track_peak = 0.0;
	rg.album_gain = 0.0;
	rg.album_peak = 0.0;
    }

    return rg;
}