|
|
@ -0,0 +1,191 @@ |
|
|
|
/***************************************************************************** |
|
|
|
* mpeg_restamp.c: Restamps PCR and PTS/DTS to make them appear continuous |
|
|
|
***************************************************************************** |
|
|
|
* Copyright (C) 2013 VideoLAN |
|
|
|
* |
|
|
|
* Authors: Christophe Massiot <massiot@via.ecp.fr> |
|
|
|
* |
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining |
|
|
|
* a copy of this software and associated documentation files (the |
|
|
|
* "Software"), to deal in the Software without restriction, including |
|
|
|
* without limitation the rights to use, copy, modify, merge, publish, |
|
|
|
* distribute, sublicense, and/or sell copies of the Software, and to |
|
|
|
* permit persons to whom the Software is furnished to do so, subject |
|
|
|
* to the following conditions: |
|
|
|
* |
|
|
|
* The above copyright notice and this permission notice shall be |
|
|
|
* included in all copies or substantial portions of the Software. |
|
|
|
* |
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
|
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
|
|
*****************************************************************************/ |
|
|
|
|
|
|
|
/* Limitation: this supposes the PES header is not fragmented over several |
|
|
|
* TS packets, and that the stream is not scrambled. */ |
|
|
|
|
|
|
|
#include <unistd.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <stdint.h> |
|
|
|
#include <stdbool.h> |
|
|
|
#include <inttypes.h> |
|
|
|
#include <string.h> |
|
|
|
#include <stdio.h> |
|
|
|
#include <errno.h> |
|
|
|
#include <time.h> |
|
|
|
|
|
|
|
#include <bitstream/mpeg/ts.h> |
|
|
|
#include <bitstream/mpeg/pes.h> |
|
|
|
|
|
|
|
#define UINT33_MAX UINT64_C(8589934592) |
|
|
|
#define TS_CLOCK_MAX UINT33_MAX |
|
|
|
#define CLOCK_FREQ UINT64_C(90000) |
|
|
|
#define MAX_PCR_INTERVAL (CLOCK_FREQ / 10) |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* Local declarations |
|
|
|
*****************************************************************************/ |
|
|
|
static uint64_t i_last_pcr_date; |
|
|
|
static uint64_t i_last_pcr = TS_CLOCK_MAX; |
|
|
|
static uint64_t i_pcr_offset = 0; |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* get_date: |
|
|
|
*****************************************************************************/ |
|
|
|
static uint64_t get_date(void) |
|
|
|
{ |
|
|
|
struct timespec ts; |
|
|
|
|
|
|
|
/* Try to use POSIX monotonic clock if available */ |
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts) == EINVAL) |
|
|
|
/* Run-time fallback to real-time clock (always available) */ |
|
|
|
(void)clock_gettime(CLOCK_REALTIME, &ts); |
|
|
|
|
|
|
|
return ((uint64_t)ts.tv_sec * CLOCK_FREQ) |
|
|
|
+ ((uint64_t)ts.tv_nsec * (CLOCK_FREQ / 10000) / 100000); |
|
|
|
} |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* handle_pcr: |
|
|
|
*****************************************************************************/ |
|
|
|
static void handle_pcr(uint8_t *p_ts, uint64_t i_date) |
|
|
|
{ |
|
|
|
uint64_t i_pcr = tsaf_get_pcr(p_ts); |
|
|
|
bool b_discontinuity = tsaf_has_discontinuity(p_ts); |
|
|
|
|
|
|
|
if (i_last_pcr == TS_CLOCK_MAX) |
|
|
|
i_last_pcr = i_pcr; |
|
|
|
else { |
|
|
|
/* handle 2^33 wrap-arounds */ |
|
|
|
uint64_t i_delta = |
|
|
|
(TS_CLOCK_MAX + i_pcr - |
|
|
|
(i_last_pcr % TS_CLOCK_MAX)) % TS_CLOCK_MAX; |
|
|
|
if (i_delta <= MAX_PCR_INTERVAL && !b_discontinuity) |
|
|
|
i_last_pcr = i_pcr; |
|
|
|
else { |
|
|
|
i_last_pcr += i_date - i_last_pcr_date; |
|
|
|
i_last_pcr %= TS_CLOCK_MAX; |
|
|
|
i_pcr_offset += TS_CLOCK_MAX + i_last_pcr - i_pcr; |
|
|
|
i_pcr_offset %= TS_CLOCK_MAX; |
|
|
|
i_last_pcr = i_pcr; |
|
|
|
} |
|
|
|
} |
|
|
|
i_last_pcr_date = i_date; |
|
|
|
if (!i_pcr_offset) |
|
|
|
return; |
|
|
|
|
|
|
|
i_pcr += i_pcr_offset; |
|
|
|
i_pcr %= TS_CLOCK_MAX; |
|
|
|
tsaf_set_pcr(p_ts, i_pcr); |
|
|
|
tsaf_clear_discontinuity(p_ts); |
|
|
|
} |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* handle_ts: |
|
|
|
*****************************************************************************/ |
|
|
|
static uint64_t handle_ts(uint64_t i_ts) |
|
|
|
{ |
|
|
|
i_ts += i_pcr_offset; |
|
|
|
i_ts %= TS_CLOCK_MAX; |
|
|
|
return i_ts; |
|
|
|
} |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* Main loop |
|
|
|
*****************************************************************************/ |
|
|
|
static void usage(const char *psz) |
|
|
|
{ |
|
|
|
fprintf(stderr, "usage: %s [<mtu>] < <input file> [> <output>]\n", psz); |
|
|
|
exit(EXIT_FAILURE); |
|
|
|
} |
|
|
|
|
|
|
|
int main(int i_argc, char **ppsz_argv) |
|
|
|
{ |
|
|
|
if (i_argc > 2 || i_argc < 1 || |
|
|
|
(!strcmp(ppsz_argv[1], "-h") || !strcmp(ppsz_argv[1], "--help"))) |
|
|
|
usage(ppsz_argv[0]); |
|
|
|
|
|
|
|
unsigned int i_mtu = TS_SIZE; |
|
|
|
if (i_argc == 2) { |
|
|
|
i_mtu = strtoul(ppsz_argv[1], NULL, 0); |
|
|
|
if (!i_mtu) |
|
|
|
usage(ppsz_argv[0]); |
|
|
|
} |
|
|
|
|
|
|
|
for ( ; ; ) { |
|
|
|
uint8_t p_buffer[i_mtu]; |
|
|
|
ssize_t i_read = read(STDIN_FILENO, p_buffer, i_mtu); |
|
|
|
uint64_t date = get_date(); |
|
|
|
if (i_read == -1) { |
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) |
|
|
|
continue; |
|
|
|
exit(EXIT_FAILURE); |
|
|
|
} |
|
|
|
|
|
|
|
if (!i_read) |
|
|
|
exit(EXIT_SUCCESS); |
|
|
|
|
|
|
|
uint8_t *p_ts = p_buffer; |
|
|
|
while (p_ts < p_buffer + i_mtu && ts_validate(p_ts)) { |
|
|
|
if (ts_has_adaptation(p_ts) && ts_get_adaptation(p_ts) && |
|
|
|
tsaf_has_pcr(p_ts)) |
|
|
|
handle_pcr(p_ts, date); |
|
|
|
|
|
|
|
uint16_t header_size = TS_HEADER_SIZE + |
|
|
|
(ts_has_adaptation(p_ts) ? 1 : 0) + |
|
|
|
ts_get_adaptation(p_ts); |
|
|
|
if (ts_get_unitstart(p_ts) && ts_has_payload(p_ts) && |
|
|
|
header_size + PES_HEADER_SIZE_PTS <= TS_SIZE && |
|
|
|
pes_validate(p_ts + header_size) && |
|
|
|
pes_get_streamid(p_ts + header_size) != |
|
|
|
PES_STREAM_ID_PRIVATE_2 && |
|
|
|
pes_validate_header(p_ts + header_size) && |
|
|
|
pes_has_pts(p_ts + header_size) && |
|
|
|
pes_validate_pts(p_ts + header_size)) { |
|
|
|
pes_set_pts(p_ts + header_size, |
|
|
|
handle_ts(pes_get_pts(p_ts + header_size))); |
|
|
|
|
|
|
|
if (header_size + PES_HEADER_SIZE_PTSDTS <= TS_SIZE && |
|
|
|
pes_has_dts(p_ts + header_size) && |
|
|
|
pes_validate_dts(p_ts + header_size)) |
|
|
|
pes_set_dts(p_ts + header_size, |
|
|
|
handle_ts(pes_get_dts(p_ts + header_size))); |
|
|
|
} |
|
|
|
|
|
|
|
p_ts += TS_SIZE; |
|
|
|
} |
|
|
|
|
|
|
|
ssize_t i_written = write(STDOUT_FILENO, p_buffer, i_mtu); |
|
|
|
if (i_written == -1) { |
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) |
|
|
|
continue; |
|
|
|
exit(EXIT_FAILURE); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return EXIT_FAILURE; |
|
|
|
} |