[xiph-commits] r16353 - in trunk/ffmpeg2theora: . src

j at svn.xiph.org j at svn.xiph.org
Tue Jul 28 13:08:40 PDT 2009


Author: j
Date: 2009-07-28 13:08:40 -0700 (Tue, 28 Jul 2009)
New Revision: 16353

Modified:
   trunk/ffmpeg2theora/ChangeLog
   trunk/ffmpeg2theora/src/ffmpeg2theora.c
   trunk/ffmpeg2theora/src/ffmpeg2theora.h
   trunk/ffmpeg2theora/src/theorautils.c
   trunk/ffmpeg2theora/src/theorautils.h
Log:
add 2 pass encoding

Modified: trunk/ffmpeg2theora/ChangeLog
===================================================================
--- trunk/ffmpeg2theora/ChangeLog	2009-07-28 14:44:15 UTC (rev 16352)
+++ trunk/ffmpeg2theora/ChangeLog	2009-07-28 20:08:40 UTC (rev 16353)
@@ -7,6 +7,8 @@
     - update to ffmpeg trunk and new ffmepg api
     - use new libtheora encoding api
       add new encoding options --soft-target, --buf-delay
+    - two pass encoding, --two-pass
+      or in two calls with --first-pass and --second-pass
 
 0.24 2009-03-12
     - fix to make --max_size --no_upscaling work

Modified: trunk/ffmpeg2theora/src/ffmpeg2theora.c
===================================================================
--- trunk/ffmpeg2theora/src/ffmpeg2theora.c	2009-07-28 14:44:15 UTC (rev 16352)
+++ trunk/ffmpeg2theora/src/ffmpeg2theora.c	2009-07-28 20:08:40 UTC (rev 16353)
@@ -53,6 +53,9 @@
     NULL_FLAG,
     DEINTERLACE_FLAG,
     SOFTTARGET_FLAG,
+    TWOPASS_FLAG,
+    FIRSTPASS_FLAG,
+    SECONDPASS_FLAG,
     OPTIMIZE_FLAG,
     SYNC_FLAG,
     NOAUDIO_FLAG,
@@ -178,7 +181,7 @@
         this->picture_height=0;      // set to 0 to not resize the output
         this->video_quality=-1; // defaults set later
         this->video_bitrate=0;
-        this->keyint=64;
+        this->keyint=0;
         this->force_input_fps.num = -1;
         this->force_input_fps.den = 1;
         this->sync=0;
@@ -764,7 +767,7 @@
                         this->picture_width, this->picture_height, this->pix_fmt,
                         sws_flags, NULL, NULL, NULL
             );
