-
Notifications
You must be signed in to change notification settings - Fork 64
Fix Windows build and add Windows CPU tests #806
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d81c3e3
78ea520
a0dd832
1b5f926
218ca2d
4cce232
ab53ca9
8450297
3b79ff5
e4f00a4
ac8116d
9573fc2
963590e
2f2c91d
d896211
01f9dc0
60df908
09d077e
61ef07b
1ae09aa
6921d4c
dab2d8e
ff91aca
bc919cc
d988393
9da6539
99d0308
7247adc
11a7c55
2bdb643
7b12d7d
e7969d3
cbff515
f7c8bd5
02ad2d7
8764c60
110f5b0
f0edf32
aacbfc6
fc17647
5da6109
99cedf1
fd0b7d5
e08b5e7
f073d1f
2f51f25
ae8a2c6
3b06000
06d0627
5903c07
c7c6077
7822d73
8a30f92
b7b0ea9
f25a0a7
89e213d
281cc38
f2c3414
12df6c7
9048f6c
c02ffc5
4e4f739
e40dfc2
ff6d5d1
f83e9da
23c20de
7657857
b531d4b
d89f81a
5171e8c
3892751
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,20 +53,6 @@ function(make_torchcodec_sublibrary | |
# Avoid adding the "lib" prefix which we already add explicitly. | ||
set_target_properties(${library_name} PROPERTIES PREFIX "") | ||
|
||
if(WIN32) | ||
# On Windows, the built artifacts are put in Release/Debug | ||
# subdirectories by default. We want to avoid that, otherwise our | ||
# install() step would not know where to find those. | ||
set_target_properties(${library_name} PROPERTIES | ||
RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR} | ||
RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR} | ||
LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR} | ||
LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR} | ||
ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR} | ||
ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR} | ||
) | ||
endif() | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @traversaro correctly pointed out that this can now be removed, as a consequence of the other |
||
target_link_libraries( | ||
${library_name} | ||
PUBLIC | ||
|
@@ -84,16 +70,17 @@ function(make_torchcodec_libraries | |
# | ||
# 1. libtorchcodec_coreN.{ext}: Base library which contains the | ||
# implementation of VideoDecoder and everything VideoDecoder needs. On | ||
# Linux, {ext} is so. On Mac, it is dylib. | ||
# Linux, {ext} is so. On Mac, it is dylib. On Windows it's dll. | ||
# | ||
# 2. libtorchcodec_custom_opsN.{ext}: Implementation of the PyTorch custom | ||
# ops. Depends on libtorchcodec_coreN.{ext}. On Linux, {ext} is so. | ||
# On Mac, it is dylib. | ||
# On Mac, it is dylib. On Windows it's dll. | ||
# | ||
# 3. libtorchcodec_pybind_opsN.{ext}: Implementation of the pybind11 ops. We | ||
# keep these separate from the PyTorch custom ops because we have to | ||
# load these libraries separately on the Python side. Depends on | ||
# libtorchcodec_coreN.{ext}. On BOTH Linux and Mac {ext} is so. | ||
# libtorchcodec_coreN.{ext}. On BOTH Linux and Mac {ext} is so. On | ||
# Windows, it's pyd. | ||
|
||
# 1. Create libtorchcodec_coreN.{ext}. | ||
set(core_library_name "libtorchcodec_core${ffmpeg_major_version}") | ||
|
@@ -174,6 +161,14 @@ function(make_torchcodec_libraries | |
"${pybind_ops_sources}" | ||
"${pybind_ops_dependencies}" | ||
) | ||
|
||
if(WIN32) | ||
# On Windows, we need to set the suffix to .pyd so that Python can | ||
# import the shared library as a module. Just setting the MODULE type | ||
# isn't enough. | ||
set_target_properties(${pybind_ops_library_name} PROPERTIES SUFFIX ".pyd") | ||
endif() | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one is actually important, tried removing it in #826 and loading fails. |
||
# pybind11 limits the visibility of symbols in the shared library to prevent | ||
# stray initialization of py::objects. The rest of the object code must | ||
# match. See: | ||
|
@@ -223,7 +218,7 @@ function(make_torchcodec_libraries | |
install( | ||
TARGETS ${all_libraries} | ||
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX} | ||
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} # For Windows DLLs | ||
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} # For Windows | ||
) | ||
|
||
endfunction() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,10 +104,14 @@ AudioEncoder::~AudioEncoder() { | |
|
||
void AudioEncoder::close_avio() { | ||
if (avFormatContext_ && avFormatContext_->pb) { | ||
avio_flush(avFormatContext_->pb); | ||
if (avFormatContext_->pb->error == 0) { | ||
avio_flush(avFormatContext_->pb); | ||
} | ||
|
||
if (!avioContextHolder_) { | ||
avio_close(avFormatContext_->pb); | ||
if (avFormatContext_->pb->error == 0) { | ||
avio_close(avFormatContext_->pb); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Above is a drive-by as I was trying to fix the problem with FFmpeg 5. I'm not sure it fixed anything, but it is probably still a good change to have? I can extract it out in another PR if it's preferred. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this code imply that if the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on my digging, it's also that it's risky to call flush when there's an error state. Seems reasonable to keep it in. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, it because when error isn't 0 then flushing or calling |
||
// avoids closing again in destructor, which would segfault. | ||
avFormatContext_->pb = nullptr; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
assert_tensor_close_on_at_least, | ||
get_ffmpeg_major_version, | ||
in_fbcode, | ||
IS_WINDOWS, | ||
NASA_AUDIO_MP3, | ||
SINE_MONO_S32, | ||
TestContainerFile, | ||
|
@@ -151,15 +152,29 @@ def test_bad_input_parametrized(self, method, tmp_path): | |
raise ValueError(f"Unknown method: {method}") | ||
|
||
decoder = AudioEncoder(self.decode(NASA_AUDIO_MP3).data, sample_rate=10) | ||
with pytest.raises(RuntimeError, match="invalid sample rate=10"): | ||
avcodec_open2_failed_msg = "avcodec_open2 failed: Invalid argument" | ||
with pytest.raises( | ||
RuntimeError, | ||
match=avcodec_open2_failed_msg if IS_WINDOWS else "invalid sample rate=10", | ||
): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. above and in other tests below: on Windows, we're hitting this early return: So we're unable to validate some parameters early, and we just fail in the call to |
||
getattr(decoder, method)(**valid_params) | ||
|
||
decoder = AudioEncoder( | ||
self.decode(NASA_AUDIO_MP3).data, sample_rate=NASA_AUDIO_MP3.sample_rate | ||
) | ||
with pytest.raises(RuntimeError, match="invalid sample rate=10"): | ||
with pytest.raises( | ||
RuntimeError, | ||
match=avcodec_open2_failed_msg if IS_WINDOWS else "invalid sample rate=10", | ||
): | ||
getattr(decoder, method)(sample_rate=10, **valid_params) | ||
with pytest.raises(RuntimeError, match="invalid sample rate=99999999"): | ||
with pytest.raises( | ||
RuntimeError, | ||
match=( | ||
avcodec_open2_failed_msg | ||
if IS_WINDOWS | ||
else "invalid sample rate=99999999" | ||
), | ||
): | ||
getattr(decoder, method)(sample_rate=99999999, **valid_params) | ||
with pytest.raises(RuntimeError, match="bit_rate=-1 must be >= 0"): | ||
getattr(decoder, method)(**valid_params, bit_rate=-1) | ||
|
@@ -175,12 +190,14 @@ def test_bad_input_parametrized(self, method, tmp_path): | |
self.decode(NASA_AUDIO_MP3).data, sample_rate=NASA_AUDIO_MP3.sample_rate | ||
) | ||
for num_channels in (0, 3): | ||
with pytest.raises( | ||
RuntimeError, | ||
match=re.escape( | ||
match = ( | ||
avcodec_open2_failed_msg | ||
if IS_WINDOWS | ||
else re.escape( | ||
f"Desired number of channels ({num_channels}) is not supported" | ||
), | ||
): | ||
) | ||
) | ||
with pytest.raises(RuntimeError, match=match): | ||
getattr(decoder, method)(**valid_params, num_channels=num_channels) | ||
|
||
@pytest.mark.parametrize("method", ("to_file", "to_tensor", "to_file_like")) | ||
|
@@ -240,6 +257,9 @@ def test_against_cli( | |
|
||
if get_ffmpeg_major_version() == 4 and format == "wav": | ||
pytest.skip("Swresample with FFmpeg 4 doesn't work on wav files") | ||
if IS_WINDOWS and get_ffmpeg_major_version() <= 5 and format == "mp3": | ||
# TODO: https://github.com/pytorch/torchcodec/issues/837 | ||
pytest.skip("Encoding mp3 on Windows is weirdly buggy") | ||
|
||
encoded_by_ffmpeg = tmp_path / f"ffmpeg_output.{format}" | ||
subprocess.run( | ||
|
@@ -295,8 +315,15 @@ def test_against_cli( | |
rtol, atol = 0, 1e-3 | ||
else: | ||
rtol, atol = None, None | ||
|
||
if IS_WINDOWS and format == "mp3": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this condition be hit? It might be duplicate with the check and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The check above only skips on FFmpeg <= 5, but FFmpeg 6 and 7 will still reach this line :) |
||
# We're getting a "Could not open input file" on Windows mp3 files when decoding. | ||
# TODO: https://github.com/pytorch/torchcodec/issues/837 | ||
return | ||
|
||
samples_by_us = self.decode(encoded_by_us) | ||
samples_by_ffmpeg = self.decode(encoded_by_ffmpeg) | ||
|
||
assert_close( | ||
samples_by_us.data, | ||
samples_by_ffmpeg.data, | ||
|
@@ -320,6 +347,9 @@ def test_against_to_file( | |
): | ||
if get_ffmpeg_major_version() == 4 and format == "wav": | ||
pytest.skip("Swresample with FFmpeg 4 doesn't work on wav files") | ||
if IS_WINDOWS and get_ffmpeg_major_version() <= 5 and format == "mp3": | ||
# TODO: https://github.com/pytorch/torchcodec/issues/837 | ||
pytest.skip("Encoding mp3 on Windows is weirdly buggy") | ||
|
||
encoder = AudioEncoder(self.decode(asset).data, sample_rate=asset.sample_rate) | ||
|
||
|
@@ -340,9 +370,12 @@ def test_against_to_file( | |
else: | ||
raise ValueError(f"Unknown method: {method}") | ||
|
||
torch.testing.assert_close( | ||
self.decode(encoded_file).data, self.decode(encoded_output).data | ||
) | ||
if not (IS_WINDOWS and format == "mp3"): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question here, is this a duplicate check with line 319? On a similar note, it might be better to explicitly call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not a duplicate because this will be reached by FFmpeg 6 and 7. You're right that we should generally prefer using In this specific case I think it's best not to call |
||
# We're getting a "Could not open input file" on Windows mp3 files when decoding. | ||
# TODO: https://github.com/pytorch/torchcodec/issues/837 | ||
torch.testing.assert_close( | ||
self.decode(encoded_file).data, self.decode(encoded_output).data | ||
) | ||
|
||
def test_encode_to_tensor_long_output(self): | ||
# Check that we support re-allocating the output tensor when the encoded | ||
|
@@ -417,7 +450,7 @@ def test_num_channels( | |
|
||
sample_rate = 16_000 | ||
source_samples = torch.rand(num_channels_input, 1_000) | ||
format = "mp3" | ||
format = "flac" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this change enable the test to pass on Windows? If so, we could parameterize the format and add an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct, I changed from mp3 to flac due to the issues we had on Windows. I agree that we could parameterize, but I'm not sure it's necessary for the purpose of this test, as it wouldn't make it more robust. Mostly this test is just about checking the number of output channels is respected, which is agnostic to the format. |
||
|
||
encoder = AudioEncoder(source_samples, sample_rate=sample_rate) | ||
params = dict(num_channels=num_channels_output) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Above is the main fix that was needed for the extensions to load properly, something I had been blocked on for a few weeks. The fix is from @traversaro :
NicolasHug#1
IIUC, on windows and with our current setup, cmake is generating multiple build files. And without this fix, the different build files would be inheriting different build configuration. Typically we'd be building some parts with the
Release
build type, while other would be inheriting a different build type, causing problems at runtime when loading the libraries.@traversaro thank you so much again for unblocking us!