From b69efb80ab5e216c49e4d9214780eb657e3ae975 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 29 Sep 2021 17:05:25 -0400 Subject: [PATCH] 2021-09-30 Fred Gleason * Overhauled the code for reading MPEG frame headers in 'RDWaveFile'. * Fixed a bug in the rdxport 'Import' service that could result in incorrect end marker placement when processing variable bit rate (VBR) MPEG files. Signed-off-by: Fred Gleason --- ChangeLog | 5 + lib/rdwavefile.cpp | 745 +++++++++++++-------------------------------- lib/rdwavefile.h | 24 +- 3 files changed, 226 insertions(+), 548 deletions(-) diff --git a/ChangeLog b/ChangeLog index cb65be1e..c02834a3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -20805,3 +20805,8 @@ 2021-09-16 Fred Gleason * Modified the mode of files generated by the 'SaveWebgetFilesDirectory=' directive 0440 to 0664. +2021-09-30 Fred Gleason + * Overhauled the code for reading MPEG frame headers in 'RDWaveFile'. + * Fixed a bug in the rdxport 'Import' service that could result in + incorrect end marker placement when processing variable bit rate + (VBR) MPEG files. diff --git a/lib/rdwavefile.cpp b/lib/rdwavefile.cpp index d6534fcb..8b3656d8 100644 --- a/lib/rdwavefile.cpp +++ b/lib/rdwavefile.cpp @@ -2,7 +2,7 @@ // // A class for handling audio files. // -// (C) Copyright 2002-2018 Fred Gleason +// (C) Copyright 2002-2021 Fred Gleason // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU Library General Public License @@ -17,7 +17,6 @@ // License along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // -// #include #include @@ -91,7 +90,7 @@ RDWaveFile::RDWaveFile(QString file_name) head_emphasis=1; head_flags=0; pts=0; - mpeg_id=RDWaveFile::NonMpeg; + // mpeg_id=RDWaveFile::NonMpeg; mpeg_frame_size=0; id3v1_tag=false; id3v2_tag[0]=false; @@ -269,9 +268,6 @@ bool RDWaveFile::openWave(RDWaveData *data) } data_length=wave_file.size()-data_start; sample_length=1152*(data_length/mpeg_frame_size); - ext_time_length=(unsigned)(1000.0*(double)sample_length/ - (double)samples_per_sec); - time_length=ext_time_length/1000; lseek(wave_file.handle(),data_start,SEEK_SET); format_chunk=true; } @@ -328,9 +324,6 @@ bool RDWaveFile::openWave(RDWaveData *data) } data_start=id3v2_offset[0]; sample_length=1152*(data_length/mpeg_frame_size); - ext_time_length= - (unsigned)(1000.0*(double)sample_length/(double)samples_per_sec); - time_length=ext_time_length/1000; data_chunk=true; lseek(wave_file.handle(),data_start,SEEK_SET); format_chunk=true; @@ -799,7 +792,7 @@ void RDWaveFile::closeWave(int samples) head_emphasis=1; head_flags=0; pts=0; - mpeg_id=RDWaveFile::NonMpeg; + // mpeg_id=RDWaveFile::NonMpeg; mpeg_frame_size=0; id3v1_tag=false; id3v2_tag[0]=false; @@ -1750,14 +1743,6 @@ QString RDWaveFile::getCartTimerLabel(int index) const return QString(""); } -/* -void RDWaveFile::setCartTimerLabel(int index,QString label) -{ - if(index>3; + if(version_index==0x01) { // Illegal Version ID return false; } - // - // MPEG Id - // - if((buffer[1]&0x08)==0) { - mpeg_id=RDWaveFile::Mpeg2; - } - else { - mpeg_id=RDWaveFile::Mpeg1; - } - // // Layer // - switch((buffer[1]&0x06)>>1) { - case 1: - head_layer=3; - break; - - case 2: - head_layer=2; - break; - - case 3: - head_layer=1; - break; - - default: - return false; + layer_index=(0x06&header[1])>>1; + if(layer_index==0x00) { // Illegal Layer + return false; } + head_layer=__layer_numbers[layer_index]; // // Bitrate // - switch(mpeg_id) { - case RDWaveFile::Mpeg1: - switch(head_layer) { - case 1: - switch(buffer[2]>>4) { - case 1: - head_bit_rate=32000; - break; - - case 2: - head_bit_rate=64000; - break; - - case 3: - head_bit_rate=96000; - break; - - case 4: - head_bit_rate=128000; - break; - - case 5: - head_bit_rate=160000; - break; - - case 6: - head_bit_rate=192000; - break; - - case 7: - head_bit_rate=224000; - break; - - case 8: - head_bit_rate=256000; - break; - - case 9: - head_bit_rate=288000; - break; - - case 10: - head_bit_rate=320000; - break; - - case 11: - head_bit_rate=352000; - break; - - case 12: - head_bit_rate=384000; - break; - - case 13: - head_bit_rate=416000; - break; - - case 14: - head_bit_rate=448000; - break; - - default: - return false; - } - break; - - case 2: - switch(buffer[2]>>4) { - case 1: - head_bit_rate=32000; - break; - - case 2: - head_bit_rate=48000; - break; - - case 3: - head_bit_rate=56000; - break; - - case 4: - head_bit_rate=64000; - break; - - case 5: - head_bit_rate=80000; - break; - - case 6: - head_bit_rate=96000; - break; - - case 7: - head_bit_rate=112000; - break; - - case 8: - head_bit_rate=128000; - break; - - case 9: - head_bit_rate=160000; - break; - - case 10: - head_bit_rate=192000; - break; - - case 11: - head_bit_rate=224000; - break; - - case 12: - head_bit_rate=256000; - break; - - case 13: - head_bit_rate=320000; - break; - - case 14: - head_bit_rate=384000; - break; - - default: - return false; - break; - } - break; - - case 3: - switch(buffer[2]>>4) { - case 1: - head_bit_rate=32000; - break; - - case 2: - head_bit_rate=40000; - break; - - case 3: - head_bit_rate=48000; - break; - - case 4: - head_bit_rate=56000; - break; - - case 5: - head_bit_rate=64000; - break; - - case 6: - head_bit_rate=80000; - break; - - case 7: - head_bit_rate=96000; - break; - - case 8: - head_bit_rate=112000; - break; - - case 9: - head_bit_rate=128000; - break; - - case 10: - head_bit_rate=160000; - break; - - case 11: - head_bit_rate=192000; - break; - - case 12: - head_bit_rate=224000; - break; - - case 13: - head_bit_rate=256000; - break; - - case 14: - head_bit_rate=320000; - break; - - default: - return false; - break; - } - break; - } - break; - - case RDWaveFile::Mpeg2: - switch(head_layer) { - case 1: - switch(buffer[2]>>4) { - case 1: - head_bit_rate=32000; - break; - - case 2: - head_bit_rate=48000; - break; - - case 3: - head_bit_rate=56000; - break; - - case 4: - head_bit_rate=64000; - break; - - case 5: - head_bit_rate=80000; - break; - - case 6: - head_bit_rate=96000; - break; - - case 7: - head_bit_rate=112000; - break; - - case 8: - head_bit_rate=128000; - break; - - case 9: - head_bit_rate=144000; - break; - - case 10: - head_bit_rate=160000; - break; - - case 11: - head_bit_rate=176000; - break; - - case 12: - head_bit_rate=192000; - break; - - case 13: - head_bit_rate=224000; - break; - - case 14: - head_bit_rate=256000; - break; - - default: - return false; - } - break; - - case 2: - switch(buffer[2]>>4) { - case 1: - head_bit_rate=8000; - break; - - case 2: - head_bit_rate=16000; - break; - - case 3: - head_bit_rate=24000; - break; - - case 4: - head_bit_rate=32000; - break; - - case 5: - head_bit_rate=40000; - break; - - case 6: - head_bit_rate=48000; - break; - - case 7: - head_bit_rate=56000; - break; - - case 8: - head_bit_rate=64000; - break; - - case 9: - head_bit_rate=80000; - break; - - case 10: - head_bit_rate=96000; - break; - - case 11: - head_bit_rate=112000; - break; - - case 12: - head_bit_rate=128000; - break; - - case 13: - head_bit_rate=144000; - break; - - case 14: - head_bit_rate=160000; - break; - - default: - return false; - break; - } - break; - - case 3: - switch(buffer[2]>>4) { - case 1: - head_bit_rate=8000; - break; - - case 2: - head_bit_rate=16000; - break; - - case 3: - head_bit_rate=24000; - break; - - case 4: - head_bit_rate=32000; - break; - - case 5: - head_bit_rate=40000; - break; - - case 6: - head_bit_rate=48000; - break; - - case 7: - head_bit_rate=56000; - break; - - case 8: - head_bit_rate=64000; - break; - - case 9: - head_bit_rate=80000; - break; - - case 10: - head_bit_rate=96000; - break; - - case 11: - head_bit_rate=112000; - break; - - case 12: - head_bit_rate=128000; - break; - - case 13: - head_bit_rate=144000; - break; - - case 14: - head_bit_rate=160000; - break; - - default: - return false; - break; - } - break; - } - break; - - default: - return false; + bitrate_index=(0xF0&header[2])>>4; + if(__bitrates[version_index][layer_index][bitrate_index]<0) { + return false; } + head_bit_rate=1000*__bitrates[version_index][layer_index][bitrate_index]; // - // Sample Rate + // Samplerate // - switch((buffer[2]>>2)&0x03) { - case 0: - switch(mpeg_id) { - case RDWaveFile::Mpeg1: - samples_per_sec=44100; - break; - - case RDWaveFile::Mpeg2: - samples_per_sec=22050; - break; - - default: - break; - } - break; - - case 1: - switch(mpeg_id) { - case RDWaveFile::Mpeg1: - samples_per_sec=48000; - break; - - case RDWaveFile::Mpeg2: - samples_per_sec=24000; - break; - - default: - break; - } - break; - - case 2: - switch(mpeg_id) { - case RDWaveFile::Mpeg1: - samples_per_sec=32000; - break; - - case RDWaveFile::Mpeg2: - samples_per_sec=16000; - break; - - default: - break; - } - break; - - default: - return false; - break; + samprate_index=(0x0C&header[2])>>2; + if((__samplerates[version_index][samprate_index]<0)&&(bitrate_index!=0)) { + return false; } + samples_per_sec=__samplerates[version_index][samprate_index]; + + // + // Padding bit + // + padding=(0x02&header[2])>>1; // // Mode // - switch(buffer[3]>>6) { - case 0: - head_mode=ACM_MPEG_STEREO; - channels=2; - break; - - case 1: - head_mode=ACM_MPEG_JOINTSTEREO; - channels=2; - break; - - case 2: - head_mode=ACM_MPEG_DUALCHANNEL; - channels=2; - break; - - case 3: - head_mode=ACM_MPEG_SINGLECHANNEL; - channels=1; - break; - } + mode_index=(0xC0&header[3])>>6; + head_mode=__head_modes[mode_index]; + channels=__channels[mode_index]; // // Flags // - if((buffer[2]&0x01)!=0) { + head_flags=0; + if((0x01&header[2])!=0) { head_flags|=ACM_MPEG_PRIVATEBIT; - } - if((buffer[3]&0x08)!=0) { + } + if((header[3]&0x08)!=0) { head_flags|=ACM_MPEG_COPYRIGHT; } - if((buffer[3]&0x04)!=0) { + if((header[3]&0x04)!=0) { head_flags|=ACM_MPEG_ORIGINALHOME; } - if((buffer[1]&0x01)!=0) { - head_flags|=ACM_MPEG_PROTECTIONBIT; - } - if(mpeg_id==RDWaveFile::Mpeg1) { + if(version_index==3) { head_flags|=ACM_MPEG_ID_MPEG1; } // - // Frame Size (without padding) + // Frame Size // + if(layer_index==3) { // Layer I + frame_size=(12000*__bitrates[version_index][layer_index][bitrate_index]/ + __samplerates[version_index][samprate_index]+padding)*4; + } + else { // Layers II and III + frame_size=(144000*__bitrates[version_index][layer_index][bitrate_index]/ + __samplerates[version_index][samprate_index])+padding; + } + + // + // Load the frame data + // + frame=new char[frame_size]; + if((n=read(fd,frame,frame_size-4))!=(frame_size-4)) { + delete frame; + return false; + } + + // + // Look for the Xing/Info tag (for VBR data) + // + if(((frame[__side_data_offset[version_index][mode_index]]=='X')&& + (frame[__side_data_offset[version_index][mode_index]+1]=='i')&& + (frame[__side_data_offset[version_index][mode_index]+2]=='n')&& + (frame[__side_data_offset[version_index][mode_index]+3]=='g'))|| + ((frame[__side_data_offset[version_index][mode_index]]=='I')&& + (frame[__side_data_offset[version_index][mode_index]+1]=='n')&& + (frame[__side_data_offset[version_index][mode_index]+2]=='f')&& + (frame[__side_data_offset[version_index][mode_index]+3]=='o'))) { + if((frame[__side_data_offset[version_index][mode_index]+7]&0x01)==0x01) { + total_frame_quan= + 16777216*frame[__side_data_offset[version_index][mode_index]+8]+ + 65536*frame[__side_data_offset[version_index][mode_index]+9]+ + 256*frame[__side_data_offset[version_index][mode_index]+10]+ + frame[__side_data_offset[version_index][mode_index]+11]; + time_length= + total_frame_quan*__samples_per_frame[version_index][layer_index]/ + __samplerates[version_index][samprate_index]; + ext_time_length=1000*total_frame_quan* + __samples_per_frame[version_index][layer_index]/ + __samplerates[version_index][samprate_index]; + } + } + if(total_frame_quan<0) { + // + // No VBR tag, assume CBR + // + sample_length=1152.0*((double)data_length/(144.0*(double)head_bit_rate/ + (double)samples_per_sec)); + ext_time_length=1000.0*(double)sample_length/(double)samples_per_sec; + time_length=ext_time_length/1000; + } mpeg_frame_size=144*head_bit_rate/samples_per_sec; + delete frame; + return true; } diff --git a/lib/rdwavefile.h b/lib/rdwavefile.h index cf244446..c20edf69 100644 --- a/lib/rdwavefile.h +++ b/lib/rdwavefile.h @@ -1,8 +1,8 @@ // rdwavefile.h // -// A class for handling Microsoft WAV files. +// A class for handling audio files. // -// (C) Copyright 2002-2018 Fred Gleason +// (C) Copyright 2002-2021 Fred Gleason // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU Library General Public License @@ -22,14 +22,16 @@ #ifndef RDWAVEFILE_H #define RDWAVEFILE_H -#include #include #include #include -#include -#include -#include -#include + +#include + +#include +#include +#include +#include #ifdef HAVE_VORBIS #include @@ -37,10 +39,9 @@ #endif // HAVE_VORBIS #include - -#include #include #include +#include // // Number of timers allowed in the CartChunk structure. @@ -102,8 +103,7 @@ * In addition to 'FMT' and 'DATA' chunks, chunk types of particular * interest to broadcast applications are supported, including those * specified by the Broadcast Wave File specification (EBU Tech Document - * 3285, with suppliments) and the CartChunk specification (currently - * proposed to become AES standard AES-46). + * 3285, with suppliments) and AES standard AES-46 (aka CartChunk). **/ class RDWaveFile { @@ -112,7 +112,6 @@ class RDWaveFile DolbyAc2=6,DolbyAc3=7,Vorbis=8,Pcm24=9}; enum Type {Unknown=0,Wave=1,Mpeg=2,Ogg=3,Atx=4,Tmc=5,Flac=6,Ambos=7, Aiff=8,M4A=9}; - enum MpegID {NonMpeg=0,Mpeg1=1,Mpeg2=2}; /** * Create an RDWaveFile object. @@ -1100,7 +1099,6 @@ class RDWaveFile unsigned short head_flags; // MPEG header flags unsigned long pts; // The MPEG PTS unsigned ptr_offset_msecs; - RDWaveFile::MpegID mpeg_id; int mpeg_frame_size; bool id3v1_tag; bool id3v2_tag[2];