[PATCH] enable mplex to multiplex subtitle streams

Roland Stadtmueller <[email protected]> Mon, 8 Mar 2010 21:27:20 +0100
Newsgroups gmane.comp.video.mjpeg.devel
Message-ID <[email protected]>
Hello,

you may want to consider the attached patch for muxing subtitle streams for 
mplex:

The patch enables mplex to work off private stream 1 (ps1) subtitles as found 
when extracted from a dvd.

e.g.:$  tcextract -i chapter_5.vob  -a 0x21 -x ps1 > chp5_1.ps1

$ mplex -d 1000:32 -f 8 -o test.vob chp5.mpv chp5_0a.mpa chp5_0.ps1

The "-d 1000:32" parameter forces the ps1 stream to be delayed for 1000 90kHz 
ticks before the first subtitle stream starts, and the mux'ed stream id is 32 
instead of 33 (0x21)(syntax: -d <dts-shift>:<subtitle-streamid>] [, 
<dts>[:...] ...], see help for details)

You can use the patch as alternative to spumux - if you know how to provide an 
alternative palette to dvdauthor (-p parameter);-). 

This patch can NOT replace the menu generation feature of spumux and  cannot 
(yet) work with more common subtitle formats like vobsub and similar.

Regards,
   Roland

------------------------------------------------------------------------------
Download Intel&#174; Parallel Studio Eval
Try the new software tools for yourself. Speed compiling, find bugs
proactively, and fine-tune applications for parallel performance.
See why Intel Parallel Studio got high marks during beta.
http://p.sf.net/sfu/intel-sw-dev

_______________________________________________
Mjpeg-developer mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mjpeg-developer
subtitles.diff (text/x-patch, 19.7 KB)
diff -Naur mplex/Makefile.am mplex.new/Makefile.am
--- mplex/Makefile.am	2007-02-20 03:14:58.000000000 +0100
+++ mplex.new/Makefile.am	2010-03-08 20:12:36.000000000 +0100
@@ -41,6 +41,7 @@
 	systems.cpp \
 	videostrm_in.cpp \
 	videostrm_out.cpp \
+	subpstream.cpp \
 	$(ZALPHA_FILES)
 
 libmplex_includedir = $(pkgincludedir)/mplex
diff -Naur mplex/ac3strm_in.cpp mplex.new/ac3strm_in.cpp
--- mplex/ac3strm_in.cpp	2004-07-15 07:03:05.000000000 +0200
+++ mplex.new/ac3strm_in.cpp	2010-03-08 06:12:44.000000000 +0100
@@ -236,9 +236,9 @@
             
         header_skip = 5;        // Initially skipped past  5 bytes of header 
 
-	num_frames++;
+		num_frames++;
         access_unit.start = AU_start;
-	access_unit.length = framesize;
+		access_unit.length = framesize;
         mjpeg_info( "AC3 frame size = %d", framesize );
         bit_rate = ac3_bitrate_index[framesize_code>>1];
 		samples_per_second = ac3_frequency[frequency];
@@ -288,6 +288,9 @@
         bs.SeekFwdBits(skip);
 		prev_offset = AU_start;
 		AU_start = bs.bitcount();
