static int mad_stream_info(input_object *obj, stream_info *info) { struct mad_local_data *data; unsigned len; char metadata[256]; char *s, *p; if (!obj || !info) return 0; data = (struct mad_local_data *)obj->local_data; if (data) { if (!data->parse_id3) { sprintf(data->sinfo.title, "%s", data->filename); } else if (!data->parsed_id3) { if (reader_seekable(data->mad_fd)) { parse_id3 (data->path, &data->sinfo); if ((len = strlen(data->sinfo.title))) { s = data->sinfo.title + (len - 1); while (s != data->sinfo.title && *s == ' ') *(s--) = '\0'; } if ((len = strlen(data->sinfo.artist))) { s = data->sinfo.artist + (len - 1); while (s != data->sinfo.artist && *s == ' ') *(s--) = '\0'; } } strncpy (data->sinfo.path, data->path, sizeof(data->sinfo.path)); data->parsed_id3 = 1; } memset(metadata, 0, sizeof(metadata)); if ((len = reader_metadata(data->mad_fd, sizeof(metadata), metadata))) { //alsaplayer_error("Metadata: %s", metadata); if ((s = strstr(metadata, "StreamTitle='"))) { s += 13; if ((p = strstr(s, "'"))) { *p = '\0'; snprintf(data->sinfo.title, 128, "%s", s); } else { alsaplayer_error("Malformed metadata: \"%s\"", metadata); } } } /* Restore permanently filled info */ memcpy (info, &data->sinfo, sizeof (data->sinfo)); /* Compose path, stream_type and status fields */ sprintf(info->stream_type, "MP3 %dKHz %s %-3ldkbit", data->frame.header.samplerate / 1000, obj->nr_channels == 2 ? "stereo" : "mono", data->frame.header.bitrate / 1000); if (data->seeking) sprintf(info->status, "Seeking..."); } return 1; }
/* Only shows ID3 tags if at least one of TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE is present. */ static int show_id3(struct id3_tag const *tag) { unsigned int i; char emptystring[31]; char *names[6]; memset (emptystring, ' ', 30); emptystring[30] = '\0'; if (!parse_id3(names, tag)) { return 0; } if (options.opt & MPG321_REMOTE_PLAY) { printf("@I ID3:"); for (i=0; i<=5; i++) { if(!names[i]) { printf(emptystring); } else { printf("%s", names[i]); free(names[i]); } } printf("\n"); } else { /* Emulate mpg123 original behaviour */ for (i=0; i<=5; i++) { fprintf (stderr, "%s", info_id3[i].name); if (!names[i]) { fprintf (stderr, emptystring); } else { fprintf (stderr, "%s", names[i]); free (names[i]); } if (i%2) fprintf (stderr, "\n"); } } return 1; }
int main(int argc, char *argv[]) { int fd = 0; char *currentfile, old_dir[PATH_MAX]; playlist *pl = NULL; struct id3_file *id3struct = NULL; struct id3_tag *id3tag = NULL; int retval; buffer playbuf; struct mad_decoder decoder; pthread_t keyb_thread; key_t sem_key; key_t mem_key; key_t frames_key; union semun sem_ops; int shm_id; int frames_id; mad_decoder_position = 0; output_buffer_position = 0; old_dir[0] = '\0'; playbuf.pl = pl = new_playlist(); if (!pl) { fprintf(stderr, "malloc failed at startup!\n"); exit(1); } loop_remaining = 1; options.volume = MAD_F_ONE; status = MPG321_PLAYING; /* Get the command line options */ parse_options(argc, argv, pl); if(options.opt & MPG321_PRINT_FFT) if(!(options.opt & MPG321_REMOTE_PLAY)) { /* printf("FFT analysis can only be used in Remote mode play.\n\n"); */ usage(argv[0]); exit(0); } /* If there were no files and no playlist specified just print the usage */ if (!playlist_file && optind == argc) { usage(argv[0]); exit(0); } if (playlist_file) load_playlist(pl, playlist_file); if(options.opt & MPG321_RECURSIVE_DIR) add_cmdline_files_recursive_dir(pl, argv); else add_cmdline_files(pl, argv); if (shuffle_play) shuffle_files(pl); if(options.opt & MPG321_ENABLE_BUFFER) { /* Initialize semaphore and shared memeory */ if(access(argv[0],X_OK) == 0) sem_key = ftok(argv[0],0); else sem_key = ftok(MPG321_PATH,0); if(sem_key == -1) { perror("Cannot obtain resources for semaphores"); exit(EXIT_FAILURE); } semarray = semget(sem_key,3,IPC_CREAT | IPC_EXCL | S_IRWXU); if(semarray == -1) { perror("Cannot initialize semaphores"); exit(EXIT_FAILURE); } sem_ops.val = buffer_size-1; if(semctl(semarray,0,SETVAL,sem_ops) == -1) { perror("Error while initializing mad_decoder semaphore"); if(semctl(semarray,0,IPC_RMID) == -1) perror("Error while destroying semaphores"); goto out; //exit(EXIT_FAILURE); } sem_ops.val = 0; if(semctl(semarray,1,SETVAL,sem_ops) == -1) { perror("Error while initializing mad_decoder semaphore"); if(semctl(semarray,0,IPC_RMID) == -1) perror("Error while destroying semaphores"); goto out; //exit(EXIT_FAILURE); } sem_ops.val = 0; if(semctl(semarray,2,SETVAL,sem_ops) == -1) { perror("Error while initializing mad_decoder semaphore"); if(semctl(semarray,0,IPC_RMID) == -1) perror("Error while destroying semaphores"); goto out; //exit(EXIT_FAILURE); } /* Shared Memory */ mem_key = ftok(argv[0],1); shm_id = shmget(mem_key,buffer_size * sizeof(output_frame), IPC_CREAT | S_IREAD | S_IWRITE); if(shm_id == -1) { perror("Cannot initialize shared buffer"); goto out; //exit(EXIT_FAILURE); } Output_Queue = shmat(shm_id,NULL,0); if(*(int *)Output_Queue == -1) { perror("Error while attaching shared buffer to mad_decoder"); if(shmctl(shm_id,IPC_RMID,NULL)) perror("Cannot destroy shared buffer"); goto out; //exit(EXIT_FAILURE); } static int n; for(n=0;n<buffer_size;n++) { memset((Output_Queue+n)->data,'\0',4608); memset((Output_Queue+n)->time,'\0',80); (Output_Queue+n)->length = 0; (Output_Queue+n)->seconds = 0; (Output_Queue+n)->num_frames = 0; } frames_key = ftok(argv[0],2); frames_id = shmget(frames_key,buffer_size * sizeof(decoded_frames), IPC_CREAT | S_IREAD | S_IWRITE); if(frames_id == -1) { perror("Cannot initialize shared frames counter"); goto out; //exit(EXIT_FAILURE); } Decoded_Frames = shmat(frames_id,NULL,0); if(*(int *)Decoded_Frames == -1) { perror("Error while attaching shared frames counter to mad_decoder"); if(shmctl(frames_id,IPC_RMID,NULL)) perror("Cannot destroy shared frames counter"); goto out; //exit(EXIT_FAILURE); } Decoded_Frames->is_http = 0; Decoded_Frames->is_file = 0; } else { ao_initialize(); check_default_play_device(); } if (!(options.opt & MPG321_REMOTE_PLAY)) { handle_signals(-1); /* initialize signal handler */ remote_input_buf[0] = '\0'; } if (!(options.opt & MPG321_QUIET_PLAY)) mpg123_boilerplate(); if (options.opt & MPG321_REMOTE_PLAY) { printf ("@R MPG123\n"); if(options.opt & MPG321_ENABLE_BUFFER) { #ifdef HAVE_ALSA init_alsa_volume_control("default"); /* For the moment use "default", it works on most of the systems. Tested in Debian,Fedora,Ubuntu,RedHat,CentOS,Gentoo */ if(options.volume != MAD_F_ONE) mpg321_alsa_set_volume((long)options.volume*volume_max/100); #endif } } /* Fork here. */ if(options.opt & MPG321_ENABLE_BUFFER) { output_pid = fork(); if(output_pid == -1) { perror("Error while forking output process"); goto out; /* Release shared memeory and semaphores */ // exit(EXIT_FAILURE); } if(output_pid == 0) { frame_buffer_p(); exit(EXIT_SUCCESS); } signal(SIGUSR1,handle_signals); if(!(options.opt & MPG321_REMOTE_PLAY)) { #ifdef HAVE_ALSA init_alsa_volume_control("default"); if(options.volume != MAD_F_ONE) mpg321_alsa_set_volume((long)options.volume*volume_max/100); #endif } } if( (options.volume != MAD_F_ONE) && !(options.opt & MPG321_ENABLE_BUFFER)) { options.volume = mad_f_tofixed((long)options.volume/100.0); } else{ options.volume = MAD_F_ONE; /* When using the buffer options.volume when decoding each frame should be equal to MAD_F_ONE */ // options.volume = mad_f_tofixed((long)100.0/100.0); } if (!(options.opt & MPG321_REMOTE_PLAY)) { if(options.opt & MPG321_ENABLE_BASIC) { /* Now create and detach the basic controls thread */ sem_init(&main_lock,0,0); pthread_create(&keyb_thread,NULL,read_keyb,NULL); pthread_detach(keyb_thread); } } if(set_xterm) { tty_control(); get_term_title(title); }else { if (!(options.opt & MPG321_REMOTE_PLAY)) { if (tcgetattr(0, &terminal_settings) < 0) perror("tcgetattr()"); memcpy(&old_terminal_settings, &terminal_settings, sizeof(struct termios)); /* Early thread start */ sem_post(&main_lock); } } /* Play the mpeg files or zip it! */ while((currentfile = get_next_file(pl, &playbuf))) { //printf("Current File: %s\n",currentfile); if (quit_now) break; signal(SIGINT, SIG_DFL); playbuf.buf = NULL; playbuf.fd = -1; playbuf.length = 0; playbuf.done = 0; playbuf.num_frames = 0; current_frame = 0; playbuf.max_frames = -1; strncpy(playbuf.filename,currentfile, PATH_MAX); playbuf.filename[PATH_MAX-1] = '\0'; if (status == MPG321_PLAYING || status == MPG321_STOPPED) file_change = 1; mad_timer_reset(&playbuf.duration); mad_timer_reset(¤t_time); id3struct = NULL; if (!(options.opt & MPG321_QUIET_PLAY) && file_change) { /* id3struct = id3_file_open (currentfile, ID3_FILE_MODE_READONLY);*/ if (id3struct == NULL) get_id3_info(currentfile, &id3struct, &id3tag); if(id3tag) show_id3(id3tag); } scrobbler_time = -1; if(options.opt & MPG321_USE_SCROBBLER) { if(id3struct == NULL) get_id3_info(currentfile,&id3struct,&id3tag); if (id3tag) { char emptystring[31], emptyyear[5] = " "; int i; if(parse_id3(scrobbler_args, id3tag)) { memset(emptystring, ' ', 30); emptystring[30] = '\0'; if((options.opt & MPG321_VERBOSE_PLAY) && (options.opt & MPG321_USE_SCROBBLER)) { fprintf(stderr, "\nPreparing for the AudioScrobbler:\n"); for(i = 0; i < 6; i++) { if(scrobbler_args[i] == NULL) scrobbler_args[i] = ( i == 3 ? emptyyear: emptystring); fprintf(stderr, "- %s\n", scrobbler_args[i]); } } } } } if (options.opt & MPG321_REMOTE_PLAY && file_change) { if(id3struct == NULL) get_id3_info(currentfile, &id3struct, &id3tag); if(id3tag) { if (!show_id3(id3tag)) { /* This shouldn't be necessary, but it appears that libid3tag doesn't necessarily know if there are no id3 tags on a given mp3 */ char * basec = strdup(currentfile); char * basen = basename(basec); char * dot = strrchr(basen, '.'); if (dot) *dot = '\0'; printf("@I %s\n", basen); free(basec); } } else { char * basec = strdup(currentfile); char * basen = basename(basec); char * dot = strrchr(basen, '.'); if (dot) *dot = '\0'; printf("@I %s\n", basen); free(basec); } } if(id3struct != NULL) id3_file_close(id3struct); /* Create the MPEG stream */ /* Check if source is on the network */ if((fd = raw_open(currentfile)) != 0 || (fd = http_open(currentfile)) != 0 || (fd = ftp_open(currentfile)) != 0) { playbuf.fd = fd; playbuf.buf = malloc(BUF_SIZE); playbuf.length = BUF_SIZE; if(options.opt & MPG321_ENABLE_BUFFER) { Decoded_Frames->is_http = 1; Decoded_Frames->is_file = 0; } calc_http_length(&playbuf); mad_decoder_init(&decoder, &playbuf, read_from_fd, read_header, /*filter*/0, output, handle_error, /* message */ 0); } /* Check if we are to use stdin for input */ else if(strcmp(currentfile, "-") == 0) { playbuf.fd = fileno(stdin); playbuf.buf = malloc(BUF_SIZE); playbuf.length = BUF_SIZE; mad_decoder_init(&decoder, &playbuf, read_from_fd, read_header, /*filter*/0, output, handle_error, /* message */ 0); } /* currentfile is a local file (presumably.) mmap() it */ else { struct stat stat; if((fd = open(currentfile, O_RDONLY)) == -1) { mpg321_error(currentfile); /* Restore TTY from keyboard reader thread */ if(options.opt & MPG321_ENABLE_BASIC) if (tcsetattr(0, TCSANOW, &old_terminal_settings) < 0) perror("tcsetattr ICANON"); if(set_xterm) { set_tty_restore(); osc_print(0,0,title); if (ctty) fclose(ctty); } if( options.opt & MPG321_REMOTE_PLAY) if(remote_restart) { clear_remote_file(pl); /* If restart is enabled, restart remote shell when file doesn't exist*/ continue; } if(options.opt & MPG321_ENABLE_BUFFER) goto out; else exit(1); /* mpg123 stops immediately if it can't open a file */ /* If sth goes wrong break!!!*/ break; } if(options.opt & MPG321_ENABLE_BUFFER) { Decoded_Frames->is_http = 0; Decoded_Frames->is_file = 1; } if(fstat(fd, &stat) == -1) { mpg321_error(currentfile); close(fd); continue; } if (!S_ISREG(stat.st_mode)) { if(S_ISFIFO(stat.st_mode)) { fallback = 1; goto fall_back_to_read_from_fd; } close(fd); continue; } retval = calc_length(currentfile, &playbuf); //FIXME Check also if it is an mp3 file. If not break and go to the next file possible if(retval < 0) { if(options.opt & MPG321_REMOTE_PLAY) { fprintf(stderr,"@E Corrupted file: %s\n",currentfile); close(fd); if(remote_restart) { clear_remote_file(pl); /* If restart is enabled, restart remote shell when file is corrupted */ continue; } break; } mpg321_error(currentfile); close(fd); // break; //FIXME Break and stop OR continue the playlist ???? continue; } if((options.opt & MPG321_VERBOSE_PLAY) && (options.opt & MPG321_USE_SCROBBLER)) fprintf(stderr, "Track duration: %ld seconds\n",playbuf.duration.seconds); if(options.opt & MPG321_USE_SCROBBLER) scrobbler_set_time(playbuf.duration.seconds); if ((options.maxframes != -1) && (options.maxframes <= playbuf.num_frames)) { playbuf.max_frames = options.maxframes; } playbuf.frames = malloc((playbuf.num_frames + 1) * sizeof(void*)); playbuf.times = malloc((playbuf.num_frames + 1) * sizeof(mad_timer_t)); #ifdef __uClinux__ if((playbuf.buf = mmap(0, playbuf.length, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) #else if((playbuf.buf = mmap(0, playbuf.length, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) #endif { mpg321_error(currentfile); continue; } playbuf.frames[0] = playbuf.buf; mad_decoder_init(&decoder, &playbuf, read_from_mmap, read_header, /*filter*/0, output, handle_error, /* message */ 0); fall_back_to_read_from_fd: //FIXME. Reported that on some embedded systems with low memory, less than 16MB doesn't work properly. if(fallback) { playbuf.fd = fd; playbuf.buf = malloc(BUF_SIZE); playbuf.length = BUF_SIZE; mad_decoder_init(&decoder, &playbuf, read_from_fd, read_header, /*filter*/0, output, handle_error, /* message */ 0); fallback = 1; } } if(!(options.opt & MPG321_QUIET_PLAY))/*zip it!!!*/ { /* Because dirname might modify the argument */ char * dirc = strdup(currentfile); char * basec = strdup(currentfile); char * basen = basename(basec); char * dirn = dirname(dirc); /* make sure that the file has a pathname; otherwise don't print out a Directory: listing */ if(strchr(currentfile, '/') && strncmp(old_dir, dirn, PATH_MAX) != 0) { /* Print information about the file */ fprintf(stderr, "\n"); fprintf(stderr,"Directory: %s\n", dirn); strncpy(old_dir, dirn, PATH_MAX); old_dir[PATH_MAX-1] = '\0'; } /* print a newline between different songs only, not after Directory: listing */ else { fprintf(stderr, "\n"); } fprintf(stderr,"Playing MPEG stream from %s ...\n", basen); /* Printing xterm title */ if(set_xterm) { osc_print(0,0,basen); } free(dirc); free(basec); } signal(SIGINT, handle_signals); signal(SIGCHLD, handle_sigchld); /*Give control back so that we can implement SIG's*/ if(set_xterm) { set_tty_restore(); if (tcgetattr(0, &terminal_settings) < 0) perror("tcgetattr()"); memcpy(&old_terminal_settings, &terminal_settings, sizeof(struct termios)); /* disable canonical mode processing in the line discipline driver */ terminal_settings.c_lflag &= ~(ICANON | ECHO); /* apply our new settings */ if (tcsetattr(0, TCSANOW, &terminal_settings) < 0) perror("tcsetattr ICANON"); if(options.opt & MPG321_ENABLE_BASIC) { /* Late thread start */ sem_post(&main_lock); } } /* Every time the user gets us to rewind, we exit decoding, reinitialize it, and re-start it */ if(options.opt & MPG321_ENABLE_BUFFER) { Decoded_Frames->total_decoded_frames = 0; Decoded_Frames->done = 0; } while (1) { decoder.options |= MAD_OPTION_IGNORECRC; mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC); if(options.opt & MPG321_ENABLE_BUFFER) { static struct sembuf start_sops = {2,-1,0}; semop(semarray,&start_sops,1); mad_decoder_position = 0; output_buffer_position = 0; union semun sem_ops; sem_ops.val = 0; semctl(semarray,2,SETVAL,sem_ops); Decoded_Frames->total_decoded_frames = 0; Decoded_Frames->done = 0; Decoded_Frames->is_http = 0; Decoded_Frames->is_file = 0; } /* if we're rewinding on an mmap()ed stream */ if(status == MPG321_REWINDING && playbuf.fd == -1) { mad_decoder_init(&decoder, &playbuf, read_from_mmap, read_header, /*filter*/0, output, /*error*/0, /* message */ 0); } else break; } if (!(options.opt & MPG321_QUIET_PLAY)) { char time_formatted[11]; mad_timer_string(current_time, time_formatted, "%.1u:%.2u", MAD_UNITS_MINUTES, MAD_UNITS_SECONDS, 0); fprintf(stderr," \r"); fprintf(stderr, "\n[%s] Decoding of %s finished.\n",time_formatted, basename(currentfile)); /* Report total decoded seconds. Maybe for the frame buffer report only total played time?? */ } if (options.opt & MPG321_REMOTE_PLAY && status == MPG321_STOPPED) { clear_remote_file(pl); } mad_decoder_finish(&decoder); if (playbuf.frames) free(playbuf.frames); if (playbuf.times) free(playbuf.times); if (playbuf.fd == -1) { munmap(playbuf.buf, playbuf.length); close(fd); } else { free(playbuf.buf); if (playbuf.fd != fileno(stdin)) close(playbuf.fd); } } if(!(options.opt & MPG321_ENABLE_BUFFER)) { if(playdevice) ao_close(playdevice); ao_shutdown(); } if (!(options.opt & MPG321_REMOTE_PLAY)) { if(options.opt & MPG321_ENABLE_BASIC) { pflag = 1; /* Restore TTY from keyboard reader thread */ if (tcsetattr(0, TCSANOW, &old_terminal_settings) < 0) perror("tcsetattr ICANON"); } } /*Restoring TTY*/ if(set_xterm) { set_tty_restore(); osc_print(0,0,title); if (ctty) fclose(ctty); } out: if(options.opt & MPG321_ENABLE_BUFFER) { if(kill(output_pid,SIGUSR1) == -1) perror("Error while stopping output process"); static int wstatus; wait(&wstatus); if(wstatus == -1) perror("Error while waiting for output process to exit"); if(semctl(semarray,0,IPC_RMID) == -1) perror("Error while destroying semaphores"); if(shmdt(Output_Queue) == -1) perror("Error while detaching shared buffer"); if(shmctl(shm_id,IPC_RMID,NULL)) perror("Cannot destroy shared buffer"); if(shmctl(frames_id,IPC_RMID,NULL)) perror("Cannot destroy shared buffer"); } return(0); }
void _parse_wav(PerlIO *infile, Buffer *buf, char *file, uint32_t file_size, HV *info, HV *tags) { uint32_t offset = 12; while ( offset < file_size - 8 ) { char chunk_id[5]; uint32_t chunk_size; // Verify we have at least 8 bytes if ( !_check_buf(infile, buf, 8, WAV_BLOCK_SIZE) ) { return; } strncpy( chunk_id, (char *)buffer_ptr(buf), 4 ); chunk_id[4] = '\0'; buffer_consume(buf, 4); chunk_size = buffer_get_int_le(buf); // Adjust for padding if ( chunk_size % 2 ) { chunk_size++; } offset += 8; DEBUG_TRACE("%s size %d\n", chunk_id, chunk_size); // Seek past data, everything else we parse // XXX: Are there other large chunks we should ignore? if ( !strcmp( chunk_id, "data" ) ) { SV **bitrate; my_hv_store( info, "audio_offset", newSVuv(offset) ); my_hv_store( info, "audio_size", newSVuv(chunk_size) ); // Calculate duration, unless we already know it (i.e. from 'fact') if ( !my_hv_fetch( info, "song_length_ms" ) ) { bitrate = my_hv_fetch( info, "bitrate" ); if (bitrate != NULL) { my_hv_store( info, "song_length_ms", newSVuv( (chunk_size / (SvIV(*bitrate) / 8.)) * 1000 ) ); } } // sanity check size, this is inside the data chunk code // to support setting audio_offset even when the data size is wrong if (chunk_size > file_size - offset) { DEBUG_TRACE("data size > file_size, skipping\n"); return; } // Seek past data if there are more chunks after it if ( file_size > offset + chunk_size ) { PerlIO_seek(infile, offset + chunk_size, SEEK_SET); } buffer_clear(buf); } else if ( !strcmp( chunk_id, "id3 " ) || !strcmp( chunk_id, "ID3 " ) || !strcmp( chunk_id, "ID32" ) ) { // Read header to verify version unsigned char *bptr = buffer_ptr(buf); if ( (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') && bptr[3] < 0xff && bptr[4] < 0xff && bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80 ) { // Start parsing ID3 from offset parse_id3(infile, file, info, tags, offset, file_size); } // Seek past ID3 and clear buffer PerlIO_seek(infile, offset + chunk_size, SEEK_SET); buffer_clear(buf); } else { // sanity check size if (chunk_size > file_size - offset) { DEBUG_TRACE("chunk_size > file_size, skipping\n"); return; } // Make sure we have enough data if ( !_check_buf(infile, buf, chunk_size, WAV_BLOCK_SIZE) ) { return; } if ( !strcmp( chunk_id, "fmt " ) ) { _parse_wav_fmt(buf, chunk_size, info); } else if ( !strcmp( chunk_id, "LIST" ) ) { _parse_wav_list(buf, chunk_size, tags); } else if ( !strcmp( chunk_id, "PEAK" ) ) { _parse_wav_peak(buf, chunk_size, info, 0); } else if ( !strcmp( chunk_id, "fact" ) ) { // A 4-byte fact chunk in a non-PCM wav is the number of samples // Use it to calculate duration if ( chunk_size == 4 ) { uint32_t num_samples = buffer_get_int_le(buf); SV **samplerate = my_hv_fetch( info, "samplerate" ); if (samplerate != NULL) { my_hv_store( info, "song_length_ms", newSVuv( (num_samples * 1000) / SvIV(*samplerate) ) ); } } else { // Unknown, skip it buffer_consume(buf, chunk_size); } } else { if ( !strcmp(chunk_id, "SAUR") // Wavosour data chunk || !strcmp(chunk_id, "otom") // Wavosaur? || !strcmp(chunk_id, "PAD ") // Padding ) { // Known chunks to skip } else { // Warn about unknown chunks so we can investigate them PerlIO_printf(PerlIO_stderr(), "Unhandled WAV chunk %s size %d (skipped)\n", chunk_id, chunk_size); } buffer_consume(buf, chunk_size); } } offset += chunk_size; } }
void _parse_aiff(PerlIO *infile, Buffer *buf, char *file, uint32_t file_size, HV *info, HV *tags) { uint32_t offset = 12; while ( offset < file_size - 8 ) { char chunk_id[5]; int chunk_size; // Verify we have at least 8 bytes if ( !_check_buf(infile, buf, 8, WAV_BLOCK_SIZE) ) { return; } strncpy( chunk_id, (char *)buffer_ptr(buf), 4 ); chunk_id[4] = '\0'; buffer_consume(buf, 4); chunk_size = buffer_get_int(buf); // Adjust for padding if ( chunk_size % 2 ) { chunk_size++; } offset += 8; DEBUG_TRACE("%s size %d\n", chunk_id, chunk_size); // Seek past SSND, everything else we parse // XXX: Are there other large chunks we should ignore? if ( !strcmp( chunk_id, "SSND" ) ) { my_hv_store( info, "audio_offset", newSVuv(offset) ); my_hv_store( info, "audio_size", newSVuv(chunk_size) ); // Seek past data if there are more chunks after it if ( file_size > offset + chunk_size ) { PerlIO_seek(infile, offset + chunk_size, SEEK_SET); } buffer_clear(buf); } else if ( !strcmp( chunk_id, "id3 " ) || !strcmp( chunk_id, "ID3 " ) || !strcmp( chunk_id, "ID32" ) ) { // Read header to verify version unsigned char *bptr = buffer_ptr(buf); if ( (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') && bptr[3] < 0xff && bptr[4] < 0xff && bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80 ) { // Start parsing ID3 from offset parse_id3(infile, file, info, tags, offset, file_size); } // Seen ID3 chunks with the chunk size in little-endian instead of big-endian if (chunk_size < 0 || offset + chunk_size > file_size) { break; } // Seek past ID3 and clear buffer DEBUG_TRACE("Seeking past ID3 to %d\n", offset + chunk_size); PerlIO_seek(infile, offset + chunk_size, SEEK_SET); buffer_clear(buf); } else { // Make sure we have enough data if ( !_check_buf(infile, buf, chunk_size, WAV_BLOCK_SIZE) ) { return; } if ( !strcmp( chunk_id, "COMM" ) ) { _parse_aiff_comm(buf, chunk_size, info); } else if ( !strcmp( chunk_id, "PEAK" ) ) { _parse_wav_peak(buf, chunk_size, info, 1); } else { PerlIO_printf(PerlIO_stderr(), "Unhandled AIFF chunk %s size %d (skipped)\n", chunk_id, chunk_size); buffer_consume(buf, chunk_size); } } offset += chunk_size; } }