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

Paul B Mahol onemda at gmail.com
Tue Aug 6 11:54:01 CEST 2013


On 8/5/13, James Almer <jamrial at gmail.com> wrote:
> 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);
> +            }

So this eats endless memory? How much of it is buffered?

This is extremly sloppy/ugly/complicated/buggy design.

> +
> +            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
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>


More information about the ffmpeg-devel mailing list