From 4704ca52c0da802c89763391768eafa69f8a2f6e Mon Sep 17 00:00:00 2001 From: Chris Smowton <cs448@cam.ac.uk> Date: Mon, 22 Sep 2014 00:11:16 +0100 Subject: [PATCH] Add M4A import support using MP4V2 and faad. --- configure.ac | 23 ++++++++- lib/rdaudioconvert.cpp | 74 ++++++++++++++++++++++++++++ lib/rdaudioconvert.h | 2 + lib/rdwavefile.cpp | 107 +++++++++++++++++++++++++++++++++++++++++ lib/rdwavefile.h | 4 +- 5 files changed, 208 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 04b39930..5c20fb9c 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 @@ -206,10 +208,24 @@ 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_LIB(mp4v2,MP4Read,[MP4V2_FOUND=yes],[]) + fi + if test $MP4V2_FOUND ; then + MP4V2_LIBS="-lmp4v2" + AC_DEFINE(HAVE_MP4V2) + fi +fi + # # Set Hard Library Dependencies # -AC_SUBST(LIB_RDLIBS,"-lm -lpthread -lqui -lrd -lcurl -lid3 $FLAC_LIBS -lsndfile -lsamplerate -lcdda_interface -lcdda_paranoia -lcrypt -ldl -lpam -lSoundTouch") +AC_SUBST(LIB_RDLIBS,"-lm -lpthread -lqui -lrd -lcurl -lid3 $FLAC_LIBS $MP4V2_LIBS -lsndfile -lsamplerate -lcdda_interface -lcdda_paranoia -lcrypt -ldl -lpam -lSoundTouch") # # Setup MPEG Dependencies @@ -531,6 +547,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/rdaudioconvert.cpp b/lib/rdaudioconvert.cpp index a9767ca2..edd9bbbb 100644 --- a/lib/rdaudioconvert.cpp +++ b/lib/rdaudioconvert.cpp @@ -23,11 +23,13 @@ #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> +#include <sys/wait.h> #include <fcntl.h> #include <unistd.h> #include <math.h> #include <dlfcn.h> #include <errno.h> +#include <unistd.h> #include <sndfile.h> #include <samplerate.h> @@ -307,6 +309,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 +680,73 @@ RDAudioConvert::ErrorCode RDAudioConvert::Stage1Mpeg(const QString &dstfile, #endif // HAVE_MAD } +RDAudioConvert::ErrorCode RDAudioConvert::Stage1M4A(const QString &dstfile, + RDWaveFile *wave) { + + const char* args[7]; + int childstatus = 0; + + pid_t child = fork(); + + QString tmpname = dstfile + ".m4a_temp.wav"; + + if(child == 0) { + + freopen("/dev/null", "w", stdout); + freopen("/dev/null", "w", stderr); + + args[0] = "faad"; + args[1] = wave->getName(); + args[2] = "-o"; + args[3] = tmpname; + args[4] = "-b"; + args[5] = "4"; // Emit a Float32 format wave file, like the other stage 1s. + args[6] = 0; + execvp("faad", (char* const*)args); + exit(109); + + } + else { + + waitpid(child, &childstatus, 0); + + // Killed by a signal (e.g. OOM?) + if(!WIFEXITED(childstatus)) { + unlink(tmpname); + return RDAudioConvert::ErrorInternal; + } + + // Returned 109, probably because we could't find faad? + if(WEXITSTATUS(childstatus) == 109) + return RDAudioConvert::ErrorFormatNotSupported; + + // Two elements are missing: + // 1. need to measure the peak amplitude; + // 2. need to trim if a subrange was requested. + // For now just use the sndfile import path to accomplish both tasks. + + { + + SF_INFO sf_src_info; + SNDFILE* sf_src; + + memset(&sf_src_info, 0, sizeof(sf_src_info)); + // If this fails it is likely because faad could not decode. + if((sf_src = sf_open(tmpname, SFM_READ, &sf_src_info)) == NULL) + return RDAudioConvert::ErrorFormatError; + + RDAudioConvert::ErrorCode err = Stage1SndFile(dstfile, sf_src, &sf_src_info); + + sf_close(sf_src); + unlink(tmpname); + + return err; + + } + + } + +} RDAudioConvert::ErrorCode RDAudioConvert::Stage1SndFile(const QString &dstfile, SNDFILE *sf_src, diff --git a/lib/rdaudioconvert.h b/lib/rdaudioconvert.h index 3b833c9a..14eeb976 100644 --- a/lib/rdaudioconvert.h +++ b/lib/rdaudioconvert.h @@ -70,6 +70,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); diff --git a/lib/rdwavefile.cpp b/lib/rdwavefile.cpp index c09f9985..5c2408de 100644 --- a/lib/rdwavefile.cpp +++ b/lib/rdwavefile.cpp @@ -48,6 +48,10 @@ #include <rdwavefile.h> #include <rdconf.h> +#ifdef HAVE_MP4V2 +#include <mp4v2/mp4v2.h> +#endif + RDWaveFile::RDWaveFile(QString file_name) { // @@ -322,6 +326,94 @@ bool RDWaveFile::openWave(RDWaveData *data) ReadId3Metadata(); break; + case RDWaveFile::M4A: + { +#ifdef HAVE_MP4V2 + + format_tag=WAVE_FORMAT_M4A; + + MP4FileHandle f = MP4Read(getName()); + if(f == MP4_INVALID_FILE_HANDLE) + return false; + + // Find an audio track, and populate sample rate, bits/sample etc. + uint32_t nTracks = MP4GetNumberOfTracks(f); + + MP4TrackId audioTrack = MP4_INVALID_TRACK_ID; + for(uint32_t trackIndex = 0; trackIndex < nTracks && audioTrack == MP4_INVALID_TRACK_ID; ++trackIndex) { + + MP4TrackId thisTrack = MP4FindTrackId(f, trackIndex); + const char* trackType = MP4GetTrackType(f, thisTrack); + if(trackType && !strcmp(trackType, MP4_AUDIO_TRACK_TYPE)) { + + const char* dataName = MP4GetTrackMediaDataName(f, thisTrack); + // The M4A format is only currently useful for AAC in an M4A container: + if(dataName && + (!strcasecmp(dataName, "mp4a")) && + MP4GetTrackEsdsObjectTypeId(f, thisTrack) == MP4_MPEG4_AUDIO_TYPE) { + + audioTrack = thisTrack; + + } + + } + + } + + if(audioTrack == MP4_INVALID_TRACK_ID) { + MP4Close(f); + return false; + } + + // Found audio track. Get audio data: + avg_bytes_per_sec = MP4GetTrackBitRate(f, audioTrack); + channels = MP4GetTrackAudioChannels(f, audioTrack); + + MP4Duration trackDuration = MP4GetTrackDuration(f, audioTrack); + ext_time_length = (unsigned)MP4ConvertFromTrackDuration(f, audioTrack, trackDuration, + MP4_MSECS_TIME_SCALE); + time_length = ext_time_length / 1000; + samples_per_sec = MP4GetTrackTimeScale(f, audioTrack); + bits_per_sample = 16; + data_start = 0; + sample_length = 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 = MP4TagsAlloc(); + 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); + + MP4TagsFree(tags); + + } + + MP4Close(f); + + return true; + +#else + return false; +#endif + break; + } + case RDWaveFile::Ogg: #ifdef HAVE_VORBIS format_tag=WAVE_FORMAT_VORBIS; @@ -2020,6 +2112,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 +2306,18 @@ bool RDWaveFile::IsAiff(int fd) return true; } +bool RDWaveFile::IsM4A(int fd) +{ +#ifdef HAVE_MP4V2 + MP4FileHandle f = MP4Read(getName()); + bool ret = f != MP4_INVALID_FILE_HANDLE; + if(ret) + MP4Close(f); + 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..c4a632fc 100644 --- a/lib/rdwavefile.h +++ b/lib/rdwavefile.h @@ -116,7 +116,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 +1046,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, @@ -1378,6 +1379,7 @@ class RDWaveFile */ #define WAVE_FORMAT_VORBIS 0xFFFF #define WAVE_FORMAT_FLAC 0xFFFE +#define WAVE_FORMAT_M4A 0xFFFD /* * Proprietary Format Categories