Mercurial > audlegacy-plugins
diff src/flacng/plugin.c @ 930:2f742d127b3e trunk
[svn] - initial import of flacng from audacious-flacng-0.012
author | nenolod |
---|---|
date | Mon, 09 Apr 2007 10:55:23 -0700 |
parents | |
children | b6c95e2a14f4 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/flacng/plugin.c Mon Apr 09 10:55:23 2007 -0700 @@ -0,0 +1,706 @@ +/* + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <audacious/util.h> +#include <audacious/output.h> +#include <glib/gi18n.h> +#include "flacng.h" +#include "tools.h" +#include "plugin.h" +#include "seekable_stream_callbacks.h" +#include "version.h" +#include "debug.h" + +static gchar *flac_fmts[] = { "flac", NULL }; + +InputPlugin flac_ip = { + NULL, + NULL, + "FLACng Audio Plugin", + flac_init, + flac_aboutbox, + NULL, + flac_is_our_file, + NULL, + flac_play_file, + flac_stop, + flac_pause, + flac_seek, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + flac_get_song_info, + NULL, + NULL, + flac_get_song_tuple, // get a tuple + NULL, + NULL, // write a tuple back to a file as a tag +/* flac_is_our_fd */ NULL, // version of is_our_file which is handed an FD + flac_fmts // vector of fileextensions allowed by the plugin +}; + +FLAC__StreamDecoder* test_decoder; +FLAC__StreamDecoder* main_decoder; +callback_info* test_info; +callback_info* main_info; +gboolean plugin_initialized = FALSE; +gint seek_to = -1; +static GThread* thread; +GMutex* flac_pl_mutex; + +/* === */ + +InputPlugin* get_iplugin_info(void) { + + _ENTER; + + _DEBUG("%s (%s)", flac_ip.description, _VERSION); + + _LEAVE &flac_ip; +} + +/* --- */ + +void flac_init(void) { + + FLAC__StreamDecoderInitStatus ret; + + _ENTER; + + /* + * Callback structure and decoder for file test + * purposes + */ + if (NULL == (test_info = init_callback_info("test"))) { + _ERROR("Could not initialize the test callback structure!"); + _LEAVE; + } + _DEBUG("Test callback structure at %p", test_info); + + if (NULL == (test_decoder = FLAC__stream_decoder_new())) { + _ERROR("Could not create the test FLAC decoder instance!"); + _LEAVE; + } + + + /* + * We want the VORBISCOMMENT metadata, for the file tags + * and SEEKTABLE + */ + FLAC__stream_decoder_set_metadata_respond(test_decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); + FLAC__stream_decoder_set_metadata_respond(test_decoder, FLAC__METADATA_TYPE_SEEKTABLE); + + /* + * Callback structure and decoder for main decoding + * loop + */ + if (NULL == (main_info = init_callback_info("main"))) { + _ERROR("Could not initialize the main callback structure!"); + _LEAVE; + } + _DEBUG("Main callback structure at %p", main_info); + + if (NULL == (main_decoder = FLAC__stream_decoder_new())) { + _ERROR("Could not create the main FLAC decoder instance!"); + _LEAVE; + } + + + /* + * We want the VORBISCOMMENT metadata, for the file tags + * and SEEKTABLE + */ + FLAC__stream_decoder_set_metadata_respond(main_decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); + FLAC__stream_decoder_set_metadata_respond(main_decoder, FLAC__METADATA_TYPE_SEEKTABLE); + + /* + * Initialize decoders + */ + if (FLAC__STREAM_DECODER_INIT_STATUS_OK != (ret = FLAC__stream_decoder_init_stream( + test_decoder, + read_callback, + seek_callback, + tell_callback, + length_callback, + eof_callback, + write_callback, + metadata_callback, + error_callback, + test_info))) { + _ERROR("Could not initialize test FLAC decoder: %s(%d)", StreamDecoderInitState(ret), ret); + _LEAVE; + } + + if (FLAC__STREAM_DECODER_INIT_STATUS_OK != (ret = FLAC__stream_decoder_init_stream( + main_decoder, + read_callback, + seek_callback, + tell_callback, + length_callback, + eof_callback, + write_callback, + metadata_callback, + error_callback, + main_info))) { + _ERROR("Could not initialize main FLAC decoder: %s(%d)", StreamDecoderInitState(ret), ret); + _LEAVE; + } + + /* + * Initialize the play loop mutex + */ + flac_pl_mutex = g_mutex_new(); + + _DEBUG("plugin initialized OK!"); + plugin_initialized = TRUE; + _LEAVE; +} + +/* --- */ + +gboolean flac_is_our_file(gchar* filename) { + + _ENTER; + + if (!plugin_initialized) { + _ERROR("Plugin not initialized!"); + _LEAVE FALSE; + } + + _DEBUG("Testing file: %s", filename); + + if (FALSE == read_metadata(filename, test_decoder, test_info)) { + _DEBUG("File not handled by this plugin!"); + _LEAVE FALSE; + } + + /* + * See if the metadata has changed + */ + if (FALSE == test_info->metadata_changed) { + _DEBUG("No metadata found in stream"); + _LEAVE FALSE; + } + + /* + * If we get here, the file is supported by FLAC. + * The stream characteristics have been filled in by + * the metadata callback. + * We can close the stream now. + */ + + vfs_fclose(test_info->input_stream); + test_info->input_stream = NULL; + + + _DEBUG("Stream encoded at %d Hz, %d bps, %d channels", + test_info->stream.samplerate, + test_info->stream.bits_per_sample, + test_info->stream.channels); + + if (MAX_SUPPORTED_CHANNELS < test_info->stream.channels) { + _ERROR("This number of channels (%d) is currently not supported, stream not handled by this plugin", + test_info->stream.channels); + _LEAVE FALSE; + } + + if ((16 != test_info->stream.bits_per_sample) && + (24 != test_info->stream.bits_per_sample) && + (8 != test_info->stream.bits_per_sample)) { + _ERROR("This number of bits (%d) is currently not supported, stream not handled by this plugin", + test_info->stream.bits_per_sample); + _LEAVE FALSE; + } + + /* + * Looks good. + */ + + _DEBUG("Accepting file %s", filename); + + reset_info(test_info); + + _LEAVE TRUE; +} + +/* --- */ + +void squeeze_audio(gint32* src, void* dst, guint count, guint src_res, guint dst_res) { + + /* + * Changes the sample width of count samples in src from + * src_res to dst_res and places the result in dst + */ + + gint i; + gint32* rp = src; + gint8* wp = dst; + gint16* wp2 = dst; + gint32* wp4 = dst; + + _ENTER; + + _DEBUG("Converting %d samples %d->%d", count, src_res, dst_res); + + if ((src_res % 8 != 0) || (dst_res % 8 != 0)) { + _ERROR("Can not convert from %d bps to %d bps: not a multiple of 8", + src_res, dst_res); + _LEAVE; + } + + if (16 == dst_res) { + if (8 == src_res) { + for (i=0; i<count; i++, wp2++, rp++) { + *wp2 = (*rp << 8) & 0xffff; + } + } else if (16 == src_res) { + for (i=0; i<count; i++, wp2++, rp++) { + *wp2 = *rp & 0xffff; + } + } else if (24 == src_res) { + for (i=0; i<count; i++, wp2++, rp++) { + *wp2 = (*rp >> 8) & 0xffff; + } + } else if (32 == src_res) { + for (i=0; i<count; i++, wp2++, rp++) { + *wp2 = (*rp >> 16) & 0xffff; + } + } + } else if (8 == dst_res) { + if (8 == src_res) { + for (i=0; i<count; i++, wp++, rp++) { + *wp = *rp & 0xff; + } + } else if (16 == src_res) { + for (i=0; i<count; i++, wp++, rp++) { + *wp = (*rp >> 8) & 0xff; + } + } else if (24 == src_res) { + for (i=0; i<count; i++, wp++, rp++) { + *wp = (*rp >> 16) & 0xff; + } + } else if (32 == src_res) { + for (i=0; i<count; i++, wp++, rp++) { + *wp = (*rp >> 24) & 0xff; + } + } + } else if (32 == dst_res) { + if (8 == src_res) { + for (i=0; i<count; i++, wp4++, rp++) { + *wp4 = (*rp << 24) & 0xffffffff; + } + } else if (16 == src_res) { + for (i=0; i<count; i++, wp4++, rp++) { + *wp4 = (*rp << 16) & 0xffffffff; + } + } else if (24 == src_res) { + for (i=0; i<count; i++, wp4++, rp++) { + *wp4 = (*rp << 8) & 0xffffffff; + } + } else if (32 == src_res) { + for (i=0; i<count; i++, wp4++, rp++) { + *wp4 = *rp; + } + } + } + + _LEAVE; +} + +/* --- */ + +static gpointer flac_play_loop(gpointer arg) { + + /* + * The main play loop. + * Decode a frame, push the decoded data to the output plugin + * chunkwise. Repeat until finished. + * + * Must be entered with flac_pl_mutex held! + */ + + gint ofree; + gint32* read_pointer; + gint elements_left; + gint seek_sample; + FLAC__StreamDecoderState state; + struct stream_info stream_info; + guint sample_count; + void* play_buffer; + InputPlayback* playback = (InputPlayback *) arg; + + _ENTER; + + if (NULL == (play_buffer = malloc(BUFFER_SIZE_BYTE))) { + _ERROR("Could not allocate conversion buffer"); + playback->playing = FALSE; + g_mutex_unlock(flac_pl_mutex); + _LEAVE NULL; + } + + stream_info.samplerate = main_info->stream.samplerate; + stream_info.channels = main_info->stream.channels; + main_info->metadata_changed = FALSE; + + if (!playback->output->open_audio(FMT_S16_NE, + main_info->stream.samplerate, + main_info->stream.channels)) { + playback->playing = FALSE; + _ERROR("Could not open output plugin!"); + g_mutex_unlock(flac_pl_mutex); + _LEAVE NULL; + } + + while (TRUE == playback->playing) { + + /* + * Try to decode a single frame of audio + */ + if (FALSE == FLAC__stream_decoder_process_single(main_decoder)) { + /* + * Something went wrong + */ + _ERROR("Error while decoding!"); + break; + } + + /* + * Maybe the metadata changed midstream. Check. + */ + + if (main_info->metadata_changed) { + /* + * Samplerate and channels are important + */ + if (stream_info.samplerate != main_info->stream.samplerate) { + _ERROR("Samplerate changed midstream (now: %d, was: %d). This is not supported yet.", + main_info->stream.samplerate, stream_info.samplerate); + break; + } + + if (stream_info.channels != main_info->stream.channels) { + _ERROR("Number of channels changed midstream (now: %d, was: %d). This is not supported yet.", + main_info->stream.channels, stream_info.channels); + break; + } + + main_info->metadata_changed = FALSE; + } + + /* + * Compare the frame metadata to the current stream metadata + */ + if (main_info->stream.samplerate != main_info->frame.samplerate) { + _ERROR("Frame samplerate mismatch (stream: %d, frame: %d)!", + main_info->stream.samplerate, main_info->frame.samplerate); + break; + } + + if (main_info->stream.channels != main_info->frame.channels) { + _ERROR("Frame channel mismatch (stream: %d, frame: %d)!", + main_info->stream.channels, main_info->frame.channels); + break; + } + + /* + * If the frame decoded was an audio frame we now + * have data in info->output_buffer + * + * The data in this buffer is in 32 bit wide samples, even if the + * real sample width is smaller. It has to be converted before + * feeding it to he output plugin. + * + * Feed the data in chunks of OUTPUT_BLOCK_SIZE elements to the output + * plugin + */ + read_pointer = main_info->output_buffer; + elements_left = main_info->buffer_used; + + while ((TRUE == playback->playing) && (0 != elements_left)) { + + sample_count = MIN(OUTPUT_BLOCK_SIZE, elements_left); + + /* + * Convert the audio. + * Currently this is hardcoded to 16 bit. + * 8 and 24 bit are sampled up/down linear. + */ + _DEBUG("Converting %d samples to FMT_S16_NE", sample_count); + squeeze_audio(read_pointer, play_buffer, sample_count, main_info->frame.bits_per_sample, 16); + + _DEBUG("Copying %d samples to output plugin", sample_count); + + produce_audio(flac_ip.output->written_time(), FMT_S16_NE, main_info->frame.channels, sample_count * sizeof(gint16), play_buffer, NULL); + + read_pointer += sample_count; + elements_left -= sample_count; + + _DEBUG("%d elements left to be output", elements_left); + } + + /* + * Clear the buffer. + */ + main_info->write_pointer = main_info->output_buffer; + main_info->buffer_free = BUFFER_SIZE_SAMP; + main_info->buffer_used = 0; + + /* + * Do we have to seek to somewhere? + */ + if (-1 != seek_to) { + _DEBUG("Seek requested to %d seconds", seek_to); + + if (FALSE == main_info->stream.has_seektable) { + _ERROR("Stream does not have a seektable, can not seek!"); + } else { + seek_sample = seek_to * main_info->stream.samplerate; + _DEBUG("Seek requested to sample %d", seek_sample); + if (FALSE == FLAC__stream_decoder_seek_absolute(main_decoder, seek_sample)) { + _ERROR("Could not seek to sample %d!", seek_sample); + } else { + /* + * Flush the buffers + */ + flac_ip.output->flush(seek_to * 1000); + } + } + seek_to = -1; + } + + /* + * Have we reached the end of the stream? + */ + state = FLAC__stream_decoder_get_state(main_decoder); + if (0 == elements_left && (FLAC__STREAM_DECODER_END_OF_STREAM == state)) { + /* + * Yes. Drain the output buffer and stop playing. + */ + + _DEBUG("End of stream reached, draining output buffer"); + + while((-1 == seek_to) && flac_ip.output->buffer_playing() && playback->playing == TRUE) { + g_usleep(40000); + } + + if (-1 == seek_to) { + _DEBUG("Output buffer empty."); + playback->playing = FALSE; + } + } + } + + /* + * Clean up a bit + */ + playback->playing = FALSE; + _DEBUG("Closing audio device"); + flac_ip.output->close_audio(); + _DEBUG("Audio device closed"); + + free(play_buffer); + + if (FALSE == FLAC__stream_decoder_flush(main_decoder)) { + _ERROR("Could not flush decoder state!"); + } + + /* + * Release the play loop mutex + */ + g_mutex_unlock(flac_pl_mutex); + + g_thread_exit(NULL); + + _LEAVE NULL; +} + +/* --- */ + +void flac_play_file (InputPlayback* input) { + + gint l; + + _ENTER; + + if (!plugin_initialized) { + _ERROR("plugin not initialized!"); + _LEAVE; + } + + /* + * This will end a currently running decoder thread + */ + input->playing = FALSE; + xmms_usleep(20000); + + if (FALSE == read_metadata(input->filename, main_decoder, main_info)) { + _ERROR("Could not prepare file for playing!"); + _LEAVE; + } + + /* + * Calculate the length + */ + if (0 == main_info->stream.samplerate) { + _ERROR("Invalid sample rate for stream!"); + l = -1; + } else { + l = (main_info->stream.samples / main_info->stream.samplerate) * 1000; + } + + /* + * Grab the play loop mutex + */ + _DEBUG("Waiting for play loop mutex..."); + g_mutex_lock(flac_pl_mutex); + _DEBUG("Got play loop mutex"); + + input->playing = TRUE; + + flac_ip.set_info(get_title(input->filename, main_info), l, -1, main_info->stream.samplerate, main_info->stream.channels); + + thread = g_thread_create(flac_play_loop, input, TRUE, NULL); + + _LEAVE; +} + +/* --- */ + +void flac_stop(InputPlayback* input) { + + _ENTER; + + input->playing = FALSE; + + _LEAVE; +} + +/* --- */ + +void flac_pause(InputPlayback* input, gshort p) { + + _ENTER; + + input->output->pause(p); + + _LEAVE; +} + +/* --- */ + +void flac_seek(InputPlayback* input, gint time) { + + _ENTER; + + if (!input->playing) { + _DEBUG("Can not seek while not playing"); + _LEAVE; + } + + _DEBUG("Requesting seek to %d", time); + seek_to = time; + + while (-1 != seek_to) { + xmms_usleep(10000); + } + + _LEAVE; +} + +/* --- */ + +void flac_get_song_info(gchar* filename, gchar** title, gint* length) { + + gint l; + + _ENTER; + + if (FALSE == read_metadata(filename, test_decoder, test_info)) { + _ERROR("Could not read file info!"); + *length = -1; + *title = g_strdup(""); + _LEAVE; + } + + /* + * Calculate the stream length (milliseconds) + */ + if (0 == test_info->stream.samplerate) { + _ERROR("Invalid sample rate for stream!"); + l = -1; + } else { + l = (test_info->stream.samples / test_info->stream.samplerate) * 1000; + _DEBUG("Stream length: %d seconds", l/1000); + } + + *length = l; + *title = get_title(filename, test_info); + + reset_info(test_info); + + _LEAVE; +} + +/* --- */ + +TitleInput *flac_get_song_tuple(gchar* filename) { + + gint l; + TitleInput *tuple; + + _ENTER; + + if (FALSE == read_metadata(filename, test_decoder, test_info)) { + _ERROR("Could not read metadata tuple for file <%s>", filename); + _LEAVE NULL; + } + + tuple = get_tuple(filename, test_info); + + reset_info(test_info); + + _LEAVE tuple; +} + +/* --- */ + +void flac_aboutbox(void) { + + static GtkWidget* about_window; + + if (about_window) { + gdk_window_raise(about_window->window); + } + + about_window = xmms_show_message(_("About FLAC Audio Plugin"), + ("FLAC Audio Plugin (" _VERSION ")\n\n" + "Original code by\n" + "Ralf Ertzinger <ralf@skytale.net>\n" + "\n" + "http://www.skytale.net/projects/bmp-flac2/"), + _("OK"), FALSE, NULL, NULL); + g_signal_connect(G_OBJECT(about_window), "destroy", + G_CALLBACK(gtk_widget_destroyed), &about_window); +}