X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fmovenc.c;h=5cd014d26404144aeb563df24b79c333fad7c03f;hb=98422c44cf86de6da8f73a7bd80284ed165c5a98;hp=991452f9ee8d4509093cee7f8868fc1f2f9b7dcf;hpb=7943a90a0d6674e6cb3202fbe032b6e07ed13d0f;p=ffmpeg diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 991452f9ee8..5cd014d2640 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -2,6 +2,7 @@ * MOV, 3GP, MP4 muxer * Copyright (c) 2003 Thomas Raivio * Copyright (c) 2004 Gildas Bazin + * Copyright (c) 2009 Baptiste Coudurier * * This file is part of FFmpeg. * @@ -19,12 +20,14 @@ * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + #include "avformat.h" #include "riff.h" #include "avio.h" #include "isom.h" #include "avc.h" -#include "libavcodec/bitstream.h" +#include "libavcodec/get_bits.h" +#include "libavcodec/put_bits.h" #undef NDEBUG #include @@ -41,13 +44,15 @@ #define MODE_IPOD 0x20 typedef struct MOVIentry { - unsigned int flags, size; + unsigned int size; uint64_t pos; unsigned int samplesInChunk; - char key_frame; unsigned int entries; - int64_t cts; + int cts; int64_t dts; +#define MOV_SYNC_SAMPLE 0x0001 +#define MOV_PARTIAL_SYNC_SAMPLE 0x0002 + uint32_t flags; } MOVIentry; typedef struct MOVIndex { @@ -59,7 +64,9 @@ typedef struct MOVIndex { long sampleCount; long sampleSize; int hasKeyframes; - int hasBframes; +#define MOV_TRACK_CTTS 0x0001 +#define MOV_TRACK_STPS 0x0002 + uint32_t flags; int language; int trackID; int tag; ///< stsd fourcc @@ -79,7 +86,7 @@ typedef struct MOVMuxContext { int64_t mdat_pos; uint64_t mdat_size; long timescale; - MOVTrack tracks[MAX_STREAMS]; + MOVTrack *tracks; } MOVMuxContext; //FIXME support 64 bit variant with wide placeholders @@ -184,18 +191,18 @@ static int mov_write_stsc_tag(ByteIOContext *pb, MOVTrack *track) } /* Sync sample atom */ -static int mov_write_stss_tag(ByteIOContext *pb, MOVTrack *track) +static int mov_write_stss_tag(ByteIOContext *pb, MOVTrack *track, uint32_t flag) { int64_t curpos, entryPos; int i, index = 0; int64_t pos = url_ftell(pb); put_be32(pb, 0); // size - put_tag(pb, "stss"); + put_tag(pb, flag == MOV_SYNC_SAMPLE ? "stss" : "stps"); put_be32(pb, 0); // version & flags entryPos = url_ftell(pb); put_be32(pb, track->entry); // entry count for (i=0; ientry; i++) { - if(track->cluster[i].key_frame == 1) { + if (track->cluster[i].flags & flag) { put_be32(pb, i+1); index++; } @@ -323,7 +330,7 @@ static int mov_write_esds_tag(ByteIOContext *pb, MOVTrack *track) // Basic track->enc->sample_rate > 24000) put_byte(pb, 0x6B); // 11172-3 else - put_byte(pb, codec_get_tag(ff_mp4_obj_type, track->enc->codec_id)); + put_byte(pb, ff_codec_get_tag(ff_mp4_obj_type, track->enc->codec_id)); // the following fields is made of 6 bits to identify the streamtype (4 for video, 5 for audio) // plus 1 bit to indicate upstream and 1 bit set to 1 (reserved) @@ -544,98 +551,160 @@ static int mov_write_avid_tag(ByteIOContext *pb, MOVTrack *track) return 0; } -static const AVCodecTag codec_3gp_tags[] = { - { CODEC_ID_H263, MKTAG('s','2','6','3') }, +static int mp4_get_codec_tag(AVFormatContext *s, MOVTrack *track) +{ + int tag = track->enc->codec_tag; + + if (!ff_codec_get_tag(ff_mp4_obj_type, track->enc->codec_id)) + return 0; + + if (track->enc->codec_id == CODEC_ID_H264) tag = MKTAG('a','v','c','1'); + else if (track->enc->codec_id == CODEC_ID_AC3) tag = MKTAG('a','c','-','3'); + else if (track->enc->codec_id == CODEC_ID_DIRAC) tag = MKTAG('d','r','a','c'); + else if (track->enc->codec_id == CODEC_ID_MOV_TEXT) tag = MKTAG('t','x','3','g'); + else if (track->enc->codec_type == CODEC_TYPE_VIDEO) tag = MKTAG('m','p','4','v'); + else if (track->enc->codec_type == CODEC_TYPE_AUDIO) tag = MKTAG('m','p','4','a'); + + return tag; +} + +static const AVCodecTag codec_ipod_tags[] = { { CODEC_ID_H264, MKTAG('a','v','c','1') }, { CODEC_ID_MPEG4, MKTAG('m','p','4','v') }, { CODEC_ID_AAC, MKTAG('m','p','4','a') }, - { CODEC_ID_AMR_NB, MKTAG('s','a','m','r') }, - { CODEC_ID_AMR_WB, MKTAG('s','a','w','b') }, + { CODEC_ID_ALAC, MKTAG('a','l','a','c') }, + { CODEC_ID_AC3, MKTAG('a','c','-','3') }, { CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') }, + { CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') }, { CODEC_ID_NONE, 0 }, }; -static const AVCodecTag mov_pix_fmt_tags[] = { - { PIX_FMT_YUYV422, MKTAG('y','u','v','s') }, - { PIX_FMT_UYVY422, MKTAG('2','v','u','y') }, - { PIX_FMT_BGR555, MKTAG('r','a','w',' ') }, - { PIX_FMT_RGB24, MKTAG('r','a','w',' ') }, - { PIX_FMT_BGR32_1, MKTAG('r','a','w',' ') }, +static int ipod_get_codec_tag(AVFormatContext *s, MOVTrack *track) +{ + int tag = track->enc->codec_tag; + + // keep original tag for subs, ipod supports both formats + if (!(track->enc->codec_type == CODEC_TYPE_SUBTITLE && + (tag == MKTAG('t','x','3','g') || + tag == MKTAG('t','e','x','t')))) + tag = ff_codec_get_tag(codec_ipod_tags, track->enc->codec_id); + + if (!match_ext(s->filename, "m4a") && !match_ext(s->filename, "m4v")) + av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a nor .m4v " + "Quicktime/Ipod might not play the file\n"); + + return tag; +} + +static int mov_get_dv_codec_tag(AVFormatContext *s, MOVTrack *track) +{ + int tag; + + if (track->enc->height == 480) /* NTSC */ + if (track->enc->pix_fmt == PIX_FMT_YUV422P) tag = MKTAG('d','v','5','n'); + else tag = MKTAG('d','v','c',' '); + else if (track->enc->pix_fmt == PIX_FMT_YUV422P) tag = MKTAG('d','v','5','p'); + else if (track->enc->pix_fmt == PIX_FMT_YUV420P) tag = MKTAG('d','v','c','p'); + else tag = MKTAG('d','v','p','p'); + + return tag; +} + +static const struct { + enum PixelFormat pix_fmt; + uint32_t tag; + unsigned bps; +} mov_pix_fmt_tags[] = { + { PIX_FMT_YUYV422, MKTAG('y','u','v','s'), 0 }, + { PIX_FMT_UYVY422, MKTAG('2','v','u','y'), 0 }, + { PIX_FMT_BGR555, MKTAG('r','a','w',' '), 16 }, + { PIX_FMT_RGB555LE,MKTAG('L','5','5','5'), 16 }, + { PIX_FMT_RGB565LE,MKTAG('L','5','6','5'), 16 }, + { PIX_FMT_RGB565BE,MKTAG('B','5','6','5'), 16 }, + { PIX_FMT_RGB24, MKTAG('r','a','w',' '), 24 }, + { PIX_FMT_BGR24, MKTAG('2','4','B','G'), 24 }, + { PIX_FMT_ARGB, MKTAG('r','a','w',' '), 32 }, + { PIX_FMT_BGRA, MKTAG('B','G','R','A'), 32 }, + { PIX_FMT_RGBA, MKTAG('R','G','B','A'), 32 }, }; -static const AVCodecTag codec_ipod_tags[] = { +static int mov_get_rawvideo_codec_tag(AVFormatContext *s, MOVTrack *track) +{ + int tag = track->enc->codec_tag; + 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) { + tag = mov_pix_fmt_tags[i].tag; + track->enc->bits_per_coded_sample = mov_pix_fmt_tags[i].bps; + break; + } + } + + return tag; +} + +static int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track) +{ + int tag = track->enc->codec_tag; + + if (!tag || (track->enc->strict_std_compliance >= FF_COMPLIANCE_NORMAL && + (tag == MKTAG('d','v','c','p') || + track->enc->codec_id == CODEC_ID_RAWVIDEO || + av_get_bits_per_sample(track->enc->codec_id)))) { // pcm audio + if (track->enc->codec_id == CODEC_ID_DVVIDEO) + tag = mov_get_dv_codec_tag(s, track); + else if (track->enc->codec_id == CODEC_ID_RAWVIDEO) + tag = mov_get_rawvideo_codec_tag(s, track); + else if (track->enc->codec_type == CODEC_TYPE_VIDEO) { + tag = ff_codec_get_tag(codec_movvideo_tags, track->enc->codec_id); + if (!tag) { // if no mac fcc found, try with Microsoft tags + tag = ff_codec_get_tag(ff_codec_bmp_tags, track->enc->codec_id); + if (tag) + av_log(s, AV_LOG_INFO, "Warning, using MS style video codec tag, " + "the file may be unplayable!\n"); + } + } else if (track->enc->codec_type == CODEC_TYPE_AUDIO) { + tag = ff_codec_get_tag(codec_movaudio_tags, track->enc->codec_id); + if (!tag) { // if no mac fcc found, try with Microsoft tags + int ms_tag = ff_codec_get_tag(ff_codec_wav_tags, track->enc->codec_id); + if (ms_tag) { + tag = MKTAG('m', 's', ((ms_tag >> 8) & 0xff), (ms_tag & 0xff)); + av_log(s, AV_LOG_INFO, "Warning, using MS style audio codec tag, " + "the file may be unplayable!\n"); + } + } + } else if (track->enc->codec_type == CODEC_TYPE_SUBTITLE) + tag = ff_codec_get_tag(ff_codec_movsubtitle_tags, track->enc->codec_id); + } + + return tag; +} + +static const AVCodecTag codec_3gp_tags[] = { + { CODEC_ID_H263, MKTAG('s','2','6','3') }, { CODEC_ID_H264, MKTAG('a','v','c','1') }, { CODEC_ID_MPEG4, MKTAG('m','p','4','v') }, { CODEC_ID_AAC, MKTAG('m','p','4','a') }, - { CODEC_ID_ALAC, MKTAG('a','l','a','c') }, - { CODEC_ID_AC3, MKTAG('a','c','-','3') }, + { CODEC_ID_AMR_NB, MKTAG('s','a','m','r') }, + { CODEC_ID_AMR_WB, MKTAG('s','a','w','b') }, { CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') }, - { CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') }, { CODEC_ID_NONE, 0 }, }; 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 (!codec_get_tag(ff_mp4_obj_type, track->enc->codec_id)) - return 0; - if (track->enc->codec_id == CODEC_ID_H264) tag = MKTAG('a','v','c','1'); - else if (track->enc->codec_id == CODEC_ID_AC3) tag = MKTAG('a','c','-','3'); - else if (track->enc->codec_id == CODEC_ID_DIRAC) tag = MKTAG('d','r','a','c'); - else if (track->enc->codec_id == CODEC_ID_MOV_TEXT) tag = MKTAG('t','x','3','g'); - else if (track->enc->codec_type == CODEC_TYPE_VIDEO) tag = MKTAG('m','p','4','v'); - else if (track->enc->codec_type == CODEC_TYPE_AUDIO) tag = MKTAG('m','p','4','a'); - } else if (track->mode == MODE_IPOD) { - if (track->enc->codec_type == CODEC_TYPE_SUBTITLE && - (tag == MKTAG('t','x','3','g') || - tag == MKTAG('t','e','x','t'))) - track->tag = tag; // keep original tag - else - tag = codec_get_tag(codec_ipod_tags, track->enc->codec_id); - if (!match_ext(s->filename, "m4a") && !match_ext(s->filename, "m4v")) - av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a nor .m4v " - "Quicktime/Ipod might not play the file\n"); - } else if (track->mode & MODE_3GP) { - tag = codec_get_tag(codec_3gp_tags, track->enc->codec_id); - } else if (!tag || (track->enc->strict_std_compliance >= FF_COMPLIANCE_NORMAL && - (tag == MKTAG('d','v','c','p') || - track->enc->codec_id == CODEC_ID_RAWVIDEO))) { - if (track->enc->codec_id == CODEC_ID_DVVIDEO) { - if (track->enc->height == 480) /* NTSC */ - if (track->enc->pix_fmt == PIX_FMT_YUV422P) tag = MKTAG('d','v','5','n'); - else tag = MKTAG('d','v','c',' '); - else if (track->enc->pix_fmt == PIX_FMT_YUV422P) tag = MKTAG('d','v','5','p'); - else if (track->enc->pix_fmt == PIX_FMT_YUV420P) tag = MKTAG('d','v','c','p'); - else tag = MKTAG('d','v','p','p'); - } else if (track->enc->codec_id == CODEC_ID_RAWVIDEO) { - tag = codec_get_tag(mov_pix_fmt_tags, track->enc->pix_fmt); - if (!tag) // restore tag - tag = track->enc->codec_tag; - } else { - if (track->enc->codec_type == CODEC_TYPE_VIDEO) { - tag = codec_get_tag(codec_movvideo_tags, track->enc->codec_id); - if (!tag) { // if no mac fcc found, try with Microsoft tags - tag = codec_get_tag(codec_bmp_tags, track->enc->codec_id); - if (tag) - av_log(s, AV_LOG_INFO, "Warning, using MS style video codec tag, " - "the file may be unplayable!\n"); - } - } else if (track->enc->codec_type == CODEC_TYPE_AUDIO) { - tag = codec_get_tag(codec_movaudio_tags, track->enc->codec_id); - if (!tag) { // if no mac fcc found, try with Microsoft tags - int ms_tag = codec_get_tag(codec_wav_tags, track->enc->codec_id); - if (ms_tag) { - tag = MKTAG('m', 's', ((ms_tag >> 8) & 0xff), (ms_tag & 0xff)); - av_log(s, AV_LOG_INFO, "Warning, using MS style audio codec tag, " - "the file may be unplayable!\n"); - } - } - } else if (track->enc->codec_type == CODEC_TYPE_SUBTITLE) { - tag = codec_get_tag(ff_codec_movsubtitle_tags, track->enc->codec_id); - } - } - } + + if (track->mode == MODE_MP4 || track->mode == MODE_PSP) + tag = mp4_get_codec_tag(s, track); + else if (track->mode == MODE_IPOD) + tag = ipod_get_codec_tag(s, track); + else if (track->mode & MODE_3GP) + tag = ff_codec_get_tag(codec_3gp_tags, track->enc->codec_id); + else + tag = mov_get_codec_tag(s, track); + return tag; } @@ -848,9 +917,11 @@ static int mov_write_stbl_tag(ByteIOContext *pb, MOVTrack *track) mov_write_stts_tag(pb, track); if (track->enc->codec_type == CODEC_TYPE_VIDEO && track->hasKeyframes && track->hasKeyframes < track->entry) - mov_write_stss_tag(pb, track); + mov_write_stss_tag(pb, track, MOV_SYNC_SAMPLE); + if (track->mode == MODE_MOV && track->flags & MOV_TRACK_STPS) + mov_write_stss_tag(pb, track, MOV_PARTIAL_SYNC_SAMPLE); if (track->enc->codec_type == CODEC_TYPE_VIDEO && - track->hasBframes) + track->flags & MOV_TRACK_CTTS) mov_write_ctts_tag(pb, track); mov_write_stsc_tag(pb, track); mov_write_stsz_tag(pb, track); @@ -942,8 +1013,11 @@ static int mov_write_hdlr_tag(ByteIOContext *pb, MOVTrack *track) put_be32(pb ,0); /* reserved */ put_be32(pb ,0); /* reserved */ put_be32(pb ,0); /* reserved */ - put_byte(pb, strlen(descr)); /* string counter */ + if (!track || track->mode == MODE_MOV) + put_byte(pb, strlen(descr)); /* pascal string */ put_buffer(pb, descr, strlen(descr)); /* handler description */ + if (track && track->mode != MODE_MOV) + put_byte(pb, 0); /* c string */ return updateSize(pb, pos); } @@ -1107,7 +1181,7 @@ static int mov_write_trak_tag(ByteIOContext *pb, MOVTrack *track, AVStream *st) put_be32(pb, 0); /* size */ put_tag(pb, "trak"); mov_write_tkhd_tag(pb, track, st); - if (track->mode == MODE_PSP || track->hasBframes) + if (track->mode == MODE_PSP || track->flags & MOV_TRACK_CTTS) mov_write_edts_tag(pb, track); // PSP Movies require edts box mov_write_mdia_tag(pb, track); if (track->mode == MODE_PSP) @@ -1363,7 +1437,7 @@ static int mov_write_3gp_udta_tag(ByteIOContext *pb, AVFormatContext *s, put_be16(pb, atoi(t->value)); else { put_be16(pb, language_code("eng")); /* language */ - ascii_to_wc(pb, t->value); + put_buffer(pb, t->value, strlen(t->value)+1); /* UTF8 string value */ if (!strcmp(tag, "albm") && (t = av_metadata_get(s->metadata, "year", NULL, 0))) put_byte(pb, atoi(t->value)); @@ -1662,6 +1736,10 @@ static int mov_write_header(AVFormatContext *s) } } + mov->tracks = av_mallocz(s->nb_streams*sizeof(*mov->tracks)); + if (!mov->tracks) + return AVERROR(ENOMEM); + for(i=0; inb_streams; i++){ AVStream *st= s->streams[i]; MOVTrack *track= &mov->tracks[i]; @@ -1676,7 +1754,7 @@ static int mov_write_header(AVFormatContext *s) if (!track->tag) { av_log(s, AV_LOG_ERROR, "track %d: could not find tag, " "codec not currently supported in container\n", i); - return -1; + goto error; } if(st->codec->codec_type == CODEC_TYPE_VIDEO){ if (track->tag == MKTAG('m','x','3','p') || track->tag == MKTAG('m','x','3','n') || @@ -1684,12 +1762,11 @@ static int mov_write_header(AVFormatContext *s) track->tag == MKTAG('m','x','5','p') || track->tag == MKTAG('m','x','5','n')) { if (st->codec->width != 720 || (st->codec->height != 608 && st->codec->height != 512)) { av_log(s, AV_LOG_ERROR, "D-10/IMX must use 720x608 or 720x512 video resolution\n"); - return -1; + goto error; } track->height = track->tag>>24 == 'n' ? 486 : 576; } track->timescale = st->codec->time_base.den; - av_set_pts_info(st, 64, 1, st->codec->time_base.den); if (track->mode == MODE_MOV && track->timescale > 100000) av_log(s, AV_LOG_WARNING, "WARNING codec timebase is very high. If duration is too long,\n" @@ -1697,10 +1774,9 @@ static int mov_write_header(AVFormatContext *s) "or choose different container.\n"); }else if(st->codec->codec_type == CODEC_TYPE_AUDIO){ track->timescale = st->codec->sample_rate; - av_set_pts_info(st, 64, 1, 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); - return -1; + goto error; }else if(st->codec->frame_size > 1){ /* assume compressed audio */ track->audio_vbr = 1; }else{ @@ -1711,14 +1787,15 @@ static int mov_write_header(AVFormatContext *s) track->enc->codec_id == CODEC_ID_MP3 && track->enc->sample_rate < 16000){ av_log(s, AV_LOG_ERROR, "track %d: muxing mp3 at %dhz is not supported\n", i, track->enc->sample_rate); - return -1; + goto error; } }else if(st->codec->codec_type == CODEC_TYPE_SUBTITLE){ track->timescale = st->codec->time_base.den; - av_set_pts_info(st, 64, 1, st->codec->time_base.den); } if (!track->height) track->height = st->codec->height; + + av_set_pts_info(st, 64, 1, track->timescale); } mov_write_mdat_tag(pb, mov); @@ -1727,6 +1804,30 @@ static int mov_write_header(AVFormatContext *s) put_flush_packet(pb); + return 0; + error: + av_freep(&mov->tracks); + return -1; +} + +static int mov_parse_mpeg2_frame(AVPacket *pkt, uint32_t *flags) +{ + uint32_t c = -1; + int i, closed_gop = 0; + + for (i = 0; i < pkt->size - 4; i++) { + c = (c<<8) + pkt->data[i]; + if (c == 0x1b8) { // gop + closed_gop = pkt->data[i+4]>>6 & 0x01; + } else if (c == 0x100) { // pic + int temp_ref = (pkt->data[i+1]<<2) | (pkt->data[i+2]>>6); + if (!temp_ref || closed_gop) // I picture is not reordered + *flags = MOV_SYNC_SAMPLE; + else + *flags = MOV_PARTIAL_SYNC_SAMPLE; + break; + } + } return 0; } @@ -1777,7 +1878,7 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) } if ((enc->codec_id == CODEC_ID_DNXHD || - enc->codec_id == CODEC_ID_AC3) && !trk->vosLen) { + enc->codec_id == CODEC_ID_AC3) && !trk->vosLen) { /* copy frame to create needed atoms */ trk->vosLen = size; trk->vosData = av_malloc(size); @@ -1804,11 +1905,20 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) pkt->pts = pkt->dts; } if (pkt->dts != pkt->pts) - trk->hasBframes = 1; + trk->flags |= MOV_TRACK_CTTS; trk->cluster[trk->entry].cts = pkt->pts - pkt->dts; - trk->cluster[trk->entry].key_frame = !!(pkt->flags & PKT_FLAG_KEY); - if(trk->cluster[trk->entry].key_frame) - trk->hasKeyframes++; + trk->cluster[trk->entry].flags = 0; + if (pkt->flags & PKT_FLAG_KEY) { + if (mov->mode == MODE_MOV && enc->codec_id == CODEC_ID_MPEG2VIDEO) { + mov_parse_mpeg2_frame(pkt, &trk->cluster[trk->entry].flags); + if (trk->cluster[trk->entry].flags & MOV_PARTIAL_SYNC_SAMPLE) + trk->flags |= MOV_TRACK_STPS; + } else { + trk->cluster[trk->entry].flags = MOV_SYNC_SAMPLE; + } + if (trk->cluster[trk->entry].flags & MOV_SYNC_SAMPLE) + trk->hasKeyframes++; + } trk->entry++; trk->sampleCount += samplesInChunk; mov->mdat_size += size; @@ -1850,6 +1960,8 @@ static int mov_write_trailer(AVFormatContext *s) put_flush_packet(pb); + av_freep(&mov->tracks); + return res; }