2003-07-26 03:01:58 +00:00
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#include <math.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#include <mjpeg_types.h>
|
|
|
|
|
#include <mjpeg_logging.h>
|
|
|
|
|
#include <format_codes.h>
|
|
|
|
|
|
|
|
|
|
#include "videostrm.hh"
|
|
|
|
|
#include "outputstream.hh"
|
|
|
|
|
#include <cassert>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
|
Find the timecode corresponding to given position in the system stream
|
|
|
|
|
(assuming the SCR starts at 0 at the beginning of the stream
|
|
|
|
|
@param bytepos byte position in the stream
|
|
|
|
|
@param ts returns the number of clockticks the bytepos is from the file start
|
|
|
|
|
****************************************************************** */
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
OutputStream::ByteposTimecode (bitcount_t bytepos, clockticks & ts)
|
|
|
|
|
{
|
|
|
|
|
ts = (bytepos * CLOCKS) / static_cast < bitcount_t > (dmux_rate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**********
|
|
|
|
|
*
|
|
|
|
|
* UpdateSectorHeaders - Update the sector headers after a change in stream
|
|
|
|
|
* position / SCR
|
|
|
|
|
**********
|
|
|
|
|
|
|
|
|
|
**********
|
|
|
|
|
*
|
|
|
|
|
* NextPosAndSCR - Update nominal (may be >= actual) byte count
|
|
|
|
|
* and SCR to next output sector.
|
|
|
|
|
*
|
|
|
|
|
********/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
OutputStream::NextPosAndSCR ()
|
|
|
|
|
{
|
|
|
|
|
bytes_output += sector_transport_size;
|
|
|
|
|
ByteposTimecode (bytes_output, current_SCR);
|
|
|
|
|
if (start_of_new_pack) {
|
|
|
|
|
psstrm->CreatePack (&pack_header, current_SCR, mux_rate);
|
|
|
|
|
pack_header_ptr = &pack_header;
|
|
|
|
|
if (include_sys_header)
|
|
|
|
|
sys_header_ptr = &sys_header;
|
|
|
|
|
else
|
|
|
|
|
sys_header_ptr = NULL;
|
|
|
|
|
|
|
|
|
|
} else
|
|
|
|
|
pack_header_ptr = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**********
|
|
|
|
|
*
|
|
|
|
|
* NextPosAndSCR - Update nominal (may be >= actual) byte count
|
|
|
|
|
* and SCR to next output sector.
|
|
|
|
|
* @param bytepos byte position in the stream
|
|
|
|
|
********/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
OutputStream::SetPosAndSCR (bitcount_t bytepos)
|
|
|
|
|
{
|
|
|
|
|
bytes_output = bytepos;
|
|
|
|
|
ByteposTimecode (bytes_output, current_SCR);
|
|
|
|
|
if (start_of_new_pack) {
|
|
|
|
|
psstrm->CreatePack (&pack_header, current_SCR, mux_rate);
|
|
|
|
|
pack_header_ptr = &pack_header;
|
|
|
|
|
if (include_sys_header)
|
|
|
|
|
sys_header_ptr = &sys_header;
|
|
|
|
|
else
|
|
|
|
|
sys_header_ptr = NULL;
|
|
|
|
|
|
|
|
|
|
} else
|
|
|
|
|
pack_header_ptr = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Stream syntax parameters.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************
|
|
|
|
|
|
|
|
|
|
Initialisation of stream syntax paramters based on selected
|
|
|
|
|
user options.
|
|
|
|
|
******************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: this mixes class member parameters with opt_ globals...
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
OutputStream::InitSyntaxParameters ()
|
|
|
|
|
{
|
|
|
|
|
video_buffer_size = 0;
|
|
|
|
|
seg_starts_with_video = false;
|
|
|
|
|
audio_buffer_size = 4 * 1024;
|
|
|
|
|
|
|
|
|
|
switch (opt_mux_format) {
|
|
|
|
|
case MPEG_FORMAT_VCD:
|
|
|
|
|
opt_data_rate = 75 * 2352; /* 75 raw CD sectors/sec */
|
|
|
|
|
video_buffer_size = 46 * 1024;
|
|
|
|
|
opt_VBR = 0;
|
|
|
|
|
|
|
|
|
|
case MPEG_FORMAT_VCD_NSR: /* VCD format, non-standard rate */
|
|
|
|
|
mjpeg_info ("Selecting VCD output profile");
|
|
|
|
|
if (video_buffer_size == 0)
|
|
|
|
|
video_buffer_size = opt_buffer_size * 1024;
|
|
|
|
|
vbr = opt_VBR;
|
|
|
|
|
opt_mpeg = 1;
|
|
|
|
|
packets_per_pack = 1;
|
|
|
|
|
sys_header_in_pack1 = 0;
|
|
|
|
|
always_sys_header_in_pack = 0;
|
|
|
|
|
sector_transport_size = 2352; /* Each 2352 bytes with 2324 bytes payload */
|
|
|
|
|
transport_prefix_sectors = 30;
|
|
|
|
|
sector_size = 2324;
|
|
|
|
|
buffers_in_video = 1;
|
|
|
|
|
always_buffers_in_video = 0;
|
|
|
|
|
buffers_in_audio = 1; // This is needed as otherwise we have
|
|
|
|
|
always_buffers_in_audio = 1; // to stuff the packer header which
|
|
|
|
|
// must be 13 bytes for VCD audio
|
|
|
|
|
vcd_zero_stuffing = 20; // The famous 20 zero bytes for VCD
|
|
|
|
|
// audio sectors.
|
|
|
|
|
dtspts_for_all_vau = false;
|
|
|
|
|
sector_align_iframeAUs = false;
|
|
|
|
|
timestamp_iframe_only = false;
|
|
|
|
|
seg_starts_with_video = true;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MPEG_FORMAT_MPEG2:
|
|
|
|
|
mjpeg_info ("Selecting generic MPEG2 output profile");
|
|
|
|
|
opt_mpeg = 2;
|
|
|
|
|
packets_per_pack = 1;
|
|
|
|
|
sys_header_in_pack1 = 1;
|
|
|
|
|
always_sys_header_in_pack = 0;
|
|
|
|
|
sector_transport_size = 2048; /* Each 2352 bytes with 2324 bytes payload */
|
|
|
|
|
transport_prefix_sectors = 0;
|
|
|
|
|
sector_size = 2048;
|
|
|
|
|
video_buffer_size = 234 * 1024;
|
|
|
|
|
buffers_in_video = 1;
|
|
|
|
|
always_buffers_in_video = 0;
|
|
|
|
|
buffers_in_audio = 1;
|
|
|
|
|
always_buffers_in_audio = 1;
|
|
|
|
|
vcd_zero_stuffing = 0;
|
|
|
|
|
dtspts_for_all_vau = 0;
|
|
|
|
|
sector_align_iframeAUs = false;
|
|
|
|
|
timestamp_iframe_only = false;
|
|
|
|
|
video_buffers_iframe_only = false;
|
|
|
|
|
vbr = opt_VBR;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MPEG_FORMAT_SVCD:
|
|
|
|
|
opt_data_rate = 150 * 2324;
|
|
|
|
|
video_buffer_size = 230 * 1024;
|
|
|
|
|
|
|
|
|
|
case MPEG_FORMAT_SVCD_NSR: /* Non-standard data-rate */
|
|
|
|
|
mjpeg_info ("Selecting SVCD output profile");
|
|
|
|
|
if (video_buffer_size == 0)
|
|
|
|
|
video_buffer_size = opt_buffer_size * 1024;
|
|
|
|
|
opt_mpeg = 2;
|
|
|
|
|
packets_per_pack = 1;
|
|
|
|
|
sys_header_in_pack1 = 0;
|
|
|
|
|
always_sys_header_in_pack = 0;
|
|
|
|
|
sector_transport_size = 2324;
|
|
|
|
|
transport_prefix_sectors = 0;
|
|
|
|
|
sector_size = 2324;
|
|
|
|
|
vbr = true;
|
|
|
|
|
buffers_in_video = 1;
|
|
|
|
|
always_buffers_in_video = 0;
|
|
|
|
|
buffers_in_audio = 1;
|
|
|
|
|
always_buffers_in_audio = 0;
|
|
|
|
|
vcd_zero_stuffing = 0;
|
|
|
|
|
dtspts_for_all_vau = 0;
|
|
|
|
|
sector_align_iframeAUs = true;
|
|
|
|
|
seg_starts_with_video = true;
|
|
|
|
|
timestamp_iframe_only = false;
|
|
|
|
|
video_buffers_iframe_only = false;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MPEG_FORMAT_VCD_STILL:
|
|
|
|
|
opt_data_rate = 75 * 2352; /* 75 raw CD sectors/sec */
|
|
|
|
|
vbr = false;
|
|
|
|
|
opt_mpeg = 1;
|
|
|
|
|
packets_per_pack = 1;
|
|
|
|
|
sys_header_in_pack1 = 0;
|
|
|
|
|
always_sys_header_in_pack = 0;
|
|
|
|
|
sector_transport_size = 2352; /* Each 2352 bytes with 2324 bytes payload */
|
|
|
|
|
transport_prefix_sectors = 0;
|
|
|
|
|
sector_size = 2324;
|
|
|
|
|
buffers_in_video = 1;
|
|
|
|
|
always_buffers_in_video = 0;
|
|
|
|
|
buffers_in_audio = 1;
|
|
|
|
|
always_buffers_in_audio = 0;
|
|
|
|
|
vcd_zero_stuffing = 20;
|
|
|
|
|
dtspts_for_all_vau = 1;
|
|
|
|
|
sector_align_iframeAUs = true;
|
|
|
|
|
timestamp_iframe_only = false;
|
|
|
|
|
video_buffers_iframe_only = false;
|
|
|
|
|
if (opt_buffer_size == 0)
|
|
|
|
|
opt_buffer_size = 46;
|
|
|
|
|
else if (opt_buffer_size > 220) {
|
|
|
|
|
mjpeg_error_exit1 ("VCD stills has max. permissible video buffer size of 220KB");
|
|
|
|
|
} else {
|
|
|
|
|
/* Add a margin for sequence header overheads for HR stills */
|
|
|
|
|
/* So the user simply specifies the nominal size... */
|
|
|
|
|
opt_buffer_size += 4;
|
|
|
|
|
}
|
|
|
|
|
video_buffer_size = opt_buffer_size * 1024;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MPEG_FORMAT_SVCD_STILL:
|
|
|
|
|
mjpeg_info ("Selecting SVCD output profile");
|
|
|
|
|
if (opt_data_rate == 0)
|
|
|
|
|
opt_data_rate = 150 * 2324;
|
|
|
|
|
video_buffer_size = 230 * 1024;
|
|
|
|
|
opt_mpeg = 2;
|
|
|
|
|
packets_per_pack = 1;
|
|
|
|
|
sys_header_in_pack1 = 0;
|
|
|
|
|
always_sys_header_in_pack = 0;
|
|
|
|
|
sector_transport_size = 2324;
|
|
|
|
|
transport_prefix_sectors = 0;
|
|
|
|
|
sector_size = 2324;
|
|
|
|
|
vbr = true;
|
|
|
|
|
buffers_in_video = 1;
|
|
|
|
|
always_buffers_in_video = 0;
|
|
|
|
|
buffers_in_audio = 1;
|
|
|
|
|
always_buffers_in_audio = 0;
|
|
|
|
|
vcd_zero_stuffing = 0;
|
|
|
|
|
dtspts_for_all_vau = 0;
|
|
|
|
|
sector_align_iframeAUs = true;
|
|
|
|
|
timestamp_iframe_only = false;
|
|
|
|
|
video_buffers_iframe_only = false;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MPEG_FORMAT_DVD:
|
|
|
|
|
mjpeg_info ("Selecting DVD output profile (INCOMPLETE!!!!)");
|
|
|
|
|
opt_data_rate = 1260000;
|
|
|
|
|
opt_mpeg = 2;
|
|
|
|
|
packets_per_pack = 1;
|
|
|
|
|
sys_header_in_pack1 = false; // Handle by control packets
|
|
|
|
|
always_sys_header_in_pack = false;
|
|
|
|
|
sector_transport_size = 2048;
|
|
|
|
|
transport_prefix_sectors = 0;
|
|
|
|
|
sector_size = 2048;
|
|
|
|
|
video_buffer_size = 232 * 1024;
|
|
|
|
|
buffers_in_video = true;
|
|
|
|
|
always_buffers_in_video = false;
|
|
|
|
|
buffers_in_audio = true;
|
|
|
|
|
always_buffers_in_audio = false;
|
|
|
|
|
vcd_zero_stuffing = 0;
|
|
|
|
|
dtspts_for_all_vau = 0;
|
|
|
|
|
sector_align_iframeAUs = true;
|
|
|
|
|
timestamp_iframe_only = true;
|
|
|
|
|
video_buffers_iframe_only = true;
|
|
|
|
|
vbr = true;
|
|
|
|
|
if (opt_max_segment_size == 0)
|
|
|
|
|
opt_max_segment_size = 2000 * 1024 * 1024;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default: /* MPEG_FORMAT_MPEG1 - auto format MPEG1 */
|
|
|
|
|
mjpeg_info ("Selecting generic MPEG1 output profile");
|
|
|
|
|
opt_mpeg = 1;
|
|
|
|
|
vbr = opt_VBR;
|
|
|
|
|
packets_per_pack = opt_packets_per_pack;
|
|
|
|
|
always_sys_header_in_pack = opt_always_system_headers;
|
|
|
|
|
sys_header_in_pack1 = 1;
|
|
|
|
|
sector_transport_size = opt_sector_size;
|
|
|
|
|
transport_prefix_sectors = 0;
|
|
|
|
|
sector_size = opt_sector_size;
|
|
|
|
|
if (opt_buffer_size == 0) {
|
|
|
|
|
opt_buffer_size = 46;
|
|
|
|
|
}
|
|
|
|
|
video_buffer_size = opt_buffer_size * 1024;
|
|
|
|
|
buffers_in_video = 1;
|
|
|
|
|
always_buffers_in_video = 1;
|
|
|
|
|
buffers_in_audio = 0;
|
|
|
|
|
always_buffers_in_audio = 1;
|
|
|
|
|
vcd_zero_stuffing = 0;
|
|
|
|
|
dtspts_for_all_vau = 0;
|
|
|
|
|
sector_align_iframeAUs = false;
|
|
|
|
|
timestamp_iframe_only = false;
|
|
|
|
|
video_buffers_iframe_only = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Compute the number of run-in sectors needed to fill up the buffers to
|
|
|
|
|
* suit the type of stream being muxed.
|
|
|
|
|
*
|
|
|
|
|
* For stills we have to ensure an entire buffer is loaded as we only
|
|
|
|
|
* ever process one frame at a time.
|
|
|
|
|
* @returns the number of run-in sectors needed to fill up the buffers to suit the type of stream being muxed.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
unsigned int
|
|
|
|
|
OutputStream::RunInSectors ()
|
|
|
|
|
{
|
|
|
|
|
vector < ElementaryStream * >::iterator str;
|
|
|
|
|
unsigned int sectors_delay = 1;
|
|
|
|
|
|
|
|
|
|
for (str = vstreams.begin (); str < vstreams.end (); ++str) {
|
|
|
|
|
|
|
|
|
|
if (MPEG_STILLS_FORMAT (opt_mux_format)) {
|
|
|
|
|
sectors_delay += (unsigned int) (1.02 * (*str)->BufferSize ()) / sector_size + 2;
|
|
|
|
|
} else if (vbr)
|
|
|
|
|
sectors_delay += 3 * (*str)->BufferSize () / (4 * sector_size);
|
|
|
|
|
else
|
|
|
|
|
sectors_delay += 5 * (*str)->BufferSize () / (6 * sector_size);
|
|
|
|
|
}
|
|
|
|
|
sectors_delay += astreams.size ();
|
|
|
|
|
return sectors_delay;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Initializes the output stream. Traverses the input files and calculates their payloads.
|
|
|
|
|
Estimates the multiplex rate. Estimates the neccessary stream delay for the different substreams.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
OutputStream::Init (vector < ElementaryStream * >*strms, PS_Stream *strm)
|
|
|
|
|
{
|
|
|
|
|
vector < ElementaryStream * >::iterator str;
|
|
|
|
|
clockticks delay;
|
|
|
|
|
unsigned int sectors_delay;
|
|
|
|
|
|
|
|
|
|
Pack_struc dummy_pack;
|
|
|
|
|
Sys_header_struc dummy_sys_header;
|
|
|
|
|
Sys_header_struc *sys_hdr;
|
|
|
|
|
unsigned int nominal_rate_sum;
|
|
|
|
|
|
|
|
|
|
packets_left_in_pack = 0; /* Suppress warning */
|
|
|
|
|
video_first = false;
|
|
|
|
|
|
|
|
|
|
estreams = strms;
|
|
|
|
|
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str) {
|
|
|
|
|
switch ((*str)->Kind ()) {
|
|
|
|
|
case ElementaryStream::audio:
|
|
|
|
|
astreams.push_back (*str);
|
|
|
|
|
break;
|
|
|
|
|
case ElementaryStream::video:
|
|
|
|
|
vstreams.push_back (*str);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
completed.push_back (false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mjpeg_info ("SYSTEMS/PROGRAM stream:");
|
|
|
|
|
this->psstrm = strm;
|
|
|
|
|
|
|
|
|
|
psstrm->Init (opt_mpeg, sector_size, opt_max_segment_size);
|
|
|
|
|
|
|
|
|
|
/* These are used to make (conservative) decisions
|
|
|
|
|
about whether a packet should fit into the recieve buffers...
|
|
|
|
|
Audio packets always have PTS fields, video packets needn'.
|
|
|
|
|
TODO: Really this should be encapsulated in Elementary stream...?
|
|
|
|
|
*/
|
|
|
|
|
psstrm->CreatePack (&dummy_pack, 0, mux_rate);
|
|
|
|
|
if (always_sys_header_in_pack) {
|
|
|
|
|
vector < MuxStream * >muxstreams;
|
|
|
|
|
AppendMuxStreamsOf (*estreams, muxstreams);
|
|
|
|
|
psstrm->CreateSysHeader (&dummy_sys_header, mux_rate, !vbr, 1, true, true, muxstreams);
|
|
|
|
|
sys_hdr = &dummy_sys_header;
|
|
|
|
|
} else
|
|
|
|
|
sys_hdr = NULL;
|
|
|
|
|
|
|
|
|
|
nominal_rate_sum = 0;
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str) {
|
|
|
|
|
switch ((*str)->Kind ()) {
|
|
|
|
|
case ElementaryStream::audio:
|
|
|
|
|
(*str)->SetMaxPacketData (psstrm->PacketPayload (**str, NULL, NULL,
|
|
|
|
|
false, true, false));
|
|
|
|
|
(*str)->SetMinPacketData (psstrm->PacketPayload (**str, sys_hdr, &dummy_pack,
|
|
|
|
|
always_buffers_in_audio, true, false));
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case ElementaryStream::video:
|
|
|
|
|
(*str)->SetMaxPacketData (psstrm->PacketPayload (**str, NULL, NULL,
|
|
|
|
|
false, false, false));
|
|
|
|
|
(*str)->SetMinPacketData (psstrm->PacketPayload (**str, sys_hdr, &dummy_pack,
|
|
|
|
|
always_buffers_in_video, true, true));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
mjpeg_error_exit1 ("INTERNAL: Only audio and video payload calculations implemented!");
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((*str)->NominalBitRate () == 0 && opt_data_rate == 0)
|
|
|
|
|
mjpeg_error_exit1
|
|
|
|
|
("Variable bit-rate stream present: output stream (max) data-rate *must* be specified!");
|
|
|
|
|
nominal_rate_sum += (*str)->NominalBitRate ();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Attempt to guess a sensible mux rate for the given video and *
|
|
|
|
|
audio estreams-> This is a rough and ready guess for MPEG-1 like
|
|
|
|
|
formats. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dmux_rate = static_cast < int >(1.015 * nominal_rate_sum);
|
|
|
|
|
|
|
|
|
|
dmux_rate = (dmux_rate / 50 + 25) * 50;
|
|
|
|
|
|
|
|
|
|
mjpeg_info ("rough-guess multiplexed stream data rate : %07d", dmux_rate * 8);
|
|
|
|
|
if (opt_data_rate != 0)
|
|
|
|
|
mjpeg_info ("target data-rate specified : %7d", opt_data_rate * 8);
|
|
|
|
|
|
|
|
|
|
if (opt_data_rate == 0) {
|
|
|
|
|
mjpeg_info ("Setting best-guess data rate.");
|
|
|
|
|
} else if (opt_data_rate >= dmux_rate) {
|
|
|
|
|
mjpeg_info ("Setting specified specified data rate: %7d", opt_data_rate * 8);
|
|
|
|
|
dmux_rate = opt_data_rate;
|
|
|
|
|
} else if (opt_data_rate < dmux_rate) {
|
|
|
|
|
mjpeg_warn ("Target data rate lower than computed requirement!");
|
|
|
|
|
mjpeg_warn ("N.b. a 20%% or so discrepancy in variable bit-rate");
|
|
|
|
|
mjpeg_warn ("streams is common and harmless provided no time-outs will occur");
|
|
|
|
|
dmux_rate = opt_data_rate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mux_rate = dmux_rate / 50;
|
|
|
|
|
|
|
|
|
|
/* To avoid Buffer underflow, the DTS of the first video and audio AU's
|
|
|
|
|
must be offset sufficiently forward of the SCR to allow the buffer
|
|
|
|
|
time to fill before decoding starts. Calculate the necessary delays...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
sectors_delay = RunInSectors ();
|
|
|
|
|
|
|
|
|
|
ByteposTimecode (static_cast < bitcount_t > (sectors_delay * sector_transport_size), delay);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
video_delay = delay + static_cast < clockticks > (opt_video_offset * CLOCKS / 1000);
|
|
|
|
|
audio_delay = delay + static_cast < clockticks > (opt_audio_offset * CLOCKS / 1000);
|
|
|
|
|
mjpeg_info ("Sectors = %d Video delay = %lld Audio delay = %lld",
|
|
|
|
|
sectors_delay, video_delay / 300, audio_delay / 300);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Now that all mux parameters are set we can trigger parsing
|
|
|
|
|
// of actual input stream data and calculation of associated
|
|
|
|
|
// PTS/DTS by causing the read of the first AU's...
|
|
|
|
|
//
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str) {
|
|
|
|
|
(*str)->NextAU ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Now that we have both output and input streams initialised and
|
|
|
|
|
// data-rates set we can make a decent job of setting the maximum
|
|
|
|
|
// STD buffer delay in video streams.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
for (str = vstreams.begin (); str < vstreams.end (); ++str) {
|
|
|
|
|
static_cast < VideoStream * >(*str)->SetMaxStdBufferDelay (dmux_rate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Let's try to read in unit after unit and to write it out into
|
|
|
|
|
the outputstream. The only difficulty herein lies into the
|
|
|
|
|
buffer management, and into the fact the the actual access
|
|
|
|
|
unit *has* to arrive in time, that means the whole unit
|
|
|
|
|
(better yet, packet data), has to arrive before arrival of
|
|
|
|
|
DTS. If both buffers are full we'll generate a padding packet
|
|
|
|
|
|
|
|
|
|
Of course, when we start we're starting a new segment with no
|
|
|
|
|
bytes output...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
ByteposTimecode (sector_transport_size, ticks_per_sector);
|
|
|
|
|
seg_state = start_segment;
|
|
|
|
|
running_out = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Prints the current status of the substreams.
|
|
|
|
|
@param level the desired log level
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
OutputStream::MuxStatus (log_level_t level)
|
|
|
|
|
{
|
|
|
|
|
vector < ElementaryStream * >::iterator str;
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str) {
|
|
|
|
|
switch ((*str)->Kind ()) {
|
|
|
|
|
case ElementaryStream::video:
|
|
|
|
|
mjpeg_log (level,
|
|
|
|
|
"Video %02x: buf=%7d frame=%06d sector=%08d",
|
|
|
|
|
(*str)->stream_id, (*str)->bufmodel.Space (), (*str)->au->dorder, (*str)->nsec);
|
|
|
|
|
break;
|
|
|
|
|
case ElementaryStream::audio:
|
|
|
|
|
mjpeg_log (level,
|
|
|
|
|
"Audio %02x: buf=%7d frame=%06d sector=%08d",
|
|
|
|
|
(*str)->stream_id, (*str)->bufmodel.Space (), (*str)->au->dorder, (*str)->nsec);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
mjpeg_log (level,
|
|
|
|
|
"Other %02x: buf=%7d sector=%08d",
|
|
|
|
|
(*str)->stream_id, (*str)->bufmodel.Space (), (*str)->nsec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!vbr)
|
|
|
|
|
mjpeg_log (level, "Padding : sector=%08d", pstrm.nsec);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Append input substreams to the output multiplex stream.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
OutputStream::AppendMuxStreamsOf (vector < ElementaryStream * >&elem, vector < MuxStream * >&mux)
|
|
|
|
|
{
|
|
|
|
|
vector < ElementaryStream * >::iterator str;
|
|
|
|
|
for (str = elem.begin (); str < elem.end (); ++str) {
|
|
|
|
|
mux.push_back (static_cast < MuxStream * >(*str));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************
|
|
|
|
|
Program start-up packets. Generate any irregular packets
|
|
|
|
|
needed at the start of the stream...
|
|
|
|
|
Note: *must* leave a sensible in-stream system header in
|
|
|
|
|
sys_header.
|
|
|
|
|
TODO: get rid of this grotty sys_header global.
|
|
|
|
|
******************************************************************/
|
|
|
|
|
void
|
|
|
|
|
OutputStream::OutputPrefix ()
|
|
|
|
|
{
|
|
|
|
|
vector < MuxStream * >vmux, amux, emux;
|
|
|
|
|
AppendMuxStreamsOf (vstreams, vmux);
|
|
|
|
|
AppendMuxStreamsOf (astreams, amux);
|
|
|
|
|
AppendMuxStreamsOf (*estreams, emux);
|
|
|
|
|
|
|
|
|
|
/* Deal with transport padding */
|
|
|
|
|
SetPosAndSCR (bytes_output + transport_prefix_sectors * sector_transport_size);
|
|
|
|
|
|
|
|
|
|
/* VCD: Two padding packets with video and audio system headers */
|
|
|
|
|
split_at_seq_end = !opt_multifile_segment;
|
|
|
|
|
|
|
|
|
|
switch (opt_mux_format) {
|
|
|
|
|
case MPEG_FORMAT_VCD:
|
|
|
|
|
case MPEG_FORMAT_VCD_NSR:
|
|
|
|
|
|
|
|
|
|
/* Annoyingly VCD generates seperate system headers for
|
|
|
|
|
audio and video ... DOH... */
|
|
|
|
|
if (astreams.size () > 1 || vstreams.size () > 1 ||
|
|
|
|
|
astreams.size () + vstreams.size () != estreams->size ()) {
|
|
|
|
|
mjpeg_error_exit1 ("VCD man only have max. 1 audio and 1 video stream");
|
|
|
|
|
}
|
|
|
|
|
/* First packet carries video-info-only sys_header */
|
|
|
|
|
psstrm->CreateSysHeader (&sys_header, mux_rate, false, true, true, true, vmux);
|
|
|
|
|
sys_header_ptr = &sys_header;
|
|
|
|
|
pack_header_ptr = &pack_header;
|
|
|
|
|
OutputPadding (false);
|
|
|
|
|
|
|
|
|
|
/* Second packet carries audio-info-only sys_header */
|
|
|
|
|
psstrm->CreateSysHeader (&sys_header, mux_rate, false, true, true, true, amux);
|
|
|
|
|
sys_header_ptr = &sys_header;
|
|
|
|
|
pack_header_ptr = &pack_header;
|
|
|
|
|
OutputPadding (true);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MPEG_FORMAT_SVCD:
|
|
|
|
|
case MPEG_FORMAT_SVCD_NSR:
|
|
|
|
|
/* First packet carries sys_header */
|
|
|
|
|
psstrm->CreateSysHeader (&sys_header, mux_rate, !vbr, true, true, true, emux);
|
|
|
|
|
sys_header_ptr = &sys_header;
|
|
|
|
|
pack_header_ptr = &pack_header;
|
|
|
|
|
OutputPadding (false);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MPEG_FORMAT_VCD_STILL:
|
|
|
|
|
split_at_seq_end = false;
|
|
|
|
|
/* First packet carries small-still sys_header */
|
|
|
|
|
/* TODO No support mixed-mode stills sequences... */
|
|
|
|
|
psstrm->CreateSysHeader (&sys_header, mux_rate, false, false, true, true, emux);
|
|
|
|
|
sys_header_ptr = &sys_header;
|
|
|
|
|
pack_header_ptr = &pack_header;
|
|
|
|
|
OutputPadding (false);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MPEG_FORMAT_SVCD_STILL:
|
|
|
|
|
/* TODO: Video only at present */
|
|
|
|
|
/* First packet carries video-info-only sys_header */
|
|
|
|
|
psstrm->CreateSysHeader (&sys_header, mux_rate, false, true, true, true, vmux);
|
|
|
|
|
sys_header_ptr = &sys_header;
|
|
|
|
|
pack_header_ptr = &pack_header;
|
|
|
|
|
OutputPadding (false);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MPEG_FORMAT_DVD:
|
|
|
|
|
/* A DVD System header is a weird thing. We seem to need to
|
|
|
|
|
include buffer info about streams 0xb8, 0xb9, 0xbf even if
|
|
|
|
|
they're not physically present but the buffers for the actual
|
|
|
|
|
video streams aren't included.
|
|
|
|
|
|
|
|
|
|
TODO: I have no idead about MPEG audio streams if present...
|
|
|
|
|
*/
|
|
|
|
|
{
|
|
|
|
|
DummyMuxStream dvd_0xb9_strm_dummy (0xb9, 1, video_buffer_size);
|
|
|
|
|
DummyMuxStream dvd_0xb8_strm_dummy (0xb8, 0, 4096);
|
|
|
|
|
DummyMuxStream dvd_0xbf_strm_dummy (0xbf, 1, 2048);
|
|
|
|
|
|
|
|
|
|
vector < MuxStream * >dvdmux;
|
|
|
|
|
vector < MuxStream * >::iterator muxstr;
|
|
|
|
|
dvdmux.push_back (&dvd_0xb9_strm_dummy);
|
|
|
|
|
dvdmux.push_back (&dvd_0xb8_strm_dummy);
|
|
|
|
|
unsigned int max_priv1_buffer = 0;
|
|
|
|
|
|
|
|
|
|
for (muxstr = amux.begin (); muxstr < amux.end (); ++muxstr) {
|
|
|
|
|
// We mux *many* substreams on PRIVATE_STR_1
|
|
|
|
|
// we set the system header buffer size to the maximum
|
|
|
|
|
// of all those we find
|
|
|
|
|
if ((*muxstr)->stream_id == PRIVATE_STR_1 && (*muxstr)->BufferSize () > max_priv1_buffer) {
|
|
|
|
|
max_priv1_buffer = (*muxstr)->BufferSize ();
|
|
|
|
|
} else
|
|
|
|
|
dvdmux.push_back (*muxstr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DummyMuxStream dvd_priv1_strm_dummy (PRIVATE_STR_1, 1, max_priv1_buffer);
|
|
|
|
|
|
|
|
|
|
if (max_priv1_buffer > 0)
|
|
|
|
|
dvdmux.push_back (&dvd_priv1_strm_dummy);
|
|
|
|
|
|
|
|
|
|
dvdmux.push_back (&dvd_0xbf_strm_dummy);
|
|
|
|
|
psstrm->CreateSysHeader (&sys_header, mux_rate, !vbr, false, true, true, dvdmux);
|
|
|
|
|
sys_header_ptr = &sys_header;
|
|
|
|
|
pack_header_ptr = &pack_header;
|
|
|
|
|
/* It is then followed up by a pair of PRIVATE_STR_2 packets which
|
|
|
|
|
we keep empty 'cos we don't know what goes there...
|
|
|
|
|
*/
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
/* Create the in-stream header in case it is needed */
|
|
|
|
|
psstrm->CreateSysHeader (&sys_header, mux_rate, !vbr, false, true, true, emux);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************
|
|
|
|
|
Program shutdown packets. Generate any irregular packets
|
|
|
|
|
needed at the end of the stream...
|
|
|
|
|
|
|
|
|
|
******************************************************************/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
OutputStream::OutputSuffix ()
|
|
|
|
|
{
|
|
|
|
|
psstrm->CreatePack (&pack_header, current_SCR, mux_rate);
|
|
|
|
|
psstrm->CreateSector (&pack_header, NULL, 0, pstrm, false, true, 0, 0, TIMESTAMPBITS_NO);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************
|
|
|
|
|
|
|
|
|
|
Main multiplex iteration.
|
|
|
|
|
Opens and closes all needed files and manages the correct
|
|
|
|
|
call od the respective Video- and Audio- packet routines.
|
|
|
|
|
The basic multiplexing is done here. Buffer capacity and
|
|
|
|
|
Timestamp checking is also done here, decision is taken
|
|
|
|
|
wether we should genereate a Video-, Audio- or Padding-
|
|
|
|
|
packet.
|
|
|
|
|
******************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
OutputStream::OutputMultiplex ()
|
|
|
|
|
{
|
|
|
|
|
for (;;) {
|
|
|
|
|
bool completion = true;
|
|
|
|
|
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str)
|
|
|
|
|
completion &= (*str)->MuxCompleted ();
|
|
|
|
|
if (completion)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/* A little state-machine for handling the transition from one
|
|
|
|
|
segment to the next
|
|
|
|
|
*/
|
|
|
|
|
bool runout_incomplete;
|
|
|
|
|
VideoStream *master;
|
|
|
|
|
|
|
|
|
|
switch (seg_state) {
|
|
|
|
|
|
|
|
|
|
/* Audio and slave video access units at end of segment.
|
|
|
|
|
If there are any audio AU's whose PTS implies they
|
|
|
|
|
should be played *before* the video AU starting the
|
|
|
|
|
next segement is presented we mux them out. Once
|
|
|
|
|
they're gone we've finished this segment so we write
|
|
|
|
|
the suffix switch file, and start muxing a new segment.
|
|
|
|
|
*/
|
|
|
|
|
case runout_segment:
|
|
|
|
|
runout_incomplete = false;
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str) {
|
|
|
|
|
runout_incomplete |= !(*str)->RunOutComplete ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (runout_incomplete)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/* Otherwise we write the stream suffix and start a new
|
|
|
|
|
stream file */
|
|
|
|
|
OutputSuffix ();
|
|
|
|
|
psstrm->NextFile ();
|
|
|
|
|
|
|
|
|
|
running_out = false;
|
|
|
|
|
seg_state = start_segment;
|
|
|
|
|
|
|
|
|
|
/* Starting a new segment.
|
|
|
|
|
We send the segment prefix, video and audio reciever
|
|
|
|
|
buffers are assumed to start empty. We reset the segment
|
|
|
|
|
length count and hence the SCR.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
case start_segment:
|
|
|
|
|
mjpeg_info ("New sequence commences...");
|
|
|
|
|
SetPosAndSCR (0);
|
|
|
|
|
MuxStatus (LOG_INFO);
|
|
|
|
|
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str) {
|
|
|
|
|
(*str)->AllDemuxed ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
packets_left_in_pack = packets_per_pack;
|
|
|
|
|
start_of_new_pack = true;
|
|
|
|
|
include_sys_header = sys_header_in_pack1;
|
|
|
|
|
buffers_in_video = always_buffers_in_video;
|
|
|
|
|
video_first = seg_starts_with_video & (vstreams.size () > 0);
|
|
|
|
|
OutputPrefix ();
|
|
|
|
|
|
|
|
|
|
/* Set the offset applied to the raw PTS/DTS of AU's to
|
|
|
|
|
make the DTS of the first AU in the master (video) stream
|
|
|
|
|
precisely the video delay plus whatever time we wasted in
|
|
|
|
|
the sequence pre-amble.
|
|
|
|
|
|
|
|
|
|
The DTS of the remaining streams are set so that
|
|
|
|
|
(modulo the relevant delay offset) they maintain the
|
|
|
|
|
same relative timing to the master stream.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
clockticks ZeroSCR;
|
|
|
|
|
|
|
|
|
|
if (vstreams.size () != 0)
|
|
|
|
|
ZeroSCR = vstreams[0]->au->DTS;
|
|
|
|
|
else
|
|
|
|
|
ZeroSCR = (*estreams)[0]->au->DTS;
|
|
|
|
|
|
|
|
|
|
for (str = vstreams.begin (); str < vstreams.end (); ++str)
|
|
|
|
|
(*str)->SetSyncOffset (video_delay + current_SCR - ZeroSCR);
|
|
|
|
|
for (str = astreams.begin (); str < astreams.end (); ++str)
|
|
|
|
|
(*str)->SetSyncOffset (audio_delay + current_SCR - ZeroSCR);
|
|
|
|
|
pstrm.nsec = 0;
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str)
|
|
|
|
|
(*str)->nsec = 0;
|
|
|
|
|
seg_state = mid_segment;
|
|
|
|
|
//for( str = estreams->begin(); str < estreams->end(); ++str )
|
|
|
|
|
//{
|
|
|
|
|
//mjpeg_info("STREAM %02x: SCR=%lld mux=%d reqDTS=%lld",
|
|
|
|
|
//(*str)->stream_id,
|
|
|
|
|
//current_SCR /300,
|
|
|
|
|
//(*str)->MuxPossible(current_SCR),
|
|
|
|
|
//(*str)->RequiredDTS()/300
|
|
|
|
|
//);
|
|
|
|
|
//}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case mid_segment:
|
|
|
|
|
/* Once we exceed our file size limit, we need to
|
|
|
|
|
start a new file soon. If we want a single stream we
|
|
|
|
|
simply switch.
|
|
|
|
|
|
|
|
|
|
Otherwise we're in the last gop of the current segment
|
|
|
|
|
(and need to start running streams out ready for a
|
|
|
|
|
clean continuation in the next segment).
|
|
|
|
|
TODO: runout_PTS really needs to be expressed in
|
|
|
|
|
sync delay adjusted units...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
master = vstreams.size () > 0 ? static_cast < VideoStream * >(vstreams[0]) : 0;
|
|
|
|
|
if (psstrm->FileLimReached ()) {
|
|
|
|
|
if (opt_multifile_segment || master == 0)
|
|
|
|
|
psstrm->NextFile ();
|
|
|
|
|
else {
|
|
|
|
|
if (master->NextAUType () == IFRAME) {
|
|
|
|
|
seg_state = runout_segment;
|
|
|
|
|
runout_PTS = master->NextRequiredPTS ();
|
|
|
|
|
mjpeg_debug ("Running out to (raw) PTS %lld SCR=%lld",
|
|
|
|
|
runout_PTS / 300, current_SCR / 300);
|
|
|
|
|
running_out = true;
|
|
|
|
|
seg_state = runout_segment;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (master != 0 && master->EndSeq ()) {
|
|
|
|
|
if (split_at_seq_end && master->Lookahead () != 0) {
|
|
|
|
|
if (!master->SeqHdrNext () || master->NextAUType () != IFRAME) {
|
|
|
|
|
mjpeg_error_exit1 ("Sequence split detected %d but no following sequence found...",
|
|
|
|
|
master->NextAUType ());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
runout_PTS = master->NextRequiredPTS ();
|
|
|
|
|
mjpeg_debug ("Running out to %lld SCR=%lld", runout_PTS / 300, current_SCR / 300);
|
|
|
|
|
MuxStatus (LOG_INFO);
|
|
|
|
|
running_out = true;
|
|
|
|
|
seg_state = runout_segment;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
padding_packet = false;
|
|
|
|
|
start_of_new_pack = (packets_left_in_pack == packets_per_pack);
|
|
|
|
|
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str) {
|
|
|
|
|
(*str)->DemuxedTo (current_SCR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Find the ready-to-mux stream with the most urgent DTS
|
|
|
|
|
//
|
|
|
|
|
ElementaryStream *despatch = 0;
|
|
|
|
|
clockticks earliest = 0;
|
|
|
|
|
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str) {
|
|
|
|
|
/* DEBUG
|
|
|
|
|
mjpeg_info("STREAM %02x: SCR=%lld mux=%d reqDTS=%lld",
|
|
|
|
|
(*str)->stream_id,
|
|
|
|
|
current_SCR /300,
|
|
|
|
|
(*str)->MuxPossible(current_SCR),
|
|
|
|
|
(*str)->RequiredDTS()/300
|
|
|
|
|
);
|
|
|
|
|
*/
|
|
|
|
|
if ((*str)->MuxPossible (current_SCR) &&
|
|
|
|
|
(!video_first || (*str)->Kind () == ElementaryStream::video)
|
|
|
|
|
) {
|
|
|
|
|
if (despatch == 0 || earliest > (*str)->RequiredDTS ()) {
|
|
|
|
|
despatch = *str;
|
|
|
|
|
earliest = (*str)->RequiredDTS ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (underrun_ignore > 0)
|
|
|
|
|
--underrun_ignore;
|
|
|
|
|
|
|
|
|
|
if (despatch) {
|
|
|
|
|
despatch->OutputSector ();
|
|
|
|
|
video_first = false;
|
|
|
|
|
if (current_SCR >= earliest && underrun_ignore == 0) {
|
|
|
|
|
mjpeg_warn ("Stream %02x: data will arrive too late sent(SCR)=%lld required(DTS)=%d",
|
|
|
|
|
despatch->stream_id, current_SCR / 300, (int) earliest / 300);
|
|
|
|
|
MuxStatus (LOG_WARN);
|
|
|
|
|
// Give the stream a chance to recover
|
|
|
|
|
underrun_ignore = 300;
|
|
|
|
|
++underruns;
|
|
|
|
|
if (underruns > 10 && !opt_ignore_underrun) {
|
|
|
|
|
mjpeg_error_exit1 ("Too many frame drops -exiting");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (despatch->nsec > 50 && despatch->Lookahead () != 0 && !running_out)
|
|
|
|
|
despatch->UpdateBufferMinMax ();
|
|
|
|
|
padding_packet = false;
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
//
|
|
|
|
|
// If we got here no stream could be muxed out.
|
|
|
|
|
// We therefore generate padding packets if necessary
|
|
|
|
|
// usually this is because reciever buffers are likely to be
|
|
|
|
|
// full.
|
|
|
|
|
//
|
|
|
|
|
if (vbr) {
|
|
|
|
|
//
|
|
|
|
|
// VBR: For efficiency we bump SCR up to five times or
|
|
|
|
|
// until it looks like buffer status will change
|
|
|
|
|
NextPosAndSCR ();
|
|
|
|
|
clockticks next_change = static_cast < clockticks > (0);
|
|
|
|
|
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str) {
|
|
|
|
|
clockticks change_time = (*str)->bufmodel.NextChange ();
|
|
|
|
|
|
|
|
|
|
if (next_change == 0 || change_time < next_change)
|
|
|
|
|
next_change = change_time;
|
|
|
|
|
}
|
|
|
|
|
unsigned int bumps = 5;
|
|
|
|
|
|
|
|
|
|
while (bumps > 0 && next_change > current_SCR + ticks_per_sector) {
|
|
|
|
|
NextPosAndSCR ();
|
|
|
|
|
--bumps;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Just output a padding packet
|
|
|
|
|
OutputPadding (false);
|
|
|
|
|
}
|
|
|
|
|
padding_packet = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Update the counter for pack packets. VBR is a tricky
|
|
|
|
|
case as here padding packets are "virtual" */
|
|
|
|
|
|
|
|
|
|
if (!(vbr && padding_packet)) {
|
|
|
|
|
--packets_left_in_pack;
|
|
|
|
|
if (packets_left_in_pack == 0)
|
|
|
|
|
packets_left_in_pack = packets_per_pack;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MuxStatus (LOG_DEBUG);
|
|
|
|
|
/* Unless sys headers are always required we turn them off after the first
|
|
|
|
|
packet has been generated */
|
|
|
|
|
include_sys_header = always_sys_header_in_pack;
|
|
|
|
|
|
|
|
|
|
pcomp = completed.begin ();
|
|
|
|
|
str = estreams->begin ();
|
|
|
|
|
while (str < estreams->end ()) {
|
|
|
|
|
if (!(*pcomp) && (*str)->MuxCompleted ()) {
|
|
|
|
|
mjpeg_info ("STREAM %02x completed @ %d.", (*str)->stream_id, (*str)->au->dorder);
|
|
|
|
|
MuxStatus (LOG_DEBUG);
|
|
|
|
|
(*pcomp) = true;
|
|
|
|
|
}
|
|
|
|
|
++str;
|
|
|
|
|
++pcomp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
OutputStream::Close ()
|
|
|
|
|
{
|
|
|
|
|
// Tidy up
|
|
|
|
|
OutputSuffix ();
|
|
|
|
|
psstrm->Close ();
|
|
|
|
|
mjpeg_info ("Multiplex completion at SCR=%lld.", current_SCR / 300);
|
|
|
|
|
MuxStatus (LOG_INFO);
|
|
|
|
|
for (str = estreams->begin (); str < estreams->end (); ++str) {
|
|
|
|
|
(*str)->Close ();
|
|
|
|
|
if ((*str)->nsec <= 50)
|
|
|
|
|
mjpeg_info ("BUFFERING stream too short for useful statistics");
|
|
|
|
|
else
|
|
|
|
|
mjpeg_info ("BUFFERING min %d Buf max %d", (*str)->BufferMin (), (*str)->BufferMax ());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (underruns > 0) {
|
|
|
|
|
mjpeg_error ("foo");
|
|
|
|
|
mjpeg_error_exit1 ("MUX STATUS: Frame data under-runs detected!");
|
|
|
|
|
} else {
|
|
|
|
|
mjpeg_info ("foo");
|
|
|
|
|
mjpeg_info ("MUX STATUS: no under-runs detected.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
Calculate the packet payload of the output stream at a certain timestamp.
|
|
|
|
|
@param strm the output stream
|
|
|
|
|
@param buffers the number of buffers
|
|
|
|
|
@param PTSstamp presentation time stamp
|
|
|
|
|
@param DTSstamp decoding time stamp
|
|
|
|
|
*/
|
|
|
|
|
unsigned int
|
|
|
|
|
OutputStream::PacketPayload (MuxStream & strm, bool buffers, bool PTSstamp, bool DTSstamp)
|
|
|
|
|
{
|
|
|
|
|
return psstrm->PacketPayload (strm, sys_header_ptr, pack_header_ptr, buffers, PTSstamp, DTSstamp)
|
|
|
|
|
- strm.StreamHeaderSize ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/***************************************************
|
|
|
|
|
|
|
|
|
|
WritePacket - Write out a normal packet carrying data from one of
|
|
|
|
|
the elementary stream being muxed.
|
|
|
|
|
@param max_packet_data_size the maximum packet data size allowed
|
|
|
|
|
@param strm output mux stream
|
|
|
|
|
@param buffers ?
|
|
|
|
|
@param PTSstamp presentation time stamp of the packet
|
|
|
|
|
@param DTSstamp decoding time stamp of the packet
|
|
|
|
|
@param timestamps ?
|
|
|
|
|
@param returns the written bytes/packets (?)
|
|
|
|
|
***************************************************/
|
|
|
|
|
|
|
|
|
|
unsigned int
|
|
|
|
|
OutputStream::WritePacket (unsigned int max_packet_data_size,
|
|
|
|
|
MuxStream & strm,
|
|
|
|
|
bool buffers, clockticks PTS, clockticks DTS, uint8_t timestamps)
|
|
|
|
|
{
|
|
|
|
|
unsigned int written = psstrm->CreateSector (pack_header_ptr,
|
|
|
|
|
sys_header_ptr,
|
|
|
|
|
max_packet_data_size,
|
|
|
|
|
strm,
|
|
|
|
|
buffers,
|
|
|
|
|
false,
|
|
|
|
|
PTS,
|
|
|
|
|
DTS,
|
|
|
|
|
timestamps);
|
|
|
|
|
|
|
|
|
|
NextPosAndSCR ();
|
|
|
|
|
return written;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/***************************************************
|
|
|
|
|
*
|
|
|
|
|
* WriteRawSector - Write out a packet carrying data for
|
|
|
|
|
* a control packet with irregular content.
|
|
|
|
|
@param rawsector data for the raw sector
|
|
|
|
|
@param length length of the raw sector
|
|
|
|
|
***************************************************/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
OutputStream::WriteRawSector (uint8_t * rawsector, unsigned int length)
|
|
|
|
|
{
|
|
|
|
|
//
|
|
|
|
|
// Writing raw sectors when packs stretch over multiple sectors
|
|
|
|
|
// is a recipe for disaster!
|
|
|
|
|
//
|
|
|
|
|
assert (packets_per_pack == 1);
|
|
|
|
|
psstrm->RawWrite (rawsector, length);
|
|
|
|
|
NextPosAndSCR ();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************
|
|
|
|
|
OutputPadding
|
|
|
|
|
|
|
|
|
|
generates Pack/Sys Header/Packet information for a
|
|
|
|
|
padding stream and saves the sector
|
|
|
|
|
|
|
|
|
|
We have to pass in a special flag to cope with appalling mess VCD
|
|
|
|
|
makes of audio packets (the last 20 bytes being dropped thing) 0 =
|
|
|
|
|
Fill the packet completetely. This include "audio packets" that
|
|
|
|
|
include no actual audio, only a system header and padding.
|
|
|
|
|
@param vcd_audio_pad flag for VCD audio padding
|
|
|
|
|
******************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
OutputStream::OutputPadding (bool vcd_audio_pad)
|
|
|
|
|
{
|
|
|
|
|
if (vcd_audio_pad)
|
|
|
|
|
psstrm->CreateSector (pack_header_ptr, sys_header_ptr,
|
|
|
|
|
0, vcdapstrm, false, false, 0, 0, TIMESTAMPBITS_NO);
|
|
|
|
|
else
|
|
|
|
|
psstrm->CreateSector (pack_header_ptr, sys_header_ptr,
|
|
|
|
|
0, pstrm, false, false, 0, 0, TIMESTAMPBITS_NO);
|
|
|
|
|
++pstrm.nsec;
|
|
|
|
|
NextPosAndSCR ();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************
|
|
|
|
|
* OutputGOPControlSector
|
|
|
|
|
* DVD System headers are carried in peculiar sectors carrying 2
|
|
|
|
|
* PrivateStream2 packets. We're sticking 0's in the packets
|
|
|
|
|
* as we have no idea what's supposed to be in there.
|
|
|
|
|
*
|
|
|
|
|
* Thanks to Brent Byeler who worked out this work-around.
|
|
|
|
|
*
|
|
|
|
|
******************************************************************/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
OutputStream::OutputDVDPriv2 ()
|
|
|
|
|
{
|
|
|
|
|
uint8_t *packet_size_field;
|
|
|
|
|
uint8_t *index;
|
2003-08-13 21:09:20 +00:00
|
|
|
|
uint8_t *sector_buf;
|
2003-07-26 03:01:58 +00:00
|
|
|
|
unsigned int tozero;
|
|
|
|
|
|
2003-08-13 21:09:20 +00:00
|
|
|
|
sector_buf = new uint8_t[sector_size];
|
|
|
|
|
|
2003-07-26 03:01:58 +00:00
|
|
|
|
assert (sector_size == 2048);
|
|
|
|
|
PS_Stream::BufferSectorHeader (sector_buf, pack_header_ptr, &sys_header, index);
|
|
|
|
|
PS_Stream::BufferPacketHeader (index, PRIVATE_STR_2, 2, // MPEG 2
|
|
|
|
|
false, // No buffers
|
|
|
|
|
0, 0, 0, // No timestamps
|
|
|
|
|
0, TIMESTAMPBITS_NO, packet_size_field, index);
|
|
|
|
|
tozero = sector_buf + 1024 - index;
|
|
|
|
|
memset (index, 0, tozero);
|
|
|
|
|
index += tozero;
|
|
|
|
|
PS_Stream::BufferPacketSize (packet_size_field, index);
|
|
|
|
|
|
|
|
|
|
PS_Stream::BufferPacketHeader (index, PRIVATE_STR_2, 2, // MPEG 2
|
|
|
|
|
false, // No buffers
|
|
|
|
|
0, 0, 0, // No timestamps
|
|
|
|
|
0, TIMESTAMPBITS_NO, packet_size_field, index);
|
|
|
|
|
tozero = sector_buf + 2048 - index;
|
|
|
|
|
memset (index, 0, tozero);
|
|
|
|
|
index += tozero;
|
|
|
|
|
PS_Stream::BufferPacketSize (packet_size_field, index);
|
|
|
|
|
|
|
|
|
|
WriteRawSector (sector_buf, sector_size);
|
2003-08-13 21:09:20 +00:00
|
|
|
|
|
|
|
|
|
delete sector_buf;
|
2003-07-26 03:01:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Local variables:
|
|
|
|
|
* c-file-style: "stroustrup"
|
|
|
|
|
* tab-width: 4
|
|
|
|
|
* indent-tabs-mode: nil
|
|
|
|
|
* End:
|
|
|
|
|
*/
|