From 07ea26d7ce6bde1350ceb15380f4908da454f004 Mon Sep 17 00:00:00 2001 From: Vasyl Gello Date: Sat, 15 Oct 2022 16:17:48 +0000 Subject: [PATCH] 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 --- src/stream/FFmpegStream.cpp | 222 ++++++++++++++++++++++++++++++------ src/stream/FFmpegStream.h | 2 + 2 files changed, 192 insertions(+), 32 deletions(-) diff --git a/src/stream/FFmpegStream.cpp b/src/stream/FFmpegStream.cpp index f2140a17..95d1da83 100644 --- a/src/stream/FFmpegStream.cpp +++ b/src/stream/FFmpegStream.cpp @@ -29,6 +29,7 @@ #endif extern "C" { +#include #include #include } @@ -1586,6 +1587,168 @@ 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) + /* extract_extradata bitstream filter is implemented only + * for certain codecs, as noted in discussion of PR#21248 + */ + + AVCodecID codecId = codecCtx->codec_id; + + // clang-format off + if ( + codecId != AV_CODEC_ID_MPEG1VIDEO && + codecId != AV_CODEC_ID_MPEG2VIDEO && + codecId != AV_CODEC_ID_H264 && + codecId != AV_CODEC_ID_HEVC && + codecId != AV_CODEC_ID_MPEG4 && + codecId != AV_CODEC_ID_VC1 && + codecId != AV_CODEC_ID_AV1 && + codecId != AV_CODEC_ID_AVS2 && + codecId != AV_CODEC_ID_AVS3 && + codecId != AV_CODEC_ID_CAVS + ) + // clang-format on + return 0; + + 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 +1780,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 356905dd..f0634d0f 100644 --- a/src/stream/FFmpegStream.h +++ b/src/stream/FFmpegStream.h @@ -109,6 +109,8 @@ class FFmpegStream 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