Welcome to mirror list, hosted at ThFree Co, Russian Federation.

hrtfrender.rs « examples « audiofx « audio - gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: cc156e5004fdeacfcf724dd98cfda4146609f81b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Copyright (C) 2021 Tomasz Andrzejak <andreiltd@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0

use gst::prelude::*;

use anyhow::{bail, Error};

use std::sync::{Arc, Condvar, Mutex};
use std::{env, thread, time};

// Rotation in radians to apply to object position every 100 ms
const ROTATION: f32 = 2.0 / 180.0 * std::f32::consts::PI;

fn run() -> Result<(), Error> {
    gst::init()?;
    gstrsaudiofx::plugin_register_static()?;

    let args: Vec<String> = env::args().collect();

    // Ircam binaries that the hrtf plugin is using can be downloaded from:
    // https://github.com/mrDIMAS/hrir_sphere_builder/tree/master/hrtf_base/IRCAM
    //
    // Ideally provide a mono input as this example is moving a single object. Otherwise
    // the input will be downmixed to mono with the audioconvert element.
    //
    // e.g.: hrtfrender 'file:///path/to/my/awesome/mono/wav/awesome.wav' IRC_1002_C.bin
    if args.len() != 3 {
        bail!("Usage: {} URI HRIR", args[0].clone());
    }

    let uri = &args[1];
    let hrir = &args[2];

    let pipeline = gst::parse::launch(&format!(
        "uridecodebin uri={uri} ! audioconvert ! audio/x-raw,channels=1 !
            hrtfrender hrir-file={hrir} name=hrtf ! audioresample ! autoaudiosink"
    ))?
    .downcast::<gst::Pipeline>()
    .expect("type error");

    let hrtf = pipeline.by_name("hrtf").expect("hrtf element not found");

    // At the beginning put an object in front of listener
    let objs = [gst::Structure::builder("application/spatial-object")
        .field("x", 0f32)
        .field("y", 0f32)
        .field("z", 1f32)
        .field("distance-gain", 1f32)
        .build()];

    hrtf.set_property("spatial-objects", gst::Array::new(objs));

    let state_cond = Arc::new((Mutex::new(gst::State::Null), Condvar::new()));
    let state_cond_clone = Arc::clone(&state_cond);

    thread::spawn(move || {
        // Wait for the pipeline to start up
        {
            let (lock, cvar) = &*state_cond_clone;
            let mut state = lock.lock().unwrap();

            while *state != gst::State::Playing {
                state = cvar.wait(state).unwrap();
            }
        }

        loop {
            // get current object position and rotate it clockwise
            let s = hrtf.property::<gst::Array>("spatial-objects")[0]
                .get::<gst::Structure>()
                .expect("type error");

            // positive values are on the right side of a listener
            let x = s.get::<f32>("x").expect("type error");
            // elevation, positive value is up
            let y = s.get::<f32>("y").expect("type error");
            // positive values are in front of a listener
            let z = s.get::<f32>("z").expect("type error");
            // gain
            let gain = s.get::<f32>("distance-gain").expect("type error");

            // rotate clockwise: https://en.wikipedia.org/wiki/Rotation_matrix
            let new_x = x * f32::cos(ROTATION) + z * f32::sin(ROTATION);
            let new_z = -x * f32::sin(ROTATION) + z * f32::cos(ROTATION);

            let objs = [gst::Structure::builder("application/spatial-object")
                .field("x", new_x)
                .field("y", y)
                .field("z", new_z)
                .field("distance-gain", gain)
                .build()];

            hrtf.set_property("spatial-objects", gst::Array::new(objs));

            thread::sleep(time::Duration::from_millis(100));
        }
    });

    pipeline.set_state(gst::State::Playing)?;

    let bus = pipeline.bus().unwrap();
    for msg in bus.iter_timed(gst::ClockTime::NONE) {
        use gst::MessageView;

        match msg.view() {
            MessageView::StateChanged(state_changed) => {
                if state_changed.src().map(|s| s == &pipeline).unwrap_or(false)
                    && state_changed.current() == gst::State::Playing
                {
                    let (lock, cvar) = &*state_cond;
                    let mut state = lock.lock().unwrap();

                    *state = gst::State::Playing;
                    cvar.notify_one();
                }
            }
            MessageView::Eos(..) => break,
            MessageView::Error(err) => {
                println!(
                    "Error from {:?}: {} ({:?})",
                    msg.src().map(|s| s.path_string()),
                    err.error(),
                    err.debug()
                );
                break;
            }
            _ => (),
        }
    }

    pipeline.set_state(gst::State::Null)?;

    Ok(())
}

fn main() {
    match run() {
        Ok(r) => r,
        Err(e) => eprintln!("Error! {e}"),
    }
}