Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/torchcodec/_core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,8 @@ else()
set(ffmpeg_major_version "6")
elseif (${libavcodec_major_version} STREQUAL "61")
set(ffmpeg_major_version "7")
elseif (${libavcodec_major_version} STREQUAL "62")
set(ffmpeg_major_version "8")
else()
message(
FATAL_ERROR
Expand Down
61 changes: 61 additions & 0 deletions src/torchcodec/_core/FFMPEGCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

#include <c10/util/Exception.h>

extern "C" {
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
}

namespace facebook::torchcodec {

AutoAVPacket::AutoAVPacket() : avPacket_(av_packet_alloc()) {
Expand Down Expand Up @@ -374,6 +379,62 @@ SwrContext* createSwrContext(
return swrContext;
}

AVFilterContext* createBuffersinkFilter(
AVFilterGraph* filterGraph,
const char* name,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're always passing "out" here, so let's just remove this parameter and hard-code "out" below. If we ever need another name, we can add the parameter then.

enum AVPixelFormat outputFormat) {
const AVFilter* buffersink = avfilter_get_by_name("buffersink");
if (!buffersink) {
return nullptr;
}
AVFilterContext* sinkContext = nullptr;
int status;

// av_opt_set_int_list was replaced by av_opt_set() in FFmpeg 8.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// av_opt_set_int_list was replaced by av_opt_set() in FFmpeg 8.
// av_opt_set_int_list was replaced by av_opt_set_array() in FFmpeg 8.

#if LIBAVUTIL_VERSION_MAJOR >= 60 // FFmpeg >= 8
// Output options like pixel_formats must be set before filter init
sinkContext = avfilter_graph_alloc_filter(filterGraph, buffersink, name);
if (!sinkContext) {
return nullptr;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to TORCH_CHECK early here (and elsewhere) instead of returning a nullptr, it'll make it easier to figure out where the error happens.

// av_opt_set uses the string representation of a pixel format
const char* fmt_name = av_get_pix_fmt_name(outputFormat);
if (!fmt_name) {
return nullptr;
}
status = av_opt_set(
sinkContext, "pixel_formats", fmt_name, AV_OPT_SEARCH_CHILDREN);
if (status < 0) {
return nullptr;
}
status = avfilter_init_str(sinkContext, nullptr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this? In #935 (review) the only thing we had to change was to replace av_opt_set_int_list with av_opt_set_array, but I think we can still rely on avfilter_graph_create_filter for FFmpeg 8 ? Same above with avfilter_graph_alloc_filter which may not be needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the new flow of allocation -> set flags -> init is required to set the pixel_formats flag. If we try to retain the previous pattern, this error appears:
Option 'pixel_formats' is not a runtime option and so cannot be set after the object has been initialized

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my own ref, claude is telling me that the way you've done it above is the actual new recommended way:

Details
● Based on my research, here's the answer to your question about FFmpeg 8 changes and simpler alternatives:

  Why avfilter_init_str() is Required in FFmpeg 8

  The use of avfilter_init_str() is unavoidable and actually represents the correct, simplified approach for FFmpeg
   8. Here's why:

  The API Change

  FFmpeg 8 deprecated av_opt_set_int_list() and replaced it with array-type options that require a two-step 
  initialization process:

  1. Allocate filter context: avfilter_graph_alloc_filter()
  2. Set array options: av_opt_set_array()
  3. Initialize filter: avfilter_init_str() or avfilter_init_dict()

  Why This is Actually Simpler

  Your current implementation is the simplified approach. The alternative would be more complex:

  Alternative 1: String-based (less flexible)
  // Would require converting enum to string manually
  char pix_fmt_str[32];
  av_get_pix_fmt_string(pix_fmt_str, sizeof(pix_fmt_str), outputFormat);
  avfilter_graph_create_filter(&sinkContext, buffersink, name,
      "pixel_formats=yuv420p", nullptr, filterGraph);

  Alternative 2: Dictionary-based (more verbose)
  AVDictionary* opts = nullptr;
  char pix_fmt_str[32];
  av_get_pix_fmt_string(pix_fmt_str, sizeof(pix_fmt_str), outputFormat);
  av_dict_set(&opts, "pixel_formats", pix_fmt_str, 0);
  sinkContext = avfilter_graph_alloc_filter(filterGraph, buffersink, name);
  avfilter_init_dict(sinkContext, &opts);
  av_dict_free(&opts);

  Your Current Implementation is Optimal

  The pattern you have is the recommended FFmpeg 8 approach:

  sinkContext = avfilter_graph_alloc_filter(filterGraph, buffersink, name);
  av_opt_set_array(sinkContext, "pixel_formats", AV_OPT_SEARCH_CHILDREN,
                   0, 1, AV_OPT_TYPE_PIXEL_FMT, &outputFormat);
  avfilter_init_str(sinkContext, nullptr);

  This is simpler because:
  - Type-safe: Uses AV_OPT_TYPE_PIXEL_FMT directly
  - No string conversion: Avoids enum-to-string conversion
  - Clean separation: Options set before initialization
  - Minimal code: Fewer lines than alternatives

  Recommendation

  Keep your current implementation. The avfilter_init_str() call is not a workaround—it's the intended FFmpeg 8 API
   design. The separation of allocation, option-setting, and initialization provides better error handling and
  flexibility than the older combined approach.

  The FFmpeg 8 changes actually made the API more consistent, even if it requires one additional function call.

if (status < 0) {
return nullptr;
}
#else // FFmpeg <= 7
// For older FFmpeg versions, create filter and then set options
status = avfilter_graph_create_filter(
&sinkContext, buffersink, name, nullptr, nullptr, filterGraph);
if (status < 0) {
return nullptr;
}

enum AVPixelFormat pix_fmts[] = {outputFormat, AV_PIX_FMT_NONE};

status = av_opt_set_int_list(
sinkContext,
"pix_fmts",
pix_fmts,
AV_PIX_FMT_NONE,
AV_OPT_SEARCH_CHILDREN);
if (status < 0) {
return nullptr;
}
#endif

return sinkContext;
}

UniqueAVFrame convertAudioAVFrameSamples(
const UniqueSwrContext& swrContext,
const UniqueAVFrame& srcAVFrame,
Expand Down
6 changes: 6 additions & 0 deletions src/torchcodec/_core/FFMPEGCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ extern "C" {
#include <libavutil/display.h>
#include <libavutil/file.h>
#include <libavutil/opt.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>
#include <libavutil/version.h>
#include <libswresample/swresample.h>
Expand Down Expand Up @@ -237,4 +238,9 @@ int64_t computeSafeDuration(
const AVRational& frameRate,
const AVRational& timeBase);

AVFilterContext* createBuffersinkFilter(
AVFilterGraph* filterGraph,
const char* name,
enum AVPixelFormat outputFormat);

} // namespace facebook::torchcodec
24 changes: 4 additions & 20 deletions src/torchcodec/_core/FilterGraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// LICENSE file in the root directory of this source tree.

#include "src/torchcodec/_core/FilterGraph.h"
#include "src/torchcodec/_core/FFMPEGCommon.h"

extern "C" {
#include <libavfilter/buffersink.h>
Expand Down Expand Up @@ -63,7 +64,6 @@ FilterGraph::FilterGraph(
}

const AVFilter* buffersrc = avfilter_get_by_name("buffer");
const AVFilter* buffersink = avfilter_get_by_name("buffersink");

UniqueAVBufferSrcParameters srcParams(av_buffersrc_parameters_alloc());
TORCH_CHECK(srcParams, "Failed to allocate buffersrc params");
Expand Down Expand Up @@ -93,26 +93,10 @@ FilterGraph::FilterGraph(
"Failed to create filter graph : ",
getFFMPEGErrorStringFromErrorCode(status));

status = avfilter_graph_create_filter(
&sinkContext_, buffersink, "out", nullptr, nullptr, filterGraph_.get());
sinkContext_ = createBuffersinkFilter(
filterGraph_.get(), "out", filtersContext.outputFormat);
TORCH_CHECK(
status >= 0,
"Failed to create filter graph: ",
getFFMPEGErrorStringFromErrorCode(status));

enum AVPixelFormat pix_fmts[] = {
filtersContext.outputFormat, AV_PIX_FMT_NONE};

status = av_opt_set_int_list(
sinkContext_,
"pix_fmts",
pix_fmts,
AV_PIX_FMT_NONE,
AV_OPT_SEARCH_CHILDREN);
TORCH_CHECK(
status >= 0,
"Failed to set output pixel formats: ",
getFFMPEGErrorStringFromErrorCode(status));
sinkContext_ != nullptr, "Failed to create and configure buffersink");

UniqueAVFilterInOut outputs(avfilter_inout_alloc());
UniqueAVFilterInOut inputs(avfilter_inout_alloc());
Expand Down
2 changes: 1 addition & 1 deletion src/torchcodec/_core/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def load_torchcodec_shared_libraries():
# libraries do not meet those conditions.

exceptions = []
for ffmpeg_major_version in (7, 6, 5, 4):
for ffmpeg_major_version in (8, 7, 6, 5, 4):
pybind_ops_module_name = _get_pybind_ops_module_name(ffmpeg_major_version)
decoder_library_name = f"libtorchcodec_core{ffmpeg_major_version}"
custom_ops_library_name = f"libtorchcodec_custom_ops{ffmpeg_major_version}"
Expand Down
Loading