ogg muxer
authorBaptiste Coudurier <baptiste.coudurier@gmail.com>
Sat, 10 Nov 2007 18:27:03 +0000 (18:27 +0000)
committerBaptiste Coudurier <baptiste.coudurier@gmail.com>
Sat, 10 Nov 2007 18:27:03 +0000 (18:27 +0000)
Originally committed as revision 10999 to svn://svn.ffmpeg.org/ffmpeg/trunk

Changelog
MAINTAINERS
libavcodec/Makefile
libavformat/Makefile
libavformat/allformats.c
libavformat/avformat.h
libavformat/oggenc.c [new file with mode: 0644]
tests/libav.regression.ref
tests/regression.sh

index f3f2acf..dfff018 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -103,6 +103,7 @@ version <next>
 - Beam Software SIFF demuxer and decoder
 - libvorbis Vorbis decoding removed in favor of native decoder
 - IntraX8 (J-Frame) sub-decoder for WMV2 and VC-1
+- OGG muxer
 
 version 0.4.9-pre1:
 
index 342e327..14dac7d 100644 (file)
@@ -237,6 +237,7 @@ Muxers/Demuxers:
   nut.c                                 Michael Niedermayer
   nuv.c                                 Reimar Doeffinger
   ogg2.c, ogg2.h                        Mans Rullgard
+  oggenc.c                              Baptiste Coudurier
   oggparsevorbis.c                      Mans Rullgard
   oggparseogm.c                         Mans Rullgard
   psxstr.c                              Mike Melanson
index 2423065..f1958bb 100644 (file)
@@ -292,6 +292,9 @@ OBJS-$(CONFIG_ADPCM_XA_DECODER)        += adpcm.o
 OBJS-$(CONFIG_ADPCM_YAMAHA_DECODER)    += adpcm.o
 OBJS-$(CONFIG_ADPCM_YAMAHA_ENCODER)    += adpcm.o
 
+# libavformat dependencies
+OBJS-$(CONFIG_OGG_MUXER)               += xiph.o
+
 # external codec libraries
 OBJS-$(CONFIG_LIBA52)                  += liba52.o
 OBJS-$(CONFIG_LIBAMR)                  += libamr.o
index e97236c..3d29abe 100644 (file)
@@ -113,6 +113,7 @@ OBJS-$(CONFIG_OGG_DEMUXER)               += oggdec.o         \
                                             oggparseflac.o   \
                                             oggparseogm.o    \
                                             riff.o
+OBJS-$(CONFIG_OGG_MUXER)                 += oggenc.o
 OBJS-$(CONFIG_OSS_DEMUXER)               += audio.o
 OBJS-$(CONFIG_OSS_MUXER)                 += audio.o
 OBJS-$(CONFIG_PSP_MUXER)                 += movenc.o riff.o isom.o
index ad5c6af..b92201a 100644 (file)
@@ -122,7 +122,7 @@ void av_register_all(void)
     REGISTER_MUXER    (NULL, null);
     REGISTER_MUXDEMUX (NUT, nut);
     REGISTER_DEMUXER  (NUV, nuv);
-    REGISTER_DEMUXER  (OGG, ogg);
+    REGISTER_MUXDEMUX (OGG, ogg);
     REGISTER_MUXDEMUX (OSS, oss);
     REGISTER_MUXDEMUX (PCM_ALAW,  pcm_alaw);
     REGISTER_MUXDEMUX (PCM_MULAW, pcm_mulaw);
index 93eee35..d127d7d 100644 (file)
@@ -21,8 +21,8 @@
 #ifndef FFMPEG_AVFORMAT_H
 #define FFMPEG_AVFORMAT_H
 
-#define LIBAVFORMAT_VERSION_INT ((51<<16)+(18<<8)+0)
-#define LIBAVFORMAT_VERSION     51.18.0
+#define LIBAVFORMAT_VERSION_INT ((51<<16)+(19<<8)+0)
+#define LIBAVFORMAT_VERSION     51.19.0
 #define LIBAVFORMAT_BUILD       LIBAVFORMAT_VERSION_INT
 
 #define LIBAVFORMAT_IDENT       "Lavf" AV_STRINGIFY(LIBAVFORMAT_VERSION)
