diff options
Diffstat (limited to '')
-rw-r--r-- | trunk/src/inputPlugins/aac_plugin.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/trunk/src/inputPlugins/aac_plugin.c b/trunk/src/inputPlugins/aac_plugin.c new file mode 100644 index 000000000..529689706 --- /dev/null +++ b/trunk/src/inputPlugins/aac_plugin.c @@ -0,0 +1,475 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "../inputPlugin.h" + +#ifdef HAVE_FAAD + +#define AAC_MAX_CHANNELS 6 + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../inputStream.h" +#include "../outputBuffer.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <faad.h> + +/* all code here is either based on or copied from FAAD2's frontend code */ +typedef struct { + InputStream *inStream; + long bytesIntoBuffer; + long bytesConsumed; + long fileOffset; + unsigned char *buffer; + int atEof; +} AacBuffer; + +static void fillAacBuffer(AacBuffer * b) +{ + if (b->bytesConsumed > 0) { + int bread; + + if (b->bytesIntoBuffer) { + memmove((void *)b->buffer, (void *)(b->buffer + + b->bytesConsumed), + b->bytesIntoBuffer); + } + + if (!b->atEof) { + bread = readFromInputStream(b->inStream, + (void *)(b->buffer + + b-> + bytesIntoBuffer), + 1, b->bytesConsumed); + if (bread != b->bytesConsumed) + b->atEof = 1; + b->bytesIntoBuffer += bread; + } + + b->bytesConsumed = 0; + + if (b->bytesIntoBuffer > 3) { + if (memcmp(b->buffer, "TAG", 3) == 0) + b->bytesIntoBuffer = 0; + } + if (b->bytesIntoBuffer > 11) { + if (memcmp(b->buffer, "LYRICSBEGIN", 11) == 0) { + b->bytesIntoBuffer = 0; + } + } + if (b->bytesIntoBuffer > 8) { + if (memcmp(b->buffer, "APETAGEX", 8) == 0) { + b->bytesIntoBuffer = 0; + } + } + } +} + +static void advanceAacBuffer(AacBuffer * b, int bytes) +{ + b->fileOffset += bytes; + b->bytesConsumed = bytes; + b->bytesIntoBuffer -= bytes; +} + +static int adtsSampleRates[] = + { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0 +}; + +static int adtsParse(AacBuffer * b, float *length) +{ + int frames, frameLength; + int tFrameLength = 0; + int sampleRate = 0; + float framesPerSec, bytesPerFrame; + + /* Read all frames to ensure correct time and bitrate */ + for (frames = 0;; frames++) { + fillAacBuffer(b); + + if (b->bytesIntoBuffer > 7) { + /* check syncword */ + if (!((b->buffer[0] == 0xFF) && + ((b->buffer[1] & 0xF6) == 0xF0))) { + break; + } + + if (frames == 0) { + sampleRate = adtsSampleRates[(b-> + buffer[2] & 0x3c) + >> 2]; + } + + frameLength = ((((unsigned int)b->buffer[3] & 0x3)) + << 11) | (((unsigned int)b->buffer[4]) + << 3) | (b->buffer[5] >> 5); + + tFrameLength += frameLength; + + if (frameLength > b->bytesIntoBuffer) + break; + + advanceAacBuffer(b, frameLength); + } else + break; + } + + framesPerSec = (float)sampleRate / 1024.0; + if (frames != 0) { + bytesPerFrame = (float)tFrameLength / (float)(frames * 1000); + } else + bytesPerFrame = 0; + if (framesPerSec != 0) + *length = (float)frames / framesPerSec; + + return 1; +} + +static void initAacBuffer(InputStream * inStream, AacBuffer * b, float *length, + size_t * retFileread, size_t * retTagsize) +{ + size_t fileread; + size_t bread; + size_t tagsize; + + if (length) + *length = -1; + + memset(b, 0, sizeof(AacBuffer)); + + b->inStream = inStream; + + fileread = inStream->size; + + b->buffer = xmalloc(FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + memset(b->buffer, 0, FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + + bread = readFromInputStream(inStream, b->buffer, 1, + FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + b->bytesIntoBuffer = bread; + b->bytesConsumed = 0; + b->fileOffset = 0; + + if (bread != FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS) + b->atEof = 1; + + tagsize = 0; + if (!memcmp(b->buffer, "ID3", 3)) { + tagsize = (b->buffer[6] << 21) | (b->buffer[7] << 14) | + (b->buffer[8] << 7) | (b->buffer[9] << 0); + + tagsize += 10; + advanceAacBuffer(b, tagsize); + fillAacBuffer(b); + } + + if (retFileread) + *retFileread = fileread; + if (retTagsize) + *retTagsize = tagsize; + + if (length == NULL) + return; + + if ((b->buffer[0] == 0xFF) && ((b->buffer[1] & 0xF6) == 0xF0)) { + adtsParse(b, length); + seekInputStream(b->inStream, tagsize, SEEK_SET); + + bread = readFromInputStream(b->inStream, b->buffer, 1, + FAAD_MIN_STREAMSIZE * + AAC_MAX_CHANNELS); + if (bread != FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS) + b->atEof = 1; + else + b->atEof = 0; + b->bytesIntoBuffer = bread; + b->bytesConsumed = 0; + b->fileOffset = tagsize; + } else if (memcmp(b->buffer, "ADIF", 4) == 0) { + int bitRate; + int skipSize = (b->buffer[4] & 0x80) ? 9 : 0; + bitRate = + ((unsigned int)(b-> + buffer[4 + + skipSize] & 0x0F) << 19) | ((unsigned + int)b-> + buffer[5 + + + skipSize] + << 11) | + ((unsigned int)b-> + buffer[6 + skipSize] << 3) | ((unsigned int)b->buffer[7 + + skipSize] + & 0xE0); + + if (fileread != 0 && bitRate != 0) + *length = fileread * 8.0 / bitRate; + else + *length = fileread; + } +} + +static float getAacFloatTotalTime(char *file) +{ + AacBuffer b; + float length; + size_t fileread, tagsize; + faacDecHandle decoder; + faacDecConfigurationPtr config; + unsigned long sampleRate; + unsigned char channels; + InputStream inStream; + long bread; + + if (openInputStream(&inStream, file) < 0) + return -1; + + initAacBuffer(&inStream, &b, &length, &fileread, &tagsize); + + if (length < 0) { + decoder = faacDecOpen(); + + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + faacDecSetConfiguration(decoder, config); + + fillAacBuffer(&b); +#ifdef HAVE_FAAD_BUFLEN_FUNCS + bread = faacDecInit(decoder, b.buffer, b.bytesIntoBuffer, + &sampleRate, &channels); +#else + bread = faacDecInit(decoder, b.buffer, &sampleRate, &channels); +#endif + if (bread >= 0 && sampleRate > 0 && channels > 0) + length = 0; + + faacDecClose(decoder); + } + + if (b.buffer) + free(b.buffer); + closeInputStream(&inStream); + + return length; +} + +static int getAacTotalTime(char *file) +{ + int time = -1; + float length; + + if ((length = getAacFloatTotalTime(file)) >= 0) + time = length + 0.5; + + return time; +} + +static int aac_decode(OutputBuffer * cb, DecoderControl * dc, char *path) +{ + float time; + float totalTime; + faacDecHandle decoder; + faacDecFrameInfo frameInfo; + faacDecConfigurationPtr config; + long bread; + unsigned long sampleRate; + unsigned char channels; + int eof = 0; + unsigned int sampleCount; + char *sampleBuffer; + size_t sampleBufferLen; + /*float * seekTable; + long seekTableEnd = -1; + int seekPositionFound = 0; */ + mpd_uint16 bitRate = 0; + AacBuffer b; + InputStream inStream; + + if ((totalTime = getAacFloatTotalTime(path)) < 0) + return -1; + + if (openInputStream(&inStream, path) < 0) + return -1; + + initAacBuffer(&inStream, &b, NULL, NULL, NULL); + + decoder = faacDecOpen(); + + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; +#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX + config->downMatrix = 1; +#endif +#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR + config->dontUpSampleImplicitSBR = 0; +#endif + faacDecSetConfiguration(decoder, config); + + fillAacBuffer(&b); + +#ifdef HAVE_FAAD_BUFLEN_FUNCS + bread = faacDecInit(decoder, b.buffer, b.bytesIntoBuffer, + &sampleRate, &channels); +#else + bread = faacDecInit(decoder, b.buffer, &sampleRate, &channels); +#endif + if (bread < 0) { + ERROR("Error not a AAC stream.\n"); + faacDecClose(decoder); + closeInputStream(b.inStream); + if (b.buffer) + free(b.buffer); + return -1; + } + + dc->audioFormat.bits = 16; + + dc->totalTime = totalTime; + + time = 0.0; + + advanceAacBuffer(&b, bread); + + while (!eof) { + fillAacBuffer(&b); + + if (b.bytesIntoBuffer == 0) { + eof = 1; + break; + } +#ifdef HAVE_FAAD_BUFLEN_FUNCS + sampleBuffer = faacDecDecode(decoder, &frameInfo, b.buffer, + b.bytesIntoBuffer); +#else + sampleBuffer = faacDecDecode(decoder, &frameInfo, b.buffer); +#endif + + if (frameInfo.error > 0) { + ERROR("error decoding AAC file: %s\n", path); + ERROR("faad2 error: %s\n", + faacDecGetErrorMessage(frameInfo.error)); + eof = 1; + break; + } +#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE + sampleRate = frameInfo.samplerate; +#endif + + if (dc->state != DECODE_STATE_DECODE) { + dc->audioFormat.channels = frameInfo.channels; + dc->audioFormat.sampleRate = sampleRate; + getOutputAudioFormat(&(dc->audioFormat), + &(cb->audioFormat)); + dc->state = DECODE_STATE_DECODE; + } + + advanceAacBuffer(&b, frameInfo.bytesconsumed); + + sampleCount = (unsigned long)(frameInfo.samples); + + if (sampleCount > 0) { + bitRate = frameInfo.bytesconsumed * 8.0 * + frameInfo.channels * sampleRate / + frameInfo.samples / 1000 + 0.5; + time += + (float)(frameInfo.samples) / frameInfo.channels / + sampleRate; + } + + sampleBufferLen = sampleCount * 2; + + sendDataToOutputBuffer(cb, NULL, dc, 0, sampleBuffer, + sampleBufferLen, time, bitRate, NULL); + if (dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } else if (dc->stop) { + eof = 1; + break; + } + } + + flushOutputBuffer(cb); + + faacDecClose(decoder); + closeInputStream(b.inStream); + if (b.buffer) + free(b.buffer); + + if (dc->state != DECODE_STATE_DECODE) + return -1; + + if (dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *aacTagDup(char *file) +{ + MpdTag *ret = NULL; + int time; + + time = getAacTotalTime(file); + + if (time >= 0) { + if ((ret = id3Dup(file)) == NULL) + ret = newMpdTag(); + ret->time = time; + } else { + DEBUG("aacTagDup: Failed to get total song time from: %s\n", + file); + } + + return ret; +} + +static char *aacSuffixes[] = { "aac", NULL }; + +InputPlugin aacPlugin = { + "aac", + NULL, + NULL, + NULL, + NULL, + aac_decode, + aacTagDup, + INPUT_PLUGIN_STREAM_FILE, + aacSuffixes, + NULL +}; + +#else + +InputPlugin aacPlugin; + +#endif /* HAVE_FAAD */ |