[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