Skip to content
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

Fix input_needed_for_output to not return one less than needed, causing draining down the line #811

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

padenot
Copy link
Collaborator

@padenot padenot commented Jan 23, 2025

The calculation was incorrect. This meant that in some circumstances,
e.g. playing a 44.1Hz stream on a 96kHz interface, with a block of 441
frames on Windows, rounding was eventually returning 440 instead of 441
in the callback, leading to the stream entering the draining phase.

…ng draining down the line

The calculation was incorrect. This meant that in some circumstances,
e.g. playing a 44.1Hz stream on a 96kHz interface, with a block of 441
frames on Windows, rounding was eventually returning 440 instead of 441
in the callback, leading to the stream entering the draining phase.
…ck sizes and sample-rates

This might be a bit much, runtime is 25s debug ASAN, 6s opt, but should
be thorough.
@padenot
Copy link
Collaborator Author

padenot commented Jan 23, 2025

This fixes BMO#1940319.

Comment on lines +9 to +11
#include "cubeb/cubeb.h"
#include "cubeb_log.h"
#include "cubeb_resampler.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

These should be outside #ifdef NOMINMAX.
I'd put them beside the cubeb_resampler_internal.h include.


cubeb_resampler * resampler = cubeb_resampler_create(
nullptr, nullptr, &out_params, source_rate, data_cb, nullptr,
CUBEB_RESAMPLER_QUALITY_DEFAULT, CUBEB_RESAMPLER_RECLOCK_NONE);
Copy link
Contributor

Choose a reason for hiding this comment

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

CUBEB_RESAMPLER_QUALITY_VOIP should make the test run a bit faster.
I don't expect the resampler latency to affect any of the input output block size arithmetic after the prefill at intialization.

Copy link
Contributor

@karlt karlt left a comment

Choose a reason for hiding this comment

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

I see now that github interprets review of a commit as review of the entire pull request.

Thank you for testing thoroughly.
I expect that this function should be able to be simple without special cases. The special cases make me suspect that something else may not be quite right.

Comment on lines -291 to -292
int32_t resampled_frames_left =
samples_to_frames(resampling_out_buffer.length());
Copy link
Contributor

Choose a reason for hiding this comment

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

Please explain in the commit message that resampling_out_buffer.length() is removed from the equation because resampled frames are not carried over from one output() operation to the next.

@@ -288,15 +288,18 @@ template <typename T> class cubeb_resampler_speex_one_way : public processor {
assert(output_frame_count >= 0); // Check overflow
Copy link
Contributor

Choose a reason for hiding this comment

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

  • exactly output_frame_count resampled frames. This can return a number
  • slightly bigger than what is strictly necessary, but it guaranteed that the
  • number of output frames will be exactly equal. */

The description is not really describing what the function is doing.

Unless this function knows the internal state of the speex resampler, it cannot know how to produce exactly output_frame_count resampled frames from the speex resampler.

  • Consider resampling at a ratio of 2 input frames to 3 output frames. Sometimes an input of 1 frame will produce 1 frame of output, but sometimes 2 frames of output.

The cubeb_resampler_speex_one_way::output() should return exactly output_frame_count frames, but that is because output_frame_count is passed to output().

The function can calculate a number of input frames that is known sufficient to produce (at least) output_frame_count resampled frames.

Comment on lines +293 to 294
if (input_frames_needed_frac < 0) {
return 0;
Copy link
Contributor

@karlt karlt Jan 27, 2025

Choose a reason for hiding this comment

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

resampling_ratio is always non-negative, as is length(), so float arithmetic will always produce a non-negative input_frames_needed_frac, and so this path is never taken.

I'm uncomfortable with using float arithmetic because I don't know that we can prove that n / m * (i * m) >= n * i over the ranges of rates that we care about. Replacing resampling_ratio with a fraction, with integer arithmetic, would provide assurance of this. We'd then want some checks on the ranges for the integer arithmetic.

Comment on lines +298 to +300
if (static_cast<float>(unresampled_frames_left) > resampling_ratio &&
resampling_ratio > 1) {
input_frame_needed -= unresampled_frames_left;
Copy link
Contributor

Choose a reason for hiding this comment

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

I can't think why unresampled_frames_left should be ignored when less than resampling_ratio nor when resampling_ratio <= 1.

Can unresampled_frames_left always be subtracted from input_frame_needed?

The main defect that I see in the old code is that unresampled_frames_left was subtracted from output_frame_count rather than from an input frame count.
Did you observe a problem with that defect resolved?
If so, then what were the specific parameters?

Copy link
Contributor

Choose a reason for hiding this comment

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

This was an attempt with always subtracting unresampled_frames_left, which fails with 16000 -> 44100 wanting 96 output samples. I'm still looking into this:
https://pernos.co/debug/KvmgNpBzyzyI7mFKNzXHhw/index.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants