[FFmpeg-devel] [PATCH 3/3] lavf/movenc: support iTunes cover art
Matthieu Bouron
matthieu.bouron at gmail.com
Sun Jun 30 16:15:46 CEST 2013
Cover art muxing is done by introducing the -cover_stream_index option
which takes an output stream index as argument.
The stream used for the cover art is not muxed as a track in the
resulting file.
---
libavformat/movenc.c | 157 +++++++++++++++++++++++++++++++++++++++++++----
libavformat/movenc.h | 5 ++
libavformat/movenchint.c | 1 +
3 files changed, 152 insertions(+), 11 deletions(-)
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 5577530..f411493 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -63,6 +63,7 @@ static const AVOption options[] = {
{ "ism_lookahead", "Number of lookahead entries for ISM files", offsetof(MOVMuxContext, ism_lookahead), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM},
{ "use_editlist", "use edit list", offsetof(MOVMuxContext, use_editlist), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 1, AV_OPT_FLAG_ENCODING_PARAM},
{ "video_track_timescale", "set timescale of all video tracks", offsetof(MOVMuxContext, video_track_timescale), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM},
+ { "cover_stream_index", "video stream index to use for cover art", offsetof(MOVMuxContext, cover_stream_index), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM},
{ NULL },
};
@@ -2067,6 +2068,46 @@ static int mov_write_int8_metadata(AVFormatContext *s, AVIOContext *pb,
return size;
}
+static int mov_write_covr(AVFormatContext *s, AVIOContext *pb, AVPacketList *covers)
+{
+ int64_t pos = avio_tell(pb);
+
+ avio_wb32(pb, 0);
+ ffio_wfourcc(pb, "covr");
+
+ while (covers) {
+ int type;
+ AVPacket *pkt = &covers->pkt;
+ enum AVCodecID codec_id = s->streams[pkt->stream_index]->codec->codec_id;
+
+ switch(codec_id) {
+ case CODEC_ID_MJPEG:
+ type = 0xD;
+ break;
+ case CODEC_ID_PNG:
+ type = 0xE;
+ break;
+ case CODEC_ID_BMP:
+ type = 0x1B;
+ break;
+ default:
+ covers = covers->next;
+ av_log(s, AV_LOG_ERROR, "unsupported codec %s for cover, skipping", s->streams[pkt->stream_index]->codec->codec_name);
+ continue;
+ }
+
+ avio_wb32(pb, 16 + pkt->size);
+ ffio_wfourcc(pb, "data");
+ avio_wb32(pb, type);
+ avio_wb32(pb , 0);
+ avio_write(pb, pkt->data, pkt->size);
+
+ covers = covers->next;
+ }
+
+ return update_size(pb, pos);
+}
+
/* iTunes meta data list */
static int mov_write_ilst_tag(AVIOContext *pb, MOVMuxContext *mov,
AVFormatContext *s)
@@ -2096,6 +2137,10 @@ static int mov_write_ilst_tag(AVIOContext *pb, MOVMuxContext *mov,
mov_write_int8_metadata (s, pb, "stik", "media_type",1);
mov_write_int8_metadata (s, pb, "hdvd", "hd_video", 1);
mov_write_int8_metadata (s, pb, "pgap", "gapless_playback",1);
+
+ if (mov->covers)
+ mov_write_covr(s, pb, mov->covers);
+
mov_write_trkn_tag(pb, mov, s);
mov_write_tmpo_tag(pb, s);
return update_size(pb, pos);
@@ -2198,7 +2243,7 @@ static int mov_write_udta_tag(AVIOContext *pb, MOVMuxContext *mov,
int i, ret, size;
uint8_t *buf;
- for (i = 0; i < s->nb_streams; i++)
+ for (i = 0; i < mov->nb_streams; i++)
if (mov->tracks[i].enc->flags & CODEC_FLAG_BITEXACT) {
return 0;
}
@@ -2801,7 +2846,7 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s)
for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i];
- if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)
+ if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO && i != mov->cover_stream_index)
has_video = 1;
if (st->codec->codec_id == AV_CODEC_ID_H264)
has_h264 = 1;
@@ -3008,6 +3053,18 @@ static int get_moov_size(AVFormatContext *s)
return ret;
}
+static MOVTrack *mov_get_track(MOVMuxContext *mov, int stream_index)
+{
+ int i;
+
+ for (i = 0; i < mov->nb_streams; i++) {
+ MOVTrack *track = &mov->tracks[i];
+ if (track->stream_index == stream_index)
+ return track;
+ }
+ return NULL;
+}
+
static int mov_flush_fragment(AVFormatContext *s)
{
MOVMuxContext *mov = s->priv_data;
@@ -3137,12 +3194,15 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
{
MOVMuxContext *mov = s->priv_data;
AVIOContext *pb = s->pb;
- MOVTrack *trk = &mov->tracks[pkt->stream_index];
- AVCodecContext *enc = trk->enc;
+ MOVTrack *trk = mov_get_track(mov, pkt->stream_index);
+ AVCodecContext *enc;
unsigned int samples_in_chunk = 0;
int size= pkt->size;
uint8_t *reformatted_data = NULL;
+ if (!trk) return 0;
+ enc = trk->enc;
+
if (mov->flags & FF_MOV_FLAG_FRAGMENT) {
int ret;
if (mov->fragments > 0) {
@@ -3290,11 +3350,14 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
static int mov_write_single_packet(AVFormatContext *s, AVPacket *pkt)
{
MOVMuxContext *mov = s->priv_data;
- MOVTrack *trk = &mov->tracks[pkt->stream_index];
- AVCodecContext *enc = trk->enc;
+ MOVTrack *trk = mov_get_track(mov, pkt->stream_index);
+ AVCodecContext *enc;
int64_t frag_duration = 0;
int size = pkt->size;
+ if (!trk) return 0;
+ enc = trk->enc;
+
if (!pkt->size) return 0; /* Discard 0 sized packets */
if (trk->entry && pkt->stream_index < s->nb_streams)
@@ -3378,6 +3441,37 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt)
}
}
+ if (pkt->stream_index == mov->cover_stream_index) {
+ AVStream *st = s->streams[pkt->stream_index];
+
+ /* warn only once */
+ if (st->nb_frames == 1) {
+ av_log(s, AV_LOG_WARNING, "Got more than one picture in stream %d,"
+ " ignoring.\n", pkt->stream_index);
+ }
+
+ if (st->nb_frames == 0) {
+ int ret;
+ AVPacketList *last, *covers = av_mallocz(sizeof(AVPacketList));
+ if (!covers)
+ return AVERROR(ENOMEM);
+
+ if ((ret = av_copy_packet(&covers->pkt, pkt)) < 0) {
+ av_freep(&covers);
+ return ret;
+ }
+
+ if (!mov->covers)
+ mov->covers = covers;
+ else {
+ last = mov->covers;
+ while (last->next)
+ last = last->next;
+ last->next = covers;
+ }
+ }
+ }
+
return mov_write_single_packet(s, pkt);
}
}
@@ -3489,6 +3583,7 @@ static int mov_create_timecode_track(AVFormatContext *s, int index, int src_inde
track->mode = mov->mode;
track->tag = MKTAG('t','m','c','d');
track->src_track = src_index;
+ track->stream_index = index;
track->timescale = mov->tracks[src_index].timescale;
if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME)
track->timecode_flags |= MOV_TIMECODE_FLAG_DROPFRAME;
@@ -3512,7 +3607,7 @@ static int mov_write_header(AVFormatContext *s)
AVIOContext *pb = s->pb;
MOVMuxContext *mov = s->priv_data;
AVDictionaryEntry *t, *global_tcr = av_dict_get(s->metadata, "timecode", NULL, 0);
- int i, hint_track = 0, tmcd_track = 0;
+ int i, hint_track = 0, tmcd_track = 0, stream_index = -1;
/* Set the FRAGMENT flag if any of the fragmentation methods are
* enabled. */
@@ -3567,6 +3662,30 @@ static int mov_write_header(AVFormatContext *s)
}
mov->nb_streams = s->nb_streams;
+
+ if (mov->cover_stream_index >= 0) {
+ AVStream *st;
+ if (mov->cover_stream_index >= s->nb_streams) {
+ av_log(s, AV_LOG_ERROR, "cover stream #%d does not exist\n", mov->cover_stream_index);
+ return -1;
+ }
+
+ st = s->streams[mov->cover_stream_index];
+ if (st->codec->codec_type != AVMEDIA_TYPE_VIDEO) {
+ av_log(s, AV_LOG_ERROR, "cover stream #%d is not a video stream\n", mov->cover_stream_index);
+ return -1;
+ }
+
+ if (st->codec->codec_id != CODEC_ID_MJPEG &&
+ st->codec->codec_id != CODEC_ID_PNG &&
+ st->codec->codec_id != CODEC_ID_BMP) {
+ av_log(s, AV_LOG_ERROR, "cover stream #%d has wrong codec type, must be either png, mjpeg or bmp\n", mov->cover_stream_index);
+ return -1;
+ }
+
+ mov->nb_streams--;
+ }
+
if (mov->mode & (MODE_MOV|MODE_IPOD) && s->nb_chapters)
mov->chapter_track = mov->nb_streams++;
@@ -3575,7 +3694,7 @@ static int mov_write_header(AVFormatContext *s)
hint_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 ||
+ if ((st->codec->codec_type == AVMEDIA_TYPE_VIDEO && i != mov->cover_stream_index) ||
st->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
mov->nb_streams++;
}
@@ -3588,7 +3707,7 @@ static int mov_write_header(AVFormatContext *s)
/* +1 tmcd track for each video stream with a timecode */
for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i];
- if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO &&
+ if ((st->codec->codec_type == AVMEDIA_TYPE_VIDEO && i != mov->cover_stream_index) &&
(global_tcr || av_dict_get(st->metadata, "timecode", NULL, 0)))
mov->nb_meta_tmcd++;
}
@@ -3614,7 +3733,15 @@ static int mov_write_header(AVFormatContext *s)
for(i=0; i<s->nb_streams; i++){
AVStream *st= s->streams[i];
- MOVTrack *track= &mov->tracks[i];
+ MOVTrack *track;
+
+ /* skip cover art stream */
+ if (i == mov->cover_stream_index)
+ continue;
+
+ stream_index++;
+ track = &mov->tracks[stream_index];
+ track->stream_index = i;
AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL,0);
track->enc = st->codec;
@@ -3731,7 +3858,7 @@ static int mov_write_header(AVFormatContext *s)
/* Initialize the hint tracks for each audio and video stream */
for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i];
- if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO ||
+ if ((st->codec->codec_type == AVMEDIA_TYPE_VIDEO && i != mov->cover_stream_index) ||
st->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
ff_mov_init_hinting(s, hint_track, i);
hint_track++;
@@ -3871,6 +3998,7 @@ static int mov_write_trailer(AVFormatContext *s)
{
MOVMuxContext *mov = s->priv_data;
AVIOContext *pb = s->pb;
+ AVPacketList *covers = mov->covers;
int64_t moov_pos;
int res = 0;
int i;
@@ -3962,6 +4090,13 @@ static int mov_write_trailer(AVFormatContext *s)
av_freep(&mov->tracks);
+ while (covers) {
+ AVPacketList *next = covers->next;
+ av_free_packet(&covers->pkt);
+ av_freep(&covers);
+ covers = next;
+ }
+
return res;
}
diff --git a/libavformat/movenc.h b/libavformat/movenc.h
index a6571d5..dbdba3d 100644
--- a/libavformat/movenc.h
+++ b/libavformat/movenc.h
@@ -141,6 +141,8 @@ typedef struct MOVTrack {
int packet_entry;
int slices;
} vc1_info;
+
+ int stream_index;
} MOVTrack;
typedef struct MOVMuxContext {
@@ -172,6 +174,9 @@ typedef struct MOVMuxContext {
int use_editlist;
int video_track_timescale;
+
+ AVPacketList *covers;
+ int cover_stream_index;
} MOVMuxContext;
#define FF_MOV_FLAG_RTP_HINT 1
diff --git a/libavformat/movenchint.c b/libavformat/movenchint.c
index cc90f0b..82c19dc 100644
--- a/libavformat/movenchint.c
+++ b/libavformat/movenchint.c
@@ -36,6 +36,7 @@ int ff_mov_init_hinting(AVFormatContext *s, int index, int src_index)
track->tag = MKTAG('r','t','p',' ');
track->src_track = src_index;
+ track->stream_index = index;
track->enc = avcodec_alloc_context3(NULL);
if (!track->enc)
--
1.8.3.1
More information about the ffmpeg-devel
mailing list