[FFmpeg-devel] [PATCH] flacenc: Support attaching pictures

James Almer jamrial at gmail.com
Mon Aug 5 07:46:04 CEST 2013


This is based on the implementation from the mp3 muxer, so certain
chunks of code were copied and adapted where needed.

Signed-off-by: James Almer <jamrial at gmail.com>
---
 libavformat/flacenc.c | 197 +++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 187 insertions(+), 10 deletions(-)

diff --git a/libavformat/flacenc.c b/libavformat/flacenc.c
index 87e9362..ca97de5 100644
--- a/libavformat/flacenc.c
+++ b/libavformat/flacenc.c
@@ -23,9 +23,20 @@
 #include "avformat.h"
 #include "avio_internal.h"
 #include "flacenc.h"
+#include "id3v2.h"
 #include "vorbiscomment.h"
 #include "libavcodec/bytestream.h"
+#include "libavutil/pixdesc.h"
 
+typedef struct FLACContext {
+    /* index of the audio stream */
+    int audio_stream_idx;
+    /* number of attached pictures we still need to write */
+    int pics_to_write;
+    /* audio packets are queued here until we get all the attached pictures */
+    AVPacketList *queue, *queue_end;
+    int type;
+} FLACContext;
 
 static int flac_write_block_padding(AVIOContext *pb, unsigned int n_padding_bytes,
                                     int last_block)
@@ -62,19 +73,113 @@ static int flac_write_block_comment(AVIOContext *pb, AVDictionary **m,
     return 0;
 }
 
