X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fmovenc.c;h=8196b73077f5f56b35c94001be5e35f14ae8fde7;hb=ec7ecb88117fd2d086f0be45ded9743c94100ef4;hp=7f970be35f054c869db8479c052342f300900ed6;hpb=15c6be8c7da7b94b5e131396e9a82ab6fe4a6b64;p=ffmpeg diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 7f970be35f0..8196b73077f 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -52,11 +52,12 @@ static const AVOption options[] = { { "separate_moof", "Write separate moof/mdat atoms for each track", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_SEPARATE_MOOF}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "frag_custom", "Flush fragments on caller requests", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_FRAG_CUSTOM}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "isml", "Create a live smooth streaming feed (for pushing to a publishing point)", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_ISML}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, - FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags) + FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags), { "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.dbl = 1}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, { "iods_audio_profile", "iods audio profile atom.", offsetof(MOVMuxContext, iods_audio_profile), AV_OPT_TYPE_INT, {.dbl = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM}, { "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}, + { "min_frag_duration", "Minimum fragment duration", offsetof(MOVMuxContext, min_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 }, @@ -421,7 +422,6 @@ static int mov_write_wave_tag(AVIOContext *pb, MOVTrack *track) } else if (track->enc->codec_id == CODEC_ID_AMR_NB) { mov_write_amr_tag(pb, track); } else if (track->enc->codec_id == CODEC_ID_AC3) { - mov_write_chan_tag(pb, track); mov_write_ac3_tag(pb, track); } else if (track->enc->codec_id == CODEC_ID_ALAC) { mov_write_extradata_tag(pb, track); @@ -660,7 +660,8 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVTrack *track) } avio_wb16(pb, 0); /* packet size (= 0) */ - avio_wb16(pb, track->enc->sample_rate); + avio_wb16(pb, track->enc->sample_rate <= UINT16_MAX ? + track->enc->sample_rate : 0); avio_wb16(pb, 0); /* Reserved */ } @@ -678,7 +679,7 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVTrack *track) track->enc->codec_id == CODEC_ID_ALAC || track->enc->codec_id == CODEC_ID_ADPCM_MS || track->enc->codec_id == CODEC_ID_ADPCM_IMA_WAV || - mov_pcm_le_gt16(track->enc->codec_id))) + (mov_pcm_le_gt16(track->enc->codec_id) && version==1))) mov_write_wave_tag(pb, track); else if(track->tag == MKTAG('m','p','4','a')) mov_write_esds_tag(pb, track); @@ -693,6 +694,9 @@ static int mov_write_audio_tag(AVIOContext *pb, MOVTrack *track) else if (track->vos_len > 0) mov_write_glbl_tag(pb, track); + if (track->mode == MODE_MOV && track->enc->codec_type == AVMEDIA_TYPE_AUDIO) + mov_write_chan_tag(pb, track); + return update_size(pb, pos); } @@ -855,6 +859,7 @@ static const struct { uint32_t tag; unsigned bps; } mov_pix_fmt_tags[] = { + { PIX_FMT_YUYV422, MKTAG('y','u','v','2'), 0 }, { PIX_FMT_YUYV422, MKTAG('y','u','v','s'), 0 }, { PIX_FMT_UYVY422, MKTAG('2','v','u','y'), 0 }, { PIX_FMT_RGB555BE,MKTAG('r','a','w',' '), 16 }, @@ -877,7 +882,7 @@ static int mov_get_rawvideo_codec_tag(AVFormatContext *s, MOVTrack *track) int i; for (i = 0; i < FF_ARRAY_ELEMS(mov_pix_fmt_tags); i++) { - if (track->enc->pix_fmt == mov_pix_fmt_tags[i].pix_fmt) { + if (track->enc->codec_tag == mov_pix_fmt_tags[i].tag && track->enc->pix_fmt == mov_pix_fmt_tags[i].pix_fmt) { tag = mov_pix_fmt_tags[i].tag; track->enc->bits_per_coded_sample = mov_pix_fmt_tags[i].bps; break; @@ -938,7 +943,7 @@ static const AVCodecTag codec_3gp_tags[] = { static int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track) { - int tag = track->enc->codec_tag; + int tag; if (track->mode == MODE_MP4 || track->mode == MODE_PSP) tag = mp4_get_codec_tag(s, track); @@ -1020,7 +1025,7 @@ static int mov_write_pasp_tag(AVIOContext *pb, MOVTrack *track) static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track) { int64_t pos = avio_tell(pb); - char compressor_name[32]; + char compressor_name[32] = { 0 }; avio_wb32(pb, 0); /* size */ avio_wl32(pb, track->tag); // store it byteswapped @@ -1051,7 +1056,6 @@ static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track) avio_wb32(pb, 0); /* Data size (= 0) */ avio_wb16(pb, 1); /* Frame count (= 1) */ - memset(compressor_name,0,32); /* FIXME not sure, ISO 14496-1 draft where it shall be set to 0 */ if (track->mode == MODE_MOV && track->enc->codec && track->enc->codec->name) av_strlcpy(compressor_name,track->enc->codec->name,32); @@ -1069,7 +1073,10 @@ static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track) mov_write_d263_tag(pb); else if(track->enc->codec_id == CODEC_ID_SVQ3) mov_write_svq3_tag(pb); - else if(track->enc->codec_id == CODEC_ID_DNXHD) + else if(track->enc->codec_id == CODEC_ID_AVUI) { + mov_write_extradata_tag(pb, track); + avio_wb32(pb, 0); + } else if(track->enc->codec_id == CODEC_ID_DNXHD) mov_write_avid_tag(pb, track); else if(track->enc->codec_id == CODEC_ID_H264) { mov_write_avcc_tag(pb, track); @@ -1090,6 +1097,26 @@ static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } +static int mov_write_tmcd_tag(AVIOContext *pb, MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + int frame_duration = track->enc->time_base.num; + int nb_frames = (track->timescale + frame_duration/2) / frame_duration; + + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "tmcd"); /* Data format */ + avio_wb32(pb, 0); /* Reserved */ + avio_wb32(pb, 1); /* Data reference index */ + avio_wb32(pb, 0); /* Flags */ + avio_wb32(pb, track->timecode_flags); /* Flags (timecode) */ + avio_wb32(pb, track->timescale); /* Timescale */ + avio_wb32(pb, frame_duration); /* Frame duration */ + avio_w8(pb, nb_frames); /* Number of frames */ + avio_wb24(pb, 0); /* Reserved */ + /* TODO: source reference string */ + return update_size(pb, pos); +} + static int mov_write_rtp_tag(AVIOContext *pb, MOVTrack *track) { int64_t pos = avio_tell(pb); @@ -1125,6 +1152,8 @@ static int mov_write_stsd_tag(AVIOContext *pb, MOVTrack *track) mov_write_subtitle_tag(pb, track); else if (track->enc->codec_tag == MKTAG('r','t','p',' ')) mov_write_rtp_tag(pb, track); + else if (track->enc->codec_tag == MKTAG('t','m','c','d')) + mov_write_tmcd_tag(pb, track); return update_size(pb, pos); } @@ -1257,9 +1286,32 @@ static int mov_write_nmhd_tag(AVIOContext *pb) return 12; } -static int mov_write_gmhd_tag(AVIOContext *pb) +static int mov_write_tcmi_tag(AVIOContext *pb, MOVTrack *track) { - avio_wb32(pb, 0x20); /* size */ + int64_t pos = avio_tell(pb); + const char *font = "Lucida Grande"; + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "tcmi"); /* timecode media information atom */ + avio_wb32(pb, 0); /* version & flags */ + avio_wb16(pb, 0); /* text font */ + avio_wb16(pb, 0); /* text face */ + avio_wb16(pb, 12); /* text size */ + avio_wb16(pb, 0); /* (unknown, not in the QT specs...) */ + avio_wb16(pb, 0x0000); /* text color (red) */ + avio_wb16(pb, 0x0000); /* text color (green) */ + avio_wb16(pb, 0x0000); /* text color (blue) */ + avio_wb16(pb, 0xffff); /* background color (red) */ + avio_wb16(pb, 0xffff); /* background color (green) */ + avio_wb16(pb, 0xffff); /* background color (blue) */ + avio_w8(pb, strlen(font)); /* font len (part of the pascal string) */ + avio_write(pb, font, strlen(font)); /* font name */ + return update_size(pb, pos); +} + +static int mov_write_gmhd_tag(AVIOContext *pb, MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "gmhd"); avio_wb32(pb, 0x18); /* gmin size */ ffio_wfourcc(pb, "gmin");/* generic media info */ @@ -1270,7 +1322,34 @@ static int mov_write_gmhd_tag(AVIOContext *pb) avio_wb16(pb, 0x8000); /* opColor (b?) */ avio_wb16(pb, 0); /* balance */ avio_wb16(pb, 0); /* reserved */ - return 0x20; + + /* + * This special text atom is required for + * Apple Quicktime chapters. The contents + * don't appear to be documented, so the + * bytes are copied verbatim. + */ + avio_wb32(pb, 0x2C); /* size */ + ffio_wfourcc(pb, "text"); + avio_wb16(pb, 0x01); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x01); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00); + avio_wb32(pb, 0x00004000); + avio_wb16(pb, 0x0000); + + if (track->enc->codec_tag == MKTAG('t','m','c','d')) { + int64_t tmcd_pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "tmcd"); + mov_write_tcmi_tag(pb, track); + update_size(pb, tmcd_pos); + } + return update_size(pb, pos); } static int mov_write_smhd_tag(AVIOContext *pb) @@ -1313,9 +1392,16 @@ static int mov_write_hdlr_tag(AVIOContext *pb, MOVTrack *track) if (track->tag == MKTAG('t','x','3','g')) hdlr_type = "sbtl"; else hdlr_type = "text"; descr = "SubtitleHandler"; + } else if (track->enc->codec_tag == MKTAG('t','m','c','d')) { + hdlr_type = "tmcd"; + descr = "TimeCodeHandler"; } else if (track->enc->codec_tag == MKTAG('r','t','p',' ')) { hdlr_type = "hint"; descr = "HintHandler"; + } else { + hdlr = "dhlr"; + hdlr_type = "url "; + descr = "DataHandler"; } } @@ -1360,8 +1446,10 @@ static int mov_write_minf_tag(AVIOContext *pb, MOVTrack *track) else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO) mov_write_smhd_tag(pb); else if (track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE) { - if (track->tag == MKTAG('t','e','x','t')) mov_write_gmhd_tag(pb); + if (track->tag == MKTAG('t','e','x','t')) mov_write_gmhd_tag(pb, track); else mov_write_nmhd_tag(pb); + } else if (track->tag == MKTAG('t','m','c','d')) { + mov_write_gmhd_tag(pb, track); } else if (track->tag == MKTAG('r','t','p',' ')) { mov_write_hmhd_tag(pb); } @@ -1419,11 +1507,30 @@ static int mov_write_mdia_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } +/* transformation matrix + |a b u| + |c d v| + |tx ty w| */ +static void write_matrix(AVIOContext *pb, int16_t a, int16_t b, int16_t c, + int16_t d, int16_t tx, int16_t ty) +{ + avio_wb32(pb, a << 16); /* 16.16 format */ + avio_wb32(pb, b << 16); /* 16.16 format */ + avio_wb32(pb, 0); /* u in 2.30 format */ + avio_wb32(pb, c << 16); /* 16.16 format */ + avio_wb32(pb, d << 16); /* 16.16 format */ + avio_wb32(pb, 0); /* v in 2.30 format */ + avio_wb32(pb, tx << 16); /* 16.16 format */ + avio_wb32(pb, ty << 16); /* 16.16 format */ + avio_wb32(pb, 1 << 30); /* w in 2.30 format */ +} + static int mov_write_tkhd_tag(AVIOContext *pb, MOVTrack *track, AVStream *st) { int64_t duration = av_rescale_rnd(track->track_duration, MOV_TIMESCALE, track->timescale, AV_ROUND_UP); int version = duration < INT32_MAX ? 0 : 1; + int rotation = 0; if (track->mode == MODE_ISM) version = 1; @@ -1458,16 +1565,19 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVTrack *track, AVStream *st) avio_wb16(pb, 0); /* reserved */ /* Matrix structure */ - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x40000000); /* reserved */ - + if (st && st->metadata) { + AVDictionaryEntry *rot = av_dict_get(st->metadata, "rotate", NULL, 0); + rotation = (rot && rot->value) ? atoi(rot->value) : 0; + } + if (rotation == 90) { + write_matrix(pb, 0, 1, -1, 0, track->enc->height, 0); + } else if (rotation == 180) { + write_matrix(pb, -1, 0, 0, -1, track->enc->width, track->enc->height); + } else if (rotation == 270) { + write_matrix(pb, 0, -1, 1, 0, 0, track->enc->width); + } else { + write_matrix(pb, 1, 0, 0, 1, 0, 0); + } /* Track width and height, for visual only */ if(st && (track->enc->codec_type == AVMEDIA_TYPE_VIDEO || track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE)) { @@ -1728,15 +1838,7 @@ static int mov_write_mvhd_tag(AVIOContext *pb, MOVMuxContext *mov) avio_wb32(pb, 0); /* reserved */ /* Matrix structure */ - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x00010000); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x0); /* reserved */ - avio_wb32(pb, 0x40000000); /* reserved */ + write_matrix(pb, 1, 0, 0, 1, 0, 0); avio_wb32(pb, 0); /* reserved (preview time) */ avio_wb32(pb, 0); /* reserved (preview duration) */ @@ -1992,7 +2094,10 @@ static int mov_write_udta_tag(AVIOContext *pb, MOVMuxContext *mov, mov_write_string_metadata(s, pb_buf, "\251alb", "album" , 0); mov_write_string_metadata(s, pb_buf, "\251day", "date" , 0); mov_write_string_metadata(s, pb_buf, "\251swr", "encoder" , 0); + // currently ignored by mov.c mov_write_string_metadata(s, pb_buf, "\251des", "comment" , 0); + // add support for libquicktime, this atom is also actually read by mov.c + mov_write_string_metadata(s, pb_buf, "\251cmt", "comment" , 0); mov_write_string_metadata(s, pb_buf, "\251gen", "genre" , 0); mov_write_string_metadata(s, pb_buf, "\251cpy", "copyright" , 0); } else { @@ -2118,6 +2223,14 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, mov->tracks[mov->tracks[i].src_track].track_id; } } + for (i = 0; i < mov->nb_streams; i++) { + if (mov->tracks[i].tag == MKTAG('t','m','c','d')) { + int src_trk = mov->tracks[i].src_track; + mov->tracks[src_trk].tref_tag = mov->tracks[i].tag; + mov->tracks[src_trk].tref_id = mov->tracks[i].track_id; + mov->tracks[i].track_duration = mov->tracks[src_trk].track_duration; + } + } mov_write_mvhd_tag(pb, mov); if (mov->mode != MODE_MOV && !mov->iods_skip) @@ -2157,26 +2270,6 @@ static void param_write_hex(AVIOContext *pb, const char *name, const uint8_t *va avio_printf(pb, "\n", name, buf); } -static void write_h264_extradata(AVIOContext *pb, AVCodecContext *enc) -{ - uint16_t sps_size, pps_size, len; - char buf[150]; - sps_size = AV_RB16(&enc->extradata[6]); - if (11 + sps_size > enc->extradata_size) - return; - pps_size = AV_RB16(&enc->extradata[9 + sps_size]); - if (11 + sps_size + pps_size > enc->extradata_size) - return; - len = FFMIN(sizeof(buf)/2 - 1, sps_size); - ff_data_to_hex(buf, &enc->extradata[8], len, 0); - buf[2*len] = '\0'; - avio_printf(pb, "extradata[11 + sps_size], len, 0); - buf[2*len] = '\0'; - avio_printf(pb, "00000001%s\" valuetype=\"data\"/>\n", buf); -} - static int mov_write_isml_manifest(AVIOContext *pb, MOVMuxContext *mov) { int64_t pos = avio_tell(pb); @@ -2218,18 +2311,21 @@ static int mov_write_isml_manifest(AVIOContext *pb, MOVMuxContext *mov) param_write_int(pb, "systemBitrate", track->enc->bit_rate); param_write_int(pb, "trackID", track_id); if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) { - if (track->enc->codec_id == CODEC_ID_H264 && - track->enc->extradata_size >= 11 && - track->enc->extradata[0] == 1) { - write_h264_extradata(pb, track->enc); - } else { - param_write_hex(pb, "CodecPrivateData", track->enc->extradata, - track->enc->extradata_size); - } if (track->enc->codec_id == CODEC_ID_H264) { + uint8_t *ptr; + int size = track->enc->extradata_size; + if (!ff_avc_write_annexb_extradata(track->enc->extradata, &ptr, + &size)) { + param_write_hex(pb, "CodecPrivateData", + ptr ? ptr : track->enc->extradata, + size); + av_free(ptr); + } param_write_string(pb, "FourCC", "H264"); } else if (track->enc->codec_id == CODEC_ID_VC1) { param_write_string(pb, "FourCC", "WVC1"); + param_write_hex(pb, "CodecPrivateData", track->enc->extradata, + track->enc->extradata_size); } param_write_int(pb, "MaxWidth", track->enc->width); param_write_int(pb, "MaxHeight", track->enc->height); @@ -2884,7 +2980,7 @@ static int mov_flush_fragment(AVFormatContext *s) return 0; } -static int mov_write_packet_internal(AVFormatContext *s, AVPacket *pkt) +int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) { MOVMuxContext *mov = s->priv_data; AVIOContext *pb = s->pb; @@ -2894,22 +2990,6 @@ static int mov_write_packet_internal(AVFormatContext *s, AVPacket *pkt) int size= pkt->size; uint8_t *reformatted_data = NULL; - if (!s->pb->seekable && !(mov->flags & FF_MOV_FLAG_EMPTY_MOOV)) - return 0; /* Can't handle that */ - - if (!size) return 0; /* Discard 0 sized packets */ - - if ((mov->max_fragment_duration && trk->entry && - av_rescale_q(pkt->dts - trk->cluster[0].dts, - s->streams[pkt->stream_index]->time_base, - AV_TIME_BASE_Q) >= mov->max_fragment_duration) || - (mov->max_fragment_size && mov->mdat_size + size >= mov->max_fragment_size) || - (mov->flags & FF_MOV_FLAG_FRAG_KEYFRAME && - enc->codec_type == AVMEDIA_TYPE_VIDEO && - trk->entry && pkt->flags & AV_PKT_FLAG_KEY)) { - mov_flush_fragment(s); - } - if (mov->flags & FF_MOV_FLAG_FRAGMENT) { int ret; if (mov->fragments > 0) { @@ -3042,13 +3122,35 @@ static int mov_write_packet_internal(AVFormatContext *s, AVPacket *pkt) return 0; } -int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) +static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) { if (!pkt) { mov_flush_fragment(s); return 1; } else { - return mov_write_packet_internal(s, pkt); + MOVMuxContext *mov = s->priv_data; + MOVTrack *trk = &mov->tracks[pkt->stream_index]; + AVCodecContext *enc = trk->enc; + int64_t frag_duration = 0; + int size = pkt->size; + + if (!pkt->size) return 0; /* Discard 0 sized packets */ + + if (trk->entry && pkt->stream_index < s->nb_streams) + frag_duration = av_rescale_q(pkt->dts - trk->cluster[0].dts, + s->streams[pkt->stream_index]->time_base, + AV_TIME_BASE_Q); + if ((mov->max_fragment_duration && + frag_duration >= mov->max_fragment_duration) || + (mov->max_fragment_size && mov->mdat_size + size >= mov->max_fragment_size) || + (mov->flags & FF_MOV_FLAG_FRAG_KEYFRAME && + enc->codec_type == AVMEDIA_TYPE_VIDEO && + trk->entry && pkt->flags & AV_PKT_FLAG_KEY)) { + if (frag_duration >= mov->min_fragment_duration) + mov_flush_fragment(s); + } + + return ff_mov_write_packet(s, pkt); } } @@ -3056,6 +3158,8 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) // as samples, and a tref pointing from the other tracks to the chapter one. static void mov_create_chapter_track(AVFormatContext *s, int tracknum) { + AVIOContext *pb; + MOVMuxContext *mov = s->priv_data; MOVTrack *track = &mov->tracks[tracknum]; AVPacket pkt = { .stream_index = tracknum, .flags = AV_PKT_FLAG_KEY }; @@ -3067,6 +3171,50 @@ static void mov_create_chapter_track(AVFormatContext *s, int tracknum) track->enc = avcodec_alloc_context3(NULL); track->enc->codec_type = AVMEDIA_TYPE_SUBTITLE; + if (avio_open_dyn_buf(&pb) >= 0) { + int size; + uint8_t *buf; + + /* Stub header (usually for Quicktime chapter track) */ + // TextSampleEntry + avio_wb32(pb, 0x01); // displayFlags + avio_w8(pb, 0x00); // horizontal justification + avio_w8(pb, 0x00); // vertical justification + avio_w8(pb, 0x00); // bgColourRed + avio_w8(pb, 0x00); // bgColourGreen + avio_w8(pb, 0x00); // bgColourBlue + avio_w8(pb, 0x00); // bgColourAlpha + // BoxRecord + avio_wb16(pb, 0x00); // defTextBoxTop + avio_wb16(pb, 0x00); // defTextBoxLeft + avio_wb16(pb, 0x00); // defTextBoxBottom + avio_wb16(pb, 0x00); // defTextBoxRight + // StyleRecord + avio_wb16(pb, 0x00); // startChar + avio_wb16(pb, 0x00); // endChar + avio_wb16(pb, 0x01); // fontID + avio_w8(pb, 0x00); // fontStyleFlags + avio_w8(pb, 0x00); // fontSize + avio_w8(pb, 0x00); // fgColourRed + avio_w8(pb, 0x00); // fgColourGreen + avio_w8(pb, 0x00); // fgColourBlue + avio_w8(pb, 0x00); // fgColourAlpha + // FontTableBox + avio_wb32(pb, 0x0D); // box size + ffio_wfourcc(pb, "ftab"); // box atom name + avio_wb16(pb, 0x01); // entry count + // FontRecord + avio_wb16(pb, 0x01); // font ID + avio_w8(pb, 0x00); // font name length + + if ((size = avio_close_dyn_buf(pb, &buf)) > 0) { + track->enc->extradata = buf; + track->enc->extradata_size = size; + } else { + av_free(&buf); + } + } + for (i = 0; i < s->nb_chapters; i++) { AVChapter *c = s->chapters[i]; AVDictionaryEntry *t; @@ -3087,12 +3235,48 @@ static void mov_create_chapter_track(AVFormatContext *s, int tracknum) } } +static int mov_create_timecode_track(AVFormatContext *s, int index, int src_index, const char *tcstr) +{ + MOVMuxContext *mov = s->priv_data; + MOVTrack *track = &mov->tracks[index]; + AVStream *src_st = s->streams[src_index]; + AVTimecode tc; + AVPacket pkt = {.stream_index = index, .flags = AV_PKT_FLAG_KEY, .size = 4}; + AVRational rate = {src_st->codec->time_base.den, src_st->codec->time_base.num}; + + /* compute the frame number */ + int ret = av_timecode_init_from_string(&tc, rate, tcstr, s); + if (ret < 0) + return ret; + + /* tmcd track based on video stream */ + track->mode = mov->mode; + track->tag = MKTAG('t','m','c','d'); + track->src_track = src_index; + track->timescale = src_st->codec->time_base.den; + if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME) + track->timecode_flags |= MOV_TIMECODE_FLAG_DROPFRAME; + + /* encode context: tmcd data stream */ + track->enc = avcodec_alloc_context3(NULL); + track->enc->codec_type = AVMEDIA_TYPE_DATA; + track->enc->codec_tag = track->tag; + track->enc->time_base = src_st->codec->time_base; + + /* the tmcd track just contains one packet with the frame number */ + pkt.data = av_malloc(pkt.size); + AV_WB32(pkt.data, tc.start); + ret = ff_mov_write_packet(s, &pkt); + av_free(pkt.data); + return ret; +} + static int mov_write_header(AVFormatContext *s) { AVIOContext *pb = s->pb; MOVMuxContext *mov = s->priv_data; - AVDictionaryEntry *t; - int i, hint_track = 0; + AVDictionaryEntry *t, *global_tcr = av_dict_get(s->metadata, "timecode", NULL, 0); + int i, hint_track = 0, tmcd_track = 0; /* Set the FRAGMENT flag if any of the fragmentation methods are * enabled. */ @@ -3149,6 +3333,17 @@ static int mov_write_header(AVFormatContext *s) } } + if (mov->mode == MODE_MOV) { + /* Add a tmcd track for each video stream with a timecode */ + tmcd_track = mov->nb_streams; + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && + (global_tcr || av_dict_get(st->metadata, "timecode", NULL, 0))) + mov->nb_streams++; + } + } + mov->tracks = av_mallocz(mov->nb_streams*sizeof(*mov->tracks)); if (!mov->tracks) return AVERROR(ENOMEM); @@ -3192,10 +3387,11 @@ static int mov_write_header(AVFormatContext *s) }else if(st->codec->codec_type == AVMEDIA_TYPE_AUDIO){ track->timescale = st->codec->sample_rate; if(!st->codec->frame_size && !av_get_bits_per_sample(st->codec->codec_id)) { - av_log(s, AV_LOG_ERROR, "track %d: codec frame size is not set\n", i); - goto error; + av_log(s, AV_LOG_WARNING, "track %d: codec frame size is not set\n", i); + track->audio_vbr = 1; }else if(st->codec->codec_id == CODEC_ID_ADPCM_MS || - st->codec->codec_id == CODEC_ID_ADPCM_IMA_WAV){ + st->codec->codec_id == CODEC_ID_ADPCM_IMA_WAV || + st->codec->codec_id == CODEC_ID_ILBC){ if (!st->codec->block_align) { av_log(s, AV_LOG_ERROR, "track %d: codec block align is not set for adpcm\n", i); goto error; @@ -3206,20 +3402,16 @@ static int mov_write_header(AVFormatContext *s) }else{ track->sample_size = (av_get_bits_per_sample(st->codec->codec_id) >> 3) * st->codec->channels; } - if (track->mode != MODE_MOV) { - if (track->timescale > UINT16_MAX) { - av_log(s, AV_LOG_ERROR, "track %d: output format does not support " - "sample rate %dhz\n", i, track->timescale); - goto error; - } - if (track->enc->codec_id == CODEC_ID_MP3 && track->timescale < 16000) { - av_log(s, AV_LOG_ERROR, "track %d: muxing mp3 at %dhz is not supported\n", - i, track->enc->sample_rate); - goto error; - } + if (track->mode != MODE_MOV && + track->enc->codec_id == CODEC_ID_MP3 && track->timescale < 16000) { + av_log(s, AV_LOG_ERROR, "track %d: muxing mp3 at %dhz is not supported\n", + i, track->enc->sample_rate); + goto error; } }else if(st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE){ track->timescale = st->codec->time_base.den; + }else{ + track->timescale = MOV_TIMESCALE; } if (!track->height) track->height = st->codec->height; @@ -3276,6 +3468,24 @@ static int mov_write_header(AVFormatContext *s) } } + if (mov->mode == MODE_MOV) { + /* Initialize the tmcd tracks */ + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + t = global_tcr; + + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + if (!t) + t = av_dict_get(st->metadata, "timecode", NULL, 0); + if (!t) + continue; + if (mov_create_timecode_track(s, tmcd_track, i, t->value) < 0) + goto error; + tmcd_track++; + } + } + } + avio_flush(pb); if (mov->flags & FF_MOV_FLAG_ISML) @@ -3341,6 +3551,8 @@ static int mov_write_trailer(AVFormatContext *s) for (i=0; inb_streams; i++) { if (mov->tracks[i].tag == MKTAG('r','t','p',' ')) ff_mov_close_hinting(&mov->tracks[i]); + else if (mov->tracks[i].tag == MKTAG('t','m','c','d')) + av_freep(&mov->tracks[i].enc); if (mov->flags & FF_MOV_FLAG_FRAGMENT && mov->tracks[i].vc1_info.struct_offset && s->pb->seekable) { int64_t off = avio_tell(pb); @@ -3370,133 +3582,126 @@ static int mov_write_trailer(AVFormatContext *s) MOV_CLASS(mov) AVOutputFormat ff_mov_muxer = { .name = "mov", - .long_name = NULL_IF_CONFIG_SMALL("MOV format"), + .long_name = NULL_IF_CONFIG_SMALL("QuickTime / MOV"), .extensions = "mov", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = CODEC_ID_AAC, -#if CONFIG_LIBX264_ENCODER - .video_codec = CODEC_ID_H264, -#else - .video_codec = CODEC_ID_MPEG4, -#endif + .video_codec = CONFIG_LIBX264_ENCODER ? + CODEC_ID_H264 : CODEC_ID_MPEG4, .write_header = mov_write_header, - .write_packet = ff_mov_write_packet, + .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, - .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, - .codec_tag = (const AVCodecTag* const []){ff_codec_movvideo_tags, ff_codec_movaudio_tags, 0}, - .priv_class = &mov_muxer_class, + .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, + .codec_tag = (const AVCodecTag* const []){ + ff_codec_movvideo_tags, ff_codec_movaudio_tags, 0 + }, + .priv_class = &mov_muxer_class, }; #endif #if CONFIG_TGP_MUXER MOV_CLASS(tgp) AVOutputFormat ff_tgp_muxer = { .name = "3gp", - .long_name = NULL_IF_CONFIG_SMALL("3GP format"), + .long_name = NULL_IF_CONFIG_SMALL("3GP (3GPP file format)"), .extensions = "3gp", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = CODEC_ID_AMR_NB, .video_codec = CODEC_ID_H263, .write_header = mov_write_header, - .write_packet = ff_mov_write_packet, + .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, - .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, - .codec_tag = (const AVCodecTag* const []){codec_3gp_tags, 0}, - .priv_class = &tgp_muxer_class, + .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, + .codec_tag = (const AVCodecTag* const []){ codec_3gp_tags, 0 }, + .priv_class = &tgp_muxer_class, }; #endif #if CONFIG_MP4_MUXER MOV_CLASS(mp4) AVOutputFormat ff_mp4_muxer = { .name = "mp4", - .long_name = NULL_IF_CONFIG_SMALL("MP4 format"), + .long_name = NULL_IF_CONFIG_SMALL("MP4 (MPEG-4 Part 14)"), .mime_type = "application/mp4", .extensions = "mp4", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = CODEC_ID_AAC, -#if CONFIG_LIBX264_ENCODER - .video_codec = CODEC_ID_H264, -#else - .video_codec = CODEC_ID_MPEG4, -#endif + .video_codec = CONFIG_LIBX264_ENCODER ? + CODEC_ID_H264 : CODEC_ID_MPEG4, .write_header = mov_write_header, - .write_packet = ff_mov_write_packet, + .write_packet = 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 = &mp4_muxer_class, + .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, + .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 }, + .priv_class = &mp4_muxer_class, }; #endif #if CONFIG_PSP_MUXER MOV_CLASS(psp) AVOutputFormat ff_psp_muxer = { .name = "psp", - .long_name = NULL_IF_CONFIG_SMALL("PSP MP4 format"), + .long_name = NULL_IF_CONFIG_SMALL("PSP MP4 (MPEG-4 Part 14)"), .extensions = "mp4,psp", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = CODEC_ID_AAC, -#if CONFIG_LIBX264_ENCODER - .video_codec = CODEC_ID_H264, -#else - .video_codec = CODEC_ID_MPEG4, -#endif + .video_codec = CONFIG_LIBX264_ENCODER ? + CODEC_ID_H264 : CODEC_ID_MPEG4, .write_header = mov_write_header, - .write_packet = ff_mov_write_packet, + .write_packet = 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 = &psp_muxer_class, + .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, + .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 }, + .priv_class = &psp_muxer_class, }; #endif #if CONFIG_TG2_MUXER MOV_CLASS(tg2) AVOutputFormat ff_tg2_muxer = { .name = "3g2", - .long_name = NULL_IF_CONFIG_SMALL("3GP2 format"), + .long_name = NULL_IF_CONFIG_SMALL("3GP2 (3GPP2 file format)"), .extensions = "3g2", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = CODEC_ID_AMR_NB, .video_codec = CODEC_ID_H263, .write_header = mov_write_header, - .write_packet = ff_mov_write_packet, + .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, - .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, - .codec_tag = (const AVCodecTag* const []){codec_3gp_tags, 0}, - .priv_class = &tg2_muxer_class, + .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, + .codec_tag = (const AVCodecTag* const []){ codec_3gp_tags, 0 }, + .priv_class = &tg2_muxer_class, }; #endif #if CONFIG_IPOD_MUXER MOV_CLASS(ipod) AVOutputFormat ff_ipod_muxer = { .name = "ipod", - .long_name = NULL_IF_CONFIG_SMALL("iPod H.264 MP4 format"), + .long_name = NULL_IF_CONFIG_SMALL("iPod H.264 MP4 (MPEG-4 Part 14)"), .mime_type = "application/mp4", .extensions = "m4v,m4a", .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_packet = mov_write_packet, .write_trailer = mov_write_trailer, - .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, - .codec_tag = (const AVCodecTag* const []){codec_ipod_tags, 0}, - .priv_class = &ipod_muxer_class, + .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, + .codec_tag = (const AVCodecTag* const []){ codec_ipod_tags, 0 }, + .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"), + .long_name = NULL_IF_CONFIG_SMALL("ISMV/ISMA (Smooth Streaming)"), .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_packet = mov_write_packet, .write_trailer = mov_write_trailer, .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, - .codec_tag = (const AVCodecTag* const []){ff_mp4_obj_type, 0}, + .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 }, .priv_class = &ismv_muxer_class, }; #endif