-            if (!info.frontend) {
+            if (!info.frontend && !(info.twopass==3 && info.passno==2)) {
                 if (this->frame_topBand || this->frame_bottomBand ||
                     this->frame_leftBand || this->frame_rightBand ||
                     this->picture_width != (display_width-this->frame_leftBand - this->frame_rightBand) ||
@@ -1021,11 +1024,15 @@
                     info.speed_level = max_speed_level;
                 th_encode_ctl(info.td, TH_ENCCTL_SET_SPLEVEL, &info.speed_level, sizeof(int));
             }
-            if(this->buf_delay >= 0){
+            if(info.passno!=1 && this->buf_delay >= 0){
+                int arg = this->buf_delay;
                 ret = th_encode_ctl(info.td, TH_ENCCTL_SET_RATE_BUFFER,
                                     &this->buf_delay, sizeof(this->buf_delay));
+                if (this->buf_delay != arg)
+                    fprintf(stderr, "Warning: could not set desired buffer delay of %d, using %d instead.\n",
+                                    arg, this->buf_delay);
                 if(ret < 0){
-                    fprintf(stderr, "Warning: could not set desired buffer delay. %d\n", ret);
+                    fprintf(stderr, "Warning: could not set desired buffer delay.\n");
                 }
             }
             /* setting just the granule shift only allows power-of-two keyframe
@@ -1042,16 +1049,58 @@
               if(ret<0)
                 fprintf(stderr, "Could not set encoder flags for --soft-target\n");
                 /* Default buffer control is overridden on two-pass */
-                if(this->buf_delay<0){
-                if((this->keyint*7>>1)>5*this->framerate_new.num/this->framerate_new.den)
-                  arg = this->keyint*7>>1;
-                else
-                  arg = 30*this->framerate_new.num/this->framerate_new.den;
-                ret = th_encode_ctl(info.td, TH_ENCCTL_SET_RATE_BUFFER, &arg,sizeof(arg));
-                if(ret<0)
-                  fprintf(stderr, "Could not set rate control buffer for --soft-target\n");
+                if(!info.twopass && this->buf_delay<0){
+                    if((this->keyint*7>>1)>5*this->framerate_new.num/this->framerate_new.den)
+                        arg = this->keyint*7>>1;
+                    else
+                        arg = 30*this->framerate_new.num/this->framerate_new.den;
+                    ret = th_encode_ctl(info.td, TH_ENCCTL_SET_RATE_BUFFER, &arg,sizeof(arg));
+                    if(ret<0)
+                        fprintf(stderr, "Could not set rate control buffer for --soft-target\n");
               }
             }
+            /* set up two-pass if needed */
+            if(info.passno==1){
+              unsigned char *buffer;
+              int bytes;
+              bytes=th_encode_ctl(info.td,TH_ENCCTL_2PASS_OUT,&buffer,sizeof(buffer));
+              if(bytes<0){
+                fprintf(stderr,"Could not set up the first pass of two-pass mode.\n");
+                fprintf(stderr,"Did you remember to specify an estimated bitrate?\n");
+                exit(1);
+              }
+              /*Perform a seek test to ensure we can overwrite this placeholder data at
+                 the end; this is better than letting the user sit through a whole
+                 encode only to find out their pass 1 file is useless at the end.*/
+              if(fseek(info.twopass_file,0,SEEK_SET)<0){
+                fprintf(stderr,"Unable to seek in two-pass data file.\n");
+                exit(1);
+              }
+              if(fwrite(buffer,1,bytes,info.twopass_file)<bytes){
+                fprintf(stderr,"Unable to write to two-pass data file.\n");
+                exit(1);
+              }
+              fflush(info.twopass_file);
+            }
+            if(info.passno==2){
+              /* enable second pass here, actual data feeding comes later */
+              if(th_encode_ctl(info.td,TH_ENCCTL_2PASS_IN,NULL,0)<0){
+                fprintf(stderr,"Could not set up the second pass of two-pass mode.\n");
+                exit(1);
+              }
+              if(info.twopass==3){
+                /* 'automatic' second pass */
+                if(av_seek_frame( this->context, -1, (int64_t)AV_TIME_BASE*this->start_time, 1)<0){
+                  fprintf(stderr,"Could not rewind video input file for second pass!\n");
+                  exit(1);
+                }
+                if(fseek(info.twopass_file,0,SEEK_SET)<0){
+                  fprintf(stderr,"Unable to seek in two-pass data file.\n");
+                  exit(1);
+                }
+              }
+            }
+
         }
         /* audio settings here */
         info.channels = this->channels;
@@ -1302,7 +1351,8 @@
                     }
                 }
             }
