From c299a41e4b01823ec397a0e30143116de422017b Mon Sep 17 00:00:00 2001
From: Vasyl Gello <vasek.gello@gmail.com>
Date: Sat, 15 Oct 2022 16:17:48 +0000
Subject: [PATCH 3/3] ffmpeg5: Get extradata with extract_extradata BSF

Fixes the transport stream playback failures described in
https://bugs.debian.org/1016925

@Rogo95 made an excellent technical analysis of the root cause
and reported that to the bug thread.

Later on, James Almer (@jamrial) suggested the solution to use
extract_extradata bitstream filter to replace the removed split()
function.

Finally, I adapted the following code snippet:
https://gist.github.com/moonpfe/f6795d51294d91ee0f82f62ff6985db0
to Kodi and tested it by playing the affected files in TS format.

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
---
 src/stream/FFmpegStream.cpp | 200 ++++++++++++++++++++++++++++++------
 src/stream/FFmpegStream.h   |   2 +
 2 files changed, 170 insertions(+), 32 deletions(-)

diff --git a/src/stream/FFmpegStream.cpp b/src/stream/FFmpegStream.cpp
index f2140a1..3910d1e 100644
--- a/src/stream/FFmpegStream.cpp
+++ b/src/stream/FFmpegStream.cpp
@@ -29,6 +29,7 @@
 #endif
 
 extern "C" {
+#include <libavcodec/bsf.h>
 #include <libavutil/dict.h>
 #include <libavutil/opt.h>
 }
@@ -1586,6 +1587,146 @@ bool FFmpegStream::SeekTime(double time, bool backwards, double* startpts)
     return false;
 }
 
