2020-07-28 08:20:33 -05:00
|
|
|
/*
|
2021-12-21 13:34:37 -06:00
|
|
|
* Copyright (c) 2020, 2021, 2022 Valve Corporation
|
2020-07-28 08:20:33 -05:00
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
|
|
* are permitted provided that the following conditions are met:
|
|
|
|
*
|
|
|
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
|
|
* list of conditions and the following disclaimer.
|
|
|
|
*
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer in the documentation and/or
|
|
|
|
* other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
|
|
* may be used to endorse or promote products derived from this software without
|
|
|
|
* specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
|
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
|
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
2021-12-14 08:25:35 -06:00
|
|
|
use gst::glib;
|
2020-07-28 08:20:33 -05:00
|
|
|
use gst::prelude::*;
|
|
|
|
use gst::subclass::prelude::*;
|
|
|
|
use gst::EventView;
|
|
|
|
|
|
|
|
use once_cell::sync::Lazy;
|
|
|
|
|
|
|
|
/* Opus is a great fit for our usecase except for one problem: it only supports a few samplerates.
|
|
|
|
* Notably it doesn't support 44100 Hz, which is a very frequently used samplerate. This bin
|
|
|
|
* provides a capssetter element which will override the rate we get from Opus with the rate the
|
|
|
|
* application requested. Similarly, on the transcoder side, we just encode the audio as if it were
|
|
|
|
* at 48 kHz, even if it is actually at 44.1 kHz.
|
|
|
|
*
|
|
|
|
* The downside to this is a small decrease in audio quality. If Opus is most responsive between 20
|
|
|
|
* Hz and 20 kHz, then when 44.1 audio is converted to 48, we'll gain noise between 18-20 Hz
|
|
|
|
* (although WMA probably already filtered that out) and start to lose audio above 18,375 kHz. This
|
|
|
|
* is at the very edge of human hearing, so we're unlikely to lose any noticeable quality.
|
|
|
|
*
|
|
|
|
* Resampling is an option, but has some problems. It's significantly more complicated, and more
|
|
|
|
* CPU-intensive. Also, XAudio2 buffers can be started and ended at arbitrary points, so if we
|
|
|
|
* start moving audio data from one buffer into another due to resampling, it may result in audible
|
|
|
|
* artifacts. I think just encoding at the wrong rate is the best compromise. If the application
|
|
|
|
* actually cared about audio quality, they probably would not have used WMA in the first place.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|
|
|
gst::DebugCategory::new(
|
|
|
|
"protonaudioconverterbin",
|
|
|
|
gst::DebugColorFlags::empty(),
|
|
|
|
Some("Proton audio converter bin"))
|
|
|
|
});
|
|
|
|
|
2021-12-14 08:25:35 -06:00
|
|
|
pub struct AudioConvBin {
|
2020-07-28 08:20:33 -05:00
|
|
|
audioconv: gst::Element,
|
|
|
|
opusdec: gst::Element,
|
|
|
|
capssetter: gst::Element,
|
|
|
|
srcpad: gst::GhostPad,
|
|
|
|
sinkpad: gst::GhostPad,
|
|
|
|
}
|
|
|
|
|
2021-12-14 08:25:35 -06:00
|
|
|
#[glib::object_subclass]
|
2020-07-28 08:20:33 -05:00
|
|
|
impl ObjectSubclass for AudioConvBin {
|
|
|
|
const NAME: &'static str = "ProtonAudioConverterBin";
|
2021-12-14 08:25:35 -06:00
|
|
|
type Type = super::AudioConvBin;
|
2020-07-28 08:20:33 -05:00
|
|
|
type ParentType = gst::Bin;
|
|
|
|
|
2021-12-14 08:25:35 -06:00
|
|
|
fn with_class(klass: &Self::Class) -> Self {
|
2020-07-28 08:20:33 -05:00
|
|
|
|
2021-12-14 08:25:35 -06:00
|
|
|
let templ = klass.pad_template("src").unwrap();
|
2020-07-28 08:20:33 -05:00
|
|
|
let srcpad = gst::GhostPad::builder_with_template(&templ, Some("src")).build();
|
|
|
|
|
2021-12-14 08:25:35 -06:00
|
|
|
let templ = klass.pad_template("sink").unwrap();
|
2020-07-28 08:20:33 -05:00
|
|
|
let sinkpad = gst::GhostPad::builder_with_template(&templ, Some("sink"))
|
|
|
|
.event_function(|pad, parent, event| {
|
|
|
|
AudioConvBin::catch_panic_pad_function(
|
|
|
|
parent,
|
|
|
|
|| false,
|
2022-11-04 14:30:33 +02:00
|
|
|
|audioconvbin| audioconvbin.sink_event(pad, event)
|
2020-07-28 08:20:33 -05:00
|
|
|
)
|
|
|
|
}).build();
|
|
|
|
|
2022-11-04 14:30:33 +02:00
|
|
|
let audioconv = gst::ElementFactory::make("protonaudioconverter").build().unwrap();
|
|
|
|
let opusdec = gst::ElementFactory::make("opusdec").build().unwrap();
|
|
|
|
let capssetter = gst::ElementFactory::make("capssetter").build().unwrap();
|
2020-07-28 08:20:33 -05:00
|
|
|
|
|
|
|
AudioConvBin {
|
|
|
|
audioconv,
|
|
|
|
opusdec,
|
|
|
|
capssetter,
|
|
|
|
srcpad,
|
|
|
|
sinkpad,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ObjectImpl for AudioConvBin {
|
2022-11-04 14:30:33 +02:00
|
|
|
fn constructed(&self) {
|
|
|
|
self.parent_constructed();
|
|
|
|
|
|
|
|
let obj = self.obj();
|
2020-07-28 08:20:33 -05:00
|
|
|
|
2021-12-14 08:25:35 -06:00
|
|
|
obj.add(&self.audioconv).unwrap();
|
|
|
|
obj.add(&self.opusdec).unwrap();
|
|
|
|
obj.add(&self.capssetter).unwrap();
|
2020-07-28 08:20:33 -05:00
|
|
|
|
|
|
|
self.audioconv.link(&self.opusdec).unwrap();
|
|
|
|
self.opusdec.link(&self.capssetter).unwrap();
|
|
|
|
|
|
|
|
self.sinkpad
|
2021-12-14 08:25:35 -06:00
|
|
|
.set_target(Some(&self.audioconv.static_pad("sink").unwrap()))
|
2020-07-28 08:20:33 -05:00
|
|
|
.unwrap();
|
|
|
|
self.srcpad
|
2021-12-14 08:25:35 -06:00
|
|
|
.set_target(Some(&self.capssetter.static_pad("src").unwrap()))
|
2020-07-28 08:20:33 -05:00
|
|
|
.unwrap();
|
|
|
|
|
2021-12-14 08:25:35 -06:00
|
|
|
obj.add_pad(&self.sinkpad).unwrap();
|
|
|
|
obj.add_pad(&self.srcpad).unwrap();
|
2020-07-28 08:20:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-04 14:30:33 +02:00
|
|
|
impl GstObjectImpl for AudioConvBin { }
|
|
|
|
|
2020-07-28 08:20:33 -05:00
|
|
|
impl BinImpl for AudioConvBin { }
|
|
|
|
|
2021-12-14 08:25:35 -06:00
|
|
|
impl ElementImpl for AudioConvBin {
|
|
|
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
|
|
|
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
|
|
|
gst::subclass::ElementMetadata::new(
|
|
|
|
"Proton audio converter with rate fixup",
|
2022-01-17 15:23:42 +01:00
|
|
|
"Codec/Decoder/Audio",
|
2021-12-14 08:25:35 -06:00
|
|
|
"Converts audio for Proton, fixing up samplerates",
|
|
|
|
"Andrew Eikum <aeikum@codeweavers.com>")
|
|
|
|
});
|
|
|
|
|
|
|
|
Some(&*ELEMENT_METADATA)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
|
|
|
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
|
|
|
let mut caps = gst::Caps::new_empty();
|
|
|
|
{
|
|
|
|
let caps = caps.get_mut().unwrap();
|
|
|
|
caps.append(gst::Caps::builder("audio/x-wma").build());
|
|
|
|
}
|
|
|
|
let sink_pad_template = gst::PadTemplate::new(
|
|
|
|
"sink",
|
|
|
|
gst::PadDirection::Sink,
|
|
|
|
gst::PadPresence::Always,
|
|
|
|
&caps).unwrap();
|
|
|
|
|
2022-01-17 15:23:42 +01:00
|
|
|
let caps = gst::Caps::builder("audio/x-raw")
|
|
|
|
.field("format", "S16LE") /* opusdec always output S16LE */
|
|
|
|
.build();
|
2021-12-14 08:25:35 -06:00
|
|
|
let src_pad_template = gst::PadTemplate::new(
|
|
|
|
"src",
|
|
|
|
gst::PadDirection::Src,
|
|
|
|
gst::PadPresence::Always,
|
|
|
|
&caps).unwrap();
|
|
|
|
|
|
|
|
vec![src_pad_template, sink_pad_template]
|
|
|
|
});
|
|
|
|
|
|
|
|
PAD_TEMPLATES.as_ref()
|
|
|
|
}
|
|
|
|
}
|
2020-07-28 08:20:33 -05:00
|
|
|
|
|
|
|
impl AudioConvBin {
|
|
|
|
fn sink_event(
|
|
|
|
&self,
|
|
|
|
pad: &gst::GhostPad,
|
|
|
|
event: gst::Event
|
|
|
|
) -> bool {
|
|
|
|
match event.view() {
|
|
|
|
EventView::Caps(event_caps) => {
|
|
|
|
/* set up capssetter with this rate */
|
2021-12-14 08:25:35 -06:00
|
|
|
if let Some(s) = event_caps.caps().structure(0) {
|
2020-07-28 08:20:33 -05:00
|
|
|
|
2021-12-14 08:25:35 -06:00
|
|
|
if let Ok(override_rate) = s.get::<i32>("rate") {
|
2020-07-28 08:20:33 -05:00
|
|
|
|
|
|
|
let mut rate_caps = gst::Caps::new_empty();
|
|
|
|
{
|
|
|
|
let rate_caps = rate_caps.get_mut().unwrap();
|
|
|
|
let s = gst::Structure::builder("audio/x-raw")
|
|
|
|
.field("rate", &override_rate)
|
|
|
|
.build();
|
|
|
|
rate_caps.append_structure(s);
|
|
|
|
}
|
2022-11-04 14:30:33 +02:00
|
|
|
self.capssetter.set_property("caps", &rate_caps);
|
2020-07-28 08:20:33 -05:00
|
|
|
}else{
|
2022-11-04 14:30:33 +02:00
|
|
|
gst::warning!(CAT, "event has no rate");
|
2020-07-28 08:20:33 -05:00
|
|
|
}
|
|
|
|
} else {
|
2022-11-04 14:30:33 +02:00
|
|
|
gst::warning!(CAT, "event has no structure");
|
2020-07-28 08:20:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/* forward on to the real pad */
|
2021-12-14 08:25:35 -06:00
|
|
|
self.audioconv.static_pad("sink").unwrap()
|
2020-07-28 08:20:33 -05:00
|
|
|
.send_event(event)
|
|
|
|
},
|
2022-11-04 14:30:33 +02:00
|
|
|
_ => gst::Pad::event_default(pad, Some(&*self.obj()), event)
|
2020-07-28 08:20:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|