7a7899f6f5
Implement an Event Information Table (EIT) as per EN 300 468 5.2.4. The EIT provides information in chronological order regarding the events contained within each service. For now only present event information is supported. [mchehab+huawei@kernel.org: removed an extra blank line] Signed-off-by: Daniel W. S. Almeida <dwlsalmeida@gmail.com> Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org> Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
504 lines
13 KiB
C
504 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Vidtv serves as a reference DVB driver and helps validate the existing APIs
|
|
* in the media subsystem. It can also aid developers working on userspace
|
|
* applications.
|
|
*
|
|
* This file contains the multiplexer logic for TS packets from different
|
|
* elementary streams
|
|
*
|
|
* Loosely based on libavcodec/mpegtsenc.c
|
|
*
|
|
* Copyright (C) 2020 Daniel W. S. Almeida
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/dev_printk.h>
|
|
#include <linux/ratelimit.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/math64.h>
|
|
|
|
#include "vidtv_mux.h"
|
|
#include "vidtv_ts.h"
|
|
#include "vidtv_pes.h"
|
|
#include "vidtv_encoder.h"
|
|
#include "vidtv_channel.h"
|
|
#include "vidtv_common.h"
|
|
#include "vidtv_psi.h"
|
|
|
|
static struct vidtv_mux_pid_ctx
|
|
*vidtv_mux_get_pid_ctx(struct vidtv_mux *m, u16 pid)
|
|
{
|
|
struct vidtv_mux_pid_ctx *ctx;
|
|
|
|
hash_for_each_possible(m->pid_ctx, ctx, h, pid)
|
|
if (ctx->pid == pid)
|
|
return ctx;
|
|
return NULL;
|
|
}
|
|
|
|
static struct vidtv_mux_pid_ctx
|
|
*vidtv_mux_create_pid_ctx_once(struct vidtv_mux *m, u16 pid)
|
|
{
|
|
struct vidtv_mux_pid_ctx *ctx;
|
|
|
|
ctx = vidtv_mux_get_pid_ctx(m, pid);
|
|
|
|
if (ctx)
|
|
goto end;
|
|
|
|
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
|
ctx->pid = pid;
|
|
ctx->cc = 0;
|
|
hash_add(m->pid_ctx, &ctx->h, pid);
|
|
|
|
end:
|
|
return ctx;
|
|
}
|
|
|
|
static void vidtv_mux_pid_ctx_init(struct vidtv_mux *m)
|
|
{
|
|
struct vidtv_psi_table_pat_program *p = m->si.pat->program;
|
|
u16 pid;
|
|
|
|
hash_init(m->pid_ctx);
|
|
/* push the pcr pid ctx */
|
|
vidtv_mux_create_pid_ctx_once(m, m->pcr_pid);
|
|
/* push the null packet pid ctx */
|
|
vidtv_mux_create_pid_ctx_once(m, TS_NULL_PACKET_PID);
|
|
/* push the PAT pid ctx */
|
|
vidtv_mux_create_pid_ctx_once(m, VIDTV_PAT_PID);
|
|
/* push the SDT pid ctx */
|
|
vidtv_mux_create_pid_ctx_once(m, VIDTV_SDT_PID);
|
|
/* push the NIT pid ctx */
|
|
vidtv_mux_create_pid_ctx_once(m, VIDTV_NIT_PID);
|
|
/* push the EIT pid ctx */
|
|
vidtv_mux_create_pid_ctx_once(m, VIDTV_EIT_PID);
|
|
|
|
/* add a ctx for all PMT sections */
|
|
while (p) {
|
|
pid = vidtv_psi_get_pat_program_pid(p);
|
|
vidtv_mux_create_pid_ctx_once(m, pid);
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
static void vidtv_mux_pid_ctx_destroy(struct vidtv_mux *m)
|
|
{
|
|
int bkt;
|
|
struct vidtv_mux_pid_ctx *ctx;
|
|
struct hlist_node *tmp;
|
|
|
|
hash_for_each_safe(m->pid_ctx, bkt, tmp, ctx, h) {
|
|
hash_del(&ctx->h);
|
|
kfree(ctx);
|
|
}
|
|
}
|
|
|
|
static void vidtv_mux_update_clk(struct vidtv_mux *m)
|
|
{
|
|
/* call this at every thread iteration */
|
|
u64 elapsed_time;
|
|
|
|
m->timing.past_jiffies = m->timing.current_jiffies;
|
|
m->timing.current_jiffies = get_jiffies_64();
|
|
|
|
elapsed_time = jiffies_to_usecs(m->timing.current_jiffies -
|
|
m->timing.past_jiffies);
|
|
|
|
/* update the 27Mhz clock proportionally to the elapsed time */
|
|
m->timing.clk += (CLOCK_UNIT_27MHZ / USEC_PER_SEC) * elapsed_time;
|
|
}
|
|
|
|
static u32 vidtv_mux_push_si(struct vidtv_mux *m)
|
|
{
|
|
u32 initial_offset = m->mux_buf_offset;
|
|
|
|
struct vidtv_mux_pid_ctx *pat_ctx;
|
|
struct vidtv_mux_pid_ctx *pmt_ctx;
|
|
struct vidtv_mux_pid_ctx *sdt_ctx;
|
|
struct vidtv_mux_pid_ctx *nit_ctx;
|
|
struct vidtv_mux_pid_ctx *eit_ctx;
|
|
|
|
struct vidtv_psi_pat_write_args pat_args = {};
|
|
struct vidtv_psi_pmt_write_args pmt_args = {};
|
|
struct vidtv_psi_sdt_write_args sdt_args = {};
|
|
struct vidtv_psi_nit_write_args nit_args = {};
|
|
struct vidtv_psi_eit_write_args eit_args = {};
|
|
|
|
u32 nbytes; /* the number of bytes written by this function */
|
|
u16 pmt_pid;
|
|
u32 i;
|
|
|
|
pat_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_PAT_PID);
|
|
sdt_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_SDT_PID);
|
|
nit_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_NIT_PID);
|
|
eit_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_EIT_PID);
|
|
|
|
pat_args.buf = m->mux_buf;
|
|
pat_args.offset = m->mux_buf_offset;
|
|
pat_args.pat = m->si.pat;
|
|
pat_args.buf_sz = m->mux_buf_sz;
|
|
pat_args.continuity_counter = &pat_ctx->cc;
|
|
|
|
m->mux_buf_offset += vidtv_psi_pat_write_into(pat_args);
|
|
|
|
for (i = 0; i < m->si.pat->programs; ++i) {
|
|
pmt_pid = vidtv_psi_pmt_get_pid(m->si.pmt_secs[i],
|
|
m->si.pat);
|
|
|
|
if (pmt_pid > TS_LAST_VALID_PID) {
|
|
dev_warn_ratelimited(m->dev,
|
|
"PID: %d not found\n", pmt_pid);
|
|
continue;
|
|
}
|
|
|
|
pmt_ctx = vidtv_mux_get_pid_ctx(m, pmt_pid);
|
|
|
|
pmt_args.buf = m->mux_buf;
|
|
pmt_args.offset = m->mux_buf_offset;
|
|
pmt_args.pmt = m->si.pmt_secs[i];
|
|
pmt_args.pid = pmt_pid;
|
|
pmt_args.buf_sz = m->mux_buf_sz;
|
|
pmt_args.continuity_counter = &pmt_ctx->cc;
|
|
pmt_args.pcr_pid = m->pcr_pid;
|
|
|
|
/* write each section into buffer */
|
|
m->mux_buf_offset += vidtv_psi_pmt_write_into(pmt_args);
|
|
}
|
|
|
|
sdt_args.buf = m->mux_buf;
|
|
sdt_args.offset = m->mux_buf_offset;
|
|
sdt_args.sdt = m->si.sdt;
|
|
sdt_args.buf_sz = m->mux_buf_sz;
|
|
sdt_args.continuity_counter = &sdt_ctx->cc;
|
|
|
|
m->mux_buf_offset += vidtv_psi_sdt_write_into(sdt_args);
|
|
|
|
nit_args.buf = m->mux_buf;
|
|
nit_args.offset = m->mux_buf_offset;
|
|
nit_args.nit = m->si.nit;
|
|
nit_args.buf_sz = m->mux_buf_sz;
|
|
nit_args.continuity_counter = &nit_ctx->cc;
|
|
|
|
m->mux_buf_offset += vidtv_psi_nit_write_into(nit_args);
|
|
|
|
eit_args.buf = m->mux_buf;
|
|
eit_args.offset = m->mux_buf_offset;
|
|
eit_args.eit = m->si.eit;
|
|
eit_args.buf_sz = m->mux_buf_sz;
|
|
eit_args.continuity_counter = &eit_ctx->cc;
|
|
|
|
m->mux_buf_offset += vidtv_psi_eit_write_into(eit_args);
|
|
|
|
nbytes = m->mux_buf_offset - initial_offset;
|
|
|
|
m->num_streamed_si++;
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
static u32 vidtv_mux_push_pcr(struct vidtv_mux *m)
|
|
{
|
|
struct pcr_write_args args = {};
|
|
struct vidtv_mux_pid_ctx *ctx;
|
|
u32 nbytes = 0;
|
|
|
|
ctx = vidtv_mux_get_pid_ctx(m, m->pcr_pid);
|
|
args.dest_buf = m->mux_buf;
|
|
args.pid = m->pcr_pid;
|
|
args.buf_sz = m->mux_buf_sz;
|
|
args.continuity_counter = &ctx->cc;
|
|
|
|
/* the 27Mhz clock will feed both parts of the PCR bitfield */
|
|
args.pcr = m->timing.clk;
|
|
|
|
nbytes += vidtv_ts_pcr_write_into(args);
|
|
m->mux_buf_offset += nbytes;
|
|
|
|
m->num_streamed_pcr++;
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
static bool vidtv_mux_should_push_pcr(struct vidtv_mux *m)
|
|
{
|
|
u64 next_pcr_at;
|
|
|
|
if (m->num_streamed_pcr == 0)
|
|
return true;
|
|
|
|
next_pcr_at = m->timing.start_jiffies +
|
|
usecs_to_jiffies(m->num_streamed_pcr *
|
|
m->timing.pcr_period_usecs);
|
|
|
|
return time_after64(m->timing.current_jiffies, next_pcr_at);
|
|
}
|
|
|
|
static bool vidtv_mux_should_push_si(struct vidtv_mux *m)
|
|
{
|
|
u64 next_si_at;
|
|
|
|
if (m->num_streamed_si == 0)
|
|
return true;
|
|
|
|
next_si_at = m->timing.start_jiffies +
|
|
usecs_to_jiffies(m->num_streamed_si *
|
|
m->timing.si_period_usecs);
|
|
|
|
return time_after64(m->timing.current_jiffies, next_si_at);
|
|
}
|
|
|
|
static u32 vidtv_mux_packetize_access_units(struct vidtv_mux *m,
|
|
struct vidtv_encoder *e)
|
|
{
|
|
u32 nbytes = 0;
|
|
|
|
struct pes_write_args args = {};
|
|
u32 initial_offset = m->mux_buf_offset;
|
|
struct vidtv_access_unit *au = e->access_units;
|
|
|
|
u8 *buf = NULL;
|
|
struct vidtv_mux_pid_ctx *pid_ctx = vidtv_mux_create_pid_ctx_once(m,
|
|
be16_to_cpu(e->es_pid));
|
|
|
|
args.dest_buf = m->mux_buf;
|
|
args.dest_buf_sz = m->mux_buf_sz;
|
|
args.pid = be16_to_cpu(e->es_pid);
|
|
args.encoder_id = e->id;
|
|
args.continuity_counter = &pid_ctx->cc;
|
|
args.stream_id = be16_to_cpu(e->stream_id);
|
|
args.send_pts = true;
|
|
|
|
while (au) {
|
|
buf = e->encoder_buf + au->offset;
|
|
args.from = buf;
|
|
args.access_unit_len = au->nbytes;
|
|
args.dest_offset = m->mux_buf_offset;
|
|
args.pts = au->pts;
|
|
args.pcr = m->timing.clk;
|
|
|
|
m->mux_buf_offset += vidtv_pes_write_into(args);
|
|
|
|
au = au->next;
|
|
}
|
|
|
|
/*
|
|
* clear the encoder state once the ES data has been written to the mux
|
|
* buffer
|
|
*/
|
|
e->clear(e);
|
|
|
|
nbytes = m->mux_buf_offset - initial_offset;
|
|
return nbytes;
|
|
}
|
|
|
|
static u32 vidtv_mux_poll_encoders(struct vidtv_mux *m)
|
|
{
|
|
u32 nbytes = 0;
|
|
u32 au_nbytes;
|
|
struct vidtv_channel *cur_chnl = m->channels;
|
|
struct vidtv_encoder *e = NULL;
|
|
|
|
while (cur_chnl) {
|
|
e = cur_chnl->encoders;
|
|
|
|
while (e) {
|
|
e->encode(e);
|
|
/* get the TS packets into the mux buffer */
|
|
au_nbytes = vidtv_mux_packetize_access_units(m, e);
|
|
nbytes += au_nbytes;
|
|
m->mux_buf_offset += au_nbytes;
|
|
/* grab next encoder */
|
|
e = e->next;
|
|
}
|
|
|
|
/* grab the next channel */
|
|
cur_chnl = cur_chnl->next;
|
|
}
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
static u32 vidtv_mux_pad_with_nulls(struct vidtv_mux *m, u32 npkts)
|
|
{
|
|
struct null_packet_write_args args = {};
|
|
u32 initial_offset = m->mux_buf_offset;
|
|
u32 nbytes; /* the number of bytes written by this function */
|
|
u32 i;
|
|
struct vidtv_mux_pid_ctx *ctx;
|
|
|
|
ctx = vidtv_mux_get_pid_ctx(m, TS_NULL_PACKET_PID);
|
|
|
|
args.dest_buf = m->mux_buf;
|
|
args.buf_sz = m->mux_buf_sz;
|
|
args.continuity_counter = &ctx->cc;
|
|
args.dest_offset = m->mux_buf_offset;
|
|
|
|
for (i = 0; i < npkts; ++i) {
|
|
m->mux_buf_offset += vidtv_ts_null_write_into(args);
|
|
args.dest_offset = m->mux_buf_offset;
|
|
}
|
|
|
|
nbytes = m->mux_buf_offset - initial_offset;
|
|
|
|
/* sanity check */
|
|
if (nbytes != npkts * TS_PACKET_LEN)
|
|
dev_err_ratelimited(m->dev, "%d != %d\n",
|
|
nbytes, npkts * TS_PACKET_LEN);
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
static void vidtv_mux_clear(struct vidtv_mux *m)
|
|
{
|
|
/* clear the packets currently in the mux */
|
|
memset(m->mux_buf, 0, m->mux_buf_sz * sizeof(*m->mux_buf));
|
|
/* point to the beginning of the buffer again */
|
|
m->mux_buf_offset = 0;
|
|
}
|
|
|
|
#define ERR_RATE 10000000
|
|
static void vidtv_mux_tick(struct work_struct *work)
|
|
{
|
|
struct vidtv_mux *m = container_of(work,
|
|
struct vidtv_mux,
|
|
mpeg_thread);
|
|
struct dtv_frontend_properties *c = &m->fe->dtv_property_cache;
|
|
u32 nbytes;
|
|
u32 npkts;
|
|
u32 tot_bits = 0;
|
|
|
|
while (m->streaming) {
|
|
nbytes = 0;
|
|
|
|
vidtv_mux_update_clk(m);
|
|
|
|
if (vidtv_mux_should_push_pcr(m))
|
|
nbytes += vidtv_mux_push_pcr(m);
|
|
|
|
if (vidtv_mux_should_push_si(m))
|
|
nbytes += vidtv_mux_push_si(m);
|
|
|
|
nbytes += vidtv_mux_poll_encoders(m);
|
|
nbytes += vidtv_mux_pad_with_nulls(m, 256);
|
|
|
|
npkts = nbytes / TS_PACKET_LEN;
|
|
|
|
/* if the buffer is not aligned there is a bug somewhere */
|
|
if (nbytes % TS_PACKET_LEN)
|
|
dev_err_ratelimited(m->dev, "Misaligned buffer\n");
|
|
|
|
if (m->on_new_packets_available_cb)
|
|
m->on_new_packets_available_cb(m->priv,
|
|
m->mux_buf,
|
|
npkts);
|
|
|
|
vidtv_mux_clear(m);
|
|
|
|
/*
|
|
* Update bytes and packet counts at DVBv5 stats
|
|
*
|
|
* For now, both pre and post bit counts are identical,
|
|
* but post BER count can be lower than pre BER, if the error
|
|
* correction logic discards packages.
|
|
*/
|
|
c->pre_bit_count.stat[0].uvalue = nbytes * 8;
|
|
c->post_bit_count.stat[0].uvalue = nbytes * 8;
|
|
c->block_count.stat[0].uvalue += npkts;
|
|
|
|
/*
|
|
* Even without any visible errors for the user, the pre-BER
|
|
* stats usually have an error range up to 1E-6. So,
|
|
* add some random error increment count to it.
|
|
*
|
|
* Please notice that this is a poor guy's implementation,
|
|
* as it will produce one corrected bit error every time
|
|
* ceil(total bytes / ERR_RATE) is incremented, without
|
|
* any sort of (pseudo-)randomness.
|
|
*/
|
|
tot_bits += nbytes * 8;
|
|
if (tot_bits > ERR_RATE) {
|
|
c->pre_bit_error.stat[0].uvalue++;
|
|
tot_bits -= ERR_RATE;
|
|
}
|
|
|
|
usleep_range(VIDTV_SLEEP_USECS, VIDTV_MAX_SLEEP_USECS);
|
|
}
|
|
}
|
|
|
|
void vidtv_mux_start_thread(struct vidtv_mux *m)
|
|
{
|
|
if (m->streaming) {
|
|
dev_warn_ratelimited(m->dev, "Already streaming. Skipping.\n");
|
|
return;
|
|
}
|
|
|
|
m->streaming = true;
|
|
m->timing.start_jiffies = get_jiffies_64();
|
|
schedule_work(&m->mpeg_thread);
|
|
}
|
|
|
|
void vidtv_mux_stop_thread(struct vidtv_mux *m)
|
|
{
|
|
if (m->streaming) {
|
|
m->streaming = false; /* thread will quit */
|
|
cancel_work_sync(&m->mpeg_thread);
|
|
}
|
|
}
|
|
|
|
struct vidtv_mux *vidtv_mux_init(struct dvb_frontend *fe,
|
|
struct device *dev,
|
|
struct vidtv_mux_init_args args)
|
|
{
|
|
struct vidtv_mux *m = kzalloc(sizeof(*m), GFP_KERNEL);
|
|
|
|
m->dev = dev;
|
|
m->fe = fe;
|
|
m->timing.pcr_period_usecs = args.pcr_period_usecs;
|
|
m->timing.si_period_usecs = args.si_period_usecs;
|
|
|
|
m->mux_rate_kbytes_sec = args.mux_rate_kbytes_sec;
|
|
|
|
m->on_new_packets_available_cb = args.on_new_packets_available_cb;
|
|
|
|
m->mux_buf = vzalloc(args.mux_buf_sz);
|
|
m->mux_buf_sz = args.mux_buf_sz;
|
|
|
|
m->pcr_pid = args.pcr_pid;
|
|
m->transport_stream_id = args.transport_stream_id;
|
|
m->priv = args.priv;
|
|
m->network_id = args.network_id;
|
|
m->network_name = kstrdup(args.network_name, GFP_KERNEL);
|
|
m->timing.current_jiffies = get_jiffies_64();
|
|
|
|
if (args.channels)
|
|
m->channels = args.channels;
|
|
else
|
|
vidtv_channels_init(m);
|
|
|
|
/* will alloc data for pmt_sections after initializing pat */
|
|
vidtv_channel_si_init(m);
|
|
|
|
INIT_WORK(&m->mpeg_thread, vidtv_mux_tick);
|
|
|
|
vidtv_mux_pid_ctx_init(m);
|
|
|
|
return m;
|
|
}
|
|
|
|
void vidtv_mux_destroy(struct vidtv_mux *m)
|
|
{
|
|
vidtv_mux_stop_thread(m);
|
|
vidtv_mux_pid_ctx_destroy(m);
|
|
vidtv_channel_si_destroy(m);
|
|
vidtv_channels_destroy(m);
|
|
kfree(m->network_name);
|
|
vfree(m->mux_buf);
|
|
kfree(m);
|
|
}
|