From ef2fa1d99d6c0e68451e34967892d97d1abd0ef9 Mon Sep 17 00:00:00 2001 From: Thomas Schmitt Date: Sun, 31 Dec 2017 12:15:36 +0100 Subject: [PATCH] Adapting to surplus chunks and subchunks in .wav files --- cdrskin/cdrskin_timestamp.h | 2 +- doc/waveformat.txt | 84 ++++++++++++++++++++++ libburn/libdax_audioxtr.c | 137 ++++++++++++++++++++++++++++-------- 3 files changed, 192 insertions(+), 31 deletions(-) create mode 100644 doc/waveformat.txt diff --git a/cdrskin/cdrskin_timestamp.h b/cdrskin/cdrskin_timestamp.h index 7726796..b3a27d9 100644 --- a/cdrskin/cdrskin_timestamp.h +++ b/cdrskin/cdrskin_timestamp.h @@ -1 +1 @@ -#define Cdrskin_timestamP "2017.09.16.105713" +#define Cdrskin_timestamP "2017.12.31.111507" diff --git a/doc/waveformat.txt b/doc/waveformat.txt new file mode 100644 index 0000000..543145a --- /dev/null +++ b/doc/waveformat.txt @@ -0,0 +1,84 @@ + + Sound extraction for CD-DA burning from .WAV audio file format + +Using information and text snippets +from https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ + in may 2013. The link is now dead. An apparent copy of the page + is 2017 at: http://soundfile.sapp.org/doc/WaveFormat/ +from https://en.wikipedia.org/wiki/WAV + +For libburnia-project.org by Thomas Schmitt +December 2017 + + +The WAVE file format is an application of the Microsoft RIFF container format +for multimedia files. A RIFF file consists of Chunks which contain Subchunks. +The Chunks form a linked list within the file, the Subchunks form a linked +list inside their Chunk. +All numbers are stored in little-endian byte order. + +A .WAV file consists at least of one Chunk with id "RIFF", which contains +one Subchunk with id "fmt " and one with id "data": + +Offset Size Name Description + + 0 4 ChunkID Contains the letters "RIFF" + 4 4 ChunkSize The size of the rest of the chunk following + this field. I.e. the two fields ChunkID and + ChunkSize are not included in this count. + 8 4 Format Contains the letters "WAVE" + + +The "fmt " subchunk describes the sound data's format: + +Offset Size Name Description + + 0 4 Subchunk1ID Contains the letters "fmt " + + 4 4 Subchunk1Size The size of the rest of the Subchunk following + this field. I.e. Subchunk1ID and Subchunk1Size + are not included in this count. + 8 2 AudioFormat PCM = 1 (i.e. Linear quantization) + Values other than 1 indicate some + form of compression. +10 2 NumChannels Mono = 1, Stereo = 2, etc. +12 4 SampleRate 8000, 44100, etc. +16 4 ByteRate == SampleRate * NumChannels * BitsPerSample/8 +20 2 BlockAlign == NumChannels * BitsPerSample/8 + The number of bytes for one sample including + all channels. +22 2 BitsPerSample 8 bits = 8, 16 bits = 16, etc. +More data may follow in this Subchunk if AudioFormat is not PCM. + + +The "data" subchunk contains the size of the data and the actual sound: + +Offset Size Name Description + + 0 4 Subchunk2ID Contains the letters "data" + + 4 4 Subchunk2Size == NumSamples * NumChannels * BitsPerSample/8 + The number of audio data bytes. + 8 * Data The audio data bytes. + + +CD-DA prescribes these "fmt " parameters: + AudioFormat == 1 + SampleRate == 44100 + BitsPerSample == 16 + NumChannels == 2 (stereo) + (little-endian byte order) + +If matching parameters are given in the .WAV file, one can directly use the +data bytes of Subchunk "data" as payload for burning a CD-DA track. + + +Above simple form can be expanded by other Chunks or Subchunks of Chunk "RIFF". +A .wav file appeared which beared a Subchunk "LIST" inside Chunk "RIFF". +Wikipedia mentions Chunks "INFO", "CSET", "JUNK", "PAD ". + +Therefore one should expect such Chunks before Chunk "RIFF" and Subchunks +other than "fmt " and "data" inside the "RIFF" Chunk. +Multiple Chunks "RIFF" and Subchunks "fmt " or "data" per file have not been +seen yet. They would make extraction more cumbersome. + diff --git a/libburn/libdax_audioxtr.c b/libburn/libdax_audioxtr.c index 2f56d67..39f92ed 100644 --- a/libburn/libdax_audioxtr.c +++ b/libburn/libdax_audioxtr.c @@ -55,6 +55,7 @@ int libdax_audioxtr_new(struct libdax_audioxtr **xtr, char *path, int flag) o->bits_per_sample= 0; o->msb_first= 0; + o->wav_data_location= 44; o->wav_subchunk2_size= 0; o->au_data_location= 0; @@ -121,47 +122,123 @@ static int libdax_audioxtr_open(struct libdax_audioxtr *o, int flag) return(1); } +/* @param flag: bit0= sequential file, skip by reading data +*/ +static int libdax_audioxtr_skip(struct libdax_audioxtr *o, + off_t *old_pos, + off_t pos, int flag) +{ + int ret; + size_t to_read; + static char buf[256]; /* Thread safe because the content does not matter */ + + if((flag & 1) || o->fd == 0) { /* stdin */ + while(pos - *old_pos > 0) { + to_read= pos - *old_pos; + if(to_read > sizeof(buf)) + to_read= sizeof(buf); + ret= read(o->fd, buf, to_read); + if(ret < (int) to_read) + return(0); + *old_pos+= to_read; + } + } else { + ret= lseek(o->fd, pos, SEEK_SET); + if(ret == -1) + return(0); + *old_pos= pos; + } + return(1); +} static int libdax_audioxtr_identify_wav(struct libdax_audioxtr *o, int flag) { - int ret; - char buf[45]; + int ret, fmt_seen= 0, data_seen= 0; + off_t pos= 0, old_pos= 0, riff_end= 0; + char buf[16]; + unsigned char *ubuf; /* check wether this is a MS WAVE file .wav */ - /* info used: http://ccrma.stanford.edu/courses/422/projects/WaveFormat/ */ + /* info used: http://ccrma.stanford.edu/courses/422/projects/WaveFormat/ + https://en.wikipedia.org/wiki/WAV + see summary in: doc/waveformat.txt + */ + ubuf= (unsigned char *) buf; - if(o->fd!=0) { - ret= lseek(o->fd,0,SEEK_SET); - if(ret==-1) + /* Look for ChunkID "RIFF" , tolerate other known chunks */ + while(1) { + ret= libdax_audioxtr_skip(o, &old_pos, pos, 0); + if(ret <= 0) return(0); + ret= read(o->fd, buf, 8); + if(ret < 8) + return(0); + old_pos+= 8; + pos= old_pos + libdax_audioxtr_to_int(o, ubuf + 4, 4, 0); + if(pos > 0xffffffff || pos - old_pos < 4) /* Too large or no Format word */ + return(0); + if(strncmp(buf, "RIFF", 4) == 0) + break; + /* Wikipedia mentions these known ChunkId values */ + if(strncmp(buf, "INFO", 4) == 0 || + strncmp(buf, "CSET", 4) == 0 || + strncmp(buf, "JUNK", 4) == 0 || + strncmp(buf, "PAD ", 4) == 0) + continue; + return(0); } - ret= read(o->fd, buf, 44); - if(ret<44) - return(0); - buf[44]= 0; /* as stopper for any string operations */ - if(strncmp(buf,"RIFF",4)!=0) /* ChunkID */ + /* Read RIFF Format header */ + ret= read(o->fd, buf, 4); + if(ret < 4) return(0); - if(strncmp(buf+8,"WAVE",4)!=0) /* Format */ - return(0); - if(strncmp(buf+12,"fmt ",4)!=0) /* Subchunk1ID */ - return(0); - if(buf[16]!=16 || buf[17]!=0 || buf[18]!=0 || buf[19]!=0) /* Subchunk1Size */ - return(0); - if(buf[20]!=1 || buf[21]!=0) /* AudioFormat must be 1 (Linear quantization) */ + old_pos+= 4; + if(strncmp(buf, "WAVE", 4) != 0) /* Format */ return(0); + riff_end= pos; - strcpy(o->fmt,".wav"); - o->msb_first= 0; - o->num_channels= libdax_audioxtr_to_int(o,(unsigned char *) buf+22,2,0); - o->sample_rate= libdax_audioxtr_to_int(o,(unsigned char *) buf+24,4,0); - o->bits_per_sample= libdax_audioxtr_to_int(o,(unsigned char *)buf+34,2,0); - sprintf(o->fmt_info, - ".wav , num_channels=%d , sample_rate=%d , bits_per_sample=%d", - o->num_channels,o->sample_rate,o->bits_per_sample); - o->wav_subchunk2_size= libdax_audioxtr_to_int(o,(unsigned char *)buf+40,4,0); - o->data_size= o->wav_subchunk2_size; - return(1); + /* Look for SubchunkID "fmt " and "data" */ + pos= old_pos; + while(old_pos < riff_end) { + ret= libdax_audioxtr_skip(o, &old_pos, pos, 0); + if(ret <= 0) + return(0); + ret= read(o->fd, buf, 8); + if(ret < 8) + return(0); + old_pos= pos + 8; + pos= old_pos + libdax_audioxtr_to_int(o, ubuf + 4, 4, 0); /* SubchunkSize */ + + if(strncmp(buf,"fmt ", 4) == 0) { + if(pos - old_pos < 16) + return(0); + ret= read(o->fd, buf, 16); + if(ret < 16) + return(0); + old_pos+= 16; + if(buf[0]!=1 || buf[1]!=0) /* AudioFormat (1 = Linear quantization) */ + return(0); + o->msb_first= 0; + o->num_channels= libdax_audioxtr_to_int(o, ubuf + 2 , 2, 0); + o->sample_rate= libdax_audioxtr_to_int(o, ubuf + 4, 4, 0); + o->bits_per_sample= libdax_audioxtr_to_int(o, ubuf + 14, 2, 0); + sprintf(o->fmt_info, + ".wav , num_channels=%d , sample_rate=%d , bits_per_sample=%d", + o->num_channels, o->sample_rate, o->bits_per_sample); + fmt_seen= 1; + + } else if(strncmp(buf,"data", 4) == 0) { + o->wav_data_location= old_pos; + o->wav_subchunk2_size= pos - old_pos; + o->data_size= o->wav_subchunk2_size; + data_seen= 1; + } + if(fmt_seen && data_seen) { + strcpy(o->fmt,".wav"); + return(1); + } + } + return(0); } @@ -256,7 +333,7 @@ static int libdax_audioxtr_init_reading(struct libdax_audioxtr *o, int flag) o->extract_count= 0; if(strcmp(o->fmt,".wav")==0) - ret= lseek(o->fd,44,SEEK_SET); + ret= lseek(o->fd, o->wav_data_location, SEEK_SET); else if(strcmp(o->fmt,".au")==0) ret= lseek(o->fd,o->au_data_location,SEEK_SET); else