/* * Utilities for reading program stream data. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "ps_fns.h" #include "ts_fns.h" #include "pes_fns.h" #include "pidint_fns.h" #include "misc_fns.h" #include "printing_fns.h" #define DEBUG 0 #define DEBUG_AC3 0 // How many bytes to look through for a pack header, before giving up #define PACK_HEADER_SEARCH_DISTANCE 100000 // If we lose where we are, should we look for the next PS pack header? // (instead of just giving up) #define RECOVER_BROKEN_PS 1 // ============================================================ // PS to TS datastructures // ============================================================ // Data we need to write PAT/PMT and otherwise manage our program streams // // DVD allows one video stream and up to 8 audio streams, // but we're only interested in a single video and a single audio stream struct program_data { // Information supplied by the user uint32_t transport_stream_id; uint32_t program_number; uint32_t pmt_pid; // PID to use for the PMT uint32_t pcr_pid; // PID to use for the PCR uint32_t video_pid; // PID to use for our single stream of video uint32_t audio_pid; // PID to use for our single stream of audio int video_stream; // Which stream id our video is (-1 means use first) int audio_stream; // Which stream id our audio is (-1 means use first) int want_ac3; // True means audio_stream is private_1/AC3 substream int audio_substream; // Which substream id if DVD and want_ac3 int video_type; // Is our video H.264, H.262, etc. (user must decide) int is_dvd; // Is our data DVD program stream (ditto) int output_dolby_as_dvb; // Output Dolby (AC-3) audio as DVB or ATSC? // Information derived from the data // PAT and PMT data pidint_list_p prog_list; pmt_p pmt; }; // ============================================================ // Read ahead support // ============================================================ /* * Read some more data into our read-ahead buffer. * * Returns 0 if it succeeds, EOF if the end-of-file is read, otherwise * 1 if some error occurs. */ static inline int get_more_data(PS_reader_p ps) { // Call `read` directly - we don't particularly mind if we get a "short" // read, since we'll just catch up later on #ifdef _WIN32 int len = _read(ps->input,&ps->data,PS_READ_AHEAD_SIZE); #else ssize_t len = read(ps->input,&ps->data,PS_READ_AHEAD_SIZE); #endif if (len == 0) return EOF; else if (len == -1) { fprint_err("### Error reading next bytes: %s\n",strerror(errno)); return 1; } ps->data_posn += ps->data_len; // length of the *last* buffer ps->data_len = len; ps->data_end = ps->data + len; // one beyond the last byte ps->data_ptr = ps->data; // start at the beginning return 0; } /* * Build a program stream context attached to an input file. This handles * read-ahead buffering for the PS. * * - `input` is the file stream to read from. * - If `quiet`, then don't report on ignored bytes at the start of the file * - `ps` is the new PS context * * Returns 0 if all goes well, 1 otherwise. */ extern int build_PS_reader(int input, int quiet, PS_reader_p *ps) { int err; PS_reader_p new = malloc(SIZEOF_PS_READER); if (new == NULL) { print_err("### Unable to allocate program stream read context\n"); return 1; } new->input = input; new->data_posn = 0; new->data_len = 0; new->start = 0; err = get_more_data(new); if (err) { print_err("### Unable to start reading from new PS read context\n"); free(new); return 1; } // And look for the first pack header err = find_PS_pack_header_start(new,FALSE,PACK_HEADER_SEARCH_DISTANCE, &(new->start)); if (err) { fprint_err("### File does not appear to be PS\n" " Cannot find PS pack header in first %d bytes of file\n", PACK_HEADER_SEARCH_DISTANCE); free(new); return 1; } // Seeking won't work on standard input, so don't even try if (input != STDIN_FILENO) { // But we don't *really* want to have read its start yet err = seek_using_PS_reader(new,new->start); if (err) { print_err("### Error seeking to start of first pack header\n"); free(new); return 1; } } if (!quiet && new->start != 0) fprint_err("!!! PS file does not start with pack header\n" " First PS pack header is at " OFFSET_T_FORMAT "\n",new->start); *ps = new; return 0; } /* * Tidy up the PS read-ahead context after we've finished with it. * * Specifically: * * - free the datastructure * - set `ps` to NULL * * Does not close the associated file. */ extern void free_PS_reader(PS_reader_p *ps) { if (*ps != NULL) { (*ps)->input = -1; // "forget" our input free(*ps); *ps = NULL; } } /* * Open a PS file for reading. * * - `name` is the name of the file. If this is NULL, then standard input * is used. * - If `quiet`, then don't report on ignored bytes at the start of the file * - `ps` is the new PS context * * Returns 0 if all goes well, 1 otherwise. */ extern int open_PS_file(char *name, int quiet, PS_reader_p *ps) { int f; if (name == NULL) f = STDIN_FILENO; else { f = open_binary_file(name,FALSE); if (f == -1) return 1; } return build_PS_reader(f,quiet,ps); } /* * Close a PS file, and free the reader context * * (Doesn't close the file if it was standard input) * * Returns 0 if all goes well, 1 otherwise. */ extern int close_PS_file(PS_reader_p *ps) { if ((*ps)->input != STDIN_FILENO) { int err = close_file((*ps)->input); if (err) return 1; } free_PS_reader(ps); return 0; } /* * Given a program stream, attempt to determine if it holds H.262 or H.264 * data. * * Leaves the PS rewound to its "start". * * NOTE: It is probably better to use determine_PS_video_type(). * * - `ps` is the program stream to check (assumed just to have been * opened/built). This cannot be standard input, as it must be * seekable. * - `is_h264` is the result * * Returns 0 if all goes well, 1 if there was an error (including the * stream not appearing to be either). */ extern int determine_if_PS_is_h264(PS_reader_p ps, int *is_h264) { int err; PES_reader_p reader; // Try to decide what sort of data stream we have // The simplest (albeit clumsy) way to do this is to use technology // that already does this for us - i.e., build a temporary PES reader // around our file // It's then safe to build the temporary PES reader err = build_PS_PES_reader(ps,FALSE,FALSE,&reader); if (err) { print_err("### Error trying to determine PS stream type\n"); return 1; } // Which knows the file type *is_h264 = reader->is_h264; (void) free_PES_reader(&reader); // And then make sure that our file position is where we think it should be err = rewind_program_stream(ps); if (err) { print_err("### Error rewinding PS file after determining its type\n"); return 1; } return 0; } /* * Given a program stream, attempt to determine what type of video data it * contains. * * Leaves the PS rewound to its "start". * * - `ps` is the program stream to check (assumed just to have been * opened/built). This cannot be standard input, as it must be * seekable. * - `video_type` is the result. Calls determine_PES_video_type(). * * Returns 0 if all goes well, 1 if there was an error (including the * stream not appearing to be either). */ extern int determine_PS_video_type(PS_reader_p ps, int *video_type) { int err; PES_reader_p reader; // Try to decide what sort of data stream we have // The simplest (albeit clumsy) way to do this is to use technology // that already does this for us - i.e., build a temporary PES reader // around our file // It's then safe to build the temporary PES reader err = build_PS_PES_reader(ps,FALSE,FALSE,&reader); if (err) { print_err("### Error trying to determine PS stream type\n"); return 1; } // Which thinks it knows the file type *video_type = reader->video_type; (void) free_PES_reader(&reader); // And then make sure that our file position is where we think it should be err = rewind_program_stream(ps); if (err) { print_err("### Error rewinding PS file after determining its type\n"); return 1; } return 0; } /* * Seek within the PS file. * * Note that if the intent is to *rewind* to the start of the PS data, * then `rewind_program_stream` should be used instead, as offset 0 is * not necessarily the same as the start of the program stream. * * - `ps` is the PS read-ahead context * - `posn` is the file offset to seek to * * Return 0 if all goes well, 1 if something goes wrong */ extern int seek_using_PS_reader(PS_reader_p ps, offset_t posn) { int err = seek_file(ps->input,posn); if (err) return 1; ps->data_posn = posn; ps->data_len = 0; return get_more_data(ps); } /* * Rewind the PS context to the remembered "start of data" * * Returns 0 if all goes well, 1 if something goes wrong */ extern int rewind_program_stream(PS_reader_p ps) { return seek_using_PS_reader(ps,ps->start); } /* * Retrieve the next N bytes from the program stream, into an existing array. * * - `ps` is the PS read-ahead context * - `num_bytes` is how many bytes to read * - `buffer` is the buffer to read them into * - `posn` is the offset of said data in the file (NULL if the value is not * wanted). * * Returns 0 if all goes well, EOF if end-of-file is encountered before all * of the bytes have been read, 1 if some other error occurred. */ static int read_PS_bytes(PS_reader_p ps, int num_bytes, byte *buffer, offset_t *posn) { int err; int offset = 0; int num_bytes_wanted = num_bytes; int num_bytes_left = ps->data_end - ps->data_ptr; if (posn != NULL) *posn = ps->data_posn + (ps->data_ptr - ps->data); for (;;) { if (num_bytes_left < num_bytes_wanted) { memcpy(&(buffer[offset]),ps->data_ptr,num_bytes_left); offset += num_bytes_left; num_bytes_wanted -= num_bytes_left; err = get_more_data(ps); if (err) return err; num_bytes_left = ps->data_len; } else { memcpy(&(buffer[offset]),ps->data_ptr,num_bytes_wanted); ps->data_ptr += num_bytes_wanted; break; } } return 0; } // ============================================================ // Primitives // ============================================================ /* * Print out a stream id in a manner consistent with the PS usages * of the stream id values. */ extern void print_stream_id(int is_msg, byte stream_id) { byte number; char *str = NULL; switch (stream_id) { // H.222 Program stream specific codes case 0xB9: str = "PS MPEG_program_end_code"; break; case 0xBA: str = "PS Pack header start code"; break; case 0xBB: str = "PS System header start code"; break; case 0xBC: str = "PS Program stream map"; break; case 0xFF: str = "PS Program stream directory"; break; // Other "simple" values from H.222 Table 2-18, page 32 case 0xBD: str = "Private stream 1"; break; case 0xBE: str = "Padding stream"; break; case 0xBF: str = "Private stream 2"; break; case 0xF0: str = "ECM stream"; break; case 0xF1: str = "EMM stream"; break; case 0xF2: str = "DSMCC stream"; break; case 0xF3: str = "13522 stream"; break; case 0xF4: str = "H.222.1 A stream"; break; case 0xF5: str = "H.222.1 B stream"; break; case 0xF6: str = "H.222.1 C stream"; break; case 0xF7: str = "H.222.1 D stream"; break; case 0xF8: str = "H.222.1 E stream"; break; case 0xF9: str = "Ancillary stream"; break; case 0x00: str = "H.262 Picture"; break; case 0xB2: str = "H.262 User data"; break; case 0xB3: str = "H.262 Sequence header"; break; case 0xB4: str = "H.262 Sequence error"; break; case 0xB5: str = "H.262 Extension"; break; case 0xB7: str = "H.262 Sequence end"; break; case 0xB8: str = "H.262 Group start"; break; default: str = NULL; break; } if (str != NULL) fprint_msg_or_err(is_msg,str); else if (stream_id >= 0xC0 && stream_id <=0xDF) { number = stream_id & 0x1F; fprint_msg_or_err(is_msg,"Audio stream 0x%02X",number); } else if (stream_id >= 0xE0 && stream_id <= 0xEF) { number = stream_id & 0x0F; fprint_msg_or_err(is_msg,"Video stream 0x%X",number); } else if (stream_id >= 0xFC && stream_id <= 0xFE) fprint_msg_or_err(is_msg,"Reserved data stream"); else fprint_msg_or_err(is_msg,"Unrecognised stream id"); } /* * Look for the start (the first 4 bytes) of the next program stream packet. * * Assumes that (for some reason) alignment has been lost, and thus it is * necessary to scan forwards to find the next 00 00 01 prefix. * * Otherwise equivalent to a call of `read_PS_packet_start`. * * - `ps` is the PS read-ahead context we're reading from * - if `verbose`, then we want to explain what we're doing * - if `max` is non-zero, then it is the maximum number of bytes * to scan before giving up. * - `posn` is the file offset of the start of the packet * - `stream_id` is the identifying byte, after the 00 00 01 prefix. Note * that this is set correctly if MPEG_program_end_code was read, and is * 0 if an error occurred. * * Returns: * * 0 if it succeeds, * * EOF if EOF is read, or an MPEG_program_end_code is read, or * * 1 if some error (including the first 3 bytes not being 00 00 01) occurs. */ extern int find_PS_packet_start(PS_reader_p ps, int verbose, uint32_t max, offset_t *posn, byte *stream_id) { int err; byte prev1 = 0xff; byte prev2 = 0xff; byte prev3 = 0xff; uint32_t count = 0; *stream_id = 0; for (;;) { byte *ptr; for (ptr = ps->data_ptr; ptr < ps->data_end; ptr++) { if (prev3 == 0x00 && prev2 == 0x00 && prev1 == 0x01) { if (*ptr == 0xB9) // MPEG_program_end_code { if (verbose) print_msg("Stopping at MPEG_program_end_code\n"); *stream_id = 0xB9; return EOF; } else { *stream_id = *ptr; *posn = ps->data_posn + (ptr - ps->data) - 3; ps->data_ptr = ptr + 1; return 0; } } if (max > 0) { count ++; if (count > max) { fprint_err("### No PS packet start found in %d bytes\n",max); return 1; } } prev3 = prev2; prev2 = prev1; prev1 = *ptr; } // We've run out of data - get some more err = get_more_data(ps); if (err) return err; } } /* * Look for the next PS pack header. * * Equivalent to calling `find_PS_packet_start` until `stream_id` is 0xBA * (in other words, equivalent to having read the pack header start with * `read_PS_packet_start`). * * If you want to call `read_PS_packet_start` to read this pack header start * in again, then call ``seek_using_PS_reader(ps,posn)`` to reposition ready * to read it. * * - `ps` is the PS read-ahead context we're reading from * - if `verbose`, then the 00 00 01 XX sequences found will be logged * to stderr, indicating the progress of our search * - if `max` is non-zero, then it is the maximum number of bytes * to scan before giving up. * - `posn` is the file offset of the start of the packet found * * Returns: * * 0 if it succeeds, * * EOF if EOF is read, or an MPEG_program_end_code is read, or * * 1 if some error (including the first 3 bytes not being 00 00 01) occurs. */ extern int find_PS_pack_header_start(PS_reader_p ps, int verbose, uint32_t max, offset_t *posn) { int err; byte stream_id = 0; while (stream_id != 0xBA) { err = find_PS_packet_start(ps,verbose,max,posn,&stream_id); if (err) { print_err("### Error looking for PS pack header (0xBA)\n"); return 1; } if (verbose) { fprint_err(" Found: stream id %02X at " OFFSET_T_FORMAT " (", stream_id,*posn); print_stream_id(FALSE,stream_id); print_err(")\n"); } } return 0; } /* * Read in (the rest of) a PS packet according to its length. * * Suitable for use reading PS PES packets and PS system header packets. * * NOTE that the `data` buffer in the `packet` is realloc'ed by this * function. It is thus important to ensure that the `packet` datastructure * contains a NULL pointer for said buffer before the first call of this * function. * * - `ps` is the PS read-ahead context we're reading from * - `stream_id` identifies what sort of packet it is * - `packet` is the packet we're reading the PES packet into. * * Returns 0 if it succeeds, EOF if it unexpectedly reads end-of-file, and 1 * if some other error occurs. `packet->data` will be NULL if EOF is returned. */ extern int read_PS_packet_body(PS_reader_p ps, byte stream_id, PS_packet_p packet) { int err; byte buf[2]; // First, the packet length err = read_PS_bytes(ps,2,buf,NULL); if (err) { fprint_err("### %s reading PS packet length\n", (err==EOF?"Unexpected end of file":"Error")); if (packet->data!=NULL) free(packet->data); packet->data = NULL; return err; } packet->packet_length = (buf[0] << 8) | buf[1]; #if DEBUG fprint_msg("Packet length %d\n",packet->packet_length); #endif // Remember that the packet length is the length of data // *after* the packet length field. Also, it is only allowed // to be 0 within a Transport Stream, so it should never be 0 for us // - but let's check anyway if (packet->packet_length == 0) { print_err("### Packet has length 0 - not allowed in PS\n"); if (packet->data!=NULL) free(packet->data); packet->data = NULL; return 1; } // Since we are, in general, expecting to write the packet out again // at some point, it is convenient to also store the leading bytes #if 0 // XXX naughty stuff packet->data = realloc(packet->data,packet->packet_length + 6 + 10); #else packet->data = realloc(packet->data,packet->packet_length + 6); #endif if (packet->data == NULL) { print_err("### Unable to allocate PS packet data buffer\n"); return 1; } packet->data_len = packet->packet_length + 6; // So let us reestablish said leading bytes packet->data[0] = 0; packet->data[1] = 0; packet->data[2] = 1; packet->data[3] = stream_id; packet->data[4] = buf[0]; packet->data[5] = buf[1]; // And now we can read in the rest of the packet's data err = read_PS_bytes(ps,packet->packet_length,&(packet->data[6]),NULL); if (err) { fprint_err("### %s reading rest of PS packet\n", (err==EOF?"Unexpected end of file":"Error")); if (packet->data!=NULL) free(packet->data); packet->data = NULL; return err; } #if 0 // XXX naughty stuff - add some trailing zero bytes packet->data[packet->data_len + 0] = 0; packet->data[packet->data_len + 1] = 0; packet->data[packet->data_len + 2] = 0; packet->data[packet->data_len + 3] = 0; packet->data[packet->data_len + 4] = 0; packet->data[packet->data_len + 5] = 0; packet->data[packet->data_len + 6] = 0; packet->data[packet->data_len + 7] = 0; packet->data[packet->data_len + 8] = 0; packet->data[packet->data_len + 9] = 0; packet->packet_length += 10; packet->data_len += 10; #endif return 0; } /* * Read in the body of the pack header (but *not* the system header packets * therein). * * - `ps` is the PS read-ahead context we're reading from * - `hdr` is the packet we've read * * Returns 0 if it succeeds, EOF if it unexpectedly reads end-of-file, and * 1 if some other error occurs. */ extern int read_PS_pack_header_body(PS_reader_p ps, PS_pack_header_p hdr) { int err; byte dummy[8]; // a 3 bit length means no more than 7 stuffing bytes // Read just the first 8 bytes, in case it's an MPEG-1 pack header err = read_PS_bytes(ps,8,hdr->data,NULL); if (err) { fprint_err("### %s reading body of PS pack header\n", (err==EOF?"Unexpected end of file":"Error")); return err; } if ((hdr->data[0] & 0xF0) == 0x20) { #if DEBUG print_msg("ISO/IEC 11171-1/MPEG-1 pack header\n"); print_data(TRUE,"Pack header",hdr->data,8,8); #endif hdr->pack_stuffing_length = 0; // since it doesn't exist hdr->scr = (((uint64_t)(hdr->data[0] & 0x09)) << 29) | (((uint64_t) hdr->data[1] ) << 22) | (((uint64_t)(hdr->data[2] & 0xFE)) << 14) | (((uint64_t) hdr->data[3] ) << 7) | (((uint64_t)(hdr->data[4] & 0xFE)) >> 1); hdr->program_mux_rate = (((uint32_t)(hdr->data[5] & 0x7F)) << 15) | (((uint32_t) hdr->data[6] ) << 7) | (((uint32_t)(hdr->data[7] & 0xFE)) >> 1); // In MPEG-1, SCR = NINT(SysClockFreq * t[i]) % 2**33 // where SysClockFreq = 90,000 Hz // // In H.222.0, SCR = SCRbase[i] * 300 + SCRext[i] // = (((SysClockFreq * t[i]) DIV 300) % 2**33) * 300 + // ((SysClockFreq * t[i]) DIV 1) % 300 // where SysClockFreq = 27,000,000 Hz // Fudge these to match H.222.0 in case anyone tries to use them later on hdr->scr = hdr->scr * 300; hdr->scr_base = hdr->scr / 300; hdr->scr_extn = 0; // i.e., hdr->scr % 300 } else { #if DEBUG print_msg("ISO/IEC 13818-1/H.222.0 pack header\n"); #endif err = read_PS_bytes(ps,2,&(hdr->data[8]),NULL); if (err) { fprint_err("### %s reading last 2 bytes of body of PS pack header\n", (err==EOF?"Unexpected end of file":"Error")); return err; } #if DEBUG print_data(TRUE,"Pack header",hdr->data,10,10); #endif hdr->scr_base = (((uint64_t)(hdr->data[0] & 0x38)) << 27) | (((uint64_t)(hdr->data[0] & 0x03)) << 28) | (((uint64_t) hdr->data[1] ) << 20) | (((uint64_t)(hdr->data[2] & 0xF8)) << 12) | (((uint64_t)(hdr->data[2] & 0x03)) << 13) | (((uint64_t) hdr->data[3] ) << 5) | (((uint64_t)(hdr->data[4] & 0xF8)) >> 3); hdr->scr_extn = (((uint32_t)(hdr->data[4] & 0x03)) << 7) | (((uint32_t) hdr->data[5] ) >> 1); hdr->scr = hdr->scr_base * 300 + hdr->scr_extn; hdr->program_mux_rate = (((uint32_t)hdr->data[6] << 14)) | (((uint32_t)hdr->data[7] << 6)) | (((uint32_t)hdr->data[8] >> 2)); hdr->pack_stuffing_length = hdr->data[9] & 0x07; } #if DEBUG // XXX fprint_msg("Pack header body: scr_base " LLU_FORMAT ", scr_extn %u, scr " LLU_FORMAT ", mux rate %u, stuffing %d\n", hdr->scr_base,hdr->scr_extn,hdr->scr,hdr->program_mux_rate, hdr->pack_stuffing_length); #endif // And ignore that many stuffing bytes... if (hdr->pack_stuffing_length > 0) { err = read_PS_bytes(ps,hdr->pack_stuffing_length,dummy,NULL); if (err) { fprint_err("### %s reading PS pack header stuffing bytes\n", (err==EOF?"Unexpected end of file":"Error")); return err; } } return 0; } /* * Clear the contents of a PS packet datastructure. Frees the internal * `data` array. */ extern void clear_PS_packet(PS_packet_p packet) { if (packet->data != NULL) { free(packet->data); packet->data = NULL; packet->data_len = 0; } packet->packet_length = 0; } /* * Tidy up and free a PS packet datastructure after we've finished with it. * * Empties the PS packet datastructure, frees it, and sets `unit` to NULL. * * If `unit` is already NULL, does nothing. */ extern void free_PS_packet(PS_packet_p *packet) { if (*packet == NULL) return; clear_PS_packet(*packet); free(*packet); *packet = NULL; } /* * Read in the start (the first 4 bytes) of the next program stream packet. * * If the bytes read don't appear to be valid (i.e., they do not start with * the 00 00 01 prefix), then the next pack header will be sought and read in. * * Note that sequences of 00 bytes before the 00 00 01 will be ignored. * * - `ps` is the PS read-ahead context we're reading from * - if `verbose`, then we want to explain what we're doing * - `posn` is the file offset of the start of the packet * - `stream_id` is the identifying byte, after the 00 00 01 prefix. Note * that this is set correctly if MPEG_program_end_code was read, and is * 0 if an error occurred. * * Returns: * * 0 if it succeeds, * * EOF if EOF is read, or an MPEG_program_end_code is read, * * 2 if the bytes read are not 00 00 01 `stream_id`, or * * 1 if some other error occurs. */ extern int read_PS_packet_start(PS_reader_p ps, int verbose, offset_t *posn, byte *stream_id) { int err; byte buf[4]; *stream_id = 0; err = read_PS_bytes(ps,4,buf,posn); if (err == EOF) return EOF; else if (err) { print_err("### Error reading start of PS packet\n"); return 1; } // It's not uncommon to get a sequence of 00 bytes between packs // (in particular, after an audio pack). We don't really want to grumble // about such things, but just to cope if (buf[0] == 0 && buf[1] == 0 && buf[2] == 0) { #if 0 // XXX fprint_msg("// %02x %02x %02x %02x\n",buf[0],buf[1],buf[2],buf[3]); #endif while (buf[2] == 0) // we already know buf[0] and buf[1] are zero { buf[2] = buf[3]; err = read_PS_bytes(ps,1,&(buf[3]),posn); if (err == EOF) return EOF; else if (err) { print_err("### Error skipping 00 bytes before start of PS packet\n"); return 1; } } #if 0 // XXX fprint_msg("\\\\ %02x %02x %02x %02x\n",buf[0],buf[1],buf[2],buf[3]); #endif } if (buf[0] != 0 || buf[1] != 0 || buf[2] != 1) { fprint_err("!!! PS packet at " OFFSET_T_FORMAT " should start " "00 00 01, but instead found %02X %02X %02X\n", *posn,buf[0],buf[1],buf[2]); #if RECOVER_BROKEN_PS print_err("!!! Attempting to find next PS pack header\n"); err = find_PS_pack_header_start(ps,TRUE,0,posn); if (err == EOF) return EOF; else if (err) { print_err("### Error trying to find start of next pack header\n"); return 1; } fprint_err("!!! Continuing with PS pack header at " OFFSET_T_FORMAT "\n",*posn); *stream_id = 0xBA; return 0; #else return 2; #endif } *stream_id = buf[3]; #if DEBUG fprint_msg("Packet at " OFFSET_T_FORMAT ", stream id %02X (",*posn,*stream_id); print_stream_id(TRUE,*stream_id); print_msg(")\n"); #endif if (buf[3] == 0xB9) // MPEG_program_end_code { if (verbose) print_msg("Stopping at MPEG_program_end_code\n"); return EOF; } else return 0; } /* Determine the details of an AC3 stream, by looking at its start * * Naughtily assume that `data` is long enough... */ static inline void determine_ac3_details(byte *data, int verbose, byte *bsmod, byte *acmod) { // The end of the syncinfo int fscod = (data[4] & 0xC0) >> 6; int frmsizecode = (data[4] & 0x3F); // The start of the bit stream info int bsid = (data[5] & 0xF8) >> 3; *bsmod = (data[5] & 0x07); *acmod = (data[6] & 0xC0) >> 6; if (verbose) { fprint_msg(" fscod %x (sample rate %skHz)\n",fscod, (fscod==0?"48":fscod==1?"44.1":fscod==2?"32":"??")); fprint_msg(" frmsizecode %x\n",frmsizecode); fprint_msg(" bsid %x (%s)\n",bsid, (bsid==8?"standard":bsid==6?"A52a alternate": bsid<8?"standard subset":"???")); fprint_msg(" bsmod %x (%s)\n",*bsmod,BSMOD_STR(*bsmod,*acmod)); fprint_msg(" acmod %x (%s)\n",*acmod,ACMOD_STR(*acmod)); } } /* * Inspect the given PS packet, and determine if it contains AC3 or DTS audio data. * * - `packet` is the packet's data, already established as private_data_1 * - `is_dvd` is true if the data should be interpreted as DVD data * - if `verbose`, report on the details of what we find out * - `substream_index` returns the substream's index, taken from the low * nibble of the substream id, and adjusted to start at 0. This will be * a value in the range 0-7 for DTS, AC3 and LPCM, and in the range 0-1F * (0-31) for subpictures. * - for AC3, `bsmod` and `acmod` return the appropriate quantities, * otherwise they are 0. * * Returns one of the SUBSTREAM_* values. */ extern int identify_private1_data(struct PS_packet *packet, int is_dvd, int verbose, int *substream_index, byte *bsmod, byte *acmod) { // If this packet contains the start of a data packet, then // try to determine if it is AC-3 int PES_header_data_length = packet->data[6+2]; int what = SUBSTREAM_OTHER; byte *data; #if 0 // Hmm - the data never does seem to have the pusi bit set if (verbose) { int data_alignment_indicator = (packet->data[6] & 0x04); if (data_alignment_indicator) print_msg("*** Data aligned\n"); else print_msg("--- Data not aligned\n"); fprint_msg("*** PES header data length 0x%x (%d)\n", PES_header_data_length,PES_header_data_length); } #endif *substream_index = 0; *bsmod = 0; *acmod = 0; // This *should* be the start of the underlying packet // Note that PES_header_data_length should not be 0 for // non-video streams... data = packet->data + 6 + 3 + PES_header_data_length; // DVD data has some structure within private_stream_1 if (is_dvd) { int substream_id, frame_count, offset; substream_id = data[0]; frame_count = data[1]; offset = (data[2] << 8) | data[3]; if (0x20 <= substream_id && substream_id <= 0x3F) { what = SUBSTREAM_SUBPICTURES; *substream_index = substream_id - 0x20; } else if (0x80 <= substream_id && substream_id <= 0x87) { what = SUBSTREAM_AC3; *substream_index = substream_id - 0x80; } else if (0x88 <= substream_id && substream_id <= 0x8F) { what = SUBSTREAM_DTS; *substream_index = substream_id - 0x88; } else if (0xA0 <= substream_id && substream_id <= 0xA7) { what = SUBSTREAM_LPCM; *substream_index = substream_id - 0xA0; } if (verbose) { fprint_msg(">>> substream_id %02x (%s index %d)\n",substream_id, (what==SUBSTREAM_AC3?"AC3": what==SUBSTREAM_DTS?"DTS": what==SUBSTREAM_LPCM?"LPCM": what==SUBSTREAM_SUBPICTURES?"subpictures":"???"), *substream_index); fprint_msg(">>> frame_count %02x (%d)\n",frame_count,frame_count); fprint_msg(">>> offset %04x (%d)\n",offset,offset); } // For AC3 and DTS, it's easy to check that it *does* appear to be what it // says, so let's do so if (what == SUBSTREAM_AC3 || what == SUBSTREAM_DTS) { int packet_length = (packet->data[4] << 8) | packet->data[5]; byte *sub_data = data + 3 + offset; // Roughly check if the offset looks plausible // TODO: make this check more robust! if (sub_data >= packet->data + packet_length) // leave off the + 6 { // Looks like it's a silly offset, so probably NOT what we want if (verbose) fprint_msg("*** expected %s, but data at %p is beyond" " packet->end at %p\n",(what==SUBSTREAM_DTS?"DTS":"AC3"), sub_data,packet->data+6+packet_length); what = SUBSTREAM_ERROR; // we definitely mustn't try to interpret it! } else if (what == SUBSTREAM_AC3 && !(sub_data[0] == 0x0B && sub_data[1] == 0x77)) { fprint_msg("*** expected AC3 sync 0x0B77, but found 0x%02x%02x\n", sub_data[0],sub_data[1]); what = SUBSTREAM_ERROR; } else if (what == SUBSTREAM_DTS && !(sub_data[0] == 0x7F && sub_data[1] == 0xFE && sub_data[2] == 0x80 && sub_data[3] == 0x01)) { fprint_msg("*** expected DTS sync 0x7FFE8001," " but found 0x%02x%02x%02x%02x\n", sub_data[0],sub_data[1],sub_data[2],sub_data[3]); what = SUBSTREAM_ERROR; } if (what == SUBSTREAM_AC3) determine_ac3_details(sub_data,verbose,bsmod,acmod); } } else { // For non-DVD data, we have to decide for ourselves what we've got if (data[0] == 0x0B && data[1] == 0x77) { what = SUBSTREAM_AC3; determine_ac3_details(data,verbose,bsmod,acmod); } else if (data[0] == 0x7F && data[1] == 0xFE && data[2] == 0x80 && data[3] == 0x01) what = SUBSTREAM_DTS; } if (verbose) { switch (what) { case SUBSTREAM_AC3: print_msg("*** Looks like AC3\n"); break; case SUBSTREAM_DTS: print_msg("*** Looks like DTS\n"); break; case SUBSTREAM_LPCM: print_msg("*** Looks like LPCM\n"); break; case SUBSTREAM_SUBPICTURES: print_msg("*** Looks like sub-pictures\n"); break; case SUBSTREAM_OTHER: fprint_msg("*** Other substream: %02x %02x %02x %02x\n", data[0],data[1],data[2],data[3]); break; default: fprint_msg("*** Error recognising substream: %02x %02x %02x %02x\n", data[0],data[1],data[2],data[3]); break; } } return what; } // ============================================================ // PS to TS functions // ============================================================ /* * Write out a video packet * * - `output` is the transport stream we're writing to * - `header` is the data from the PS pack header, including its SCR data * - `stream_id` is the stream id of the PS packet we're writing * - `packet` is the PS packet itself * - `prog_data` is the programming information we're using * - `num_video_ignored` will be be updated if we ignore this video packet * - `num_video_written` will be updated if we don't * - if `verbose` then we want to output diagnostic information * - if `quiet` then we want to be as quiet as we can * * Returns 0 if all went well, 1 if something went wrong. */ static int write_video(TS_writer_p output, struct PS_pack_header *header, byte stream_id, struct PS_packet *packet, struct program_data *prog_data, int *num_video_ignored, int *num_video_written, int verbose, int quiet) { int err; // Unless the user has requested a particular video stream, we want // to use the first we find... // (For DVD this should also be the only stream, since DVD only allows // one video stream, but that's not for us to check.) if (prog_data->video_stream == -1) { prog_data->video_stream = stream_id; } else if (stream_id != prog_data->video_stream) { static int ignored_stream[NUMBER_VIDEO_STREAMS] = {0}; int this_stream = stream_id & 0x0F; if (!ignored_stream[this_stream]) { ignored_stream[this_stream] = TRUE; if (!quiet) fprint_msg("Ignoring video stream 0x%x (%d)\n",this_stream,this_stream); } (*num_video_ignored) ++; return 0; } if (*num_video_written == 0) { if (!quiet) fprint_msg("Video: stream %d, PID 0x%03x, stream type 0x%02x\n" " %s\n", stream_id & 0x0F,prog_data->video_pid,prog_data->video_type, h222_stream_type_str(prog_data->video_type)); err = add_stream_to_pmt(prog_data->pmt,prog_data->video_pid, prog_data->video_type,0,NULL); if (err) return 1; prog_data->pmt->version_number ++; // And it makes sense to write out our (updated) program // information before we write out the first packet of // the new video (!) err = write_pat_and_pmt(output, prog_data->transport_stream_id, prog_data->prog_list, prog_data->pmt_pid, prog_data->pmt); if (err) { print_err("### Error writing TS program data before video packet\n"); return 1; } } // This is our video stream - output it as such err = write_PES_as_TS_PES_packet(output,packet->data,packet->data_len, prog_data->video_pid, DEFAULT_VIDEO_STREAM_ID, TRUE,header->scr_base,header->scr_extn); if (err) return 1; (*num_video_written) ++; if (verbose) { print_msg("v"); flush_msg(); } return 0; } /* * Write out the data for our DVD private_stream_1 audio packet * * - `output` is the transport stream we're writing to * - `packet` is the PS packet itself * - `prog_data` is the programming information we're using * * Returns 0 if all went well, 1 if something went wrong. */ static int write_DVD_AC3_data(TS_writer_p output, struct PS_packet *packet, struct program_data *prog_data) { // DVD private_stream_1 is packaged as substreams - we need to unpack // it before we output it // Basically, looking at the substream offset: // 0 means there is no first frame in this packet (i.e., the data // is the middle/end of a frame // 1 means the data *starts* with the first frame (to which any // PTS applies) // N means that the frame to which the PTS applies starts at // offset N-1 int PES_header_data_length = packet->data[6+2]; byte *data = packet->data + 6 + 3 + PES_header_data_length; int data_len = packet->data_len - 6 - 3 - PES_header_data_length; //int substream_id = data[0]; int frame_count = data[1]; // frames starting in this packet int offset = (data[2] << 8) | data[3]; // If there is no PTS in this packet, do we need to split, even // if the offset is > 1? I'll assume not... int got_pts; uint64_t pts; int PES_packet_length = (packet->data[4] << 8) | packet->data[5]; int err = find_PTS_in_PES(packet->data,packet->data_len,&got_pts,&pts); if (err) { print_err("### Error looking for PTS in PES packet\n"); return 1; } #if DEBUG_AC3 fprint_msg(".. frame_count=%d, offset=%4d, got_pts=%d ", frame_count,offset,got_pts); if (frame_count > 0 && offset > 0) { if (data[offset+3]==0x0B && data[offset+4]==0x77) print_msg("(frame is AC3)\n"); else fprint_msg("(frame appears to start %02x %02x)\n", data[offset+3],data[offset+4]); } #endif if (frame_count == 0 || offset <= 1 || !got_pts) { // We can output the data from this packet "unsplit". // However, first we need to create a new packet that does not // contain the DVD substream header - this is most easily done // by (a) copying the audio data bytes "down" over the header // we want to lose, and (b) adjusting the various packet length // fields appropriately #if DEBUG_AC3 fprint_msg("move data down 4, leaving %4d\n",data_len-4); #endif (void) memmove(data,data+4,data_len-4); // 4 bytes of substream header // After copying, need to remember to adjust the packet length PES_packet_length -= 4; // still 4 bytes of substream header packet->data[4] = (PES_packet_length & 0xFF00) >> 8; packet->data[5] = (PES_packet_length & 0x00FF); // And we then have something suitable for outputting as-is err = write_PES_as_TS_PES_packet(output,packet->data,packet->data_len-4, prog_data->audio_pid, PRIVATE1_AUDIO_STREAM_ID, FALSE,0,0); if (err) return 1; } else { // We need to output the data *before* the "first" packet // in a plain PES packet, by itself, and then output the // packet to which the PTS applies as a separate PES packet. #if DEBUG_AC3 fprint_msg("write first %4d bytes, then move data down %4d, leaving %4d\n", offset-1,3+offset,data_len-3-offset); #endif // First, the part before the first packet... err = write_ES_as_TS_PES_packet(output,data+4,offset-1, prog_data->audio_pid, PRIVATE1_AUDIO_STREAM_ID); if (err) return 1; // And we can then do the "move the data down" trick // Remember that offset 1 means the first byte after the substream // header, which we would normally expect to be offset 0... (void) memmove(data,data+3+offset,data_len-3-offset); // After copying, need to remember to adjust the packet length PES_packet_length -= 3 + offset; packet->data[4] = (PES_packet_length & 0xFF00) >> 8; packet->data[5] = (PES_packet_length & 0x00FF); // And we then have something suitable for outputting as-is err = write_PES_as_TS_PES_packet(output,packet->data, packet->data_len-3-offset, prog_data->audio_pid, PRIVATE1_AUDIO_STREAM_ID, FALSE,0,0); if (err) return 1; } return 0; } /* * Write out an audio packet * * - `output` is the transport stream we're writing to * - `stream_id` is the stream id of the PS packet we're writing * - `packet` is the PS packet itself * - `prog_data` is the programming information we're using * - `num_audio_ignored` will be be updated if we ignore this audio packet * - `num_audio_written` will be updated if we don't * - if `verbose` then we want to output diagnostic information * - if `quiet` then we want to be as quiet as we can * * Returns 0 if all went well, 1 if something went wrong. */ static int write_audio(TS_writer_p output, byte stream_id, struct PS_packet *packet, struct program_data *prog_data, int *num_audio_ignored, int *num_audio_written, int verbose, int quiet) { int err; int substream_index = -1; byte bsmod = 0xFF; byte asmod = 0xFF; int is_h222_pes = IS_H222_PES(packet->data); // For MPEG audio, unless the user has requested a particular audio stream, // we want to use the first we find. // For AC3, we only want audio from the requested substream // (DVD allows up to 8 audio streams, but we're only outputting one. // // Note that we assume that any audio data using private stream 1 to // transmit DVD-style audio packets will also be using PES (H.222.0) // packeting - i.e., *not* using MPEG-1 packets. This allows the code // to "dissect" DVD audio packets to be simpler, and seems a reasonable // assumption. if (prog_data->audio_stream == -1) // take the first audio stream we find... { if (stream_id == PRIVATE1_AUDIO_STREAM_ID && is_h222_pes) { // Find out what type of data this is int what = identify_private1_data(packet,prog_data->is_dvd,verbose, &substream_index,&bsmod,&asmod); if (what != SUBSTREAM_AC3) { // We're not interested in it return 0; } prog_data->audio_stream = stream_id; prog_data->audio_substream = substream_index; // only meaningful for DVD } else // some other ("normal") audio stream prog_data->audio_stream = stream_id; } else if (stream_id != prog_data->audio_stream) { if (!quiet) { static int ignored_stream[NUMBER_AUDIO_STREAMS] = {0}; int this_stream = stream_id & 0x1F; if (!ignored_stream[this_stream]) { ignored_stream[this_stream] = TRUE; fprint_msg("Ignoring audio stream 0x%x (%d)\n",this_stream,this_stream); } } (*num_audio_ignored) ++; return 0; } else if (prog_data->is_dvd && stream_id == PRIVATE1_AUDIO_STREAM_ID && prog_data->audio_stream == PRIVATE1_AUDIO_STREAM_ID && is_h222_pes) { // Check if this is the right substream int what = identify_private1_data(packet,prog_data->is_dvd,verbose, &substream_index,&bsmod,&asmod); if (what != SUBSTREAM_AC3) { if (!quiet) { #define MAX_IGNORED_NON_AC3 10 // report the first 10 substreams we're ignoring static uint32_t ignored_non_AC3[MAX_IGNORED_NON_AC3] = {0}; uint32_t lookfor = (what << 16) | substream_index; int ii; for (ii = 0; ii < MAX_IGNORED_NON_AC3; ii++) { if (ignored_non_AC3[ii] == lookfor) break; // we've reported it before else if (ignored_non_AC3[ii] == 0) { // We've not reported it before, and have room to remember it ignored_non_AC3[ii] = lookfor; fprint_msg("Ignoring %sprivate_stream_1 substream 0x%x (%d)" " containing %s\n", (SUBSTREAM_IS_AUDIO(what)?"":"non-audio "), substream_index,substream_index,SUBSTREAM_STR(what)); break; } } } if (SUBSTREAM_IS_AUDIO(what)) (*num_audio_ignored) ++; return 0; } else if (substream_index != prog_data->audio_substream) { if (!quiet) { static int ignored_ac3_substream[NUMBER_AC3_SUBSTREAMS] = {0}; if (!ignored_ac3_substream[substream_index]) { ignored_ac3_substream[substream_index] = TRUE; fprint_msg("Ignoring private_stream_1 substream 0x%x (%d) " "containing AC3\n",substream_index,substream_index); } } (*num_audio_ignored) ++; return 0; } } if (*num_audio_written == 0) { byte audio_stream_type; if (stream_id == PRIVATE1_AUDIO_STREAM_ID) { if (prog_data->output_dolby_as_dvb) audio_stream_type = DVB_DOLBY_AUDIO_STREAM_TYPE; else audio_stream_type = ATSC_DOLBY_AUDIO_STREAM_TYPE; } else audio_stream_type = MPEG2_AUDIO_STREAM_TYPE; if (!quiet) { if (stream_id == PRIVATE1_AUDIO_STREAM_ID) { print_msg("Audio: private stream 1,"); if (prog_data->is_dvd && is_h222_pes) fprint_msg(" substream %d,",prog_data->audio_substream); fprint_msg(" PID 0x%03x, AC-3 (Dolby)\n",prog_data->audio_pid); fprint_msg(" %s\n audio coding mode %s\n", BSMOD_STR(bsmod,asmod),ACMOD_STR(asmod)); } else { fprint_msg("Audio: stream %d, PID 0x%03x, stream type 0x%02x = %s\n", stream_id & 0x1F,prog_data->audio_pid,audio_stream_type, h222_stream_type_str(audio_stream_type)); } } if (audio_stream_type == DVB_DOLBY_AUDIO_STREAM_TYPE) { byte desc[] = {0x6A, 0x01, 0x00}; int desc_len = 3; err = add_stream_to_pmt(prog_data->pmt,prog_data->audio_pid, audio_stream_type,desc_len,desc); } else if (audio_stream_type == ATSC_DOLBY_AUDIO_STREAM_TYPE) { byte desc[] = {0x05, 0x04, 0x41, 0x43, 0x2D, 0x33}; int desc_len = 6; err = add_stream_to_pmt(prog_data->pmt,prog_data->audio_pid, audio_stream_type,desc_len,desc); } else err = add_stream_to_pmt(prog_data->pmt,prog_data->audio_pid, audio_stream_type,0,NULL); if (err) return 1; prog_data->pmt->version_number ++; // And it makes sense to write out our (updated) program // information before we write out the first packet of // the new audio err = write_pat_and_pmt(output, prog_data->transport_stream_id, prog_data->prog_list, prog_data->pmt_pid, prog_data->pmt); if (err) { print_err("### Error writing TS program data before audio packet\n"); return 1; } } if (prog_data->is_dvd && stream_id == PRIVATE1_AUDIO_STREAM_ID && is_h222_pes) { // Unpack the DVD substreams before outputting them err = write_DVD_AC3_data(output,packet,prog_data); if (err) return 1; } else { err = write_PES_as_TS_PES_packet(output,packet->data,packet->data_len, prog_data->audio_pid, (prog_data->want_ac3? PRIVATE1_AUDIO_STREAM_ID: DEFAULT_AUDIO_STREAM_ID), FALSE,0,0); if (err) return 1; } (*num_audio_written) ++; if (verbose) { print_msg("a"); flush_msg(); } return 0; } /* * Read program stream and write transport stream * * - `ps` is the program stream * - `output` is the transport stream * - `pad_start` is the number of filler TS packets to start the output * with. * - `program_repeat` is how often (after how many PS packs) to repeat * the program information (PAT/PMT) * - `is_dvd` should be true if this input represents DVD data; i.e., with * private_stream_1 used for AC-3/DTS/etc., and with substream headers * therein. * - `video_stream` indicates which video stream we want - i.e., the stream * with id 0xE0 + . -1 means the first encountered. * - `audio_stream` indicates which audio stream we want. If `want_ac3_audio` * is false, then this will be the stream with id 0xC0 + , * or -1 for the first audio stream encountered. * - if `want_ac3_audio` is true, then if `is_dvd` is true, then we want * audio from private_stream_1 (0xBD) with substream id , * otherwise we ignore `audio_stream` and assume that all data in * private_stream_1 is the audio we want. * - `output_dolby_as_dvb` should be true if Dolby (AC-3) audio (if selected) should * be output using the DVB stream type 0x06, false if using the ATSC stream * type 0x81. This is ignored if the audio being output is not Dolby. * - `pmt_pid` is the PID of the PMT to write * - `pcr_pid` is the PID of the TS unit containing the PCR * - `video_pid` is the PID for the video we write * - `keep_audio` is true if the audio stream should be output, false if * it should be ignored * - `audio_pid` is the PID for the audio we write * - if `max` is non-zero, then we want to stop reading after we've read * `max` packs * - if `verbose` then we want to output diagnostic information * - if `quiet` then we want to be as quiet as we can * * Returns 0 if all went well, 1 if something went wrong. */ static int _ps_to_ts(PS_reader_p ps, TS_writer_p output, struct program_data *prog_data, int pad_start, int program_repeat, int keep_audio, int max, int verbose, int quiet) { int ii, err; offset_t posn = 0; // The location in the input file of the current packet int count = 0; // Number of PS packets byte stream_id; // The packet's stream id int end_of_file = FALSE; // Summary data int num_packs = 0; int num_audio_written = 0; int num_video_written = 0; int num_video_ignored = 0; int num_audio_ignored = 0; struct PS_packet packet = {0}; struct PS_pack_header header = {0}; // Start off our output with some null packets - this is in case the // reader needs some time to work out its byte alignment before it starts // looking for 0x47 bytes for (ii=0; iipmt_pid,prog_data->pcr_pid); // Read the start of the first packet (we confidently expect this // to be a pack header) err = read_PS_packet_start(ps,verbose,&posn,&stream_id); if (err == EOF) { print_err("### Error reading first pack header\n"); print_err(" Unexpected end of PS at start of stream\n"); return 1; } else if (err) { print_err("### Error reading first pack header\n"); return 1; } count ++; if (stream_id != 0xba) { print_err("### Program stream does not start with pack header\n"); fprint_err(" First packet has stream id %02X (",stream_id); print_stream_id(FALSE,stream_id); print_err(")\n"); return 1; } // But given that, we can now happily loop reading in packs // I *think* using this macro makes the code marginally more readable, // and it helps emphasise that the code *is* identical each time #define READ_NEXT_PS_PACKET_START \ err = read_PS_packet_start(ps,FALSE,&posn,&stream_id); \ if (err == EOF) \ { \ end_of_file = TRUE; \ break; \ } \ else if (err) \ return 1; \ count ++; for (;;) { int num_system_headers = 0; if (max > 0 && num_packs >= max) { if (verbose) fprint_msg("Stopping after %d packs\n",num_packs); return 0; } num_packs ++; // Write out our program data every so often, to give the reader // a chance to resynchronise with our program stream if (num_packs % program_repeat == 0) { if (verbose) { print_msg("PGM"); flush_msg(); } err = write_pat_and_pmt(output, prog_data->transport_stream_id, prog_data->prog_list, prog_data->pmt_pid, prog_data->pmt); if (err) { print_err("### Error writing out TS program data\n"); return 1; } } err = read_PS_pack_header_body(ps,&header); if (err) { fprint_err("### Error reading data for pack header starting at " OFFSET_T_FORMAT "\n",posn); return 1; } // Look at the start of the next packet READ_NEXT_PS_PACKET_START; // If it's a system header, ignore it if (stream_id == 0xbb) { err = read_PS_packet_body(ps,stream_id,&packet); if (err) { fprint_err("### Error reading system header starting at " OFFSET_T_FORMAT "\n",posn); return 1; } num_system_headers ++; READ_NEXT_PS_PACKET_START; } if (end_of_file) break; // Then read the data packets while (stream_id != 0xba) // i.e., until the start of the next pack { err = read_PS_packet_body(ps,stream_id,&packet); if (err) { fprint_err("### Error reading PS packet starting at " OFFSET_T_FORMAT "\n",posn); return 1; } if (IS_AUDIO_STREAM_ID(stream_id)) { if (keep_audio) { err = write_audio(output,stream_id,&packet,prog_data, &num_audio_ignored,&num_audio_written, verbose,quiet); if (err) { fprint_err("### Error writing audio packet at " OFFSET_T_FORMAT " to TS\n",posn); return 1; } } } else if (IS_VIDEO_STREAM_ID(stream_id)) { err = write_video(output,&header,stream_id,&packet,prog_data, &num_video_ignored,&num_video_written,verbose,quiet); if (err) { fprint_err("### Error writing video packet at " OFFSET_T_FORMAT " to TS\n",posn); return 1; } } else if (verbose) { // For the moment, we ignore program stream map (0xBC) and // program stream directory (0xFF), and indeed everything else } READ_NEXT_PS_PACKET_START; } if (end_of_file) break; } clear_PS_packet(&packet); if (verbose) print_msg("\n"); if (!quiet) { fprint_msg("Packets (total): %6d\n",count); fprint_msg("Packs: %6d\n",num_packs); fprint_msg("Video packets written: %6d\n",num_video_written); fprint_msg("Audio packets written: %6d\n",num_audio_written); if (num_video_ignored > 0) fprint_msg("Video packets ignored: %6d\n",num_video_ignored); if (num_audio_ignored > 0) fprint_msg("Audio packets ignored: %6d\n",num_audio_ignored); } return 0; } /* * Read program stream and write transport stream * * - `ps` is the program stream * - `output` is the transport stream * - `pad_start` is the number of filler TS packets to start the output * with. * - `program_repeat` is how often (after how many PS packs) to repeat * the program information (PAT/PMT) * - `video_type` indicates what type of video is being transferred. It should * be VIDEO_H264, VIDEO_H262, etc. * - `is_dvd` should be true if this input represents DVD data; i.e., with * private_stream_1 used for AC-3/DTS/etc., and with substream headers * therein. * - `video_stream` indicates which video stream we want - i.e., the stream * with id 0xE0 + . -1 means the first encountered. * - `audio_stream` indicates which audio stream we want. If `want_ac3_audio` * is false, then this will be the stream with id 0xC0 + , * or -1 for the first audio stream encountered. * - if `want_ac3_audio` is true, then if `is_dvd` is true, then we want * audio from private_stream_1 (0xBD) with substream id , * otherwise we ignore `audio_stream` and assume that all data in * private_stream_1 is the audio we want. * - `output_dolby_as_dvb` should be true if Dolby (AC-3) audio (if selected) should * be output using the DVB stream type 0x06, false if using the ATSC stream * type 0x81. This is ignored if the audio being output is not Dolby. * - `pmt_pid` is the PID of the PMT to write * - `pcr_pid` is the PID of the TS unit containing the PCR * - `video_pid` is the PID for the video we write * - `keep_audio` is true if the audio stream should be output, false if * it should be ignored * - `audio_pid` is the PID for the audio we write * - if `max` is non-zero, then we want to stop reading after we've read * `max` packs * - if `verbose` then we want to output diagnostic information * - if `quiet` then we want to be as quiet as we can * * Returns 0 if all went well, 1 if something went wrong. */ extern int ps_to_ts(PS_reader_p ps, TS_writer_p output, int pad_start, int program_repeat, int video_type, int is_dvd, int video_stream, int audio_stream, int want_ac3_audio, int output_dolby_as_dvb, uint32_t pmt_pid, uint32_t pcr_pid, uint32_t video_pid, int keep_audio, uint32_t audio_pid, int max, int verbose, int quiet) { int err; struct program_data prog_data = {0}; prog_data.transport_stream_id = 1; prog_data.program_number = 1; prog_data.pmt_pid = pmt_pid; prog_data.pcr_pid = pcr_pid; prog_data.video_pid = video_pid; prog_data.audio_pid = audio_pid; prog_data.video_type = video_type; prog_data.output_dolby_as_dvb = output_dolby_as_dvb; prog_data.video_stream = video_stream; prog_data.want_ac3 = want_ac3_audio; prog_data.is_dvd = is_dvd; if (want_ac3_audio) { prog_data.audio_stream = PRIVATE1_AUDIO_STREAM_ID; if (is_dvd) prog_data.audio_substream = audio_stream; else prog_data.audio_substream = -1; // use the first we find } else prog_data.audio_stream = audio_stream; // We have one program - we'll make it program 1 #define PROGRAM_NUMBER 1 err = build_pidint_list(&prog_data.prog_list); if (err) return 1; err = append_to_pidint_list(prog_data.prog_list,pmt_pid,PROGRAM_NUMBER); if (err) { free_pidint_list(&prog_data.prog_list); return 1; } prog_data.pmt = build_pmt(PROGRAM_NUMBER,0,pcr_pid); if (err) { free_pidint_list(&prog_data.prog_list); return 1; } err = _ps_to_ts(ps,output,&prog_data,pad_start,program_repeat,keep_audio, max,verbose,quiet); if (err) { free_pidint_list(&prog_data.prog_list); free_pmt(&prog_data.pmt); return 1; } free_pidint_list(&prog_data.prog_list); free_pmt(&prog_data.pmt); return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: