(Apologies for potential duplicate message, just realised I'd used the wrong From address and so ended up in the moderator's bin)

Hey again,

The next feature Cam FM really wanted was M4A import support. The attached patch provides it using libmp4v2 for metadata extraction and faad (dynamically located searching rdxport.cgi's path at runtime) for audio extraction. It calls the libsndfile import path on a temporary wav produced using faad.

I appreciate this differs from the other stage 1 audio conversion filters, which perform audio extraction in-process; however it seemed as if I would need to more or less copy-and-paste 1,000 lines worth of faad into Rivendell to go that way, and miss out on future faad patches to boot. Therefore I stuck with invoking an external process. Because faad cannot be instructed to seek and decode only a subset of the file this will be very inefficient at importing a small snippet from a huge source file; however I expect this is a rare use case if it exists at all?

The patch has been tested as follows:

* Imports audio from LC-AAC files (likely works with other variants of AAC supported by FAAD, but these are highly compressed and not suited for radio play regardless)
* Imports title, artist, album metadata
* Compiles and runs with Ubuntu 12.04's libmp4v2-dev package installed
* Correctly configures the feature out if the package is not installed

Chris

diff -urB '--exclude=Makefile.in' '--exclude=aclocal.m4' 
'--exclude=autom4te.cache' '--exclude=configure' rivendell-2.8.1/configure.in 
rivendell-2.8.1-hacked/configure.in
--- rivendell-2.8.1/configure.in        2014-02-17 13:45:36.000000000 +0000
+++ rivendell-2.8.1-hacked/configure.in 2014-03-20 18:16:36.913874091 +0000
@@ -80,6 +80,8 @@
                      [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
@@ -201,9 +203,23 @@
 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,"-lqui -lrd -lcurl -lid3 $FLAC_LIBS -lsndfile -lsamplerate 
-lcdda_interface -lcdda_paranoia -lcrypt -ldl -lpam -lSoundTouch")
+AC_SUBST(LIB_RDLIBS,"-lqui -lrd -lcurl -lid3 $FLAC_LIBS $MP4V2_LIBS -lsndfile 
-lsamplerate -lcdda_interface -lcdda_paranoia -lcrypt -ldl -lpam -lSoundTouch")
 
 #
 # Setup MPEG Dependencies
@@ -524,6 +540,11 @@
 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 -urB '--exclude=Makefile.in' '--exclude=aclocal.m4' 
'--exclude=autom4te.cache' '--exclude=configure' 
rivendell-2.8.1/lib/rdaudioconvert.cpp 
rivendell-2.8.1-hacked/lib/rdaudioconvert.cpp
--- rivendell-2.8.1/lib/rdaudioconvert.cpp      2014-02-18 18:04:57.000000000 
+0000
+++ rivendell-2.8.1-hacked/lib/rdaudioconvert.cpp       2014-03-20 
22:31:30.536085042 +0000
@@ -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>
@@ -309,6 +311,11 @@
       delete wave;
       return err;
 
+    case RDWaveFile::M4A:
+      err=Stage1M4A(dstfile,wave);
+      delete wave;
+      return err;
+
     case RDWaveFile::Unknown:
       break;
     }
@@ -659,6 +681,73 @@
 #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 -urB '--exclude=Makefile.in' '--exclude=aclocal.m4' 
'--exclude=autom4te.cache' '--exclude=configure' 
rivendell-2.8.1/lib/rdaudioconvert.h rivendell-2.8.1-hacked/lib/rdaudioconvert.h
--- rivendell-2.8.1/lib/rdaudioconvert.h        2014-02-17 17:57:39.000000000 
+0000
+++ rivendell-2.8.1-hacked/lib/rdaudioconvert.h 2014-03-20 15:22:38.728495331 
+0000
@@ -70,6 +70,8 @@
                                         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 -urB '--exclude=Makefile.in' '--exclude=aclocal.m4' 
'--exclude=autom4te.cache' '--exclude=configure' 
rivendell-2.8.1/lib/rdwavefile.cpp rivendell-2.8.1-hacked/lib/rdwavefile.cpp
--- rivendell-2.8.1/lib/rdwavefile.cpp  2014-02-27 15:26:07.000000000 +0000
+++ rivendell-2.8.1-hacked/lib/rdwavefile.cpp   2014-03-20 22:33:12.532085656 
+0000
@@ -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 @@
     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;
@@ -2009,6 +2101,9 @@
   if(IsOgg(fd)) {
     return RDWaveFile::Ogg;
   }
+  if(IsM4A(fd)) {
+    return RDWaveFile::M4A;
+  }
   if(IsMpeg(fd)) {
     return RDWaveFile::Mpeg;
   }
@@ -2200,6 +2295,18 @@
   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,char *chunk_name,unsigned *chunk_size,
                            bool big_end)
diff -urB '--exclude=Makefile.in' '--exclude=aclocal.m4' 
'--exclude=autom4te.cache' '--exclude=configure' 
rivendell-2.8.1/lib/rdwavefile.h rivendell-2.8.1-hacked/lib/rdwavefile.h
--- rivendell-2.8.1/lib/rdwavefile.h    2014-01-15 19:56:32.000000000 +0000
+++ rivendell-2.8.1-hacked/lib/rdwavefile.h     2014-03-20 15:29:18.964497739 
+0000
@@ -116,7 +116,7 @@
   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 @@
    bool IsTmc(int fd);
    bool IsFlac(int fd);
    bool IsAiff(int fd);
+   bool IsM4A(int fd);
    off_t FindChunk(int fd,char *chunk_name,unsigned *chunk_size,
                   bool big_end=false);
    bool GetChunk(int fd,char *chunk_name,unsigned *chunk_size,
@@ -1277,6 +1278,7 @@
  */
 #define WAVE_FORMAT_VORBIS 0xFFFF
 #define WAVE_FORMAT_FLAC 0xFFFE
+#define WAVE_FORMAT_M4A 0xFFFD
 
 /*
  * Proprietary Format Categories

_______________________________________________
Rivendell-dev mailing list
[email protected]
http://caspian.paravelsystems.com/mailman/listinfo/rivendell-dev

Reply via email to