| // 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); |
| |
| /* 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_psi_pat_write_args pat_args = {}; |
| struct vidtv_psi_pmt_write_args pmt_args = {}; |
| struct vidtv_psi_sdt_write_args sdt_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); |
| |
| 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); |
| |
| 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->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); |
| vfree(m->mux_buf); |
| kfree(m); |
| } |