So I decided to take another look at this work starting with some digging
through Ubuntu using "apt-get rdepends" to see what apps depend on
libmp4v2... I settled on a c application called cmus. I thought maybe
the code for the mp4 plugin could serve as a basis for our own sound source
file.
I decided to have a go at converting the plugin to c++ and trying to reduce
the number of dependencies on other cmus source files... I'm not a great
pure c dev to begin with ( it took me a while to figure out that I needed to
explicitly cast stuff when moving from c -> c++ )...
The attached code is as condensed as I could get it... I expect this guts
of this plugin will more or less replace the crap I checked in before (with
the exception of the parseHeader function I wrote previously); i.e. a faad
AAC decode is required to actually get sound out of a m4a... I'm not sure
how well the model for fetching frames lines up with the soundsource format
of reading data cause I'm not too familar with either.
Attachments:
- mp4-ged.cpp - modified version of cmus mp4.c (
http://repo.or.cz/w/cmus.git?a=blob_plain;f=mp4.chb=ed1b8ff4adff64788da84c51e4c149c824dcd7d6
)
- *.h - header files needed to get it to compile
g++ $(pkg-config --cflags QtCore) $(pkg-config --libs-only-l QtCore) -lmp4v2
-lfaad -o mp4-ged mp4-ged.cpp
The above command is how I tested I satisfied everything, it will bitch
about missing _start because it has no main() method.
Hopefully this brain dump can serve to inspire others. :D
-G
On Sun, Aug 24, 2008 at 2:53 AM, Garth Dahlstrom <[EMAIL PROTECTED]>wrote:
> With r2269, I committed 5 updated files (soundsourcem4a.cpp) to partially
> enable M4A support for Mixxx. If the C header for libmp4v2 is found in your
> /usr/include directory (from "libmp4v2-dev"), this will be enabled in the
> build.
>
> It's able to parse the header file, and I think the init is okay. The read
> and seek functions don't work however... I roughed out what I thought was
> needed for these, but they don't work. :(
>
> So, if anyone wants to take a crack at fixing this, I'd sure appreciate the
> help. I've mostly been going off the docs for mp4.h @
> http://linux.die.net/man/3/mp4
>
> Cheers,
>
> -G
>
> --
> __
> --- == __/ t.O ==--
> http://stacktrace.org/
>
--
__
--- == __/ t.O ==--
http://stacktrace.org/
// g++ $(pkg-config --cflags QtCore) $(pkg-config --libs-only-l QtCore) -lmp4v2 -lfaad -o mp4-ged mp4-ged.cpp
#include <QtCore>
#include <stdlib.h>
/*
* Copyright 2006 dnk <[EMAIL PROTECTED]>
*
* 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"
#include <mp4.h>
#include <faad.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
struct mp4_private_ipd {
char *overflow_buf;
int overflow_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_ipd *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 = (mp4_private_ipd*) calloc(1, sizeof(mp4_private_ipd)); // FIXME: there was some alloc error checking in the orgininal ver
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 */
faacDecSetConfiguration(priv->decoder, neaac_cfg);
/* open mpeg-4 file */
priv->mp4.handle = MP4Read(ip_data->filename, 0);
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;
}
priv->mp4.num_samples = MP4GetTrackNumberOfSamples(priv->mp4.handle, priv->mp4.track);
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.
*/
buf = NULL;
buf_size = 0;
}
/* init decoder according to mpeg-4 audio config */
if (faacDecInit2(priv->decoder, buf, buf_size, (uint32_t*) &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);
free(priv);
return -IP_ERROR_FILE_FORMAT;
}
static int mp4_close(struct input_plugin_data *ip_data)
{
struct mp4_private_ipd *priv;
priv = (mp4_private_ipd*) ip_data->private_ipd;
if (priv->mp4.handle)
MP4Close(priv->mp4.handle);
if (priv->decoder)
faacDecClose(priv->decoder);
free(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_ipd *priv;
unsigned char *aac_data = NULL;
unsigned int aac_data_len = 0;
faacDecFrameInfo frame_info;
char *sample_buf;
int bytes;
priv = (mp4_private_ipd*) ip_data->private_ipd;
// BUG_ON(priv->overflow_buf_len);
if (priv->mp4.sample > priv->mp4.num_samples)
return 0; /* EOF */
if (MP4ReadSample(priv->mp4.handle, priv->mp4.track, priv->mp4.sample,
&aac_data, &aac_data_len, NULL, NULL, NULL, NULL) == 0) {
qDebug() << "error reading mp4 sample" << priv->mp4.sample;
errno = EINVAL;
return -1;
}
priv->mp4.sample++;
if (!aac_data) {
qDebug() << "aac_data == NULL";
errno = EINVAL;
return -1;
}
sample_buf = (char *) faacDecDecode(priv->decoder, &frame_info, aac_data, aac_data_len);
free(aac_data);
if (!sample_buf || frame_info.bytesconsumed <= 0) {
qDebug() << "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;
}
/* 16-bit samples */
bytes = frame_info.samples * 2;
if (bytes > count) {
/* decoded too much; keep overflow. */
priv->overflow_buf = sample_buf + count;
priv->overflow_buf_len = bytes - count;
memcpy(buffer, sample_buf, count);
return count;
} else {
memcpy(buffer, sample_buf, bytes);
}
return bytes;
}
static int mp4_read(struct input_plugin_data *ip_data, char *buffer, int count)
{
struct mp4_private_ipd *priv;
int rc;
priv = (mp4_private_ipd*) ip_data->private_ipd;
/* 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;
return len;
}
do {
rc = decode_one_frame(ip_data, buffer, count);
} while (rc == -2);
return rc;
}
static int mp4_seek(struct input_plugin_data *ip_data, double offset)
{
struct mp4_private_ipd *priv;
MP4SampleId sample;
uint32_t scale;
priv = (mp4_private_ipd*) 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;
priv->mp4.sample = sample;
qDebug() << "seeking to sample" << sample;
return 0;
}
static int mp4_read_comments(struct input_plugin_data *ip_data,
struct keyval **comments)
{
struct mp4_private_ipd *priv;
uint16_t meta_num, meta_total;
uint8_t val;
/*uint8_t *ustr;
uint32_t size;*/
char *str;
GROWING_KEYVALS(c);
priv = (mp4_private_ipd*) ip_data->private_ipd;
/* 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_ipd *priv;
uint32_t scale;
uint64_t duration;
priv = (mp4_private_ipd*) 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 };
#ifndef _COMMENT_H
#define _COMMENT_H
struct keyval {
char *key;
char *val;
};
struct growing_keyvals {
struct keyval *comments;
int alloc;
int count;
};
#define GROWING_KEYVALS(name) struct growing_keyvals name = { NULL, 0, 0 }
struct keyval *comments_dup(const struct keyval *comments);
void comments_free(struct keyval *comments);
/* case insensitive key */
const char *comments_get_val(const struct keyval *comments, const char *key);
const char *comments_get_albumartist(const struct keyval *comments);
int comments_get_int(const struct keyval *comments, const char *key);
int comments_get_date(const struct keyval *comments, const char *key);
int comments_add(struct growing_keyvals *c, const char *key, char *val);
int comments_add_const(struct growing_keyvals *c, const char *key, const char *val);
void comments_terminate(struct growing_keyvals *c);
#endif
/*
* Copyright 2004-2005 Timo Hirvonen
*
* 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.
*/
#ifndef _IP_H
#define _IP_H
#include "comment.h"
#include "sf.h"
enum {
/* no error */
IP_ERROR_SUCCESS,
/* system error (error code in errno) */
IP_ERROR_ERRNO,
/* file type not supported */
IP_ERROR_UNRECOGNIZED_FILE_TYPE,
/* function not supported (usually seek) */
IP_ERROR_FUNCTION_NOT_SUPPORTED,
/* input plugin detected corrupted file */
IP_ERROR_FILE_FORMAT,
/* malformed uri */
IP_ERROR_INVALID_URI,
/* sample format not supported */
IP_ERROR_SAMPLE_FORMAT,
/* error parsing response line / headers */
IP_ERROR_HTTP_RESPONSE,
/* usually 404 */
IP_ERROR_HTTP_STATUS,
/* */
IP_ERROR_INTERNAL
};
struct input_plugin_data {
/* filled by ip-layer */
char *filename;
int fd;
unsigned int remote : 1;
unsigned int metadata_changed : 1;
/* shoutcast */
int counter;
int metaint;
char *metadata;
/* filled by plugin */
sample_format_t sf;
void * private_ipd;
};
struct input_plugin_ops {
int (*open)(struct input_plugin_data *ip_data);
int (*close)(struct input_plugin_data *ip_data);
int (*read)(struct input_plugin_data *ip_data, char *buffer, int count);
int (*seek)(struct input_plugin_data *ip_data, double offset);
int (*read_comments)(struct input_plugin_data *ip_data,
struct keyval **comments);
int (*duration)(struct input_plugin_data *ip_data);
};
#endif
/*
* Copyright 2004 Timo Hirvonen
*
* 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.
*/
#ifndef _SF_H
#define _SF_H
/*
* 0 1 big_endian 0-1
* 1 1 is_signed 0-1
* 2 1 unused 0
* 3-5 3 bits >> 3 0-7 (* 8 = 0-56)
* 6-23 18 rate 0-262143
* 24-31 8 channels 0-255
*/
typedef unsigned int sample_format_t;
#define SF_BIGENDIAN_MASK 0x00000001
#define SF_SIGNED_MASK 0x00000002
#define SF_BITS_MASK 0x00000038
#define SF_RATE_MASK 0x00ffffc0
#define SF_CHANNELS_MASK 0xff000000
#define SF_BIGENDIAN_SHIFT 0
#define SF_SIGNED_SHIFT 1
#define SF_BITS_SHIFT 0
#define SF_RATE_SHIFT 6
#define SF_CHANNELS_SHIFT 24
#define sf_get_bigendian(sf) (((sf) & SF_BIGENDIAN_MASK) >> SF_BIGENDIAN_SHIFT)
#define sf_get_signed(sf) (((sf) & SF_SIGNED_MASK ) >> SF_SIGNED_SHIFT)
#define sf_get_bits(sf) (((sf) & SF_BITS_MASK ) >> SF_BITS_SHIFT)
#define sf_get_rate(sf) (((sf) & SF_RATE_MASK ) >> SF_RATE_SHIFT)
#define sf_get_channels(sf) (((sf) & SF_CHANNELS_MASK ) >> SF_CHANNELS_SHIFT)
#define sf_bigendian(val) (((val) << SF_BIGENDIAN_SHIFT) & SF_BIGENDIAN_MASK)
#define sf_signed(val) (((val) << SF_SIGNED_SHIFT ) & SF_SIGNED_MASK)
#define sf_bits(val) (((val) << SF_BITS_SHIFT ) & SF_BITS_MASK)
#define sf_rate(val) (((val) << SF_RATE_SHIFT ) & SF_RATE_MASK)
#define sf_channels(val) (((val) << SF_CHANNELS_SHIFT ) & SF_CHANNELS_MASK)
#define sf_get_sample_size(sf) (sf_get_bits((sf)) >> 3)
#define sf_get_frame_size(sf) (sf_get_sample_size((sf)) * sf_get_channels((sf)))
#define sf_get_second_size(sf) (sf_get_rate((sf)) * sf_get_frame_size((sf)))
#endif
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Mixxx-devel mailing list
Mixxx-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mixxx-devel