-            if ((audio_eos && !audio_done) || (ret >= 0 && pkt.stream_index == this->audio_index)) {
+            if (info.passno!=1)
+              if ((audio_eos && !audio_done) || (ret >= 0 && pkt.stream_index == this->audio_index)) {
                 this->pts_offset = (double) pkt.pts / AV_TIME_BASE -
                     (double) this->sample_count / this->sample_rate;
                 while((audio_eos && !audio_done) || avpkt.size > 0 ) {
@@ -1352,6 +1402,7 @@
                 }
             }
 
+            if (info.passno!=1)
             if (!this->disable_subtitles && subtitles_enabled[pkt.stream_index] && is_supported_subtitle_stream(this, pkt.stream_index)) {
               AVStream *stream=this->context->streams[pkt.stream_index];
               AVCodecContext *enc = stream->codec;
@@ -1425,7 +1476,7 @@
             }
 
             /* if we have subtitles starting before then, add it */
-            if (info.with_kate) {
+            if (info.passno!=1 && info.with_kate) {
                 double avtime = info.audio_only ? info.audiotime :
                     info.video_only ? info.videotime :
                     info.audiotime < info.videotime ? info.audiotime : info.videotime;
@@ -1446,8 +1497,8 @@
             }
 
             /* flush out the file */
-            
             oggmux_flush (&info, video_eos + audio_eos);
+
             av_free_packet (&pkt);
         } while (ret >= 0 && !(audio_done && video_done));
 
@@ -1651,6 +1702,19 @@
         "                         higher/smoother overall. Soft target also\n"
         "                         allows an optional -v setting to specify\n"
         "                         a minimum allowed quality.\n\n"
+        "      --two-pass         Compress input using two-pass rate control\n"
+        "                         This option requires that the input to the\n"
+        "                         to the encoder is seekable and performs\n"
+        "                         both passes automatically.\n\n"
+        "      --first-pass <filename> Perform first-pass of a two-pass rate\n"
+        "                         controlled encoding, saving pass data to\n"
+        "                         <filename> for a later second pass\n\n"
+        "      --second-pass <filename> Perform second-pass of a two-pass rate\n"
+        "                         controlled encoding, reading first-pass\n"
+        "                         data from <filename>.  The first pass\n"
+        "                         data must come from a first encoding pass\n"
+        "                         using identical input video to work\n"
+        "                         properly.\n\n"
         "      --optimize         optimize video output filesize (slower) (same as speedlevel 0)\n"
         "      --speedlevel       [0 2] encoding is faster with higher values the cost is quality and bandwidth\n"
 
@@ -1800,7 +1864,9 @@
         {"videobitrate",required_argument,NULL,'V'},
         {"audioquality",required_argument,NULL,'a'},
         {"audiobitrate",required_argument,NULL,'A'},
-        {"soft-target",0,&flag,SOFTTARGET_FLAG},
+        {"two-pass",0,&flag,TWOPASS_FLAG},
+        {"first-pass",required_argument,&flag,FIRSTPASS_FLAG},
+        {"second-pass",required_argument,&flag,SECONDPASS_FLAG},
         {"keyint",required_argument,NULL,'K'},
         {"buf-delay",required_argument,NULL,'d'},
         {"deinterlace",0,&flag,DEINTERLACE_FLAG},
@@ -1887,6 +1953,33 @@
                             convert->soft_target = 1;
                             flag = -1;
                             break;
+                        case TWOPASS_FLAG:
+                            info.twopass = 3;
+                            info.twopass_file = tmpfile();
+                            if(!info.twopass_file){
+                                fprintf(stderr,"Unable to open temporary file for twopass data\n");
+                                exit(1);
+                            }
+                            flag = -1;
+                            break;
+                        case FIRSTPASS_FLAG:
+                            info.twopass = 1;
+                            info.twopass_file = fopen(optarg,"wb");
+                            if(!info.twopass_file){
+                                fprintf(stderr,"Unable to open \'%s\' for twopass data\n", optarg);
+                                exit(1);
+                            }
+                            flag = -1;
+                            break;
+                        case SECONDPASS_FLAG:
+                            info.twopass = 2;
+                            info.twopass_file = fopen(optarg,"rb");
+                            if(!info.twopass_file){
+                                fprintf(stderr,"Unable to open \'%s\' for twopass data\n", optarg);
+                                exit(1);
+                            }
+                            flag = -1;
+                            break;
                         case PP_FLAG:
                             if (!strcmp(optarg, "help")) {
                                 fprintf(stdout, "%s", pp_help);
@@ -2256,6 +2349,12 @@
         exit(1);
     }
 
+    if(convert->keyint <= 0) {
+        /*Use a default keyframe frequency of 64 for 1-pass (streaming) mode, and
+           256 for two-pass mode.*/
+        convert->keyint = info.twopass?256:64;
+    }
+
     if (convert->soft_target) {
         if (convert->video_bitrate <= 0) {
           fprintf(stderr,"Soft rate target (--soft-tagret) requested without a bitrate (-V).\n");
@@ -2320,13 +2419,15 @@
                     info.outfile = stdout;
                 }
                 else {
-                    info.outfile = fopen(outputfile_name,"wb");
+                    if(info.twopass!=1)
+                        info.outfile = fopen(outputfile_name,"wb");
                 }
 #else
                 if (!strcmp(outputfile_name,"-")) {
                     snprintf(outputfile_name,sizeof(outputfile_name),"/dev/stdout");
                 }
-                info.outfile = fopen(outputfile_name,"wb");
+                if(info.twopass!=1)
+                    info.outfile = fopen(outputfile_name,"wb");
 #endif
                 if (output_json) {
                     if (using_stdin) {
@@ -2358,7 +2459,7 @@
 
                 convert->pts_offset =
                     (double) convert->context->start_time / AV_TIME_BASE;
-                if (!info.outfile) {
+                if (info.twopass!=1 && !info.outfile) {
                     if (info.frontend)
                         fprintf(info.frontend, "\"{result\": \"Unable to open output file.\"}\n");
                     else
@@ -2368,7 +2469,9 @@
                 if (convert->context->duration != AV_NOPTS_VALUE) {
                     info.duration = (double)convert->context->duration / AV_TIME_BASE;
                 }
-                ff2theora_output(convert);
+                for(info.passno=(info.twopass==3?1:info.twopass);info.passno<=(info.twopass==3?2:info.twopass);info.passno++){
+                    ff2theora_output(convert);
+                }
                 convert->audio_index = convert->video_index = -1;
             }
             else{

Modified: trunk/ffmpeg2theora/src/ffmpeg2theora.h
===================================================================
--- trunk/ffmpeg2theora/src/ffmpeg2theora.h	2009-07-28 14:44:15 UTC (rev 16352)
+++ trunk/ffmpeg2theora/src/ffmpeg2theora.h	2009-07-28 20:08:40 UTC (rev 16353)
@@ -103,6 +103,7 @@
     int uv_lut_used;
     unsigned char y_lut[256];
     unsigned char uv_lut[256];
+
 }
 *ff2theora;
 

Modified: trunk/ffmpeg2theora/src/theorautils.c
===================================================================
--- trunk/ffmpeg2theora/src/theorautils.c	2009-07-28 14:44:15 UTC (rev 16352)
+++ trunk/ffmpeg2theora/src/theorautils.c	2009-07-28 20:08:40 UTC (rev 16353)
@@ -71,6 +71,10 @@
     info->k_page=0;
 #endif
 
+    info->twopass_file = NULL;
+    info->twopass = 0;
+    info->passno = 0;
+
     info->with_kate = 0;
     info->n_kate_streams = 0;
     info->kate_streams = NULL;
@@ -254,7 +258,6 @@
         ogg_stream_init (&info->to, rand ());    /* oops, add one ot the above */
     }
     /* init theora done */
-
     /* initialize Vorbis too, if we have audio. */
     if (!info->video_only) {
         int ret;
@@ -285,7 +288,7 @@
     /* audio init done */
 
     /* initialize kate if we have subtitles */
-    if (info->with_kate) {
+    if (info->with_kate && info->passno!=1) {
 #ifdef HAVE_KATE
         int ret, n;
         for (n=0; n<info->n_kate_streams; ++n) {
@@ -309,7 +312,7 @@
 
     /* first packet should be skeleton fishead packet, if skeleton is used */
 
-    if (info->with_skeleton) {
+    if (info->with_skeleton && info->passno!=1) {
         ogg_stream_init (&info->so, rand());
         add_fishead_packet (info);
         if (ogg_stream_pageout (&info->so, &og) != 1) {
@@ -337,13 +340,15 @@
           fprintf(stderr, "Internal Theora library error.\n");
           exit(1);
         }
-        ogg_stream_packetin(&info->to, &op);
-        if(ogg_stream_pageout(&info->to, &og) != 1) {
-            fprintf(stderr, "Internal Ogg library error.\n");
-            exit(1);
+        if(info->passno!=1){
+            ogg_stream_packetin(&info->to, &op);
+            if(ogg_stream_pageout(&info->to, &og) != 1) {
+                fprintf(stderr, "Internal Ogg library error.\n");
+                exit(1);
+            }
+            fwrite(og.header, 1, og.header_len, info->outfile);
+            fwrite(og.body, 1, og.body_len, info->outfile);
         }
-        fwrite(og.header, 1, og.header_len, info->outfile);
-        fwrite(og.body, 1, og.body_len, info->outfile);
 
         /* create the remaining theora headers */
         for(;;){
@@ -353,10 +358,11 @@
             exit(1);
           }
           else if(!ret) break;
-          ogg_stream_packetin(&info->to, &op);
+          if(info->passno!=1)
+            ogg_stream_packetin(&info->to, &op);
         }
     }
-    if (!info->video_only) {
+    if (!info->video_only && info->passno!=1) {
         ogg_packet header;
         ogg_packet header_comm;
         ogg_packet header_code;
@@ -378,7 +384,7 @@
     }
 
 #ifdef HAVE_KATE
-    if (info->with_kate) {
+    if (info->with_kate && info->passno!=1) {
         int n;
         for (n=0; n<info->n_kate_streams; ++n) {
             oggmux_kate_stream *ks=info->kate_streams+n;
@@ -406,7 +412,7 @@
 #endif
 
     /* output the appropriate fisbone packets */
-    if (info->with_skeleton) {
+    if (info->with_skeleton && info->passno!=1) {
         add_fisbone_packet (info);
         while (1) {
             int result = ogg_stream_flush (&info->so, &og);
@@ -425,7 +431,7 @@
     /* Flush the rest of our headers. This ensures
      * the actual data in each stream will start
      * on a new page, as per spec. */
-    while (1 && !info->audio_only) {
+    while (1 && !info->audio_only && info->passno!=1) {
         int result = ogg_stream_flush (&info->to, &og);
         if (result < 0) {
             /* can't get here */
@@ -437,7 +443,7 @@
         fwrite (og.header, 1, og.header_len, info->outfile);
         fwrite (og.body, 1, og.body_len, info->outfile);
     }
-    while (1 && !info->video_only) {
+    while (1 && !info->video_only && info->passno!=1) {
         int result = ogg_stream_flush (&info->vo, &og);
         if (result < 0) {
             /* can't get here */
@@ -450,7 +456,7 @@
         fwrite (og.body, 1, og.body_len, info->outfile);
     }
 #ifdef HAVE_KATE
-    if (info->with_kate) {
+    if (info->with_kate && info->passno!=1) {
         int n;
         for (n=0; n<info->n_kate_streams; ++n) {
             oggmux_kate_stream *ks=info->kate_streams+n;
@@ -470,7 +476,7 @@
     }
 #endif
 
-    if (info->with_skeleton) {
+    if (info->with_skeleton && info->passno!=1) {
         int result;
 
         /* build and add the e_o_s packet */
@@ -505,12 +511,79 @@
  */
 void oggmux_add_video (oggmux_info *info, th_ycbcr_buffer ycbcr, int e_o_s) {
     ogg_packet op;
-    int r;
+    int r, ret;
+
+    if(info->passno==2){
+        for(;;){
+          static unsigned char buffer[80];
+          static int buf_pos;
+          int bytes;
+          /*Ask the encoder how many bytes it would like.*/
+          bytes=th_encode_ctl(info->td,TH_ENCCTL_2PASS_IN,NULL,0);
+          if(bytes<0){
+            fprintf(stderr,"Error submitting pass data in second pass.\n");
+            exit(1);
+          }
+          /*If it's got enough, stop.*/
+          if(bytes==0)break;
+          /*Read in some more bytes, if necessary.*/
+          if(bytes>80-buf_pos)bytes=80-buf_pos;
+          if(bytes>0&&fread(buffer+buf_pos,1,bytes,info->twopass_file)<bytes){
+            fprintf(stderr,"Could not read frame data from two-pass data file!\n");
+            exit(1);
+          }
+          /*And pass them off.*/
+          ret=th_encode_ctl(info->td,TH_ENCCTL_2PASS_IN,buffer,bytes);
+          if(ret<0){
+            fprintf(stderr,"Error submitting pass data in second pass.\n");
+            exit(1);
+          }
+          /*If the encoder consumed the whole buffer, reset it.*/
+          if(ret>=bytes)buf_pos=0;
+          /*Otherwise remember how much it used.*/
+          else buf_pos+=ret;
+        }
+    }
+
     th_encode_ycbcr_in(info->td, ycbcr);
+    /* in two-pass mode's first pass we need to extract and save the pass data */
+    if(info->passno==1){
+
+        unsigned char *buffer;
+        int bytes = th_encode_ctl(info->td, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer));
+        if(bytes<0){
+          fprintf(stderr,"Could not read two-pass data from encoder.\n");
+          exit(1);
+        }
+        if(fwrite(buffer,1,bytes,info->twopass_file)<bytes){
+          fprintf(stderr,"Unable to write to two-pass data file.\n");
+          exit(1);
+        }
+        fflush(info->twopass_file);
+    }
+
     while (th_encode_packetout (info->td, e_o_s, &op) > 0) {
         ogg_stream_packetin (&info->to, &op);
         info->v_pkg++;
     }
+    if(info->passno==1 && e_o_s){
+        /* need to read the final (summary) packet */
+        unsigned char *buffer;
+        int bytes = th_encode_ctl(info->td, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer));
+        if(bytes<0){
+          fprintf(stderr,"Could not read two-pass summary data from encoder.\n");
+          exit(1);
+        }
+        if(fseek(info->twopass_file,0,SEEK_SET)<0){
+          fprintf(stderr,"Unable to seek in two-pass data file.\n");
+          exit(1);
+        }
+        if(fwrite(buffer,1,bytes,info->twopass_file)<bytes){
+          fprintf(stderr,"Unable to write to two-pass data file.\n");
+          exit(1);
+        }
+        fflush(info->twopass_file);
+    }
 }
 
 /**
@@ -641,7 +714,17 @@
     int remaining_seconds = (long) remaining % 60;
     int remaining_minutes = ((long) remaining / 60) % 60;
     int remaining_hours = (long) remaining / 3600;
-    if (timebase - last > 0.5) {
+
+    if (info->passno==1) {
+        remaining = time(NULL) - info->start_time;
+        remaining_seconds = (long) remaining % 60;
+        remaining_minutes = ((long) remaining / 60) % 60;
+        remaining_hours = (long) remaining / 3600;
+        fprintf (stderr,"\r  Scanning video first pass, time elapsed: %02d:%02d:%02d ",
+            remaining_hours, remaining_minutes, remaining_seconds
+        );
+    }
+    else if (timebase - last > 0.5) {
         last = timebase;
         if (info->frontend) {
             fprintf(info->frontend, "{\"duration\": %lf, \"position\": %.02lf, \"audio_kbps\":  %d, \"video_kbps\": %d, \"remaining\": %.02lf}\n",
@@ -652,7 +735,7 @@
             );
             fflush (info->frontend);
         }
-        else if (timebase > 0 ) {
+        else if (timebase > 0) {
             if (!remaining) {
                 remaining = time(NULL) - info->start_time;
                 remaining_seconds = (long) remaining % 60;
@@ -665,11 +748,12 @@
                 );
             }
             else {
-                fprintf (stderr,"\r  %d:%02d:%02d.%02d audio: %dkbps video: %dkbps, ET: %02d:%02d:%02d, est. size: %.01lf MB ",
+                fprintf (stderr,"\r  %d:%02d:%02d.%02d audio: %dkbps video: %dkbps, ET: %02d:%02d:%02d, est. size: %.01lf MB   ",
                     hours, minutes, seconds, hundredths,
                     info->akbps, info->vkbps,
                     remaining_hours, remaining_minutes, remaining_seconds,
-                    estimated_size(info, timebase)
+                    estimated_size(info, timebase),
+                    info->passno
                 );
             }
         }
@@ -777,6 +861,10 @@
     ogg_page og;
     int best;
 
+    if (info->passno==1) {
+        print_stats(info, info->videotime);
+        return;
+    }
     /* flush out the ogg pages to info->outfile */
     while (1) {
         /* Get pages for both streams, if not already present, and if available.*/
@@ -936,8 +1024,10 @@
     if (info->with_skeleton)
         ogg_stream_clear (&info->so);
 
-    if (info->outfile && info->outfile != stdout)
+    if (info->passno!=1 && info->outfile && info->outfile != stdout)
         fclose (info->outfile);
+    if(info->twopass_file)
+        fclose(info->twopass_file);
 
     if (info->videopage)
         free(info->videopage);

Modified: trunk/ffmpeg2theora/src/theorautils.h
===================================================================
--- trunk/ffmpeg2theora/src/theorautils.h	2009-07-28 14:44:15 UTC (rev 16352)
+++ trunk/ffmpeg2theora/src/theorautils.h	2009-07-28 20:08:40 UTC (rev 16353)
@@ -125,6 +125,10 @@
     int k_page;
 #endif
 
+    FILE *twopass_file;
+    int twopass;
+    int passno;
+
     int n_kate_streams;
     oggmux_kate_stream *kate_streams;
 }
@@ -140,4 +144,5 @@
 extern void oggmux_flush (oggmux_info *info, int e_o_s);
 extern void oggmux_close (oggmux_info *info);
 
+
 #endif



More information about the commits mailing list