[FFmpeg-devel] Multiprogram mode for mpeg TS

Submitted by ffmpeg@a.legko.ru on June 23, 2017, 2:42 p.m.

Details

Message ID alpine.LNX.2.20.1706231708430.9344@scil.sinp.msu.ru
State New
Headers show

Commit Message

ffmpeg@a.legko.ru June 23, 2017, 2:42 p.m.
Hi!
(corrected version of the patch.)

1. adding missed but required options for libavformat/mpegts encoder in
    ffmpeg_opt.c. also, it makes it possible to add pcr_pid for separate
    program inside TS.
    options are:
      service_provider: any provider name  (only default FFMPEG is present in original code)
      service_name: alias for title, used in mpegtsenc.c. it is present there, but never defined.
      pcr_pid: if required, it is possible to set the PCR pid for separate program inside TS

2. ffmpeg.c, when encoding multiple streams, stops encoding threads even
    when loop counter is set for separate input stream. adding corresponding
    function to correct the situation.

3. libavformat/mpegtsenc.c: adding multiprogram TS mode; thus, streams
    can be organized into progs as it is done in DVB streams. for that, PCR
    selection (auto) algo was changed (according to TS standart, each prog
    must have PCR pid inside). old PCR selection (auto) mode remains for 1-prog case.

4. possible to set default title|service_name/service_provider/pcr_pid
    via -metadata option (as in example below for the last prog). metadata
    options are used in 1-prog stream.


