Skip to content

Commit

Permalink
Fix resampler for ffmpeg 6
Browse files Browse the repository at this point in the history
To get ffmpeg 6 to work, we need to set AVFrame.channels. This does not exist in FFmpeg 7, and worse yet, Cython's compile time is terrible. 
To get the builds working for both 6 and 7, we implement our own comptime in scripts/comptime.
  • Loading branch information
WyattBlue authored Jul 31, 2024
1 parent 582b230 commit b68ec33
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 48 deletions.
1 change: 1 addition & 0 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ jobs:
. $CONDA/etc/profile.d/conda.sh
conda activate pyav
python scripts\\fetch-vendor.py --config-file scripts\\ffmpeg-${{ matrix.config.ffmpeg }}.json $CONDA_PREFIX\\Library
python scripts\\comptime.py ${{ matrix.config.ffmpeg }}
python setup.py build_ext --inplace --ffmpeg-dir=$CONDA_PREFIX\\Library
- name: Test
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
LDFLAGS ?= ""
CFLAGS ?= "-O0"
CFLAGS ?= "-O0 -Wno-incompatible-function-pointer-types"

PYAV_PYTHON ?= python
PYAV_PIP ?= pip
Expand Down
1 change: 1 addition & 0 deletions av/audio/frame.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ cdef class AudioFrame(Frame):
self.ptr.nb_samples = nb_samples
self.ptr.format = <int>format
self.ptr.ch_layout = layout
# [FFMPEG6] self.ptr.channels = layout.nb_channels # ffmpeg 6 only

# Sometimes this is called twice. Oh well.
self._init_user_attributes()
Expand Down
2 changes: 2 additions & 0 deletions include/libavcodec/avcodec.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ cdef extern from "libavcodec/avcodec.h" nogil:

int nb_samples # Audio samples
int sample_rate # Audio Sample rate
# [FFMPEG6] int channels

AVChannelLayout ch_layout

int64_t pts
Expand Down
1 change: 1 addition & 0 deletions scripts/build
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ which ffmpeg || exit 2
ffmpeg -version || exit 3
echo

"$PYAV_PYTHON" scripts/comptime.py
"$PYAV_PYTHON" setup.py config build_ext --inplace || exit 1
30 changes: 30 additions & 0 deletions scripts/comptime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os
import sys

def replace_in_file(file_path):
try:
with open(file_path, "r", encoding="utf-8") as file:
content = file.read()

modified_content = content.replace("# [FFMPEG6] ", "")

with open(file_path, "w") as file:
file.write(modified_content)
except UnicodeDecodeError:
pass


def process_directory(directory):
for root, dirs, files in os.walk(directory):
for file in files:
file_path = os.path.join(root, file)
replace_in_file(file_path)

if sys.platform == "win32":
is_6 = sys.argv[1].startswith("6")
else:
is_6 = os.environ.get("PYAV_LIBRARY").startswith("ffmpeg-6")

if is_6:
process_directory("av")
process_directory("include")
95 changes: 48 additions & 47 deletions tests/test_audioresampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,53 +68,54 @@ def test_matching_passthrough(self):
oframes = resampler.resample(None)
self.assertEqual(len(oframes), 0)

# TODO: Fails on ffmpeg 6
# def test_pts_assertion_same_rate(self):
# av.logging.set_level(av.logging.VERBOSE)
# resampler = AudioResampler("s16", "mono")

# # resample one frame
# iframe = AudioFrame("s16", "stereo", 1024)
# iframe.sample_rate = 48000
# iframe.time_base = "1/48000"
# iframe.pts = 0

# oframes = resampler.resample(iframe)
# self.assertEqual(len(oframes), 1)

# oframe = oframes[0]
# self.assertEqual(oframe.pts, 0)
# self.assertEqual(oframe.time_base, iframe.time_base)
# self.assertEqual(oframe.sample_rate, iframe.sample_rate)
# self.assertEqual(oframe.samples, iframe.samples)

# # resample another frame
# iframe.pts = 1024

# oframes = resampler.resample(iframe)
# self.assertEqual(len(oframes), 1)

# oframe = oframes[0]
# self.assertEqual(oframe.pts, 1024)
# self.assertEqual(oframe.time_base, iframe.time_base)
# self.assertEqual(oframe.sample_rate, iframe.sample_rate)
# self.assertEqual(oframe.samples, iframe.samples)

# # resample another frame with a pts gap, do not raise exception
# iframe.pts = 9999
# oframes = resampler.resample(iframe)
# self.assertEqual(len(oframes), 1)

# oframe = oframes[0]
# self.assertEqual(oframe.pts, 9999)
# self.assertEqual(oframe.time_base, iframe.time_base)
# self.assertEqual(oframe.sample_rate, iframe.sample_rate)
# self.assertEqual(oframe.samples, iframe.samples)

# # flush
# oframes = resampler.resample(None)
# self.assertEqual(len(oframes), 0)
# av.logging.set_level(None)
def test_pts_assertion_same_rate(self):
import av

av.logging.set_level(av.logging.VERBOSE)
resampler = AudioResampler("s16", "mono")

# resample one frame
iframe = AudioFrame("s16", "stereo", 1024)
iframe.sample_rate = 48000
iframe.time_base = Fraction(1, 48000)
iframe.pts = 0

oframes = resampler.resample(iframe)
self.assertEqual(len(oframes), 1)

oframe = oframes[0]
self.assertEqual(oframe.pts, 0)
self.assertEqual(oframe.time_base, iframe.time_base)
self.assertEqual(oframe.sample_rate, iframe.sample_rate)
self.assertEqual(oframe.samples, iframe.samples)

# resample another frame
iframe.pts = 1024

oframes = resampler.resample(iframe)
self.assertEqual(len(oframes), 1)

oframe = oframes[0]
self.assertEqual(oframe.pts, 1024)
self.assertEqual(oframe.time_base, iframe.time_base)
self.assertEqual(oframe.sample_rate, iframe.sample_rate)
self.assertEqual(oframe.samples, iframe.samples)

# resample another frame with a pts gap, do not raise exception
iframe.pts = 9999
oframes = resampler.resample(iframe)
self.assertEqual(len(oframes), 1)

oframe = oframes[0]
self.assertEqual(oframe.pts, 9999)
self.assertEqual(oframe.time_base, iframe.time_base)
self.assertEqual(oframe.sample_rate, iframe.sample_rate)
self.assertEqual(oframe.samples, iframe.samples)

# flush
oframes = resampler.resample(None)
self.assertEqual(len(oframes), 0)
av.logging.set_level(None)

def test_pts_assertion_new_rate_up(self):
resampler = AudioResampler("s16", "mono", 44100)
Expand Down

0 comments on commit b68ec33

Please sign in to comment.