+        
+				 
+
         if( AU_start - prev_offset != access_unit.length*8 )
         {
             mjpeg_warn( "Discarding incomplete final frame AC3 stream %d!",
@@ -382,6 +385,7 @@
 AC3Stream::ReadPacketPayload(uint8_t *dst, unsigned int to_read)
 {
     bitcount_t read_start = bs.GetBytePos();
+
     // Remember to change StreamHeaderLen if you write a different
     // length re-using this code...
     unsigned int bytes_read = bs.GetBytes( dst+4, to_read-4 );
diff -Naur mplex/audiostrm.hpp mplex.new/audiostrm.hpp
--- mplex/audiostrm.hpp	2005-10-13 04:49:23.000000000 +0200
+++ mplex.new/audiostrm.hpp	2010-03-08 06:20:38.000000000 +0100
@@ -182,9 +182,10 @@
 class SUBPStream : public AudioStream
 {
 public:   
-    SUBPStream(IBitStream &ibs,Multiplexor &into );
+    SUBPStream(IBitStream &ibs,SubtitleStreamParams* params,Multiplexor &into );
     virtual void Init(const int stream_num);
     virtual void Close();
+    static bool Probe(IBitStream &bs );
     // TODO: rough and ready measure...
     virtual unsigned int NominalBitRate() {return 50*1024;}
     virtual unsigned int ReadPacketPayload(uint8_t *dst, unsigned int to_read);
@@ -192,9 +193,8 @@
     
 
 private:
-	void OutputHdrInfo();
 	virtual void FillAUbuffer(unsigned int frames_to_buffer);
-    
+    bool ParseAUBitwise();
     static const unsigned int default_buffer_size;
 
 	/* State variables for scanning source bit-stream */
@@ -202,8 +202,10 @@
     unsigned int samples_per_second;
     unsigned int bit_rate;
     unsigned int stream_num;
-    unsigned int frequency	;
     unsigned int num_frames;
+    int64_t     initial_offset;  // DTS of first Subp.
+    SubtitleStreamParams* parms;
+    int8_t sub_stream_id; // substream_id
 }; 	
 
 
diff -Naur mplex/interact.cpp mplex.new/interact.cpp
--- mplex/interact.cpp	2006-01-15 15:19:23.000000000 +0100
+++ mplex.new/interact.cpp	2010-03-08 06:12:44.000000000 +0100
@@ -45,6 +45,7 @@
     "LPCM audio",
     "DTS audio",
     "MPEG video",
+    "MPEG Subtitle",
     "Z Alpha channel"
 };
 
@@ -79,6 +80,7 @@
     run_in_frames = 0;      // Select default run-in...
     audio_tracks = 0;
     video_tracks = 0;
+    subtitle_tracks = 0;
     lpcm_tracks = 0;
 #ifdef ZALPHA
     z_alpha_tracks = 0;
@@ -186,6 +188,18 @@
         }
 
         bs->UndoChanges( undo );
+
+        if( SUBPStream::Probe( *bs ) )
+        {
+            mjpeg_info ("File %s looks like an Subpicture stream.",
+                        bs->StreamName());
+            bs->UndoChanges( undo );
+            streams.push_back( new JobStream( bs, SUBP_STREAM) );
+			++subtitle_tracks;
+            continue;
+        }
+
+
 #ifdef ZALPHA
         if( ZAlphaStream::Probe( *bs ) )
         {
@@ -222,6 +236,11 @@
 	{
 		lpcm_param.push_back(LpcmParams::Default(mux_format));
 	}
+	for( i = subtitle_params.size(); i < subtitle_tracks; ++i )
+	{
+		subtitle_params.push_back(SubtitleStreamParams::Default(mux_format));
+	}
+
 
 	//
 	// Set standard values if the selected profile implies this...
@@ -234,9 +253,10 @@
 		}
 	}
 
-	mjpeg_info( "Found %d audio streams and %d video streams",
+	mjpeg_info( "Found %d audio streams, %d video streams and %d subtitle streams",
                 audio_tracks,
-				video_tracks
+				video_tracks,
+				subtitle_tracks
         );
         
 }
diff -Naur mplex/interact.hpp mplex.new/interact.hpp
--- mplex/interact.hpp	2006-02-01 23:23:01.000000000 +0100
+++ mplex.new/interact.hpp	2010-03-08 06:23:47.000000000 +0100
@@ -54,6 +54,7 @@
   int video_offset;             // A/V sync offset. Always one 0 and the
                                 // other positive. Specified in 
   int audio_offset;             // MPEG-2 CLOCKS: 1/(90000*300)-th sec
+
   unsigned int sector_size;
   bool VBR;                     // Force VBR even if profile suggests CBR
   bool CBR;                     // Force CBR even if profile suggests VBR
