changeset 2731:6b43751d7264 libavformat

ogg muxer
author bcoudurier
date Sat, 10 Nov 2007 18:27:03 +0000
parents 7782a46ddbf5
children 2b101e9d25c0
files Makefile allformats.c avformat.h oggenc.c
diffstat 4 files changed, 231 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sat Nov 10 12:05:30 2007 +0000
+++ b/Makefile	Sat Nov 10 18:27:03 2007 +0000
@@ -113,6 +113,7 @@
                                             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
--- a/allformats.c	Sat Nov 10 12:05:30 2007 +0000
+++ b/allformats.c	Sat Nov 10 18:27:03 2007 +0000
@@ -122,7 +122,7 @@
     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);
--- a/avformat.h	Sat Nov 10 12:05:30 2007 +0000
+++ b/avformat.h	Sat Nov 10 18:27:03 2007 +0000
@@ -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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/oggenc.c	Sat Nov 10 18:27:03 2007 +0000
@@ -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,
+};