following example is for multi-prog TS streaming (open VLC with 
udp://@1234 and look at Playback/Program menu). here we have 4 progs with 
the loop(3 times) for auu.wav (when it stops, streaming remains with 1
silent prog):

ffmpeg -re \
 	-i ZZ.avi \
 	-i test.wav \
 	-stream_loop 3 -i auu.wav \
 	-i existone.mp3 \
 	-map 0:v \
 	-map 0:a \
 	-map_channel 0.1.0:1.0 \
 	-map_channel 0.1.1:1.1 \
 	-vcodec libx264 -b:v 400k \
 	-mpegts_original_network_id 0x1122 \
 	-mpegts_transport_stream_id 0x3344 \
 	-mpegts_service_id 0x5566 \
 	-streamid 0:0x159 \
 	-metadata service_provider="Some provider" \
 	-metadata service_name="Some Channel" \
 	-c:a:0 libfdk_aac -profile:a aac_he  -ac 2 -b:a 32k \
 	-streamid 1:0x160 \
 	-f mpegts \
 	-map 1:a \
 	-mpegts_original_network_id 0x1123 \
 	-mpegts_transport_stream_id 0x3345 \
 	-mpegts_service_id 0x55CA \
 	-map_channel 1.0.0:2.0 \
 	-map_channel 1.0.1:2.1 \
 	-c:a:1 libfdk_aac -profile:a aac_he_v2  -ac 2 -b:a 32k \
 	-streamid 2:0x180 \
 	-f mpegts \
 	-map 2:a \
 	-mpegts_original_network_id 0x1127 \
 	-mpegts_transport_stream_id 0x3348 \
 	-mpegts_service_id 0x55CE \
 	-map_channel 2.0.0:3.0 \
 	-map_channel 2.0.1:3.1 \
 	-c:a:2 libfdk_aac -profile:a aac_he_v2  -ac 2 -b:a 32k \
 	-streamid 3:0x182 \
 	-map 3:a \
 	-mpegts_original_network_id 0x1129 \
 	-mpegts_transport_stream_id 0x3349 \
 	-mpegts_service_id 0x55CF \
 	-map_channel 3.0.0:4.0 \
 	-map_channel 3.0.1:4.1 \
 	-c:a:3 libfdk_aac -profile:a aac_he_v2  -ac 2 -b:a 32k \
 	-streamid 4:0x184 \
 	-program title="Xren0":service_name="Zanunda":service_provider="provider4":program_num=0x5576:st=0:st=1 \
 	-program title="Xren1":service_provider="provider4":program_num=0x5578:st=2 \
 	-program title="Xren2":service_provider="provider5":program_num=0x5579:st=3 \
 	-program program_num=0x5581:st=4 \
 	-f mpegts  udp://192.11.1.12:1234\&pkt_size=1316
From b043d9f5c894f4b9c9964e43392ee42adce2ef33 Mon Sep 17 00:00:00 2001
From: root <ffmpeg@scil.sinp.msu.ru>

Date: Fri, 23 Jun 2017 17:01:07 +0300
Subject: [PATCH] ffmpeg.c - add thread restart (when required by looping) for
 multi-stream encoding ffmpeg_opt.c - add required but missing options for
 mpegtsenc.c mpegtsenc.c - add support for multi-programm mpeg TS, add PCR
 selection algo for it

---
 ffmpeg.c                |  27 ++++++++
 ffmpeg_opt.c            |   7 ++-
 libavformat/mpegtsenc.c | 162 ++++++++++++++++++++++++++++++++++++++++--------
 3 files changed, 169 insertions(+), 27 deletions(-)

-- 
2.7.4

Comments

Andreas Håkon June 26, 2017, 8:19 a.m.
-------- Original Message --------
Subject: [FFmpeg-devel] Multiprogram mode for mpeg TS
Local Time: June 23, 2017 4:42 PM
UTC Time: June 23, 2017 2:42 PM
From: ffmpeg@a.legko.ru
To: ffmpeg-devel@ffmpeg.org
> Hi!
> (corrected version of the patch.)
Good patch! I found the MPTS support in FFMpeg a must have.
One question: Can you add support for the NIT table? Without it, the output MPTS isn't DVB compilant.
Thank you for your work.
Hendrik Leppkes June 26, 2017, 8:52 a.m.
On Fri, Jun 23, 2017 at 4:42 PM,  <ffmpeg@a.legko.ru> wrote:
>
> Hi!
> (corrected version of the patch.)
>
> 1. adding missed but required options for libavformat/mpegts encoder in
>    ffmpeg_opt.c. also, it makes it possible to add pcr_pid for separate
>    program inside TS.
>    options are:
>      service_provider: any provider name  (only default FFMPEG is present in
> original code)
>      service_name: alias for title, used in mpegtsenc.c. it is present
> there, but never defined.
>      pcr_pid: if required, it is possible to set the PCR pid for separate
> program inside TS
>
> 2. ffmpeg.c, when encoding multiple streams, stops encoding threads even
>    when loop counter is set for separate input stream. adding corresponding
>    function to correct the situation.
>
> 3. libavformat/mpegtsenc.c: adding multiprogram TS mode; thus, streams
>    can be organized into progs as it is done in DVB streams. for that, PCR
>    selection (auto) algo was changed (according to TS standart, each prog
>    must have PCR pid inside). old PCR selection (auto) mode remains for
> 1-prog case.
>
> 4. possible to set default title|service_name/service_provider/pcr_pid
>    via -metadata option (as in example below for the last prog). metadata
>    options are used in 1-prog stream.
>
>
>

Individual changes should be submitted as seperate patches. Having
several unrelated changes in one patch makes it impossible to apply or
even properly review it.

- Hendrik
ffmpeg@a.legko.ru June 26, 2017, 9:51 a.m.
On Mon, 26 Jun 2017, Hendrik Leppkes wrote:

> Individual changes should be submitted as seperate patches. Having
> several unrelated changes in one patch makes it impossible to apply or
> even properly review it.
all changes are related:

multiprog cannot exist when some of prog goes away (pthread ends in 
ffmpeg.c) (this touches stream looping too).

options MUST be set in ffmpeg_ops.c to make it possible to name 
and separate each prog in TS. thus ops.c is corrected.

mpegtsenc.c - all changes above used here.

applying patch to the latest git causes no trouble, as I can see..
Michael Niedermayer June 27, 2017, 3:32 p.m.
On Mon, Jun 26, 2017 at 12:51:08PM +0300, ffmpeg@a.legko.ru wrote:
> 
> 
> On Mon, 26 Jun 2017, Hendrik Leppkes wrote:
> 
> >Individual changes should be submitted as seperate patches. Having
> >several unrelated changes in one patch makes it impossible to apply or
> >even properly review it.
> all changes are related:
> 
> multiprog cannot exist when some of prog goes away (pthread ends in
> ffmpeg.c) (this touches stream looping too).
> 
> options MUST be set in ffmpeg_ops.c to make it possible to name and
> separate each prog in TS. thus ops.c is corrected.
> 
> mpegtsenc.c - all changes above used here.
> 
> applying patch to the latest git causes no trouble, as I can see..

libavformat has a API and ABI (ffmpeg and others) are using it.

If you change libavformat, its needed to update the version of
libavformat. And to ensure the changes do not break ABI/API

Changes to a application using libavformat do not belong in that the
same patch that changed the lib.
There are many applications using libavformat both in our git and in
many other git repositories from other projects.
ffmpeg@a.legko.ru June 28, 2017, 11:44 a.m.
On Tue, 27 Jun 2017, Michael Niedermayer wrote:

>> mpegtsenc.c - all changes above used here.
>>
>> applying patch to the latest git causes no trouble, as I can see..
>
> libavformat has a API and ABI (ffmpeg and others) are using it.
>
> If you change libavformat, its needed to update the version of
> libavformat. And to ensure the changes do not break ABI/API
>
> Changes to a application using libavformat do not belong in that the
> same patch that changed the lib.
> There are many applications using libavformat both in our git and in
> many other git repositories from other projects.
OK, clear about separate patches.

Patch hide | download patch | download mbox

diff --git a/ffmpeg.c b/ffmpeg.c
index a783e6e..2866754 100644
--- a/ffmpeg.c
+++ b/ffmpeg.c
@@ -4013,6 +4013,29 @@  static void free_input_threads(void)
     }
 }
 
