411 lines
13 KiB
C
411 lines
13 KiB
C
// ITU-T H.222.0(06/2012)
|
||
// Information technology – Generic coding of moving pictures and associated audio information: Systems
|
||
// 2.4.3.1 Transport stream(p34)
|
||
|
||
#include "mpeg-ts-proto.h"
|
||
#include "mpeg-util.h"
|
||
#include "mpeg-ts.h"
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <assert.h>
|
||
#include <errno.h>
|
||
|
||
#define PCR_DELAY 0 //(700 * 90) // 700ms
|
||
#define PAT_PERIOD (400 * 90) // 500ms
|
||
|
||
#define TS_HEADER_LEN 4 // 1-bytes sync byte + 2-bytes PID + 1-byte CC
|
||
#define PES_HEADER_LEN 6 // 3-bytes packet_start_code_prefix + 1-byte stream_id + 2-bytes PES_packet_length
|
||
|
||
#define TS_PAYLOAD_UNIT_START_INDICATOR 0x40
|
||
|
||
// adaptation flags
|
||
#define AF_FLAG_PCR 0x10
|
||
#define AF_FLAG_RANDOM_ACCESS_INDICATOR 0x40 // random_access_indicator
|
||
|
||
typedef struct _mpeg_ts_enc_context_t {
|
||
struct pat_t pat;
|
||
int h264_h265_with_aud;
|
||
|
||
int64_t pat_period;
|
||
int64_t pcr_period;
|
||
int64_t pcr_clock; // last pcr time
|
||
|
||
struct mpeg_ts_func_t func;
|
||
void *param;
|
||
|
||
uint8_t payload[1024]; // maximum PAT/PMT payload length
|
||
} mpeg_ts_enc_context_t;
|
||
|
||
static int mpeg_ts_write_section_header(const mpeg_ts_enc_context_t *ts, int pid, unsigned int *cc, const void *payload, size_t len)
|
||
{
|
||
uint8_t *data = NULL;
|
||
data = ts->func.alloc(ts->param, TS_PACKET_SIZE);
|
||
if (!data) {
|
||
return ENOMEM;
|
||
}
|
||
|
||
assert(len < TS_PACKET_SIZE - 5); // TS-header + pointer
|
||
|
||
// TS Header
|
||
|
||
// sync_byte
|
||
data[0] = 0x47;
|
||
// transport_error_indicator = 0
|
||
// payload_unit_start_indicator = 1
|
||
// transport_priority = 0
|
||
data[1] = 0x40 | ((pid >> 8) & 0x1F);
|
||
data[2] = pid & 0xFF;
|
||
// transport_scrambling_control = 0x00
|
||
// adaptation_field_control = 0x01-No adaptation_field, payload only, 0x03-adaptation and payload
|
||
data[3] = 0x10 | (*cc & 0x0F);
|
||
*cc = (*cc + 1) % 16; // update continuity_counter
|
||
|
||
// // Adaptation
|
||
// if(len < TS_PACKET_SIZE - 5)
|
||
// {
|
||
// data[3] |= 0x20; // with adaptation
|
||
// data[4] = TS_PACKET_SIZE - len - 5 - 1; // 4B-Header + 1B-pointer + 1B-self
|
||
// if(data[4] > 0)
|
||
// {
|
||
// // adaptation
|
||
// data[5] = 0; // no flag
|
||
// memset(data+6, 0xFF, data[4]-1);
|
||
// }
|
||
// }
|
||
|
||
// pointer (payload_unit_start_indicator==1)
|
||
//data[TS_PACKET_SIZE-len-1] = 0x00;
|
||
data[4] = 0x00;
|
||
|
||
// TS Payload
|
||
//memmove(data + TS_PACKET_SIZE - len, payload, len);
|
||
memmove(data + 5, payload, len);
|
||
memset(data + 5 + len, 0xff, TS_PACKET_SIZE - len - 5);
|
||
|
||
ts->func.write(ts->param, data, TS_PACKET_SIZE);
|
||
ts->func.free(ts->param, data);
|
||
return 0;
|
||
}
|
||
|
||
static int ts_write_pes(mpeg_ts_enc_context_t *tsctx, const struct pmt_t *pmt, struct pes_t *stream, const uint8_t *payload, size_t bytes)
|
||
{
|
||
// 2.4.3.6 PES packet
|
||
// Table 2-21
|
||
|
||
size_t len = 0;
|
||
int start = 1; // first packet
|
||
uint8_t *p = NULL;
|
||
uint8_t *data = NULL;
|
||
uint8_t *header = NULL;
|
||
|
||
while (bytes > 0) {
|
||
data = tsctx->func.alloc(tsctx->param, TS_PACKET_SIZE);
|
||
if (!data) {
|
||
return ENOMEM;
|
||
}
|
||
|
||
// TS Header
|
||
data[0] = 0x47; // sync_byte
|
||
data[1] = 0x00 | ((stream->pid >> 8) & 0x1F);
|
||
data[2] = stream->pid & 0xFF;
|
||
data[3] = 0x10 | (stream->cc & 0x0F); // no adaptation, payload only
|
||
data[4] = 0; // clear adaptation length
|
||
data[5] = 0; // clear adaptation flags
|
||
|
||
stream->cc = (stream->cc + 1) % 16;
|
||
|
||
// 2.7.2 Frequency of coding the program clock reference
|
||
// http://www.bretl.com/mpeghtml/SCR.HTM
|
||
// the maximum between PCRs is 100ms.
|
||
if (start && stream->pid == pmt->PCR_PID) {
|
||
data[3] |= 0x20; // +AF
|
||
data[5] |= AF_FLAG_PCR; // +PCR_flag
|
||
}
|
||
|
||
// random_access_indicator
|
||
if (start && stream->data_alignment_indicator && PTS_NO_VALUE != stream->pts) {
|
||
//In the PCR_PID the random_access_indicator may only be set to '1'
|
||
//in a transport stream packet containing the PCR fields.
|
||
data[3] |= 0x20; // +AF
|
||
data[5] |= AF_FLAG_RANDOM_ACCESS_INDICATOR; // +random_access_indicator
|
||
}
|
||
|
||
if (data[3] & 0x20) {
|
||
data[4] = 1; // 1-byte flag
|
||
|
||
if (data[5] & AF_FLAG_PCR) { // PCR_flag
|
||
int64_t pcr = 0;
|
||
pcr = (PTS_NO_VALUE == stream->dts) ? stream->pts : stream->dts;
|
||
pcr_write(data + 6, (pcr - PCR_DELAY) * 300); // TODO: delay???
|
||
data[4] += 6; // 6-PCR
|
||
}
|
||
|
||
header = data + TS_HEADER_LEN + 1 + data[4]; // 4-TS + 1-AF-Len + AF-Payload
|
||
} else {
|
||
header = data + TS_HEADER_LEN;
|
||
}
|
||
|
||
p = header;
|
||
|
||
// PES header
|
||
if (start) {
|
||
data[1] |= TS_PAYLOAD_UNIT_START_INDICATOR; // payload_unit_start_indicator
|
||
|
||
p += pes_write_header(stream, header, TS_PACKET_SIZE - (header - data));
|
||
|
||
if (PSI_STREAM_H264 == stream->codecid && !tsctx->h264_h265_with_aud) {
|
||
// 2.14 Carriage of Rec. ITU-T H.264 | ISO/IEC 14496-10 video
|
||
// Each AVC access unit shall contain an access unit delimiter NAL Unit
|
||
nbo_w32(p, 0x00000001);
|
||
p[4] = 0x09; // AUD
|
||
p[5] = 0xF0; // any slice type (0xe) + rbsp stop one bit
|
||
p += 6;
|
||
} else if (PSI_STREAM_H265 == stream->codecid && !tsctx->h264_h265_with_aud) {
|
||
// 2.17 Carriage of HEVC
|
||
// Each HEVC access unit shall contain an access unit delimiter NAL unit.
|
||
nbo_w32(p, 0x00000001);
|
||
p[4] = 0x46; // 35-AUD_NUT
|
||
p[5] = 0x01;
|
||
p[6] = 0x50; // B&P&I (0x2) + rbsp stop one bit
|
||
p += 7;
|
||
}
|
||
|
||
// PES_packet_length = PES-Header + Payload-Size
|
||
// A value of 0 indicates that the PES packet length is neither specified nor bounded
|
||
// and is allowed only in PES packets whose payload consists of bytes from a
|
||
// video elementary stream contained in transport stream packets
|
||
if ((p - header - PES_HEADER_LEN) + bytes > 0xFFFF) {
|
||
nbo_w16(header + 4, 0); // 2.4.3.7 PES packet => PES_packet_length
|
||
} else {
|
||
nbo_w16(header + 4, (uint16_t)((p - header - PES_HEADER_LEN) + bytes));
|
||
}
|
||
}
|
||
|
||
len = p - data; // TS + PES header length
|
||
if (len + bytes < TS_PACKET_SIZE) {
|
||
// stuffing_len = TS_PACKET_SIZE - (len + bytes)
|
||
|
||
// move pes header
|
||
if (p - header > 0) {
|
||
assert(start);
|
||
memmove(data + (TS_PACKET_SIZE - bytes - (p - header)), header, p - header);
|
||
}
|
||
|
||
// adaptation
|
||
if (data[3] & 0x20) { // has AF?
|
||
assert(0 != data[5] && data[4] > 0);
|
||
memset(data + TS_HEADER_LEN + 1 + data[4], 0xFF, TS_PACKET_SIZE - (len + bytes));
|
||
data[4] += (uint8_t)(TS_PACKET_SIZE - (len + bytes));
|
||
} else {
|
||
assert(len == (size_t)(p - header) + TS_HEADER_LEN);
|
||
data[3] |= 0x20; // +AF
|
||
data[4] = (uint8_t)(TS_PACKET_SIZE - (len + bytes) - 1/*AF length*/);
|
||
if (data[4] > 0) {
|
||
data[5] = 0; // no flag
|
||
}
|
||
if (data[4] > 1) {
|
||
memset(data + 6, 0xFF, TS_PACKET_SIZE - (len + bytes) - 2);
|
||
}
|
||
}
|
||
len = bytes;
|
||
|
||
p = data + 5 + data[4] + (p - header);
|
||
} else {
|
||
len = TS_PACKET_SIZE - len;
|
||
}
|
||
|
||
// payload
|
||
memcpy(p, payload, len);
|
||
|
||
payload += len;
|
||
bytes -= len;
|
||
start = 0;
|
||
|
||
// send with TS-header
|
||
tsctx->func.write(tsctx->param, data, TS_PACKET_SIZE);
|
||
tsctx->func.free(tsctx->param, data);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static struct pes_t *mpeg_ts_find(mpeg_ts_enc_context_t *ts, int pid, struct pmt_t **pmt)
|
||
{
|
||
size_t i, j;
|
||
struct pes_t *stream;
|
||
|
||
for (i = 0; i < ts->pat.pmt_count; i++) {
|
||
*pmt = &ts->pat.pmts[i];
|
||
for (j = 0; j < (*pmt)->stream_count; j++) {
|
||
stream = &(*pmt)->streams[j];
|
||
if (pid == (int)stream->pid) {
|
||
return stream;
|
||
}
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
int mpeg_ts_write(void *ts, int pid, int flags, int64_t pts, int64_t dts, const void *data, size_t bytes)
|
||
{
|
||
size_t i, r;
|
||
struct pmt_t *pmt = NULL;
|
||
struct pes_t *stream = NULL;
|
||
mpeg_ts_enc_context_t *tsctx;
|
||
|
||
tsctx = (mpeg_ts_enc_context_t *)ts;
|
||
stream = mpeg_ts_find(tsctx, pid, &pmt);
|
||
if (NULL == stream) {
|
||
return -ENOENT; // not found
|
||
}
|
||
|
||
stream->pts = pts;
|
||
stream->dts = dts;
|
||
stream->data_alignment_indicator = (flags & MPEG_FLAG_IDR_FRAME) ? 1 : 0; // idr frame
|
||
tsctx->h264_h265_with_aud = (flags & MPEG_FLAG_H264_H265_WITH_AUD) ? 1 : 0;
|
||
|
||
// set PCR_PID
|
||
assert(1 == tsctx->pat.pmt_count);
|
||
if (0x1FFF == pmt->PCR_PID || (PES_SID_VIDEO == (stream->sid & PES_SID_VIDEO) && pmt->PCR_PID != stream->pid)) {
|
||
pmt->PCR_PID = stream->pid;
|
||
tsctx->pat_period = 0;
|
||
}
|
||
|
||
if (pmt->PCR_PID == stream->pid) {
|
||
++tsctx->pcr_clock;
|
||
}
|
||
|
||
if (0 == tsctx->pat_period || tsctx->pat_period + PAT_PERIOD <= dts) {
|
||
tsctx->pat_period = dts;
|
||
|
||
// PAT(program_association_section)
|
||
r = pat_write(&tsctx->pat, tsctx->payload);
|
||
mpeg_ts_write_section_header(ts, PAT_TID_PAS, &tsctx->pat.cc, tsctx->payload, r); // PID = 0x00 program association table
|
||
|
||
// PMT(Transport stream program map section)
|
||
for (i = 0; i < tsctx->pat.pmt_count; i++) {
|
||
r = pmt_write(&tsctx->pat.pmts[i], tsctx->payload);
|
||
mpeg_ts_write_section_header(ts, tsctx->pat.pmts[i].pid, &tsctx->pat.pmts[i].cc, tsctx->payload, r);
|
||
}
|
||
}
|
||
|
||
ts_write_pes(tsctx, pmt, stream, data, bytes);
|
||
return 0;
|
||
}
|
||
|
||
void *mpeg_ts_create(const struct mpeg_ts_func_t *func, void *param)
|
||
{
|
||
mpeg_ts_enc_context_t *tsctx = NULL;
|
||
|
||
assert(func);
|
||
tsctx = (mpeg_ts_enc_context_t *)calloc(1, sizeof(mpeg_ts_enc_context_t));
|
||
if (!tsctx) {
|
||
return NULL;
|
||
}
|
||
|
||
mpeg_ts_reset(tsctx);
|
||
|
||
tsctx->pat.tsid = 1;
|
||
tsctx->pat.ver = 0x00;
|
||
tsctx->pat.cc = 0;
|
||
|
||
tsctx->pat.pmt_count = 1; // only one program in ts
|
||
tsctx->pat.pmts[0].pid = 0x100;
|
||
tsctx->pat.pmts[0].pn = 1;
|
||
tsctx->pat.pmts[0].ver = 0x00;
|
||
tsctx->pat.pmts[0].cc = 0;
|
||
tsctx->pat.pmts[0].pminfo_len = 0;
|
||
tsctx->pat.pmts[0].pminfo = NULL;
|
||
tsctx->pat.pmts[0].PCR_PID = 0x1FFF; // 0x1FFF-don't set PCR
|
||
|
||
//tsctx->pat.pmts[0].stream_count = 2; // H.264 + AAC
|
||
//tsctx->pat.pmts[0].streams[0].pid = 0x101;
|
||
//tsctx->pat.pmts[0].streams[0].sid = PES_SID_AUDIO;
|
||
//tsctx->pat.pmts[0].streams[0].codecid = PSI_STREAM_AAC;
|
||
//tsctx->pat.pmts[0].streams[1].pid = 0x102;
|
||
//tsctx->pat.pmts[0].streams[1].sid = PES_SID_VIDEO;
|
||
//tsctx->pat.pmts[0].streams[1].codecid = PSI_STREAM_H264;
|
||
|
||
memcpy(&tsctx->func, func, sizeof(tsctx->func));
|
||
tsctx->param = param;
|
||
return tsctx;
|
||
}
|
||
|
||
int mpeg_ts_destroy(void *ts)
|
||
{
|
||
uint32_t i, j;
|
||
mpeg_ts_enc_context_t *tsctx = NULL;
|
||
tsctx = (mpeg_ts_enc_context_t *)ts;
|
||
|
||
for (i = 0; i < tsctx->pat.pmt_count; i++) {
|
||
for (j = 0; j < tsctx->pat.pmts[i].stream_count; j++) {
|
||
if (tsctx->pat.pmts[i].streams[j].esinfo) {
|
||
free(tsctx->pat.pmts[i].streams[j].esinfo);
|
||
}
|
||
}
|
||
}
|
||
|
||
free(tsctx);
|
||
return 0;
|
||
}
|
||
|
||
int mpeg_ts_reset(void *ts)
|
||
{
|
||
mpeg_ts_enc_context_t *tsctx;
|
||
tsctx = (mpeg_ts_enc_context_t *)ts;
|
||
tsctx->pat_period = 0;
|
||
tsctx->pcr_period = 80 * 90; // 100ms maximum
|
||
tsctx->pcr_clock = 0;
|
||
return 0;
|
||
}
|
||
|
||
int mpeg_ts_add_stream(void *ts, int codecid, const void *extra_data, size_t extra_data_size)
|
||
{
|
||
struct pmt_t *pmt = NULL;
|
||
struct pes_t *stream = NULL;
|
||
mpeg_ts_enc_context_t *tsctx;
|
||
|
||
tsctx = (mpeg_ts_enc_context_t *)ts;
|
||
pmt = &tsctx->pat.pmts[0];
|
||
if (pmt->stream_count >= sizeof(pmt->streams) / sizeof(pmt->streams[0])) {
|
||
assert(0);
|
||
return -1;
|
||
}
|
||
|
||
stream = &pmt->streams[pmt->stream_count];
|
||
stream->codecid = (uint8_t)codecid;
|
||
stream->pid = (uint16_t)(0x101 + pmt->stream_count);
|
||
stream->esinfo_len = 0;
|
||
stream->esinfo = NULL;
|
||
|
||
// stream id
|
||
// Table 2-22 – Stream_id assignments
|
||
if (mpeg_stream_type_video(codecid)) {
|
||
// Rec. ITU-T H.262 | ISO/IEC 13818-2, ISO/IEC 11172-2, ISO/IEC 14496-2
|
||
// or Rec. ITU-T H.264 | ISO/IEC 14496-10 video stream number
|
||
stream->sid = PES_SID_VIDEO;
|
||
} else if (mpeg_stream_type_audio(codecid)) {
|
||
// ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or ISO/IEC 14496-3
|
||
// audio stream number
|
||
stream->sid = PES_SID_AUDIO;
|
||
} else {
|
||
// private_stream_1
|
||
stream->sid = PES_SID_PRIVATE_1;
|
||
}
|
||
|
||
if (extra_data_size > 0 && extra_data) {
|
||
stream->esinfo = malloc(extra_data_size);
|
||
if (!stream->esinfo) {
|
||
return -ENOMEM;
|
||
}
|
||
memcpy(stream->esinfo, extra_data, extra_data_size);
|
||
stream->esinfo_len = (uint16_t)extra_data_size;
|
||
}
|
||
|
||
pmt->stream_count++;
|
||
pmt->ver = (pmt->ver + 1) % 32;
|
||
mpeg_ts_reset(ts); // immediate update pat/pmt
|
||
return stream->pid;
|
||
}
|