Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

Rust sendrecv #20

Closed
wants to merge 25 commits into from
Closed

Rust sendrecv #20

wants to merge 25 commits into from

Conversation

maxmcd
Copy link
Contributor

@maxmcd maxmcd commented May 31, 2018

There's more work to be done here (No flag support, no cleanup, a few shortcuts) but the rust script seems fully functional. Wanted to inquire about two things:

  1. Is this rust code welcome (I'm just writing this up as a personal exercise) and is there any feedback?
  2. Are the docker-compose and related Dockerfiles desired? I can move them off the branch, but figured they might be helpful.

Can complete the rust code and clean up/document docker-compose upon confirmation of #2.

}

fn sdp_message_as_text(offer: gstreamer_webrtc::WebRTCSessionDescription) -> Option<String> {
unsafe {
Copy link
Contributor

Choose a reason for hiding this comment

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

sdroege/gstreamer-rs#108 (comment) for why this is necessary currently. We should fix that before merging this PR :)

Copy link
Contributor

Choose a reason for hiding this comment

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

I've added those now here sdroege/gstreamer-rs@85ca3b9

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lovely timing, as mine was incorrect: maxmcd/gstreamer-rs@dd0918b

:)

Copy link
Contributor

Choose a reason for hiding this comment

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

A mutable borrow is not needed here as all these are immutable fields after all, and from_glib_none is needed here because we don't take ownership of the field (otherwise we would steal it from the struct). Ideally we would just borrow it, but there's no borrowing mechanism in glib-rs at this point that could be used for this (and generally borrowing from GLib types is dangerous by default except for some special cases), so we have to copy here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let me know if my version works and if there are any other problems :) And thanks again for working on this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Your version works perfectly. Replaced the unsafe call.

return AppState::PeerConnecting;
}

fn register_with_server(app_state: &Arc<AtomicUsize>, out: &ws::Sender) -> AppState {
Copy link
Contributor

@sdroege sdroege May 31, 2018

Choose a reason for hiding this comment

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

Might be nicer to move the app state into a struct for extensibility later instead of directly passing around that single integer everywhere (and maybe wrap that struct into an Rc<Mutex<_>>)

webrtc
.emit(
"set-local-description",
&[&offer.to_value(), &promise.to_value()],
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm what's the point of the promise if it's not used anywhere afterwards?

Copy link
Contributor Author

@maxmcd maxmcd May 31, 2018

Choose a reason for hiding this comment

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

I was mostly parroting the original c implementation: webrtc-sendrecv.c#L252-L255

It does seem though that no promise is allowed: https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-bad/html/gst-plugins-bad-plugins-webrtcbin.html#GstWebRTCBin-set-local-description

On that point, I couldn't figure out how to pass null in a situation like this. Is there some stand-in in the library?

Copy link
Contributor

Choose a reason for hiding this comment

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

You should be able to pass &None::<gst::Promise> here

});
let options = gst::Structure::new_empty("options");
webrtc_clone
.emit("create-offer", &[&options.to_value(), &promise.to_value()])
Copy link
Contributor

Choose a reason for hiding this comment

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

The to_value() in all the emit calls should not be needed, only the &

sink.sync_state_with_parent().unwrap();
gst::Element::link_many(&[&q, &conv, &resample, &sink]).unwrap();
} else {
pipe.clone()
Copy link
Contributor

Choose a reason for hiding this comment

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

You could move this dynamic_cast outside the if and have it only once then, storing the bin in a local variable

queue !
application/x-rtp,media=audio,encoding-name=OPUS,payload=97 ! sendrecv.
",
).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

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

Explicitly creating the elements would seem nicer than string programming, but I guess it's fine :)

.unwrap();

let ret = pipe.set_state(gst::State::Playing);
assert_ne!(ret, gst::StateChangeReturn::Failure);
Copy link
Contributor

Choose a reason for hiding this comment

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

You could also do into_result().unwrap() on the return value

