/* * Copyright 2008-2013 Various Authors * Copyright 2006 dnk * * 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, see . */ #include "../ip.h" #include "../xmalloc.h" #include "../debug.h" #include "../id3.h" #include "../file.h" #ifdef HAVE_CONFIG #include "../config/mp4.h" #endif #include "../comment.h" #include "../utils.h" #include "aac.h" #if USE_MPEG4IP #include #else #include #endif #include #include #include #include #include #include /* no perfect fallback, for example faac uses only 1024 samples to prime */ #define ENCODER_DELAY_DEFAULT 2112 struct mp4_private { char *overflow_buf; int overflow_buf_len; unsigned char channels; unsigned long sample_rate; unsigned long frame_size; unsigned long encoder_delay; unsigned long drop_samples; unsigned long decoded_samples; NeAACDecHandle decoder; /* typedef void * */ struct { MP4FileHandle handle; /* typedef void * */ MP4TrackId track; MP4SampleId sample; /* "media sample" (AAC access unit) id */ MP4SampleId num_samples; MP4Duration duration; /* audio samples, including encoder delay */ uint32_t scale; /* sample_rate but from mp4 header */ } mp4; struct { unsigned long samples; unsigned long bytes; } current; }; static bool try_iTunSMPB(struct mp4_private *priv) { /* SMPB == seamless playback, older tag for gapless playback */ MP4ItmfItemList *itmf_list = MP4ItmfGetItemsByMeaning(priv->mp4.handle, "com.apple.iTunes", "iTunSMPB"); if (!itmf_list) return false; MP4ItmfItem* item = itmf_list->elements; if (item == NULL || item->dataList.size == 0) { MP4ItmfItemListFree(itmf_list); return false; } char *pos = item->dataList.elements[0].value; (void)strtol(pos, &pos, 16); // pad unsigned long delay = strtol(pos, &pos, 16); (void)strtol(pos, &pos, 16); // remainder unsigned long samples = strtol(pos, &pos, 16); priv->encoder_delay = delay; priv->mp4.duration = delay + samples; MP4ItmfItemListFree(itmf_list); return true; } static MP4TrackId mp4_get_track(MP4FileHandle *handle) { int i, num_tracks; const char *track_type; uint8_t obj_type; MP4TrackId id; num_tracks = MP4GetNumberOfTracks(handle, NULL, 0); for (i = 0; i < num_tracks; i++) { id = MP4FindTrackId(handle, i, NULL, 0); track_type = MP4GetTrackType(handle, id); if (!track_type) continue; if (!MP4_IS_AUDIO_TRACK_TYPE(track_type)) continue; /* MP4GetTrackAudioType */ obj_type = MP4GetTrackEsdsObjectTypeId(handle, id); if (obj_type == MP4_INVALID_AUDIO_TYPE) continue; if (obj_type == MP4_MPEG4_AUDIO_TYPE) { obj_type = MP4GetTrackAudioMpeg4Type(handle, id); if (MP4_IS_MPEG4_AAC_AUDIO_TYPE(obj_type)) return id; } else { if (MP4_IS_AAC_AUDIO_TYPE(obj_type)) return id; } } return MP4_INVALID_TRACK_ID; } static void mp4_get_channel_map(struct input_plugin_data *ip_data) { struct mp4_private *priv = ip_data->private; unsigned char *aac_data = NULL; unsigned int aac_data_len = 0; NeAACDecFrameInfo frame_info; int i; ip_data->channel_map[0] = CHANNEL_POSITION_INVALID; if (MP4ReadSample(priv->mp4.handle, priv->mp4.track, priv->mp4.sample, &aac_data, &aac_data_len, NULL, NULL, NULL, NULL) == 0) return; if (!aac_data) return; NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_len); free(aac_data); /* avoid squash of first frame by starting at 1 */ NeAACDecPostSeekReset(priv->decoder, 1); if (frame_info.error != 0 || frame_info.bytesconsumed <= 0 || frame_info.channels > CHANNELS_MAX) return; for (i = 0; i < frame_info.channels; i++) ip_data->channel_map[i] = channel_position_aac(frame_info.channel_position[i]); } static void mp4_close_handle(MP4FileHandle handle) { #ifdef MP4_CLOSE_DO_NOT_COMPUTE_BITRATE MP4Close(handle, 0); #else MP4Close(handle); #endif } static int mp4_open(struct input_plugin_data *ip_data) { struct mp4_private *priv; NeAACDecConfigurationPtr neaac_cfg; unsigned char *buf; unsigned int buf_size; int rc = -IP_ERROR_FILE_FORMAT; const struct mp4_private priv_init = { .frame_size = 1024, .decoded_samples = 0, .decoder = NULL }; /* http://sourceforge.net/forum/message.php?msg_id=3578887 */ if (ip_data->remote) return -IP_ERROR_FUNCTION_NOT_SUPPORTED; /* kindly ask mp4v2 to not spam stderr */ MP4LogSetLevel(MP4_LOG_NONE); /* init private struct */ priv = xnew(struct mp4_private, 1); *priv = priv_init; ip_data->private = priv; priv->decoder = NeAACDecOpen(); /* set decoder config */ neaac_cfg = NeAACDecGetCurrentConfiguration(priv->decoder); neaac_cfg->outputFormat = FAAD_FMT_16BIT; /* force 16 bit audio */ neaac_cfg->downMatrix = 0; /* NOT 5.1 -> stereo */ NeAACDecSetConfiguration(priv->decoder, neaac_cfg); /* open mpeg-4 file */ #ifdef MP4_DETAILS_ALL priv->mp4.handle = MP4Read(ip_data->filename, 0); #else priv->mp4.handle = MP4Read(ip_data->filename); #endif if (!priv->mp4.handle) { d_print("MP4Read failed\n"); goto out; } /* find aac audio track */ priv->mp4.track = mp4_get_track(priv->mp4.handle); if (priv->mp4.track == MP4_INVALID_TRACK_ID) { d_print("MP4FindTrackId failed\n"); if (MP4GetNumberOfTracks(priv->mp4.handle, MP4_AUDIO_TRACK_TYPE, 0) > 0) rc = -IP_ERROR_UNSUPPORTED_FILE_TYPE; goto out; } priv->mp4.num_samples = MP4GetTrackNumberOfSamples(priv->mp4.handle, priv->mp4.track); priv->mp4.sample = 1; priv->mp4.duration = MP4GetTrackDuration(priv->mp4.handle, priv->mp4.track); priv->mp4.scale = MP4GetTrackTimeScale(priv->mp4.handle, priv->mp4.track); if (priv->mp4.scale == 0) { rc = -IP_ERROR_INTERNAL; goto out; } if (MP4GetTrackNumberOfEdits(priv->mp4.handle, priv->mp4.track) == 1) { priv->encoder_delay = MP4GetTrackEditMediaStart(priv->mp4.handle, priv->mp4.track, 1); // usually 2048 /* The Edit List reliably gives the original duration excluding encoder delay (but * typically only in ms precision). We can convert this to samples and check whether * the sample accurate TrackDuration includes encoder delay. By my understanding it * should not, but in the files with an Edit List I looked at it did. * * Could just skip the rest of this block and still be mostly compatible... */ uint32_t movie_scale = MP4GetTimeScale(priv->mp4.handle); // usually 1000 MP4Duration movie_duration = MP4GetTrackEditDuration(priv->mp4.handle, priv->mp4.track, 1); // usually in ms MP4Duration orig_samples_low_prec = movie_duration * priv->mp4.scale / movie_scale; MP4Duration total_samples_low_prec = priv->encoder_delay + orig_samples_low_prec; if (abs_delta(priv->mp4.duration, orig_samples_low_prec) < 100) priv->mp4.duration += priv->encoder_delay; else if (abs_delta(priv->mp4.duration, total_samples_low_prec) > 100) priv->mp4.duration = total_samples_low_prec; } else if (try_iTunSMPB(priv)) { // nothing } else { priv->encoder_delay = ENCODER_DELAY_DEFAULT; priv->mp4.duration += ENCODER_DELAY_DEFAULT; } priv->drop_samples = priv->encoder_delay; buf = NULL; buf_size = 0; mp4AudioSpecificConfig mp4ASC = {0}; if (!MP4GetTrackESConfiguration(priv->mp4.handle, priv->mp4.track, &buf, &buf_size)) { /* failed to get mpeg-4 audio config... this is ok. * NeAACDecInit2() will simply use default values instead. */ buf = NULL; buf_size = 0; } else if (NeAACDecAudioSpecificConfig(buf, buf_size, &mp4ASC) >= 0) { if (mp4ASC.frameLengthFlag == 1) priv->frame_size = 960; if (mp4ASC.sbr_present_flag == 1 || mp4ASC.forceUpSampling) priv->frame_size *= 2; } /* init decoder according to mpeg-4 audio config */ if (NeAACDecInit2(priv->decoder, buf, buf_size, &priv->sample_rate, &priv->channels) < 0) { free(buf); goto out; } free(buf); d_print("sample rate %luhz, channels %u\n", priv->sample_rate, priv->channels); ip_data->sf = sf_rate(priv->sample_rate) | sf_channels(priv->channels) | sf_bits(16) | sf_signed(1); ip_data->sf |= sf_host_endian(); mp4_get_channel_map(ip_data); return 0; out: if (priv->mp4.handle) mp4_close_handle(priv->mp4.handle); if (priv->decoder) NeAACDecClose(priv->decoder); free(priv); return rc; } static int mp4_close(struct input_plugin_data *ip_data) { struct mp4_private *priv; priv = ip_data->private; if (priv->mp4.handle) mp4_close_handle(priv->mp4.handle); if (priv->decoder) NeAACDecClose(priv->decoder); free(priv); ip_data->private = 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; unsigned char *aac_data = NULL; unsigned int aac_data_len = 0; NeAACDecFrameInfo frame_info; char *sample_buf; int bytes; priv = ip_data->private; 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) { d_print("error reading mp4 sample %d\n", priv->mp4.sample); errno = EINVAL; return -1; } priv->mp4.sample++; if (!aac_data) { d_print("aac_data == NULL\n"); errno = EINVAL; return -1; } sample_buf = NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_len); free(aac_data); if (!sample_buf || frame_info.bytesconsumed <= 0) { d_print("fatal error: %s\n", NeAACDecGetErrorMessage(frame_info.error)); errno = EINVAL; return -1; } if (frame_info.error != 0) { d_print("frame error: %s\n", NeAACDecGetErrorMessage(frame_info.error)); return -2; } priv->current.samples += frame_info.samples; priv->current.bytes += frame_info.bytesconsumed; /* gapless handling */ int frame_samples = frame_info.samples / frame_info.channels; priv->decoded_samples += frame_samples; if (priv->drop_samples) { int skip = min_u(priv->drop_samples, frame_samples); priv->drop_samples -= skip; frame_samples -= skip; frame_info.samples = frame_samples * frame_info.channels; memmove(sample_buf, sample_buf + (skip * frame_info.channels * 2), frame_info.samples * 2); } if (priv->decoded_samples > priv->mp4.duration) { int extra_samples = (priv->decoded_samples - priv->mp4.duration) * frame_info.channels; if (frame_info.samples >= extra_samples) frame_info.samples -= extra_samples; } if (frame_info.samples <= 0) return -2; if (frame_info.channels != priv->channels || frame_info.samplerate != priv->sample_rate) { d_print("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 *priv; int rc; priv = ip_data->private; /* 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 *priv; MP4SampleId sample; priv = ip_data->private; sample = MP4GetSampleIdFromTime(priv->mp4.handle, priv->mp4.track, (MP4Timestamp)(offset * (double)priv->mp4.scale), 1); if (sample == MP4_INVALID_SAMPLE_ID) return -IP_ERROR_INTERNAL; priv->mp4.sample = sample; priv->decoded_samples = (sample - 1) * priv->frame_size; if (priv->decoded_samples < priv->encoder_delay) priv->drop_samples = priv->encoder_delay - priv->decoded_samples; else priv->drop_samples = 0; NeAACDecPostSeekReset(priv->decoder, sample); d_print("seeking to sample %d\n", sample); return 0; } static int mp4_read_comments(struct input_plugin_data *ip_data, struct keyval **comments) { struct mp4_private *priv; #if USE_MPEG4IP uint16_t meta_num, meta_total; uint8_t val; char *str; /*uint8_t *ustr; uint32_t size;*/ #else const MP4Tags *tags; MP4ItmfItemList* itmf_list; #endif GROWING_KEYVALS(c); priv = ip_data->private; #if USE_MPEG4IP /* 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 = xnew(char, 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); } #else /* !USE_MPEG4IP, new interface */ tags = MP4TagsAlloc(); MP4TagsFetch(tags, priv->mp4.handle); if (tags->artist) comments_add_const(&c, "artist", tags->artist); if (tags->albumArtist) comments_add_const(&c, "albumartist", tags->albumArtist); if (tags->sortArtist) comments_add_const(&c, "artistsort", tags->sortArtist); if (tags->sortAlbumArtist) comments_add_const(&c, "albumartistsort", tags->sortAlbumArtist); if (tags->sortAlbum) comments_add_const(&c, "albumsort", tags->sortAlbum); if (tags->album) comments_add_const(&c, "album", tags->album); if (tags->name) comments_add_const(&c, "title", tags->name); if (tags->genre) { comments_add_const(&c, "genre", tags->genre); } else if (tags->genreType) { char const *genre = id3_get_genre(*tags->genreType - 1); if (genre) comments_add_const(&c, "genre", genre); } if (tags->releaseDate && strcmp(tags->releaseDate, "0") != 0) comments_add_const(&c, "date", tags->releaseDate); if (tags->compilation) comments_add_const(&c, "compilation", *tags->compilation ? "yes" : "no"); if (tags->track) { char buf[6]; snprintf(buf, 6, "%u", tags->track->index); comments_add_const(&c, "tracknumber", buf); } if (tags->disk) { char buf[6]; snprintf(buf, 6, "%u", tags->disk->index); comments_add_const(&c, "discnumber", buf); } if (tags->tempo) { char buf[6]; snprintf(buf, 6, "%u", *tags->tempo); comments_add_const(&c, "bpm", buf); } MP4TagsFree(tags); itmf_list = MP4ItmfGetItemsByMeaning(priv->mp4.handle, "com.apple.iTunes", NULL); if (itmf_list) { int i; for (i = 0; i < itmf_list->size; i++) { MP4ItmfItem* item = &itmf_list->elements[i]; if (item->dataList.size < 1) continue; if (item->dataList.size > 1) d_print("ignore multiple values for tag %s\n", item->name); else { MP4ItmfData* data = &item->dataList.elements[0]; char *val = xstrndup(data->value, data->valueSize); comments_add(&c, item->name, val); } } MP4ItmfItemListFree(itmf_list); } #endif keyvals_terminate(&c); *comments = c.keyvals; return 0; } static int mp4_duration(struct input_plugin_data *ip_data) { struct mp4_private *priv; priv = ip_data->private; return priv->mp4.duration / priv->mp4.scale; } static long mp4_bitrate(struct input_plugin_data *ip_data) { struct mp4_private *priv = ip_data->private; long bitrate = MP4GetTrackBitRate(priv->mp4.handle, priv->mp4.track); return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED; } static long mp4_current_bitrate(struct input_plugin_data *ip_data) { struct mp4_private *priv = ip_data->private; long bitrate = -1; if (priv->current.samples > 0) { priv->current.samples /= priv->channels; bitrate = (8 * priv->current.bytes * priv->sample_rate) / priv->current.samples; priv->current.samples = 0; priv->current.bytes = 0; } return bitrate; } static char *mp4_codec(struct input_plugin_data *ip_data) { return xstrdup("aac"); } static const char *object_type_to_str(uint8_t obj_type) { switch (obj_type) { case MP4_MPEG4_AAC_MAIN_AUDIO_TYPE: return "Main"; case MP4_MPEG4_AAC_LC_AUDIO_TYPE: return "LC"; case MP4_MPEG4_AAC_SSR_AUDIO_TYPE: return "SSR"; case MP4_MPEG4_AAC_LTP_AUDIO_TYPE: return "LTP"; #ifdef MP4_MPEG4_AAC_HE_AUDIO_TYPE case MP4_MPEG4_AAC_HE_AUDIO_TYPE: return "HE"; #endif case MP4_MPEG4_AAC_SCALABLE_AUDIO_TYPE: return "Scalable"; } return NULL; } static char *mp4_codec_profile(struct input_plugin_data *ip_data) { struct mp4_private *priv = ip_data->private; const char *profile; uint8_t obj_type; obj_type = MP4GetTrackEsdsObjectTypeId(priv->mp4.handle, priv->mp4.track); if (obj_type == MP4_MPEG4_AUDIO_TYPE) obj_type = MP4GetTrackAudioMpeg4Type(priv->mp4.handle, priv->mp4.track); profile = object_type_to_str(obj_type); return profile ? xstrdup(profile) : NULL; } 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, .bitrate = mp4_bitrate, .bitrate_current = mp4_current_bitrate, .codec = mp4_codec, .codec_profile = mp4_codec_profile }; const int ip_priority = 50; const char * const ip_extensions[] = { "mp4", "m4a", "m4b", NULL }; const char * const ip_mime_types[] = { /*"audio/mp4", "audio/mp4a-latm",*/ NULL }; const struct input_plugin_opt ip_options[] = { { NULL } }; const unsigned ip_abi_version = IP_ABI_VERSION;