avformat/http: return EINVAL if ff_http_do_new_request is called with non-http URLContext
[ffmpeg.git] / libavformat / ffmenc.c
index 99d2e98..ef7dc3a 100644 (file)
  */
 
 #include "libavutil/intreadwrite.h"
-#include "libavutil/intfloat_readwrite.h"
+#include "libavutil/intfloat.h"
+#include "libavutil/avassert.h"
+#include "libavutil/parseutils.h"
+#include "libavutil/opt.h"
 #include "avformat.h"
+#include "avio_internal.h"
+#include "internal.h"
 #include "ffm.h"
 
 static void flush_packet(AVFormatContext *s)
@@ -33,8 +38,7 @@ static void flush_packet(AVFormatContext *s)
     fill_size = ffm->packet_end - ffm->packet_ptr;
     memset(ffm->packet_ptr, 0, fill_size);
 
-    if (avio_tell(pb) % ffm->packet_size)
-        av_abort();
+    av_assert1(avio_tell(pb) % ffm->packet_size == 0);
 
     /* put header */
     avio_wb16(pb, PACKET_ID);
@@ -81,107 +85,213 @@ static void ffm_write_data(AVFormatContext *s,
     }
 }
 
+static void write_header_chunk(AVIOContext *pb, AVIOContext *dpb, unsigned id)
+{
+    uint8_t *dyn_buf;
+    int dyn_size= avio_close_dyn_buf(dpb, &dyn_buf);
+    avio_wb32(pb, id);
+    avio_wb32(pb, dyn_size);
+    avio_write(pb, dyn_buf, dyn_size);
+    av_free(dyn_buf);
+}
+
+static int ffm_write_header_codec_ctx(AVIOContext *pb, AVCodecParameters *ctxpar, unsigned tag, int type)
+{
+    AVIOContext *tmp;
+    char *buf = NULL;
+    int ret, need_coma = 0;
+    AVCodecContext *ctx = NULL;
+
+#define SKIP_DEFAULTS   AV_OPT_SERIALIZE_SKIP_DEFAULTS
+#define OPT_FLAGS_EXACT AV_OPT_SERIALIZE_OPT_FLAGS_EXACT
+#define ENC             AV_OPT_FLAG_ENCODING_PARAM
+
+    if (avio_open_dyn_buf(&tmp) < 0)
+        return AVERROR(ENOMEM);
+
+    // AVCodecParameters does not suport AVOptions, we thus must copy it over to a context that does
+    // otherwise it could be used directly and this would be much simpler
+    ctx = avcodec_alloc_context3(NULL);
+    if (!ctx) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+    avcodec_parameters_to_context(ctx, ctxpar);
+
+    if ((ret = av_opt_serialize(ctx, ENC | type, SKIP_DEFAULTS, &buf, '=', ',')) < 0)
+        goto fail;
+    if (buf && strlen(buf)) {
+        avio_write(tmp, buf, strlen(buf));
+        av_freep(&buf);
+        need_coma = 1;
+    }
+    if ((ret = av_opt_serialize(ctx, 0, SKIP_DEFAULTS | OPT_FLAGS_EXACT, &buf, '=', ',')) < 0)
+        goto fail;
+    if (buf && strlen(buf)) {
+        if (need_coma)
+            avio_w8(tmp, ',');
+        avio_write(tmp, buf, strlen(buf));
+    }
+    av_freep(&buf);
+    avio_w8(tmp, 0);
+    write_header_chunk(pb, tmp, tag);
+    avcodec_free_context(&ctx);
+    return 0;
+  fail:
+    av_free(buf);
+    ffio_free_dyn_buf(&tmp);
+    avcodec_free_context(&ctx);
+    return ret;
+
+#undef SKIP_DEFAULTS
+#undef OPT_FLAGS_EXACT
+#undef ENC
+}
+
+static int ffm_write_recommended_config(AVIOContext *pb, AVCodecParameters *codecpar, unsigned tag,
+                                        const char *configuration)
+{
+    int ret;
+    const AVCodec *enc = avcodec_find_encoder(codecpar->codec_id);
+    AVIOContext *tmp;
+    AVDictionaryEntry *t = NULL;
+    AVDictionary *all = NULL, *comm = NULL, *prv = NULL;
+    char *buf = NULL;
+
+    if (!enc || !enc->priv_class || !enc->priv_data_size) {
+        /* codec is not known/has no private options, so save everything as common options */
+        if (avio_open_dyn_buf(&tmp) < 0)
+            return AVERROR(ENOMEM);
+        avio_put_str(tmp, configuration);
+        write_header_chunk(pb, tmp, tag);
+        return 0;
+    }
+
+    if ((ret = av_dict_parse_string(&all, configuration, "=", ",", 0)) < 0)
+        return ret;
+
+    while ((t = av_dict_get(all, "", t, AV_DICT_IGNORE_SUFFIX))) {
+        if (av_opt_find((void *)&enc->priv_class, t->key, NULL, 0, AV_OPT_SEARCH_FAKE_OBJ)) {
+            if ((ret = av_dict_set(&prv, t->key, t->value, 0)) < 0)
+                goto fail;
+        } else if ((ret = av_dict_set(&comm, t->key, t->value, 0)) < 0)
+            goto fail;
+    }
+
+    if (comm) {
+        if ((ret = av_dict_get_string(comm, &buf, '=', ',')) < 0 ||
+            (ret = avio_open_dyn_buf(&tmp)) < 0)
+            goto fail;
+        avio_put_str(tmp, buf);
+        av_freep(&buf);
+        write_header_chunk(pb, tmp, tag);
+    }
+    if (prv) {
+        if ((ret = av_dict_get_string(prv, &buf, '=', ',')) < 0 ||
+            (ret = avio_open_dyn_buf(&tmp)) < 0)
+            goto fail;
+        avio_put_str(tmp, buf);
+        write_header_chunk(pb, tmp, MKBETAG('C', 'P', 'R', 'V'));
+    }
+
+  fail:
+    av_free(buf);
+    av_dict_free(&all);
+    av_dict_free(&comm);
+    av_dict_free(&prv);
+    return ret;
+}
+
 static int ffm_write_header(AVFormatContext *s)
 {
     FFMContext *ffm = s->priv_data;
     AVStream *st;
     AVIOContext *pb = s->pb;
-    AVCodecContext *codec;
-    int bit_rate, i;
+    AVCodecParameters *codecpar;
+    int bit_rate, i, ret;
+
+    if ((ret = ff_parse_creation_time_metadata(s, &ffm->start_time, 0)) < 0)
+        return ret;
 
     ffm->packet_size = FFM_PACKET_SIZE;
 
     /* header */
-    avio_wl32(pb, MKTAG('F', 'F', 'M', '1'));
+    avio_wl32(pb, MKTAG('F', 'F', 'M', '2'));
     avio_wb32(pb, ffm->packet_size);
     avio_wb64(pb, 0); /* current write position */
 
+    if(avio_open_dyn_buf(&pb) < 0)
+        return AVERROR(ENOMEM);
+
     avio_wb32(pb, s->nb_streams);
     bit_rate = 0;
     for(i=0;i<s->nb_streams;i++) {
         st = s->streams[i];
-        bit_rate += st->codec->bit_rate;
+        bit_rate += st->codecpar->bit_rate;
     }
     avio_wb32(pb, bit_rate);
 
+    write_header_chunk(s->pb, pb, MKBETAG('M', 'A', 'I', 'N'));
+
     /* list of streams */
     for(i=0;i<s->nb_streams;i++) {
+        int flags = 0;
         st = s->streams[i];
-        av_set_pts_info(st, 64, 1, 1000000);
+        avpriv_set_pts_info(st, 64, 1, 1000000);
+        if(avio_open_dyn_buf(&pb) < 0)
+            return AVERROR(ENOMEM);
 
-        codec = st->codec;
+        codecpar = st->codecpar;
         /* generic info */
-        avio_wb32(pb, codec->codec_id);
-        avio_w8(pb, codec->codec_type);
-        avio_wb32(pb, codec->bit_rate);
-        avio_wb32(pb, codec->flags);
-        avio_wb32(pb, codec->flags2);
-        avio_wb32(pb, codec->debug);
+        avio_wb32(pb, codecpar->codec_id);
+        avio_w8(pb, codecpar->codec_type);
+        avio_wb32(pb, codecpar->bit_rate);
+        if (codecpar->extradata_size)
+            flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
+
+        // If the user is not providing us with a configuration we have to fill it in as we cannot access the encoder
+        if (!st->recommended_encoder_configuration) {
+            if (s->flags & AVFMT_FLAG_BITEXACT)
+                flags |= AV_CODEC_FLAG_BITEXACT;
+        }
+
+        avio_wb32(pb, flags);
+        avio_wb32(pb, 0); // flags2
+        avio_wb32(pb, 0); // debug
+        if (codecpar->extradata_size) {
+            avio_wb32(pb, codecpar->extradata_size);
+            avio_write(pb, codecpar->extradata, codecpar->extradata_size);
+        }
+        write_header_chunk(s->pb, pb, MKBETAG('C', 'O', 'M', 'M'));
         /* specific info */
-        switch(codec->codec_type) {
+        switch(codecpar->codec_type) {
         case AVMEDIA_TYPE_VIDEO:
-            avio_wb32(pb, codec->time_base.num);
-            avio_wb32(pb, codec->time_base.den);
-            avio_wb16(pb, codec->width);
-            avio_wb16(pb, codec->height);
-            avio_wb16(pb, codec->gop_size);
-            avio_wb32(pb, codec->pix_fmt);
-            avio_w8(pb, codec->qmin);
-            avio_w8(pb, codec->qmax);
-            avio_w8(pb, codec->max_qdiff);
-            avio_wb16(pb, (int) (codec->qcompress * 10000.0));
-            avio_wb16(pb, (int) (codec->qblur * 10000.0));
-            avio_wb32(pb, codec->bit_rate_tolerance);
-            avio_put_str(pb, codec->rc_eq ? codec->rc_eq : "tex^qComp");
-            avio_wb32(pb, codec->rc_max_rate);
-            avio_wb32(pb, codec->rc_min_rate);
-            avio_wb32(pb, codec->rc_buffer_size);
-            avio_wb64(pb, av_dbl2int(codec->i_quant_factor));
-            avio_wb64(pb, av_dbl2int(codec->b_quant_factor));
-            avio_wb64(pb, av_dbl2int(codec->i_quant_offset));
-            avio_wb64(pb, av_dbl2int(codec->b_quant_offset));
-            avio_wb32(pb, codec->dct_algo);
-            avio_wb32(pb, codec->strict_std_compliance);
-            avio_wb32(pb, codec->max_b_frames);
-            avio_wb32(pb, codec->luma_elim_threshold);
-            avio_wb32(pb, codec->chroma_elim_threshold);
-            avio_wb32(pb, codec->mpeg_quant);
-            avio_wb32(pb, codec->intra_dc_precision);
-            avio_wb32(pb, codec->me_method);
-            avio_wb32(pb, codec->mb_decision);
-            avio_wb32(pb, codec->nsse_weight);
-            avio_wb32(pb, codec->frame_skip_cmp);
-            avio_wb64(pb, av_dbl2int(codec->rc_buffer_aggressivity));
-            avio_wb32(pb, codec->codec_tag);
-            avio_w8(pb, codec->thread_count);
-            avio_wb32(pb, codec->coder_type);
-            avio_wb32(pb, codec->me_cmp);
-            avio_wb32(pb, codec->partitions);
-            avio_wb32(pb, codec->me_subpel_quality);
-            avio_wb32(pb, codec->me_range);
-            avio_wb32(pb, codec->keyint_min);
-            avio_wb32(pb, codec->scenechange_threshold);
-            avio_wb32(pb, codec->b_frame_strategy);
-            avio_wb64(pb, av_dbl2int(codec->qcompress));
-            avio_wb64(pb, av_dbl2int(codec->qblur));
-            avio_wb32(pb, codec->max_qdiff);
-            avio_wb32(pb, codec->refs);
-            avio_wb32(pb, codec->directpred);
+            if (st->recommended_encoder_configuration) {
+                av_log(NULL, AV_LOG_DEBUG, "writing recommended configuration: %s\n",
+                       st->recommended_encoder_configuration);
+                if ((ret = ffm_write_recommended_config(s->pb, codecpar, MKBETAG('S', '2', 'V', 'I'),
+                                                        st->recommended_encoder_configuration)) < 0)
+                return ret;
+            } else if ((ret = ffm_write_header_codec_ctx(s->pb, codecpar, MKBETAG('S', '2', 'V', 'I'), AV_OPT_FLAG_VIDEO_PARAM)) < 0)
+                return ret;
             break;
         case AVMEDIA_TYPE_AUDIO:
-            avio_wb32(pb, codec->sample_rate);
-            avio_wl16(pb, codec->channels);
-            avio_wl16(pb, codec->frame_size);
-            avio_wl16(pb, codec->sample_fmt);
+            if (st->recommended_encoder_configuration) {
+                av_log(NULL, AV_LOG_DEBUG, "writing recommended configuration: %s\n",
+                       st->recommended_encoder_configuration);
+                if ((ret = ffm_write_recommended_config(s->pb, codecpar, MKBETAG('S', '2', 'A', 'U'),
+                                                        st->recommended_encoder_configuration)) < 0)
+                return ret;
+            } else if ((ret = ffm_write_header_codec_ctx(s->pb, codecpar, MKBETAG('S', '2', 'A', 'U'), AV_OPT_FLAG_AUDIO_PARAM)) < 0)
+                return ret;
             break;
         default:
             return -1;
         }
-        if (codec->flags & CODEC_FLAG_GLOBAL_HEADER) {
-            avio_wb32(pb, codec->extradata_size);
-            avio_write(pb, codec->extradata, codec->extradata_size);
-        }
     }
+    pb = s->pb;
+
+    avio_wb64(pb, 0); // end of header
 
     /* flush until end of block reached */
     while ((avio_tell(pb) % ffm->packet_size) != 0)
@@ -192,7 +302,7 @@ static int ffm_write_header(AVFormatContext *s)
     /* init packet mux */
     ffm->packet_ptr = ffm->packet;
     ffm->packet_end = ffm->packet + ffm->packet_size - FFM_HEADER_SIZE;
-    assert(ffm->packet_end >= ffm->packet);
+    av_assert0(ffm->packet_end >= ffm->packet);
     ffm->frame_offset = 0;
     ffm->dts = 0;
     ffm->first_packet = 1;
@@ -202,11 +312,12 @@ static int ffm_write_header(AVFormatContext *s)
 
 static int ffm_write_packet(AVFormatContext *s, AVPacket *pkt)
 {
+    FFMContext *ffm = s->priv_data;
     int64_t dts;
     uint8_t header[FRAME_HEADER_SIZE+4];
     int header_size = FRAME_HEADER_SIZE;
 
-    dts = s->timestamp + pkt->dts;
+    dts = ffm->start_time + pkt->dts;
     /* packet size & key_frame */
     header[0] = pkt->stream_index;
     header[1] = 0;
@@ -214,7 +325,7 @@ static int ffm_write_packet(AVFormatContext *s, AVPacket *pkt)
         header[1] |= FLAG_KEY_FRAME;
     AV_WB24(header+2, pkt->size);
     AV_WB24(header+5, pkt->duration);
-    AV_WB64(header+8, s->timestamp + pkt->pts);
+    AV_WB64(header+8, ffm->start_time + pkt->pts);
     if (pkt->pts != pkt->dts) {
         header[1] |= FLAG_DTS;
         AV_WB32(header+16, pkt->pts - pkt->dts);
@@ -228,27 +339,24 @@ static int ffm_write_packet(AVFormatContext *s, AVPacket *pkt)
 
 static int ffm_write_trailer(AVFormatContext *s)
 {
-    AVIOContext *pb = s->pb;
     FFMContext *ffm = s->priv_data;
 
     /* flush packets */
     if (ffm->packet_ptr > ffm->packet)
         flush_packet(s);
 
-    avio_flush(pb);
-
     return 0;
 }
 
 AVOutputFormat ff_ffm_muxer = {
     .name              = "ffm",
-    .long_name         = NULL_IF_CONFIG_SMALL("FFM (FFserver live feed) format"),
-    .mime_type         = "",
+    .long_name         = NULL_IF_CONFIG_SMALL("FFM (FFserver live feed)"),
     .extensions        = "ffm",
     .priv_data_size    = sizeof(FFMContext),
-    .audio_codec       = CODEC_ID_MP2,
-    .video_codec       = CODEC_ID_MPEG1VIDEO,
+    .audio_codec       = AV_CODEC_ID_MP2,
+    .video_codec       = AV_CODEC_ID_MPEG1VIDEO,
     .write_header      = ffm_write_header,
     .write_packet      = ffm_write_packet,
     .write_trailer     = ffm_write_trailer,
+    .flags             = AVFMT_TS_NEGATIVE,
 };