2021-09-30 Fred Gleason <fredg@paravelsystems.com>

* 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 <fredg@paravelsystems.com>
This commit is contained in:
Fred Gleason 2021-09-29 17:05:25 -04:00
parent 429a41deac
commit b69efb80ab
3 changed files with 226 additions and 548 deletions

View File

@ -20805,3 +20805,8 @@
2021-09-16 Fred Gleason <fredg@paravelsystems.com>
* Modified the mode of files generated by the
'SaveWebgetFilesDirectory=' directive 0440 to 0664.
2021-09-30 Fred Gleason <fredg@paravelsystems.com>
* 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.

View File

@ -2,7 +2,7 @@
//
// A class for handling audio files.
//
// (C) Copyright 2002-2018 Fred Gleason <fredg@paravelsystems.com>
// (C) Copyright 2002-2021 Fred Gleason <fredg@paravelsystems.com>
//
// 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 <stdlib.h>
#include <stdio.h>
@ -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<MAX_TIMERS) {
cart_timer_label[index]=label;
}
}
*/
unsigned RDWaveFile::getCartTimerSample(int index) const
{
@ -1767,14 +1752,6 @@ unsigned RDWaveFile::getCartTimerSample(int index) const
return 0;
}
/*
void RDWaveFile::setCartTimerSample(int index,unsigned sample)
{
if(index<MAX_TIMERS) {
cart_timer_sample[index]=sample;
}
}
*/
QString RDWaveFile::getCartURL() const
{
@ -2210,11 +2187,6 @@ QString RDWaveFile::formatText(RDWaveFile::Format fmt)
return ret;
}
enum Format {Pcm8=0,Pcm16=1,Float32=2,MpegL1=3,MpegL2=4,MpegL3=5,
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};
QString RDWaveFile::typeText(RDWaveFile::Type type)
{
@ -3664,552 +3636,255 @@ void RDWaveFile::ReadId3Metadata()
bool RDWaveFile::GetMpegHeader(int fd,int offset)
{
unsigned char buffer[4];
int n;
//
// See:
//
// http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
// https://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header
//
// for helpful information regarding the arcana of interpreting MPEG
// header data structures. Sometimes, fasting and prayer can help too!
//
//
// Bitrate table
//
// __bitrates[VERSION_INDEX][LAYER_INDEX][BITRATE_INDEX]
//
// N.B. Bitrate values in this table are in thousands of bits per second.
// Certain other data structures in this class require bits per second!
//
static int __bitrates[4][4][16]={
{ // *** MPEG 2.5 ***
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Reserved Layer
{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1}, // Layer III
{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1}, // Layer II
{0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,-1} // Layer I
},
{ // *** Invalid MPEG Version ***
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Reserved Layer
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Layer III
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Layer II
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1} // Layer I
},
{ // *** MPEG 2 ***
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Reserved Layer
{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1}, // Layer III
{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1}, // Layer II
{0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,-1} // Layer I
},
{ // *** MPEG 1 ***
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Reserved Layer
{0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,-1}, // Layer III
{0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,-1},// Layer II
{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,-1} // Layer I
}
};
//
// Sample Rate Table
//
// __samplerates[VERSION_INDEX][SAMPRATE_INDEX]
static int __samplerates[4][4]={
{11025,12000,8000,-1}, // *** MPEG 2.5 ***
{-1,-1,-1,-1}, // *** Invalid MPEG Version ***
{22050,24000,16000,-1}, // *** MPEG 2 ***
{44100,48000,32000,-1} // *** MPEG 1 ***
};
//
// Channels table
//
// __channels[MODE_INDEX]
static int __channels[4]={2,2,2,1};
//
// Head Mode Table
//
// __head_modes[MODE_INDEX];
static int __head_modes[4]={ACM_MPEG_STEREO,ACM_MPEG_JOINTSTEREO,
ACM_MPEG_DUALCHANNEL,ACM_MPEG_SINGLECHANNEL};
//
// Layer Numbers Table
//
// __layer_numbers[LAYER_INDEX]
static int __layer_numbers[4]={0,3,2,1};
//
// Samples per MPEG Frame Table
//
// __samples_per_frames[VERSION_INDEX][LAYER_INDEX]
static int __samples_per_frame[4][4]={
{0,576,1152,384}, // *** MPEG 2.5 ***
{0,0,0,384}, // *** Invalid MPEG Version ***
{0,576,1152,384}, // *** MPEG 2 ***
{0,1152,1152,384} // *** MPEG 1 ***
};
//
// Side Data Offset Table (Layer III only)
//
// __side_data_offset[VERSION_INDEX][MODE]
static int __side_data_offset[4][4]={
{17,17,17,9}, // *** MPEG 2.5 ***
{0,0,0,0}, // *** Invalid MPEG Version ***
{17,17,17,9}, // *** MPEG 2 ***
{32,32,32,17} // *** MPEG 1 ***
};
char header[4];
char *frame=NULL;
ssize_t n;
// off_t frame_start;
int version_index;
int layer_index;
int bitrate_index;
int samprate_index;
int padding=0;
int mode_index;
int frame_size;
int total_frame_quan=-1;
lseek(fd,offset,SEEK_SET);
if((n=read(fd,buffer,4))!=4) {
if((n=read(fd,header,4))!=4) {
return false;
}
// frame_start=lseek(fd,0,SEEK_CUR)-4;
//
// Sync bits
//
if(((0xFF&header[0])!=0xFF)||((0xE0&header[1])!=0xE0)) {
return false;
}
//
// Sync
// MPEG Audio Version ID
//
if((buffer[0]!=0xFF)||((buffer[1]&0xE0)!=0xE0)) {
version_index=(0x18&header[1])>>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;
}

View File

@ -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 <fredg@paravelsystems.com>
// (C) Copyright 2002-2021 Fred Gleason <fredg@paravelsystems.com>
//
// 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 <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <qobject.h>
#include <qstring.h>
#include <qdatetime.h>
#include <qfile.h>
#include <vector>
#include <QDateTime>
#include <QFile>
#include <QObject>
#include <QString>
#ifdef HAVE_VORBIS
#include <vorbis/vorbisfile.h>
@ -37,10 +39,9 @@
#endif // HAVE_VORBIS
#include <rdmp4.h>
#include <rdwavedata.h>
#include <rdringbuffer.h>
#include <rdsettings.h>
#include <rdwavedata.h>
//
// 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];