+static int flac_write_block_picture(AVFormatContext *s, AVPacket *pkt,
+                                    int last_block)
+{
+    AVDictionaryEntry *m;
+    AVIOContext *pb = s->pb;
+    FLACContext *flac = s->priv_data;
+    AVStream *st = s->streams[pkt->stream_index];
+    const CodecMime *mime = ff_id3v2_mime_tags;
+    const char  *mimetype = NULL, *desc = "";
+    int i, mimelen, desclen, type = 3;
+
+    if (st->codec->codec_id == AV_CODEC_ID_GIF) {
+        avpriv_report_missing_feature(s, "GIF picture");
+        return AVERROR_PATCHWELCOME;
+    } else if (st->codec->codec_id != AV_CODEC_ID_MJPEG &&
+               st->codec->codec_id != AV_CODEC_ID_PNG) {
+        av_log(s, AV_LOG_ERROR, "Unsupported picture format");
+        return AVERROR(EINVAL);
+    }
+
+    /* get the mimetype*/
+    while (mime->id != AV_CODEC_ID_NONE) {
+        if (mime->id == st->codec->codec_id) {
+            mimetype = mime->str;
+            break;
+        }
+        mime++;
+    }
+    if (!mimetype) {
+        av_log(s, AV_LOG_ERROR, "No mimetype is known for stream %d, cannot "
+               "write an attached picture.\n", st->index);
+        return AVERROR(EINVAL);
+    }
+    mimelen = strlen(mimetype);
+
+    /* get the picture type */
+    m = av_dict_get(st->metadata, "comment", NULL, 0);
+    for (i = 0; m && i < FF_ARRAY_ELEMS(ff_id3v2_picture_types); i++) {
+        if (strstr(ff_id3v2_picture_types[i], m->value) == ff_id3v2_picture_types[i]) {
+            type = i;
+            break;
+        }
+    }
+    if (type == 1 || type == 2) {
+        if (flac->type & type) {
+            av_log(s, AV_LOG_ERROR, "Only one picture of type \"%s\" can be muxed.\n",
+                   ff_id3v2_picture_types[type]);
+            return AVERROR(EINVAL);
+        }
+        if (type == 1 && st->codec->codec_id != AV_CODEC_ID_PNG) {
+            av_log(s, AV_LOG_ERROR, "Picture of type \"%s\" must be PNG.\n",
+                   ff_id3v2_picture_types[type]);
+            return AVERROR(EINVAL);
+        }
+        flac->type |= type;
+    }
+
+    /* get the description */
+    if ((m = av_dict_get(st->metadata, "title", NULL, 0)))
+        desc = m->value;
+    desclen = strlen(desc);
+
+    /* start writing */
+    avio_w8   (pb, last_block ? 0x86 : 0x06);
+    avio_wb24 (pb, mimelen + desclen + 32 + pkt->size);
+    avio_wb32 (pb, type);
+    avio_wb32 (pb, mimelen);
+    avio_write(pb, mimetype, mimelen);
+    avio_wb32 (pb, desclen);
+    avio_write(pb, desc, desclen);
+    avio_wb32 (pb, st->codec->width);
+    avio_wb32 (pb, st->codec->height);
+    avio_wb32 (pb, av_get_bits_per_pixel(av_pix_fmt_desc_get(st->codec->pix_fmt)));
+    avio_wb32 (pb, 0); // TODO: Number of colors for indexed pics (GIF)
+    avio_wb32 (pb, pkt->size);
+    avio_write(pb, pkt->data, pkt->size);
+
+    return 0;
+}
+
 static int flac_write_header(struct AVFormatContext *s)
 {
     int ret;
-    AVCodecContext *codec = s->streams[0]->codec;
+    AVCodecContext *codec;
+    FLACContext *flac = s->priv_data;
 
-    if (s->nb_streams > 1) {
-        av_log(s, AV_LOG_ERROR, "only one stream is supported\n");
-        return AVERROR(EINVAL);
+    flac->audio_stream_idx = -1;
+    for (ret = 0; ret < s->nb_streams; ret++) {
+        AVStream *st = s->streams[ret];
+        if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
+            if (flac->audio_stream_idx >= 0 || st->codec->codec_id != AV_CODEC_ID_FLAC) {
+                av_log(s, AV_LOG_ERROR, "Invalid audio stream. Exactly one FLAC "
+                       "audio stream is required.\n");
+                return AVERROR(EINVAL);
+            }
+            flac->audio_stream_idx = ret;
+        } else if (st->codec->codec_type != AVMEDIA_TYPE_VIDEO) {
+            av_log(s, AV_LOG_ERROR, "Only audio streams and pictures are allowed in FLAC.\n");
+            return AVERROR(EINVAL);
+        }
     }
-    if (codec->codec_id != AV_CODEC_ID_FLAC) {
-        av_log(s, AV_LOG_ERROR, "unsupported codec\n");
+    if (flac->audio_stream_idx < 0) {
+        av_log(s, AV_LOG_ERROR, "No audio stream present.\n");
         return AVERROR(EINVAL);
     }
+    flac->pics_to_write = s->nb_streams - 1;
+    codec = s->streams[flac->audio_stream_idx]->codec;
 
     ret = ff_flac_write_header(s->pb, codec, 0);
     if (ret)
@@ -89,19 +194,45 @@ static int flac_write_header(struct AVFormatContext *s)
      * every 10s.  So one might add padding to allow that later
      * but there seems to be no simple way to get the duration here.
      * So let's try the flac default of 8192 bytes */
-    flac_write_block_padding(s->pb, 8192, 1);
+    if (!flac->pics_to_write)
+        flac_write_block_padding(s->pb, 8192, 1);
 
     return ret;
 }
 
+static int flac_queue_flush(AVFormatContext *s)
+{
+    FLACContext *flac = s->priv_data;
+    AVPacketList *pktl;
+
+    flac_write_block_padding(s->pb, 8192, 1);
+    while ((pktl = flac->queue)) {
+        avio_write(s->pb, pktl->pkt.data, pktl->pkt.size);
+        av_free_packet(&pktl->pkt);
+        flac->queue = pktl->next;
+        av_freep(&pktl);
+    }
+    flac->queue_end = NULL;
+
+    return 0;
+}
+
 static int flac_write_trailer(struct AVFormatContext *s)
 {
     AVIOContext *pb = s->pb;
+    FLACContext *flac = s->priv_data;
+    AVCodecContext *codec = s->streams[flac->audio_stream_idx]->codec;
     uint8_t *streaminfo;
     enum FLACExtradataFormat format;
     int64_t file_size;
 
-    if (!avpriv_flac_is_extradata_valid(s->streams[0]->codec, &format, &streaminfo))
+    if (flac->pics_to_write) {
+        av_log(s, AV_LOG_WARNING, "No packets were sent for some of the "
+               "attached pictures.\n");
+        flac_queue_flush(s);
+    }
+
+    if (!avpriv_flac_is_extradata_valid(codec, &format, &streaminfo))
         return -1;
 
     if (pb->seekable) {
@@ -119,7 +250,52 @@ static int flac_write_trailer(struct AVFormatContext *s)
 
 static int flac_write_packet(struct AVFormatContext *s, AVPacket *pkt)
 {
-    avio_write(s->pb, pkt->data, pkt->size);
+    FLACContext *flac = s->priv_data;
+
+    if (pkt->stream_index == flac->audio_stream_idx) {
+        if (flac->pics_to_write) {
+            /* buffer audio packets until we get all the pictures */
+            AVPacketList *pktl = av_mallocz(sizeof(*pktl));
+            if (!pktl)
+                return AVERROR(ENOMEM);
+
+            pktl->pkt     = *pkt;
+            pktl->pkt.buf = av_buffer_ref(pkt->buf);
+            if (!pktl->pkt.buf) {
+                av_freep(&pktl);
+                return AVERROR(ENOMEM);
+            }
+
+            if (flac->queue_end)
+                flac->queue_end->next = pktl;
+            else
+                flac->queue = pktl;
+            flac->queue_end = pktl;
+        } else {
+            avio_write(s->pb, pkt->data, pkt->size);
+            return 0;
+        }
+    } else {
+        AVStream *st = s->streams[pkt->stream_index];
+        int ret;
+
+        /* warn only once for each stream */
+        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 (!flac->pics_to_write || st->nb_frames >= 1)
+            return 0;
+
+        if ((ret = flac_write_block_picture(s, pkt, 0)) < 0)
+            return ret;
+        flac->pics_to_write--;
+
+        /* flush the buffered audio packets */
+        if (!flac->pics_to_write)
+            return flac_queue_flush(s);
+    }
+
     return 0;
 }
 
@@ -128,8 +304,9 @@ AVOutputFormat ff_flac_muxer = {
     .long_name         = NULL_IF_CONFIG_SMALL("raw FLAC"),
     .mime_type         = "audio/x-flac",
     .extensions        = "flac",
+    .priv_data_size    = sizeof(FLACContext),
     .audio_codec       = AV_CODEC_ID_FLAC,
-    .video_codec       = AV_CODEC_ID_NONE,
+    .video_codec       = AV_CODEC_ID_PNG,
     .write_header      = flac_write_header,
     .write_packet      = flac_write_packet,
     .write_trailer     = flac_write_trailer,
-- 
1.8.1.5



More information about the ffmpeg-devel mailing list