mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-08-02 16:27:19 -04:00
fix: restore generic decoder fallback for ffmpeg cameras
Extract the "use whatever decoder ffmpeg provides" logic from FFmpeg_Input into a shared open_fallback_decoder() and call it from FfmpegCamera, which had lost the fallback: its generic path only ran inside the preferred-codec loop, so an empty get_decoder_data() result failed outright instead of trying the codec's default decoder. The dec_codecs table stays a preference list; a codec present in the ffmpeg build but absent from the table (e.g. libopenh264 for H264) now decodes via the fallback instead of requiring a hard-coded entry. Add tests/zm_ffmpeg_fallback.cpp covering the fallback with a codec not in dec_codecs (mpeg2video), the optional codec_out argument, and the no-decoder case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -149,6 +149,33 @@ std::list<const CodecData*> 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)
|
||||
|
||||
|
||||
@@ -332,6 +332,11 @@ struct CodecData {
|
||||
};
|
||||
std::list<const CodecData*> get_encoder_data(const std::string & wanted_codec, const std::string &wanted_coder) ;
|
||||
std::list<const CodecData*> 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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
83
tests/zm_ffmpeg_fallback.cpp
Normal file
83
tests/zm_ffmpeg_fallback.cpp
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<const AVCodec *>(0x1);
|
||||
AVCodecContext *ctx = open_fallback_decoder(par, &codec);
|
||||
|
||||
REQUIRE(ctx == nullptr);
|
||||
// codec_out must be left untouched on failure.
|
||||
REQUIRE(codec == reinterpret_cast<const AVCodec *>(0x1));
|
||||
|
||||
avcodec_parameters_free(&par);
|
||||
}
|
||||
Reference in New Issue
Block a user