x if x == AppState::ServerRegistering as usize => AppState::ServerRegisteringError,
x if x == AppState::PeerConnecting as usize => AppState::PeerConnectionError,
x if x == AppState::PeerConnected as usize => AppState::PeerCallError,
x if x == AppState::PeerCallNegotiating as usize => AppState::PeerCallError,
Copy link
Contributor

Choose a reason for hiding this comment

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

See above, if you make the app struct a Rc<Mutex<_>> then you could store the state directly as an enum and match properly on it, exhaustively

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can we get away with Rc with the promises? I assumed that would behave the same way as spawning a new thread and a sync::Arc would be needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, Arc is needed, sorry :)

.unwrap();
self.update_state(AppState::PeerCallStarted);
}
if json_msg.get("ice").is_some() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we could use serde-json here for directly converting between a Rust struct and JSON?

@sdroege
Copy link
Contributor

sdroege commented May 31, 2018

Looks quite nice, thanks :) I have no opinions about the Docker stuff and how the webrtc library is used is for someone else to review.

From the Rust side, in addition to the comments above it might be nice to do proper error handling in various places instead of just unwrapping/expecting

@maxmcd
Copy link
Contributor Author

maxmcd commented May 31, 2018

Thanks so much for the detailed feedback @sdroege!

@maxmcd
Copy link
Contributor Author

maxmcd commented Jun 5, 2018

I'm done with edits for the moment. The mutex stuff is a little messy in places and it's still quite panicky. Had a difficult time consolidating the async nature of the code with proper error handling.

Also, cleaned up docker and added peer-id/server flag support to gain parity with the c version.

@sdroege
Copy link
Contributor

sdroege commented Jun 6, 2018

So this is working and basically equivalent with the C version now, and could get another full review?

RUN sed -i 's/sslctx.load_cert_chain(chain_pem, keyfile=key_pem)/pass/g' \
./simple-server.py
RUN sed -i 's/ssl=sslctx,//g' \
./simple-server.py
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe you want to add an option to simple-server.py to not use ssl instead? If you set ssl=None when calling serve(), it's the same as not passing an sslctx.

RUN sed -i 's/wss:\/\/webrtc.nirbheek.in:8443/ws:\/\/signalling:8443/g' \
/opt/webrtc-sendrecv.c
RUN sed -i 's/strict_ssl = TRUE/strict_ssl = FALSE/g' \
/opt/webrtc-sendrecv.c
Copy link
Contributor

Choose a reason for hiding this comment

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

Here, too. I think it would be generally useful for people (while running local servers) if we do that.

@maxmcd
Copy link
Contributor Author

maxmcd commented Jun 6, 2018

@sdroege yes absolutely

@sdroege
Copy link
Contributor

sdroege commented Jun 13, 2018

Added some minor fixes. Is set_property_from_str acceptable when constructing the pipeline? As an example: seems like I would need GstAudioTestSrcWave to be imported to properly replace: audiotestsrc.set_property_from_str("wave", "red-noise");

That's fine. The alternative would be something like this https://github.com/sdroege/gstreamer-rs/blob/f4d57a66522183d4927b47af422e8f321182111f/examples/src/bin/playbin.rs#L27-L35 (there's basically the same API for enums)

@sdroege
Copy link
Contributor

sdroege commented Jun 13, 2018

For how to integrate GstBus and proper error handling here, see #20 (comment). That's a general architectural problem right now with how the ws crate is used.

@sdroege
Copy link
Contributor

sdroege commented Jun 13, 2018

For how to integrate GstBus and proper error handling here, see #20 (comment). That's a general architectural problem right now with how the ws crate is used.

You could basically have another thread that does the ws stuff, and the main thread runs the GstBus main loop. And from the ws handler you could put messages on the bus also, or otherwise call things on the main thread via the glib main context API.

The other way around, calling things from elsewhere into the ws thread/event loop does not seem to be possible from a short look over the API. But maybe you also don't need that?

Otherwise there are some tokio based websocket libraries that might be possible to use, but that complicates everything quite a lot :)

@maxmcd
Copy link
Contributor Author

maxmcd commented Jun 14, 2018

Not sure how I'm missing this. How would I create a message for the bus and what MessageView type would you recommend using for posting websocket messages to the bus?

