Add transcoder. Update vk-video dependency.#7
Conversation
Committer: varsill <lukasz.kita0@gmail.com>
…his behaviour. Make sure no output pads are added when playback is already playing. Add reference outputs. Bump version to v0.2.0
| def_options output_specs: [ | ||
| spec: [Membrane.VKVideo.Transcoder.OutputSpec.t()], | ||
| description: """ | ||
| List of output specifications. Each entry defines the target resolution, | ||
| framerate, encoder tune, rate control, and scaling algorithm for one output | ||
| stream. Output pads are referenced as `Pad.ref(:output, index)` where | ||
| `index` is the zero-based position in this list. | ||
| """ | ||
| ] |
| Fields: | ||
| * `width` - output frame width in pixels | ||
| * `height` - output frame height in pixels | ||
| * `frame_rate` - output frame rate as `{numerator, denominator}` |
There was a problem hiding this comment.
Shouldn't it be called approx_framerate and optional, like in the encoder?
There was a problem hiding this comment.
yeah, especially that vk-video won't change the framerate. you need to pass the input framerate here. Perhaps not the best API on our part..
There was a problem hiding this comment.
I fixed it, now there is a single approx_framerate element option which is propagated to all transcoder output configs.
| Unexpected :output pad was linked: \ | ||
| #{inspect(pad_ref)} |
There was a problem hiding this comment.
| Unexpected :output pad was linked: \ | |
| #{inspect(pad_ref)} | |
| Pad #{inspect(pad_ref)} was linked when playback already changed to `:playing`. Make sure to link all transcoder pads in the spec where the transcoder is spawned. |
| let transcoder = TranscoderResource { transcoder_mutex }; | ||
|
|
||
| let resource = ResourceArc::new(Resource::Transcoder(transcoder)); | ||
| Ok((ok(), resource)) |
There was a problem hiding this comment.
If we always raise on errors, what's the point of returning an ok tuple? Applies to all functions
There was a problem hiding this comment.
we don't alwasy raise, but I think we should
| Fields: | ||
| * `width` - output frame width in pixels | ||
| * `height` - output frame height in pixels | ||
| * `frame_rate` - output frame rate as `{numerator, denominator}` |
There was a problem hiding this comment.
yeah, especially that vk-video won't change the framerate. you need to pass the input framerate here. Perhaps not the best API on our part..
| pub enum Resource { | ||
| Encoder(EncoderResource), | ||
| Decoder(DecoderResource), | ||
| Transcoder(TranscoderResource), | ||
| Device(DeviceResource), | ||
| } |
There was a problem hiding this comment.
I don't understand why this instead of implementing Resource for each thing. A limitation of rustler? can you only have a single resource per crate?
There was a problem hiding this comment.
AFAIK indeed I can define a single resource per crate 🤔
| .atom_to_string()? | ||
| .as_str() | ||
| { | ||
| "nearest_neighbour" => ScalingAlgorithm::NearestNeighbor, |
| let non_zero_width = std::num::NonZero::new(spec.width).ok_or(Error::BadArg)?; | ||
| let non_zero_height = std::num::NonZero::new(spec.height).ok_or(Error::BadArg)?; |
There was a problem hiding this comment.
maybe hard for someone to figure out which argument is bad based on the BadArg alone
There was a problem hiding this comment.
In line 52 etc you raise strings instead of returning simple errors, this should be unified I think
There was a problem hiding this comment.
I added RaiseTerms with string reason instead
| let transcoder = TranscoderResource { transcoder_mutex }; | ||
|
|
||
| let resource = ResourceArc::new(Resource::Transcoder(transcoder)); | ||
| Ok((ok(), resource)) |
There was a problem hiding this comment.
we don't alwasy raise, but I think we should
| .transcoder_mutex | ||
| .lock() | ||
| .map_err(|err| Error::RaiseTerm(Box::new(err.to_string())))?; | ||
| let transcoder = guard.as_mut().ok_or(Error::BadArg)?; |
There was a problem hiding this comment.
Either rename guard to transcoder and remove this line, or add explicit typing in this line to unconfuse the language server. otherwise this line doesnt make sense, you can call guard.transcode() transparently
There was a problem hiding this comment.
As discussed - we cannot get rid of this guard, I added an explicit typing
| let mut guard = transcoder | ||
| .transcoder_mutex | ||
| .lock() | ||
| .map_err(|err| Error::RaiseTerm(Box::new(err.to_string())))?; |
There was a problem hiding this comment.
not sure if this map_err makes sense here (it might, I'm just not certain). If this is an Err, it means that some thread panicked while having the mutex locked. maybe this is the correct way of handling this, if it happens in rust code the normal thing is to panic this thread too (to propagate panics to everyone who wants to access the mutex), since something hard to detect has gone very wrong
There was a problem hiding this comment.
Do I get it right that only if I panic when I encounter Err, other threads trying to get the lock will get Err too? (even though my thread didn't obtain the lock at all? 🤔)
There was a problem hiding this comment.
no, sorry explained it wrong. if any thread gets an Err, all others will too
| rustler = "0.37.0" | ||
| vk-video = { git = "https://github.com/software-mansion/smelter.git", features=["expose_parsers"] } | ||
| wgpu = "25.0.2" | ||
| termcolor = "1.4.1" |
There was a problem hiding this comment.
why termcolor? if you needed to add this it probably means that we need to fix something on our side, you shouldn't have to do that
There was a problem hiding this comment.
removed, it was a leftover ;)
There was a problem hiding this comment.
since you have these tests, does that mean you have a CI runner with a GPU? can I have it too???
jerzywilczek
left a comment
There was a problem hiding this comment.
Also, you don't run any rust checks on CI. I'd run at least cargo fmt --check and cargo clippy -- -D warnings
| output_spec: [ | ||
| spec: Membrane.VKVideo.Transcoder.OutputSpec.t(), | ||
| description: """ | ||
| Output specification for this pad. Defines the target resolution, | ||
| encoder tune, rate control, and scaling algorithm for the output stream. | ||
| """ |
There was a problem hiding this comment.
I'd rather put the fields of the OutputSpec as separate pad options
There was a problem hiding this comment.
We need to keep OutputSpec as a struct for the sake of convenience in passing it via rustler, but I will make it a private struct and create it based on pad options
| cond do | ||
| state.override_framerate? and is_nil(state.transcoder) -> | ||
| spawn_transcoder(state) | ||
|
|
||
| not state.override_framerate? -> | ||
| new_framerate = stream_format.framerate || @default_framerate | ||
|
|
||
| if is_nil(state.transcoder) or new_framerate != state.framerate do | ||
| %{state | framerate: new_framerate} |> spawn_transcoder() | ||
| else | ||
| {[], state} | ||
| end | ||
|
|
||
| true -> | ||
| {[], state} | ||
| end |
There was a problem hiding this comment.
This is a bit convoluted, I'd first calculate the new framerate and then spawn the transcoder if it's not there or the framerate changed
| {buffer_actions ++ eos_actions, %{state | transcoder: nil}} | ||
| end | ||
|
|
||
| defp build_buffer_actions(outputs, state) do |
There was a problem hiding this comment.
outputs isn't a fortunate name, as it can mean 'output pads'
| Enum.with_index(frame_per_pads) | ||
| |> Enum.map(fn {frame, i} -> | ||
| {pad_ref, _spec} = Enum.at(state.output_specs, i) |
There was a problem hiding this comment.
| Enum.with_index(frame_per_pads) | |
| |> Enum.map(fn {frame, i} -> | |
| {pad_ref, _spec} = Enum.at(state.output_specs, i) | |
| Enum.zip(frame_per_pads, state.output_specs) | |
| |> Enum.map(fn {frame, {pad_ref, _spec}} -> |
| output_specs: %{}, | ||
| device: nil, | ||
| override_framerate?: opts.approx_framerate != nil, | ||
| framerate: opts.approx_framerate |
There was a problem hiding this comment.
maybe let's call it approx_framerate everywhere
… transcoder. Add circle ci linter config for rust
| def_input_pad :input, | ||
| alias Membrane.VKVideo.{DeviceServer, Native, Transcoder.OutputSpec} | ||
|
|
||
| def_options( |
| def handle_init(_ctx, opts) do | ||
| state = %{ | ||
| transcoder: nil, | ||
| output_specs: %{}, |
| new_framerate = state.approx_framerate || stream_format.framerate || @default_framerate | ||
|
|
||
| needs_spawn? = | ||
| is_nil(state.transcoder) or | ||
| (not state.override_framerate? and new_framerate != state.approx_framerate) | ||
|
|
||
| if needs_spawn? do | ||
| %{state | approx_framerate: new_framerate} |> spawn_transcoder() | ||
| else | ||
| {[], state} | ||
| end |
| def handle_stream_format(:input, stream_format, _ctx, state) do | ||
| new_framerate = state.approx_framerate || stream_format.framerate || @default_framerate | ||
| new_framerate = | ||
| if not is_nil(state.approx_framerate_option) do | ||
| state.approx_framerate_option | ||
| else | ||
| stream_format.framerate || @default_framerate | ||
| end | ||
|
|
||
| needs_spawn? = | ||
| is_nil(state.transcoder) or | ||
| (not state.override_framerate? and new_framerate != state.approx_framerate) | ||
| old_framerate = state.approx_framerate | ||
|
|
||
| if needs_spawn? do | ||
| %{state | approx_framerate: new_framerate} |> spawn_transcoder() | ||
| state = %{state | approx_framerate: new_framerate} | ||
|
|
||
| if is_nil(state.transcoder) or new_framerate != old_framerate do | ||
| spawn_transcoder(state) | ||
| else | ||
| {[], state} | ||
| end |
This PR:
Membrane.VKVideo.Transcoderalong withMembrane.VKVideo.Transcoder.OutputConfigspecifying desired output of the transcodertranscoder.rswith transcoder-specific functionsnew_transcoder(),transcoderandflush_transcoder()NIFsEncodedFramestruct fromencoder.rsto commonlib.rswgpuandnagaTAKE_TEST_REFERENCES_SNAPSHOT=1env