diff mpeg.c @ 335:b0ac206f232d libavformat

better and simpler logic for MPEG muxing - fixed rare MPEG muxing PTS generation bug (stuffing is added in such rare cases) - fixed AC3 payload size generation - generate correct AC3 frame header (need spec checking)
author bellard
date Tue, 16 Dec 2003 11:25:30 +0000
parents 4530681af424
children d75fd4c6ab62
line wrap: on
line diff
--- a/mpeg.c	Tue Dec 16 11:21:25 2003 +0000
+++ b/mpeg.c	Tue Dec 16 11:25:30 2003 +0000
@@ -24,6 +24,8 @@
 typedef struct {
     uint8_t buffer[MAX_PAYLOAD_SIZE];
     int buffer_ptr;
+    int nb_frames;    /* number of starting frame encountered (AC3) */
+    int frame_start_offset; /* starting offset of the frame + 1 (0 if none) */
     uint8_t id;
     int max_buffer_size; /* in bytes */
     int packet_number;
@@ -33,10 +35,10 @@
 
 typedef struct {
     int packet_size; /* required packet size */
-    int packet_data_max_size; /* maximum data size inside a packet */
     int packet_number;
     int pack_header_freq;     /* frequency (in packets^-1) at which we send pack headers */
     int system_header_freq;
+    int system_header_size;
     int mux_rate; /* bitrate in units of 50 bytes/s */
     /* stream info */
     int audio_bound;
@@ -166,6 +168,25 @@
     return size;
 }
 
+static int get_system_header_size(AVFormatContext *ctx)
+{
+    int buf_index, i, private_stream_coded;
+    StreamInfo *stream;
+
+    buf_index = 12;
+    private_stream_coded = 0;
+    for(i=0;i<ctx->nb_streams;i++) {
+        stream = ctx->streams[i]->priv_data;
+        if (stream->id < 0xc0) {
+            if (private_stream_coded)
+                continue;
+            private_stream_coded = 1;
+        }
+        buf_index += 3;
+    }
+    return buf_index;
+}
+
 static int mpeg_mux_init(AVFormatContext *ctx)
 {
     MpegMuxContext *s = ctx->priv_data;
@@ -182,10 +203,6 @@
     else
         s->packet_size = 2048;
         
-    /* startcode(4) + length(2) + flags(1) */
-    s->packet_data_max_size = s->packet_size - 7;
-    if (s->is_mpeg2)
-        s->packet_data_max_size -= 2;
     s->audio_bound = 0;
     s->video_bound = 0;
     mpa_id = AUDIO_ID;
@@ -260,6 +277,7 @@
         stream->start_pts = AV_NOPTS_VALUE;
         stream->start_dts = AV_NOPTS_VALUE;
     }
+    s->system_header_size = get_system_header_size(ctx);
     s->last_scr = 0;
     return 0;
  fail:
@@ -279,6 +297,50 @@
     put_be16(pb, (uint16_t)((((timestamp) & 0x7fff) << 1) | 1));
 }
 
+
+/* return the exact available payload size for the next packet for
+   stream 'stream_index'. 'pts' and 'dts' are only used to know if
+   timestamps are needed in the packet header. */
+static int get_packet_payload_size(AVFormatContext *ctx, int stream_index,
+                                   int64_t pts, int64_t dts)
+{
+    MpegMuxContext *s = ctx->priv_data;
+    int buf_index;
+    StreamInfo *stream;
+
+    buf_index = 0;
+    if (((s->packet_number % s->pack_header_freq) == 0)) {
+        /* pack header size */
+        if (s->is_mpeg2) 
+            buf_index += 14;
+        else
+            buf_index += 12;
+        if ((s->packet_number % s->system_header_freq) == 0)
+            buf_index += s->system_header_size;
+    }
+
+    /* packet header size */
+    buf_index += 6;
+    if (s->is_mpeg2)
+        buf_index += 3;
+    if (pts != AV_NOPTS_VALUE) {
+        if (dts != AV_NOPTS_VALUE)
+            buf_index += 5 + 5;
+        else
+            buf_index += 5;
+    } else {
+        if (!s->is_mpeg2)
+            buf_index++;
+    }
+    
+    stream = ctx->streams[stream_index]->priv_data;
+    if (stream->id < 0xc0) {
+        /* AC3 private data header */
+        buf_index += 4;
+    }
+    return s->packet_size - buf_index; 
+}
+
 /* flush the packet on stream stream_index */
 static void flush_packet(AVFormatContext *ctx, int stream_index, 
                          int64_t pts, int64_t dts, int64_t scr)
@@ -286,7 +348,8 @@
     MpegMuxContext *s = ctx->priv_data;
     StreamInfo *stream = ctx->streams[stream_index]->priv_data;
     uint8_t *buf_ptr;
-    int size, payload_size, startcode, id, len, stuffing_size, i, header_len;
+    int size, payload_size, startcode, id, stuffing_size, i, header_len;
+    int packet_size;
     uint8_t buffer[128];
     
     id = stream->id;
@@ -325,20 +388,21 @@
             header_len++;
     }
 
-    payload_size = s->packet_size - (size + 6 + header_len);
+    packet_size = s->packet_size - (size + 6);
+    payload_size = packet_size - header_len;
     if (id < 0xc0) {
         startcode = PRIVATE_STREAM_1;
         payload_size -= 4;
     } else {
         startcode = 0x100 + id;
     }
+
     stuffing_size = payload_size - stream->buffer_ptr;
     if (stuffing_size < 0)
         stuffing_size = 0;
-
     put_be32(&ctx->pb, startcode);
 