@sdroege
Copy link
Contributor

sdroege commented Jun 14, 2018

Not sure how I'm missing this. How would I create a message for the bus and what MessageView type would you recommend using for posting websocket messages to the bus?

You would use bus.post() for posting the message, and you could either create an application message, or directly an error. Application would probably be nicer.

See https://github.com/sdroege/gstreamer-rs/blob/db3fe694154c697afdaf3efb6ec65332546942e0/tutorials/src/bin/basic-tutorial-5.rs#L264-L267 for an example involving an element, you can simply do bus.post(msg) in your case instead of using element.post_message(msg). The structure from the application message can contain anything you want apart from its name.

You can catch the message on the bus like https://github.com/sdroege/gstreamer-rs/blob/db3fe694154c697afdaf3efb6ec65332546942e0/tutorials/src/bin/basic-tutorial-5.rs#L241-L244, for example.

@maxmcd
Copy link
Contributor Author

maxmcd commented Jun 15, 2018

Rewrote the script with rust-websocket and have better error handling with no panics and (I think) much less blocking. Much happier with where this is now, doesn't seem to be fighting against itself as much.

The error handling within the callback closures is a little hacky, and looking at some of the gstreamer-rs examples my bus/loop setup could be a little cleaner.

Ready for another round of feedback/review.

MessageView::Element(_) => {}
MessageView::Tag(_) => {}
MessageView::NewClock(_) => {}
MessageView::Warning(warning) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

You also want to handle Error messages, but all the others above are not really needed :) An Error message is always fatal

println!("Got error message! {}", msg);
return self.handle_error();
}
let json_msg: JsonMsg = serde_json::from_str(&msg).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of unwrapping, failing to deserialize the JSON should probably just be an error like other errors

@sdroege
Copy link
Contributor

sdroege commented Jun 16, 2018

Looks very good, apart from the minor things above I didn't see anything wrong while shortly looking over it. Will do another proper review in the next days but this looks like it's done. Much better with the other websocket library, and the need for 2 threads instead of 1 for that can probably be refactored later (e.g. by using the tokio-based async API on it). But that's for later

}
}

fn app_state_lt(&self, state: AppState, error_msg: &'static str) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this could mirror the Ord trait a bit more and be called app_state_cmp and return a std::cmp::Ordering. But details. Just lt seems a bit short :)

Copy link
Contributor

@sdroege sdroege left a comment

Choose a reason for hiding this comment

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

Looks good to me, thanks a lot for your work :)

@sdroege sdroege closed this in ad9b875 Jun 18, 2018
@sdroege
Copy link
Contributor

sdroege commented Jun 18, 2018

All merged now :)

@maxmcd
Copy link
Contributor Author

maxmcd commented Jun 18, 2018

Nice! Thanks so much for your patience and feedback @sdroege

@MathieuDuponchelle
Copy link
Contributor

@maxmcd , nice work, as far as I can tell this only works when the signalling server is launched with --disable-ssl , or am I missing something?

@maxmcd
Copy link
Contributor Author

maxmcd commented Oct 15, 2018

@MathieuDuponchelle correct, at least that's what I used when I worked on this, I'm sure you could get ssl working again. This is somewhat documented here: https://github.com/centricular/gstwebrtc-demos/blob/master/signalling/Dockerfile

The priority was getting a quick docker demo working so would check the dockerfiles and docker-compose file for quick reference on how to run.

@sdroege
Copy link
Contributor

sdroege commented Oct 15, 2018

This will need some refactoring to do all the ws communication on a single thread via tokio/futures instead of splitting the socket in its sender and receiver side

@sdroege
Copy link
Contributor

sdroege commented Oct 16, 2018

@maxmcd would you be interested in working on this? In any case, we should probably open a new issue for this :)

@maxmcd
Copy link
Contributor Author

maxmcd commented Oct 17, 2018

Yep, would be happy to.

@sdroege
Copy link
Contributor

sdroege commented Oct 17, 2018

Great, let us know how it goes and if you need help or want someone else to take over

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

Successfully merging this pull request may close these issues.

4 participants