+int FFmpegStream::GetPacketExtradata(const AVPacket* pkt, const AVCodecParserContext* parserCtx, AVCodecContext* codecCtx, uint8_t **p_extradata)
+{
+  int extradata_size = 0;
+
+  if (!pkt || !p_extradata)
+    return 0;
+
+  *p_extradata = nullptr;
+
+#if LIBAVFORMAT_BUILD >= AV_VERSION_INT(59, 0, 100)
+  AVBSFContext *bsf = nullptr;
+  AVPacket *dst_pkt = nullptr;
+  const AVBitStreamFilter *f;
+  AVPacket *pkt_ref = nullptr;
+  int ret = 0;
+  uint8_t *ret_extradata = nullptr;
+  size_t ret_extradata_size = 0;
+
+  f = av_bsf_get_by_name("extract_extradata");
+  if (!f)
+    return 0;
+
+  bsf = nullptr;
+  ret = av_bsf_alloc(f, &bsf);
+  if (ret < 0)
+    return 0;
+
+  bsf->par_in->codec_id = codecCtx->codec_id;
+
+  ret = av_bsf_init(bsf);
+  if (ret < 0)
+  {
+    av_bsf_free(&bsf);
+    return 0;
+  }
+
+  dst_pkt = av_packet_alloc();
+  pkt_ref = dst_pkt;
+
+  ret = av_packet_ref(pkt_ref, pkt);
+  if (ret < 0)
+  {
+    av_bsf_free(&bsf);
+    av_packet_free(&dst_pkt);
+    return 0;
+  }
+
+  ret = av_bsf_send_packet(bsf, pkt_ref);
+  if (ret < 0)
+  {
+    av_packet_unref(pkt_ref);
+    av_bsf_free(&bsf);
+    av_packet_free(&dst_pkt);
+    return 0;
+  }
+
+  ret = 0;
+  while (ret >= 0)
+  {
+    ret = av_bsf_receive_packet(bsf, pkt_ref);
+    if (ret < 0)
+    {
+      if (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
+        break;
+
+      continue;
+    }
+
+    ret_extradata = av_packet_get_side_data(pkt_ref,
+                                            AV_PKT_DATA_NEW_EXTRADATA,
+                                            &ret_extradata_size);
+    if (ret_extradata &&
+        ret_extradata_size > 0 &&
+        ret_extradata_size < FF_MAX_EXTRADATA_SIZE)
+    {
+      *p_extradata = (uint8_t*)av_malloc(ret_extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
+      if (!*p_extradata)
+      {
+        Log(LOGLEVEL_ERROR,
+            "%s - failed to allocate %zu bytes for extradata",
+            __FUNCTION__,
+            ret_extradata_size);
+
+        av_packet_unref(pkt_ref);
+        av_bsf_free(&bsf);
+        av_packet_free(&dst_pkt);
+        return 0;
+      }
+
+      Log(LOGLEVEL_DEBUG,
+          "%s - fetching extradata, extradata_size(%zu)",
+          __FUNCTION__,
+          ret_extradata_size);
+
+      memcpy(*p_extradata, ret_extradata, ret_extradata_size);
+      memset(*p_extradata + ret_extradata_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+      extradata_size = ret_extradata_size;
+
+      av_packet_unref(pkt_ref);
+      break;
+    }
+
+    av_packet_unref(pkt_ref);
+  }
+
+  av_bsf_free(&bsf);
+  av_packet_free(&dst_pkt);
+#else
+  if (codecCtx && parserCtx && parserCtx->parser && parserCtx->parser->split)
+    extradata_size = parserCtx->parser->split(codecCtx, pkt->data, pkt->size);
+
+  if (extradata_size <= 0 || extradata_size >= FF_MAX_EXTRADATA_SIZE)
+  {
+    Log(LOGLEVEL_DEBUG, "%s - fetched extradata of weird size %zd",
+        __FUNCTION__, extradata_size);
+    return 0;
+  }
+
+  *p_extradata = (uint8_t*)av_malloc(extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
+  if (!*p_extradata)
+  {
+    Log(LOGLEVEL_ERROR,
+        "%s - failed to allocate %zd bytes for extradata",
+        __FUNCTION__,
+        extradata_size);
+    return 0;
+  }
+
+  Log(LOGLEVEL_DEBUG,
+      "%s - fetching extradata, extradata_size(%zd)",
+      __FUNCTION__,
+      extradata_size);
+
+  memcpy(*p_extradata, pkt->data, extradata_size);
+  memset(*p_extradata + extradata_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+#endif
+
+  return extradata_size;
+}
+
 void FFmpegStream::ParsePacket(AVPacket* pkt)
 {
   AVStream* st = m_pFormatContext->streams[pkt->stream_index];
@@ -1617,43 +1758,38 @@ void FFmpegStream::ParsePacket(AVPacket* pkt)
 
     if (parser->second->m_parserCtx &&
         parser->second->m_parserCtx->parser &&
-        parser->second->m_parserCtx->parser->split &&
         !st->codecpar->extradata)
     {
-      int i = parser->second->m_parserCtx->parser->split(parser->second->m_codecCtx, pkt->data, pkt->size);
-      if (i > 0 && i < FF_MAX_EXTRADATA_SIZE)
+      int i = GetPacketExtradata(pkt,
+                               parser->second->m_parserCtx,
+                               parser->second->m_codecCtx,
+                               &st->codecpar->extradata);
+      if (i > 0)
       {
-        st->codecpar->extradata = (uint8_t*)av_malloc(i + AV_INPUT_BUFFER_PADDING_SIZE);
-        if (st->codecpar->extradata)
-        {
-          Log(LOGLEVEL_DEBUG, "CDVDDemuxFFmpeg::ParsePacket() fetching extradata, extradata_size(%d)", i);
-          st->codecpar->extradata_size = i;
-          memcpy(st->codecpar->extradata, pkt->data, i);
-          memset(st->codecpar->extradata + i, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+        st->codecpar->extradata_size = i;
 
-          if (parser->second->m_parserCtx->parser->parser_parse)
+        if (parser->second->m_parserCtx->parser->parser_parse)
+        {
+          parser->second->m_codecCtx->extradata = st->codecpar->extradata;
+          parser->second->m_codecCtx->extradata_size = st->codecpar->extradata_size;
+          const uint8_t* outbufptr;
+          int bufSize;
+          parser->second->m_parserCtx->flags |= PARSER_FLAG_COMPLETE_FRAMES;
+          parser->second->m_parserCtx->parser->parser_parse(parser->second->m_parserCtx,
+                                                            parser->second->m_codecCtx,
+                                                            &outbufptr, &bufSize,
+                                                            pkt->data, pkt->size);
+          parser->second->m_codecCtx->extradata = nullptr;
+          parser->second->m_codecCtx->extradata_size = 0;
+
+          if (parser->second->m_parserCtx->width != 0)
           {
-            parser->second->m_codecCtx->extradata = st->codecpar->extradata;
-            parser->second->m_codecCtx->extradata_size = st->codecpar->extradata_size;
-            const uint8_t* outbufptr;
-            int bufSize;
-            parser->second->m_parserCtx->flags |= PARSER_FLAG_COMPLETE_FRAMES;
-            parser->second->m_parserCtx->parser->parser_parse(parser->second->m_parserCtx,
-                                                              parser->second->m_codecCtx,
-                                                              &outbufptr, &bufSize,
-                                                              pkt->data, pkt->size);
-            parser->second->m_codecCtx->extradata = nullptr;
-            parser->second->m_codecCtx->extradata_size = 0;
-
-            if (parser->second->m_parserCtx->width != 0)
-            {
-              st->codecpar->width = parser->second->m_parserCtx->width;
-              st->codecpar->height = parser->second->m_parserCtx->height;
-            }
-            else
-            {
-              Log(LOGLEVEL_ERROR, "CDVDDemuxFFmpeg::ParsePacket() invalid width/height");
-            }
+            st->codecpar->width = parser->second->m_parserCtx->width;
+            st->codecpar->height = parser->second->m_parserCtx->height;
+          }
+          else
+          {
+            Log(LOGLEVEL_ERROR, "CDVDDemuxFFmpeg::ParsePacket() invalid width/height");
           }
         }
       }
diff --git a/src/stream/FFmpegStream.h b/src/stream/FFmpegStream.h
index 356905d..f0634d0 100644
--- a/src/stream/FFmpegStream.h
+++ b/src/stream/FFmpegStream.h
@@ -109,6 +109,8 @@ protected:
   bool IsPaused() { return m_speed == STREAM_PLAYSPEED_PAUSE; }
   virtual bool CheckReturnEmptyOnPacketResult(int result);
 
+  int GetPacketExtradata(const AVPacket* pkt, const AVCodecParserContext* parserCtx, AVCodecContext* codecCtx, uint8_t **p_extradata);
+
   int64_t m_demuxerId;
   mutable std::recursive_mutex m_mutex;
   double m_currentPts; // used for stream length estimation
-- 
2.35.1

