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

Forward error correction #18

Merged
merged 15 commits into from
Apr 26, 2018
Merged

Conversation

stop-start
Copy link

@stop-start stop-start commented Mar 26, 2018

Issue: #17

  • Added relevant CTLs in encoder and decoder
  • Added methods decodeFEC and decodeFECFloat32
  • Added tests for all of those

For the FEC test, I manually "dropped" packets and used the csv with chart method using the code in the opus_test.go for visualizing the results. It looks good I think.
I attached the graphs pictures.

fec
fec_32

@hraban
Copy link
Owner

hraban commented Mar 27, 2018

Thanks Elinor, this looks very promising. I'll review it during the week. 👍

Copy link
Owner

@hraban hraban left a comment

Choose a reason for hiding this comment

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

Looks great. Some minor changes and consistency issues here and there, but nothing big. My only real concern is documentation: how do we explain to users how to use this? Are supposed to detect packet loss themselves, how are they to know the length of the lost packets, etc. It doesn't have to be a novel; a quick line or two in the readme + on the godoc string can help someone to figure out the rest for themselves.

Thanks for the great work.

encoder.go Outdated

// SetInBandFEC configures the encoder's use of inband forward error
// correction (FEC)
func (enc *Encoder) SetInBandFEC(fec int) error {
Copy link
Owner

Choose a reason for hiding this comment

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

this argument could be a boolean, as with SetDTX

encoder.go Outdated
}

// InBandFEC gets the encoder's configured inband forward error correction (FEC)
func (enc *Encoder) InBandFEC() (int, error) {
Copy link
Owner

Choose a reason for hiding this comment

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

return type boolean

opus_test.go Outdated
// This is hard to check programatically, but I plotted the graphs in a
// spreadsheet and it looked great. The encoded waves both start out with a
// string of zero samples before they pick up, and the G4 is phase shifted
// by half a period, but that's all fine for lossy audio encoding.
Copy link
Owner

Choose a reason for hiding this comment

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

this comment relates to a stereo signal and isn't accurate here. you can remove it and just say "Commented out for the same reason as in TestStereo", for example.

opus_test.go Outdated
// This is hard to check programatically, but I plotted the graphs in a
// spreadsheet and it looked great. The encoded waves both start out with a
// string of zero samples before they pick up, and the G4 is phase shifted
// by half a period, but that's all fine for lossy audio encoding.
Copy link
Owner

Choose a reason for hiding this comment

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

same on this comment being out of sync with the content.

t.Errorf("Unexpected encoder loss percentage value. Got %d, but expected %d",
lp, lossPerc)
}
}
Copy link
Owner

Choose a reason for hiding this comment

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

this only test successes, not failures. can you add a test for -1 and 101, ensuring failure?

decoder_test.go Outdated
t.Fatalf("Couldn't decode data: %v", err)
}
samples, err := dec.LastPacketDuration()
if err!=nil{
Copy link
Owner

Choose a reason for hiding this comment

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

make sure to run gofmt on all your changes. most editors / ides provide hooks to run it automatically on save, e.g. vim-go does it by default, emacs go-mode has a hook you can enable in .emacs. more info: https://blog.golang.org/go-fmt-your-code . I've added a new test to travis which automatically detects this: if you rebase your changes onto v2 it will show you if it worked:

git remote add origin upstream https://github.com/hraban/opus
git fetch upstream
git rebase upstream/v2
git push --force-with-lease

your PR will update to show the travis build status

Copy link
Author

Choose a reason for hiding this comment

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

Sorry about that, for whatever reason Goland is always removing the file watchers. I keep having to set "use gofmt on save"

decoder.go Outdated
@@ -105,3 +111,64 @@ func (dec *Decoder) DecodeFloat32(data []byte, pcm []float32) (int, error) {
}
return n, nil
}

// Decode encoded Opus data into the supplied buffer with forward error
// correction. The supplied buffer will be entirely filled.
Copy link
Owner

Choose a reason for hiding this comment

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

