/* Speex Xmms plugin * (c) Jens Burkal, license: GPL * * libspeex.c: Main plugin file * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libspeex.h" #include "gui/interface.h" #include "gui/support.h" #ifdef USE_UTF8 #include "utf8.h" #endif #define VBR_UPDATE_FREQ 1000 #define BUFFER_LEN 200 #define TITLE_HACK static void init (void); static void about_spx (void); static int is_our_file (char*); static void play (char *filename); static void spx_play_loop (void); static void *spx_parse_header(ogg_packet, SpeexStereoState*); static void stop (void); static void spx_pause (short); static void seek (int); static int get_time (void); static void get_song_info (char *, char **, int *); static void show_error (char *, char *); Speex_File_State *speex_fs = NULL; Speex_Configuration *speex_cfg = NULL; static pthread_t spx_decode_thread; #include #include "speexutil.h" #include "config.h" #include "fileinfo.h" #include "http.h" InputPlugin speex_ip = { NULL, NULL, NULL, init, about_spx, spx_config, is_our_file, NULL, play, stop, spx_pause, seek, NULL, get_time, NULL, NULL, NULL, NULL, NULL, NULL, NULL, get_song_info, spx_fileinfo, NULL }; InputPlugin *get_iplugin_info(void) { #ifdef TITLE_HACK speex_ip.description = " Speex plugin " VERSION; #else speex_ip.description = "Speex plugin " VERSION #endif return &speex_ip; } static void init (void) { if (!speex_cfg) speex_cfg = malloc(sizeof(Speex_Configuration)); spx_config_load(); } char *generate_title (char *filename, speex_comment_t *comment) { // If we don't get a comment-stuct, lets use the filename char *tmpstr, *tmp; char *titlestring; TitleInput* title; if (!comment && filename) { tmpstr = g_strdup(filename); tmp = strrchr(tmpstr, '.'); if (tmp) *tmp = '\0'; return g_basename(tmpstr); } XMMS_NEW_TITLEINPUT(title); #ifdef USE_UTF8 title->performer = convert_from_utf8(speex_comment_get("author", comment)); title->track_name = convert_from_utf8(speex_comment_get("title", comment)); title->date = convert_from_utf8(speex_comment_get("date", comment)); #else title->performer = speex_comment_get("author", comment); title->track_name = speex_comment_get("title", comment); title->date = speex_comment_get("date", comment); #endif if (speex_cfg->use_title) titlestring = xmms_get_titlestring(speex_cfg->title_format, title); else titlestring = xmms_get_titlestring(xmms_get_gentitle_format(), title); #ifdef USE_UTF8 g_free(title->performer); g_free(title->track_name); g_free(title->date); #endif g_free(title); return titlestring; } static void about_spx (void) { GtkWidget *aboutwindow; aboutwindow = create_aboutbox(); gtk_widget_show(aboutwindow); return; } static int is_our_file (char *filename) { // Everything with our extention will be accepted char *ext; ext = (char*) strrchr(filename, '.'); return (ext && strcmp(ext, SPEEX_EXT) == 0); } static void play (char *filename) { speex_comment_t comments; if (speex_fs == NULL) speex_fs = malloc(sizeof(Speex_File_State)); memset(speex_fs, 0, sizeof(Speex_File_State)); if (strstr(filename, "http://")) speex_fs->streaming = TRUE; else speex_fs->streaming = FALSE; speex_fs->playing = TRUE; if (speex_fs->title) g_free(speex_fs->title); if (speex_fs->streaming) { speex_fs->length = -1; speex_fs->title = generate_title(filename, NULL); } else { speex_file_info(filename, NULL, &comments, &speex_fs->length); speex_fs->length *= 1000; speex_fs->title = generate_title(filename, &comments); } if (speex_fs->streaming) { #ifdef DEBUG fprintf(stderr, PACKAGE ": opening url: %s\n", filename); #endif speex_http_open(filename); // Will always return 0 } else { if ((speex_fs->spxfile = fopen(filename, "rb")) == NULL) { #ifdef DEBUG fprintf(stderr, PACKAGE ": error opening file\n"); #endif return; } } pthread_create(&spx_decode_thread, NULL, (void*)spx_play_loop, NULL); } static void spx_play_loop (void) { void *data; int readsize; float outbuff[2000]; short output[2000]; int i, j, result, seeked_to; int packetbytes, last_poll; ogg_sync_state oy; ogg_page og; ogg_packet op; ogg_stream_state os; void *spx_decoder = NULL; SpeexBits sbits; SpeexStereoState stereo = SPEEX_STEREO_STATE_INIT; speex_comment_t comments; int valid_comment; #ifdef DEBUG fprintf(stderr, PACKAGE ": thread created\n"); #endif ogg_sync_init(&oy); speex_bits_init(&sbits); speex_fs->eof = FALSE; speex_fs->seek_to = -1; // Well, lets just play... while (speex_fs->playing) { while (1) { data = ogg_sync_buffer(&oy, BUFFER_LEN); if (speex_fs->streaming) readsize = speex_http_read(data, BUFFER_LEN); else readsize = fread(data, sizeof(char), BUFFER_LEN, speex_fs->spxfile); ogg_sync_wrote(&oy, readsize); if ((readsize < BUFFER_LEN) || (!speex_fs->streaming && feof(speex_fs->spxfile))) // Bad file { #ifdef DEBUG fprintf(stderr, PACKAGE ": unexpected eof\n"); #endif return; } if (ogg_sync_pageout(&oy, &og) == 1) break; } ogg_stream_init(&os, ogg_page_serialno(&og)); if (ogg_stream_pagein(&os, &og) < 0) { #ifdef DEBUG fprintf(stderr, PACKAGE ": stream error\n"); #endif return; } if (ogg_stream_packetout(&os, &op) != 1) { #ifdef DEBUG fprintf(stderr, PACKAGE ": cannot read header\n"); #endif return; } // op must now be the first packet/page and must be the header // *** HEADER *** spx_decoder = spx_parse_header(op, &stereo); if (spx_decoder == NULL) return; #ifdef DEBUG if (speex_cfg->use_enhancer) fprintf(stderr, PACKAGE ": enhancer enabled\n"); else fprintf(stderr, PACKAGE ": enhancer disabled\n"); #endif speex_decoder_ctl(spx_decoder, SPEEX_SET_ENH, &speex_cfg->use_enhancer); speex_ip.set_info(speex_fs->title, speex_fs->length, -1, speex_fs->freq, speex_fs->channels); if (speex_ip.output->open_audio(FMT_S16_NE, speex_fs->freq, speex_fs->channels) == 0) { #ifdef DEBUG fprintf(stderr, PACKAGE ": audio error\n"); #endif return; } speex_fs->eos = FALSE; // we have now gotten through initialisation, and the next packet is the comment packet // *** COMMENT *** if (ogg_stream_packetout(&os, &op) == 1) { // Nothing } else { while (ogg_sync_pageout(&oy, &og) < 1) { data = ogg_sync_buffer(&oy, BUFFER_LEN); if (speex_fs->streaming) readsize = speex_http_read(data, BUFFER_LEN); else readsize = fread(data, sizeof(char), BUFFER_LEN, speex_fs->spxfile); ogg_sync_wrote(&oy, readsize); } ogg_stream_pagein(&os, &og); if (ogg_stream_packetout(&os, &op) == 1) { // This 2. initialization of comments is done because of streaming valid_comment = speex_comment_init(op.packet, op.bytes, &comments); #ifdef DEBUG if (valid_comment) { fprintf(stderr, "Vendor: %s\n", speex_comment_get_vendor(&comments)); for (i=0, speex_comment_first(&comments); !speex_comment_isdone(&comments); i++) fprintf(stderr, "Comment %d: %s\n", i, speex_comment_get_next(&comments)); } else { fprintf(stderr, "non speexcomment\n"); } #endif if (valid_comment) { speex_fs->title = generate_title(NULL, &comments); speex_comment_free(&comments); } } } // now we're all done with the headers // *** DECODE *** packetbytes = 0; last_poll = speex_ip.output->output_time(); while (speex_fs->playing) { while (speex_fs->playing) { result = ogg_sync_pageout(&oy, &og); if (result == -1) { // Sync error. Had to skip bytes. // This occurs when we seek, not a problem. break; } else if (result == 0) { // Read some more data break; } else if (result == 1) { // We have a page ogg_stream_pagein(&os, &og); while (speex_fs->playing) { result = ogg_stream_packetout(&os, &op); if (result < 1) { // We have either lost sync or we need more data break; } else if (op.e_o_s) { #ifdef DEBUG fprintf(stderr, PACKAGE ": eos\n"); #endif speex_fs->eos = TRUE; } else if (speex_fs->seek_to != -1) { break; } { // Vbr handling packetbytes += op.bytes; if (speex_ip.output->output_time() > last_poll + VBR_UPDATE_FREQ) { speex_ip.set_info(speex_fs->title, speex_fs->length, (packetbytes*8*1024) / (speex_ip.output->output_time() - last_poll), speex_fs->freq, speex_fs->channels); last_poll = speex_ip.output->output_time(); packetbytes = 0; } // Decode speex_bits_read_from(&sbits, (char *)op.packet, op.bytes); // For multiple frames within packets for (j=0; j < speex_fs->nframes; j++) { speex_decode(spx_decoder, &sbits, outbuff); // Stereo if (speex_fs->channels == 2) { speex_decode_stereo(outbuff, speex_fs->framesize, &stereo); } // Converting and clipping check for (i = 0; i < speex_fs->framesize*speex_fs->channels; i++) { if (outbuff[i] > 32000) output[i] = 32000; else if (outbuff[i] < -32000) output[i] = -32000; else output[i] = outbuff[i]; } // While the output-buffer is too small to hold our data, sleep while (speex_ip.output->buffer_free() < (speex_fs->framesize* speex_fs->channels * sizeof(short)) && speex_fs->playing && speex_fs->seek_to == -1) xmms_usleep(10000); // gotos are ugly, I know, you find a way to break out of this :) if (speex_fs->seek_to != -1) goto seekhere; speex_ip.add_vis_pcm(speex_ip.output->written_time(), FMT_S16_NE, speex_fs->channels, speex_fs->framesize * sizeof(short), &output); speex_ip.output->write_audio(&output, speex_fs->framesize * speex_fs->channels * sizeof(short)); } } } } } if (speex_fs->eof || speex_fs->eos) break; seekhere: if (speex_fs->seek_to != -1) { if (speex_ip.output->output_time() > speex_fs->seek_to * 1000) // backwards search seeked_to = speex_seek(speex_fs->spxfile, speex_fs->seek_to, 0, speex_fs->freq); else seeked_to = speex_seek(speex_fs->spxfile, speex_fs->seek_to, 1, speex_fs->freq); // Flush away... ogg_sync_reset(&oy); ogg_stream_reset(&os); speex_ip.output->flush(seeked_to); speex_fs->seek_to = -1; } if (!speex_fs->eof) { data = ogg_sync_buffer(&oy, BUFFER_LEN); if (speex_fs->streaming) readsize = speex_http_read(data, BUFFER_LEN); else readsize = fread(data, sizeof(char), BUFFER_LEN, speex_fs->spxfile); ogg_sync_wrote(&oy, readsize); if (readsize < BUFFER_LEN || (!speex_fs->streaming && feof(speex_fs->spxfile))) { #ifdef DEBUG fprintf(stderr, PACKAGE ": eof\n"); #endif speex_fs->eof = TRUE; } } } // while !eos // Cleanup for this logical bitstream ogg_stream_clear(&os); //If we've exited because of eof/eos, wait until the buffer is empty if (speex_fs->playing) while (speex_ip.output->buffer_playing()) xmms_usleep(10000); // We have to close the audio output before playing the next stream speex_ip.output->close_audio(); if (speex_fs->eof) break; } // while !eof ogg_sync_clear(&oy); speex_bits_destroy(&sbits); speex_decoder_destroy(spx_decoder); if (speex_fs->streaming) speex_http_close(); else fclose (speex_fs->spxfile); // Signal xmms to play next file if eof speex_fs->seek_to = -1; pthread_exit(NULL); } static void *spx_parse_header(ogg_packet op, SpeexStereoState *stereo) { SpeexCallback callback; SpeexHeader *header; SpeexMode *mode; void *spxdec; #ifdef DEBUG fprintf(stderr, PACKAGE ": parsing header\n"); #endif header = speex_packet_to_header((char*)op.packet, op.bytes); if (!header) { #ifdef DEBUG fprintf(stderr, PACKAGE ": Cannot read header.\n"); #endif return NULL; } mode = speex_mode_list[header->mode]; if (mode->bitstream_version > header->mode_bitstream_version) { fprintf(stderr, PACKAGE ": Incorrect version.\n"); show_error("Error", "\nThe file was encoded with an older version of Speex.\nYou need to downgrade the version in order to play it.\n"); return NULL; } if (mode->bitstream_version < header->mode_bitstream_version) { fprintf(stderr, PACKAGE ": Incorrect version.\n"); show_error("Error", "\nThe file was encoded with a newer version of Speex.\nYou need to upgrade in order to play it.\n"); return NULL; } speex_fs->freq = header->rate; speex_fs->channels = header->nb_channels; speex_fs->vbr = header->vbr; speex_fs->nframes = header->frames_per_packet; if (!speex_fs->nframes) speex_fs->nframes = 1; spxdec = speex_decoder_init(mode); if (spxdec == NULL) { #ifdef DEBUG fprintf(stderr, PACKAGE ": decoder init failed\n"); #endif return NULL; } speex_decoder_ctl(spxdec, SPEEX_GET_FRAME_SIZE, &speex_fs->framesize); // Stereo code if (speex_fs->channels != 1) { callback.callback_id = SPEEX_INBAND_STEREO; callback.func = speex_std_stereo_request_handler; callback.data = stereo; speex_decoder_ctl(spxdec, SPEEX_SET_HANDLER, &callback); } return spxdec; } static void spx_pause (short paused) { speex_ip.output->pause(paused); } static void stop (void) { speex_fs->playing = FALSE; // Wait for the thread to close pthread_join(spx_decode_thread, NULL); #ifdef DEBUG fprintf(stderr, PACKAGE ": stop\n"); #endif } static void seek (int time) { // The argument given to this function is the time in seconds if (speex_fs->streaming) return; speex_fs->seek_to = time; while (speex_fs->seek_to != -1) xmms_usleep(20000); } static int get_time (void) { if (speex_fs->playing && !speex_fs->eof) return speex_ip.output->output_time(); else return -1; } static void get_song_info (char *filename, char **title, int *length) { speex_comment_t comments; #ifdef DEBUG fprintf(stderr, "get_song_info\n"); #endif if (strstr(filename, "http://")) { *length = -1; (*title) = generate_title(filename, NULL); } else { speex_file_info(filename, NULL, &comments, length); *length *= 1000; (*title) = g_strdup(generate_title(filename, &comments)); speex_comment_free(&comments); } return; } static void show_error (char *title, char *error) { GtkWidget *window, *okbutton, *label; window = gtk_dialog_new(); gtk_window_set_title(GTK_WINDOW(window), title); label = gtk_label_new(error); gtk_container_add(GTK_CONTAINER(GTK_DIALOG(window)->vbox), label); okbutton = gtk_button_new_with_label("Ok"); gtk_container_add(GTK_CONTAINER(GTK_DIALOG(window)->action_area), okbutton); gtk_signal_connect_object(GTK_OBJECT(okbutton), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(window)); gtk_widget_draw_focus(GTK_WIDGET(okbutton)); gtk_widget_show_all(window); }