(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