diff options
Diffstat (limited to 'plugins/aac/aac.c')
-rw-r--r-- | plugins/aac/aac.c | 929 |
1 files changed, 389 insertions, 540 deletions
diff --git a/plugins/aac/aac.c b/plugins/aac/aac.c index b3417879..a7abece0 100644 --- a/plugins/aac/aac.c +++ b/plugins/aac/aac.c @@ -29,12 +29,7 @@ #include "../../deadbeef.h" #include "aac_parser.h" -#ifdef USE_MP4FF -#include "mp4ff/mp4ff.h" -#else -#warning linking mp4v2 to faad2 is illegal -#include <mp4v2/mp4v2.h> -#endif +#include "mp4ff.h" #define min(x,y) ((x)<(y)?(x):(y)) #define max(x,y) ((x)>(y)?(x):(y)) @@ -42,8 +37,6 @@ //#define trace(...) { fprintf(stderr, __VA_ARGS__); } #define trace(fmt,...) -// FIXME: use aac_probe in both _init and _insert, in order to avoid LOTS of code duplication - static DB_decoder_t plugin; static DB_functions_t *deadbeef; @@ -51,13 +44,8 @@ static DB_functions_t *deadbeef; #define AAC_BUFFER_SIZE (FAAD_MIN_STREAMSIZE * 16) #define OUT_BUFFER_SIZE 100000 -#ifdef USE_MP4FF #define MP4FILE mp4ff_t * #define MP4FILE_CB mp4ff_callback_t -#else -#define MP4FILE MP4FileHandle -#define MP4FILE_CB MP4FileProvider -#endif // aac channel mapping @@ -76,11 +64,9 @@ typedef struct { DB_fileinfo_t info; NeAACDecHandle dec; DB_FILE *file; - MP4FILE mp4file; + MP4FILE mp4; MP4FILE_CB mp4reader; NeAACDecFrameInfo frame_info; // last frame info - int32_t timescale; - uint32_t maxSampleSize; int mp4track; int mp4samples; int mp4sample; @@ -110,44 +96,19 @@ aac_open (uint32_t hints) { return _info; } -#ifdef USE_MP4FF static uint32_t aac_fs_read (void *user_data, void *buffer, uint32_t length) { - trace ("aac_fs_read %d\n", length); +// trace ("aac_fs_read %d\n", length); aac_info_t *info = user_data; return deadbeef->fread (buffer, 1, length, info->file); } static uint32_t aac_fs_seek (void *user_data, uint64_t position) { aac_info_t *info = user_data; - trace ("aac_fs_seek %lld (%lld)\n", position, position + info->junk); +// trace ("aac_fs_seek %lld (%lld)\n", position, position + info->junk); return deadbeef->fseek (info->file, position+info->junk, SEEK_SET); } -#else -static void * -aac_fs_open (const char *fname, MP4FileMode mode) { - return deadbeef->fopen (fname); -} -static int -aac_fs_seek (void* handle, int64_t pos) { - return deadbeef->fseek ((DB_FILE*)handle, pos, SEEK_SET); -} - -static int -aac_fs_read (void *handle, void *buffer, int64_t length, int64_t *nin, int64_t maxChunkSize) { - if (deadbeef->fread (buffer, length, 1, (DB_FILE*)handle) != 1) { - return 1; - } - *nin = length; - return 0; -} -static int -aac_fs_close (void *handle) { - deadbeef->fclose ((DB_FILE*)handle); - return 1; -} -#endif static int parse_aac_stream(DB_FILE *fp, int *psamplerate, int *pchannels, float *pduration, int *ptotalsamples) @@ -206,7 +167,7 @@ parse_aac_stream(DB_FILE *fp, int *psamplerate, int *pchannels, float *pduration continue; } else { - trace ("aac: frame #%d sync: %dch %d %d %d %d\n", frame, channels, samplerate, bitrate, samples, size); +// trace ("aac: frame #%d sync: %dch %d %d %d %d\n", frame, channels, samplerate, bitrate, samples, size); frame++; nsamples += samples; if (!stream_sr) { @@ -260,188 +221,73 @@ parse_aac_stream(DB_FILE *fp, int *psamplerate, int *pchannels, float *pduration return firstframepos; } -// returns -1 for error, 0 for mp4, 1 for aac -int -aac_probe (DB_FILE *fp, const char *fname, MP4FILE_CB *cb, float *duration, int *samplerate, int *channels, int *totalsamples, int *mp4track, MP4FILE *pmp4) { - // try mp4 - trace ("aac_probe: pos=%lld, junk=%d\n", deadbeef->ftell (fp), ((aac_info_t*)cb->user_data)->junk); - - if (mp4track) { - *mp4track = -1; - } - if (*pmp4) { - *pmp4 = NULL; - } - *duration = -1; -#ifdef USE_MP4FF - trace ("mp4ff_open_read\n"); - mp4ff_t *mp4 = mp4ff_open_read (cb); -#else - MP4FileHandle mp4 = MP4ReadProvider (fname, 0, cb); -#endif - if (!mp4) { - trace ("not an mp4 file\n"); - return -1; - } - if (pmp4) { - *pmp4 = mp4; - } -#ifdef USE_MP4FF - int ntracks = mp4ff_total_tracks (mp4); - if (ntracks > 0) { - trace ("m4a container detected, ntracks=%d\n", ntracks); - int i = -1; - trace ("looking for mp4 data...\n"); - int sr = -1; - unsigned char* buff = 0; - unsigned int buff_size = 0; - for (i = 0; i < ntracks; i++) { - mp4AudioSpecificConfig mp4ASC; - mp4ff_get_decoder_config(mp4, i, &buff, &buff_size); - if (buff) { - int rc = AudioSpecificConfig(buff, buff_size, &mp4ASC); - sr = mp4ASC.samplingFrequency; - if(rc < 0) { - free (buff); - buff = 0; - continue; - } - break; - } - } - if (i != ntracks && buff) - { - trace ("found audio track (%d)\n", i); - // init mp4 decoding - NeAACDecHandle dec = NeAACDecOpen (); - unsigned long srate; - unsigned char ch; - if (NeAACDecInit2(dec, buff, buff_size, &srate, &ch) < 0) { - trace ("NeAACDecInit2 returned error\n"); - goto error; - } - *samplerate = srate; - *channels = ch; - int samples = mp4ff_num_samples(mp4, i); - samples = (int64_t)samples * srate / mp4ff_time_scale (mp4, i); - int tsamples = samples; - NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration (dec); - conf->dontUpSampleImplicitSBR = 1; - NeAACDecSetConfiguration (dec, conf); - mp4AudioSpecificConfig mp4ASC; - int mp4framesize; - if (NeAACDecAudioSpecificConfig(buff, buff_size, &mp4ASC) >= 0) - { - mp4framesize = 1024; - if (mp4ASC.frameLengthFlag == 1) { - mp4framesize = 960; - } - // commented this out, since it fixes double-duration bug on - // some mp4 files - //if (mp4ASC.sbr_present_flag == 1) { - // mp4framesize *= 2; - //} - } - else { - trace ("NeAACDecAudioSpecificConfig failed, can't get mp4framesize\n"); - goto error; - } - tsamples *= mp4framesize; - - trace ("mp4 nsamples=%d, samplerate=%d, timescale=%d, duration=%lld\n", samples, *samplerate, mp4ff_time_scale(mp4, i), mp4ff_get_track_duration(mp4, i)); - *duration = (float)tsamples / (*samplerate); - trace ("mp4 duration: %f (tsamples %d/samplerate %d)\n", *duration, tsamples, *samplerate); - - NeAACDecClose (dec); - - if (totalsamples) { - *totalsamples = tsamples; - } - if (mp4track) { - *mp4track = i; - } - if (!*pmp4) { - mp4ff_close (mp4); - } - return 0; -error: - NeAACDecClose (dec); +static int +mp4_track_get_info(mp4ff_t *mp4, int track, float *duration, int *samplerate, int *channels, int *totalsamples, int *mp4framesize) { + int sr = -1; + unsigned char* buff = 0; + unsigned int buff_size = 0; + mp4AudioSpecificConfig mp4ASC; + mp4ff_get_decoder_config(mp4, track, &buff, &buff_size); + if (buff) { + int rc = AudioSpecificConfig(buff, buff_size, &mp4ASC); + sr = mp4ASC.samplingFrequency; + if(rc < 0) { free (buff); - if (!*pmp4) { - mp4ff_close (mp4); - } + trace ("aac: AudioSpecificConfig returned result=%d\n", rc); return -1; } - else { - trace ("audio track not found\n"); - mp4ff_close (mp4); - mp4 = NULL; - } - if (buff) { - free (buff); - buff = NULL; - } + } + + unsigned long srate; + unsigned char ch; + int samples; + // init mp4 decoding + NeAACDecHandle dec = NeAACDecOpen (); + if (NeAACDecInit2(dec, buff, buff_size, &srate, &ch) < 0) { + trace ("NeAACDecInit2 returned error\n"); + goto error; } -#else - MP4FileHandle mp4File = mp4; - MP4TrackId trackId = MP4FindTrackId(mp4File, 0, "audio", 0); - trace ("trackid: %d\n", trackId); - uint32_t timeScale = MP4GetTrackTimeScale(mp4File, trackId); - MP4Duration trackDuration = MP4GetTrackDuration(mp4File, trackId); - MP4SampleId numSamples = MP4GetTrackNumberOfSamples(mp4File, trackId); - u_int8_t* pConfig; - uint32_t configSize = 0; - bool res = MP4GetTrackESConfiguration(mp4File, trackId, &pConfig, &configSize); - if (res && pConfig) { - mp4AudioSpecificConfig mp4ASC; - int rc = AudioSpecificConfig(pConfig, configSize, &mp4ASC); - free (pConfig); - if (rc >= 0) { - *samplerate = mp4ASC.samplingFrequency; - *channels = MP4GetTrackAudioChannels (mp4File, trackId); -// int64_t duration = MP4ConvertFromTrackDuration (mp4File, trackId, trackDuration, timeScale); - int samples = MP4GetTrackNumberOfSamples (mp4File, trackId) * 1024 * (*channels); - trace ("mp4 nsamples=%d, timescale=%d, samplerate=%d\n", samples, timeScale, *samplerate); - *duration = (float)samples / (*samplerate); - - if (totalsamples) { - *totalsamples = samples; - } - if (mp4track) { - *mp4track = trackId; - } - if (!*pmp4) { - MP4Close (mp4); - } - return 0; - } + *samplerate = srate; + *channels = ch; + samples = (int64_t)mp4ff_num_samples(mp4, track); + + NeAACDecClose (dec); + + int i_sample_count = mp4ff_num_samples (mp4, track); + int i_sample; + + int64_t total_dur = 0; + for( i_sample = 0; i_sample < i_sample_count; i_sample++ ) + { + total_dur += mp4ff_get_sample_duration (mp4, track, i_sample); } -#endif - if (*pmp4) { - *pmp4 = NULL; + if (totalsamples) { + *totalsamples = total_dur * (*samplerate) / mp4ff_time_scale (mp4, track); + *mp4framesize = (*totalsamples) / i_sample; } + *duration = total_dur / (float)mp4ff_time_scale (mp4, track); - if (mp4) { -#if USE_MP4FF - mp4ff_close (mp4); -#else - MP4Close (mp4); -#endif - mp4 = NULL; + return 0; +error: + if (dec) { + NeAACDecClose (dec); } - trace ("mp4 track not found, looking for aac stream...\n"); + free (buff); + return -1; +} + +// returns -1 for error, 0 for aac +int +aac_probe (DB_FILE *fp, float *duration, int *samplerate, int *channels, int *totalsamples) { - // not an mp4, try raw aac -#if USE_MP4FF deadbeef->rewind (fp); -#endif if (parse_aac_stream (fp, samplerate, channels, duration, totalsamples) == -1) { trace ("aac stream not found\n"); return -1; } trace ("found aac stream\n"); - return 1; + return 0; } @@ -449,7 +295,9 @@ static int aac_init (DB_fileinfo_t *_info, DB_playItem_t *it) { aac_info_t *info = (aac_info_t *)_info; + deadbeef->pl_lock (); info->file = deadbeef->fopen (deadbeef->pl_find_meta (it, ":URI")); + deadbeef->pl_unlock (); if (!info->file) { return -1; } @@ -474,156 +322,67 @@ aac_init (DB_fileinfo_t *_info, DB_playItem_t *it) { } info->mp4track = -1; -#if USE_MP4FF info->mp4reader.read = aac_fs_read; info->mp4reader.write = NULL; info->mp4reader.seek = aac_fs_seek; info->mp4reader.truncate = NULL; info->mp4reader.user_data = info; -#else - info->mp4reader.open = aac_fs_open; - info->mp4reader.seek = aac_fs_seek; - info->mp4reader.read = aac_fs_read; - info->mp4reader.write = NULL; - info->mp4reader.close = aac_fs_close; -#endif if (!info->file->vfs->is_streaming ()) { -#ifdef USE_MP4FF trace ("aac_init: mp4ff_open_read %s\n", deadbeef->pl_find_meta (it, ":URI")); - info->mp4file = mp4ff_open_read (&info->mp4reader); - if (info->mp4file) { - int ntracks = mp4ff_total_tracks (info->mp4file); - if (ntracks > 0) { - trace ("m4a container detected, ntracks=%d\n", ntracks); - int i = -1; - unsigned char* buff = 0; - unsigned int buff_size = 0; - for (i = 0; i < ntracks; i++) { - mp4AudioSpecificConfig mp4ASC; - mp4ff_get_decoder_config (info->mp4file, i, &buff, &buff_size); - if(buff){ - int rc = AudioSpecificConfig(buff, buff_size, &mp4ASC); - if(rc < 0) - continue; - break; - } + info->mp4 = mp4ff_open_read (&info->mp4reader); + if (info->mp4) { + int ntracks = mp4ff_total_tracks (info->mp4); + for (int i = 0; i < ntracks; i++) { + if (mp4ff_get_track_type (info->mp4, i) != TRACK_AUDIO) { + continue; } - trace ("mp4 probe-buffer size: %d\n", buff_size); - - if (i != ntracks && buff) - { - trace ("mp4 track: %d\n", i); - int samples = mp4ff_num_samples(info->mp4file, i); - info->mp4samples = samples; + int res = mp4_track_get_info (info->mp4, i, &duration, &samplerate, &channels, &totalsamples, &info->mp4framesize); + if (res >= 0 && duration > 0) { info->mp4track = i; - - // init mp4 decoding - info->dec = NeAACDecOpen (); - unsigned long srate; - unsigned char ch; - if (NeAACDecInit2(info->dec, buff, buff_size, &srate, &ch) < 0) { - trace ("NeAACDecInit2 returned error\n"); - free (buff); - return -1; - } - samplerate = srate; - channels = ch; - samples = (int64_t)samples * srate / mp4ff_time_scale (info->mp4file, i); - totalsamples = samples; - NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration (info->dec); - conf->dontUpSampleImplicitSBR = 1; - NeAACDecSetConfiguration (info->dec, conf); - mp4AudioSpecificConfig mp4ASC; - if (NeAACDecAudioSpecificConfig(buff, buff_size, &mp4ASC) >= 0) - { - info->mp4framesize = 1024; - if (mp4ASC.frameLengthFlag == 1) { - info->mp4framesize = 960; - } -// if (mp4ASC.sbr_present_flag == 1) { -// info->mp4framesize *= 2; -// } - } - totalsamples *= info->mp4framesize; - duration = (float)totalsamples / samplerate; - } - else { - mp4ff_close (info->mp4file); - info->mp4file = NULL; - } - if (buff) { - free (buff); + break; } } - else { - mp4ff_close (info->mp4file); - info->mp4file = NULL; - } - } -// {{{ libmp4v2 code -#else - trace ("aac_init: MP4ReadProvider %s\n", deadbeef->pl_find_meta (it, ":URI")); - info->mp4file = MP4ReadProvider (deadbeef->pl_find_meta (it, ":URI"), 0, &info->mp4reader); - info->mp4track = MP4FindTrackId(info->mp4file, 0, "audio", 0); - trace ("aac_init: MP4FindTrackId returned %d\n", info->mp4track); - if (info->mp4track >= 0) { - info->timescale = MP4GetTrackTimeScale(info->mp4file, info->mp4track); - - u_int8_t* pConfig; - uint32_t configSize = 0; - bool res = MP4GetTrackESConfiguration(info->mp4file, info->mp4track, &pConfig, &configSize); - - mp4AudioSpecificConfig mp4ASC; - int rc = AudioSpecificConfig(pConfig, configSize, &mp4ASC); - if (rc >= 0) { - _info->samplerate = mp4ASC.samplingFrequency; - _info->channels = MP4GetTrackAudioChannels (info->mp4file, info->mp4track); - totalsamples = MP4GetTrackNumberOfSamples (info->mp4file, info->mp4track) * 1024 * _info->channels; + trace ("track: %d\n", info->mp4track); + if (info->mp4track >= 0) { + // prepare decoder + int res = mp4_track_get_info (info->mp4, info->mp4track, &duration, &samplerate, &channels, &totalsamples, &info->mp4framesize); + if (res != 0) { + trace ("aac: mp4_track_get_info(%d) returned error\n", info->mp4track); + return -1; + } // init mp4 decoding + info->mp4samples = mp4ff_num_samples(info->mp4, info->mp4track); info->dec = NeAACDecOpen (); unsigned long srate; unsigned char ch; - if (NeAACDecInit2(info->dec, pConfig, configSize, &srate, &ch) < 0) { + unsigned char* buff = 0; + unsigned int buff_size = 0; + mp4AudioSpecificConfig mp4ASC; + mp4ff_get_decoder_config (info->mp4, info->mp4track, &buff, &buff_size); + if (NeAACDecInit2(info->dec, buff, buff_size, &srate, &ch) < 0) { trace ("NeAACDecInit2 returned error\n"); + free (buff); return -1; } - samplerate = srate; - channels = ch; - NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration (info->dec); - conf->dontUpSampleImplicitSBR = 1; - NeAACDecSetConfiguration (info->dec, conf); - mp4AudioSpecificConfig mp4ASC; - if (NeAACDecAudioSpecificConfig(pConfig, configSize, &mp4ASC) >= 0) - { - info->mp4framesize = 1024; - if (mp4ASC.frameLengthFlag == 1) { - info->mp4framesize = 960; - } -// if (mp4ASC.sbr_present_flag == 1) { -// info->mp4framesize *= 2; -// } + + if (buff) { + free (buff); } - //totalsamples *= info->mp4framesize; - free (pConfig); - info->maxSampleSize = MP4GetTrackMaxSampleSize(info->mp4file, info->mp4track); - info->samplebuffer = malloc (info->maxSampleSize); - info->mp4sample = 1; + trace ("aac: successfully initialized track %d\n", info->mp4track); + _info->fmt.samplerate = samplerate; + _info->fmt.channels = channels; } else { - MP4Close (info->mp4file); - info->mp4file = NULL; + trace ("aac: track not found in mp4 container\n"); + mp4ff_close (info->mp4); + info->mp4 = NULL; } } - else { - MP4Close (info->mp4file); - info->mp4file = NULL; - } -#endif -// }}} - if (!info->mp4file) { - trace ("mp4 track not found, looking for aac stream...\n"); + + if (!info->mp4) { + trace ("aac: looking for raw stream...\n"); if (info->junk >= 0) { deadbeef->fseek (info->file, info->junk, SEEK_SET); @@ -646,10 +405,10 @@ aac_init (DB_fileinfo_t *_info, DB_playItem_t *it) { deadbeef->rewind (info->file); } trace ("found aac stream (junk: %d, offs: %d)\n", info->junk, offs); - } - _info->fmt.channels = channels; - _info->fmt.samplerate = samplerate; + _info->fmt.channels = channels; + _info->fmt.samplerate = samplerate; + } } else { // sync before attempting to init @@ -673,11 +432,8 @@ aac_init (DB_fileinfo_t *_info, DB_playItem_t *it) { _info->fmt.bps = 16; _info->plugin = &plugin; - if (!info->mp4file) { - //trace ("NeAACDecGetCapabilities\n"); - //unsigned long caps = NeAACDecGetCapabilities(); - - trace ("NeAACDecOpen\n"); + if (!info->mp4) { + trace ("NeAACDecOpen for raw stream\n"); info->dec = NeAACDecOpen (); trace ("prepare for NeAACDecInit: fread %d from offs %lld\n", AAC_BUFFER_SIZE, deadbeef->ftell (info->file)); @@ -721,7 +477,7 @@ aac_init (DB_fileinfo_t *_info, DB_playItem_t *it) { info->endsample = totalsamples-1; } } - trace ("totalsamples: %d, endsample: %d, samples-from-duration: %d\n", totalsamples-1, info->endsample, (int)deadbeef->pl_get_item_duration (it)*44100); + trace ("totalsamples: %d, endsample: %d, samples-from-duration: %d, samplerate %d, channels %d\n", totalsamples-1, info->endsample, (int)deadbeef->pl_get_item_duration (it)*44100, _info->fmt.samplerate, _info->fmt.channels); for (int i = 0; i < _info->fmt.channels; i++) { _info->fmt.channelmask |= 1 << i; @@ -740,12 +496,8 @@ aac_free (DB_fileinfo_t *_info) { if (info->file) { deadbeef->fclose (info->file); } - if (info->mp4file) { -#ifdef USE_MP4FF - mp4ff_close (info->mp4file); -#else - MP4Close (info->mp4file); -#endif + if (info->mp4) { + mp4ff_close (info->mp4); } if (info->dec) { NeAACDecClose (info->dec); @@ -877,37 +629,20 @@ aac_read (DB_fileinfo_t *_info, char *bytes, int size) { char *samples = NULL; - if (info->mp4file) { + if (info->mp4) { + if (info->mp4sample >= info->mp4samples) { + trace ("aac: finished with the last mp4sample\n"); + break; + } + unsigned char *buffer = NULL; int buffer_size = 0; -#ifdef USE_MP4FF - int rc = mp4ff_read_sample (info->mp4file, info->mp4track, info->mp4sample, &buffer, &buffer_size); + int rc = mp4ff_read_sample (info->mp4, info->mp4track, info->mp4sample, &buffer, &buffer_size); if (rc == 0) { + trace ("mp4ff_read_sample failed\n"); info->eof = 1; break; } -#else - - buffer = info->samplebuffer; - buffer_size = info->maxSampleSize; - MP4Timestamp sampleTime; - MP4Duration sampleDuration; - MP4Duration sampleRenderingOffset; - bool isSyncSample; - MP4ReadSample (info->mp4file, info->mp4track, info->mp4sample, &buffer, &buffer_size, &sampleTime, &sampleDuration, &sampleRenderingOffset, &isSyncSample); - // convert timestamp and duration from track time to milliseconds - u_int64_t myTime = MP4ConvertFromTrackTimestamp (info->mp4file, info->mp4track, - sampleTime, MP4_MSECS_TIME_SCALE); - - u_int64_t myDuration = MP4ConvertFromTrackDuration (info->mp4file, info->mp4track, - sampleDuration, MP4_MSECS_TIME_SCALE); -#endif - if (info->mp4sample >= info->mp4samples) { - if (buffer) { - free (buffer); - } - break; - } info->mp4sample++; samples = NeAACDecDecode(info->dec, &info->frame_info, buffer, buffer_size); @@ -915,6 +650,7 @@ aac_read (DB_fileinfo_t *_info, char *bytes, int size) { free (buffer); } if (!samples) { + trace ("aac: NeAACDecDecode returned NULL\n"); break; } } @@ -1028,8 +764,8 @@ aac_seek_sample (DB_fileinfo_t *_info, int sample) { aac_info_t *info = (aac_info_t *)_info; sample += info->startsample; - if (info->mp4file) { - int scale = _info->fmt.samplerate / mp4ff_time_scale (info->mp4file, info->mp4track) * info->mp4framesize; + if (info->mp4) { + int scale = info->mp4framesize; info->mp4sample = sample / scale; info->skipsamples = sample - info->mp4sample * scale; } @@ -1060,7 +796,6 @@ aac_seek (DB_fileinfo_t *_info, float t) { return aac_seek_sample (_info, t * _info->fmt.samplerate); } -#ifdef USE_MP4FF static const char *metainfo[] = { "artist", "artist", "title", "title", @@ -1091,36 +826,63 @@ int32_t mp4ff_meta_find_by_name(const mp4ff_t *f, const char *item, char **value void aac_load_tags (DB_playItem_t *it, mp4ff_t *mp4) { char *s = NULL; - for (int i = 0; metainfo[i]; i += 2) { - if (mp4ff_meta_find_by_name(mp4, metainfo[i], &s)) { - deadbeef->pl_add_meta (it, metainfo[i+1], s); - free (s); + int got_itunes_tags = 0; + + int n = mp4ff_meta_get_num_items (mp4); + for (int t = 0; t < n; t++) { + char *key = NULL; + char *value = NULL; + int res = mp4ff_meta_get_by_index(mp4, t, &key, &value); + if (key && value) { + got_itunes_tags = 1; + if (strcasecmp (key, "cover")) { + if (!strcasecmp (key, "replaygain_track_gain")) { + deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKGAIN, atof (value)); + } + else if (!strcasecmp (key, "replaygain_album_gain")) { + deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMGAIN, atof (value)); + } + else if (!strcasecmp (key, "replaygain_track_peak")) { + deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKPEAK, atof (value)); + } + else if (!strcasecmp (key, "replaygain_album_peak")) { + deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMPEAK, atof (value)); + } + else { + int i; + for (i = 0; metainfo[i]; i += 2) { + if (!strcasecmp (metainfo[i], key)) { + deadbeef->pl_add_meta (it, metainfo[i+1], value); + break; + } + } + if (!metainfo[i]) { + deadbeef->pl_add_meta (it, key, value); + } + } + } + } + if (key) { + free (key); + } + if (value) { + free (value); } } - if (mp4ff_meta_find_by_name(mp4, "replaygain_track_gain", &s)) { - deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKGAIN, atof (s)); - free (s); - } - if (mp4ff_meta_find_by_name(mp4, "replaygain_track_peak", &s)) { - deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKPEAK, atof (s)); - free (s); - } - if (mp4ff_meta_find_by_name(mp4, "replaygain_album_gain", &s)) { - deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMGAIN, atof (s)); - free (s); - } - if (mp4ff_meta_find_by_name(mp4, "replaygain_album_peak", &s)) { - deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMPEAK, atof (s)); - free (s); + + if (got_itunes_tags) { + uint32_t f = deadbeef->pl_get_item_flags (it); + f |= DDB_TAG_ITUNES; + deadbeef->pl_set_item_flags (it, f); } } -#endif int aac_read_metadata (DB_playItem_t *it) { -#ifdef USE_MP4FF + deadbeef->pl_lock (); DB_FILE *fp = deadbeef->fopen (deadbeef->pl_find_meta (it, ":URI")); + deadbeef->pl_unlock (); if (!fp) { return -1; } @@ -1130,12 +892,23 @@ aac_read_metadata (DB_playItem_t *it) { return -1; } + aac_info_t inf; + memset (&inf, 0, sizeof (inf)); + inf.file = fp; + inf.junk = deadbeef->junk_get_leading_size (fp); + if (inf.junk >= 0) { + deadbeef->fseek (inf.file, inf.junk, SEEK_SET); + } + else { + inf.junk = 0; + } + MP4FILE_CB cb = { .read = aac_fs_read, .write = NULL, .seek = aac_fs_seek, .truncate = NULL, - .user_data = fp + .user_data = &inf }; deadbeef->pl_delete_all_meta (it); @@ -1144,100 +917,139 @@ aac_read_metadata (DB_playItem_t *it) { if (mp4) { aac_load_tags (it, mp4); mp4ff_close (mp4); - deadbeef->pl_add_meta (it, "title", NULL); - } - else { - /*int apeerr = */deadbeef->junk_apev2_read (it, fp); - /*int v2err = */deadbeef->junk_id3v2_read (it, fp); - /*int v1err = */deadbeef->junk_id3v1_read (it, fp); - deadbeef->pl_add_meta (it, "title", NULL); } + /*int apeerr = */deadbeef->junk_apev2_read (it, fp); + /*int v2err = */deadbeef->junk_id3v2_read (it, fp); + /*int v1err = */deadbeef->junk_id3v1_read (it, fp); deadbeef->fclose (fp); -#endif return 0; } -#ifdef USE_MP4FF +typedef struct { + char *title; + int32_t startsample; + int32_t endsample; +} aac_chapter_t; + +static aac_chapter_t * +aac_load_itunes_chapters (mp4ff_t *mp4, /* out */ int *num_chapters, int samplerate) { + *num_chapters = 0; + int i_entry_count = mp4ff_chap_get_num_tracks (mp4); + int i_tracks = mp4ff_total_tracks (mp4); + int i, j; + for( i = 0; i < i_entry_count; i++ ) + { + for( j = 0; j < i_tracks; j++ ) + { + int32_t tt = mp4ff_get_track_type (mp4, j); + trace ("aac: i_tracks=%d found track id=%d type=%d (expected %d %d)\n", i_tracks, mp4ff_get_track_id (mp4, j), mp4ff_get_track_type (mp4, j), mp4ff_chap_get_track_id (mp4, i), TRACK_TEXT); + if(mp4ff_chap_get_track_id (mp4, i) == mp4ff_get_track_id (mp4, j) && + mp4ff_get_track_type (mp4, j) == TRACK_TEXT) { + trace ("aac: found subt track\n"); + break; + } + } + if( j < i_tracks ) + { + int i_sample_count = mp4ff_num_samples (mp4, j); + int i_sample; + + aac_chapter_t *chapters = malloc (sizeof (aac_chapter_t) * i_sample_count); + memset (chapters, 0, sizeof (aac_chapter_t) * i_sample_count); + *num_chapters = 0; + + int64_t total_dur = 0; + int64_t curr_sample = 0; + for( i_sample = 0; i_sample < i_sample_count; i_sample++ ) + { #if 0 -static uint32_t -mp4ff_read_cb (void *user_data, void *buffer, uint32_t length) { -// trace ("aac_fs_read %d\n", length); - FILE *fp = (FILE *)user_data; - return fread (buffer, 1, length, fp); -} -static uint32_t -mp4ff_seek_cb (void *user_data, uint64_t position) { -// trace ("aac_fs_seek\n"); - FILE *fp = (FILE *)user_data; - return fseek (fp, position, SEEK_SET); -} -static uint32_t -mp4ff_write_cb(void *user_data, void *buffer, uint32_t length) { - FILE *fp = (FILE *)user_data; - return fwrite (buffer, 1, length, fp); -} + const int64_t i_dts = mp4ff_get_track_dts (mp4, j, i_sample); + const int64_t i_pts_delta = mp4ff_get_track_pts_delta(mp4, j, i_sample); + trace ("i_dts = %lld, i_pts_delta = %lld\n", i_dts, i_pts_delta); + const unsigned int i_size = mp4ff_get_track_sample_size(mp4, j, i_sample); + if (i_size <= 0) { + continue; + } -static uint32_t -mp4ff_truncate_cb(void *user_data) -{ - FILE *fp = (FILE *)user_data; - ftruncate(fileno(fp), ftello(fp)); - return 0; -} + int64_t i_time_offset = i_dts + max (i_pts_delta, 0); #endif + int32_t dur = (int64_t)1000 * mp4ff_get_sample_duration (mp4, j, i_sample) / mp4ff_time_scale (mp4, j); // milliseconds + total_dur += dur; +#if 0 + trace ("dur: %d %f min // offs: %lld %f (from currsample: %f)\n", dur, dur / 1000.f / 60.f, i_time_offset, i_time_offset / 1000000.f / 60.f, curr_sample * 1000.f/ samplerate); +#else + trace ("dur: %d %f min\n", dur, dur / 1000.f / 60.f); #endif + unsigned char *buffer = NULL; + int buffer_size = 0; -#ifdef USE_MP4FF -#if 0 -static int -aac_write_metadata (DB_playItem_t *it) { - mp4ff_metadata_t md; - memset (&md, 0, sizeof (md)); - deadbeef->pl_lock (); - DB_metaInfo_t *meta = deadbeef->pl_get_metadata_head (it); + int rc = mp4ff_read_sample (mp4, j, i_sample, &buffer, &buffer_size); - // find numtags 1st - while (meta) { - if (meta->key[0] != ':') { - md.count++; - } - meta = meta->next; - } - - // fill tags - if (md.count) { - md.tags = malloc (sizeof (mp4ff_tag_t) * md.count); - int n = 0; - meta = deadbeef->pl_get_metadata_head (it); - while (meta) { - if (meta->key[0] != ':') { - md.tags[n].item = "";//(char *)meta->key; - md.tags[n].value = (char *)meta->value; - n++; + if (rc == 0 || !buffer) { + continue; + } + int len = (buffer[0] << 8) | buffer[1]; + len = min (len, buffer_size - 2); + chapters[*num_chapters].title = strndup (&buffer[2], len); + chapters[*num_chapters].startsample = curr_sample; + curr_sample += (int64_t)dur * samplerate / 1000.f; + chapters[*num_chapters].endsample = curr_sample - 1; + trace ("aac: chapter %d: %s, s=%d e=%d\n", *num_chapters, chapters[*num_chapters].title, chapters[*num_chapters].startsample, chapters[*num_chapters].endsample); + if (buffer) { + free (buffer); + } + (*num_chapters)++; } - meta = meta->next; + trace ("aac: total dur: %lld (%f min)\n", total_dur, total_dur / 1000.f / 60.f); + return chapters; } } + return NULL; +} - mp4ff_callback_t f = { - .read = mp4ff_read_cb, - .write = mp4ff_write_cb, - .seek = mp4ff_seek_cb, - .truncate = mp4ff_truncate_cb, - }; - - FILE *fp = fopen (deadbeef->pl_find_meta (it, ":URI"), "w"); - f.user_data = fp; +static DB_playItem_t * +aac_insert_with_chapters (ddb_playlist_t *plt, DB_playItem_t *after, DB_playItem_t *origin, aac_chapter_t *chapters, int num_chapters, int totalsamples, int samplerate) { + deadbeef->pl_lock (); + DB_playItem_t *ins = after; + for (int i = 0; i < num_chapters; i++) { + const char *uri = deadbeef->pl_find_meta_raw (origin, ":URI"); + const char *dec = deadbeef->pl_find_meta_raw (origin, ":DECODER"); + const char *ftype= "MP4 AAC";//pl_find_meta_raw (origin, ":FILETYPE"); + + DB_playItem_t *it = deadbeef->pl_item_alloc_init (uri, dec); + deadbeef->pl_set_meta_int (it, ":TRACKNUM", i); + deadbeef->pl_set_meta_int (it, "TRACK", i); + deadbeef->pl_add_meta (it, "title", chapters[i].title); + it->startsample = chapters[i].startsample; + it->endsample = chapters[i].endsample; + deadbeef->pl_replace_meta (it, ":FILETYPE", ftype); + deadbeef->plt_set_item_duration (plt, it, (float)(it->endsample - it->startsample + 1) / samplerate); + after = deadbeef->plt_insert_item (plt, after, it); + deadbeef->pl_item_unref (it); + } + deadbeef->pl_item_ref (after); + + DB_playItem_t *first = deadbeef->pl_get_next (ins, PL_MAIN); + + if (!first) { + first = deadbeef->plt_get_first (plt, PL_MAIN); + } - mp4ff_meta_update (&f, &md); - if (md.tags) { - free (md.tags); + if (!first) { + deadbeef->pl_unlock (); + return NULL; } + trace ("aac: split by chapters success\n"); + // copy metadata from embedded tags + uint32_t f = deadbeef->pl_get_item_flags (origin); + f |= DDB_IS_SUBTRACK; + deadbeef->pl_set_item_flags (origin, f); + deadbeef->pl_items_copy_junk (origin, first, after); + deadbeef->pl_item_unref (first); + deadbeef->pl_unlock (); - return 0; + return after; } -#endif -#endif static DB_playItem_t * aac_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) { @@ -1275,81 +1087,124 @@ aac_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) { // slowwww! info.file = fp; MP4FILE_CB cb = { -#ifdef USE_MP4FF .read = aac_fs_read, .write = NULL, .seek = aac_fs_seek, .truncate = NULL, .user_data = &info -#else - .open = aac_fs_open, - .seek = aac_fs_seek, - .read = aac_fs_read, - .write = NULL, - .close = aac_fs_close -#endif }; + mp4ff_t *mp4 = mp4ff_open_read (&cb); + if (mp4) { + int ntracks = mp4ff_total_tracks (mp4); + trace ("aac: numtracks=%d\n", ntracks); + int i; + for (i = 0; i < ntracks; i++) { + if (mp4ff_get_track_type (mp4, i) != TRACK_AUDIO) { + trace ("aac: track %d is not audio\n"); + continue; + } + int mp4framesize; + int res = mp4_track_get_info (mp4, i, &duration, &samplerate, &channels, &totalsamples, &mp4framesize); + if (res >= 0 && duration > 0) { + trace ("aac: found audio track %d (duration=%f, totalsamples=%d)\n", i, duration, totalsamples); + + int num_chapters; + aac_chapter_t *chapters = NULL; + if (mp4ff_chap_get_num_tracks(mp4) > 0) { + chapters = aac_load_itunes_chapters (mp4, &num_chapters, samplerate); + } - int res = aac_probe (fp, fname, &cb, &duration, &samplerate, &channels, &totalsamples, &mp4track, &mp4); - if (res == -1) { - deadbeef->fclose (fp); - return NULL; - } - else if (res == 0) { - ftype = "MP4 AAC"; - } - else if (res == 1) { - ftype = "RAW AAC"; + DB_playItem_t *it = deadbeef->pl_item_alloc_init (fname, plugin.plugin.id); + ftype = "MP4 AAC"; + deadbeef->pl_add_meta (it, ":FILETYPE", ftype); + deadbeef->pl_set_meta_int (it, ":TRACKNUM", i); + deadbeef->plt_set_item_duration (plt, it, duration); + aac_load_tags (it, mp4); + int apeerr = deadbeef->junk_apev2_read (it, fp); + int v2err = deadbeef->junk_id3v2_read (it, fp); + int v1err = deadbeef->junk_id3v1_read (it, fp); + + int64_t fsize = deadbeef->fgetlength (fp); + + char s[100]; + snprintf (s, sizeof (s), "%lld", fsize); + deadbeef->pl_add_meta (it, ":FILE_SIZE", s); + deadbeef->pl_add_meta (it, ":BPS", "16"); + snprintf (s, sizeof (s), "%d", channels); + deadbeef->pl_add_meta (it, ":CHANNELS", s); + snprintf (s, sizeof (s), "%d", samplerate); + deadbeef->pl_add_meta (it, ":SAMPLERATE", s); + int br = (int)roundf(fsize / duration * 8 / 1000); + snprintf (s, sizeof (s), "%d", br); + deadbeef->pl_add_meta (it, ":BITRATE", s); + + // embedded chapters + deadbeef->pl_lock (); // FIXME: is it needed? + if (chapters && num_chapters > 0) { + DB_playItem_t *cue = aac_insert_with_chapters (plt, after, it, chapters, num_chapters, totalsamples, samplerate); + for (int n = 0; n < num_chapters; n++) { + if (chapters[n].title) { + free (chapters[n].title); + } + } + free (chapters); + if (cue) { + deadbeef->pl_item_unref (it); + deadbeef->pl_item_unref (cue); + deadbeef->pl_unlock (); + return cue; + } + } + + // embedded cue + const char *cuesheet = deadbeef->pl_find_meta (it, "cuesheet"); + DB_playItem_t *cue = NULL; + + if (cuesheet) { + cue = deadbeef->plt_insert_cue_from_buffer (plt, after, it, cuesheet, strlen (cuesheet), totalsamples, samplerate); + if (cue) { + deadbeef->pl_item_unref (it); + deadbeef->pl_item_unref (cue); + deadbeef->pl_unlock (); + return cue; + } + } + deadbeef->pl_unlock (); + + cue = deadbeef->plt_insert_cue (plt, after, it, totalsamples, samplerate); + if (cue) { + deadbeef->pl_item_unref (it); + deadbeef->pl_item_unref (cue); + return cue; + } + + after = deadbeef->plt_insert_item (plt, after, it); + deadbeef->pl_item_unref (it); + break; + } + } + mp4ff_close (mp4); + if (i < ntracks) { + return after; + } } } - + trace ("aac: mp4 container failed, trying raw aac\n"); + int res = aac_probe (fp, &duration, &samplerate, &channels, &totalsamples); + if (res == -1) { + deadbeef->fclose (fp); + return NULL; + } + ftype = "RAW AAC"; DB_playItem_t *it = deadbeef->pl_item_alloc_init (fname, plugin.plugin.id); deadbeef->pl_add_meta (it, ":FILETYPE", ftype); deadbeef->plt_set_item_duration (plt, it, duration); trace ("duration: %f sec\n", duration); // read tags - if (mp4) { -#ifdef USE_MP4FF - aac_load_tags (it, mp4); - mp4ff_close (mp4); -#else - const MP4Tags *tags = MP4TagsAlloc (); - MP4TagsFetch (tags, mp4); - - deadbeef->pl_add_meta (it, "title", tags->name); - deadbeef->pl_add_meta (it, "artist", tags->artist); - deadbeef->pl_add_meta (it, "albumArtist", tags->albumArtist); - deadbeef->pl_add_meta (it, "album", tags->album); - deadbeef->pl_add_meta (it, "composer", tags->composer); - deadbeef->pl_add_meta (it, "comments", tags->comments); - deadbeef->pl_add_meta (it, "genre", tags->genre); - deadbeef->pl_add_meta (it, "year", tags->releaseDate); - char s[10]; - if (tags->track) { - snprintf (s, sizeof (s), "%d", tags->track->index); - deadbeef->pl_add_meta (it, "track", s); - snprintf (s, sizeof (s), "%d", tags->track->total); - deadbeef->pl_add_meta (it, "numtracks", s); - } - if (tags->disk) { - snprintf (s, sizeof (s), "%d", tags->disk->index); - deadbeef->pl_add_meta (it, "disc", s); - snprintf (s, sizeof (s), "%d", tags->disk->total); - deadbeef->pl_add_meta (it, "numdiscs", s); - } - deadbeef->pl_add_meta (it, "copyright", tags->copyright); - deadbeef->pl_add_meta (it, "vendor", tags->encodedBy); - deadbeef->pl_add_meta (it, "title", NULL); - MP4TagsFree (tags); - MP4Close (mp4); -#endif - } - int apeerr = deadbeef->junk_apev2_read (it, fp); int v2err = deadbeef->junk_id3v2_read (it, fp); int v1err = deadbeef->junk_id3v1_read (it, fp); - deadbeef->pl_add_meta (it, "title", NULL); int64_t fsize = deadbeef->fgetlength (fp); @@ -1391,14 +1246,13 @@ aac_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) { } } - deadbeef->pl_add_meta (it, "title", NULL); - after = deadbeef->plt_insert_item (plt, after, it); deadbeef->pl_item_unref (it); + return after; } -static const char * exts[] = { "aac", "mp4", "m4a", NULL }; +static const char * exts[] = { "aac", "mp4", "m4a", "m4b", NULL }; // define plugin interface static DB_decoder_t plugin = { @@ -1438,11 +1292,6 @@ static DB_decoder_t plugin = { .seek_sample = aac_seek_sample, .insert = aac_insert, .read_metadata = aac_read_metadata, -#ifdef USE_MP4FF - // mp4ff metadata writer doesn't work - // .write_metadata = aac_write_metadata, -#else -#endif .exts = exts, }; |