@@ -92,7 +93,8 @@
     AC3_AUDIO,
     LPCM_AUDIO,
     DTS_AUDIO,
-    MPEG_VIDEO
+    MPEG_VIDEO,
+    SUBP_STREAM,
 #ifdef ZALPHA
     ,
     Z_ALPHA
@@ -129,8 +131,10 @@
   vector<JobStream *> streams;
   vector<LpcmParams *> lpcm_param;
   vector<VideoParams *> video_param;
+  vector<SubtitleStreamParams*> subtitle_params;
   unsigned int audio_tracks;
   unsigned int video_tracks;
+  unsigned int subtitle_tracks;
   unsigned int lpcm_tracks;
 #ifdef ZALPHA
   unsigned int z_alpha_tracks;
diff -Naur mplex/main.cpp mplex.new/main.cpp
--- mplex/main.cpp	2008-11-30 06:43:46.000000000 +0100
+++ mplex.new/main.cpp	2010-03-08 06:23:57.000000000 +0100
@@ -252,7 +252,7 @@
 	bool ParseLpcmOpt( const char *optarg );
 	bool ParseWorkaroundOpt( const char *optarg );
 	bool ParseTimeOffset( const char *optarg );
-	
+	bool ParseSubtitleOptions( const char *optarg  );
 
 	static const char short_options[];
 
@@ -263,7 +263,7 @@
 };
 
 const char CmdLineMultiplexJob::short_options[] =
-        "o:i:b:r:O:v:f:l:s:S:p:W:L:R:VCMh";
+        "o:i:b:r:O:v:f:l:s:S:p:W:L:R:VCMhd:";
 #if defined(HAVE_GETOPT_LONG)
 struct option CmdLineMultiplexJob::long_options[] = 
 {
@@ -286,6 +286,7 @@
     { "sector-size",       1, 0, 's' },
     { "workarounds", 1, 0, 'W' },
     { "help",              0, 0, '?' },
+    { "subpicture-delay",  1, 0, 'd' },
     { 0,                   0, 0, 0   }
 };
 #endif
@@ -408,6 +409,14 @@
                 Usage(argv[0]);
             }
             break;
+                  
+        case 'd' :
+            if( ! ParseSubtitleOptions( optarg ) )
+            {
+                mjpeg_error( "Illegal Subtitle option(s): %s", optarg );
+                Usage(argv[0]);
+            }
+            break;
         case '?' :
         default :
             Usage(argv[0]);
@@ -450,8 +459,10 @@
     "--lpcm-params | -L samppersec:chan:bits [, samppersec:chan:bits]\n"
 	"--mux-limit|-l num\n"
     "  Multiplex only num seconds of material (default 0=multiplex all)\n"
-	"--sync-offset|-O num ms|s|mpt\n"
+	"--sync-offset|-O num ms|s|mpt|c\n"
     "  Specify offset of timestamps (video-audio) in mSec\n"
+	"--subpicture-delay|-d delay [ms|s|mpt|c] [:stream-id] [, delay[:stream-id]]\n"
+    "  Specify offset of timestamps (video-subpicture) in msec (default) sec, mpt or clock-ticks\n"
 	"--sector-size|-s num\n"
     "  Specify sector size in bytes for generic formats [256..16384]\n"
     "--vbr|-V\n"
@@ -468,7 +479,7 @@
     "  Maximum size of output file(s) in Mbyte (default: 0) (no limit)\n"
 	"--ignore-seqend-markers|-M\n"
     "  Don't switch to a new output file if a  sequence end marker\n"
-	"  is encountered ithe input video.\n"
+	"  is encountered in the input video.\n"
     "--vdr-index|-i <vdr-index-filename>\n"
     "  Generate a VDR index file with the output stream\n"
     "--workaround|-W workaround [, workaround ]\n"
@@ -477,6 +488,45 @@
 	exit (1);
 }
 
