diff --git a/Plugins/MAD/MAD.xcodeproj/project.pbxproj b/Plugins/MAD/MAD.xcodeproj/project.pbxproj index 1328aa3b8..571db54ef 100644 --- a/Plugins/MAD/MAD.xcodeproj/project.pbxproj +++ b/Plugins/MAD/MAD.xcodeproj/project.pbxproj @@ -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 = ""; }; 8372C93E27C7904800E250C9 /* libid3tag.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libid3tag.a; path = ../../ThirdParty/libid3tag/lib/libid3tag.a; sourceTree = ""; }; 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 = ""; }; + 83F97B6828600F9300A70B97 /* CVbriHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CVbriHeader.h; sourceTree = ""; }; /* 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 = ""; }; + 83F97B6628600F9300A70B97 /* ThirdParty */ = { + isa = PBXGroup; + children = ( + 83F97B6728600F9300A70B97 /* CVbriHeader.c */, + 83F97B6828600F9300A70B97 /* CVbriHeader.h */, + ); + path = ThirdParty; + sourceTree = ""; + }; /* 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; }; diff --git a/Plugins/MAD/MADDecoder.h b/Plugins/MAD/MADDecoder.h index 942e80086..e10a8fa04 100644 --- a/Plugins/MAD/MADDecoder.h +++ b/Plugins/MAD/MADDecoder.h @@ -32,6 +32,7 @@ // For gapless playback of mp3s BOOL _foundXingHeader; BOOL _foundLAMEHeader; + BOOL _foundVBRIHeader; BOOL _foundID3v2; BOOL _foundiTunSMPB; diff --git a/Plugins/MAD/MADDecoder.m b/Plugins/MAD/MADDecoder.m index a909a758b..4d395a8ca 100644 --- a/Plugins/MAD/MADDecoder.m +++ b/Plugins/MAD/MADDecoder.m @@ -16,6 +16,8 @@ #import +#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; + } + + if(vbri_header) { + freeVbriHeader(vbri_header); + } + + break; } - } else if(_foundXingHeader || _foundiTunSMPB) { + } 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; } diff --git a/Plugins/MAD/ThirdParty/CVbriHeader.c b/Plugins/MAD/ThirdParty/CVbriHeader.c new file mode 100755 index 000000000..834e553d9 --- /dev/null +++ b/Plugins/MAD/ThirdParty/CVbriHeader.c @@ -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 + +#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; +} diff --git a/Plugins/MAD/ThirdParty/CVbriHeader.h b/Plugins/MAD/ThirdParty/CVbriHeader.h new file mode 100755 index 000000000..707a442d7 --- /dev/null +++ b/Plugins/MAD/ThirdParty/CVbriHeader.h @@ -0,0 +1,26 @@ +#ifndef _CVbriHeader_ +#define _CVbriHeader_ + +#include + +#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