diff --git a/Changelog b/Changelog index 6a32f3f522..cc7420cb45 100644 --- a/Changelog +++ b/Changelog @@ -5,6 +5,7 @@ version : - XWD encoder and decoder - Support for fragmentation in the mov/mp4 muxer +- ISMV (Smooth Streaming) muxer version 0.8: diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 481d2c1e31..66a8cfec48 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -111,6 +111,7 @@ void av_register_all(void) REGISTER_DEMUXER (INGENIENT, ingenient); REGISTER_DEMUXER (IPMOVIE, ipmovie); REGISTER_MUXER (IPOD, ipod); + REGISTER_MUXER (ISMV, ismv); REGISTER_DEMUXER (ISS, iss); REGISTER_DEMUXER (IV8, iv8); REGISTER_MUXDEMUX (IVF, ivf); diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 29ba129372..da110ddda0 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -55,6 +55,7 @@ static const AVOption options[] = { { "iods_video_profile", "iods video profile atom.", offsetof(MOVMuxContext, iods_video_profile), AV_OPT_TYPE_INT, {.dbl = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM}, { "frag_duration", "Maximum fragment duration", offsetof(MOVMuxContext, max_fragment_duration), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, { "frag_size", "Maximum fragment size", offsetof(MOVMuxContext, max_fragment_size), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, + { "ism_lookahead", "Number of lookahead entries for ISM files", offsetof(MOVMuxContext, ism_lookahead), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM}, { NULL }, }; @@ -761,7 +762,7 @@ static int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track) { int tag = track->enc->codec_tag; - if (track->mode == MODE_MP4 || track->mode == MODE_PSP) + if (track->mode == MODE_MP4 || track->mode == MODE_PSP || track->mode == MODE_ISM) tag = mp4_get_codec_tag(s, track); else if (track->mode == MODE_IPOD) tag = ipod_get_codec_tag(s, track); @@ -1940,6 +1941,11 @@ static int mov_write_tfhd_tag(AVIOContext *pb, MOVTrack *track, flags |= 0x20; /* default-sample-flags-present */ } + /* Don't set a default sample size when creating data for silverlight, + * the player refuses to play files with that set. */ + if (track->mode == MODE_ISM) + flags &= ~0x10; + avio_wb32(pb, 0); /* size placeholder */ ffio_wfourcc(pb, "tfhd"); avio_w8(pb, 0); /* version */ @@ -2023,7 +2029,78 @@ static int mov_write_trun_tag(AVIOContext *pb, MOVTrack *track) return updateSize(pb, pos); } -static int mov_write_traf_tag(AVIOContext *pb, MOVTrack *track, int64_t moof_offset) +static int mov_write_tfxd_tag(AVIOContext *pb, MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + const uint8_t uuid[] = { + 0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6, + 0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2 + }; + + avio_wb32(pb, 0); /* size placeholder */ + ffio_wfourcc(pb, "uuid"); + avio_write(pb, uuid, sizeof(uuid)); + avio_w8(pb, 1); + avio_wb24(pb, 0); + avio_wb64(pb, track->frag_start); + avio_wb64(pb, track->start_dts + track->trackDuration - + track->cluster[0].dts); + + return updateSize(pb, pos); +} + +static int mov_write_tfrf_tag(AVIOContext *pb, MOVMuxContext *mov, + MOVTrack *track, int entry) +{ + int n = track->nb_frag_info - 1 - entry, i; + int size = 8 + 16 + 4 + 1 + 16*n; + const uint8_t uuid[] = { + 0xd4, 0x80, 0x7e, 0xf2, 0xca, 0x39, 0x46, 0x95, + 0x8e, 0x54, 0x26, 0xcb, 0x9e, 0x46, 0xa7, 0x9f + }; + + if (entry < 0) + return 0; + + avio_seek(pb, track->frag_info[entry].tfrf_offset, SEEK_SET); + avio_wb32(pb, size); + ffio_wfourcc(pb, "uuid"); + avio_write(pb, uuid, sizeof(uuid)); + avio_w8(pb, 1); + avio_wb24(pb, 0); + avio_w8(pb, n); + for (i = 0; i < n; i++) { + int index = entry + 1 + i; + avio_wb64(pb, track->frag_info[index].time); + avio_wb64(pb, track->frag_info[index].duration); + } + if (n < mov->ism_lookahead) { + int free_size = 16*(mov->ism_lookahead - n); + avio_wb32(pb, free_size); + ffio_wfourcc(pb, "free"); + for (i = 0; i < free_size - 8; i++) + avio_w8(pb, 0); + } + + return 0; +} + +static int mov_write_tfrf_tags(AVIOContext *pb, MOVMuxContext *mov, + MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + int i; + for (i = 0; i < mov->ism_lookahead; i++) { + /* Update the tfrf tag for the last ism_lookahead fragments, + * nb_frag_info - 1 is the next fragment to be written. */ + mov_write_tfrf_tag(pb, mov, track, track->nb_frag_info - 2 - i); + } + avio_seek(pb, pos, SEEK_SET); + return 0; +} + +static int mov_write_traf_tag(AVIOContext *pb, MOVMuxContext *mov, + MOVTrack *track, int64_t moof_offset) { int64_t pos = avio_tell(pb); avio_wb32(pb, 0); /* size placeholder */ @@ -2031,6 +2108,19 @@ static int mov_write_traf_tag(AVIOContext *pb, MOVTrack *track, int64_t moof_off mov_write_tfhd_tag(pb, track, moof_offset); mov_write_trun_tag(pb, track); + if (mov->mode == MODE_ISM) { + mov_write_tfxd_tag(pb, track); + + if (mov->ism_lookahead) { + int i, size = 16 + 4 + 1 + 16*mov->ism_lookahead; + + track->tfrf_offset = avio_tell(pb); + avio_wb32(pb, 8 + size); + ffio_wfourcc(pb, "free"); + for (i = 0; i < size; i++) + avio_w8(pb, 0); + } + } return updateSize(pb, pos); } @@ -2050,7 +2140,7 @@ static int mov_write_moof_tag(AVIOContext *pb, MOVMuxContext *mov, int tracks) continue; if (!track->entry) continue; - mov_write_traf_tag(pb, track, pos); + mov_write_traf_tag(pb, mov, track, pos); } end = avio_tell(pb); @@ -2158,6 +2248,8 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) ffio_wfourcc(pb, "isom"); else if (mov->mode == MODE_IPOD) ffio_wfourcc(pb, has_video ? "M4V ":"M4A "); + else if (mov->mode == MODE_ISM) + ffio_wfourcc(pb, "isml"); else ffio_wfourcc(pb, "qt "); @@ -2165,7 +2257,10 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) if(mov->mode == MODE_MOV) ffio_wfourcc(pb, "qt "); - else{ + else if (mov->mode == MODE_ISM) { + ffio_wfourcc(pb, "piff"); + ffio_wfourcc(pb, "iso2"); + } else { ffio_wfourcc(pb, "isom"); ffio_wfourcc(pb, "iso2"); if(has_h264) @@ -2342,8 +2437,11 @@ static int mov_flush_fragment(AVFormatContext *s) info = &track->frag_info[track->nb_frag_info - 1]; info->offset = avio_tell(s->pb); info->time = mov->tracks[i].frag_start; + info->duration = duration; + mov_write_tfrf_tags(s->pb, mov, track); mov_write_moof_tag(s->pb, mov, moof_tracks); + info->tfrf_offset = track->tfrf_offset; mov->fragments++; avio_wb32(s->pb, mdat_size + 8); @@ -2571,6 +2669,7 @@ static int mov_write_header(AVFormatContext *s) else if (!strcmp("mov", s->oformat->name)) mov->mode = MODE_MOV; else if (!strcmp("psp", s->oformat->name)) mov->mode = MODE_PSP; else if (!strcmp("ipod",s->oformat->name)) mov->mode = MODE_IPOD; + else if (!strcmp("ismv",s->oformat->name)) mov->mode = MODE_ISM; mov_write_ftyp_tag(pb,s); if (mov->mode == MODE_PSP) { @@ -2681,6 +2780,10 @@ static int mov_write_header(AVFormatContext *s) } if (!track->height) track->height = st->codec->height; + /* The ism specific timescale isn't mandatory, but is assumed by + * some tools, such as mp4split. */ + if (mov->mode == MODE_ISM) + track->timescale = 10000000; avpriv_set_pts_info(st, 64, 1, track->timescale); @@ -2692,6 +2795,15 @@ static int mov_write_header(AVFormatContext *s) } } + if (mov->mode == MODE_ISM) { + /* If no fragmentation options have been set, set a default. */ + if (!(mov->flags & (FF_MOV_FLAG_FRAG_KEYFRAME | + FF_MOV_FLAG_FRAG_CUSTOM)) && + !mov->max_fragment_duration && !mov->max_fragment_size) + mov->max_fragment_duration = 5000000; + mov->flags |= FF_MOV_FLAG_EMPTY_MOOV | FF_MOV_FLAG_SEPARATE_MOOF; + } + /* Set the FRAGMENT flag if any of the fragmentation methods are * enabled. */ if (mov->max_fragment_duration || mov->max_fragment_size || @@ -2906,3 +3018,21 @@ AVOutputFormat ff_ipod_muxer = { .priv_class = &ipod_muxer_class, }; #endif +#if CONFIG_ISMV_MUXER +MOV_CLASS(ismv) +AVOutputFormat ff_ismv_muxer = { + .name = "ismv", + .long_name = NULL_IF_CONFIG_SMALL("ISMV/ISMA (Smooth Streaming) format"), + .mime_type = "application/mp4", + .extensions = "ismv,isma", + .priv_data_size = sizeof(MOVMuxContext), + .audio_codec = CODEC_ID_AAC, + .video_codec = CODEC_ID_H264, + .write_header = mov_write_header, + .write_packet = ff_mov_write_packet, + .write_trailer = mov_write_trailer, + .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, + .codec_tag = (const AVCodecTag* const []){ff_mp4_obj_type, 0}, + .priv_class = &ismv_muxer_class, +}; +#endif diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 892324948e..a1e504dca5 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -38,6 +38,7 @@ // avconv -i testinput.avi -f psp -r 14.985 -s 320x240 -b 768 -ar 24000 -ab 32 M4V00001.MP4 #define MODE_3G2 0x10 #define MODE_IPOD 0x20 +#define MODE_ISM 0x40 typedef struct MOVIentry { uint64_t pos; @@ -68,6 +69,8 @@ typedef struct { typedef struct { int64_t offset; int64_t time; + int64_t duration; + int64_t tfrf_offset; } MOVFragmentInfo; typedef struct MOVIndex { @@ -113,6 +116,7 @@ typedef struct MOVIndex { int64_t moof_size_offset; int64_t data_offset; int64_t frag_start; + int64_t tfrf_offset; int nb_frag_info; MOVFragmentInfo *frag_info; @@ -137,6 +141,7 @@ typedef struct MOVMuxContext { int fragments; int max_fragment_duration; int max_fragment_size; + int ism_lookahead; } MOVMuxContext; #define FF_MOV_FLAG_RTP_HINT 1 diff --git a/libavformat/version.h b/libavformat/version.h index 5cdf1c881a..cdea318eba 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -30,7 +30,7 @@ #include "libavutil/avutil.h" #define LIBAVFORMAT_VERSION_MAJOR 53 -#define LIBAVFORMAT_VERSION_MINOR 22 +#define LIBAVFORMAT_VERSION_MINOR 23 #define LIBAVFORMAT_VERSION_MICRO 0 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \