Skip to content

Support encoding to file-like object #754

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

Open
wants to merge 27 commits into
base: main
Choose a base branch
from

Conversation

NicolasHug
Copy link
Member

@NicolasHug NicolasHug commented Jul 5, 2025

This PR adds the to_file_like() method to the AudioEncoder. This allows users to encode samples into a custom file-like object that supports seek and write methods.

Marking this as draft because there are unresolved points (see below), but this is still ready for a solid first review round.

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 5, 2025
@NicolasHug NicolasHug marked this pull request as draft July 5, 2025 14:49

# This check is useless but it's critical to keep it to ensures that samples
# is still alive during the call to encode_audio_to_file_like.
assert samples.is_contiguous()
Copy link
Member Author

@NicolasHug NicolasHug Jul 6, 2025

Choose a reason for hiding this comment

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

I hate that we have to do this but I do not see any other obvious way to keep the input samples alive for the duration of the call.
Claude is saying that we could just pass samples as a py::object. We won't be able to turn it back to a tensor (as mentioned in the code comment above), but claude claims that passing it as a parameter will ensure that pybind will keep it alive. I cannot verify this.

@scotts, any thoughts?

Copy link
Contributor

@scotts scotts Jul 17, 2025

Choose a reason for hiding this comment

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

On the keep-alive part, I believe Claude is right. If we pass something as a py::object, that gets properly reference-counted which will keep the object alive. When we launder a pointer as an int, there's no reference counting.

Of course, we would ideally just pass the tensor - but we run into problems passing tensors as tensors into the pybind11 code. The next simplest thing that we probably can't do for performance reasons is to copy the tensor into either bytes or a list, and then pass those as py::object. But since samples will be large, I don't think we want to do that.

Most workarounds I can think of are worse. One that might be just as bad, but could potentially apply to both this situation and decoder creation:

  1. On the pybind11 side, we only create the AVIOFileLikeContext. We don't create the encoder or decoder. We do still accept the file-like objects, and they are still stored in the AVIOFileLIkeContext.
  2. We return an int from the C++ side to the Python side where that int is a pointer to the AVIOFileLikeContext.
  3. On the PyTorch custom ops side, we have functions for create-from-file-like and encode-to-file-like that accept the int value and do a reinterpret_cast<AVIOFileLikeContext*> in the C++. Those are then passed to the decoder or encode.

As it is right now, we're doing a lot of ugly pointer casting with tensors. The above may actually be better, as then the pybind11 code is only really concerned with creating AVIOFIleLikeContext objects. It doesn't even need to know about encoders and decoders.

@NicolasHug NicolasHug marked this pull request as ready for review July 6, 2025 14:51
@NicolasHug NicolasHug marked this pull request as draft July 7, 2025 09:14
std::optional<int64_t> bit_rate = std::nullopt,
std::optional<int64_t> num_channels = std::nullopt) {
// We assume float32 *and* contiguity, this must be enforced by the caller.
auto tensor_options = torch::TensorOptions().dtype(torch::kFloat32);
Copy link
Contributor

Choose a reason for hiding this comment

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

If we keep this technique, we can probably allow all dtypes by passing in the dtype from the Python side as ints. I assume the Python and C++ enums agree on values, but even if they don't, we can figure out the mapping. Ugly, but possible.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I don't think we need to support more dtypes than just float32: the input samples that the user gives us must be float32 already. This comment is just here to explicitly state the assumptions that are made within encode_audio_to_file_like.

@NicolasHug NicolasHug marked this pull request as ready for review August 8, 2025 15:24
encode();
return avioContextHolder_->getOutputTensor();
return dynamic_cast<AVIOToTensorContext*>(avioContextHolder_.get())
->getOutputTensor();
Copy link
Contributor

Choose a reason for hiding this comment

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

Since dynamic_cast will return a nullptr if it can't do the cast, this a potential segfault risk. We should do a TORCH_CHECK on the result to make sure it's not a nullptr before accessing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Meta Open Source bot.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants