#include #include #include #include #include "swft.h" #include #include #include #define TMP_STRLEN 0xff #define ERROR_NO_MP3 -1 #define ERROR_WRONG_SAMPLING_RATE -2 enum { MPEG_V25 = 0, MPEG_RESERVED, MPEG_V2, MPEG_V1, }; const int mpegVersionBitrate[] = { 1, // V2.5, bitrates same as V2 -1, 1, // V2 0 // V1 }; // Only Layer3 is supported const int mp3Bitrates[][15] = { {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}, // V1 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // V2 & V2.5 }; /* As required in DefineSound, as a function of mpegVersion */ const int flashSamplingRates[] = { 1, // V25 -> 11025, -1, // dummy 2, // V2 -> 22050, 3, // V1 -> 44100; }; const int samplingRates[][4] = { {11025, 12000, 8000, -1}, // V2.5 { -1, -1, -1, -1}, // dummy {22050, 24000, 12000, -1}, // V2 {44100, 48000, 32000, -1}, // V1 }; struct MP3Info { int samplingRate; int samplesPerFrame; int flashSamplingRateFlag; int frames; int stereo; bool validMP3; bool wrongSamplingRate; }; int findFrame( const unsigned char* data, int size, int start ) { int pos = start; while( pos < size ) { if( data[pos] == 0xFF && (data[pos + 1] & 0xE0) == 0xE0 ) { return pos; } pos++; } return -1; } int getFrameSize( const unsigned char* data, int size, int pos, MP3Info &info) { if( pos + 2 >= size ) { return ERROR_NO_MP3; } unsigned char c = data[pos + 1]; int mpegVersion = (c & 0x18) >> 3; // 0:V2.5 1:reserved 2:V2 3:V1 int layer = (c & 0x06) >> 1; // 1 means Layer III // An MP3 file is Layer III, MPEG version any if( layer != 1) { fprintf(stderr, "Error: Layer should be III.\n"); return ERROR_NO_MP3; } if (mpegVersion == MPEG_RESERVED) { fprintf(stderr, "Error: Unknown MPEG version (reserved).\n"); return ERROR_NO_MP3; } c = data[pos + 2]; int bitrate = (c & 0xF0) >> 4; int samplingRate = (c & 0x0C) >> 2; int padding = (c & 0x02) >> 1; if (bitrate > 14) { fprintf(stderr, "MP3 bitrate field invalid. Corrupt MP3 file?"); return ERROR_NO_MP3; } info.samplingRate = samplingRates[mpegVersion][samplingRate]; info.flashSamplingRateFlag = flashSamplingRates[mpegVersion]; if( samplingRate != 0 ) { fprintf(stderr, "Sampling rate: %d\n", info.samplingRate); fprintf(stderr, "Error: Flash only supports sampling rates of 44100, 22050 and 11025 Hz\n"); return ERROR_WRONG_SAMPLING_RATE; } info.samplesPerFrame = mpegVersion == MPEG_V1 ? 1152 : 576; // Since we deal with Layer III only //Calculate the frame size in bytes int br_table = mpegVersionBitrate[mpegVersion]; int frameSize = (info.samplesPerFrame / 8) * (mp3Bitrates[br_table][bitrate] * 1000) / info.samplingRate + padding; return frameSize; } void getMP3Info( MP3Info& info, const unsigned char* data, int size ) { info.frames = 0; info.stereo = 0; info.validMP3 = true; info.wrongSamplingRate = false; int pos = 0; bool first = true; while( (pos = findFrame( data, size, pos)) >= 0 ) { int frameSize = getFrameSize( data, size, pos, info ); if( frameSize > 0 ) { if( first ) { if(pos + 3 < size) { if((data[pos + 3] & 0xC0) != 0xC0) info.stereo = 1; } first = false; } pos += frameSize; info.frames++; } else { if ( frameSize == ERROR_WRONG_SAMPLING_RATE ) { info.wrongSamplingRate = true; } else { info.validMP3 = false; } return; } } //no frames found -> no valid mp3 if( info.frames == 0 ) { info.validMP3 = false; } } void swft_import_mp3( xmlXPathParserContextPtr ctx, int nargs ) { xsltTransformContextPtr tctx; xmlChar *filename; xsltDocumentPtr xsltdoc; xmlDocPtr doc = NULL; xmlNodePtr node; xmlXPathObjectPtr obj; char tmp[TMP_STRLEN]; //data variables unsigned char *data = NULL; int size; struct stat filestat; xmlXPathStringFunction(ctx, 1); if (ctx->value->type != XPATH_STRING) { xsltTransformError(xsltXPathGetTransformContext(ctx), NULL, NULL, "swft:import-mp3() : invalid arg expecting a string\n"); ctx->error = XPATH_INVALID_TYPE; return; } obj = valuePop(ctx); if (obj->stringval == NULL) { valuePush(ctx, xmlXPathNewNodeSet(NULL)); return; } tctx = xsltXPathGetTransformContext(ctx); filename = obj->stringval; bool quiet = true; xmlXPathObjectPtr quietObj = xsltVariableLookup( tctx, (const xmlChar*)"quiet", NULL ); if( quietObj && quietObj->stringval ) { quiet = !strcmp("true",(const char*)quietObj->stringval ); }; FILE *fp = fopen( (const char *)filename, "rb" ); if( !fp ) { xsltTransformError(xsltXPathGetTransformContext(ctx), NULL, NULL, "swft:import-mp3() : failed to read file '%s'\n", (const char *)filename); valuePush(ctx, xmlXPathNewNodeSet(NULL)); return; } doc = xmlNewDoc( (const xmlChar *)"1.0"); doc->xmlRootNode = xmlNewDocNode( doc, NULL, (const xmlChar *)"mp3", NULL ); node = doc->xmlRootNode; swft_addFileName( node, (const char *)filename ); // get file size if( stat( (const char *)filename, &filestat ) ) goto fail; size = filestat.st_size; // flash requires an initial latency value in front of the mp3 data // TODO: check the meaning of this value and set it correctly data = new unsigned char[size + 2]; data[0] = 0; data[1] = 0; // read data if( fread( &data[2], 1, size, fp ) != size ) { fprintf(stderr,"WARNING: could not read enough (%i) bytes for MP3 %s\n", size, filename ); goto fail; } if( size == 0 ) { fprintf(stderr,"WARNING: MP3 %s is empty\n", filename ); goto fail; } MP3Info info; getMP3Info( info, &data[2], size ); if( !info.validMP3 ) { fprintf(stderr,"WARNING: this file is not a valid MP3 %s\n", filename ); goto fail; } if( info.wrongSamplingRate ) { fprintf(stderr,"WARNING: MP3 file %s has a wrong sampling rate\n", filename ); goto fail; } xmlSetProp( node, (const xmlChar *)"format", (const xmlChar *)"2" ); //MP3 snprintf(tmp,TMP_STRLEN,"%i", info.flashSamplingRateFlag); xmlSetProp( node, (const xmlChar *)"rate", (const xmlChar *)&tmp ); xmlSetProp( node, (const xmlChar *)"is16bit", (const xmlChar *)"1" ); //MP3 is always 16bit snprintf(tmp,TMP_STRLEN,"%i", info.stereo); xmlSetProp( node, (const xmlChar *)"stereo", (const xmlChar *)&tmp ); snprintf(tmp,TMP_STRLEN,"%i", info.frames * info.samplesPerFrame); xmlSetProp( node, (const xmlChar *)"samples", (const xmlChar *)&tmp ); if( !quiet ) { fprintf(stderr, "Importing MP3: '%s'\n", filename); } swft_addData( node, (char*)data, size + 2 ); valuePush( ctx, xmlXPathNewNodeSet( (xmlNodePtr)doc ) ); fail: if( fp ) fclose(fp); if( data ) delete data; }