+bool CmdLineMultiplexJob::ParseSubtitleOptions( const char *optarg  )
+{
+    double f;
+    double persecond=1000.0;
+    const char *e;
+	uint64_t subtitle_offset;
+	uint8_t stream_id;
+    e=optarg-1;
+    do 
+    {
+		e++;
+		subtitle_offset =0;
+		persecond=1000.0;
+		stream_id=0x20; // default
+		f=strtod(e,const_cast<char**>(&e));
+		if( *e  ) {
+			while(isspace(*e)) e++;
+			if(!strcmp(e,"ms")) { persecond=1000.0; e+=2;}
+			else if(!strcmp(e,"s")) {persecond=1.0; e++;}
+			else if(!strcmp(e,"mpt")){ persecond=90000.0;e+=3;}
+			else if(!strcmp(e,"c")){ persecond=CLOCKS;e++;}
+		}
+		subtitle_offset = static_cast<int>(f*CLOCKS/(persecond));
+		if( subtitle_offset < 0 )
+			subtitle_offset = 0; // always positive
+		if (*e ==':'){
+		 ++e;
+		 int nr;
+		 sscanf(e,"%hhd%n",&stream_id,&nr);
+		 e+=nr;
+		 mjpeg_info("Stream will be mapped to 0x%02hhX",stream_id);
+		}
+
+		subtitle_params.push_back(SubtitleStreamParams::Checked(subtitle_offset,stream_id));
+	} while (*e == ',');
+	
+	return true;
+
+}
 
 bool CmdLineMultiplexJob::ParseLpcmOpt( const char *optarg )
 {
@@ -608,6 +658,7 @@
         if(!strcmp(e,"ms")) persecond=1000.0;
         else if(!strcmp(e,"s")) persecond=1.0;
         else if(!strcmp(e,"mpt")) persecond=90000.0;
+        else if(!strcmp(e,"c")) persecond=CLOCKS;
 		else
 			return false;
     }
diff -Naur mplex/multiplexor.cpp mplex.new/multiplexor.cpp
--- mplex/multiplexor.cpp	2010-03-06 08:05:48.000000000 +0100
+++ mplex.new/multiplexor.cpp	2010-03-08 20:11:25.000000000 +0100
@@ -393,10 +393,10 @@
 
     unsigned int audio_track = 0;
     unsigned int video_track = 0;
+    unsigned int subp_track = 0;
 	std::vector<VideoParams *>::iterator vidparm = job.video_param.begin();
 	std::vector<LpcmParams *>::iterator lpcmparm = job.lpcm_param.begin();
-
-
+	std::vector<SubtitleStreamParams *>::iterator subpparm = job.subtitle_params.begin();
     std::vector<JobStream *>::iterator i;
     for( i = job.streams.begin() ; i < job.streams.end() ; ++i )
     {
@@ -461,6 +461,17 @@
             ++audio_track;
         }
         break;
+        case SUBP_STREAM :
+        {
+            // we use audios stream as base class
+            SUBPStream *subpStrm =  new SUBPStream( *(*i)->bs, *subpparm,*this);
+            subpStrm ->Init ( subp_track );
+            estreams.push_back(subpStrm );
+            astreams.push_back(subpStrm );
+            ++subpparm;
+            ++subp_track;
+        }
+        break;
 #ifdef ZALPHA
         // just copies the video parameters from the first video stream
         case Z_ALPHA :
@@ -1074,7 +1085,7 @@
 	std::vector<bool> completed;
 	std::vector<bool>::iterator pcomp;
 	std::vector<ElementaryStream *>::iterator str;
-
+	
 	unsigned int packets_left_in_pack = 0; /* Suppress warning */
 	bool padding_packet;
 	bool video_first = true;
diff -Naur mplex/stream_params.hpp mplex.new/stream_params.hpp
--- mplex/stream_params.hpp	2007-02-20 03:14:58.000000000 +0100
+++ mplex.new/stream_params.hpp	2010-03-08 06:12:49.000000000 +0100
@@ -23,7 +23,8 @@
 #ifndef __STREAM_PARAMS_HH__
 #define __STREAM_PARAMS_HH__
 