-    put_be16(&ctx->pb, payload_size + header_len);
+    put_be16(&ctx->pb, packet_size);
     /* stuffing */
     for(i=0;i<stuffing_size;i++)
         put_byte(&ctx->pb, 0xff);
@@ -377,10 +441,8 @@
     if (startcode == PRIVATE_STREAM_1) {
         put_byte(&ctx->pb, id);
         if (id >= 0x80 && id <= 0xbf) {
-            /* XXX: need to check AC3 spec */
-            put_byte(&ctx->pb, 1);
-            put_byte(&ctx->pb, 0);
-            put_byte(&ctx->pb, 2);
+            put_byte(&ctx->pb, stream->nb_frames);
+            put_be16(&ctx->pb, stream->frame_start_offset);
         }
     }
 
@@ -388,15 +450,10 @@
     put_buffer(&ctx->pb, stream->buffer, payload_size - stuffing_size);
     put_flush_packet(&ctx->pb);
     
-    /* preserve remaining data */
-    len = stream->buffer_ptr - payload_size;
-    if (len < 0) 
-        len = 0;
-    memmove(stream->buffer, stream->buffer + stream->buffer_ptr - len, len);
-    stream->buffer_ptr = len;
-
     s->packet_number++;
     stream->packet_number++;
+    stream->nb_frames = 0;
+    stream->frame_start_offset = 0;
 }
 
 static int mpeg_mux_write_packet(AVFormatContext *ctx, int stream_index,
@@ -405,8 +462,8 @@
     MpegMuxContext *s = ctx->priv_data;
     AVStream *st = ctx->streams[stream_index];
     StreamInfo *stream = st->priv_data;
-    int64_t dts;
-    int len;
+    int64_t dts, new_start_pts, new_start_dts;
+    int len, avail_size;
 
     /* XXX: system clock should be computed precisely, especially for
        CBR case. The current mode gives at least something coherent */
@@ -422,28 +479,53 @@
     dts = AV_NOPTS_VALUE;
 
     /* we assume here that pts != AV_NOPTS_VALUE */
+    new_start_pts = stream->start_pts;
+    new_start_dts = stream->start_dts;
+    
     if (stream->start_pts == AV_NOPTS_VALUE) {
-        stream->start_pts = pts;
-        stream->start_dts = dts;
+        new_start_pts = pts;
+        new_start_dts = dts;
     }
+    avail_size = get_packet_payload_size(ctx, stream_index,
+                                         new_start_pts, 
+                                         new_start_dts);
+    if (stream->buffer_ptr >= avail_size) {
+        /* unlikely case: outputing the pts or dts increase the packet
+           size so that we cannot write the start of the next
+           packet. In this case, we must flush the current packet with
+           padding */
+        flush_packet(ctx, stream_index,
+                     stream->start_pts, stream->start_dts, s->last_scr);
+        stream->buffer_ptr = 0;
+    }
+    stream->start_pts = new_start_pts;
+    stream->start_dts = new_start_dts;
+    stream->nb_frames++;
+    if (stream->frame_start_offset == 0)
+        stream->frame_start_offset = stream->buffer_ptr;
     while (size > 0) {
-        len = s->packet_data_max_size - stream->buffer_ptr;
+        avail_size = get_packet_payload_size(ctx, stream_index,
+                                             stream->start_pts, 
+                                             stream->start_dts);
+        len = avail_size - stream->buffer_ptr;
         if (len > size)
             len = size;
         memcpy(stream->buffer + stream->buffer_ptr, buf, len);
         stream->buffer_ptr += len;
         buf += len;
         size -= len;
-        while (stream->buffer_ptr >= s->packet_data_max_size) {
-            /* output the packet */
+        if (stream->buffer_ptr >= avail_size) {
+            /* if packet full, we send it now */
             flush_packet(ctx, stream_index,
                          stream->start_pts, stream->start_dts, s->last_scr);
+            stream->buffer_ptr = 0;
             /* Make sure only the FIRST pes packet for this frame has
                a timestamp */
             stream->start_pts = AV_NOPTS_VALUE;
             stream->start_dts = AV_NOPTS_VALUE;
         }
     }
+
     return 0;
 }
 
@@ -456,8 +538,11 @@
     /* flush each packet */
     for(i=0;i<ctx->nb_streams;i++) {
         stream = ctx->streams[i]->priv_data;
-        while (stream->buffer_ptr > 0) {
-            flush_packet(ctx, i, AV_NOPTS_VALUE, AV_NOPTS_VALUE, s->last_scr);
+        if (stream->buffer_ptr > 0) {
+            /* NOTE: we can always write the remaining data as it was
+               tested before in mpeg_mux_write_packet() */
+            flush_packet(ctx, i, stream->start_pts, stream->start_dts, 
+                         s->last_scr);
         }
     }
 
@@ -755,7 +840,7 @@
     }
     if (startcode >= 0x1e0 && startcode <= 0x1ef) {
         type = CODEC_TYPE_VIDEO;
-        codec_id = CODEC_ID_MPEG1VIDEO;
+        codec_id = CODEC_ID_MPEG2VIDEO;
     } else if (startcode >= 0x1c0 && startcode <= 0x1df) {
         type = CODEC_TYPE_AUDIO;
         codec_id = CODEC_ID_MP2;
@@ -994,7 +1079,7 @@
     "vob",
     sizeof(MpegMuxContext),
     CODEC_ID_MP2,
-    CODEC_ID_MPEG1VIDEO,
+    CODEC_ID_MPEG2VIDEO,
     mpeg_mux_init,
     mpeg_mux_write_packet,
     mpeg_mux_end,