+static int init_input_thread(int i)
+{
+    int ret;
+  
+    if (nb_input_files == 1)
+       return 0;
+
+    InputFile *f = input_files[i];
+    if (f->ctx->pb ? !f->ctx->pb->seekable :
+       strcmp(f->ctx->iformat->name, "lavfi"))
+           f->non_blocking = 1;
+    ret = av_thread_message_queue_alloc(&f->in_thread_queue,
+           f->thread_queue_size, sizeof(AVPacket));
+    if (ret < 0)
+       return ret;
+    if ((ret = pthread_create(&f->thread, NULL, input_thread, f))) {
+       av_log(NULL, AV_LOG_ERROR, "pthread_create failed: %s. Try to increase `ulimit -v` or decrease `ulimit -s`.\n", strerror(ret));
+       av_thread_message_queue_free(&f->in_thread_queue);
+       return AVERROR(ret);
+    }
+return 0;
+}
+ 
 static int init_input_threads(void)
 {
     int i, ret;
@@ -4191,9 +4214,13 @@  static int process_input(int file_index)
         ifile->eagain = 1;
         return ret;
     }
+
     if (ret < 0 && ifile->loop) {
         if ((ret = seek_to_start(ifile, is)) < 0)
             return ret;
+#if HAVE_PTHREADS
+        init_input_thread(file_index);
+#endif
         ret = get_input_packet(ifile, &pkt);
         if (ret == AVERROR(EAGAIN)) {
             ifile->eagain = 1;
diff --git a/ffmpeg_opt.c b/ffmpeg_opt.c
index bb6001f..aa4ffb5 100644
--- a/ffmpeg_opt.c
+++ b/ffmpeg_opt.c
@@ -2653,13 +2653,18 @@  loop_end:
             if (!*p2)
                 exit_program(1);
             p2++;
-
             if (!strcmp(key, "title")) {
                 av_dict_set(&program->metadata, "title", p2, 0);
             } else if (!strcmp(key, "program_num")) {
             } else if (!strcmp(key, "st")) {
                 int st_num = strtol(p2, NULL, 0);
                 av_program_add_stream_index(oc, progid, st_num);
+            } else if (!strcmp(key, "service_provider")) {
+                av_dict_set(&program->metadata, "service_provider", p2, 0);
+            } else if (!strcmp(key, "service_name")) {
+                av_dict_set(&program->metadata, "service_name", p2, 0);
+            } else if (!strcmp(key, "pcr_pid")) {
+                av_dict_set(&program->metadata, "pcr_pid", p2, 0);
             } else {
                 av_log(NULL, AV_LOG_FATAL, "Unknown program key %s.\n", key);
                 exit_program(1);
diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
index acea2e9..15260a9 100644
--- a/libavformat/mpegtsenc.c
+++ b/libavformat/mpegtsenc.c
@@ -59,6 +59,7 @@  typedef struct MpegTSService {
     int pcr_pid;
     int pcr_packet_count;
     int pcr_packet_period;
+    int pcr_type;      /* if the service has a/v pid: AVMEDIA_TYPE_UNKNOWN/AUDIO/VIDEO...*/
     AVProgram *program;
 } MpegTSService;
 
@@ -707,7 +708,8 @@  static void mpegts_write_sdt(AVFormatContext *s)
 
 static MpegTSService *mpegts_add_service(MpegTSWrite *ts, int sid,
                                          const char *provider_name,
-                                         const char *name)
+                                         const char *name,
+                                         int pcr_pid)
 {
     MpegTSService *service;
 
@@ -716,7 +718,7 @@  static MpegTSService *mpegts_add_service(MpegTSWrite *ts, int sid,
         return NULL;
     service->pmt.pid       = ts->pmt_start_pid + ts->nb_services;
     service->sid           = sid;
-    service->pcr_pid       = 0x1fff;
+    service->pcr_pid       = pcr_pid; /* was 0x1fff */
     service->provider_name = av_strdup(provider_name);
     service->name          = av_strdup(name);
     if (!service->provider_name || !service->name)
@@ -763,11 +765,12 @@  static int mpegts_init(AVFormatContext *s)
     MpegTSWriteStream *ts_st;
     MpegTSService *service;
     AVStream *st, *pcr_st = NULL;
-    AVDictionaryEntry *title, *provider;
+    AVDictionaryEntry *title, *provider, *p_pid;
+    char *endz;
     int i, j;
-    const char *service_name;
-    const char *provider_name;
-    int *pids;
+    const char *service_name, *dflt_service_name;
+    const char *provider_name, *dflt_provider_name;
+    int *pids, pcr_pid = 0x1fff, dflt_pcr_pid = 0x1fff;
     int ret;
 
     if (s->max_delay < 0) /* Not set by the caller */
@@ -778,17 +781,34 @@  static int mpegts_init(AVFormatContext *s)
 
     ts->tsid = ts->transport_stream_id;
     ts->onid = ts->original_network_id;
+
+    dflt_service_name = DEFAULT_SERVICE_NAME;
+    title = av_dict_get(s->metadata, "title", NULL, 0);
+    if(title != NULL)
+          dflt_service_name = title->value;
+    else {
+      title = av_dict_get(s->metadata, "service_name", NULL, 0);
+      if(title != NULL)
+        dflt_service_name = title->value;
+      }
+
+    dflt_provider_name = DEFAULT_PROVIDER_NAME;
+    provider = av_dict_get(s->metadata, "service_provider", NULL, 0);
+    if(provider != NULL)
+      dflt_provider_name = provider->value;
+
+    p_pid  = av_dict_get(s->metadata, "pcr_pid", NULL, 0);
+    if (p_pid)  {
+      endz = NULL;
+      dflt_pcr_pid = strtol(p_pid->value, &endz, 0);
+      }
+
     if (!s->nb_programs) {
-        /* allocate a single DVB service */
-        title = av_dict_get(s->metadata, "service_name", NULL, 0);
-        if (!title)
-            title = av_dict_get(s->metadata, "title", NULL, 0);
-        service_name  = title ? title->value : DEFAULT_SERVICE_NAME;
-        provider      = av_dict_get(s->metadata, "service_provider", NULL, 0);
-        provider_name = provider ? provider->value : DEFAULT_PROVIDER_NAME;
+        /* allocate a single DVB service/no prog */
+        service_name  = dflt_service_name;
+        provider_name = dflt_provider_name;
         service       = mpegts_add_service(ts, ts->service_id,
-                                           provider_name, service_name);
-
+                                      provider_name, service_name, dflt_pcr_pid);
         if (!service)
             return AVERROR(ENOMEM);
 
@@ -802,11 +822,18 @@  static int mpegts_init(AVFormatContext *s)
             title = av_dict_get(program->metadata, "service_name", NULL, 0);
             if (!title)
                 title = av_dict_get(program->metadata, "title", NULL, 0);
-            service_name  = title ? title->value : DEFAULT_SERVICE_NAME;
+            service_name  = title ? title->value : dflt_service_name;
             provider      = av_dict_get(program->metadata, "service_provider", NULL, 0);
-            provider_name = provider ? provider->value : DEFAULT_PROVIDER_NAME;
+            provider_name = provider ? provider->value : dflt_provider_name;
+            p_pid         = av_dict_get(s->metadata, "pcr_pid", NULL, 0);
+            if (p_pid) {
+                endz = NULL;
+                pcr_pid = strtol(p_pid->value, &endz, 0);
+                }
+                else
+                pcr_pid = dflt_pcr_pid;
             service       = mpegts_add_service(ts, program->id,
-                                               provider_name, service_name);
+                                               provider_name, service_name, pcr_pid);
 
             if (!service)
                 return AVERROR(ENOMEM);
@@ -901,12 +928,7 @@  static int mpegts_init(AVFormatContext *s)
         ts_st->first_pts_check = 1;
         ts_st->cc              = 15;
         ts_st->discontinuity   = ts->flags & MPEGTS_FLAG_DISCONT;
-        /* update PCR pid by using the first video stream */
-        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
-            service->pcr_pid == 0x1fff) {
-            service->pcr_pid = ts_st->pid;
-            pcr_st           = st;
-        }
+
         if (st->codecpar->codec_id == AV_CODEC_ID_AAC &&
             st->codecpar->extradata_size > 0) {
             AVStream *ast;
@@ -940,8 +962,94 @@  static int mpegts_init(AVFormatContext *s)
     }
 
     av_freep(&pids);
+    
+    /* automatic PCR pid selection in multiprog mode */
+    if(s->nb_programs > 0) {
+    MpegTSService *serv;
+    int k;
+    /* find a/v pid for PCR or any pid if no a/v found */
+    for (j = 0; j < ts->nb_services; j++) {
+        serv = ts->services[j];
+        serv->pcr_type = AVMEDIA_TYPE_UNKNOWN;
+        AVProgram *prog = serv->program;
+        if (serv->pcr_pid == 0x1fff) {
+           for (k = 0; k < prog->nb_stream_indexes; k++) {
+               st = s->streams[prog->stream_index[k]];
+               if(serv->pcr_type == AVMEDIA_TYPE_UNKNOWN &&
+                  (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
+                   st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO))
+                         serv->pcr_type = st->codecpar->codec_type;
+               else /* video stream preference */
+                  if(serv->pcr_type == AVMEDIA_TYPE_AUDIO &&
+                     st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+                         serv->pcr_type = st->codecpar->codec_type;
+            }
+        }
+    }
+
+    for (j = 0; j < ts->nb_services; j++) {
+        serv = ts->services[j];
+        AVProgram *prog = serv->program;
+        if(serv->pcr_pid == 0x1fff) {
+          /* find first a/v media PID to hold PCR; calculate PCR period */
+          for (k = 0; k < prog->nb_stream_indexes; k++) {
+              st = s->streams[prog->stream_index[k]];
+              if(serv->pcr_type == AVMEDIA_TYPE_UNKNOWN ||
+                 (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
+                  serv->pcr_type == AVMEDIA_TYPE_VIDEO) ||
+                 (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
+                  serv->pcr_type == AVMEDIA_TYPE_AUDIO)) {
+                         serv->pcr_pid = st->id;
+              if (ts->mux_rate > 1) {
+                         serv->pcr_packet_period = (int64_t)ts->mux_rate * \
+                                                  ts->pcr_period /
+                                                  (TS_PACKET_SIZE * 8 * 1000);
+                         } else {
+                         if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
+                             int frame_size =
+                                av_get_audio_frame_duration2(st->codecpar, 0);
+                              if (!frame_size) {
+                                  av_log(s, AV_LOG_WARNING,
+                                    "pcr_packet_period: frame size not set\n");
+                                  serv->pcr_packet_period =
+                                    st->codecpar->sample_rate / (10 * 512);
+                                  } else
+                                  serv->pcr_packet_period =
+                                  st->codecpar->sample_rate / (10 * frame_size);
+                         } else {
+                        /* max delta PCR 0.1s */
+                        /* TODO: should be avg_frame_rate */
+                            ts_st = st->priv_data;
+                            serv->pcr_packet_period =
+                                 ts_st->user_tb.den / (10 * ts_st->user_tb.num);
+                        }
+              }
+              break;
+              }
+          } /* for k */
+        }
+        if (!serv->pcr_packet_period)
+            serv->pcr_packet_period = 1;
+        /* send PCR as soon as possible */
+        serv->pcr_packet_count = serv->pcr_packet_period;
+        }
 
-    /* if no video stream, use the first stream as PCR */
+    if (ts->mux_rate > 1) {
+        ts->sdt_packet_period      = (int64_t)ts->mux_rate * SDT_RETRANS_TIME /
+                                     (TS_PACKET_SIZE * 8 * 1000);
+        ts->pat_packet_period      = (int64_t)ts->mux_rate * PAT_RETRANS_TIME /
+                                     (TS_PACKET_SIZE * 8 * 1000);
+
+        if (ts->copyts < 1)
+            ts->first_pcr = av_rescale(s->max_delay, PCR_TIME_BASE, AV_TIME_BASE);
+    } else {
+        /* Arbitrary values, PAT/PMT will also be written on video key frames */
+        ts->sdt_packet_period = 200;
+        ts->pat_packet_period = 40;
+    }
+
+    }  else { /* default PCR pid selection in singleprog mode */
+        /* if no video stream, use the first stream as PCR */
     if (service->pcr_pid == 0x1fff && s->nb_streams > 0) {
         pcr_st           = s->streams[0];
         ts_st            = pcr_st->priv_data;
@@ -983,6 +1091,8 @@  static int mpegts_init(AVFormatContext *s)
             service->pcr_packet_period = 1;
     }
 
+    }
+
     ts->last_pat_ts = AV_NOPTS_VALUE;
     ts->last_sdt_ts = AV_NOPTS_VALUE;
     // The user specified a period, use only it
@@ -994,7 +1104,7 @@  static int mpegts_init(AVFormatContext *s)
     }
 
     // output a PCR as soon as possible
-    service->pcr_packet_count = service->pcr_packet_period;
+    // service->pcr_packet_count = service->pcr_packet_period;
     ts->pat_packet_count      = ts->pat_packet_period - 1;
     ts->sdt_packet_count      = ts->sdt_packet_period - 1;