-
Notifications
You must be signed in to change notification settings - Fork 243
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 channel count upscaling #559
Conversation
This changes channel upscaling so all input samples are spread out across the output channels, instead of repeating the last sample over and over. This approach should yield more accurate results. Fixes RustAudio#558.
It seems like there is no standard order for 5.1 and 7.1 surround sound instead it differs across file formats and can even be configurable. For example see channel assignment for flac. That is gonna make correct channel conversion quite challenging. As it might need work on the decoders to unify how they provide channels. Symphonia does at least provide a consistent order it seems: symphonia docs. It makes perfect sense to turn mono to stereo by assigning both speakers the same sample. Scaling stereo to 5.1 and 7.1 should probably be done by recognizing which of the output channels is the left and which the right channel and sending the rest zero's/nothing. Now back to this PR and stereo to 5.1/7.1 up-scaling :) Does that sound good to you? |
So it can be safely assumed that the first 18 channels will always be in the WAVEFORMATEXTENSIBLE order? Like in this table here? So the way it should be is...
For channels from 9 to 18 it'd get more complicated because they don't alternate left/right anymore, but the idea would be the same. Match left with left, right with right, center with center. This kinda begs the question though. As you said, when going from mono to stereo it makes sense to duplicate the sample, but should that be done as well when going higher? maybe for 2 to 8 for example, this makes more sense? [1, 2, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 0] Let me know what you think. |
As far as I can deduce from the cpal source thats correct. I would say anything beyond 8 channels it too nice for rodio right now (though that's not my call to make).
That would ensure audio to most speaker. Though that is not what (a quick google shows) is recommended. The tldr being:
I think we can safely generalize that to: do not use more channels then the source is offers as that will decrease clarity. With an exception for going from mono to stereo (it is quite annoying to have sound only left or right).
So yes given the source above to me that makes more sense. Just for clarity that would correspond to this table:
|
Mono -> stereo remain the only exception.
c87da03
to
41604cf
Compare
oh and while your at would you mind doing a tiny refactor :) At line 74 (right now) I think this:
would be clearer like this
and still work right? Would you agree? |
Update tests as well.
} | ||
|
||
#[test] | ||
fn size_hint() { |
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.
this test seems quite complex and coupled to the implementation. How about something along these lines:
fn test(iter, from, to) {
size_hint = iter.size_hint();
actual size = iter.count() // from Iterator trait
assert( is actual size contained in size_hint? )
}
If we wanted (might be overkill) we could define a custom Test iterator with a custom uncertain size_hint (I think the min and max size hints are the same for Vec since the len is known).
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.
You have a point, but the thing is the tricky/most fallible part of the size_hint
calculation is when some of the items have already been consumed, so that's the part that is most valuable to test I think.
What about something like this?
fn test(input: &[i16], from: cpal::ChannelCount, to: cpal::ChannelCount) {
let count = ChannelCountConverter::new(input.iter().copied(), from, to).count();
let mut converter = ChannelCountConverter::new(input.iter().copied(), from, to);
for i in 0..count {
assert_eq!(converter.size_hint(), (count - i, Some(count - i)));
converter.next();
}
}
But I am okay with your version if you think this is still overkill.
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.
You can make the test function generic and clone the iterator: fn test<T>(iter: impl Iterator<Item=T> + Clone, from, to)
. Then you can pass in iterators in various states.
The overkill option would be passing in a custom iterator specifically made for this test. That would look something like this:
struct TestIter {
bounds_to_report: (usize, Option<usize>),
length: usize,
consumed: usize,
}
impl Iterator for TestIter {
Item = ();
fn next(&mut self) -> Self::item {
if self.consumed >= self.length {
None
} else
Some(())
}
}
fn size_hint(&self) -> (usize, Option<usize) {
self.bounds_to_report
}
}
This would allow us to test the behavior when the upper and lower bound are not the same. But I have looked around and no one implementing Iterator does something like this 😅, So lets not.
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.
I don't see the appeal of the + Clone
version. It seems like it'd require a lot of calls to be very useful...
I think these two options are sound:
fn test_1(input: &[i16], from: cpal::ChannelCount, to: cpal::ChannelCount) {
let converter = ChannelCountConverter::new(input.iter().copied(), from, to);
let size_hint = converter.size_hint();
let count = converter.count();
assert_eq!(size_hint, (count, Some(count)));
}
fn test_2(input: &[i16], from: cpal::ChannelCount, to: cpal::ChannelCount) {
let mut converter = ChannelCountConverter::new(input.iter().copied(), from, to);
let count = converter.clone().count();
for i in 0..count {
assert_eq!(converter.size_hint(), (count - i, Some(count - i)));
converter.next();
}
}
Let me know which one you think is best and I'll commit and push the changes.
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.
I don't see the appeal of the
+ Clone
version. It seems like it'd require a lot of calls to be very useful...
oh seems like that was just me being silly there. I had the count before the size_hint (and thus consuming the iterator) which I fixed using clone. Swapping the order like you do is obv superior 👍
The idea of passing in an iterator instead of a slice is that you can pass in partially consumed iterators. You could pass in a [1,2,3,4].into_iter().skip(3)
. Though the loop works too!
The whole i in 0..count
and then count - i
, do you agree with replacing that with for left_in_iter in (0..count).rev()
and (left_in_iter, Some(left_in_iter))
?
I don't see any way this could improve further so push it (test_2) I'll ask est31 to take a quick look and will get it merged!
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.
Oh yeah that is better. It'll have to be (0..=count).rev()
though.
I'm also fixing a bug that caused the size_hint to go up again if calling .next()
after the iterator was exhausted, and adding one final assert_eq!
to test for it.
thanks for seeing a fix here :) Greetings from the veloren team |
This changes channel upscaling so all input samples are spread out across the output channels, instead of repeating the last sample over and over. This approach should yield more accurate results. Fixes #558.