The first word of a godoc comment must be the name of the method. It's an odd convention, but a convention nonetheless. Weʼll have to stick to it.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah sorry, that show that copy/paste is not always your friend...

decoder.go Outdated
}

// Decode encoded Opus data into the supplied buffer with forward error
// correction. The supplied buffer will be entirely filled.
Copy link
Owner

Choose a reason for hiding this comment

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

first word must be method name

(*C.uchar)(&data[0]),
C.opus_int32(len(data)),
(*C.opus_int16)(&pcm[0]),
C.int(cap(pcm)),
Copy link
Owner

Choose a reason for hiding this comment

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

This brought to my attention a bug in all the Decode* functions :/ I don't remember why I originally used cap, but I think it's a bug. It's asymmetrical with io.Reader, whose signature I tried to emulate. It was probably originally chosen for alignment with the Encode* functions and the suggestion to resize them (data = data[:n]), but it doesn't make sense for Decode.

Anyway, this is all outside the scope of your PR, so it's all good here.

opus_test.go Outdated
}
pcmBuffer = pcmBuffer[:n]
if len(pcmBuffer) != n {
t.Fatalf("Length mismatch: %d samples in, %d out", len(pcmBuffer), n)
Copy link
Owner

Choose a reason for hiding this comment

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

I'm not sure about this error, by the way. the length of the pcmBuffer does not seem to be the amount of samples fed to the encoder in this pass? especially after truncating to :n. What exactly is being tested, here?

Copy link
Author

Choose a reason for hiding this comment

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

The idea of the test is to lose two packets (I guess we can do it random but to be honest it was just easier like this). All of the packets have to be decoded without fec, that's where this line comes from.
In addition to that the packets coming after the "lost ones" have to be decoded with fec on (there you can see that I don't check for the length but just the error).

Copy link
Owner

@hraban hraban Apr 2, 2018

Choose a reason for hiding this comment

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

I agree the overall test logic itself is ok, but this specific guard is a tautology. after a = a[:n], you will always len(a) ≤ n, and because you start out with len(a) = FRAME_SIZE ⋙ n this is a tautology. You might be trying to test e.g. if n ≠ PACKET_SIZE?

Copy link
Author

Choose a reason for hiding this comment

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

TBH I just copy/pasted it from the TestCodec test, but I don't mind changing it to n!=PACKET_SIZE or actually n!=FRAME_SIZE since I changed it.

Copy link
Owner

Choose a reason for hiding this comment

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

That's fine :) don't forget to change the error message. Currently you're testing for PACKET_SIZE and n, but reporting len(pcmBuffer) and n.

opus_test.go Outdated
const SAMPLE_RATE = 48000
const FRAME_SIZE_MS = 60
const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000
const PACKET_SIZE = 480 // 10ms packet
Copy link
Owner

Choose a reason for hiding this comment

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

This is in fact the frame, not the 60ms defined further up. Your entire audio stream is 60ms, the frame size is 10ms. Packets are more of a "transport level" term, when you start talking about networks. If you send every frame as a separate network message (which will typically be the case), then frame ~= packet. Either way, you'll want to use 10 as your FRAME_SIZE_MS, and use a different name for the 60 constant you defined above.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the explanation, I wasn't sure about that.

}

pcmBuffer := make([]int16, samples)
if err = dec.DecodeFEC(encodedData[i], pcmBuffer); err != nil {
Copy link
Owner

@hraban hraban Mar 31, 2018

Choose a reason for hiding this comment

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

this is cheating :) you're passing the "dropped" packet to decodefec. Should be encodedData[i-1]. This will get you a different output:

screen shot 2018-03-31 at 14 14 05

EDIT: Scratch that—I was wrong! "Forward" error correction doesn't mean the error correcting data is encoded forward, but backward! Which makes total sense, because you can't predict the data to come. So your test was correct.

@stop-start
Copy link
Author

I still need to add some explanations about FEC in the README.
I'm adding them right now

@hraban hraban merged commit 0f2e0b4 into hraban:v2 Apr 26, 2018
@hraban hraban mentioned this pull request Apr 24, 2021
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.

None yet

3 participants