diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 20cc6b8b4..e34ad8624 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -149,6 +149,33 @@ std::list get_decoder_data(int wanted_codec, const std::string return results; } +AVCodecContext *open_fallback_decoder(const AVCodecParameters *codecpar, const AVCodec **codec_out) { + const AVCodec *codec = avcodec_find_decoder(codecpar->codec_id); + if (!codec) { + Debug(1, "No fallback decoder available for codec %s", + avcodec_get_name(codecpar->codec_id)); + return nullptr; + } + Debug(1, "Trying fallback decoder %s for codec %s", + codec->name, avcodec_get_name(codecpar->codec_id)); + AVCodecContext *ctx = avcodec_alloc_context3(codec); + if (!ctx) { + Error("Failed to allocate context for fallback decoder %s", codec->name); + return nullptr; + } + avcodec_parameters_to_context(ctx, codecpar); + zm_dump_codec(ctx); + int ret = avcodec_open2(ctx, codec, nullptr); + if (ret < 0) { + Error("Could not open fallback decoder %s (error '%s')", + codec->name, av_make_error_string(ret).c_str()); + avcodec_free_context(&ctx); + return nullptr; + } + if (codec_out) *codec_out = codec; + return ctx; +} + #if HAVE_LIBAVUTIL_HWCONTEXT_H #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) diff --git a/src/zm_ffmpeg.h b/src/zm_ffmpeg.h index 1879eea04..070f2c3ba 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -332,6 +332,11 @@ struct CodecData { }; std::list get_encoder_data(const std::string & wanted_codec, const std::string &wanted_coder) ; std::list get_decoder_data(int wanted_codec, const std::string &wanted_coder) ; +// When none of the preferred decoders in dec_codecs are usable, fall back to +// whatever decoder this ffmpeg build actually provides for codecpar->codec_id. +// Returns an opened AVCodecContext (caller frees with avcodec_free_context) or +// nullptr. When non-null, *codec_out receives the chosen AVCodec. +AVCodecContext *open_fallback_decoder(const AVCodecParameters *codecpar, const AVCodec **codec_out = nullptr); int setup_hwaccel(AVCodecContext *codec_ctx, const CodecData *codec_data,AVBufferRef * &hw_device_ctx, const std::string &device, int width, int height); int libjpeg_to_ffmpeg_qv(int libjpeg_quality); enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts); diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index a81ed066f..6f3d620a0 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -779,6 +779,11 @@ int FfmpegCamera::OpenFfmpeg() { break; } // end foreach codec + if (!mVideoCodecContext) { + Debug(1, "Failed with known codecs, trying harder"); + mVideoCodecContext = open_fallback_decoder(mVideoStream->codecpar, &mVideoCodec); + } + if (!mVideoCodecContext) { Warning("Failed to open codec"); return -1; diff --git a/src/zm_ffmpeg_input.cpp b/src/zm_ffmpeg_input.cpp index 51d6e7a37..5ba4b7a7d 100644 --- a/src/zm_ffmpeg_input.cpp +++ b/src/zm_ffmpeg_input.cpp @@ -129,20 +129,8 @@ int FFmpeg_Input::Open(const char *filepath) { if (!streams[i].context) { Debug(1, "Failed with known codecs, trying harder"); - if ((streams[i].codec = avcodec_find_decoder(input_format_context->streams[i]->codecpar->codec_id))) { - Debug(1, "Using codec (%s) for stream %d", streams[i].codec->name, i); - streams[i].context = avcodec_alloc_context3(streams[i].codec); - avcodec_parameters_to_context(streams[i].context, input_format_context->streams[i]->codecpar); - - zm_dump_codec(streams[i].context); - - error = avcodec_open2(streams[i].context, streams[i].codec, nullptr); - if (error < 0) { - Error("Could not open input codec (error '%s')", av_make_error_string(error).c_str()); - avcodec_free_context(&streams[i].context); - streams[i].context = nullptr; - } - } + streams[i].context = open_fallback_decoder( + input_format_context->streams[i]->codecpar, &streams[i].codec); } if (!streams[i].context) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1c4ce61e9..c7fc55499 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -31,7 +31,8 @@ set(TEST_SOURCES zm_zone.cpp zm_zone_stride.cpp zm_image_linesize.cpp - zm_ffmpeg_camera.cpp) + zm_ffmpeg_camera.cpp + zm_ffmpeg_fallback.cpp) add_executable(tests main.cpp ${TEST_SOURCES}) diff --git a/tests/zm_ffmpeg_fallback.cpp b/tests/zm_ffmpeg_fallback.cpp new file mode 100644 index 000000000..0c4737bed --- /dev/null +++ b/tests/zm_ffmpeg_fallback.cpp @@ -0,0 +1,83 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "zm_catch2.h" + +#include "zm_ffmpeg.h" + +// open_fallback_decoder() is the "try whatever ffmpeg actually has" path used +// when none of the preferred decoders in dec_codecs are available. The +// hard-coded table is only a preference list; a codec present in the ffmpeg +// build but absent from the table must still open. + +static AVCodecParameters *make_video_par(enum AVCodecID id) { + AVCodecParameters *par = avcodec_parameters_alloc(); + par->codec_type = AVMEDIA_TYPE_VIDEO; + par->codec_id = id; + par->width = 1280; + par->height = 720; + par->format = AV_PIX_FMT_YUV420P; + return par; +} + +TEST_CASE("open_fallback_decoder: opens a decoder not listed in dec_codecs") { + // MPEG2VIDEO has a native ffmpeg decoder that is always compiled in, and it is + // deliberately not in the preferred dec_codecs table, so it only resolves via + // the fallback. If this build somehow lacks it, skip rather than false-fail. + if (!avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO)) { + WARN("mpeg2video decoder not present in this ffmpeg build; skipping"); + return; + } + + AVCodecParameters *par = make_video_par(AV_CODEC_ID_MPEG2VIDEO); + const AVCodec *codec = nullptr; + AVCodecContext *ctx = open_fallback_decoder(par, &codec); + + REQUIRE(ctx != nullptr); + REQUIRE(codec != nullptr); + REQUIRE(codec->id == AV_CODEC_ID_MPEG2VIDEO); + + avcodec_free_context(&ctx); + avcodec_parameters_free(&par); +} + +TEST_CASE("open_fallback_decoder: codec_out is optional") { + if (!avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO)) { + WARN("mpeg2video decoder not present in this ffmpeg build; skipping"); + return; + } + + AVCodecParameters *par = make_video_par(AV_CODEC_ID_MPEG2VIDEO); + AVCodecContext *ctx = open_fallback_decoder(par); // default nullptr codec_out + + REQUIRE(ctx != nullptr); + + avcodec_free_context(&ctx); + avcodec_parameters_free(&par); +} + +TEST_CASE("open_fallback_decoder: returns nullptr when no decoder exists") { + AVCodecParameters *par = make_video_par(AV_CODEC_ID_NONE); + const AVCodec *codec = reinterpret_cast(0x1); + AVCodecContext *ctx = open_fallback_decoder(par, &codec); + + REQUIRE(ctx == nullptr); + // codec_out must be left untouched on failure. + REQUIRE(codec == reinterpret_cast(0x1)); + + avcodec_parameters_free(&par); +}