summaryrefslogtreecommitdiffstats
path: root/plugins/soundsourcem4a/m4a/mp4-mixxx.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/soundsourcem4a/m4a/mp4-mixxx.cpp')
-rw-r--r--plugins/soundsourcem4a/m4a/mp4-mixxx.cpp587
1 files changed, 587 insertions, 0 deletions
diff --git a/plugins/soundsourcem4a/m4a/mp4-mixxx.cpp b/plugins/soundsourcem4a/m4a/mp4-mixxx.cpp
new file mode 100644
index 0000000000..4d7de3d05a
--- /dev/null
+++ b/plugins/soundsourcem4a/m4a/mp4-mixxx.cpp
@@ -0,0 +1,587 @@
+// mp4-mixxx.cpp
+// This file is a hopefully shortlived fork + convertsion to C++ of the M4A audio playback plugin from the C* Music Player (cmus) project.
+// The original file mp4.c is also in this directory.
+//
+// This forked and converted by Garth and Albert in Summer 2008 to support M4A playback in Mixxx
+//
+// g++ $(pkg-config --cflags QtCore) $(pkg-config --libs-only-l QtCore) -lmp4v2 -lfaad -o mp4-mixxx mp4-mixxx.cpp
+//
+#include <QtCore>
+#include <stdlib.h>
+
+#include "mathstuff.h"
+
+/*
+ * Copyright 2006 dnk <dnk@bjum.net>
+ *
+ * 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 "ip.h"
+// #include "xmalloc.h"
+// #include "debug.h"
+// #include "file.h"
+
+#ifdef __MP4V2__
+ #include <mp4v2/mp4v2.h>
+#else
+ #include <mp4.h>
+#endif
+
+#include <neaacdec.h>
+
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#ifndef _MSC_VER
+ #include <unistd.h>
+#endif
+
+#ifdef _MSC_VER
+ #define S_ISDIR(mode) (mode & _S_IFDIR)
+ #define strcasecmp stricmp
+ #define strncasecmp strnicmp
+#endif
+
+#ifdef __M4AHACK__
+ typedef uint32_t SAMPLERATE_TYPE;
+#else
+ typedef unsigned long SAMPLERATE_TYPE;
+#endif
+
+struct mp4_private {
+ char *overflow_buf;
+ int overflow_buf_len;
+
+ unsigned char *aac_data;
+ unsigned int aac_data_len;
+
+ char *sample_buf;
+ unsigned int sample_buf_frame;
+ unsigned int sample_buf_len;
+
+ unsigned char channels;
+ unsigned long sample_rate;
+
+ faacDecHandle decoder; /* typedef void * */
+
+ struct {
+ MP4FileHandle handle; /* typedef void * */
+
+ MP4TrackId track;
+ MP4SampleId sample;
+ MP4SampleId num_samples;
+ } mp4;
+};
+
+
+static MP4TrackId mp4_get_track(MP4FileHandle *handle)
+{
+ MP4TrackId num_tracks;
+ const char *track_type;
+ uint8_t obj_type;
+ MP4TrackId i;
+
+ num_tracks = MP4GetNumberOfTracks(handle, NULL, 0);
+
+ for (i = 1; i <= num_tracks; i++) {
+ track_type = MP4GetTrackType(handle, i);
+ if (!track_type)
+ continue;
+
+ if (!MP4_IS_AUDIO_TRACK_TYPE(track_type))
+ continue;
+
+ /* MP4GetTrackAudioType */
+ obj_type = MP4GetTrackEsdsObjectTypeId(handle, i);
+ if (obj_type == MP4_INVALID_AUDIO_TYPE)
+ continue;
+
+ if (obj_type == MP4_MPEG4_AUDIO_TYPE) {
+ obj_type = MP4GetTrackAudioMpeg4Type(handle, i);
+
+ if (MP4_IS_MPEG4_AAC_AUDIO_TYPE(obj_type))
+ return i;
+ } else {
+ if (MP4_IS_AAC_AUDIO_TYPE(obj_type))
+ return i;
+ }
+ }
+
+ return MP4_INVALID_TRACK_ID;
+}
+
+static int mp4_open(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv;
+ faacDecConfigurationPtr neaac_cfg;
+ unsigned char *buf;
+ unsigned int buf_size;
+
+ /* http://sourceforge.net/forum/message.php?msg_id=3578887 */
+ if (ip_data->remote)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+
+ /* init private_ipd struct */
+ // priv = xnew0(struct mp4_private, 1);
+ priv = new mp4_private();
+ //priv = (mp4_private*) calloc(1, sizeof(mp4_private));
+ // FIXME: there was some alloc error checking in the orgininal ver
+ memset(priv, 0, sizeof(*priv));
+
+ priv->overflow_buf_len = 0;
+ priv->overflow_buf = NULL;
+
+ priv->sample_buf_len = 4096;
+ priv->sample_buf = new char[priv->sample_buf_len];
+ priv->sample_buf_frame = -1;
+
+ ip_data->private_ipd = priv;
+
+ priv->decoder = faacDecOpen();
+ /* set decoder config */
+ neaac_cfg = faacDecGetCurrentConfiguration(priv->decoder);
+ neaac_cfg->outputFormat = FAAD_FMT_16BIT; /* force 16 bit audio */
+ neaac_cfg->downMatrix = 1; /* 5.1 -> stereo */
+ neaac_cfg->defObjectType = LC;
+ //qDebug() << "Decoder Config" << neaac_cfg->defObjectType
+ // << neaac_cfg->defSampleRate
+ // << neaac_cfg->useOldADTSFormat
+ // << neaac_cfg->dontUpSampleImplicitSBR;
+ faacDecSetConfiguration(priv->decoder, neaac_cfg);
+
+ /* open mpeg-4 file, check for >= ver 1.9.1 */
+#if MP4V2_PROJECT_version_hex <= 0x00010901
+ priv->mp4.handle = MP4Read(ip_data->filename, 0);
+#else
+ priv->mp4.handle = MP4Read(ip_data->filename);
+#endif
+ if (!priv->mp4.handle) {
+ qDebug() << "MP4Read failed";
+ goto out;
+ }
+
+ /* find aac audio track */
+ priv->mp4.track = mp4_get_track((MP4FileHandle*)priv->mp4.handle);
+ if (priv->mp4.track == MP4_INVALID_TRACK_ID) {
+ qDebug() << "MP4FindTrackId failed";
+ goto out;
+ }
+
+ // Allocate AAC read buffer
+ priv->aac_data_len = MP4GetTrackMaxSampleSize(priv->mp4.handle, priv->mp4.track);
+ priv->aac_data = new unsigned char[priv->aac_data_len];
+
+ priv->mp4.num_samples = MP4GetTrackNumberOfSamples(priv->mp4.handle, priv->mp4.track);
+ // MP4 frames are 1-indexed
+ priv->mp4.sample = 1;
+
+ buf = NULL;
+ buf_size = 0;
+ if (!MP4GetTrackESConfiguration(priv->mp4.handle, priv->mp4.track, &buf, &buf_size)) {
+ /* failed to get mpeg-4 audio config... this is ok.
+ * faacDecInit2() will simply use default values instead.
+ */
+ qDebug() << "Didn't get MP4 Audio Config (not a bad thing)";
+ buf = NULL;
+ buf_size = 0;
+ }
+
+ /* init decoder according to mpeg-4 audio config */
+ if (faacDecInit2(priv->decoder, buf, buf_size,
+ (SAMPLERATE_TYPE*)&priv->sample_rate, &priv->channels) < 0) {
+ free(buf);
+ goto out;
+ }
+ free(buf);
+
+ // qDebug() << "sample rate "<< priv->sample_rate <<"hz, channels" << priv->channels;
+
+ ip_data->sf = sf_rate(priv->sample_rate) | sf_channels(priv->channels) | sf_bits(16) | sf_signed(1);
+#if defined(WORDS_BIGENDIAN)
+ ip_data->sf |= sf_bigendian(1);
+#endif
+
+ return 0;
+out:
+ if (priv->mp4.handle)
+ MP4Close(priv->mp4.handle);
+ if (priv->decoder)
+ faacDecClose(priv->decoder);
+ delete [] priv->sample_buf;
+ delete [] priv->aac_data;
+ delete priv;
+ return -IP_ERROR_FILE_FORMAT;
+}
+
+static int mp4_close(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv;
+
+ priv = (mp4_private*) ip_data->private_ipd;
+
+ if (priv->mp4.handle)
+ MP4Close(priv->mp4.handle);
+
+ if (priv->decoder)
+ faacDecClose(priv->decoder);
+
+ if (priv->sample_buf) {
+ delete [] priv->sample_buf;
+ }
+
+ if (priv->aac_data) {
+ delete [] priv->aac_data;
+ }
+
+ delete priv;
+ ip_data->private_ipd = NULL;
+
+ return 0;
+}
+
+/* returns -1 on fatal errors
+ * returns -2 on non-fatal errors
+ * 0 on eof
+ * number of bytes put in 'buffer' on success */
+static int decode_one_frame(struct input_plugin_data *ip_data, void *buffer, int count)
+{
+ struct mp4_private *priv = (mp4_private*) ip_data->private_ipd;
+ faacDecFrameInfo frame_info;
+ int bytes;
+
+ //BUG_ON(priv->overflow_buf_len);
+
+ if (priv->mp4.sample > priv->mp4.num_samples)
+ return 0; /* EOF */
+
+ unsigned char *aac_data = priv->aac_data;
+ unsigned int aac_data_len = priv->aac_data_len;
+
+ // If you do this, then MP4ReadSample allocates the buffer for you. We don't
+ // want this because it's slow.
+ // unsigned char *aac_data = NULL;
+ // unsigned int aac_data_len = 0;
+
+ int this_frame = priv->mp4.sample;
+ if (MP4ReadSample(priv->mp4.handle, priv->mp4.track, this_frame,
+ &aac_data, &aac_data_len,
+ NULL, NULL, NULL, NULL) == 0) {
+ qWarning() << "m4a: error reading mp4 sample" << priv->mp4.sample;
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!aac_data) {
+ qWarning() << "m4a: aac_data == NULL";
+ errno = EINVAL;
+ return -1;
+ }
+
+ char* sample_buf = priv->sample_buf;
+ int sample_buf_len = priv->sample_buf_len;
+
+ NeAACDecDecode2(priv->decoder,
+ &frame_info,
+ aac_data, aac_data_len,
+ (void**)&sample_buf, sample_buf_len);
+
+ // qDebug() << "Sample frame" << priv->mp4.sample
+ // << "has" << frame_info.samples << "samples"
+ // << frame_info.bytesconsumed << "bytes"
+ // << frame_info.channels << "channels"
+ // << frame_info.error << "error"
+ // << frame_info.samplerate << "samplerate";
+
+ if (!sample_buf || frame_info.bytesconsumed <= 0) {
+ qWarning() << "m4a fatal error:" << faacDecGetErrorMessage(frame_info.error);
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (frame_info.error != 0) {
+ qDebug() << "frame error:" << faacDecGetErrorMessage(frame_info.error);
+ return -2;
+ }
+
+ if (frame_info.samples <= 0) {
+ return -2;
+ }
+
+ if (frame_info.channels != priv->channels ||
+ frame_info.samplerate != priv->sample_rate) {
+ qDebug() << "invalid channel or sample_rate count\n";
+ return -2;
+ }
+
+ // The frame read was successful
+ priv->sample_buf_frame = this_frame;
+ priv->mp4.sample++;
+
+ /* 16-bit samples */
+ bytes = frame_info.samples * 2;
+
+ if (bytes > count) {
+ /* decoded too much; keep overflow. */
+ //memcpy(priv->overflow_buf_base, sample_buf + count, bytes - count);
+ //priv->overflow_buf = priv->overflow_buf_base;
+
+ priv->overflow_buf = sample_buf + count;
+ priv->overflow_buf_len = bytes - count;
+ memcpy(buffer, sample_buf, count);
+ return count;
+ }
+
+ memcpy(buffer, sample_buf, bytes);
+ return bytes;
+}
+
+static int mp4_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct mp4_private *priv = (mp4_private*) ip_data->private_ipd;
+ int rc;
+
+ /* use overflow from previous call (if any) */
+ if (priv->overflow_buf_len > 0) {
+ int len = priv->overflow_buf_len;
+
+ if (len > count)
+ len = count;
+
+ memcpy(buffer, priv->overflow_buf, len);
+ priv->overflow_buf += len;
+ priv->overflow_buf_len -= len;
+
+ //qDebug() << "Reading" << len << "from overflow."
+ // << priv->overflow_buf_len << "overflow remains";
+
+ return len;
+ }
+
+ do {
+ rc = decode_one_frame(ip_data, buffer, count);
+ } while (rc == -2);
+
+ return rc;
+}
+
+static int mp4_total_samples(struct input_plugin_data *ip_data) {
+ struct mp4_private *priv = (struct mp4_private*)ip_data->private_ipd;
+ return priv->channels * priv->mp4.num_samples * 1024;
+}
+
+static int mp4_current_sample(struct input_plugin_data *ip_data) {
+ struct mp4_private *priv = (struct mp4_private*)ip_data->private_ipd;
+ int frame_length = priv->channels * 1024;
+ if (priv->overflow_buf_len == 0) {
+ return priv->mp4.sample * frame_length - priv->overflow_buf_len;
+ }
+ // rryan 9/2009 This is equivalent to the current sample. The full expression
+ // is (priv->mp4.sample - 1) * frame_length + (frame_length -
+ // priv->overflow_buf_len); but the frame_length lone terms drop out.
+
+ // -1 because if overflow buf is filled then mp4.sample is incremented, and
+ // the samples in the overflow buf are for sample - 1
+ return (priv->mp4.sample - 1) * frame_length - priv->overflow_buf_len;
+}
+
+static int mp4_seek_sample(struct input_plugin_data *ip_data, int sample)
+{
+ struct mp4_private *priv;
+ priv = (mp4_private*) ip_data->private_ipd;
+
+ Q_ASSERT(sample >= 0);
+ // The first frame is samples 0 through 2047. The first sample of the second
+ // frame is 2048. 2048 / 2048 = 1, so frame_for_sample will be 2 on the
+ // 2048'th sample. The frame_offset_samples is how many samples into the frame
+ // the sample'th sample is. For x in (0,2047), the frame offset is x. For x in
+ // (2048,4095) the offset is x-2048 and so on. sample % 2048 is therefore
+ // suitable for calculating the offset.
+ unsigned int frame_for_sample = 1 + (sample / (2 * 1024));
+ unsigned int frame_offset_samples = sample % (2 * 1024);
+ unsigned int frame_offset_bytes = frame_offset_samples * 2;
+
+ //qDebug() << "Seeking to" << frame_for_sample << ":" << frame_offset;
+
+ // Invalid sample requested -- return the current position.
+ if (frame_for_sample < 1 || frame_for_sample > priv->mp4.num_samples)
+ return mp4_current_sample(ip_data);
+
+ // We don't have the current frame decoded -- decode it.
+ if (priv->sample_buf_frame != frame_for_sample) {
+
+ // We might have to 'prime the pump' if this isn't the first frame. The
+ // decoder has internal state that it builds as it plays, and just seeking
+ // to the frame we want will result in poor audio quality (clicks and
+ // pops). This is akin to seeking in a video and seeing MPEG
+ // artifacts. Figure out how many frames we need to go backward -- 1 seems
+ // to work.
+ const int how_many_backwards = 1;
+ int start_frame = math_max(frame_for_sample - how_many_backwards, 1);
+ priv->mp4.sample = start_frame;
+
+ // rryan 9/2009 -- the documentation is sketchy on this, but I think that
+ // it tells the decoder that you are seeking so it should flush its state
+ faacDecPostSeekReset(priv->decoder, priv->mp4.sample);
+
+ // Loop until the current frame is past the frame we intended to read
+ // (i.e. we have decoded how_many_backwards + 1 frames). The desidered
+ // decoded frame will be stored in the overflow buffer, since we're asking
+ // to read 0 bytes.
+ int result;
+ do {
+ result = decode_one_frame(ip_data, 0, 0);
+ if (result < 0) qDebug() << "SEEK_ERROR";
+ } while (result == -2 || priv->mp4.sample <= frame_for_sample);
+
+ if (result == -1 || result == 0) {
+ return mp4_current_sample(ip_data);
+ }
+ } else {
+ qDebug() << "Seek within frame";
+ }
+
+ // Now the overflow buffer contains the sample we want to seek to. Fix the
+ // overflow buffer so that the next call to read() will read starting with the
+ // requested sample.
+ priv->overflow_buf = priv->sample_buf;
+ priv->overflow_buf += frame_offset_bytes;
+ priv->overflow_buf_len -= frame_offset_bytes;
+
+ return mp4_current_sample(ip_data);
+}
+
+static int mp4_seek(struct input_plugin_data *ip_data, double offset)
+{
+ struct mp4_private *priv;
+ MP4SampleId sample;
+ uint32_t scale;
+
+ priv = (mp4_private*) ip_data->private_ipd;
+
+ scale = MP4GetTrackTimeScale(priv->mp4.handle, priv->mp4.track);
+ if (scale == 0)
+ return -IP_ERROR_INTERNAL;
+
+ sample = MP4GetSampleIdFromTime(priv->mp4.handle, priv->mp4.track,
+ (MP4Timestamp)(offset * (double)scale), 0);
+ if (sample == MP4_INVALID_SAMPLE_ID)
+ return -IP_ERROR_INTERNAL;
+
+ qDebug() << "seeking from sample" << priv->mp4.sample << "to sample" << sample;
+ priv->mp4.sample = sample;
+ priv->overflow_buf_len = 0;
+
+ return priv->mp4.sample;
+}
+
+/* commented because we use TagLib now ??? -- bkgood
+static int mp4_read_comments(struct input_plugin_data *ip_data,
+ struct keyval **comments)
+{
+ struct mp4_private *priv;
+ uint16_t meta_num, meta_total;
+ uint8_t val;
+ uint8_t *ustr;
+ uint32_t size;
+ char *str;
+ GROWING_KEYVALS(c);
+
+ priv = ip_data->private;
+
+ MP4GetMetadata* provides malloced pointers, and the data
+ * is in UTF-8 (or at least it should be).
+ if (MP4GetMetadataArtist(priv->mp4.handle, &str))
+ comments_add(&c, "artist", str);
+ if (MP4GetMetadataAlbum(priv->mp4.handle, &str))
+ comments_add(&c, "album", str);
+ if (MP4GetMetadataName(priv->mp4.handle, &str))
+ comments_add(&c, "title", str);
+ if (MP4GetMetadataGenre(priv->mp4.handle, &str))
+ comments_add(&c, "genre", str);
+ if (MP4GetMetadataYear(priv->mp4.handle, &str))
+ comments_add(&c, "date", str);
+
+ if (MP4GetMetadataCompilation(priv->mp4.handle, &val))
+ comments_add_const(&c, "compilation", val ? "yes" : "no");
+#if 0
+ if (MP4GetBytesProperty(priv->mp4.handle, "moov.udta.meta.ilst.aART.data", &ustr, &size)) {
+ char *xstr;
+
+ What's this?
+ * This is the result from lack of documentation.
+ * It's supposed to return just a string, but it
+ * returns an additional 16 bytes of junk at the
+ * beginning. Could be a bug. Could be intentional.
+ * Hopefully this works around it:
+
+ if (ustr[0] == 0 && size > 16) {
+ ustr += 16;
+ size -= 16;
+ }
+ xstr = xmalloc(size + 1);
+ memcpy(xstr, ustr, size);
+ xstr[size] = 0;
+ comments_add(&c, "albumartist", xstr);
+ free(xstr);
+ }
+#endif
+ if (MP4GetMetadataTrack(priv->mp4.handle, &meta_num, &meta_total)) {
+ char buf[6];
+ snprintf(buf, 6, "%u", meta_num);
+ comments_add_const(&c, "tracknumber", buf);
+ }
+ if (MP4GetMetadataDisk(priv->mp4.handle, &meta_num, &meta_total)) {
+ char buf[6];
+ snprintf(buf, 6, "%u", meta_num);
+ comments_add_const(&c, "discnumber", buf);
+ }
+
+ comments_terminate(&c);
+ *comments = c.comments;
+ return 0;
+}
+*/
+
+static int mp4_duration(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv;
+ uint32_t scale;
+ uint64_t duration;
+
+ priv = (mp4_private*) ip_data->private_ipd;
+
+ scale = MP4GetTrackTimeScale(priv->mp4.handle, priv->mp4.track);
+ if (scale == 0)
+ return 0;
+
+ duration = MP4GetTrackDuration(priv->mp4.handle, priv->mp4.track);
+
+ return duration / scale;
+}
+/*
+const struct input_plugin_ops ip_ops = {
+ .open = mp4_open,
+ .close = mp4_close,
+ .read = mp4_read,
+ .seek = mp4_seek,
+ .read_comments = mp4_read_comments,
+ .duration = mp4_duration
+};
+
+const char * const ip_extensions[] = { "mp4", "m4a", "m4b", NULL };
+const char * const ip_mime_types[] = { "audio/mp4", "audio/mp4a-latm", NULL };
+*/