diff --git a/configure.ac b/configure.ac index ca86df12..9276b359 100644 --- a/configure.ac +++ b/configure.ac @@ -78,6 +78,8 @@ AC_ARG_ENABLE(lame,[ --disable-lame disable MPEG Layer 3 encode suppor [LAME_DISABLED=yes],[]) AC_ARG_ENABLE(flac,[ --disable-flac disable FLAC encode/decode support], [FLAC_DISABLED=yes],[]) +AC_ARG_ENABLE(mp4v2,[ --disable-mp4 disable M4A decode support], + [MP4V2_DISABLED=yes],[]) # # Check for Qt @@ -208,6 +210,19 @@ if test -z $FLAC_DISABLED ; then AC_CHECK_LIB(FLAC,FLAC__metadata_get_tags,[FLAC_METADATA_FOUND=yes],[]) fi +# +# Check for MP4V2 +# +if test -z $MP4V2_DISABLED ; then + AC_CHECK_HEADER(mp4v2/mp4v2.h,[MP4V2_HEADER_FOUND=yes],[]) + if test $MP4V2_HEADER_FOUND ; then + AC_CHECK_HEADER(neaacdec.h,[MP4V2_FOUND=yes],[]) + if test $MP4V2_FOUND ; then + AC_DEFINE(HAVE_MP4_LIBS) + fi + fi +fi + # # Set Hard Library Dependencies # @@ -534,6 +549,11 @@ AC_MSG_NOTICE("| OggVorbis Encoding/Decoding Support ... No |") else AC_MSG_NOTICE("| OggVorbis Encoding/Decoding Support ... Yes |") fi +if test -z $MP4V2_FOUND ; then +AC_MSG_NOTICE("| M4A Decoding Support ... No |") +else +AC_MSG_NOTICE("| M4A Decoding Support ... Yes |") +fi AC_MSG_NOTICE("| |") AC_MSG_NOTICE("| Optional Components: |") if test -z $USING_PAM ; then diff --git a/lib/Makefile.am b/lib/Makefile.am index 49fc7d71..1a7e2311 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -177,6 +177,7 @@ dist_librd_la_SOURCES = dbversion.h\ rdmeteraverage.cpp rdmeteraverage.h\ rdmixer.cpp rdmixer.h\ rdmonitor_config.cpp rdmonitor_config.h\ + rdmp4.cpp rdmp4.h\ rdnownext.cpp rdnownext.h\ rdoneshot.cpp rdoneshot.h\ rdpam.cpp rdpam.h\ diff --git a/lib/rdaudioconvert.cpp b/lib/rdaudioconvert.cpp index 1d0512b1..84719f7b 100644 --- a/lib/rdaudioconvert.cpp +++ b/lib/rdaudioconvert.cpp @@ -23,11 +23,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -40,6 +42,10 @@ #include #include #endif // HAVE_FLAC +#ifdef HAVE_MP4_LIBS +#include +#include +#endif // HAVE_MP4_LIBS #include #include @@ -307,6 +313,11 @@ RDAudioConvert::ErrorCode RDAudioConvert::Stage1Convert(const QString &srcfile, delete wave; return err; + case RDWaveFile::M4A: + err=Stage1M4A(dstfile,wave); + delete wave; + return err; + case RDWaveFile::Aiff: case RDWaveFile::Unknown: break; @@ -673,6 +684,165 @@ RDAudioConvert::ErrorCode RDAudioConvert::Stage1Mpeg(const QString &dstfile, #endif // HAVE_MAD } +// Based on libfaad's frontend/main.c, but using libmp4v2 for MP4 access. +RDAudioConvert::ErrorCode RDAudioConvert::Stage1M4A(const QString &dstfile, + RDWaveFile *wave) +{ +#ifdef HAVE_MP4_LIBS + SNDFILE *sf_dst=NULL; + SF_INFO sf_dst_info; + MP4FileHandle f; + MP4TrackId audioTrack; + MP4SampleId firstSample, lastSample; + uint32_t aacBufSize, aacConfigSize; + uint8_t *aacBuf, *aacConfigBuffer; + NeAACDecHandle hDecoder; + NeAACDecConfigurationPtr config; + unsigned long foundSampleRate; + unsigned char foundChannels; + RDAudioConvert::ErrorCode ret = RDAudioConvert::ErrorOk; + + if(!dlmp4.load()) { + return RDAudioConvert::ErrorFormatNotSupported; + } + + // + // Open source + // + f = dlmp4.MP4Read(wave->getName()); + if(f == MP4_INVALID_FILE_HANDLE) + return RDAudioConvert::ErrorNoSource; + + audioTrack = dlmp4.getMP4AACTrack(f); + firstSample = 1; + lastSample = dlmp4.MP4GetTrackNumberOfSamples(f, audioTrack); + if(conv_start_point > 0) { + + double startsecs = ((double)conv_start_point) / 1000; + MP4Timestamp startts = (MP4Timestamp)(startsecs * wave->getSamplesPerSec()); + firstSample = dlmp4.MP4GetSampleIdFromTime(f, audioTrack, startts, /*need_sync=*/false); + if(firstSample == MP4_INVALID_SAMPLE_ID) { + ret = RDAudioConvert::ErrorInvalidSource; + goto out_mp4; + } + + } + + if(conv_end_point > 0) { + + double stopsecs = ((double)conv_end_point) / 1000; + MP4Timestamp stopts = (MP4Timestamp)(stopsecs * wave->getSamplesPerSec()); + lastSample = dlmp4.MP4GetSampleIdFromTime(f, audioTrack, stopts, /*need_sync=*/false); + if(lastSample == MP4_INVALID_SAMPLE_ID) { + ret = RDAudioConvert::ErrorInvalidSource; + goto out_mp4; + } + + } + + aacBufSize = dlmp4.MP4GetTrackMaxSampleSize(f, audioTrack); + aacBuf = (uint8_t*)malloc(aacBufSize); + if(!aacBufSize || !aacBuf) { + // Probably the source's fault for specifying a massive buffer. + ret = RDAudioConvert::ErrorInvalidSource; + goto out_mp4; + } + + dlmp4.MP4GetTrackESConfiguration(f, audioTrack, &aacConfigBuffer, &aacConfigSize); + if(!aacConfigBuffer) { + ret = RDAudioConvert::ErrorInvalidSource; + goto out_mp4_buf; + } + + // + // Open Destination + // + + memset(&sf_dst_info,0,sizeof(sf_dst_info)); + sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT; + sf_dst_info.channels=wave->getChannels(); + sf_dst_info.samplerate=wave->getSamplesPerSec(); + if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) { + ret = RDAudioConvert::ErrorNoDestination; + goto out_mp4_configbuf; + } + sf_command(sf_dst,SFC_SET_NORM_DOUBLE,NULL,SF_FALSE); + + // + // Initialize Decoder + // + hDecoder = dlmp4.NeAACDecOpen(); + + config = dlmp4.NeAACDecGetCurrentConfiguration(hDecoder); + config->outputFormat = FAAD_FMT_FLOAT; + config->downMatrix = 1; // Downmix >2 channels to stereo. + if(!dlmp4.NeAACDecSetConfiguration(hDecoder, config)) { + ret = RDAudioConvert::ErrorInvalidSource; + goto out_decoder; + } + + if(dlmp4.NeAACDecInit2(hDecoder, aacConfigBuffer, aacConfigSize, &foundSampleRate, &foundChannels) < 0) { + ret = RDAudioConvert::ErrorInvalidSource; + goto out_decoder; + } + + if(foundSampleRate != wave->getSamplesPerSec() || foundChannels != wave->getChannels()) { + fprintf(stderr, "M4A header information inconsistent with actual file? Header: %u/%u; file: %lu/%u\n", + wave->getSamplesPerSec(), (unsigned)wave->getChannels(), foundSampleRate, (unsigned)foundChannels); + ret = RDAudioConvert::ErrorInvalidSource; + goto out_decoder; + } + + // + // Decode + // + for(MP4SampleId i = firstSample; i <= lastSample; ++i) { + + uint32_t aacBytes = aacBufSize; + if(!dlmp4.MP4ReadSample(f, audioTrack, i, &aacBuf, &aacBytes, 0, 0, 0, 0)) { + ret = RDAudioConvert::ErrorInvalidSource; + break; + } + + NeAACDecFrameInfo frameInfo; + // The library docs are not clear about the lifetime or cleanup of sample_buffer. + // I hope it lives until the next NeAACDecDecode call, and is cleaned up by NeAACDecClose + void* sample_buffer = dlmp4.NeAACDecDecode(hDecoder, &frameInfo, aacBuf, aacBytes); + if(!sample_buffer) { + ret = RDAudioConvert::ErrorInvalidSource; + break; + } + + UpdatePeak((const float*)sample_buffer, frameInfo.samples); + + if(sf_write_float(sf_dst, (const float*)sample_buffer, frameInfo.samples) != (sf_count_t)frameInfo.samples) { + ret = RDAudioConvert::ErrorInternal; + break; + } + + } + + // + // Cleanup + // + + out_decoder: + dlmp4.NeAACDecClose(hDecoder); + // out_sf: + sf_close(sf_dst); + out_mp4_configbuf: + free(aacConfigBuffer); + out_mp4_buf: + free(aacBuf); + out_mp4: + dlmp4.MP4Close(f, 0); + + return ret; + +#else + return RDAudioConvert::ErrorFormatNotSupported; +#endif +} RDAudioConvert::ErrorCode RDAudioConvert::Stage1SndFile(const QString &dstfile, SNDFILE *sf_src, diff --git a/lib/rdaudioconvert.h b/lib/rdaudioconvert.h index 3b833c9a..bce0a39d 100644 --- a/lib/rdaudioconvert.h +++ b/lib/rdaudioconvert.h @@ -33,6 +33,9 @@ #ifdef HAVE_MAD #include #endif // HAVE_MAD + +#include + #include #include @@ -70,6 +73,8 @@ class RDAudioConvert : public QObject RDWaveFile *wave); RDAudioConvert::ErrorCode Stage1Mpeg(const QString &dstfile, RDWaveFile *wave); + RDAudioConvert::ErrorCode Stage1M4A(const QString &dstfile, + RDWaveFile *wave); RDAudioConvert::ErrorCode Stage1SndFile(const QString &dstfile, SNDFILE *sf_src, SF_INFO *sf_src_info); @@ -150,6 +155,9 @@ class RDAudioConvert : public QObject int (*lame_encode_flush)(lame_global_flags *,unsigned char *,int); int (*lame_set_bWriteVbrTag)(lame_global_flags *, int); #endif // HAVE_LAME +#ifdef HAVE_MP4_LIBS + DLMP4 dlmp4; +#endif }; diff --git a/lib/rdmp4.cpp b/lib/rdmp4.cpp new file mode 100644 index 00000000..b3fa97a1 --- /dev/null +++ b/lib/rdmp4.cpp @@ -0,0 +1,108 @@ +// rdmp4.cpp +// +// Helpers for dealing with MP4 files. +// +// (C) Copyright 2014 Christopher Smowton +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU Library General Public License +// version 2 as published by the Free Software Foundation. +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// + +#ifdef HAVE_MP4_LIBS + +#include +#include +#include + +MP4TrackId DLMP4::getMP4AACTrack(MP4FileHandle f) +{ + + uint32_t nTracks = this->MP4GetNumberOfTracks(f, NULL, 0); + + for(uint32_t trackIndex = 0; trackIndex < nTracks; ++trackIndex) { + + MP4TrackId thisTrack = this->MP4FindTrackId(f, trackIndex); + const char* trackType = this->MP4GetTrackType(f, thisTrack); + if(trackType && !strcmp(trackType, MP4_AUDIO_TRACK_TYPE)) { + + const char* dataName = this->MP4GetTrackMediaDataName(f, thisTrack); + // The M4A format is only currently useful for AAC in an M4A container: + if(dataName && + (!strcasecmp(dataName, "mp4a")) && + this->MP4GetTrackEsdsObjectTypeId(f, thisTrack) == MP4_MPEG4_AUDIO_TYPE) { + + return thisTrack; + + } + + } + + } + + return MP4_INVALID_TRACK_ID; + +} + +#define check_dlsym(lval, libhandle, symname) \ + *(void**)(&lval) = dlsym(libhandle, symname); \ + if(!lval) return false; + +bool DLMP4::load() +{ + + if(loadSuccess) + return true; + + neaac_handle = dlopen("libfaad.so",RTLD_LAZY); + mp4_handle = dlopen("libmp4v2.so",RTLD_LAZY); + + if(!neaac_handle || !mp4_handle) + return false; + + check_dlsym(this->MP4Read, mp4_handle, "MP4Read"); + check_dlsym(this->MP4GetTrackNumberOfSamples, mp4_handle, "MP4GetTrackNumberOfSamples"); + check_dlsym(this->MP4GetSampleIdFromTime, mp4_handle, "MP4GetSampleIdFromTime"); + check_dlsym(this->MP4GetTrackMaxSampleSize, mp4_handle, "MP4GetTrackMaxSampleSize"); + check_dlsym(this->MP4GetTrackESConfiguration, mp4_handle, "MP4GetTrackESConfiguration"); + check_dlsym(this->MP4ReadSample, mp4_handle, "MP4ReadSample"); + check_dlsym(this->MP4GetTrackBitRate, mp4_handle, "MP4GetTrackBitRate"); + check_dlsym(this->MP4GetTrackAudioChannels, mp4_handle, "MP4GetTrackAudioChannels"); + check_dlsym(this->MP4GetTrackDuration, mp4_handle, "MP4GetTrackDuration"); + check_dlsym(this->MP4ConvertFromTrackDuration, mp4_handle, "MP4ConvertFromTrackDuration"); + check_dlsym(this->MP4GetTrackTimeScale, mp4_handle, "MP4GetTrackTimeScale"); + check_dlsym(this->MP4GetNumberOfTracks, mp4_handle, "MP4GetNumberOfTracks"); + check_dlsym(this->MP4FindTrackId, mp4_handle, "MP4FindTrackId"); + check_dlsym(this->MP4GetTrackType, mp4_handle, "MP4GetTrackType"); + check_dlsym(this->MP4GetTrackMediaDataName, mp4_handle, "MP4GetTrackMediaDataName"); + check_dlsym(this->MP4GetTrackEsdsObjectTypeId, mp4_handle, "MP4GetTrackEsdsObjectTypeId"); + check_dlsym(this->MP4TagsAlloc, mp4_handle, "MP4TagsAlloc"); + check_dlsym(this->MP4TagsFetch, mp4_handle, "MP4TagsFetch"); + check_dlsym(this->MP4TagsFree, mp4_handle, "MP4TagsFree"); + check_dlsym(this->MP4Close, mp4_handle, "MP4Close"); + + check_dlsym(this->NeAACDecOpen, neaac_handle, "NeAACDecOpen"); + check_dlsym(this->NeAACDecGetCurrentConfiguration, neaac_handle, "NeAACDecGetCurrentConfiguration"); + check_dlsym(this->NeAACDecSetConfiguration, neaac_handle, "NeAACDecSetConfiguration"); + check_dlsym(this->NeAACDecInit2, neaac_handle, "NeAACDecInit2"); + check_dlsym(this->NeAACDecDecode, neaac_handle, "NeAACDecDecode"); + check_dlsym(this->NeAACDecClose, neaac_handle, "NeAACDecClose"); + + loadSuccess = true; + return true; + +} + +#undef check_dlsym + +#endif // HAVE_MP4_LIBS diff --git a/lib/rdmp4.h b/lib/rdmp4.h new file mode 100644 index 00000000..abdf016d --- /dev/null +++ b/lib/rdmp4.h @@ -0,0 +1,97 @@ +// rdmp4.h +// +// Helpers for dealing with MP4 files. +// +// (C) Copyright 2014 Christopher Smowton +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU Library General Public License +// version 2 as published by the Free Software Foundation. +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// + +#ifndef RDMP4_H +#define RDMP4_H +#ifdef HAVE_MP4_LIBS + +#include +#include +// neaacdec.h defines "LC", as in "low-complexity AAC", which clashes with a Rivendell Command. +#undef LC + +struct DLMP4 { + +DLMP4() : loadSuccess(false) {} + + void *neaac_handle; + void *mp4_handle; + bool loadSuccess; + + // MP4v2 Functions + MP4FileHandle (*MP4Read) (const char* fileName); + MP4SampleId (*MP4GetTrackNumberOfSamples) (MP4FileHandle hFile, MP4TrackId trackId); + MP4SampleId (*MP4GetSampleIdFromTime) (MP4FileHandle hFile, MP4TrackId trackId, MP4Timestamp when, bool wantSyncSample); + uint32_t (*MP4GetTrackMaxSampleSize) (MP4FileHandle hFile, MP4TrackId trackId); + bool (*MP4GetTrackESConfiguration) (MP4FileHandle hFile, MP4TrackId trackId, uint8_t** ppConfig, uint32_t* pConfigSize); + bool (*MP4ReadSample) ( + MP4FileHandle hFile, + MP4TrackId trackId, + MP4SampleId sampleId, + uint8_t** ppBytes, + uint32_t* pNumBytes, + MP4Timestamp* pStartTime, + MP4Duration* pDuration, + MP4Duration* pRenderingOffset, + bool* pIsSyncSample); + uint32_t (*MP4GetTrackBitRate) (MP4FileHandle hFile, MP4TrackId trackId); + int (*MP4GetTrackAudioChannels) (MP4FileHandle hFile, MP4TrackId trackId); + MP4Duration (*MP4GetTrackDuration) (MP4FileHandle hFile, MP4TrackId trackId); + uint64_t (*MP4ConvertFromTrackDuration) ( + MP4FileHandle hFile, + MP4TrackId trackId, + MP4Duration duration, + uint32_t timeScale); + uint32_t (*MP4GetTrackTimeScale) (MP4FileHandle hFile, MP4TrackId trackId); + uint32_t (*MP4GetNumberOfTracks) (MP4FileHandle hFile, const char* type, uint8_t subType); + MP4TrackId (*MP4FindTrackId) (MP4FileHandle hFile, uint32_t trackIdx); + const char* (*MP4GetTrackType) (MP4FileHandle hFile, MP4TrackId); + const char* (*MP4GetTrackMediaDataName) (MP4FileHandle hFile, MP4TrackId); + uint8_t (*MP4GetTrackEsdsObjectTypeId) (MP4FileHandle hFile, MP4TrackId); + const MP4Tags* (*MP4TagsAlloc) (void); + bool (*MP4TagsFetch) (const MP4Tags* tags, MP4FileHandle hFile); + void (*MP4TagsFree) (const MP4Tags* tags); + void (*MP4Close) (MP4FileHandle hFile, uint32_t flags); + + // libfaad / NeAACDec functions + NeAACDecHandle (*NeAACDecOpen) (void); + NeAACDecConfigurationPtr (*NeAACDecGetCurrentConfiguration) (NeAACDecHandle hDecoder); + unsigned char (*NeAACDecSetConfiguration) (NeAACDecHandle hDecoder, NeAACDecConfigurationPtr config); + char (*NeAACDecInit2)(NeAACDecHandle hDecoder, + unsigned char *pBuffer, + unsigned long SizeOfDecoderSpecificInfo, + unsigned long *samplerate, + unsigned char *channels); + void* (*NeAACDecDecode) (NeAACDecHandle hDecoder, + NeAACDecFrameInfo *hInfo, + unsigned char *buffer, + unsigned long buffer_size); + void (*NeAACDecClose) (NeAACDecHandle hDecoder); + + // Helper functions: + MP4TrackId getMP4AACTrack(MP4FileHandle f); + + bool load(); + +}; + +#endif // HAVE_MP4_LIBS +#endif // RDMP4_H diff --git a/lib/rdwavefile.cpp b/lib/rdwavefile.cpp index cf18a5cd..2004c9f9 100644 --- a/lib/rdwavefile.cpp +++ b/lib/rdwavefile.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -47,6 +48,11 @@ #include #include #include +#include + +#ifdef HAVE_MP4_LIBS +#include +#endif RDWaveFile::RDWaveFile(QString file_name) { @@ -322,6 +328,75 @@ bool RDWaveFile::openWave(RDWaveData *data) ReadId3Metadata(); break; + case RDWaveFile::M4A: + { +#ifdef HAVE_MP4_LIBS + + // MP4 libs must already be loaded by now for file to have that type. + assert(dlmp4.load()); + format_tag=WAVE_FORMAT_M4A; + + MP4FileHandle f = dlmp4.MP4Read(getName()); + if(f == MP4_INVALID_FILE_HANDLE) + return false; + + // Find an audio track, and populate sample rate, bits/sample etc. + MP4TrackId audioTrack = dlmp4.getMP4AACTrack(f); + + if(audioTrack == MP4_INVALID_TRACK_ID) { + dlmp4.MP4Close(f, 0); + return false; + } + + // Found audio track. Get audio data: + avg_bytes_per_sec = dlmp4.MP4GetTrackBitRate(f, audioTrack); + channels = dlmp4.MP4GetTrackAudioChannels(f, audioTrack); + + MP4Duration trackDuration = dlmp4.MP4GetTrackDuration(f, audioTrack); + ext_time_length = (unsigned)dlmp4.MP4ConvertFromTrackDuration(f, audioTrack, trackDuration, + MP4_MSECS_TIME_SCALE); + time_length = ext_time_length / 1000; + samples_per_sec = dlmp4.MP4GetTrackTimeScale(f, audioTrack); + bits_per_sample = 16; + data_start = 0; + sample_length = dlmp4.MP4GetTrackNumberOfSamples(f, audioTrack); + data_length = sample_length * 2 * channels; + data_chunk = true; + format_chunk = true; + wave_type = RDWaveFile::M4A; + + // Now extract metadata (title, artist, etc) + + if(wave_data) { + + const MP4Tags* tags = dlmp4.MP4TagsAlloc(); + dlmp4.MP4TagsFetch(tags, f); + + wave_data->setMetadataFound(true); + + if(tags->name) + wave_data->setTitle(tags->name); + if(tags->artist) + wave_data->setArtist(tags->artist); + if(tags->composer) + wave_data->setComposer(tags->composer); + if(tags->album) + wave_data->setAlbum(tags->album); + + dlmp4.MP4TagsFree(tags); + + } + + dlmp4.MP4Close(f, 0); + + return true; + +#else + return false; +#endif + break; + } + case RDWaveFile::Ogg: #ifdef HAVE_VORBIS format_tag=WAVE_FORMAT_VORBIS; @@ -2020,6 +2095,9 @@ RDWaveFile::Type RDWaveFile::GetType(int fd) if(IsOgg(fd)) { return RDWaveFile::Ogg; } + if(IsM4A(fd)) { + return RDWaveFile::M4A; + } if(IsMpeg(fd)) { return RDWaveFile::Mpeg; } @@ -2211,6 +2289,20 @@ bool RDWaveFile::IsAiff(int fd) return true; } +bool RDWaveFile::IsM4A(int fd) +{ +#ifdef HAVE_MP4_LIBS + if(!dlmp4.load()) + return false; + MP4FileHandle f = dlmp4.MP4Read(getName()); + bool ret = f != MP4_INVALID_FILE_HANDLE; + if(ret) + dlmp4.MP4Close(f, 0); + return ret; +#else + return false; +#endif +} off_t RDWaveFile::FindChunk(int fd,const char *chunk_name,unsigned *chunk_size, bool big_end) diff --git a/lib/rdwavefile.h b/lib/rdwavefile.h index fbe4cfc1..046981d3 100644 --- a/lib/rdwavefile.h +++ b/lib/rdwavefile.h @@ -40,6 +40,8 @@ #include #endif // HAVE_VORBIS +#include + #include #include #include @@ -116,7 +118,7 @@ class RDWaveFile enum Format {Pcm8=0,Pcm16=1,Float32=2,MpegL1=3,MpegL2=4,MpegL3=5, DolbyAc2=6,DolbyAc3=7,Vorbis=8}; enum Type {Unknown=0,Wave=1,Mpeg=2,Ogg=3,Atx=4,Tmc=5,Flac=6,Ambos=7, - Aiff=8}; + Aiff=8,M4A=9}; enum MpegID {NonMpeg=0,Mpeg1=1,Mpeg2=2}; /** @@ -1046,6 +1048,7 @@ class RDWaveFile bool IsTmc(int fd); bool IsFlac(int fd); bool IsAiff(int fd); + bool IsM4A(int fd); off_t FindChunk(int fd,const char *chunk_name,unsigned *chunk_size, bool big_end=false); bool GetChunk(int fd,const char *chunk_name,unsigned *chunk_size, @@ -1240,6 +1243,10 @@ class RDWaveFile ogg_page ogg_pg; ogg_packet ogg_pack; #endif // HAVE_VORBIS +#ifdef HAVE_MP4_LIBS + DLMP4 dlmp4; +#endif + }; @@ -1378,6 +1385,7 @@ class RDWaveFile */ #define WAVE_FORMAT_VORBIS 0xFFFF #define WAVE_FORMAT_FLAC 0xFFFE +#define WAVE_FORMAT_M4A 0xFFFD /* * Proprietary Format Categories