-
+#include <config.h>
+#include <mjpeg_types.h>
 class LpcmParams
 {
 public:
@@ -57,6 +58,22 @@
   unsigned int decode_buffer_size;
 };
 
+class SubtitleStreamParams
+{
+public:
+  static SubtitleStreamParams *Checked(const int64_t stream_offset,int8_t stream_id){ return new SubtitleStreamParams (stream_offset,stream_id);} ;
+  static SubtitleStreamParams *Default(unsigned int mux_format)	{ return Checked(0,-1);};
+  bool Force(unsigned int mux_format);
+  inline int64_t Offset() { return _stream_offset; }
+  inline int8_t  StreamId() { return _stream_id; }
+private:
+  SubtitleStreamParams(const int64_t stream_offset, int8_t stream_id)
+   {_stream_offset = stream_offset;
+   	_stream_id = stream_id;};
+  
+  int64_t _stream_offset;
+  int8_t _stream_id;
+};
 
 
 //
diff -Naur mplex/subpstream.cpp mplex.new/subpstream.cpp
--- mplex/subpstream.cpp	1970-01-01 01:00:00.000000000 +0100
+++ mplex.new/subpstream.cpp	2010-03-08 20:18:11.000000000 +0100
@@ -0,0 +1,305 @@
+//
+// C++ Implementation: subpstream
+//
+// Description: 
+//
+//
+// Author: grotti <[email protected]>, (C) 2010
+//
+// Copyright: See COPYING file that comes with this distribution
+// Most of the code is stolen from ac3stream.cpp
+//
+//
+#include <config.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "audiostrm.hpp"
+#include "interact.hpp"
+#include "multiplexor.hpp"
+// minimum version code for subtitle stream that
+// can be handled by the program
+#define MIN_VERSION_CODE 0x00030000
+// common part of the subtitle header struct for major version 3
+// taken from subtitle2vobsub
+
+typedef struct {
+
+  unsigned int header_length;
+  unsigned int header_version;
+  unsigned int payload_length;
+
+  unsigned int lpts;
+  double rpts;
+
+} subtitle_header_v3_t;
+
+typedef struct {
+	char marker[8];
+	subtitle_header_v3_t header;
+} vobsub_header;
+
+static unsigned int minor_version(unsigned int version)
+{
+    // bit 0-15 contain the minor version number
+    return version & 0xffff;
+}
+
+// get the major version number from the version code
+static unsigned int major_version(unsigned int version)
+{
+    // bit 16-31 contain the major version number
+    return version >> 16;
+}
+
+
+static const char* SUBPHEADER="SUBTITLE";
+
+SUBPStream::SUBPStream(IBitStream &ibs, SubtitleStreamParams* subpparm, Multiplexor &into) : 
+	AudioStream( ibs, into ),parms(subpparm)
+    
+{
+ 
+ num_frames =0;
+ initial_offset = -1;
+}
+
+
+
+
+bool SUBPStream::Probe(IBitStream &bs )
+{
+    //char *last_dot = strrchr( bs, '.' );
+ char buffer[20];
+ bs.GetBytes((uint8_t*)buffer,strlen(SUBPHEADER));
+if (strncmp(buffer,SUBPHEADER,strlen(SUBPHEADER)) == 0)
+	return true;
+return false;
+    
+
+}
+
+void SUBPStream::Init(const int stream_num)
+{
+MuxStream::Init( PRIVATE_STR_1, 
+					 1,  // Buffer scale
+					 8192, // default buffer size
+					 false,
+					 muxinto.buffers_in_audio,
+					 muxinto.always_buffers_in_audio
+		);
+	mjpeg_info ("Scanning for header info: Subpicture stream %02x (%s)",
+                stream_num,
+                bs.StreamName()
+                );
+    sub_stream_id =parms->StreamId();
+    
+    ParseAUBitwise();
+    
+}
+
+bool SUBPStream::ParseAUBitwise()
+{
+	vobsub_header vobsub;
+	uint8_t* ptr = reinterpret_cast<uint8_t*> (&vobsub);
+	subtitle_header_v3_t &header = vobsub.header;
+	char buffer[20];
+	int i;
+	for (i=0; i<sizeof(vobsub); i++)
+		*(ptr+i) = bs.GetBits(8);
+
+	if (strncmp(vobsub.marker,SUBPHEADER,strlen(SUBPHEADER)) != 0)
+	{
+		mjpeg_error( "Subtitle: expected header %s!",SUBPHEADER);
+		return false; 
+	
+	}
+
+	if (major_version(header.header_version) != major_version(MIN_VERSION_CODE))
+	{
+		mjpeg_error( "Subtitle: expected version 0x%08X, got version 0x%08X while reading subtitle header!",MIN_VERSION_CODE,header.header_version);
+		return false; 
+	}
+	int16_t skip_len = header.header_length-sizeof(header);
+	if (skip_len)
+	{
+		assert (skip_len>0);
+		bs.SeekFwdBits(skip_len);
+	}
+	uint8_t subpid;
+	prev_offset = AU_start;
+	AU_start = bs.bitcount();
+	// packet starts here...
+	subpid = bs.GetBits(8);
+
+	
+	access_unit.start = AU_start;
+	access_unit.length = header.payload_length;
+	if(header.rpts > 0){
+
+		access_unit.PTS=header.rpts*300.0*90000.0;
+		if (initial_offset == -1)
+		{
+			if (sub_stream_id == -1)
+				sub_stream_id = subpid;
+			mjpeg_info( "SUBTITLE id 0x%02X => 0x%02X", subpid,sub_stream_id);
+			initial_offset = access_unit.PTS;
+			mjpeg_info("Stream  offset is :       %lld (PTS)",access_unit.PTS);
+			mjpeg_info("Initial offset is :       %lld (PTS)",initial_offset);
+			mjpeg_info("Cmd line offset is:       %lld (PTS)",parms->Offset());
+			initial_offset -= parms->Offset();
+			mjpeg_info("Adjustment offset :       %lld (PTS)",initial_offset);
+		}
+		access_unit.PTS-=initial_offset;
+		mjpeg_debug("Subtitle: appending PTS/DTS (int64_t) %lld",access_unit.PTS);
+		
+		} else {
+		// calculate the time from lpts
+		mjpeg_info( "Subtitle: fallback to lpts", subpid);
+		access_unit.PTS= (double)(header.lpts/300)/90000.0;
+		}
+
+	access_unit.DTS = access_unit.PTS;
+	access_unit.dorder = decoding_order;
+	mjpeg_debug("appending PTS/DTS %lld",access_unit.DTS);
+	decoding_order++;
+	aunits.Append( access_unit );
+	
+
+	bs.SeekFwdBits(header.payload_length-1);
+	num_frames++;
+	return true;
+}
+
+void SUBPStream::Close()
+{
+    stream_length = AU_start >> 3;
+	 mjpeg_info ("SUBTITLE STATISTICS:  0x%02x", sub_stream_id); 
+    mjpeg_info ("Subtitle stream length  %lld bytes.", stream_length);
+    mjpeg_info ("Nr. of subtitle packets:%d",  num_frames);
+
+}
+
+void SUBPStream::FillAUbuffer(unsigned int frames_to_buffer)
+{
+	uint32_t discont_ctr;
+	last_buffered_AU += frames_to_buffer;
+	mjpeg_debug( "Scanning %d Subpicture frames to frame %d", 
+				 frames_to_buffer, last_buffered_AU );
+	prev_offset = AU_start;
+	AU_start = bs.bitcount();
+	while (decoding_order < last_buffered_AU && !bs.eos()
+           && !muxinto.AfterMaxPTS(access_unit.PTS))
+	{
+		if (!ParseAUBitwise())
+			break;
+	
+	}
+
+	last_buffered_AU = decoding_order;
+	eoscan = bs.eos() || muxinto.AfterMaxPTS(access_unit.PTS);
+}
+
+unsigned int SUBPStream::ReadPacketPayload(uint8_t *dst, unsigned int to_read)
+{
+	bitcount_t read_start = bs.GetBytePos();
+   mjpeg_debug( "SUBPStream called: ReadPacketPayload at 0x%08lld", read_start);
+	vobsub_header vobsub;
+	subtitle_header_v3_t &header = vobsub.header;
+	bs.GetBytes((uint8_t*) &vobsub,sizeof(vobsub));
+	int16_t skip_len = header.header_length-sizeof(header);
+	if (strncmp(vobsub.marker,SUBPHEADER,strlen(SUBPHEADER)) != 0)
+	{
+		mjpeg_error( "Subtitle: expected header %s!",SUBPHEADER);
+		return 0; 
+	
+	}
+
+	assert (skip_len>=0);
+	if (skip_len)
+	{
+		uint8_t* b = (uint8_t* ) malloc(skip_len);
+		
+		bs.GetBytes(b,skip_len);
+		free (b);
+	}
+	unsigned int bytes_read = bs.GetBytes( dst, header.payload_length );
+	bs.Flush( read_start );
+	dst[0] = sub_stream_id;
+	clockticks   decode_time;
+
+   unsigned int first_header = 
+        (new_au_next_sec || au_unsent > bytes_read )
+        ? 0 
+        : au_unsent;
+
+    // BUG BUG BUG: how do we set the 1st header pointer if we have
+    // the *middle* part of a large frame?
+    assert( first_header+2 <= to_read );
+
+    unsigned int syncwords = 0;
+    unsigned int bytes_muxed = bytes_read;
+  
+	 if (bytes_muxed == 0 || MuxCompleted() )
+    {
+		goto completion;
+    }
+
+
+	/* Work through what's left of the current AU and the following AU's
+	   updating the info until we reach a point where an AU had to be
+	   split between packets.
+	   NOTE: It *is* possible for this loop to iterate. 
+
+	   The DTS/PTS field for the packet in this case would have been
+	   given the that for the first AU to start in the packet.
+
+	*/
+
+	
+	decode_time = RequiredDTS();
+	mjpeg_debug("SUBPStream: Required DTS is %lld",decode_time);
+	while (au_unsent < bytes_muxed)
+	{	  
+        // BUG BUG BUG: if we ever had odd payload / packet size we might
+        // split an AC3 frame in the middle of the syncword!
+        assert( bytes_muxed > 1 );
+		bufmodel.Queued(au_unsent, decode_time);
+		bytes_muxed -= au_unsent;
+        if( new_au_next_sec )
+            ++syncwords;
+		if( !NextAU() )
+        {
+            goto completion;
+        }
+		new_au_next_sec = true;
+		decode_time = RequiredDTS();
+		mjpeg_debug("Required DTS is %lld (while-loop)",decode_time);
+	};
+
+	// We've now reached a point where the current AU overran or
+	// fitted exactly.  We need to distinguish the latter case
+	// so we can record whether the next packet starts with an
+	// existing AU or not - info we need to decide what PTS/DTS
+	// info to write at the start of the next packet.
+	
+	if (au_unsent > bytes_muxed)
+	{
+        if( new_au_next_sec )
+            ++syncwords;
+		bufmodel.Queued( bytes_muxed, decode_time);
+		au_unsent -= bytes_muxed;
+		new_au_next_sec = false;
+	} 
+	else //  if (au_unsent == bytes_muxed)
+	{
+		bufmodel.Queued(bytes_muxed, decode_time);
+      if( new_au_next_sec )
+           ++syncwords;
+      new_au_next_sec = NextAU();
+	}	   
+completion:
+    
+	return bytes_read;
+}