diff --git a/libavformat/oggenc.c b/libavformat/oggenc.c
new file mode 100644 (file)
index 0000000..06c978e
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Ogg muxer
+ * Copyright (c) 2007 Baptiste Coudurier <baptiste dot coudurier at free dot fr>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "avformat.h"
+#include "crc.h"
+#include "xiph.h"
+#include "bytestream.h"
+
+typedef struct {
+    int64_t duration;
+    unsigned page_counter;
+    uint8_t *header[3];
+    int header_len[3];
+    /** for theora granule */
+    int kfgshift;
+    int64_t last_kf_pts;
+    int vrev;
+} OGGStreamContext;
+
+static void ogg_update_checksum(AVFormatContext *s, offset_t crc_offset)
+{
+    offset_t pos = url_ftell(&s->pb);
+    uint32_t checksum = get_checksum(&s->pb);
+    url_fseek(&s->pb, crc_offset, SEEK_SET);
+    put_be32(&s->pb, checksum);
+    url_fseek(&s->pb, pos, SEEK_SET);
+}
+
+static int ogg_write_page(AVFormatContext *s, const uint8_t *data, int size,
+                          int64_t granule, int stream_index, int flags)
+{
+    OGGStreamContext *oggstream = s->streams[stream_index]->priv_data;
+    offset_t crc_offset;
+    int page_segments, i;
+
+    size = FFMIN(size, 255*255);
+    page_segments = FFMIN((size/255)+!!size, 255);
+
+    init_checksum(&s->pb, ff_crc04C11DB7_update, 0);
+    put_tag(&s->pb, "OggS");
+    put_byte(&s->pb, 0);
+    put_byte(&s->pb, flags);
+    put_le64(&s->pb, granule);
+    put_le32(&s->pb, stream_index);
+    put_le32(&s->pb, oggstream->page_counter++);
+    crc_offset = url_ftell(&s->pb);
+    put_le32(&s->pb, 0); // crc
+    put_byte(&s->pb, page_segments);
+    for (i = 0; i < page_segments-1; i++)
+        put_byte(&s->pb, 255);
+    if (size) {
+        put_byte(&s->pb, size - (page_segments-1)*255);
+        put_buffer(&s->pb, data, size);
+    }
+    ogg_update_checksum(s, crc_offset);
+    put_flush_packet(&s->pb);
+    return size;
+}
+
+static int ogg_build_flac_headers(const uint8_t *extradata, int extradata_size,
+                                  OGGStreamContext *oggstream, int bitexact)
+{
+    const char *vendor = bitexact ? "ffmpeg" : LIBAVFORMAT_IDENT;
+    uint8_t *p;
+    if (extradata_size != 34)
+        return -1;
+    oggstream->header_len[0] = 79;
+    oggstream->header[0] = av_mallocz(79); // per ogg flac specs
+    p = oggstream->header[0];
+    bytestream_put_byte(&p, 0x7F);
+    bytestream_put_buffer(&p, "FLAC", 4);
+    bytestream_put_byte(&p, 1); // major version
+    bytestream_put_byte(&p, 0); // minor version
+    bytestream_put_be16(&p, 1); // headers packets without this one
+    bytestream_put_buffer(&p, "fLaC", 4);
+    bytestream_put_byte(&p, 0x00); // streaminfo
+    bytestream_put_be24(&p, 34);
+    bytestream_put_buffer(&p, extradata, 34);
+    oggstream->header_len[1] = 1+3+4+strlen(vendor)+4;
+    oggstream->header[1] = av_mallocz(oggstream->header_len[1]);
+    p = oggstream->header[1];
+    bytestream_put_byte(&p, 0x84); // last metadata block and vorbis comment
+    bytestream_put_be24(&p, oggstream->header_len[1] - 4);
+    bytestream_put_le32(&p, strlen(vendor));
+    bytestream_put_buffer(&p, vendor, strlen(vendor));
+    bytestream_put_le32(&p, 0); // user comment list length
+    return 0;
+}
+
+static int ogg_write_header(AVFormatContext *s)
+{
+    OGGStreamContext *oggstream;
+    int i, j;
+    for (i = 0; i < s->nb_streams; i++) {
+        AVStream *st = s->streams[i];
+        if (st->codec->codec_type == CODEC_TYPE_AUDIO)
+            av_set_pts_info(st, 64, 1, st->codec->sample_rate);
+        else if (st->codec->codec_type == CODEC_TYPE_VIDEO)
+            av_set_pts_info(st, 64, st->codec->time_base.num, st->codec->time_base.den);
+        if (st->codec->codec_id != CODEC_ID_VORBIS &&
+            st->codec->codec_id != CODEC_ID_THEORA &&
+            st->codec->codec_id != CODEC_ID_FLAC) {
+            av_log(s, AV_LOG_ERROR, "Unsupported codec id in stream %d\n", i);
+            return -1;
+        }
+
+        if (!st->codec->extradata || !st->codec->extradata_size) {
+            av_log(s, AV_LOG_ERROR, "No extradata present\n");
+            return -1;
+        }
+        oggstream = av_mallocz(sizeof(*oggstream));
+        st->priv_data = oggstream;
+        if (st->codec->codec_id == CODEC_ID_FLAC) {
+            if (ogg_build_flac_headers(st->codec->extradata, st->codec->extradata_size,
+                                       oggstream, st->codec->flags & CODEC_FLAG_BITEXACT) < 0) {
+                av_log(s, AV_LOG_ERROR, "Extradata corrupted\n");
+                av_freep(&st->priv_data);
+            }
+        } else {
+            if (ff_split_xiph_headers(st->codec->extradata, st->codec->extradata_size,
+                                      st->codec->codec_id == CODEC_ID_VORBIS ? 30 : 42,
+                                      oggstream->header, oggstream->header_len) < 0) {
+                av_log(s, AV_LOG_ERROR, "Extradata corrupted\n");
+                av_freep(&st->priv_data);
+                return -1;
+            }
+            if (st->codec->codec_id == CODEC_ID_THEORA) {
+                /** KFGSHIFT is the width of the less significant section of the granule position
+                    The less significant section is the frame count since the last keyframe */
+                oggstream->kfgshift = ((oggstream->header[0][40]&3)<<3)|(oggstream->header[0][41]>>5);
+                oggstream->vrev = oggstream->header[0][9];
+                av_log(s, AV_LOG_DEBUG, "theora kfgshift %d, vrev %d\n",
+                       oggstream->kfgshift, oggstream->vrev);
+            }
+        }
+    }
+    for (i = 0; i < 3; i++) {
+        for (j = 0; j < s->nb_streams; j++) {
+            AVStream *st = s->streams[j];
+            OGGStreamContext *oggstream = st->priv_data;
+            if (oggstream && oggstream->header_len[i]) {
+                ogg_write_page(s, oggstream->header[i], oggstream->header_len[i],
+                               0, st->index, i ? 0 : 2); // bos
+            }
+        }
+    }
+    return 0;
+}
+
+static int ogg_write_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    AVStream *st = s->streams[pkt->stream_index];
+    OGGStreamContext *oggstream = st->priv_data;
+    uint8_t *ptr = pkt->data;
+    int ret, size = pkt->size;
+    int64_t granule;
+
+    if (st->codec->codec_id == CODEC_ID_THEORA) {
+        int64_t pts = oggstream->vrev < 1 ? pkt->pts : pkt->pts + pkt->duration;
+        int pframe_count;
+        if (pkt->flags & PKT_FLAG_KEY)
+            oggstream->last_kf_pts = pts;
+        pframe_count = pts - oggstream->last_kf_pts;
+        // prevent frame count from overflow if key frame flag is not set
+        if (pframe_count >= (1<<oggstream->kfgshift)) {
+            oggstream->last_kf_pts += pframe_count;
+            pframe_count = 0;
+        }
+        granule = (oggstream->last_kf_pts<<oggstream->kfgshift) | pframe_count;
+    } else
+        granule = pkt->pts + pkt->duration;
+    oggstream->duration = granule;
+    do {
+        ret = ogg_write_page(s, ptr, size, granule, pkt->stream_index, ptr != pkt->data);
+        ptr  += ret; size -= ret;
+    } while (size > 0 || ret == 255*255); // need to output a last nil page
+
+    return 0;
+}
+
+
+static int ogg_write_trailer(AVFormatContext *s)
+{
+    int i;
+    for (i = 0; i < s->nb_streams; i++) {
+        AVStream *st = s->streams[i];
+        OGGStreamContext *oggstream = st->priv_data;
+        ogg_write_page(s, NULL, 0, oggstream->duration, i, 4); // eos
+        if (st->codec->codec_id == CODEC_ID_FLAC) {
+            av_free(oggstream->header[0]);
+            av_free(oggstream->header[1]);
+        }
+        av_freep(&st->priv_data);
+    }
+    return 0;
+}
+
+AVOutputFormat ogg_muxer = {
+    "ogg",
+    "Ogg format",
+    "application/ogg",
+    "ogg",
+    0,
+    CODEC_ID_FLAC,
+    CODEC_ID_THEORA,
+    ogg_write_header,
+    ogg_write_packet,
+    ogg_write_trailer,
+};
index 647ab0e..06dc500 100644 (file)
@@ -85,6 +85,9 @@ ae3a23a7ea13c92a2909445ca8144dcd *./tests/data/b-libav.aif
 8d117c49d6b210abe783d1b0b897cec7 *./tests/data/b-libav.voc
   32768 ./tests/data/b-libav.voc
 ./tests/data/b-libav.voc CRC=0x49972c8c
+f77f5d44edf767bcc47959787eaaf188 *./tests/data/b-libav.ogg
+23651 ./tests/data/b-libav.ogg
+./tests/data/b-libav.ogg CRC=0x93baa056
 ce356ce2708cb6033ab5d762da93cfd4 *./tests/data/b-libav-yuv420p.yuv
  304128 ./tests/data/b-libav-yuv420p.yuv
 ce356ce2708cb6033ab5d762da93cfd4 *./tests/data/b-libav-yuv422p.yuv
index 7580e86..98b88b9 100755 (executable)
@@ -715,6 +715,9 @@ do_audio_only aif
 # voc
 do_audio_only voc
 
+# ogg
+do_audio_only ogg
+
 ####################
 # pix_fmt conversions
 conversions="yuv420p yuv422p yuv444p yuyv422 yuv410p yuv411p yuvj420p \