[MAD Decoder] Add support for FhG VBRI headers
This header type was missing, causing some Fraunhofer VBR MP3 files to decode the wrong length information. Signed-off-by: Christopher Snowhill <kode54@gmail.com>swiftingly
parent
453e29b2f5
commit
a2a75d6565
|
@ -11,6 +11,7 @@
|
|||
8372C93727C7863700E250C9 /* libmad.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8372C93627C7863700E250C9 /* libmad.a */; };
|
||||
8372C93F27C7904800E250C9 /* libid3tag.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8372C93E27C7904800E250C9 /* libid3tag.a */; };
|
||||
8372C94227C7959000E250C9 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8372C94127C7959000E250C9 /* libz.tbd */; };
|
||||
83F97B6928600F9300A70B97 /* CVbriHeader.c in Sources */ = {isa = PBXBuildFile; fileRef = 83F97B6728600F9300A70B97 /* CVbriHeader.c */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -23,6 +24,8 @@
|
|||
8372C93A27C786DD00E250C9 /* HTTPSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPSource.h; path = ../HTTPSource/HTTPSource.h; sourceTree = "<group>"; };
|
||||
8372C93E27C7904800E250C9 /* libid3tag.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libid3tag.a; path = ../../ThirdParty/libid3tag/lib/libid3tag.a; sourceTree = "<group>"; };
|
||||
8372C94127C7959000E250C9 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
|
||||
83F97B6728600F9300A70B97 /* CVbriHeader.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CVbriHeader.c; sourceTree = "<group>"; };
|
||||
83F97B6828600F9300A70B97 /* CVbriHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CVbriHeader.h; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -42,6 +45,7 @@
|
|||
8372C91A27C785BD00E250C9 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
83F97B6628600F9300A70B97 /* ThirdParty */,
|
||||
8372C93A27C786DD00E250C9 /* HTTPSource.h */,
|
||||
8372C93927C7866B00E250C9 /* Logging.h */,
|
||||
8372C93827C7865A00E250C9 /* Plugin.h */,
|
||||
|
@ -70,6 +74,15 @@
|
|||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
83F97B6628600F9300A70B97 /* ThirdParty */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
83F97B6728600F9300A70B97 /* CVbriHeader.c */,
|
||||
83F97B6828600F9300A70B97 /* CVbriHeader.h */,
|
||||
);
|
||||
path = ThirdParty;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -138,6 +151,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8372C93527C7861300E250C9 /* MADDecoder.m in Sources */,
|
||||
83F97B6928600F9300A70B97 /* CVbriHeader.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
// For gapless playback of mp3s
|
||||
BOOL _foundXingHeader;
|
||||
BOOL _foundLAMEHeader;
|
||||
BOOL _foundVBRIHeader;
|
||||
BOOL _foundID3v2;
|
||||
BOOL _foundiTunSMPB;
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
#import <Accelerate/Accelerate.h>
|
||||
|
||||
#import "CVbriHeader.h"
|
||||
|
||||
@implementation MADDecoder
|
||||
|
||||
#define LAME_HEADER_SIZE ((8 * 5) + 4 + 4 + 8 + 32 + 16 + 16 + 4 + 4 + 8 + 12 + 12 + 8 + 8 + 2 + 3 + 11 + 32 + 32 + 32)
|
||||
|
@ -354,15 +356,31 @@
|
|||
_foundLAMEHeader = YES;
|
||||
break;
|
||||
}
|
||||
} else if('VBRI' == magic) {
|
||||
struct VbriHeader *vbri_header = 0;
|
||||
if(readVbriHeader(&vbri_header, mad_bit_nextbyte(&stream.anc_ptr), ancillaryBitsRemaining / 8) == 0) {
|
||||
uint32_t frames = VbriTotalFrames(vbri_header);
|
||||
totalFrames = frames * samplesPerMPEGFrame;
|
||||
_startPadding = 0;
|
||||
_endPadding = 0;
|
||||
|
||||
_foundVBRIHeader = YES;
|
||||
}
|
||||
} else if(_foundXingHeader || _foundiTunSMPB) {
|
||||
|
||||
if(vbri_header) {
|
||||
freeVbriHeader(vbri_header);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} else if(_foundXingHeader || _foundiTunSMPB || _foundVBRIHeader) {
|
||||
break;
|
||||
} else if(framesDecoded > 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!_foundiTunSMPB && !_foundXingHeader) {
|
||||
if(!_foundiTunSMPB && !_foundXingHeader && !_foundVBRIHeader) {
|
||||
// Now do CBR estimation instead of full file scanning
|
||||
size_t frameCount = (_fileSize - id3_length) / (stream.next_frame - stream.this_frame);
|
||||
mad_timer_t duration = frame.header.duration;
|
||||
|
@ -590,7 +608,7 @@
|
|||
[self didChangeValueForKey:@"properties"];
|
||||
}
|
||||
// DLog(@"FIRST FRAME!!! %i %i", _foundXingHeader, _foundLAMEHeader);
|
||||
if(_foundXingHeader) {
|
||||
if(_foundXingHeader || _foundVBRIHeader) {
|
||||
// DLog(@"Skipping xing header.");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
//---------------------------------------------------------------------------\
|
||||
//
|
||||
// (C) copyright Fraunhofer - IIS (2000)
|
||||
// All Rights Reserved
|
||||
//
|
||||
// filename: CVbriHeader.c (nee CVbriHeader.cpp)
|
||||
// MPEG Layer-3 Audio Decoder
|
||||
// author : Martin Weishart martin.weishart@iis.fhg.de
|
||||
// modified: Christopher Snowhill kode54@gmail.com
|
||||
// date : 2000-02-11
|
||||
// new date: 2022-06-19
|
||||
// contents/description: provides functions to read a VBRI header
|
||||
// of a MPEG Layer 3 bitstream encoded
|
||||
// with variable bitrate using Fraunhofer
|
||||
// variable bitrate format
|
||||
//
|
||||
//--------------------------------------------------------------------------/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "CVbriHeader.h"
|
||||
|
||||
enum offset {
|
||||
BYTE = 1,
|
||||
WORD = 2,
|
||||
DWORD = 4
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------\
|
||||
//
|
||||
// Constructor: set position in buffer to parse and create a
|
||||
// VbriHeaderTable
|
||||
//
|
||||
//---------------------------------------------------------------------------/
|
||||
|
||||
struct VbriHeaderTable {
|
||||
int SampleRate;
|
||||
char VbriSignature[5];
|
||||
unsigned int VbriVersion;
|
||||
unsigned int VbriDelay;
|
||||
unsigned int VbriQuality;
|
||||
unsigned int VbriStreamBytes;
|
||||
unsigned int VbriStreamFrames;
|
||||
unsigned int VbriTableSize;
|
||||
unsigned int VbriTableScale;
|
||||
unsigned int VbriEntryBytes;
|
||||
unsigned int VbriEntryFrames;
|
||||
int *VbriTable;
|
||||
};
|
||||
|
||||
struct VbriHeader {
|
||||
struct VbriHeaderTable VBHeader;
|
||||
int position;
|
||||
};
|
||||
|
||||
void initVbriHeaderTable(struct VbriHeaderTable *pthis) {
|
||||
pthis->VbriTable = 0;
|
||||
}
|
||||
|
||||
void freeVbriHeaderTable(struct VbriHeaderTable *pthis) {
|
||||
if(pthis->VbriTable) free(pthis->VbriTable);
|
||||
}
|
||||
|
||||
void initVbriHeader(struct VbriHeader *pthis) {
|
||||
pthis->position = 0;
|
||||
initVbriHeaderTable(&pthis->VBHeader);
|
||||
}
|
||||
|
||||
void freeVbriHeader(struct VbriHeader *pthis) {
|
||||
freeVbriHeaderTable(&pthis->VBHeader);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------\
|
||||
//
|
||||
// Method: checkheader
|
||||
// Reads the header to a struct that has to be stored and is
|
||||
// used in other functions to determine file offsets
|
||||
// Input: buffer containing the first frame
|
||||
// Output: fills struct VbriHeader
|
||||
// Return: 0 on success; 1 on error
|
||||
//
|
||||
//---------------------------------------------------------------------------/
|
||||
|
||||
static int VbriGetSampleRate(const unsigned char *buffer);
|
||||
static int VbriReadFromBuffer(struct VbriHeader *pthis, const unsigned char *HBuffer, int length);
|
||||
|
||||
int readVbriHeader(struct VbriHeader **outHeader, const unsigned char *Hbuffer, size_t length) {
|
||||
if(!outHeader) return 1;
|
||||
if(!*outHeader) {
|
||||
*outHeader = malloc(sizeof(**outHeader));
|
||||
if(!*outHeader) return 1;
|
||||
}
|
||||
|
||||
struct VbriHeader *pthis = *outHeader;
|
||||
|
||||
initVbriHeader(pthis);
|
||||
|
||||
unsigned int i, TableLength;
|
||||
|
||||
// My code will have the VBRI right at position 0
|
||||
|
||||
/*if ( length >= 4 &&
|
||||
*(Hbuffer+pthis->position ) == 'V' &&
|
||||
*(Hbuffer+pthis->position+1) == 'B' &&
|
||||
*(Hbuffer+pthis->position+2) == 'R' &&
|
||||
*(Hbuffer+pthis->position+3) == 'I')*/
|
||||
{
|
||||
/*pthis->position += DWORD ;
|
||||
length -= DWORD ;*/
|
||||
|
||||
// Caller already read and verified the 'VBRI' signature
|
||||
|
||||
if(length < 22) return 1;
|
||||
|
||||
pthis->VBHeader.VbriVersion = VbriReadFromBuffer(pthis, Hbuffer, WORD);
|
||||
pthis->VBHeader.VbriDelay = VbriReadFromBuffer(pthis, Hbuffer, WORD);
|
||||
pthis->VBHeader.VbriQuality = VbriReadFromBuffer(pthis, Hbuffer, WORD);
|
||||
pthis->VBHeader.VbriStreamBytes = VbriReadFromBuffer(pthis, Hbuffer, DWORD);
|
||||
pthis->VBHeader.VbriStreamFrames = VbriReadFromBuffer(pthis, Hbuffer, DWORD);
|
||||
pthis->VBHeader.VbriTableSize = VbriReadFromBuffer(pthis, Hbuffer, WORD);
|
||||
pthis->VBHeader.VbriTableScale = VbriReadFromBuffer(pthis, Hbuffer, WORD);
|
||||
pthis->VBHeader.VbriEntryBytes = VbriReadFromBuffer(pthis, Hbuffer, WORD);
|
||||
pthis->VBHeader.VbriEntryFrames = VbriReadFromBuffer(pthis, Hbuffer, WORD);
|
||||
|
||||
length -= 22;
|
||||
|
||||
TableLength = pthis->VBHeader.VbriTableSize * pthis->VBHeader.VbriEntryBytes;
|
||||
|
||||
if(length < TableLength) return 1;
|
||||
|
||||
pthis->VBHeader.VbriTable = calloc(sizeof(int), pthis->VBHeader.VbriTableSize + 1);
|
||||
if(!pthis->VBHeader.VbriTable) return 1;
|
||||
|
||||
for(i = 0; i <= pthis->VBHeader.VbriTableSize; i++) {
|
||||
pthis->VBHeader.VbriTable[i] = VbriReadFromBuffer(pthis, Hbuffer, pthis->VBHeader.VbriEntryBytes * BYTE) * pthis->VBHeader.VbriTableScale;
|
||||
}
|
||||
}
|
||||
/*else{
|
||||
return 1;
|
||||
}*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------\
|
||||
//
|
||||
// Method: seekPointByTime
|
||||
// Returns a point in the file to decode in bytes that is nearest
|
||||
// to a given time in seconds
|
||||
// Input: time in seconds
|
||||
// Output: None
|
||||
// Returns: point belonging to the given time value in bytes
|
||||
//
|
||||
//---------------------------------------------------------------------------/
|
||||
|
||||
#if 0
|
||||
int CVbriHeader::seekPointByTime(float EntryTimeInMilliSeconds){
|
||||
|
||||
unsigned int SamplesPerFrame, i=0, SeekPoint = 0 , fraction = 0;
|
||||
|
||||
float TotalDuration ;
|
||||
float DurationPerVbriFrames ;
|
||||
float AccumulatedTime = 0.0f ;
|
||||
|
||||
(VBHeader.SampleRate >= 32000) ? (SamplesPerFrame = 1152) : (SamplesPerFrame = 576) ;
|
||||
|
||||
TotalDuration = ((float)VBHeader.VbriStreamFrames * (float)SamplesPerFrame)
|
||||
/ (float)VBHeader.SampleRate * 1000.0f ;
|
||||
DurationPerVbriFrames = (float)TotalDuration / (float)(VBHeader.VbriTableSize+1) ;
|
||||
|
||||
if ( EntryTimeInMilliSeconds > TotalDuration ) EntryTimeInMilliSeconds = TotalDuration;
|
||||
|
||||
while ( AccumulatedTime <= EntryTimeInMilliSeconds ){
|
||||
|
||||
SeekPoint += VBHeader.VbriTable[i] ;
|
||||
AccumulatedTime += DurationPerVbriFrames;
|
||||
i++;
|
||||
|
||||
}
|
||||
|
||||
// Searched too far; correct result
|
||||
fraction = ( (int)(((( AccumulatedTime - EntryTimeInMilliSeconds ) / DurationPerVbriFrames )
|
||||
+ (1.0f/(2.0f*(float)VBHeader.VbriEntryFrames))) * (float)VBHeader.VbriEntryFrames));
|
||||
|
||||
|
||||
SeekPoint -= (int)((float)VBHeader.VbriTable[i-1] * (float)(fraction)
|
||||
/ (float)VBHeader.VbriEntryFrames) ;
|
||||
|
||||
return SeekPoint ;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
//---------------------------------------------------------------------------\
|
||||
//
|
||||
// Method: seekTimeByPoint
|
||||
// Returns a time in the file to decode in seconds that is
|
||||
// nearest to a given point in bytes
|
||||
// Input: time in seconds
|
||||
// Output: None
|
||||
// Returns: point belonging to the given time value in bytes
|
||||
//
|
||||
//---------------------------------------------------------------------------/
|
||||
|
||||
#if 0
|
||||
float CVbriHeader::seekTimeByPoint(unsigned int EntryPointInBytes){
|
||||
|
||||
unsigned int SamplesPerFrame, i=0, AccumulatedBytes = 0, fraction = 0;
|
||||
|
||||
float SeekTime = 0.0f;
|
||||
float TotalDuration ;
|
||||
float DurationPerVbriFrames ;
|
||||
|
||||
(VBHeader.SampleRate >= 32000) ? (SamplesPerFrame = 1152) : (SamplesPerFrame = 576) ;
|
||||
|
||||
TotalDuration = ((float)VBHeader.VbriStreamFrames * (float)SamplesPerFrame)
|
||||
/ (float)VBHeader.SampleRate;
|
||||
DurationPerVbriFrames = (float)TotalDuration / (float)(VBHeader.VbriTableSize+1) ;
|
||||
|
||||
while (AccumulatedBytes <= EntryPointInBytes){
|
||||
|
||||
AccumulatedBytes += VBHeader.VbriTable[i] ;
|
||||
SeekTime += DurationPerVbriFrames;
|
||||
i++;
|
||||
|
||||
}
|
||||
|
||||
// Searched too far; correct result
|
||||
fraction = (int)(((( AccumulatedBytes - EntryPointInBytes ) / (float)VBHeader.VbriTable[i-1])
|
||||
+ (1/(2*(float)VBHeader.VbriEntryFrames))) * (float)VBHeader.VbriEntryFrames);
|
||||
|
||||
SeekTime -= (DurationPerVbriFrames * (float) ((float)(fraction) / (float)VBHeader.VbriEntryFrames)) ;
|
||||
|
||||
return SeekTime ;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
//---------------------------------------------------------------------------\
|
||||
//
|
||||
// Method: seekPointByPercent
|
||||
// Returns a point in the file to decode in bytes that is
|
||||
// nearest to a given percentage of the time of the stream
|
||||
// Input: percent of time
|
||||
// Output: None
|
||||
// Returns: point belonging to the given time percentage value in bytes
|
||||
//
|
||||
//---------------------------------------------------------------------------/
|
||||
|
||||
#if 0
|
||||
int CVbriHeader::seekPointByPercent(float percent){
|
||||
|
||||
int SamplesPerFrame;
|
||||
|
||||
float TotalDuration ;
|
||||
|
||||
if (percent >= 100.0f) percent = 100.0f;
|
||||
if (percent <= 0.0f) percent = 0.0f;
|
||||
|
||||
(VBHeader.SampleRate >= 32000) ? (SamplesPerFrame = 1152) : (SamplesPerFrame = 576) ;
|
||||
|
||||
TotalDuration = ((float)VBHeader.VbriStreamFrames * (float)SamplesPerFrame)
|
||||
/ (float)VBHeader.SampleRate;
|
||||
|
||||
return seekPointByTime( (percent/100.0f) * TotalDuration * 1000.0f );
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
//---------------------------------------------------------------------------\
|
||||
//
|
||||
// Method: GetSampleRate
|
||||
// Returns the sampling rate of the file to decode
|
||||
// Input: Buffer containing the part of the first frame after the
|
||||
// syncword
|
||||
// Output: None
|
||||
// Return: sampling rate of the file to decode
|
||||
//
|
||||
//---------------------------------------------------------------------------/
|
||||
|
||||
int VbriGetSampleRate(const unsigned char *buffer) {
|
||||
unsigned char id, idx, mpeg;
|
||||
|
||||
id = (0xC0 & (buffer[1] << 3)) >> 4;
|
||||
idx = (0xC0 & (buffer[2] << 4)) >> 6;
|
||||
|
||||
mpeg = id | idx;
|
||||
|
||||
switch((int)mpeg) {
|
||||
case 0:
|
||||
return 11025;
|
||||
case 1:
|
||||
return 12000;
|
||||
case 2:
|
||||
return 8000;
|
||||
case 8:
|
||||
return 22050;
|
||||
case 9:
|
||||
return 24000;
|
||||
case 10:
|
||||
return 16000;
|
||||
case 12:
|
||||
return 44100;
|
||||
case 13:
|
||||
return 48000;
|
||||
case 14:
|
||||
return 32000;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------\
|
||||
//
|
||||
// Method: readFromBuffer
|
||||
// reads from a buffer a segment to an int value
|
||||
// Input: Buffer containig the first frame
|
||||
// Output: none
|
||||
// Return: number containing int value of buffer segmenet
|
||||
// length
|
||||
//
|
||||
//---------------------------------------------------------------------------/
|
||||
|
||||
int VbriReadFromBuffer(struct VbriHeader *pthis, const unsigned char *HBuffer, int length) {
|
||||
int i, b, number = 0;
|
||||
|
||||
int position = pthis->position;
|
||||
|
||||
if(HBuffer) {
|
||||
for(i = 0; i < length; i++) {
|
||||
b = length - 1 - i;
|
||||
number = number | (unsigned int)(HBuffer[position + i] & 0xff) << (8 * b);
|
||||
}
|
||||
pthis->position += length;
|
||||
return number;
|
||||
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
float CVbriHeader::totalDuration()
|
||||
{
|
||||
int SamplesPerFrame;
|
||||
(VBHeader.SampleRate >= 32000) ? (SamplesPerFrame = 1152) : (SamplesPerFrame = 576) ;
|
||||
return ((float)VBHeader.VbriStreamFrames * (float)SamplesPerFrame) / (float)VBHeader.SampleRate;
|
||||
}
|
||||
#endif
|
||||
|
||||
int VbriTotalFrames(struct VbriHeader *pthis) {
|
||||
if(pthis)
|
||||
return pthis->VBHeader.VbriStreamFrames;
|
||||
else
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef _CVbriHeader_
|
||||
#define _CVbriHeader_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct VbriHeader;
|
||||
|
||||
int readVbriHeader(struct VbriHeader **, const unsigned char *Hbuffer, size_t length);
|
||||
void freeVbriHeader(struct VbriHeader *);
|
||||
|
||||
// int VbriSeekPointByTime(float EntryTimeInSeconds);
|
||||
// float VbriSeekTimeByPoint(unsigned int EntryPointInBytes);
|
||||
// int VbriSeekPointByPercent(float percent);
|
||||
// float VbriTotalDuration();
|
||||
|
||||
int VbriTotalFrames(struct VbriHeader *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue