mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-10-30 17:30:48 +08:00
fix: support ALSA timestamps on 32-bit musl time64
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -61,8 +61,6 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
[[package]]
|
||||
name = "alsa"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags 2.13.2",
|
||||
|
||||
@@ -210,6 +210,8 @@ windows-sys = { version = "0.61", features = [
|
||||
protobuf-codegen = "3.7"
|
||||
|
||||
[patch.crates-io]
|
||||
# ALSA 0.11.0 uses timespec literals that fail on 32-bit musl time64.
|
||||
alsa = { path = "vendor/alsa" }
|
||||
dtls = { path = "libs/dtls" }
|
||||
|
||||
[profile.release]
|
||||
|
||||
6
vendor/alsa/.cargo_vcs_info.json
vendored
Normal file
6
vendor/alsa/.cargo_vcs_info.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "f4e5d2be8adb8f12c29d4ea1e5a48b670c50712f"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
74
vendor/alsa/Cargo.toml
vendored
Normal file
74
vendor/alsa/Cargo.toml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "alsa"
|
||||
version = "0.11.0"
|
||||
authors = ["David Henningsson <coding@diwic.se>"]
|
||||
build = false
|
||||
include = [
|
||||
"README.md",
|
||||
"LICENSE-*",
|
||||
"Cargo.toml",
|
||||
"src/",
|
||||
]
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Thin but safe wrappers for ALSA (Linux sound API)"
|
||||
documentation = "http://docs.rs/alsa"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"ALSA",
|
||||
"audio",
|
||||
"sound",
|
||||
]
|
||||
categories = [
|
||||
"multimedia::audio",
|
||||
"api-bindings",
|
||||
]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/diwic/alsa-rs"
|
||||
|
||||
[badges.is-it-maintained-issue-resolution]
|
||||
repository = "diwic/alsa-rs"
|
||||
|
||||
[badges.is-it-maintained-open-issues]
|
||||
repository = "diwic/alsa-rs"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
|
||||
[lib]
|
||||
name = "alsa"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies.alsa-sys]
|
||||
version = "0.4.0"
|
||||
|
||||
[dependencies.bitflags]
|
||||
version = "2.4.0"
|
||||
|
||||
[dependencies.cfg-if]
|
||||
version = "1.0"
|
||||
|
||||
[dependencies.libc]
|
||||
version = "0.2.178"
|
||||
|
||||
[dev-dependencies.anyhow]
|
||||
version = "1.0"
|
||||
|
||||
[lints.rust]
|
||||
missing-debug-implementations = "warn"
|
||||
34
vendor/alsa/Cargo.toml.orig
generated
vendored
Normal file
34
vendor/alsa/Cargo.toml.orig
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "alsa"
|
||||
version = "0.11.0"
|
||||
authors = ["David Henningsson <coding@diwic.se>"]
|
||||
|
||||
description = "Thin but safe wrappers for ALSA (Linux sound API)"
|
||||
repository = "https://github.com/diwic/alsa-rs"
|
||||
documentation = "http://docs.rs/alsa"
|
||||
keywords = ["ALSA", "audio", "sound"]
|
||||
license = "Apache-2.0/MIT"
|
||||
categories = ["multimedia::audio", "api-bindings"]
|
||||
readme = "README.md"
|
||||
edition = "2021"
|
||||
include = ["README.md", "LICENSE-*", "Cargo.toml", "src/"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.178"
|
||||
alsa-sys = "0.4.0"
|
||||
bitflags = "2.4.0"
|
||||
cfg-if = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0"
|
||||
|
||||
[badges]
|
||||
is-it-maintained-issue-resolution = { repository = "diwic/alsa-rs" }
|
||||
is-it-maintained-open-issues = { repository = "diwic/alsa-rs" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
|
||||
[lints.rust]
|
||||
missing-debug-implementations = "warn"
|
||||
177
vendor/alsa/LICENSE-APACHE
vendored
Normal file
177
vendor/alsa/LICENSE-APACHE
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
21
vendor/alsa/LICENSE-MIT
vendored
Normal file
21
vendor/alsa/LICENSE-MIT
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015-2021 David Henningsson, and other contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
11
vendor/alsa/ONE-KVM-PATCHES.md
vendored
Normal file
11
vendor/alsa/ONE-KVM-PATCHES.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# ALSA compatibility patch
|
||||
|
||||
This directory contains `alsa` 0.11.0 from crates.io, licensed under MIT or Apache-2.0. The upstream crate archive SHA-256 is `812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d`.
|
||||
|
||||
Upstream source: https://github.com/diwic/alsa-rs
|
||||
|
||||
In `src/pcm.rs`, `Status::get_htstamp`, `get_trigger_htstamp`, and `get_audio_htstamp` initialize `libc::timespec` with `timespec::default()` instead of a struct literal. Rust libc exposes private padding fields when targeting 32-bit musl with time64 enabled, so the original literals fail to compile. Default initialization also initializes those fields while retaining the existing timestamp behavior on other Linux targets.
|
||||
|
||||
The root Cargo patch pins this copy for both direct and transitive ALSA dependencies. Remove the patch when an upstream release includes this fix, and refresh `Cargo.lock` together with the dependency update.
|
||||
|
||||
Trailing whitespace in upstream `README.md`, `src/card.rs`, and `src/poll.rs` is normalized for repository whitespace checks.
|
||||
61
vendor/alsa/README.md
vendored
Normal file
61
vendor/alsa/README.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
ALSA bindings for Rust
|
||||
=======================
|
||||
|
||||
Thin but safe wrappers for [ALSA](https://alsa-project.org), the most
|
||||
common API for accessing audio devices on Linux.
|
||||
|
||||
[](https://crates.io/crates/alsa)
|
||||
[](https://docs.rs/alsa)
|
||||
[](https://crates.io/crates/alsa)
|
||||
|
||||
The ALSA API is rather big, so everything is not covered yet, but expect the following to work:
|
||||
|
||||
* Audio Playback (example in `pcm` module docs)
|
||||
|
||||
* Audio Recording
|
||||
|
||||
* Mixer controls
|
||||
|
||||
* HCtl API (jack detection example in `hctl` module docs)
|
||||
|
||||
* Raw midi
|
||||
|
||||
* Midi sequencer (most of it)
|
||||
|
||||
* Ctl API
|
||||
|
||||
* Device name hints (example in `device_name` module docs)
|
||||
|
||||
* Enumerations of all of the above
|
||||
|
||||
* Poll and/or wait for all of the above
|
||||
|
||||
The following is not yet implemented (mostly because nobody asked for them) :
|
||||
|
||||
* Separate timer API (snd_timer_*)
|
||||
|
||||
* Config API (snd_config_*)
|
||||
|
||||
* Plug-in API
|
||||
|
||||
Quickstart guide / API design:
|
||||
|
||||
* Most functions map 1-to-1 to alsa-lib functions, e g, `ctl::CardInfo::get_id()` is a wrapper around
|
||||
`snd_ctl_card_info_get_id` and the [alsa-lib documentation](https://www.alsa-project.org/alsa-doc/alsa-lib/)
|
||||
can be consulted for additional information.
|
||||
|
||||
* Structs are RAII and closed/freed on drop, e g, when a `PCM` struct is dropped, `snd_pcm_close` is called.
|
||||
|
||||
* To read and write buffers, call the `io_*` methods. It will return a separate struct from which you can
|
||||
read or write, and which can also be used for mmap (if supported by the driver).
|
||||
|
||||
* Error handling - most alsa-lib functions can return errors, so the return value from these is a `Result`.
|
||||
|
||||
* Enumeration of cards, devices etc is done through structs implementing `Iterator`.
|
||||
|
||||
* Many structs implement `poll::Descriptors`, to combine with your favorite async framework.
|
||||
Or just use `wait` if you don't need non-blocking functionality.
|
||||
|
||||
Notes:
|
||||
|
||||
* To run the tests successfully, there must be a "default" sound card configured. This is usually not a problem when running on normal hardware, but some CI systems, docker images etc, might not have that configured by default.
|
||||
57
vendor/alsa/src/card.rs
vendored
Normal file
57
vendor/alsa/src/card.rs
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
//! Sound card enumeration
|
||||
use libc::{c_int, c_char};
|
||||
use super::error::*;
|
||||
use crate::alsa;
|
||||
use core::ffi::CStr;
|
||||
use ::alloc::string::String;
|
||||
|
||||
/// An ALSA sound card, uniquely identified by its index.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Card(c_int);
|
||||
|
||||
/// Iterate over existing sound cards.
|
||||
#[derive(Debug)]
|
||||
pub struct Iter(c_int);
|
||||
|
||||
impl Iter {
|
||||
pub fn new() -> Iter { Iter(-1) }
|
||||
}
|
||||
|
||||
impl Iterator for Iter {
|
||||
type Item = Result<Card>;
|
||||
|
||||
fn next(&mut self) -> Option<Result<Card>> {
|
||||
match acheck!(snd_card_next(&mut self.0)) {
|
||||
Ok(_) if self.0 == -1 => None,
|
||||
Ok(_) => Some(Ok(Card(self.0))),
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Card {
|
||||
pub fn new(index: c_int) -> Card { Card(index) }
|
||||
pub fn from_str(s: &CStr) -> Result<Card> {
|
||||
acheck!(snd_card_get_index(s.as_ptr())).map(Card)
|
||||
}
|
||||
pub fn get_name(&self) -> Result<String> {
|
||||
let mut c: *mut c_char = ::core::ptr::null_mut();
|
||||
acheck!(snd_card_get_name(self.0, &mut c))
|
||||
.and_then(|_| from_alloc("snd_card_get_name", c))
|
||||
}
|
||||
pub fn get_longname(&self) -> Result<String> {
|
||||
let mut c: *mut c_char = ::core::ptr::null_mut();
|
||||
acheck!(snd_card_get_longname(self.0, &mut c))
|
||||
.and_then(|_| from_alloc("snd_card_get_longname", c))
|
||||
}
|
||||
|
||||
pub fn get_index(&self) -> c_int { self.0 }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_cards() {
|
||||
extern crate std;
|
||||
for a in Iter::new().map(|a| a.unwrap()) {
|
||||
std::println!("Card #{}: {} ({})", a.get_index(), a.get_name().unwrap(), a.get_longname().unwrap())
|
||||
}
|
||||
}
|
||||
168
vendor/alsa/src/chmap.rs
vendored
Normal file
168
vendor/alsa/src/chmap.rs
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
use crate::alsa;
|
||||
use core::{fmt, mem, slice};
|
||||
use super::error::*;
|
||||
use ::alloc::vec::Vec;
|
||||
use ::alloc::vec;
|
||||
|
||||
alsa_enum!(
|
||||
/// [SND_CHMAP_TYPE_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) constants
|
||||
ChmapType, ALL_CHMAP_TYPES[4],
|
||||
|
||||
None = SND_CHMAP_TYPE_NONE,
|
||||
Fixed = SND_CHMAP_TYPE_FIXED,
|
||||
Var = SND_CHMAP_TYPE_VAR,
|
||||
Paired = SND_CHMAP_TYPE_PAIRED,
|
||||
);
|
||||
|
||||
alsa_enum!(
|
||||
/// [SND_CHMAP_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) constants
|
||||
ChmapPosition, ALL_CHMAP_POSITIONS[37],
|
||||
|
||||
Unknown = SND_CHMAP_UNKNOWN,
|
||||
NA = SND_CHMAP_NA,
|
||||
Mono = SND_CHMAP_MONO,
|
||||
FL = SND_CHMAP_FL,
|
||||
FR = SND_CHMAP_FR,
|
||||
RL = SND_CHMAP_RL,
|
||||
RR = SND_CHMAP_RR,
|
||||
FC = SND_CHMAP_FC,
|
||||
LFE = SND_CHMAP_LFE,
|
||||
SL = SND_CHMAP_SL,
|
||||
SR = SND_CHMAP_SR,
|
||||
RC = SND_CHMAP_RC,
|
||||
FLC = SND_CHMAP_FLC,
|
||||
FRC = SND_CHMAP_FRC,
|
||||
RLC = SND_CHMAP_RLC,
|
||||
RRC = SND_CHMAP_RRC,
|
||||
FLW = SND_CHMAP_FLW,
|
||||
FRW = SND_CHMAP_FRW,
|
||||
FLH = SND_CHMAP_FLH,
|
||||
FCH = SND_CHMAP_FCH,
|
||||
FRH = SND_CHMAP_FRH,
|
||||
TC = SND_CHMAP_TC,
|
||||
TFL = SND_CHMAP_TFL,
|
||||
TFR = SND_CHMAP_TFR,
|
||||
TFC = SND_CHMAP_TFC,
|
||||
TRL = SND_CHMAP_TRL,
|
||||
TRR = SND_CHMAP_TRR,
|
||||
TRC = SND_CHMAP_TRC,
|
||||
TFLC = SND_CHMAP_TFLC,
|
||||
TFRC = SND_CHMAP_TFRC,
|
||||
TSL = SND_CHMAP_TSL,
|
||||
TSR = SND_CHMAP_TSR,
|
||||
LLFE = SND_CHMAP_LLFE,
|
||||
RLFE = SND_CHMAP_RLFE,
|
||||
BC = SND_CHMAP_BC,
|
||||
BLC = SND_CHMAP_BLC,
|
||||
BRC = SND_CHMAP_BRC,
|
||||
);
|
||||
|
||||
impl fmt::Display for ChmapPosition {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = unsafe { alsa::snd_pcm_chmap_long_name(*self as libc::c_uint) };
|
||||
let s = from_const("snd_pcm_chmap_long_name", s)?;
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// [snd_pcm_chmap_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct Chmap(*mut alsa::snd_pcm_chmap_t, bool);
|
||||
|
||||
impl Drop for Chmap {
|
||||
fn drop(&mut self) { if self.1 { unsafe { libc::free(self.0 as *mut libc::c_void) }}}
|
||||
}
|
||||
|
||||
impl Chmap {
|
||||
fn set_channels(&mut self, c: libc::c_uint) { unsafe { (*self.0) .channels = c }}
|
||||
fn as_slice_mut(&mut self) -> &mut [libc::c_uint] {
|
||||
unsafe { slice::from_raw_parts_mut((*self.0).pos.as_mut_ptr(), (*self.0).channels as usize) }
|
||||
}
|
||||
fn as_slice(&self) -> &[libc::c_uint] {
|
||||
unsafe { slice::from_raw_parts((*self.0).pos.as_ptr(), (*self.0).channels as usize) }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Chmap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut buf: Vec<libc::c_char> = vec![0; 512];
|
||||
acheck!(snd_pcm_chmap_print(self.0, buf.len() as libc::size_t, buf.as_mut_ptr()))?;
|
||||
let s = from_const("snd_pcm_chmap_print", buf.as_mut_ptr())?;
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [ChmapPosition]> for Chmap {
|
||||
fn from(a: &'a [ChmapPosition]) -> Chmap {
|
||||
let p = unsafe { libc::malloc((mem::size_of::<alsa::snd_pcm_chmap_t>() + mem::size_of::<libc::c_uint>() * a.len()) as libc::size_t) };
|
||||
if p.is_null() { panic!("Out of memory") }
|
||||
let mut r = Chmap(p as *mut alsa::snd_pcm_chmap_t, true);
|
||||
r.set_channels(a.len() as libc::c_uint);
|
||||
for (i,v) in r.as_slice_mut().iter_mut().enumerate() { *v = a[i] as libc::c_uint }
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Chmap> for Vec<ChmapPosition> {
|
||||
fn from(a: &'a Chmap) -> Vec<ChmapPosition> {
|
||||
a.as_slice().iter().map(|&v| ChmapPosition::from_c_int(v as libc::c_int, "").unwrap()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chmap_new(a: *mut alsa::snd_pcm_chmap_t) -> Chmap { Chmap(a, true) }
|
||||
pub fn chmap_handle(a: &Chmap) -> *mut alsa::snd_pcm_chmap_t { a.0 }
|
||||
|
||||
|
||||
/// Iterator over available channel maps - see [snd_pcm_chmap_query_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html)
|
||||
#[derive(Debug)]
|
||||
pub struct ChmapsQuery(*mut *mut alsa::snd_pcm_chmap_query_t, isize);
|
||||
|
||||
impl Drop for ChmapsQuery {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_pcm_free_chmaps(self.0) }}
|
||||
}
|
||||
|
||||
pub fn chmaps_query_new(a: *mut *mut alsa::snd_pcm_chmap_query_t) -> ChmapsQuery { ChmapsQuery(a, 0) }
|
||||
|
||||
impl Iterator for ChmapsQuery {
|
||||
type Item = (ChmapType, Chmap);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.0.is_null() { return None; }
|
||||
let p = unsafe { *self.0.offset(self.1) };
|
||||
if p.is_null() { return None; }
|
||||
self.1 += 1;
|
||||
let t = ChmapType::from_c_int(unsafe { (*p).type_ } as libc::c_int, "snd_pcm_query_chmaps").unwrap();
|
||||
let m = Chmap(unsafe { &mut (*p).map }, false);
|
||||
Some((t, m))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn chmap_for_first_pcm() {
|
||||
extern crate std;
|
||||
use super::*;
|
||||
use ::alloc::ffi::CString;
|
||||
use crate::device_name::HintIter;
|
||||
|
||||
use crate::Output;
|
||||
|
||||
let output = Output::local_error_handler().unwrap();
|
||||
|
||||
let i = HintIter::new(None, &*CString::new("pcm").unwrap()).unwrap();
|
||||
for p in i.map(|n| n.name.unwrap()) {
|
||||
std::println!("Chmaps for {:?}:", p);
|
||||
match PCM::open(&CString::new(p).unwrap(), Direction::Playback, false) {
|
||||
Ok(a) => for c in a.query_chmaps() {
|
||||
std::println!(" {:?}, {}", c.0, c.1);
|
||||
},
|
||||
Err(a) => std::println!(" {}", a) // It's okay to have entries in the name hint array that can't be opened
|
||||
}
|
||||
}
|
||||
|
||||
output.borrow_mut().buffer_string(|buf| {
|
||||
let str = CString::new(buf).unwrap();
|
||||
std::println!("Errors:\n{}", str.to_str().unwrap());
|
||||
});
|
||||
|
||||
}
|
||||
45
vendor/alsa/src/config.rs
vendored
Normal file
45
vendor/alsa/src/config.rs
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
//! Configuration file API
|
||||
//!
|
||||
//! For now, just contains functions regarding the global configuration
|
||||
//! stored as a cache inside alsa-lib. Calling `update_free_global` might help
|
||||
//! against valgrind reporting memory leaks.
|
||||
use crate::{alsa};
|
||||
use super::error::*;
|
||||
use super::Output;
|
||||
use core::ptr;
|
||||
|
||||
pub fn update() -> Result<bool> {
|
||||
acheck!(snd_config_update()).map(|x| x != 0)
|
||||
}
|
||||
|
||||
pub fn update_free_global() -> Result<()> {
|
||||
acheck!(snd_config_update_free_global()).map(|_| ())
|
||||
}
|
||||
|
||||
/// [snd_config_t](https://alsa-project.org/alsa-doc/alsa-lib/group___config.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct Config(*mut alsa::snd_config_t);
|
||||
|
||||
impl Drop for Config {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_config_unref(self.0) }; }
|
||||
}
|
||||
|
||||
pub fn update_ref() -> Result<Config> {
|
||||
let mut top = ptr::null_mut();
|
||||
acheck!(snd_config_update_ref(&mut top)).map(|_| Config(top))
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn save(&self, o: &mut Output) -> Result<()> {
|
||||
acheck!(snd_config_save(self.0, super::io::output_handle(o))).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_save() {
|
||||
extern crate std;
|
||||
let c = update_ref().unwrap();
|
||||
let mut outp = Output::buffer_open().unwrap();
|
||||
c.save(&mut outp).unwrap();
|
||||
std::println!("== Config save ==\n{}", outp);
|
||||
}
|
||||
566
vendor/alsa/src/ctl_int.rs
vendored
Normal file
566
vendor/alsa/src/ctl_int.rs
vendored
Normal file
@@ -0,0 +1,566 @@
|
||||
|
||||
use crate::alsa;
|
||||
use super::pcm::Info;
|
||||
use core::ffi::CStr;
|
||||
use ::alloc::ffi::CString;
|
||||
use super::Direction;
|
||||
use super::error::*;
|
||||
use super::mixer::MilliBel;
|
||||
use super::Round;
|
||||
use core::{ptr, mem, fmt, cmp};
|
||||
use crate::{Card, poll};
|
||||
use core::cell::UnsafeCell;
|
||||
use libc::{c_uint, c_void, size_t, c_long, c_int, pollfd, c_short};
|
||||
|
||||
/// We prefer not to allocate for every ElemId, ElemInfo or ElemValue.
|
||||
/// But we don't know if these will increase in the future or on other platforms.
|
||||
/// Unfortunately, Rust does not support alloca, so hard-code the sizes for now.
|
||||
|
||||
const ELEM_ID_SIZE: usize = 64;
|
||||
// const ELEM_VALUE_SIZE: usize = 1224;
|
||||
// const ELEM_INFO_SIZE: usize = 272;
|
||||
|
||||
/// [snd_ctl_pcm_next_device](https://www.alsa-project.org/alsa-doc/alsa-lib/control_8c.html#accbb0be6e5ca7361ffec0ea304ed1b05) wrapper.
|
||||
/// Iterate over devices of a card.
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceIter<'a>(&'a Ctl, c_int);
|
||||
|
||||
impl<'a> DeviceIter<'a>{
|
||||
pub fn new(ctl: &'a Ctl) -> DeviceIter<'a> {
|
||||
DeviceIter(ctl, -1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DeviceIter<'a> {
|
||||
type Item = c_int;
|
||||
|
||||
fn next(&mut self) -> Option<c_int> {
|
||||
match acheck!(snd_ctl_pcm_next_device(self.0.0, &mut self.1)) {
|
||||
Ok(_) if self.1 == -1 => None,
|
||||
Ok(_) => Some(self.1),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [snd_ctl_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct Ctl(*mut alsa::snd_ctl_t);
|
||||
|
||||
unsafe impl Send for Ctl {}
|
||||
|
||||
impl Ctl {
|
||||
/// Wrapper around open that takes a &str instead of a &CStr
|
||||
pub fn new(c: &str, nonblock: bool) -> Result<Self> {
|
||||
Self::open(&CString::new(c).unwrap(), nonblock)
|
||||
}
|
||||
|
||||
/// Open does not support async mode (it's not very Rustic anyway)
|
||||
pub fn open(c: &CStr, nonblock: bool) -> Result<Ctl> {
|
||||
let mut r = ptr::null_mut();
|
||||
let flags = if nonblock { 1 } else { 0 }; // FIXME: alsa::SND_CTL_NONBLOCK does not exist in alsa-sys
|
||||
acheck!(snd_ctl_open(&mut r, c.as_ptr(), flags)).map(|_| Ctl(r))
|
||||
}
|
||||
|
||||
pub fn from_card(c: &Card, nonblock: bool) -> Result<Ctl> {
|
||||
let s = ::alloc::format!("hw:{}", c.get_index());
|
||||
Ctl::open(&CString::new(s).unwrap(), nonblock)
|
||||
}
|
||||
|
||||
pub fn card_info(&self) -> Result<CardInfo> { CardInfo::new().and_then(|c|
|
||||
acheck!(snd_ctl_card_info(self.0, c.0)).map(|_| c)) }
|
||||
|
||||
pub fn wait(&self, timeout_ms: Option<u32>) -> Result<bool> {
|
||||
acheck!(snd_ctl_wait(self.0, timeout_ms.map(|x| x as c_int).unwrap_or(-1))).map(|i| i == 1) }
|
||||
|
||||
pub fn get_db_range(&self, id: &ElemId) -> Result<(MilliBel, MilliBel)> {
|
||||
let mut min: c_long = 0;
|
||||
let mut max: c_long = 0;
|
||||
acheck!(snd_ctl_get_dB_range(self.0, elem_id_ptr(id), &mut min, &mut max))
|
||||
.map(|_| (MilliBel(min as i64), MilliBel(max as i64)))
|
||||
}
|
||||
|
||||
pub fn convert_to_db(&self, id: &ElemId, volume: i64) -> Result<MilliBel> {
|
||||
let mut m: c_long = 0;
|
||||
acheck!(snd_ctl_convert_to_dB(self.0, elem_id_ptr(id), volume as c_long, &mut m))
|
||||
.map(|_| MilliBel(m as i64))
|
||||
}
|
||||
|
||||
pub fn convert_from_db(&self, id: &ElemId, mb: MilliBel, dir: Round) -> Result<i64> {
|
||||
let mut m: c_long = 0;
|
||||
acheck!(snd_ctl_convert_from_dB(self.0, elem_id_ptr(id), mb.0 as c_long, &mut m, dir as c_int))
|
||||
.map(|_| m as i64)
|
||||
}
|
||||
|
||||
pub fn elem_read(&self, val: &mut ElemValue) -> Result<()> {
|
||||
acheck!(snd_ctl_elem_read(self.0, elem_value_ptr(val))).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn elem_write(&self, val: &ElemValue) -> Result<()> {
|
||||
acheck!(snd_ctl_elem_write(self.0, elem_value_ptr(val))).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn elem_lock(&self, id: &ElemId) -> Result<i32> {
|
||||
acheck!(snd_ctl_elem_lock(self.0, elem_id_ptr(id)))
|
||||
}
|
||||
|
||||
pub fn elem_unlock(&self, id: &ElemId) -> Result<i32> {
|
||||
acheck!(snd_ctl_elem_unlock(self.0, elem_id_ptr(id)))
|
||||
}
|
||||
|
||||
pub fn elem_list(&self) -> Result<ElemList> {
|
||||
// obtain the list of all the elements now that we know how many there are
|
||||
let list = elem_list_new(|list| {
|
||||
acheck!(snd_ctl_elem_list(self.0, list.0))?;
|
||||
Ok(list.get_count())
|
||||
})?;
|
||||
acheck!(snd_ctl_elem_list(self.0, list.0))?;
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
/// Note: According to alsa-lib documentation, you're also supposed to have functionality for
|
||||
/// returning whether or not you are subscribed. This does not work in practice, so I'm not
|
||||
/// including that here.
|
||||
pub fn subscribe_events(&self, subscribe: bool) -> Result<()> {
|
||||
acheck!(snd_ctl_subscribe_events(self.0, if subscribe { 1 } else { 0 })).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn read(&self) -> Result<Option<Event>> {
|
||||
let e = event_new()?;
|
||||
acheck!(snd_ctl_read(self.0, e.0)).map(|r| if r == 1 { Some(e) } else { None })
|
||||
}
|
||||
|
||||
pub fn pcm_info(&self, device: u32, subdevice: u32, direction: Direction) -> Result<Info> {
|
||||
Info::new().and_then(|mut info| {
|
||||
info.set_device(device);
|
||||
info.set_subdevice(subdevice);
|
||||
info.set_stream(direction);
|
||||
acheck!(snd_ctl_pcm_info(self.0, info.0)).map(|_| info )
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Ctl {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_ctl_close(self.0) }; }
|
||||
}
|
||||
|
||||
impl poll::Descriptors for Ctl {
|
||||
fn count(&self) -> usize {
|
||||
unsafe { alsa::snd_ctl_poll_descriptors_count(self.0) as usize }
|
||||
}
|
||||
fn fill(&self, p: &mut [pollfd]) -> Result<usize> {
|
||||
let z = unsafe { alsa::snd_ctl_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) };
|
||||
from_code("snd_ctl_poll_descriptors", z).map(|_| z as usize)
|
||||
}
|
||||
fn revents(&self, p: &[pollfd]) -> Result<poll::Flags> {
|
||||
let mut r = 0;
|
||||
let z = unsafe { alsa::snd_ctl_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) };
|
||||
from_code("snd_ctl_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn ctl_ptr(a: &Ctl) -> *mut alsa::snd_ctl_t { a.0 }
|
||||
|
||||
/// [snd_ctl_card_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct CardInfo(*mut alsa::snd_ctl_card_info_t);
|
||||
|
||||
impl Drop for CardInfo {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_ctl_card_info_free(self.0) }}
|
||||
}
|
||||
|
||||
impl CardInfo {
|
||||
fn new() -> Result<CardInfo> {
|
||||
let mut p = ptr::null_mut();
|
||||
acheck!(snd_ctl_card_info_malloc(&mut p)).map(|_| CardInfo(p))
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> Result<&str> {
|
||||
from_const("snd_ctl_card_info_get_id", unsafe { alsa::snd_ctl_card_info_get_id(self.0) })}
|
||||
pub fn get_driver(&self) -> Result<&str> {
|
||||
from_const("snd_ctl_card_info_get_driver", unsafe { alsa::snd_ctl_card_info_get_driver(self.0) })}
|
||||
pub fn get_components(&self) -> Result<&str> {
|
||||
from_const("snd_ctl_card_info_get_components", unsafe { alsa::snd_ctl_card_info_get_components(self.0) })}
|
||||
pub fn get_longname(&self) -> Result<&str> {
|
||||
from_const("snd_ctl_card_info_get_longname", unsafe { alsa::snd_ctl_card_info_get_longname(self.0) })}
|
||||
pub fn get_name(&self) -> Result<&str> {
|
||||
from_const("snd_ctl_card_info_get_name", unsafe { alsa::snd_ctl_card_info_get_name(self.0) })}
|
||||
pub fn get_mixername(&self) -> Result<&str> {
|
||||
from_const("snd_ctl_card_info_get_mixername", unsafe { alsa::snd_ctl_card_info_get_mixername(self.0) })}
|
||||
pub fn get_card(&self) -> Card { Card::new(unsafe { alsa::snd_ctl_card_info_get_card(self.0) })}
|
||||
}
|
||||
|
||||
alsa_enum!(
|
||||
/// [SND_CTL_ELEM_IFACE_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) constants
|
||||
ElemIface, ALL_ELEMIFACE[7],
|
||||
|
||||
Card = SND_CTL_ELEM_IFACE_CARD,
|
||||
Hwdep = SND_CTL_ELEM_IFACE_HWDEP,
|
||||
Mixer = SND_CTL_ELEM_IFACE_MIXER,
|
||||
PCM = SND_CTL_ELEM_IFACE_PCM,
|
||||
Rawmidi = SND_CTL_ELEM_IFACE_RAWMIDI,
|
||||
Timer = SND_CTL_ELEM_IFACE_TIMER,
|
||||
Sequencer = SND_CTL_ELEM_IFACE_SEQUENCER,
|
||||
);
|
||||
|
||||
alsa_enum!(
|
||||
/// [SND_CTL_ELEM_TYPE_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) constants
|
||||
ElemType, ALL_ELEMTYPE[7],
|
||||
|
||||
None = SND_CTL_ELEM_TYPE_NONE,
|
||||
Boolean = SND_CTL_ELEM_TYPE_BOOLEAN,
|
||||
Integer = SND_CTL_ELEM_TYPE_INTEGER,
|
||||
Enumerated = SND_CTL_ELEM_TYPE_ENUMERATED,
|
||||
Bytes = SND_CTL_ELEM_TYPE_BYTES,
|
||||
IEC958 = SND_CTL_ELEM_TYPE_IEC958,
|
||||
Integer64 = SND_CTL_ELEM_TYPE_INTEGER64,
|
||||
);
|
||||
|
||||
/// [snd_ctl_elem_value_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
|
||||
pub struct ElemValue {
|
||||
ptr: *mut alsa::snd_ctl_elem_value_t,
|
||||
etype: ElemType,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
impl Drop for ElemValue {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_ctl_elem_value_free(self.ptr) }; }
|
||||
}
|
||||
|
||||
pub fn elem_value_ptr(a: &ElemValue) -> *mut alsa::snd_ctl_elem_value_t { a.ptr }
|
||||
|
||||
pub fn elem_value_new(t: ElemType, count: u32) -> Result<ElemValue> {
|
||||
let mut p = ptr::null_mut();
|
||||
acheck!(snd_ctl_elem_value_malloc(&mut p))
|
||||
.map(|_| ElemValue { ptr: p, etype: t, count })
|
||||
}
|
||||
|
||||
impl ElemValue {
|
||||
|
||||
pub fn set_id(&mut self, id: &ElemId) {
|
||||
unsafe { alsa::snd_ctl_elem_value_set_id(self.ptr, elem_id_ptr(id)) }
|
||||
}
|
||||
|
||||
// Note: The get_bytes hands out a reference to inside the object. Therefore, we can't treat
|
||||
// the content as "cell"ed, but must take a "&mut self" (to make sure the reference
|
||||
// from get_bytes has been dropped when calling a set_* function).
|
||||
|
||||
pub fn get_boolean(&self, idx: u32) -> Option<bool> {
|
||||
if self.etype != ElemType::Boolean || idx >= self.count { None }
|
||||
else { Some( unsafe { alsa::snd_ctl_elem_value_get_boolean(self.ptr, idx as c_uint) } != 0) }
|
||||
}
|
||||
|
||||
pub fn set_boolean(&mut self, idx: u32, val: bool) -> Option<()> {
|
||||
if self.etype != ElemType::Boolean || idx >= self.count { None }
|
||||
else { unsafe { alsa::snd_ctl_elem_value_set_boolean(self.ptr, idx as c_uint, if val {1} else {0}) }; Some(()) }
|
||||
}
|
||||
|
||||
pub fn get_integer(&self, idx: u32) -> Option<i32> {
|
||||
if self.etype != ElemType::Integer || idx >= self.count { None }
|
||||
else { Some( unsafe { alsa::snd_ctl_elem_value_get_integer(self.ptr, idx as c_uint) } as i32) }
|
||||
}
|
||||
|
||||
pub fn set_integer(&mut self, idx: u32, val: i32) -> Option<()> {
|
||||
if self.etype != ElemType::Integer || idx >= self.count { None }
|
||||
else { unsafe { alsa::snd_ctl_elem_value_set_integer(self.ptr, idx as c_uint, val as c_long) }; Some(()) }
|
||||
}
|
||||
|
||||
pub fn get_integer64(&self, idx: u32) -> Option<i64> {
|
||||
if self.etype != ElemType::Integer64 || idx >= self.count { None }
|
||||
else { Some( unsafe { alsa::snd_ctl_elem_value_get_integer64(self.ptr, idx as c_uint) } as i64) }
|
||||
}
|
||||
|
||||
pub fn set_integer64(&mut self, idx: u32, val: i64) -> Option<()> {
|
||||
if self.etype != ElemType::Integer || idx >= self.count { None }
|
||||
else { unsafe { alsa::snd_ctl_elem_value_set_integer64(self.ptr, idx as c_uint, val) }; Some(()) }
|
||||
}
|
||||
|
||||
pub fn get_enumerated(&self, idx: u32) -> Option<u32> {
|
||||
if self.etype != ElemType::Enumerated || idx >= self.count { None }
|
||||
else { Some( unsafe { alsa::snd_ctl_elem_value_get_enumerated(self.ptr, idx as c_uint) } as u32) }
|
||||
}
|
||||
|
||||
pub fn set_enumerated(&mut self, idx: u32, val: u32) -> Option<()> {
|
||||
if self.etype != ElemType::Enumerated || idx >= self.count { None }
|
||||
else { unsafe { alsa::snd_ctl_elem_value_set_enumerated(self.ptr, idx as c_uint, val as c_uint) }; Some(()) }
|
||||
}
|
||||
|
||||
pub fn get_byte(&self, idx: u32) -> Option<u8> {
|
||||
if self.etype != ElemType::Bytes || idx >= self.count { None }
|
||||
else { Some( unsafe { alsa::snd_ctl_elem_value_get_byte(self.ptr, idx as c_uint) } as u8) }
|
||||
}
|
||||
|
||||
pub fn set_byte(&mut self, idx: u32, val: u8) -> Option<()> {
|
||||
if self.etype != ElemType::Bytes || idx >= self.count { None }
|
||||
else { unsafe { alsa::snd_ctl_elem_value_set_byte(self.ptr, idx as c_uint, val) }; Some(()) }
|
||||
}
|
||||
|
||||
pub fn get_bytes(&self) -> Option<&[u8]> {
|
||||
if self.etype != ElemType::Bytes { None }
|
||||
else { Some( unsafe { ::core::slice::from_raw_parts(
|
||||
alsa::snd_ctl_elem_value_get_bytes(self.ptr) as *const u8, self.count as usize) } ) }
|
||||
}
|
||||
|
||||
pub fn set_bytes(&mut self, val: &[u8]) -> Option<()> {
|
||||
if self.etype != ElemType::Bytes || val.len() != self.count as usize { None }
|
||||
|
||||
// Note: the alsa-lib function definition is broken. First, the pointer is declared as mut even
|
||||
// though it's const, and second, there is a "value" missing between "elem" and "set_bytes".
|
||||
else { unsafe { alsa::snd_ctl_elem_set_bytes(self.ptr, val.as_ptr() as *mut c_void, val.len() as size_t) }; Some(()) }
|
||||
}
|
||||
|
||||
/// Creates a new ElemValue.
|
||||
pub fn new(t: ElemType) -> Result<ElemValue> {
|
||||
// See max length in include/uapi/sound/asound.h in linux kernel for these values
|
||||
let count = match t {
|
||||
ElemType::None => 1,
|
||||
ElemType::Boolean => 128,
|
||||
ElemType::Integer => 128,
|
||||
ElemType::Enumerated => 128,
|
||||
ElemType::Bytes => 512,
|
||||
ElemType::IEC958 => 1,
|
||||
ElemType::Integer64 => 64,
|
||||
};
|
||||
// if count > maxcount { return Err(Error::new(Some("ElemValue::new - count too large".into()), 1)) }
|
||||
let ev = elem_value_new(t, count)?;
|
||||
unsafe { alsa::snd_ctl_elem_value_clear(elem_value_ptr(&ev)) };
|
||||
Ok(ev)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl fmt::Debug for ElemValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::ElemType::*;
|
||||
write!(f, "ElemValue({:?}", self.etype)?;
|
||||
for a in 0..self.count { match self.etype {
|
||||
Boolean => write!(f, ",{:?}", self.get_boolean(a).unwrap()),
|
||||
Integer => write!(f, ",{:?}", self.get_integer(a).unwrap()),
|
||||
Integer64 => write!(f, ",{:?}", self.get_integer64(a).unwrap()),
|
||||
Enumerated => write!(f, ",{:?}", self.get_enumerated(a).unwrap()),
|
||||
Bytes => write!(f, ",{:?}", self.get_byte(a).unwrap()),
|
||||
_ => Ok(()),
|
||||
}?};
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
|
||||
/// [snd_ctl_elem_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct ElemInfo(*mut alsa::snd_ctl_elem_info_t);
|
||||
|
||||
pub fn elem_info_ptr(a: &ElemInfo) -> *mut alsa::snd_ctl_elem_info_t { a.0 }
|
||||
|
||||
impl Drop for ElemInfo {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_ctl_elem_info_free(self.0) }; }
|
||||
}
|
||||
|
||||
pub fn elem_info_new() -> Result<ElemInfo> {
|
||||
let mut p = ptr::null_mut();
|
||||
acheck!(snd_ctl_elem_info_malloc(&mut p)).map(|_| ElemInfo(p))
|
||||
}
|
||||
|
||||
impl ElemInfo {
|
||||
pub fn get_type(&self) -> ElemType { ElemType::from_c_int(
|
||||
unsafe { alsa::snd_ctl_elem_info_get_type(self.0) } as c_int, "snd_ctl_elem_info_get_type").unwrap() }
|
||||
pub fn get_count(&self) -> u32 { unsafe { alsa::snd_ctl_elem_info_get_count(self.0) as u32 } }
|
||||
}
|
||||
|
||||
//
|
||||
// Non-allocating version of ElemId
|
||||
//
|
||||
|
||||
/// [snd_ctl_elem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
|
||||
pub struct ElemId(UnsafeCell<[u8; ELEM_ID_SIZE]>);
|
||||
|
||||
pub fn elem_id_new() -> Result<ElemId> {
|
||||
assert!(unsafe { alsa::snd_ctl_elem_id_sizeof() } as usize <= ELEM_ID_SIZE);
|
||||
Ok(ElemId(UnsafeCell::new(unsafe { mem::zeroed() })))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn elem_id_ptr(a: &ElemId) -> *mut alsa::snd_ctl_elem_id_t { a.0.get() as *mut _ as *mut alsa::snd_ctl_elem_id_t }
|
||||
|
||||
unsafe impl Send for ElemId {}
|
||||
|
||||
impl Clone for ElemId {
|
||||
fn clone(&self) -> Self {
|
||||
ElemId(UnsafeCell::new(unsafe { *self.0.get() }))
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Allocating version of ElemId
|
||||
//
|
||||
|
||||
/*
|
||||
|
||||
/// [snd_ctl_elem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
|
||||
pub struct ElemId(*mut alsa::snd_ctl_elem_id_t);
|
||||
|
||||
impl Drop for ElemId {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_ctl_elem_id_free(self.0) }; }
|
||||
}
|
||||
|
||||
pub fn elem_id_new() -> Result<ElemId> {
|
||||
let mut p = ptr::null_mut();
|
||||
acheck!(snd_ctl_elem_id_malloc(&mut p)).map(|_| ElemId(p))
|
||||
}
|
||||
|
||||
pub fn elem_id_ptr(a: &ElemId) -> *mut alsa::snd_ctl_elem_id_t { a.0 }
|
||||
|
||||
*/
|
||||
|
||||
impl ElemId {
|
||||
pub fn get_name(&self) -> Result<&str> {
|
||||
from_const("snd_hctl_elem_id_get_name", unsafe { alsa::snd_ctl_elem_id_get_name(elem_id_ptr(self)) })}
|
||||
pub fn get_device(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_device(elem_id_ptr(self)) as u32 }}
|
||||
pub fn get_subdevice(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_subdevice(elem_id_ptr(self)) as u32 }}
|
||||
pub fn get_numid(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_numid(elem_id_ptr(self)) as u32 }}
|
||||
pub fn get_index(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_index(elem_id_ptr(self)) as u32 }}
|
||||
pub fn get_interface(&self) -> ElemIface { ElemIface::from_c_int(
|
||||
unsafe { alsa::snd_ctl_elem_id_get_interface(elem_id_ptr(self)) } as c_int, "snd_ctl_elem_id_get_interface").unwrap() }
|
||||
|
||||
pub fn set_device(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_device(elem_id_ptr(self), v) }}
|
||||
pub fn set_subdevice(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_subdevice(elem_id_ptr(self), v) }}
|
||||
pub fn set_numid(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_numid(elem_id_ptr(self), v) }}
|
||||
pub fn set_index(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_index(elem_id_ptr(self), v) }}
|
||||
pub fn set_interface(&mut self, v: ElemIface) { unsafe { alsa::snd_ctl_elem_id_set_interface(elem_id_ptr(self), v as u32) }}
|
||||
pub fn set_name(&mut self, v: &CStr) { unsafe { alsa::snd_ctl_elem_id_set_name(elem_id_ptr(self), v.as_ptr()) }}
|
||||
|
||||
/// Creates a new ElemId.
|
||||
///
|
||||
/// To ensure safety (i e make sure we never have an invalid interface enum), we need to supply it to the "new" function.
|
||||
pub fn new(iface: ElemIface) -> Self {
|
||||
let mut r = elem_id_new().unwrap();
|
||||
r.set_interface(iface);
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
impl cmp::Eq for ElemId {}
|
||||
|
||||
impl cmp::PartialEq for ElemId {
|
||||
fn eq(&self, a: &ElemId) -> bool {
|
||||
self.get_numid() == a.get_numid() && self.get_interface() == a.get_interface() &&
|
||||
self.get_index() == a.get_index() && self.get_device() == a.get_device() &&
|
||||
self.get_subdevice() == a.get_subdevice() && self.get_name().ok() == a.get_name().ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ElemId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let index = self.get_index();
|
||||
let device = self.get_device();
|
||||
let subdevice = self.get_subdevice();
|
||||
|
||||
write!(f, "ElemId(#{}, {:?}, {:?}", self.get_numid(), self.get_interface(), self.get_name())?;
|
||||
if index > 0 { write!(f, ", index={}", index)? };
|
||||
if device > 0 || subdevice > 0 { write!(f, ", device={}", device)? };
|
||||
if subdevice > 0 { write!(f, ", subdevice={}", device)? };
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
|
||||
/// [snd_ctl_elem_list_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct ElemList(*mut alsa::snd_ctl_elem_list_t);
|
||||
|
||||
impl Drop for ElemList {
|
||||
fn drop(&mut self) {
|
||||
unsafe { alsa::snd_ctl_elem_list_free_space(self.0) };
|
||||
unsafe { alsa::snd_ctl_elem_list_free(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
fn elem_list_new<F: FnOnce(&ElemList) -> Result<u32>>(f: F) -> Result<ElemList> {
|
||||
let mut p = ptr::null_mut();
|
||||
let list = acheck!(snd_ctl_elem_list_malloc(&mut p)).map(|_| ElemList(p))?;
|
||||
let count = f(&list)?;
|
||||
if count > 0 {
|
||||
acheck!(snd_ctl_elem_list_alloc_space(list.0, count))?;
|
||||
}
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
impl ElemList {
|
||||
#[inline]
|
||||
fn ensure_valid_index(&self, index: u32) -> Result<()> {
|
||||
if index >= self.get_used() {
|
||||
Err(Error::new("snd_ctl_elem_list_*", libc::EINVAL))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_count(&self) -> u32 { unsafe { alsa::snd_ctl_elem_list_get_count(self.0) } }
|
||||
pub fn get_used(&self) -> u32 { unsafe { alsa::snd_ctl_elem_list_get_used(self.0) } }
|
||||
pub fn get_id(&self, index: u32) -> Result<ElemId> {
|
||||
self.ensure_valid_index(index)?;
|
||||
let elem_id = elem_id_new()?;
|
||||
unsafe { alsa::snd_ctl_elem_list_get_id(self.0, index, elem_id_ptr(&elem_id)) };
|
||||
Ok(elem_id)
|
||||
}
|
||||
pub fn get_numid(&self, index: u32) -> Result<u32> { self.ensure_valid_index(index)?; Ok(unsafe { alsa::snd_ctl_elem_list_get_numid(self.0, index) }) }
|
||||
pub fn get_interface(&self, index: u32) -> Result<ElemIface> {
|
||||
self.ensure_valid_index(index)?;
|
||||
ElemIface::from_c_int(unsafe { alsa::snd_ctl_elem_list_get_interface(self.0, index) } as c_int, "snd_ctl_elem_list_get_interface")
|
||||
}
|
||||
pub fn get_device(&self, index: u32) -> Result<u32> { self.ensure_valid_index(index)?; Ok(unsafe { alsa::snd_ctl_elem_list_get_device(self.0, index) }) }
|
||||
pub fn get_subdevice(&self, index: u32) -> Result<u32> { self.ensure_valid_index(index)?; Ok(unsafe { alsa::snd_ctl_elem_list_get_subdevice(self.0, index) }) }
|
||||
pub fn get_name(&self, index: u32) -> Result<&str> {
|
||||
self.ensure_valid_index(index)?;
|
||||
from_const("snd_ctl_elem_list_get_name", unsafe { alsa::snd_ctl_elem_list_get_name(self.0, index) })
|
||||
}
|
||||
pub fn get_index(&self, index: u32) -> Result<u32> { self.ensure_valid_index(index)?; Ok(unsafe { alsa::snd_ctl_elem_list_get_index(self.0, index) }) }
|
||||
}
|
||||
|
||||
/// [snd_ctl_event_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct Event(*mut alsa::snd_ctl_event_t);
|
||||
|
||||
impl Drop for Event {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_ctl_event_free(self.0) }; }
|
||||
}
|
||||
|
||||
pub fn event_new() -> Result<Event> {
|
||||
let mut p = ptr::null_mut();
|
||||
acheck!(snd_ctl_event_malloc(&mut p)).map(|_| Event(p))
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub fn get_mask(&self) -> EventMask { EventMask(unsafe { alsa::snd_ctl_event_elem_get_mask(self.0) as u32 })}
|
||||
pub fn get_id(&self) -> ElemId {
|
||||
let r = elem_id_new().unwrap();
|
||||
unsafe { alsa::snd_ctl_event_elem_get_id(self.0, elem_id_ptr(&r)) };
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// [SND_CTL_EVENT_MASK_XXX](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) bitmask
|
||||
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct EventMask(pub u32);
|
||||
|
||||
impl EventMask {
|
||||
pub fn remove(&self) -> bool { return self.0 & 0xffffffff == 0xffffffff }
|
||||
pub fn value(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 0) != 0); }
|
||||
pub fn info(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 1) != 0); }
|
||||
pub fn add(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 2) != 0); }
|
||||
pub fn tlv(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 3) != 0); }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_sizeof() {
|
||||
extern crate std;
|
||||
let elemid = unsafe { alsa::snd_ctl_elem_id_sizeof() } as usize;
|
||||
let elemvalue = unsafe { alsa::snd_ctl_elem_value_sizeof() } as usize;
|
||||
let eleminfo = unsafe { alsa::snd_ctl_elem_info_sizeof() } as usize;
|
||||
|
||||
assert!(elemid <= ELEM_ID_SIZE);
|
||||
// assert!(elemvalue <= ELEM_VALUE_SIZE);
|
||||
// assert!(eleminfo <= ELEM_INFO_SIZE);
|
||||
|
||||
std::println!("Elem id: {}, Elem value: {}, Elem info: {}", elemid, elemvalue, eleminfo);
|
||||
}
|
||||
94
vendor/alsa/src/device_name.rs
vendored
Normal file
94
vendor/alsa/src/device_name.rs
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
//! Enumerate devices in the alsa library configuration
|
||||
//!
|
||||
//! # Example
|
||||
//! Print all devices found in various categories.
|
||||
//!
|
||||
//! ```
|
||||
//! use std::ffi::CString;
|
||||
//! use alsa::device_name::HintIter;
|
||||
//!
|
||||
//! for t in &["pcm", "ctl", "rawmidi", "timer", "seq", "hwdep"] {
|
||||
//! println!("{} devices:", t);
|
||||
//! let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
|
||||
//! for a in i { println!(" {:?}", a) }
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use core::ptr;
|
||||
use libc::{c_void, c_int};
|
||||
use crate::alsa;
|
||||
use super::{Card, Direction};
|
||||
use super::error::*;
|
||||
use core::ffi::CStr;
|
||||
use ::alloc::ffi::CString;
|
||||
use ::alloc::string::String;
|
||||
|
||||
/// [snd_device_name_hint](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct HintIter(*mut *mut c_void, isize);
|
||||
|
||||
impl Drop for HintIter {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_device_name_free_hint(self.0); }}
|
||||
}
|
||||
|
||||
impl HintIter {
|
||||
/// typical interfaces are: "pcm", "ctl", "rawmidi", "timer", "seq" and "hwdep".
|
||||
pub fn new(card: Option<&Card>, iface: &CStr) -> Result<HintIter> {
|
||||
let mut p = ptr::null_mut();
|
||||
let cnr = card.map(|c| c.get_index()).unwrap_or(-1) as c_int;
|
||||
acheck!(snd_device_name_hint(cnr, iface.as_ptr(), &mut p))
|
||||
.map(|_| HintIter(p, 0))
|
||||
}
|
||||
|
||||
/// A constructor variant that takes the interface as a Rust string slice.
|
||||
pub fn new_str(card: Option<&Card>, iface: &str) -> Result<HintIter> {
|
||||
HintIter::new(card, &CString::new(iface).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for HintIter {
|
||||
type Item = Hint;
|
||||
fn next(&mut self) -> Option<Hint> {
|
||||
if self.0.is_null() { return None; }
|
||||
let p = unsafe { *self.0.offset(self.1) };
|
||||
if p.is_null() { return None; }
|
||||
self.1 += 1;
|
||||
Some(Hint::new(p))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// [snd_device_name_get_hint](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Hint {
|
||||
pub name: Option<String>,
|
||||
pub desc: Option<String>,
|
||||
pub direction: Option<Direction>,
|
||||
}
|
||||
|
||||
impl Hint {
|
||||
fn get_str(p: *const c_void, name: &str) -> Option<String> {
|
||||
let name = CString::new(name).unwrap();
|
||||
let c = unsafe { alsa::snd_device_name_get_hint(p, name.as_ptr()) };
|
||||
from_alloc("snd_device_name_get_hint", c).ok()
|
||||
}
|
||||
|
||||
fn new(p: *const c_void) -> Hint {
|
||||
let d = Hint::get_str(p, "IOID").and_then(|x| match &*x {
|
||||
"Input" => Some(Direction::Capture),
|
||||
"Output" => Some(Direction::Playback),
|
||||
_ => None,
|
||||
});
|
||||
Hint { name: Hint::get_str(p, "NAME"), desc: Hint::get_str(p, "DESC"), direction: d }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_hints() {
|
||||
extern crate std;
|
||||
for t in &["pcm", "ctl", "rawmidi", "timer", "seq", "hwdep"] {
|
||||
std::println!("{} devices:", t);
|
||||
let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
|
||||
for a in i { std::println!(" {:?}", a) }
|
||||
}
|
||||
}
|
||||
5
vendor/alsa/src/direct.rs
vendored
Normal file
5
vendor/alsa/src/direct.rs
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
//! Functions that bypass alsa-lib and talk directly to the kernel.
|
||||
|
||||
pub mod pcm;
|
||||
|
||||
mod ffi;
|
||||
6713
vendor/alsa/src/direct/asound_ioctl.rs
vendored
Normal file
6713
vendor/alsa/src/direct/asound_ioctl.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
212
vendor/alsa/src/direct/ffi.rs
vendored
Normal file
212
vendor/alsa/src/direct/ffi.rs
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
//! Some definitions from the kernel headers
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
// const SNDRV_PCM_MMAP_OFFSET_DATA: c_uint = 0x00000000;
|
||||
pub const SNDRV_PCM_MMAP_OFFSET_STATUS: libc::c_uint = 0x80000000;
|
||||
pub const SNDRV_PCM_MMAP_OFFSET_CONTROL: libc::c_uint = 0x81000000;
|
||||
|
||||
pub const SNDRV_PCM_SYNC_PTR_HWSYNC: libc::c_uint = 1;
|
||||
pub const SNDRV_PCM_SYNC_PTR_APPL: libc::c_uint = 2;
|
||||
pub const SNDRV_PCM_SYNC_PTR_AVAIL_MIN: libc::c_uint = 4;
|
||||
|
||||
// #[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type snd_pcm_state_t = libc::c_int;
|
||||
|
||||
// #[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type snd_pcm_uframes_t = libc::c_ulong;
|
||||
|
||||
// I think?! Not sure how this will work with X32 ABI?!
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type __kernel_off_t = libc::c_long;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct snd_pcm_mmap_status {
|
||||
pub state: snd_pcm_state_t, /* RO: state - SNDRV_PCM_STATE_XXXX */
|
||||
pub pad1: libc::c_int, /* Needed for 64 bit alignment */
|
||||
pub hw_ptr: snd_pcm_uframes_t, /* RO: hw ptr (0...boundary-1) */
|
||||
pub tstamp: libc::timespec, /* Timestamp */
|
||||
pub suspended_state: snd_pcm_state_t, /* RO: suspended stream state */
|
||||
pub audio_tstamp: libc::timespec, /* from sample counter or wall clock */
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct snd_pcm_mmap_control {
|
||||
pub appl_ptr: snd_pcm_uframes_t, /* RW: appl ptr (0...boundary-1) */
|
||||
pub avail_min: snd_pcm_uframes_t, /* RW: min available frames for wakeup */
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct snd_pcm_channel_info {
|
||||
pub channel: libc::c_uint,
|
||||
pub offset: __kernel_off_t, /* mmap offset */
|
||||
pub first: libc::c_uint, /* offset to first sample in bits */
|
||||
pub step: libc::c_uint, /* samples distance in bits */
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union snd_pcm_mmap_status_r {
|
||||
pub status: snd_pcm_mmap_status,
|
||||
pub reserved: [libc::c_uchar; 64],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union snd_pcm_mmap_control_r {
|
||||
pub control: snd_pcm_mmap_control,
|
||||
pub reserved: [libc::c_uchar; 64],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct snd_pcm_sync_ptr {
|
||||
pub flags: libc::c_uint,
|
||||
pub s: snd_pcm_mmap_status_r,
|
||||
pub c: snd_pcm_mmap_control_r,
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "android"))] {
|
||||
// See <https://github.com/nix-rust/nix/blob/197f55b3ccbce3273bf6ce119d1a8541b5df5d66/src/sys/ioctl/linux.rs>
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(any(target_os = "android", target_env = "musl"))] {
|
||||
pub(super) type ioctl_num_type = libc::c_int;
|
||||
} else {
|
||||
pub(super) type ioctl_num_type = libc::c_ulong;
|
||||
}
|
||||
}
|
||||
|
||||
// The READ dir is consistent across arches
|
||||
pub(super) const READ: ioctl_num_type = 2;
|
||||
|
||||
// But WRITE is not, as well as having a different number of bits for the SIZEBITS
|
||||
cfg_if!{
|
||||
if #[cfg(any(
|
||||
target_arch = "mips",
|
||||
target_arch = "mips32r6",
|
||||
target_arch = "mips64",
|
||||
target_arch = "mips64r6",
|
||||
target_arch = "powerpc",
|
||||
target_arch = "powerpc64",
|
||||
target_arch = "sparc64"
|
||||
))] {
|
||||
pub(super) const WRITE: ioctl_num_type = 4;
|
||||
const SIZEBITS: ioctl_num_type = 13;
|
||||
} else {
|
||||
pub(super) const WRITE: ioctl_num_type = 1;
|
||||
const SIZEBITS: ioctl_num_type = 14;
|
||||
}
|
||||
}
|
||||
|
||||
const NRSHIFT: ioctl_num_type = 0;
|
||||
const NRBITS: ioctl_num_type = 8;
|
||||
const TYPEBITS: ioctl_num_type = 8;
|
||||
const TYPESHIFT: ioctl_num_type = NRSHIFT + NRBITS;
|
||||
const SIZESHIFT: ioctl_num_type = TYPESHIFT + TYPEBITS;
|
||||
const DIRSHIFT: ioctl_num_type = SIZESHIFT + SIZEBITS;
|
||||
|
||||
/// Replication of the [`nix::ioc!`](https://github.com/nix-rust/nix/blob/197f55b3ccbce3273bf6ce119d1a8541b5df5d66/src/sys/ioctl/linux.rs#L78-L96)
|
||||
pub(super) const fn make_request(
|
||||
dir: ioctl_num_type,
|
||||
typ: u8,
|
||||
nr: u8,
|
||||
size: usize,
|
||||
) -> ioctl_num_type {
|
||||
dir << DIRSHIFT
|
||||
| (typ as ioctl_num_type) << TYPESHIFT
|
||||
| (nr as ioctl_num_type) << NRSHIFT
|
||||
| (size as ioctl_num_type) << SIZESHIFT
|
||||
}
|
||||
} else if #[cfg(any(
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos"
|
||||
))] {
|
||||
// See <https://github.com/nix-rust/nix/blob/197f55b3ccbce3273bf6ce119d1a8541b5df5d66/src/sys/ioctl/bsd.rs>
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(not(any(target_os = "illumos", target_os = "solaris")))] {
|
||||
pub(super) type ioctl_num_type = libc::c_ulong;
|
||||
} else {
|
||||
pub(super) type ioctl_num_type = libc::c_int;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(overflowing_literals)]
|
||||
pub(super) const READ: ioctl_num_type = 0x4000_0000;
|
||||
#[allow(overflowing_literals)]
|
||||
pub(super) const WRITE: ioctl_num_type = 0x8000_0000;
|
||||
|
||||
const IOCPARM_MASK: ioctl_num_type = 0x1fff;
|
||||
|
||||
/// Replication of [`nix::ioc!`](https://github.com/nix-rust/nix/blob/197f55b3ccbce3273bf6ce119d1a8541b5df5d66/src/sys/ioctl/bsd.rs#L31-L42)
|
||||
pub(super) const fn make_request(
|
||||
dir: ioctl_num_type,
|
||||
typ: u8,
|
||||
nr: u8,
|
||||
size: usize,
|
||||
) -> ioctl_num_type {
|
||||
dir | ((size as ioctl_num_type) & IOCPARM_MASK) << 16
|
||||
| (typ as ioctl_num_type) << 8
|
||||
| nr as ioctl_num_type
|
||||
}
|
||||
} else {
|
||||
compile_error!("unknown target platform");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn sndrv_pcm_ioctl_channel_info(
|
||||
fd: libc::c_int,
|
||||
data: *mut snd_pcm_channel_info,
|
||||
) -> Result<(), crate::Error> {
|
||||
const REQUEST: ioctl_num_type = make_request(
|
||||
READ,
|
||||
b'A',
|
||||
0x32,
|
||||
core::mem::size_of::<snd_pcm_channel_info>(),
|
||||
);
|
||||
|
||||
unsafe {
|
||||
if libc::ioctl(fd, REQUEST, data) == -1 {
|
||||
Err(crate::Error::last("SNDRV_PCM_IOCTL_CHANNEL_INFO"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn sndrv_pcm_ioctl_sync_ptr(
|
||||
fd: libc::c_int,
|
||||
data: *mut snd_pcm_sync_ptr,
|
||||
) -> Result<(), crate::Error> {
|
||||
const REQUEST: ioctl_num_type = make_request(
|
||||
READ | WRITE,
|
||||
b'A',
|
||||
0x23,
|
||||
core::mem::size_of::<snd_pcm_sync_ptr>(),
|
||||
);
|
||||
|
||||
unsafe {
|
||||
if libc::ioctl(fd, REQUEST, data) == -1 {
|
||||
Err(crate::Error::last("SNDRV_PCM_IOCTL_SYNC_PTR"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pagesize() -> usize {
|
||||
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
|
||||
}
|
||||
639
vendor/alsa/src/direct/pcm.rs
vendored
Normal file
639
vendor/alsa/src/direct/pcm.rs
vendored
Normal file
@@ -0,0 +1,639 @@
|
||||
/*!
|
||||
This module bypasses alsa-lib and directly read and write into memory mapped kernel memory.
|
||||
In case of the sample memory, this is in many cases the DMA buffers that is transferred to the sound card.
|
||||
|
||||
The reasons for doing this are:
|
||||
|
||||
* Minimum overhead where it matters most: let alsa-lib do the code heavy setup -
|
||||
then steal its file descriptor and deal with sample streaming from Rust.
|
||||
* RT-safety to the maximum extent possible. Creating/dropping any of these structs causes syscalls,
|
||||
but function calls on these are just read and write from memory. No syscalls, no memory allocations,
|
||||
not even loops (with the exception of `MmapPlayback::write` that loops over samples to write).
|
||||
* Possibility to allow Send + Sync for structs
|
||||
* It's a fun experiment and an interesting deep dive into how alsa-lib does things.
|
||||
|
||||
Note: Not all sound card drivers support this direct method of communication; although almost all
|
||||
modern/common ones do. It only works with hardware devices though (such as "hw:xxx" device strings),
|
||||
don't expect it to work with, e g, the PulseAudio plugin or so.
|
||||
|
||||
For an example of how to use this mode, look in the "synth-example" directory.
|
||||
*/
|
||||
|
||||
use libc;
|
||||
use core::{mem, ptr, fmt, cmp};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::{pcm, PollDescriptors, Direction};
|
||||
use crate::pcm::Frames;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use super::ffi::*;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
type RawFd = std::os::unix::io::RawFd;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
type RawFd = core::ffi::c_int;
|
||||
|
||||
/// Read PCM status via a simple kernel syscall, bypassing alsa-lib.
|
||||
///
|
||||
/// If Status is not available on your architecture, this is the second best option.
|
||||
#[derive(Debug)]
|
||||
pub struct SyncPtrStatus(snd_pcm_mmap_status);
|
||||
|
||||
impl SyncPtrStatus {
|
||||
/// Executes sync_ptr syscall.
|
||||
///
|
||||
/// Unsafe because
|
||||
/// - setting appl_ptr and avail_min might make alsa-lib confused
|
||||
/// - no check that the fd is really a PCM
|
||||
pub unsafe fn sync_ptr(fd: RawFd, hwsync: bool, appl_ptr: Option<pcm::Frames>, avail_min: Option<pcm::Frames>) -> Result<Self> {
|
||||
let mut data = snd_pcm_sync_ptr {
|
||||
flags: (if hwsync { SNDRV_PCM_SYNC_PTR_HWSYNC } else { 0 }) +
|
||||
(if appl_ptr.is_some() { SNDRV_PCM_SYNC_PTR_APPL } else { 0 }) +
|
||||
(if avail_min.is_some() { SNDRV_PCM_SYNC_PTR_AVAIL_MIN } else { 0 }),
|
||||
c: snd_pcm_mmap_control_r {
|
||||
control: snd_pcm_mmap_control {
|
||||
appl_ptr: appl_ptr.unwrap_or(0) as snd_pcm_uframes_t,
|
||||
avail_min: avail_min.unwrap_or(0) as snd_pcm_uframes_t,
|
||||
}
|
||||
},
|
||||
s: mem::zeroed()
|
||||
};
|
||||
|
||||
sndrv_pcm_ioctl_sync_ptr(fd, &mut data)?;
|
||||
|
||||
let i = data.s.status.state;
|
||||
if (i >= (pcm::State::Open as snd_pcm_state_t)) && (i <= (pcm::State::Disconnected as snd_pcm_state_t)) {
|
||||
Ok(SyncPtrStatus(data.s.status))
|
||||
} else {
|
||||
Err(Error::unsupported("SNDRV_PCM_IOCTL_SYNC_PTR returned broken state"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hw_ptr(&self) -> pcm::Frames { self.0.hw_ptr as pcm::Frames }
|
||||
pub fn state(&self) -> pcm::State { unsafe { mem::transmute(self.0.state as u8) } /* valid range checked in sync_ptr */ }
|
||||
pub fn htstamp(&self) -> libc::timespec { self.0.tstamp }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Read PCM status directly from memory, bypassing alsa-lib.
|
||||
///
|
||||
/// This means that it's
|
||||
/// 1) less overhead for reading status (no syscall, no allocations, no virtual dispatch, just a read from memory)
|
||||
/// 2) Send + Sync, and
|
||||
/// 3) will only work for "hw" / "plughw" devices (not e g PulseAudio plugins), and not
|
||||
/// all of those are supported, although all common ones are (as of 2017, and a kernel from the same decade).
|
||||
/// Kernel supported archs are: x86, PowerPC, Alpha. Use "SyncPtrStatus" for other archs.
|
||||
///
|
||||
/// The values are updated every now and then by the kernel. Many functions will force an update to happen,
|
||||
/// e g `PCM::avail()` and `PCM::delay()`.
|
||||
///
|
||||
/// Note: Even if you close the original PCM device, ALSA will not actually close the device until all
|
||||
/// Status structs are dropped too.
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct Status(DriverMemory<snd_pcm_mmap_status>);
|
||||
|
||||
fn pcm_to_fd(p: &pcm::PCM) -> Result<RawFd> {
|
||||
let mut fds: [libc::pollfd; 1] = unsafe { mem::zeroed() };
|
||||
let c = PollDescriptors::fill(p, &mut fds)?;
|
||||
if c != 1 {
|
||||
return Err(Error::unsupported("snd_pcm_poll_descriptors returned wrong number of fds"))
|
||||
}
|
||||
Ok(fds[0].fd)
|
||||
}
|
||||
|
||||
impl Status {
|
||||
pub fn new(p: &pcm::PCM) -> Result<Self> { Status::from_fd(pcm_to_fd(p)?) }
|
||||
|
||||
pub fn from_fd(fd: RawFd) -> Result<Self> {
|
||||
DriverMemory::new(fd, 1, SNDRV_PCM_MMAP_OFFSET_STATUS as libc::off_t, false).map(Status)
|
||||
}
|
||||
|
||||
/// Current PCM state.
|
||||
pub fn state(&self) -> pcm::State {
|
||||
unsafe {
|
||||
let i = ptr::read_volatile(&(*self.0.ptr).state);
|
||||
assert!((i >= (pcm::State::Open as snd_pcm_state_t)) && (i <= (pcm::State::Disconnected as snd_pcm_state_t)));
|
||||
mem::transmute(i as u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of frames hardware has read or written
|
||||
///
|
||||
/// This number is updated every now and then by the kernel.
|
||||
/// Calling most functions on the PCM will update it, so will usually a period interrupt.
|
||||
/// No guarantees given.
|
||||
///
|
||||
/// This value wraps at "boundary" (a large value you can read from SwParams).
|
||||
pub fn hw_ptr(&self) -> pcm::Frames {
|
||||
unsafe {
|
||||
ptr::read_volatile(&(*self.0.ptr).hw_ptr) as pcm::Frames
|
||||
}
|
||||
}
|
||||
|
||||
/// Timestamp - fast version of alsa-lib's Status::get_htstamp
|
||||
///
|
||||
/// Note: This just reads the actual value in memory.
|
||||
/// Unfortunately, the timespec is too big to be read atomically on most archs.
|
||||
/// Therefore, this function can potentially give bogus result at times, at least in theory...?
|
||||
pub fn htstamp(&self) -> libc::timespec {
|
||||
unsafe {
|
||||
ptr::read_volatile(&(*self.0.ptr).tstamp)
|
||||
}
|
||||
}
|
||||
|
||||
/// Audio timestamp - fast version of alsa-lib's Status::get_audio_htstamp
|
||||
///
|
||||
/// Note: This just reads the actual value in memory.
|
||||
/// Unfortunately, the timespec is too big to be read atomically on most archs.
|
||||
/// Therefore, this function can potentially give bogus result at times, at least in theory...?
|
||||
pub fn audio_htstamp(&self) -> libc::timespec {
|
||||
unsafe {
|
||||
ptr::read_volatile(&(*self.0.ptr).audio_tstamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write PCM appl ptr directly, bypassing alsa-lib.
|
||||
///
|
||||
/// Provides direct access to appl ptr and avail min, without the overhead of
|
||||
/// alsa-lib or a syscall. Caveats that apply to Status applies to this struct too.
|
||||
#[derive(Debug)]
|
||||
pub struct Control(DriverMemory<snd_pcm_mmap_control>);
|
||||
|
||||
impl Control {
|
||||
pub fn new(p: &pcm::PCM) -> Result<Self> { Self::from_fd(pcm_to_fd(p)?) }
|
||||
|
||||
pub fn from_fd(fd: RawFd) -> Result<Self> {
|
||||
DriverMemory::new(fd, 1, SNDRV_PCM_MMAP_OFFSET_CONTROL as libc::off_t, true).map(Control)
|
||||
}
|
||||
|
||||
/// Read number of frames application has read or written
|
||||
///
|
||||
/// This value wraps at "boundary" (a large value you can read from SwParams).
|
||||
pub fn appl_ptr(&self) -> pcm::Frames {
|
||||
unsafe {
|
||||
ptr::read_volatile(&(*self.0.ptr).appl_ptr) as pcm::Frames
|
||||
}
|
||||
}
|
||||
|
||||
/// Set number of frames application has read or written
|
||||
///
|
||||
/// When the kernel wakes up due to a period interrupt, this value will
|
||||
/// be checked by the kernel. An XRUN will happen in case the application
|
||||
/// has not read or written enough data.
|
||||
pub fn set_appl_ptr(&self, value: pcm::Frames) {
|
||||
unsafe {
|
||||
ptr::write_volatile(&mut (*self.0.ptr).appl_ptr, value as snd_pcm_uframes_t)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read minimum number of frames in buffer in order to wakeup process
|
||||
pub fn avail_min(&self) -> pcm::Frames {
|
||||
unsafe {
|
||||
ptr::read_volatile(&(*self.0.ptr).avail_min) as pcm::Frames
|
||||
}
|
||||
}
|
||||
|
||||
/// Write minimum number of frames in buffer in order to wakeup process
|
||||
pub fn set_avail_min(&self, value: pcm::Frames) {
|
||||
unsafe {
|
||||
ptr::write_volatile(&mut (*self.0.ptr).avail_min, value as snd_pcm_uframes_t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DriverMemory<S> {
|
||||
ptr: *mut S,
|
||||
size: libc::size_t,
|
||||
}
|
||||
|
||||
impl<S> fmt::Debug for DriverMemory<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "DriverMemory({:?})", self.ptr) }
|
||||
}
|
||||
|
||||
impl<S> DriverMemory<S> {
|
||||
fn new(fd: RawFd, count: usize, offs: libc::off_t, writable: bool) -> Result<Self> {
|
||||
let mut total = count * mem::size_of::<S>();
|
||||
let ps = pagesize();
|
||||
assert!(total > 0);
|
||||
if total % ps != 0 { total += ps - total % ps };
|
||||
let flags = if writable { libc::PROT_WRITE | libc::PROT_READ } else { libc::PROT_READ };
|
||||
let p = unsafe { libc::mmap(ptr::null_mut(), total, flags, libc::MAP_FILE | libc::MAP_SHARED, fd, offs) };
|
||||
if p.is_null() || p == libc::MAP_FAILED {
|
||||
Err(Error::last("mmap (of driver memory)"))
|
||||
} else {
|
||||
Ok(DriverMemory { ptr: p as *mut S, size: total })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<S> Send for DriverMemory<S> {}
|
||||
unsafe impl<S> Sync for DriverMemory<S> {}
|
||||
|
||||
impl<S> Drop for DriverMemory<S> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {{ libc::munmap(self.ptr as *mut libc::c_void, self.size); } }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SampleData<S> {
|
||||
mem: DriverMemory<S>,
|
||||
frames: pcm::Frames,
|
||||
channels: u32,
|
||||
}
|
||||
|
||||
impl<S> SampleData<S> {
|
||||
pub fn new(p: &pcm::PCM) -> Result<Self> {
|
||||
let params = p.hw_params_current()?;
|
||||
let bufsize = params.get_buffer_size()?;
|
||||
let channels = params.get_channels()?;
|
||||
if params.get_access()? != pcm::Access::MMapInterleaved {
|
||||
return Err(Error::unsupported("Not MMAP interleaved data"))
|
||||
}
|
||||
|
||||
let fd = pcm_to_fd(p)?;
|
||||
let info = unsafe {
|
||||
let mut info: snd_pcm_channel_info = mem::zeroed();
|
||||
sndrv_pcm_ioctl_channel_info(fd, &mut info)?;
|
||||
info
|
||||
};
|
||||
// println!("{:?}", info);
|
||||
if (info.step != channels * mem::size_of::<S>() as u32 * 8) || (info.first != 0) {
|
||||
return Err(Error::unsupported("MMAP data size mismatch"))
|
||||
}
|
||||
Ok(SampleData {
|
||||
mem: DriverMemory::new(fd, (bufsize as usize) * (channels as usize), info.offset as libc::off_t, true)?,
|
||||
frames: bufsize,
|
||||
channels,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Dummy trait for better generics
|
||||
pub trait MmapDir: fmt::Debug {
|
||||
const DIR: Direction;
|
||||
fn avail(hwptr: Frames, applptr: Frames, buffersize: Frames, boundary: Frames) -> Frames;
|
||||
}
|
||||
|
||||
/// Dummy struct for better generics
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Playback;
|
||||
|
||||
impl MmapDir for Playback {
|
||||
const DIR: Direction = Direction::Playback;
|
||||
#[inline]
|
||||
fn avail(hwptr: Frames, applptr: Frames, buffersize: Frames, boundary: Frames) -> Frames {
|
||||
let r = hwptr.wrapping_add(buffersize).wrapping_sub(applptr);
|
||||
let r = if r < 0 { r.wrapping_add(boundary) } else { r };
|
||||
if r as usize >= boundary as usize { r.wrapping_sub(boundary) } else { r }
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy struct for better generics
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Capture;
|
||||
|
||||
impl MmapDir for Capture {
|
||||
const DIR: Direction = Direction::Capture;
|
||||
#[inline]
|
||||
fn avail(hwptr: Frames, applptr: Frames, _buffersize: Frames, boundary: Frames) -> Frames {
|
||||
let r = hwptr.wrapping_sub(applptr);
|
||||
if r < 0 { r.wrapping_add(boundary) } else { r }
|
||||
}
|
||||
}
|
||||
|
||||
pub type MmapPlayback<S> = MmapIO<S, Playback>;
|
||||
|
||||
pub type MmapCapture<S> = MmapIO<S, Capture>;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Struct containing direct I/O functions shared between playback and capture.
|
||||
pub struct MmapIO<S, D> {
|
||||
data: SampleData<S>,
|
||||
c: Control,
|
||||
ss: Status,
|
||||
bound: Frames,
|
||||
dir: PhantomData<*const D>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
/// A raw pointer to samples, and the amount of samples readable or writable.
|
||||
pub struct RawSamples<S> {
|
||||
pub ptr: *mut S,
|
||||
pub frames: Frames,
|
||||
pub channels: u32,
|
||||
}
|
||||
|
||||
impl<S> RawSamples<S> {
|
||||
#[inline]
|
||||
/// Returns `frames` * `channels`, i e the amount of samples (of type `S`) that can be read/written.
|
||||
pub fn samples(&self) -> isize { self.frames as isize * (self.channels as isize) }
|
||||
|
||||
/// Writes samples from an iterator.
|
||||
///
|
||||
/// Returns true if iterator was depleted, and the number of samples written.
|
||||
/// This is just raw read/write of memory.
|
||||
pub unsafe fn write_samples<I: Iterator<Item=S>>(&self, i: &mut I) -> (bool, isize) {
|
||||
let mut z = 0;
|
||||
let max_samples = self.samples();
|
||||
while z < max_samples {
|
||||
let b = if let Some(b) = i.next() { b } else { return (true, z) };
|
||||
ptr::write_volatile(self.ptr.offset(z), b);
|
||||
z += 1;
|
||||
};
|
||||
(false, z)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<S, D: MmapDir> MmapIO<S, D> {
|
||||
fn new(p: &pcm::PCM) -> Result<Self> {
|
||||
if p.info()?.get_stream() != D::DIR {
|
||||
return Err(Error::unsupported("Wrong direction"));
|
||||
}
|
||||
let boundary = p.sw_params_current()?.get_boundary()?;
|
||||
Ok(MmapIO {
|
||||
data: SampleData::new(p)?,
|
||||
c: Control::new(p)?,
|
||||
ss: Status::new(p)?,
|
||||
bound: boundary,
|
||||
dir: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub (crate) fn new_mmap<S, D: MmapDir>(p: &pcm::PCM) -> Result<MmapIO<S, D>> { MmapIO::new(p) }
|
||||
|
||||
impl<S, D: MmapDir> MmapIO<S, D> {
|
||||
/// Read current status
|
||||
pub fn status(&self) -> &Status { &self.ss }
|
||||
|
||||
/// Read current number of frames committed by application
|
||||
///
|
||||
/// This number wraps at 'boundary'.
|
||||
#[inline]
|
||||
pub fn appl_ptr(&self) -> Frames { self.c.appl_ptr() }
|
||||
|
||||
/// Read current number of frames read / written by hardware
|
||||
///
|
||||
/// This number wraps at 'boundary'.
|
||||
#[inline]
|
||||
pub fn hw_ptr(&self) -> Frames { self.ss.hw_ptr() }
|
||||
|
||||
/// The number at which hw_ptr and appl_ptr wraps.
|
||||
#[inline]
|
||||
pub fn boundary(&self) -> Frames { self.bound }
|
||||
|
||||
/// Total number of frames in hardware buffer
|
||||
#[inline]
|
||||
pub fn buffer_size(&self) -> Frames { self.data.frames }
|
||||
|
||||
/// Number of channels in stream
|
||||
#[inline]
|
||||
pub fn channels(&self) -> u32 { self.data.channels }
|
||||
|
||||
/// Notifies the kernel that frames have now been read / written by the application
|
||||
///
|
||||
/// This will allow the kernel to write new data into this part of the buffer.
|
||||
pub fn commit(&self, v: Frames) {
|
||||
let mut z = self.appl_ptr() + v;
|
||||
if z + v >= self.boundary() { z -= self.boundary() };
|
||||
self.c.set_appl_ptr(z)
|
||||
}
|
||||
|
||||
/// Number of frames available to read / write.
|
||||
///
|
||||
/// In case of an underrun, this value might be bigger than the buffer size.
|
||||
pub fn avail(&self) -> Frames { D::avail(self.hw_ptr(), self.appl_ptr(), self.buffer_size(), self.boundary()) }
|
||||
|
||||
/// Returns raw pointers to data to read / write.
|
||||
///
|
||||
/// Use this if you want to read/write data yourself (instead of using iterators). If you do,
|
||||
/// using `write_volatile` or `read_volatile` is recommended, since it's DMA memory and can
|
||||
/// change at any time.
|
||||
///
|
||||
/// Since this is a ring buffer, there might be more data to read/write in the beginning
|
||||
/// of the buffer as well. If so this is returned as the second return value.
|
||||
pub fn data_ptr(&self) -> (RawSamples<S>, Option<RawSamples<S>>) {
|
||||
let (hwptr, applptr) = (self.hw_ptr(), self.appl_ptr());
|
||||
let c = self.channels();
|
||||
let bufsize = self.buffer_size();
|
||||
|
||||
// These formulas mostly mimic the behaviour of
|
||||
// snd_pcm_mmap_begin (in alsa-lib/src/pcm/pcm.c).
|
||||
let offs = applptr % bufsize;
|
||||
let mut a = D::avail(hwptr, applptr, bufsize, self.boundary());
|
||||
a = cmp::min(a, bufsize);
|
||||
let b = bufsize - offs;
|
||||
let more_data = if b < a {
|
||||
let z = a - b;
|
||||
a = b;
|
||||
Some( RawSamples { ptr: self.data.mem.ptr, frames: z, channels: c })
|
||||
} else { None };
|
||||
|
||||
let p = unsafe { self.data.mem.ptr.offset(offs as isize * self.data.channels as isize) };
|
||||
(RawSamples { ptr: p, frames: a, channels: c }, more_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> MmapPlayback<S> {
|
||||
/// Write samples to the kernel ringbuffer.
|
||||
pub fn write<I: Iterator<Item=S>>(&mut self, i: &mut I) -> Frames {
|
||||
let (data, more_data) = self.data_ptr();
|
||||
let (iter_end, samples) = unsafe { data.write_samples(i) };
|
||||
let mut z = samples / data.channels as isize;
|
||||
if !iter_end {
|
||||
if let Some(data2) = more_data {
|
||||
let (_, samples2) = unsafe { data2.write_samples(i) };
|
||||
z += samples2 / data2.channels as isize;
|
||||
}
|
||||
}
|
||||
let z = z as Frames;
|
||||
self.commit(z);
|
||||
z
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> MmapCapture<S> {
|
||||
/// Read samples from the kernel ringbuffer.
|
||||
///
|
||||
/// When the iterator is dropped or depleted, the read samples will be committed, i e,
|
||||
/// the kernel can then write data to the location again. So do this ASAP.
|
||||
pub fn iter(&mut self) -> CaptureIter<'_, S> {
|
||||
let (data, more_data) = self.data_ptr();
|
||||
CaptureIter {
|
||||
m: self,
|
||||
samples: data,
|
||||
p_offs: 0,
|
||||
read_samples: 0,
|
||||
next_p: more_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over captured samples
|
||||
#[derive(Debug)]
|
||||
pub struct CaptureIter<'a, S: 'static> {
|
||||
m: &'a MmapCapture<S>,
|
||||
samples: RawSamples<S>,
|
||||
p_offs: isize,
|
||||
read_samples: isize,
|
||||
next_p: Option<RawSamples<S>>,
|
||||
}
|
||||
|
||||
impl<'a, S: 'static + Copy> CaptureIter<'a, S> {
|
||||
fn handle_max(&mut self) {
|
||||
self.p_offs = 0;
|
||||
if let Some(p2) = self.next_p.take() {
|
||||
self.samples = p2;
|
||||
} else {
|
||||
self.m.commit((self.read_samples / self.samples.channels as isize) as Frames);
|
||||
self.read_samples = 0;
|
||||
self.samples.frames = 0; // Shortcut to "None" in case anyone calls us again
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'static + Copy> Iterator for CaptureIter<'a, S> {
|
||||
type Item = S;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.p_offs >= self.samples.samples() {
|
||||
self.handle_max();
|
||||
if self.samples.frames <= 0 { return None; }
|
||||
}
|
||||
let s = unsafe { ptr::read_volatile(self.samples.ptr.offset(self.p_offs)) };
|
||||
self.p_offs += 1;
|
||||
self.read_samples += 1;
|
||||
Some(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'static> Drop for CaptureIter<'a, S> {
|
||||
fn drop(&mut self) {
|
||||
self.m.commit((self.read_samples / self.m.data.channels as isize) as Frames);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
#[ignore] // Not everyone has a recording device on plughw:1. So let's ignore this test by default.
|
||||
fn record_from_plughw_rw() {
|
||||
extern crate std;
|
||||
use crate::pcm::*;
|
||||
use crate::{ValueOr, Direction};
|
||||
use ::alloc::ffi::CString;
|
||||
let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Capture, false).unwrap();
|
||||
let ss = self::Status::new(&pcm).unwrap();
|
||||
let c = self::Control::new(&pcm).unwrap();
|
||||
let hwp = HwParams::any(&pcm).unwrap();
|
||||
hwp.set_channels(2).unwrap();
|
||||
hwp.set_rate(44100, ValueOr::Nearest).unwrap();
|
||||
hwp.set_format(Format::s16()).unwrap();
|
||||
hwp.set_access(Access::RWInterleaved).unwrap();
|
||||
pcm.hw_params(&hwp).unwrap();
|
||||
|
||||
{
|
||||
let swp = pcm.sw_params_current().unwrap();
|
||||
swp.set_tstamp_mode(true).unwrap();
|
||||
pcm.sw_params(&swp).unwrap();
|
||||
}
|
||||
assert_eq!(ss.state(), State::Prepared);
|
||||
pcm.start().unwrap();
|
||||
assert_eq!(c.appl_ptr(), 0);
|
||||
std::println!("{:?}, {:?}", ss, c);
|
||||
let mut buf = [0i16; 512*2];
|
||||
assert_eq!(pcm.io_i16().unwrap().readi(&mut buf).unwrap(), 512);
|
||||
assert_eq!(c.appl_ptr(), 512);
|
||||
|
||||
assert_eq!(ss.state(), State::Running);
|
||||
assert!(ss.hw_ptr() >= 512);
|
||||
let t2 = ss.htstamp();
|
||||
assert!(t2.tv_sec > 0 || t2.tv_nsec > 0);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
#[ignore] // Not everyone has a record device on plughw:1. So let's ignore this test by default.
|
||||
fn record_from_plughw_mmap() {
|
||||
extern crate std;
|
||||
use crate::pcm::*;
|
||||
use crate::{ValueOr, Direction};
|
||||
use ::alloc::ffi::CString;
|
||||
use ::alloc::vec::Vec;
|
||||
use std::{thread, time, println};
|
||||
|
||||
let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Capture, false).unwrap();
|
||||
let hwp = HwParams::any(&pcm).unwrap();
|
||||
hwp.set_channels(2).unwrap();
|
||||
hwp.set_rate(44100, ValueOr::Nearest).unwrap();
|
||||
hwp.set_format(Format::s16()).unwrap();
|
||||
hwp.set_access(Access::MMapInterleaved).unwrap();
|
||||
pcm.hw_params(&hwp).unwrap();
|
||||
|
||||
let ss = unsafe { SyncPtrStatus::sync_ptr(pcm_to_fd(&pcm).unwrap(), false, None, None).unwrap() };
|
||||
assert_eq!(ss.state(), State::Prepared);
|
||||
|
||||
let mut m = pcm.direct_mmap_capture::<i16>().unwrap();
|
||||
|
||||
assert_eq!(m.status().state(), State::Prepared);
|
||||
assert_eq!(m.appl_ptr(), 0);
|
||||
assert_eq!(m.hw_ptr(), 0);
|
||||
|
||||
|
||||
println!("{:?}", m);
|
||||
|
||||
let now = time::Instant::now();
|
||||
pcm.start().unwrap();
|
||||
while m.avail() < 256 { thread::sleep(time::Duration::from_millis(1)) };
|
||||
assert!(now.elapsed() >= time::Duration::from_millis(256 * 1000 / 44100));
|
||||
let (ptr1, md) = m.data_ptr();
|
||||
assert_eq!(ptr1.channels, 2);
|
||||
assert!(ptr1.frames >= 256);
|
||||
assert!(md.is_none());
|
||||
println!("Has {:?} frames at {:?} in {:?}", m.avail(), ptr1.ptr, now.elapsed());
|
||||
let samples: Vec<i16> = m.iter().collect();
|
||||
assert!(samples.len() >= ptr1.frames as usize * 2);
|
||||
println!("Collected {} samples", samples.len());
|
||||
let (ptr2, _md) = m.data_ptr();
|
||||
assert!(unsafe { ptr1.ptr.offset(256 * 2) } <= ptr2.ptr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn playback_to_plughw_mmap() {
|
||||
extern crate std;
|
||||
use crate::pcm::*;
|
||||
use crate::{ValueOr, Direction};
|
||||
use ::alloc::ffi::CString;
|
||||
|
||||
let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Playback, false).unwrap();
|
||||
let hwp = HwParams::any(&pcm).unwrap();
|
||||
hwp.set_channels(2).unwrap();
|
||||
hwp.set_rate(44100, ValueOr::Nearest).unwrap();
|
||||
hwp.set_format(Format::s16()).unwrap();
|
||||
hwp.set_access(Access::MMapInterleaved).unwrap();
|
||||
pcm.hw_params(&hwp).unwrap();
|
||||
let mut m = pcm.direct_mmap_playback::<i16>().unwrap();
|
||||
|
||||
assert_eq!(m.status().state(), State::Prepared);
|
||||
assert_eq!(m.appl_ptr(), 0);
|
||||
assert_eq!(m.hw_ptr(), 0);
|
||||
|
||||
std::println!("{:?}", m);
|
||||
let mut i = (0..(m.buffer_size() * 2)).map(|i|
|
||||
(((i / 2) as f32 * 2.0 * std::f32::consts::PI / 128.0).sin() * 8192.0) as i16);
|
||||
m.write(&mut i);
|
||||
assert_eq!(m.appl_ptr(), m.buffer_size());
|
||||
|
||||
pcm.start().unwrap();
|
||||
pcm.drain().unwrap();
|
||||
assert_eq!(m.appl_ptr(), m.buffer_size());
|
||||
assert!(m.hw_ptr() >= m.buffer_size());
|
||||
}
|
||||
232
vendor/alsa/src/error.rs
vendored
Normal file
232
vendor/alsa/src/error.rs
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
#![macro_use]
|
||||
|
||||
use libc::{c_char, c_int, c_void, free};
|
||||
use core::error::Error as StdError;
|
||||
use core::ffi::CStr;
|
||||
use core::{fmt, str};
|
||||
use ::alloc::string::{String, ToString};
|
||||
|
||||
/// ALSA error
|
||||
///
|
||||
/// Most ALSA functions can return a negative error code.
|
||||
/// If so, then that error code is wrapped into this `Error` struct.
|
||||
/// An Error is also returned in case ALSA returns a string that
|
||||
/// cannot be translated into Rust's UTF-8 strings.
|
||||
#[derive(Clone, PartialEq, Copy)]
|
||||
pub struct Error(&'static str, c_int);
|
||||
|
||||
pub type Result<T> = ::core::result::Result<T, Error>;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
extern "C" {
|
||||
pub(crate) static errno: c_int;
|
||||
}
|
||||
|
||||
macro_rules! acheck {
|
||||
($f: ident ( $($x: expr),* ) ) => {{
|
||||
let r = unsafe { alsa::$f( $($x),* ) };
|
||||
if r < 0 { Err(Error::new(stringify!($f), -r as ::libc::c_int)) }
|
||||
else { Ok(r) }
|
||||
}}
|
||||
}
|
||||
|
||||
pub fn from_const<'a>(func: &'static str, s: *const c_char) -> Result<&'a str> {
|
||||
if s.is_null() {
|
||||
return Err(invalid_str(func));
|
||||
};
|
||||
let cc = unsafe { CStr::from_ptr(s) };
|
||||
str::from_utf8(cc.to_bytes()).map_err(|_| invalid_str(func))
|
||||
}
|
||||
|
||||
pub fn from_alloc(func: &'static str, s: *mut c_char) -> Result<String> {
|
||||
if s.is_null() {
|
||||
return Err(invalid_str(func));
|
||||
};
|
||||
let c = unsafe { CStr::from_ptr(s) };
|
||||
let ss = str::from_utf8(c.to_bytes())
|
||||
.map_err(|_| {
|
||||
unsafe {
|
||||
free(s as *mut c_void);
|
||||
}
|
||||
invalid_str(func)
|
||||
})?
|
||||
.to_string();
|
||||
unsafe {
|
||||
free(s as *mut c_void);
|
||||
}
|
||||
Ok(ss)
|
||||
}
|
||||
|
||||
pub fn from_code(func: &'static str, r: c_int) -> Result<c_int> {
|
||||
if r < 0 {
|
||||
Err(Error::new(func, r))
|
||||
} else {
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new(func: &'static str, res: c_int) -> Error {
|
||||
Self(func, res)
|
||||
}
|
||||
|
||||
pub fn last(func: &'static str) -> Error {
|
||||
#[cfg(feature = "std")] {
|
||||
Self(
|
||||
func,
|
||||
std::io::Error::last_os_error().raw_os_error().unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
#[cfg(not(feature = "std"))] {
|
||||
Self(
|
||||
func,
|
||||
unsafe {errno},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsupported(func: &'static str) -> Error {
|
||||
Error(func, libc::ENOTSUP)
|
||||
}
|
||||
|
||||
/// The function which failed.
|
||||
pub fn func(&self) -> &'static str {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Underlying error
|
||||
///
|
||||
/// Match this against the re-export of `nix::Error` in this crate, not against a specific version
|
||||
/// of the nix crate. The nix crate version might be updated with minor updates of this library.
|
||||
pub fn errno(&self) -> i32 {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalid_str(func: &'static str) -> Error {
|
||||
Error(func, libc::EILSEQ)
|
||||
}
|
||||
|
||||
impl StdError for Error {
|
||||
fn description(&self) -> &str {
|
||||
"ALSA error"
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"ALSA function '{}' failed with error '{} ({})'",
|
||||
self.0,
|
||||
desc(self.1),
|
||||
self.1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// See <https://github.com/nix-rust/nix/blob/197f55b3ccbce3273bf6ce119d1a8541b5df5d66/src/errno.rs#L198>
|
||||
///
|
||||
/// Note this doesn't include the total set of possible errno variants, but they
|
||||
/// can easily be added in the future for better error messages
|
||||
fn desc(err: i32) -> &'static str {
|
||||
match err {
|
||||
libc::EPERM => "Operation not permitted",
|
||||
libc::ENOENT => "No such file or directory",
|
||||
libc::ESRCH => "No such process",
|
||||
libc::EINTR => "Interrupted system call",
|
||||
libc::EIO => "I/O error",
|
||||
libc::ENXIO => "No such device or address",
|
||||
libc::E2BIG => "Argument list too long",
|
||||
libc::ENOEXEC => "Exec format error",
|
||||
libc::EBADF => "Bad file number",
|
||||
libc::ECHILD => "No child processes",
|
||||
libc::EAGAIN => "Try again",
|
||||
libc::ENOMEM => "Out of memory",
|
||||
libc::EACCES => "Permission denied",
|
||||
libc::EFAULT => "Bad address",
|
||||
libc::ENOTBLK => "Block device required",
|
||||
libc::EBUSY => "Device or resource busy",
|
||||
libc::EEXIST => "File exists",
|
||||
libc::EXDEV => "Cross-device link",
|
||||
libc::ENODEV => "No such device",
|
||||
libc::ENOTDIR => "Not a directory",
|
||||
libc::EISDIR => "Is a directory",
|
||||
libc::EINVAL => "Invalid argument",
|
||||
libc::ENFILE => "File table overflow",
|
||||
libc::EMFILE => "Too many open files",
|
||||
libc::ENOTTY => "Not a typewriter",
|
||||
libc::ETXTBSY => "Text file busy",
|
||||
libc::EFBIG => "File too large",
|
||||
libc::ENOSPC => "No space left on device",
|
||||
libc::ESPIPE => "Illegal seek",
|
||||
libc::EROFS => "Read-only file system",
|
||||
libc::EMLINK => "Too many links",
|
||||
libc::EPIPE => "Broken pipe",
|
||||
libc::EDOM => "Math argument out of domain of func",
|
||||
libc::ERANGE => "Math result not representable",
|
||||
libc::EDEADLK => "Resource deadlock would occur",
|
||||
libc::ENAMETOOLONG => "File name too long",
|
||||
libc::ENOLCK => "No record locks available",
|
||||
libc::ENOSYS => "Function not implemented",
|
||||
libc::ENOTEMPTY => "Directory not empty",
|
||||
libc::ELOOP => "Too many symbolic links encountered",
|
||||
libc::ENOMSG => "No message of desired type",
|
||||
libc::EIDRM => "Identifier removed",
|
||||
libc::EINPROGRESS => "Operation now in progress",
|
||||
libc::EALREADY => "Operation already in progress",
|
||||
libc::ENOTSOCK => "Socket operation on non-socket",
|
||||
libc::EDESTADDRREQ => "Destination address required",
|
||||
libc::EMSGSIZE => "Message too long",
|
||||
libc::EPROTOTYPE => "Protocol wrong type for socket",
|
||||
libc::ENOPROTOOPT => "Protocol not available",
|
||||
libc::EPROTONOSUPPORT => "Protocol not supported",
|
||||
libc::ESOCKTNOSUPPORT => "Socket type not supported",
|
||||
libc::EPFNOSUPPORT => "Protocol family not supported",
|
||||
libc::EAFNOSUPPORT => "Address family not supported by protocol",
|
||||
libc::EADDRINUSE => "Address already in use",
|
||||
libc::EADDRNOTAVAIL => "Cannot assign requested address",
|
||||
libc::ENETDOWN => "Network is down",
|
||||
libc::ENETUNREACH => "Network is unreachable",
|
||||
libc::ENETRESET => "Network dropped connection because of reset",
|
||||
libc::ECONNABORTED => "Software caused connection abort",
|
||||
libc::ECONNRESET => "Connection reset by peer",
|
||||
libc::ENOBUFS => "No buffer space available",
|
||||
libc::EISCONN => "Transport endpoint is already connected",
|
||||
libc::ENOTCONN => "Transport endpoint is not connected",
|
||||
libc::ESHUTDOWN => "Cannot send after transport endpoint shutdown",
|
||||
libc::ETOOMANYREFS => "Too many references: cannot splice",
|
||||
libc::ETIMEDOUT => "Connection timed out",
|
||||
libc::ECONNREFUSED => "Connection refused",
|
||||
libc::EHOSTDOWN => "Host is down",
|
||||
libc::EHOSTUNREACH => "No route to host",
|
||||
libc::ENOTSUP => "Operation not supported",
|
||||
_ => "Unknown errno",
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for fmt::Error {
|
||||
fn from(_: Error) -> fmt::Error {
|
||||
fmt::Error
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn broken_pcm_name() {
|
||||
use ::alloc::ffi::CString;
|
||||
let e = crate::PCM::open(
|
||||
&*CString::new("this_PCM_does_not_exist").unwrap(),
|
||||
crate::Direction::Playback,
|
||||
false,
|
||||
)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(e.func(), "snd_pcm_open");
|
||||
assert_eq!(e.errno(), libc::ENOENT);
|
||||
}
|
||||
182
vendor/alsa/src/hctl.rs
vendored
Normal file
182
vendor/alsa/src/hctl.rs
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
//! HCtl API - for mixer control and jack detection
|
||||
//!
|
||||
//! # Example
|
||||
//! Print all jacks and their status
|
||||
//!
|
||||
//! ```
|
||||
//! for a in ::alsa::card::Iter::new().map(|x| x.unwrap()) {
|
||||
//! use std::ffi::CString;
|
||||
//! use alsa::hctl::HCtl;
|
||||
//! let h = HCtl::open(&CString::new(format!("hw:{}", a.get_index())).unwrap(), false).unwrap();
|
||||
//! h.load().unwrap();
|
||||
//! for b in h.elem_iter() {
|
||||
//! use alsa::ctl::ElemIface;
|
||||
//! let id = b.get_id().unwrap();
|
||||
//! if id.get_interface() != ElemIface::Card { continue; }
|
||||
//! let name = id.get_name().unwrap();
|
||||
//! if !name.ends_with(" Jack") { continue; }
|
||||
//! if name.ends_with(" Phantom Jack") {
|
||||
//! println!("{} is always present", &name[..name.len()-13])
|
||||
//! }
|
||||
//! else { println!("{} is {}", &name[..name.len()-5],
|
||||
//! if b.read().unwrap().get_boolean(0).unwrap() { "plugged in" } else { "unplugged" })
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![allow(dead_code)]
|
||||
/* There is a "field is never read" warning for the ElemIter struct. We still need to hold on
|
||||
to the HCtl pointer somehow; to guarantee that the HCtl does not go out of scope while we use
|
||||
ElemIter, which would make the snd_hctl_elem_t pointer invalid. Hence the decision to allow dead code here.
|
||||
I suppose there is a better solution for this but I'm not sure how.
|
||||
*/
|
||||
|
||||
|
||||
use crate::{alsa, Card};
|
||||
use core::ffi::CStr;
|
||||
use ::alloc::ffi::CString;
|
||||
use super::error::*;
|
||||
use core::ptr;
|
||||
use super::{ctl_int, poll};
|
||||
use libc::{c_short, c_uint, c_int, pollfd};
|
||||
|
||||
|
||||
/// [snd_hctl_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___h_control.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct HCtl(*mut alsa::snd_hctl_t);
|
||||
|
||||
unsafe impl Send for HCtl {}
|
||||
|
||||
impl Drop for HCtl {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_hctl_close(self.0) }; }
|
||||
}
|
||||
|
||||
impl HCtl {
|
||||
/// Wrapper around open that takes a &str instead of a &CStr
|
||||
pub fn new(c: &str, nonblock: bool) -> Result<HCtl> {
|
||||
Self::open(&CString::new(c).unwrap(), nonblock)
|
||||
}
|
||||
|
||||
/// Open does not support async mode (it's not very Rustic anyway)
|
||||
/// Note: You probably want to call `load` afterwards.
|
||||
pub fn open(c: &CStr, nonblock: bool) -> Result<HCtl> {
|
||||
let mut r = ptr::null_mut();
|
||||
let flags = if nonblock { 1 } else { 0 }; // FIXME: alsa::SND_CTL_NONBLOCK does not exist in alsa-sys
|
||||
acheck!(snd_hctl_open(&mut r, c.as_ptr(), flags))
|
||||
.map(|_| HCtl(r))
|
||||
}
|
||||
|
||||
/// Wrapper around open. You probably want to call `load` afterwards.
|
||||
pub fn from_card(c: &Card, nonblock: bool) -> Result<HCtl> {
|
||||
let s = ::alloc::format!("hw:{}", c.get_index());
|
||||
HCtl::new(&s, nonblock)
|
||||
}
|
||||
|
||||
pub fn load(&self) -> Result<()> { acheck!(snd_hctl_load(self.0)).map(|_| ()) }
|
||||
|
||||
pub fn elem_iter(&self) -> ElemIter<'_> { ElemIter(self, ptr::null_mut()) }
|
||||
|
||||
pub fn find_elem(&self, id: &ctl_int::ElemId) -> Option<Elem<'_>> {
|
||||
let p = unsafe { alsa::snd_hctl_find_elem(self.0, ctl_int::elem_id_ptr(id)) };
|
||||
if p.is_null() { None } else { Some(Elem(self, p)) }
|
||||
}
|
||||
|
||||
pub fn handle_events(&self) -> Result<u32> {
|
||||
acheck!(snd_hctl_handle_events(self.0)).map(|x| x as u32)
|
||||
}
|
||||
|
||||
pub fn wait(&self, timeout_ms: Option<u32>) -> Result<bool> {
|
||||
acheck!(snd_hctl_wait(self.0, timeout_ms.map(|x| x as c_int).unwrap_or(-1))).map(|i| i == 1) }
|
||||
}
|
||||
|
||||
impl poll::Descriptors for HCtl {
|
||||
fn count(&self) -> usize {
|
||||
unsafe { alsa::snd_hctl_poll_descriptors_count(self.0) as usize }
|
||||
}
|
||||
fn fill(&self, p: &mut [pollfd]) -> Result<usize> {
|
||||
let z = unsafe { alsa::snd_hctl_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) };
|
||||
from_code("snd_hctl_poll_descriptors", z).map(|_| z as usize)
|
||||
}
|
||||
fn revents(&self, p: &[pollfd]) -> Result<poll::Flags> {
|
||||
let mut r = 0;
|
||||
let z = unsafe { alsa::snd_hctl_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) };
|
||||
from_code("snd_hctl_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short))
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over elements for a `HCtl`
|
||||
#[derive(Debug)]
|
||||
pub struct ElemIter<'a>(&'a HCtl, *mut alsa::snd_hctl_elem_t);
|
||||
|
||||
impl<'a> Iterator for ElemIter<'a> {
|
||||
type Item = Elem<'a>;
|
||||
fn next(&mut self) -> Option<Elem<'a>> {
|
||||
self.1 = if self.1.is_null() { unsafe { alsa::snd_hctl_first_elem((self.0).0) }}
|
||||
else { unsafe { alsa::snd_hctl_elem_next(self.1) }};
|
||||
if self.1.is_null() { None }
|
||||
else { Some(Elem(self.0, self.1)) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// [snd_hctl_elem_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___h_control.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct Elem<'a>(&'a HCtl, *mut alsa::snd_hctl_elem_t);
|
||||
|
||||
impl<'a> Elem<'a> {
|
||||
pub fn get_id(&self) -> Result<ctl_int::ElemId> {
|
||||
let v = ctl_int::elem_id_new()?;
|
||||
unsafe { alsa::snd_hctl_elem_get_id(self.1, ctl_int::elem_id_ptr(&v)) };
|
||||
Ok(v)
|
||||
}
|
||||
pub fn info(&self) -> Result<ctl_int::ElemInfo> {
|
||||
let v = ctl_int::elem_info_new()?;
|
||||
acheck!(snd_hctl_elem_info(self.1, ctl_int::elem_info_ptr(&v))).map(|_| v)
|
||||
}
|
||||
pub fn read(&self) -> Result<ctl_int::ElemValue> {
|
||||
let i = self.info()?;
|
||||
let v = ctl_int::elem_value_new(i.get_type(), i.get_count())?;
|
||||
acheck!(snd_hctl_elem_read(self.1, ctl_int::elem_value_ptr(&v))).map(|_| v)
|
||||
}
|
||||
|
||||
pub fn write(&self, v: &ctl_int::ElemValue) -> Result<bool> {
|
||||
acheck!(snd_hctl_elem_write(self.1, ctl_int::elem_value_ptr(v))).map(|e| e > 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_hctls() {
|
||||
extern crate std;
|
||||
for a in super::card::Iter::new().map(|x| x.unwrap()) {
|
||||
use ::alloc::ffi::CString;
|
||||
let h = HCtl::open(&CString::new(::alloc::format!("hw:{}", a.get_index())).unwrap(), false).unwrap();
|
||||
h.load().unwrap();
|
||||
std::println!("Card {}:", a.get_name().unwrap());
|
||||
for b in h.elem_iter() {
|
||||
std::println!(" {:?} - {:?}", b.get_id().unwrap(), b.read().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_jacks() {
|
||||
extern crate std;
|
||||
for a in super::card::Iter::new().map(|x| x.unwrap()) {
|
||||
use ::alloc::ffi::CString;
|
||||
let h = HCtl::open(&CString::new(::alloc::format!("hw:{}", a.get_index())).unwrap(), false).unwrap();
|
||||
h.load().unwrap();
|
||||
for b in h.elem_iter() {
|
||||
let id = b.get_id().unwrap();
|
||||
if id.get_interface() != super::ctl_int::ElemIface::Card { continue; }
|
||||
let name = id.get_name().unwrap();
|
||||
if !name.ends_with(" Jack") { continue; }
|
||||
if name.ends_with(" Phantom Jack") {
|
||||
std::println!("{} is always present", &name[..name.len()-13])
|
||||
}
|
||||
else { std::println!("{} is {}", &name[..name.len()-5],
|
||||
if b.read().unwrap().get_boolean(0).unwrap() { "plugged in" } else { "unplugged" })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
vendor/alsa/src/io.rs
vendored
Normal file
90
vendor/alsa/src/io.rs
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
use crate::alsa;
|
||||
use super::error::*;
|
||||
use core::{slice, ptr, fmt};
|
||||
use core::cell::RefCell;
|
||||
use ::alloc::rc::Rc;
|
||||
use libc::{c_char, c_int};
|
||||
|
||||
/// [snd_output_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___output.html) wrapper
|
||||
pub struct Output(*mut alsa::snd_output_t);
|
||||
|
||||
unsafe impl Send for Output {}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
std::thread_local! {
|
||||
static ERROR_OUTPUT: RefCell<Option<Rc<RefCell<Output>>>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
impl Drop for Output {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_output_close(self.0) }; }
|
||||
}
|
||||
|
||||
impl Output {
|
||||
|
||||
pub fn buffer_open() -> Result<Output> {
|
||||
let mut q = ptr::null_mut();
|
||||
acheck!(snd_output_buffer_open(&mut q)).map(|_| Output(q))
|
||||
}
|
||||
|
||||
pub fn buffer_string<T, F: FnOnce(&[u8]) -> T>(&self, f: F) -> T {
|
||||
let b = unsafe {
|
||||
let mut q = ptr::null_mut();
|
||||
let s = alsa::snd_output_buffer_string(self.0, &mut q);
|
||||
if s == 0 { &[] } else { slice::from_raw_parts(q as *const u8, s as usize) }
|
||||
};
|
||||
f(b)
|
||||
}
|
||||
|
||||
|
||||
/// Installs a thread local error handler.
|
||||
///
|
||||
/// Sometimes alsa-lib writes to stderr, but if you prefer, you can write it here instead.
|
||||
/// Should you wish to empty the buffer; just call local_error_handler again and drop the old instance.
|
||||
///
|
||||
/// This is not available in `no-std` environments, because we use thread_local variables.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn local_error_handler() -> Result<Rc<RefCell<Output>>> {
|
||||
let output = Output::buffer_open()?;
|
||||
let r = Rc::new(RefCell::new(output));
|
||||
ERROR_OUTPUT.with_borrow_mut(|e| *e = Some(r.clone()));
|
||||
unsafe { alsa::snd_lib_error_set_local(Some(our_error_handler)); }
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Output {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Output(")?;
|
||||
fmt::Display::fmt(self, f)?;
|
||||
write!(f, ")")
|
||||
/* self.buffer_string(|b| f.write_str(try!(str::from_utf8(b).map_err(|_| fmt::Error)))) */
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Output {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.buffer_string(|b| {
|
||||
let s = ::alloc::string::String::from_utf8_lossy(b);
|
||||
f.write_str(&*s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output_handle(o: &Output) -> *mut alsa::snd_output_t { o.0 }
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
unsafe extern "C" fn our_error_handler(_file: *const c_char,
|
||||
_line: c_int,
|
||||
func: *const c_char,
|
||||
_err: c_int,
|
||||
fmt: *const c_char,
|
||||
arg: *mut alsa::__va_list_tag,
|
||||
) {
|
||||
ERROR_OUTPUT.with_borrow(|e| {
|
||||
let b = e.as_ref().expect("ERROR_OUTPUT not set").borrow_mut();
|
||||
alsa::snd_output_puts(b.0, func);
|
||||
alsa::snd_output_puts(b.0, c": ".as_ptr());
|
||||
alsa::snd_output_vprintf(b.0, fmt, arg);
|
||||
alsa::snd_output_putc(b.0, '\n' as i32);
|
||||
})
|
||||
}
|
||||
140
vendor/alsa/src/lib.rs
vendored
Normal file
140
vendor/alsa/src/lib.rs
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
//! Thin but safe wrappers for [ALSA](https://alsa-project.org).
|
||||
//!
|
||||
//! [GitHub repo](https://github.com/diwic/alsa-rs)
|
||||
//!
|
||||
//! [Crates.io](https://crates.io/crates/alsa)
|
||||
//!
|
||||
//! This ALSA API wrapper/binding is WIP - the ALSA API is huge, and new
|
||||
//! functions and structs might be added as requested.
|
||||
//!
|
||||
//! Most functions map 1-to-1 to alsa-lib functions, e g, `ctl::CardInfo::get_id()` is a wrapper around
|
||||
//! `snd_ctl_card_info_get_id` and the [alsa-lib documentation](https://www.alsa-project.org/alsa-doc/alsa-lib/)
|
||||
//! can be consulted for additional information.
|
||||
//!
|
||||
//! Enjoy!
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![warn(clippy::correctness, clippy::suspicious, clippy::perf)]
|
||||
#![no_std]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
extern crate std;
|
||||
|
||||
extern crate alsa_sys as alsa;
|
||||
extern crate alloc;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
macro_rules! alsa_enum {
|
||||
($(#[$attr:meta])+ $name:ident, $static_name:ident [$count:expr], $( $a:ident = $b:ident),* ,) =>
|
||||
{
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
$(#[$attr])*
|
||||
pub enum $name {
|
||||
$(
|
||||
$a = alsa::$b as isize,
|
||||
)*
|
||||
}
|
||||
|
||||
static $static_name: [$name; $count] =
|
||||
[ $( $name::$a, )* ];
|
||||
|
||||
impl $name {
|
||||
/// Returns a slice of all possible values; useful for iteration
|
||||
pub fn all() -> &'static [$name] { &$static_name[..] }
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn from_c_int(c: ::libc::c_int, s: &'static str) -> Result<$name> {
|
||||
Self::all().iter().find(|&&x| c == x as ::libc::c_int).map(|&x| x)
|
||||
.ok_or_else(|| Error::unsupported(s))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn to_c_int(&self) -> ::libc::c_int {
|
||||
return *self as ::libc::c_int;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces constants ending with PLAYBACK/CAPTURE as well as
|
||||
/// INPUT/OUTPUT
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Direction {
|
||||
Playback,
|
||||
Capture
|
||||
}
|
||||
impl Direction {
|
||||
#[inline]
|
||||
pub fn input() -> Direction { Direction::Capture }
|
||||
#[inline]
|
||||
pub fn output() -> Direction { Direction::Playback }
|
||||
}
|
||||
|
||||
/// Used to restrict hw parameters. In case the submitted
|
||||
/// value is unavailable, in which direction should one search
|
||||
/// for available values?
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ValueOr {
|
||||
/// The value set is the submitted value, or less
|
||||
Less = -1,
|
||||
/// The value set is the submitted value, or the nearest
|
||||
Nearest = 0,
|
||||
/// The value set is the submitted value, or greater
|
||||
Greater = 1,
|
||||
}
|
||||
|
||||
/// Rounding mode (used in some mixer related calls)
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Round {
|
||||
/// Round down (towards negative infinity)
|
||||
Floor = 0,
|
||||
/// Round up (towards positive infinity)
|
||||
Ceil = 1,
|
||||
}
|
||||
|
||||
mod error;
|
||||
pub use crate::error::{Error, Result};
|
||||
|
||||
pub mod card;
|
||||
pub use crate::card::Card as Card;
|
||||
|
||||
mod ctl_int;
|
||||
pub mod ctl {
|
||||
//! Control device API
|
||||
pub use super::ctl_int::{Ctl, CardInfo, DeviceIter, ElemIface, ElemId, ElemList, ElemType, ElemValue, ElemInfo};
|
||||
}
|
||||
|
||||
pub use crate::ctl::Ctl as Ctl;
|
||||
|
||||
pub mod hctl;
|
||||
pub use crate::hctl::HCtl as HCtl;
|
||||
|
||||
pub mod pcm;
|
||||
pub use crate::pcm::PCM as PCM;
|
||||
|
||||
pub mod config;
|
||||
|
||||
pub mod rawmidi;
|
||||
pub use crate::rawmidi::Rawmidi as Rawmidi;
|
||||
|
||||
pub mod device_name;
|
||||
|
||||
pub mod poll;
|
||||
pub use crate::poll::Descriptors as PollDescriptors;
|
||||
|
||||
pub mod mixer;
|
||||
pub use crate::mixer::Mixer as Mixer;
|
||||
|
||||
pub mod seq;
|
||||
pub use crate::seq::Seq as Seq;
|
||||
|
||||
mod io;
|
||||
pub use crate::io::Output;
|
||||
|
||||
// Reexported inside PCM module
|
||||
mod chmap;
|
||||
|
||||
pub mod direct;
|
||||
679
vendor/alsa/src/mixer.rs
vendored
Normal file
679
vendor/alsa/src/mixer.rs
vendored
Normal file
@@ -0,0 +1,679 @@
|
||||
//! Mixer API - Simple Mixer API for mixer control
|
||||
//!
|
||||
use core::ffi::CStr;
|
||||
use ::alloc::ffi::CString;
|
||||
use ::alloc::string::String;
|
||||
use core::{ptr, mem, fmt, ops};
|
||||
use libc::{c_long, c_int, c_uint, c_short, pollfd};
|
||||
use crate::poll;
|
||||
|
||||
use crate::alsa;
|
||||
use super::Round;
|
||||
use super::error::*;
|
||||
|
||||
const SELEM_ID_SIZE: usize = 64;
|
||||
|
||||
/// wraps [snd_mixer_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___mixer.html)
|
||||
#[derive(Debug)]
|
||||
pub struct Mixer(*mut alsa::snd_mixer_t);
|
||||
|
||||
unsafe impl Send for Mixer {}
|
||||
|
||||
impl Mixer {
|
||||
/// Opens a mixer and attaches it to a card identified by its name (like hw:0) and loads the
|
||||
/// mixer after registering a Selem.
|
||||
pub fn new(name: &str, nonblock: bool) -> Result<Mixer> {
|
||||
let mut mixer = Mixer::open(nonblock)?;
|
||||
mixer.attach(&CString::new(name).unwrap())?;
|
||||
Selem::register(&mut mixer)?;
|
||||
mixer.load()?;
|
||||
Ok(mixer)
|
||||
}
|
||||
|
||||
/// Creates a Selem by looking for a specific selem by name given a mixer (of a card)
|
||||
pub fn find_selem(&self, id: &SelemId) -> Option<Selem<'_>> {
|
||||
let selem = unsafe { alsa::snd_mixer_find_selem(self.0, id.as_ptr()) };
|
||||
|
||||
if selem.is_null() { None }
|
||||
else { Some(Selem(Elem {handle: selem, _mixer: self})) }
|
||||
}
|
||||
|
||||
pub fn open(nonblock: bool) -> Result<Mixer> {
|
||||
let mut r = ptr::null_mut();
|
||||
let flags = if nonblock { 1 } else { 0 }; // FIXME: alsa::SND_CTL_NONBLOCK does not exist in alsa-sys
|
||||
acheck!(snd_mixer_open(&mut r, flags)).map(|_| Mixer(r))
|
||||
}
|
||||
|
||||
pub fn attach(&mut self, name: &CStr) -> Result<()> {
|
||||
acheck!(snd_mixer_attach(self.0, name.as_ptr())).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn load(&mut self) -> Result<()> {
|
||||
acheck!(snd_mixer_load(self.0)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter<'_> {
|
||||
Iter {
|
||||
last_handle: ptr::null_mut(),
|
||||
mixer: self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_events(&self) -> Result<u32> {
|
||||
acheck!(snd_mixer_handle_events(self.0)).map(|x| x as u32)
|
||||
}
|
||||
|
||||
pub fn wait(&self, timeout_ms: Option<u32>) -> Result<()> {
|
||||
acheck!(snd_mixer_wait(self.0, timeout_ms.map(|x| x as c_int).unwrap_or(-1))).map(|_| ()) }
|
||||
}
|
||||
|
||||
/// Closes mixer and frees used resources
|
||||
impl Drop for Mixer {
|
||||
fn drop(&mut self) {
|
||||
unsafe { alsa::snd_mixer_close(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl poll::Descriptors for Mixer {
|
||||
fn count(&self) -> usize {
|
||||
unsafe { alsa::snd_mixer_poll_descriptors_count(self.0) as usize }
|
||||
}
|
||||
fn fill(&self, p: &mut [pollfd]) -> Result<usize> {
|
||||
let z = unsafe { alsa::snd_mixer_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) };
|
||||
from_code("snd_mixer_poll_descriptors", z).map(|_| z as usize)
|
||||
}
|
||||
fn revents(&self, p: &[pollfd]) -> Result<poll::Flags> {
|
||||
let mut r = 0;
|
||||
let z = unsafe { alsa::snd_mixer_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) };
|
||||
from_code("snd_mixer_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Wrapper for a mB (millibel) value.
|
||||
///
|
||||
/// Despite some ALSA functions named "dB", they actually take mB values instead.
|
||||
/// This is a wrapper type to help with those calculations. Its interior is the
|
||||
/// actual mB value.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct MilliBel(pub i64);
|
||||
|
||||
impl MilliBel {
|
||||
pub fn to_db(self) -> f32 { (self.0 as f32) / 100.0 }
|
||||
pub fn from_db(db: f32) -> Self { MilliBel((db * 100.0) as i64) }
|
||||
}
|
||||
|
||||
impl ops::Deref for MilliBel {
|
||||
type Target = i64;
|
||||
fn deref(&self) -> &i64 { &self.0 }
|
||||
}
|
||||
|
||||
impl ops::Add for MilliBel {
|
||||
type Output = MilliBel;
|
||||
fn add(self, rhs: Self) -> Self { MilliBel(self.0 + rhs.0) }
|
||||
}
|
||||
|
||||
impl ops::AddAssign for MilliBel {
|
||||
fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0 }
|
||||
}
|
||||
|
||||
impl ops::Sub for MilliBel {
|
||||
type Output = MilliBel;
|
||||
fn sub(self, rhs: Self) -> Self { MilliBel(self.0 - rhs.0) }
|
||||
}
|
||||
|
||||
impl ops::SubAssign for MilliBel {
|
||||
fn sub_assign(&mut self, rhs: Self) { self.0 -= rhs.0 }
|
||||
}
|
||||
|
||||
/// Wraps [snd_mixer_elem_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___mixer.html)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Elem<'a>{
|
||||
handle: *mut alsa::snd_mixer_elem_t,
|
||||
_mixer: &'a Mixer
|
||||
}
|
||||
|
||||
/// Iterator for all elements of mixer
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Iter<'a>{
|
||||
last_handle: *mut alsa::snd_mixer_elem_t,
|
||||
mixer: &'a Mixer
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = Elem<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Elem<'a>> {
|
||||
let elem = if self.last_handle.is_null() {
|
||||
unsafe { alsa::snd_mixer_first_elem(self.mixer.0) }
|
||||
} else {
|
||||
unsafe { alsa::snd_mixer_elem_next(self.last_handle) }
|
||||
};
|
||||
|
||||
if elem.is_null() {
|
||||
None
|
||||
} else {
|
||||
self.last_handle = elem;
|
||||
Some(Elem { handle: elem, _mixer: self.mixer})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Wrapper for [snd_mixer_selem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___simple_mixer.html)
|
||||
/// No allocation (uses fixed array)
|
||||
// #[derive(Copy, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct SelemId([u8; SELEM_ID_SIZE]);
|
||||
|
||||
impl SelemId {
|
||||
|
||||
pub fn new(name: &str, index: u32) -> SelemId {
|
||||
let mut s = SelemId::empty();
|
||||
s.set_name(&CString::new(name).unwrap());
|
||||
s.set_index(index);
|
||||
s
|
||||
}
|
||||
|
||||
/// Returns an empty (zeroed) SelemId. This id is not a usable id and need to be initialized
|
||||
/// like `SelemId::new()` does
|
||||
pub fn empty() -> SelemId {
|
||||
assert!(unsafe { alsa::snd_mixer_selem_id_sizeof() } as usize <= SELEM_ID_SIZE);
|
||||
// Create empty selem_id and fill from mixer
|
||||
SelemId(unsafe { mem::zeroed() })
|
||||
}
|
||||
|
||||
/// Convert SelemId into ``*mut snd_mixer_selem_id_t` that the alsa call needs.
|
||||
/// See [snd_mixer_selem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___simple_mixer.html)
|
||||
#[inline]
|
||||
fn as_ptr(&self) -> *mut alsa::snd_mixer_selem_id_t {
|
||||
self.0.as_ptr() as *const _ as *mut alsa::snd_mixer_selem_id_t
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> Result<&str> {
|
||||
let c = unsafe { alsa::snd_mixer_selem_id_get_name(self.as_ptr()) };
|
||||
from_const("snd_mixer_selem_id_get_name", c)
|
||||
}
|
||||
|
||||
pub fn get_index(&self) -> u32 {
|
||||
unsafe { alsa::snd_mixer_selem_id_get_index(self.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, name: &CStr) {
|
||||
unsafe { alsa::snd_mixer_selem_id_set_name(self.as_ptr(), name.as_ptr()) };
|
||||
}
|
||||
|
||||
pub fn set_index(&mut self, index: u32) {
|
||||
unsafe { alsa::snd_mixer_selem_id_set_index(self.as_ptr(), index) };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Wraps an Elem as a Selem
|
||||
// #[derive(Copy, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct Selem<'a>(Elem<'a>);
|
||||
|
||||
impl<'a> Selem<'a> {
|
||||
/// Creates a Selem by wrapping `elem`.
|
||||
pub fn new(elem: Elem<'a>) -> Option<Selem<'a>> {
|
||||
if unsafe { alsa::snd_mixer_elem_get_type(elem.handle) } == alsa::SND_MIXER_ELEM_SIMPLE
|
||||
{ Some(Selem(elem)) } else { None }
|
||||
}
|
||||
|
||||
/// TODO: This function might change to support regopt and to return the mixer class
|
||||
pub fn register(mixer: &mut Mixer) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_register(mixer.0, ptr::null_mut(), ptr::null_mut())).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> SelemId {
|
||||
let id = SelemId::empty();
|
||||
unsafe { alsa::snd_mixer_selem_get_id(self.handle, id.as_ptr()) };
|
||||
id
|
||||
}
|
||||
|
||||
pub fn has_capture_volume(&self) -> bool {
|
||||
unsafe { alsa::snd_mixer_selem_has_capture_volume(self.handle) > 0 }
|
||||
}
|
||||
|
||||
pub fn has_capture_switch(&self) -> bool {
|
||||
unsafe { alsa::snd_mixer_selem_has_capture_switch(self.handle) > 0 }
|
||||
}
|
||||
|
||||
pub fn has_playback_volume(&self) -> bool {
|
||||
unsafe { alsa::snd_mixer_selem_has_playback_volume(self.handle) > 0 }
|
||||
}
|
||||
|
||||
pub fn has_playback_switch(&self) -> bool {
|
||||
unsafe { alsa::snd_mixer_selem_has_playback_switch(self.handle) > 0 }
|
||||
}
|
||||
|
||||
pub fn can_capture(&self) -> bool {
|
||||
self.has_capture_volume() || self.has_capture_switch()
|
||||
}
|
||||
|
||||
pub fn can_playback(&self) -> bool {
|
||||
self.has_playback_volume() || self.has_playback_switch()
|
||||
}
|
||||
|
||||
pub fn has_volume(&self) -> bool {
|
||||
self.has_capture_volume() || self.has_playback_volume()
|
||||
}
|
||||
|
||||
/// returns range for capture volume as (min, max) values
|
||||
pub fn get_capture_volume_range(&self) -> (i64, i64) {
|
||||
let mut min: c_long = 0;
|
||||
let mut max: c_long = 0;
|
||||
unsafe { alsa::snd_mixer_selem_get_capture_volume_range(self.handle, &mut min, &mut max) };
|
||||
(min as i64, max as i64)
|
||||
}
|
||||
|
||||
/// returns (min, max) values.
|
||||
pub fn get_capture_db_range(&self) -> (MilliBel, MilliBel) {
|
||||
let mut min: c_long = 0;
|
||||
let mut max: c_long = 0;
|
||||
unsafe { alsa::snd_mixer_selem_get_capture_dB_range(self.handle, &mut min, &mut max) };
|
||||
(MilliBel(min as i64), MilliBel(max as i64))
|
||||
}
|
||||
|
||||
/// returns (min, max) values.
|
||||
pub fn get_playback_volume_range(&self) -> (i64, i64) {
|
||||
let mut min: c_long = 0;
|
||||
let mut max: c_long = 0;
|
||||
unsafe { alsa::snd_mixer_selem_get_playback_volume_range(self.handle, &mut min, &mut max) };
|
||||
(min as i64, max as i64)
|
||||
}
|
||||
|
||||
/// returns (min, max) values.
|
||||
pub fn get_playback_db_range(&self) -> (MilliBel, MilliBel) {
|
||||
let mut min: c_long = 0;
|
||||
let mut max: c_long = 0;
|
||||
unsafe { alsa::snd_mixer_selem_get_playback_dB_range(self.handle, &mut min, &mut max) };
|
||||
(MilliBel(min as i64), MilliBel(max as i64))
|
||||
}
|
||||
|
||||
pub fn is_capture_mono(&self) -> bool {
|
||||
unsafe { alsa::snd_mixer_selem_is_capture_mono(self.handle) == 1 }
|
||||
}
|
||||
|
||||
pub fn is_playback_mono(&self) -> bool {
|
||||
unsafe { alsa::snd_mixer_selem_is_playback_mono(self.handle) == 1 }
|
||||
}
|
||||
|
||||
pub fn has_capture_channel(&self, channel: SelemChannelId) -> bool {
|
||||
unsafe { alsa::snd_mixer_selem_has_capture_channel(self.handle, channel as i32) > 0 }
|
||||
}
|
||||
|
||||
pub fn has_playback_channel(&self, channel: SelemChannelId) -> bool {
|
||||
unsafe { alsa::snd_mixer_selem_has_playback_channel(self.handle, channel as i32) > 0 }
|
||||
}
|
||||
|
||||
/// Gets name from snd_mixer_selem_channel_name
|
||||
pub fn channel_name(channel: SelemChannelId) -> Result<&'static str> {
|
||||
let c = unsafe { alsa::snd_mixer_selem_channel_name(channel as i32) };
|
||||
from_const("snd_mixer_selem_channel_name", c)
|
||||
}
|
||||
|
||||
pub fn get_playback_volume(&self, channel: SelemChannelId) -> Result<i64> {
|
||||
let mut value: c_long = 0;
|
||||
acheck!(snd_mixer_selem_get_playback_volume(self.handle, channel as i32, &mut value)).and_then(|_| Ok(value as i64))
|
||||
}
|
||||
|
||||
/// returns volume in millibels.
|
||||
pub fn get_playback_vol_db(&self, channel: SelemChannelId) -> Result<MilliBel> {
|
||||
self.get_playback_volume(channel)
|
||||
.and_then(|volume| self.ask_playback_vol_db(volume))
|
||||
}
|
||||
|
||||
/// Asks alsa to convert playback volume to millibels.
|
||||
pub fn ask_playback_vol_db(&self, volume: i64) -> Result<MilliBel> {
|
||||
let mut decibel_value: c_long = 0;
|
||||
acheck!(snd_mixer_selem_ask_playback_vol_dB(self.handle, volume as c_long, &mut decibel_value))
|
||||
.map(|_| MilliBel(decibel_value as i64))
|
||||
}
|
||||
|
||||
// Asks alsa to convert millibels to playback volume.
|
||||
pub fn ask_playback_db_vol(&self, db: MilliBel, dir: Round) -> Result<i64> {
|
||||
let mut raw_volume: c_long = 0;
|
||||
acheck!(snd_mixer_selem_ask_playback_dB_vol(self.handle, db.0 as c_long, dir as c_int, &mut raw_volume))
|
||||
.map(|_| raw_volume as i64)
|
||||
}
|
||||
|
||||
pub fn get_capture_volume(&self, channel: SelemChannelId) -> Result<i64> {
|
||||
let mut value: c_long = 0;
|
||||
acheck!(snd_mixer_selem_get_capture_volume(self.handle, channel as i32, &mut value)).map(|_| value as i64)
|
||||
}
|
||||
|
||||
/// returns volume in millibels.
|
||||
pub fn get_capture_vol_db(&self, channel: SelemChannelId) -> Result<MilliBel> {
|
||||
self.get_capture_volume(channel)
|
||||
.and_then(|volume| self.ask_capture_vol_db(volume))
|
||||
}
|
||||
|
||||
/// Asks alsa to convert capture volume to millibels
|
||||
pub fn ask_capture_vol_db(&self, volume: i64) -> Result<MilliBel> {
|
||||
let mut decibel_value: c_long = 0;
|
||||
acheck!(snd_mixer_selem_ask_capture_vol_dB (self.handle, volume as c_long, &mut decibel_value))
|
||||
.map(|_| MilliBel(decibel_value as i64))
|
||||
}
|
||||
|
||||
// Asks alsa to convert millibels to capture volume.
|
||||
pub fn ask_capture_db_vol(&self, db: MilliBel, dir: Round) -> Result<i64> {
|
||||
let mut raw_volume: c_long = 0;
|
||||
acheck!(snd_mixer_selem_ask_capture_dB_vol(self.handle, db.0 as c_long, dir as c_int, &mut raw_volume))
|
||||
.map(|_| raw_volume as i64)
|
||||
}
|
||||
|
||||
pub fn set_playback_volume(&self, channel: SelemChannelId, value: i64) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_playback_volume(self.handle, channel as i32, value as c_long)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_playback_volume_range(&self, min: i64, max: i64) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_playback_volume_range(self.handle, min as c_long, max as c_long)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_playback_volume_all(&self, value: i64) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_playback_volume_all(self.handle, value as c_long)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_playback_db(&self, channel: SelemChannelId, value: MilliBel, dir: Round) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_playback_dB(self.handle, channel as i32, *value as c_long, dir as c_int)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_capture_db(&self, channel: SelemChannelId, value: MilliBel, dir: Round) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_capture_dB(self.handle, channel as i32, *value as c_long, dir as c_int)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_playback_db_all(&self, value: MilliBel, dir: Round) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_playback_dB_all(self.handle, *value as c_long, dir as c_int)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_capture_db_all(&self, value: MilliBel, dir: Round) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_capture_dB_all(self.handle, *value as c_long, dir as c_int)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_capture_volume(&self, channel: SelemChannelId, value: i64) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_capture_volume(self.handle, channel as i32, value as c_long)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_capture_volume_range(&self, min: i64, max: i64) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_capture_volume_range(self.handle, min as c_long, max as c_long)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_capture_volume_all(&self, value: i64) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_capture_volume_all(self.handle, value as c_long)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_playback_switch(&self, channel: SelemChannelId, value: i32) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_playback_switch(self.handle, channel as i32, value)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_playback_switch_all(&self, value: i32) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_playback_switch_all(self.handle, value)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_capture_switch(&self, channel: SelemChannelId, value: i32) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_capture_switch(self.handle, channel as i32, value)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn set_capture_switch_all(&self, value: i32) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_capture_switch_all(self.handle, value)).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn get_playback_switch(&self, channel: SelemChannelId) -> Result<i32> {
|
||||
let mut value: i32 = 0;
|
||||
acheck!(snd_mixer_selem_get_playback_switch(self.handle, channel as i32, &mut value)).map(|_| value)
|
||||
}
|
||||
|
||||
pub fn get_capture_switch(&self, channel: SelemChannelId) -> Result<i32> {
|
||||
let mut value: i32 = 0;
|
||||
acheck!(snd_mixer_selem_get_capture_switch(self.handle, channel as i32, &mut value)).map(|_| value)
|
||||
}
|
||||
|
||||
pub fn is_enumerated(&self) -> bool {
|
||||
unsafe { alsa::snd_mixer_selem_is_enumerated(self.handle) == 1 }
|
||||
}
|
||||
|
||||
pub fn is_enum_playback(&self) -> bool {
|
||||
unsafe { alsa::snd_mixer_selem_is_enum_playback(self.handle) == 1 }
|
||||
}
|
||||
|
||||
pub fn is_enum_capture(&self) -> bool {
|
||||
unsafe { alsa::snd_mixer_selem_is_enum_capture(self.handle) == 1 }
|
||||
}
|
||||
|
||||
pub fn get_enum_items(&self) -> Result<u32> {
|
||||
acheck!(snd_mixer_selem_get_enum_items(self.handle)).map(|v| v as u32)
|
||||
}
|
||||
|
||||
pub fn get_enum_item_name(&self, idx: u32) -> Result<String> {
|
||||
let mut temp = [0 as ::libc::c_char; 128];
|
||||
acheck!(snd_mixer_selem_get_enum_item_name(self.handle, idx, temp.len()-1, temp.as_mut_ptr()))
|
||||
.and_then(|_| from_const("snd_mixer_selem_get_enum_item_name", temp.as_ptr()))
|
||||
.map(|v| v.into())
|
||||
}
|
||||
|
||||
/// Enumerates over valid Enum values
|
||||
pub fn iter_enum(&self) -> Result<IterEnum<'_>> {
|
||||
Ok(IterEnum(self, 0, self.get_enum_items()?))
|
||||
}
|
||||
|
||||
pub fn get_enum_item(&self, channel: SelemChannelId) -> Result<u32> {
|
||||
let mut temp = 0;
|
||||
acheck!(snd_mixer_selem_get_enum_item(self.handle, channel as i32, &mut temp))
|
||||
.map(|_| temp)
|
||||
}
|
||||
|
||||
pub fn set_enum_item(&self, channel: SelemChannelId, idx: u32) -> Result<()> {
|
||||
acheck!(snd_mixer_selem_set_enum_item(self.handle, channel as i32, idx))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ops::Deref for Selem<'a> {
|
||||
type Target = Elem<'a>;
|
||||
|
||||
/// returns the elem of this selem
|
||||
fn deref(&self) -> &Elem<'a> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IterEnum<'a>(&'a Selem<'a>, u32, u32);
|
||||
|
||||
impl<'a> Iterator for IterEnum<'a> {
|
||||
type Item = Result<String>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.1 >= self.2 { None }
|
||||
else { self.1 += 1; Some(self.0.get_enum_item_name(self.1-1)) }
|
||||
}
|
||||
}
|
||||
|
||||
alsa_enum!(
|
||||
/// Wrapper for [SND_MIXER_SCHN_*](http://www.alsa-project.org/alsa-doc/alsa-lib/group___simple_mixer.html) constants
|
||||
SelemChannelId, ALL_SELEM_CHANNEL_ID[11],
|
||||
|
||||
Unknown = SND_MIXER_SCHN_UNKNOWN,
|
||||
FrontLeft = SND_MIXER_SCHN_FRONT_LEFT,
|
||||
FrontRight = SND_MIXER_SCHN_FRONT_RIGHT,
|
||||
RearLeft = SND_MIXER_SCHN_REAR_LEFT,
|
||||
RearRight = SND_MIXER_SCHN_REAR_RIGHT,
|
||||
FrontCenter = SND_MIXER_SCHN_FRONT_CENTER,
|
||||
Woofer = SND_MIXER_SCHN_WOOFER,
|
||||
SideLeft = SND_MIXER_SCHN_SIDE_LEFT,
|
||||
SideRight = SND_MIXER_SCHN_SIDE_RIGHT,
|
||||
RearCenter = SND_MIXER_SCHN_REAR_CENTER,
|
||||
Last = SND_MIXER_SCHN_LAST,
|
||||
);
|
||||
|
||||
impl SelemChannelId {
|
||||
pub fn mono() -> SelemChannelId { SelemChannelId::FrontLeft }
|
||||
}
|
||||
|
||||
impl fmt::Display for SelemChannelId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Selem::channel_name(*self).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_mixer_of_cards() {
|
||||
extern crate std;
|
||||
use std::{println, print};
|
||||
use ::alloc::format;
|
||||
use ::alloc::string::ToString;
|
||||
use super::card;
|
||||
|
||||
for card in card::Iter::new().map(|c| c.unwrap()) {
|
||||
println!("Card #{}: {} ({})", card.get_index(), card.get_name().unwrap(), card.get_longname().unwrap());
|
||||
|
||||
let mixer = Mixer::new(&format!("hw:{}", card.get_index()), false).unwrap();
|
||||
for selem in mixer.iter().filter_map(|e| Selem::new(e)) {
|
||||
|
||||
let sid = selem.get_id();
|
||||
println!("\tMixer element {},{}:", sid.get_name().unwrap(), sid.get_index());
|
||||
|
||||
if selem.has_volume() {
|
||||
print!("\t Volume limits: ");
|
||||
if selem.has_capture_volume() {
|
||||
let (vmin, vmax) = selem.get_capture_volume_range();
|
||||
let (mbmin, mbmax) = selem.get_capture_db_range();
|
||||
print!("Capture = {} - {}", vmin, vmax);
|
||||
print!(" ({} dB - {} dB)", mbmin.to_db(), mbmax.to_db());
|
||||
}
|
||||
if selem.has_playback_volume() {
|
||||
let (vmin, vmax) = selem.get_playback_volume_range();
|
||||
let (mbmin, mbmax) = selem.get_playback_db_range();
|
||||
print!("Playback = {} - {}", vmin, vmax);
|
||||
print!(" ({} dB - {} dB)", mbmin.to_db(), mbmax.to_db());
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
if selem.is_enumerated() {
|
||||
print!("\t Valid values: ");
|
||||
for v in selem.iter_enum().unwrap() { print!("{}, ", v.unwrap()) };
|
||||
print!("\n\t Current values: ");
|
||||
for v in SelemChannelId::all().iter().filter_map(|&v| selem.get_enum_item(v).ok()) {
|
||||
print!("{}, ", selem.get_enum_item_name(v).unwrap());
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
if selem.can_capture() {
|
||||
print!("\t Capture channels: ");
|
||||
if selem.is_capture_mono() {
|
||||
print!("Mono");
|
||||
} else {
|
||||
for channel in SelemChannelId::all() {
|
||||
if selem.has_capture_channel(*channel) { print!("{}, ", channel) };
|
||||
}
|
||||
}
|
||||
println!();
|
||||
print!("\t Capture volumes: ");
|
||||
for channel in SelemChannelId::all() {
|
||||
if selem.has_capture_channel(*channel) { print!("{}: {} ({} dB), ", channel,
|
||||
match selem.get_capture_volume(*channel) {Ok(v) => format!("{}", v), Err(_) => "n/a".to_string()},
|
||||
match selem.get_capture_vol_db(*channel) {Ok(v) => format!("{}", v.to_db()), Err(_) => "n/a".to_string()}
|
||||
);}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
if selem.can_playback() {
|
||||
print!("\t Playback channels: ");
|
||||
if selem.is_playback_mono() {
|
||||
print!("Mono");
|
||||
} else {
|
||||
for channel in SelemChannelId::all() {
|
||||
if selem.has_playback_channel(*channel) { print!("{}, ", channel) };
|
||||
}
|
||||
}
|
||||
println!();
|
||||
if selem.has_playback_volume() {
|
||||
print!("\t Playback volumes: ");
|
||||
for channel in SelemChannelId::all() {
|
||||
if selem.has_playback_channel(*channel) { print!("{}: {} / {}dB, ",
|
||||
channel,
|
||||
match selem.get_playback_volume(*channel) {Ok(v) => format!("{}", v), Err(_) => "n/a".to_string()},
|
||||
match selem.get_playback_vol_db(*channel) {Ok(v) => format!("{}", v.to_db()), Err(_) => "n/a".to_string()}
|
||||
);}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn get_and_set_playback_volume() {
|
||||
extern crate std;
|
||||
|
||||
let mixer = Mixer::new("hw:1", false).unwrap();
|
||||
let selem = mixer.find_selem(&SelemId::new("Master", 0)).unwrap();
|
||||
|
||||
let (rmin, rmax) = selem.get_playback_volume_range();
|
||||
let mut channel = SelemChannelId::mono();
|
||||
for c in SelemChannelId::all().iter() {
|
||||
if selem.has_playback_channel(*c) { channel = *c; break }
|
||||
}
|
||||
std::println!("Testing on {} with limits {}-{} on channel {}", selem.get_id().get_name().unwrap(), rmin, rmax, channel);
|
||||
|
||||
let old: i64 = selem.get_playback_volume(channel).unwrap();
|
||||
let new: i64 = rmax / 2;
|
||||
assert_ne!(new, old);
|
||||
|
||||
std::println!("Changing volume of {} from {} to {}", channel, old, new);
|
||||
selem.set_playback_volume(channel, new).unwrap();
|
||||
let mut result: i64 = selem.get_playback_volume(channel).unwrap();
|
||||
assert_eq!(new, result);
|
||||
|
||||
// return volume to old value
|
||||
selem.set_playback_volume(channel, old).unwrap();
|
||||
result = selem.get_playback_volume(channel).unwrap();
|
||||
assert_eq!(old, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn get_and_set_capture_volume() {
|
||||
extern crate std;
|
||||
|
||||
let mixer = Mixer::new("hw:1", false).unwrap();
|
||||
let selem = mixer.find_selem(&SelemId::new("Capture", 0)).unwrap();
|
||||
|
||||
let (rmin, rmax) = selem.get_capture_volume_range();
|
||||
let mut channel = SelemChannelId::mono();
|
||||
for c in SelemChannelId::all().iter() {
|
||||
if selem.has_playback_channel(*c) { channel = *c; break }
|
||||
}
|
||||
std::println!("Testing on {} with limits {}-{} on channel {}", selem.get_id().get_name().unwrap(), rmin, rmax, channel);
|
||||
|
||||
let old: i64 = selem.get_capture_volume(channel).unwrap();
|
||||
let new: i64 = rmax / 2;
|
||||
assert_ne!(new, old);
|
||||
|
||||
std::println!("Changing volume of {} from {} to {}", channel, old, new);
|
||||
selem.set_capture_volume(channel, new).unwrap();
|
||||
let mut result: i64 = selem.get_capture_volume(channel).unwrap();
|
||||
assert_eq!(new, result);
|
||||
|
||||
// return volume to old value
|
||||
selem.set_capture_volume(channel, old).unwrap();
|
||||
result = selem.get_capture_volume(channel).unwrap();
|
||||
assert_eq!(old, result);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn print_sizeof() {
|
||||
extern crate std;
|
||||
let selemid = unsafe { alsa::snd_mixer_selem_id_sizeof() } as usize;
|
||||
|
||||
assert!(selemid <= SELEM_ID_SIZE);
|
||||
std::println!("Selem id: {}", selemid);
|
||||
}
|
||||
1450
vendor/alsa/src/pcm.rs
vendored
Normal file
1450
vendor/alsa/src/pcm.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
77
vendor/alsa/src/poll.rs
vendored
Normal file
77
vendor/alsa/src/poll.rs
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
//! Tiny poll ffi
|
||||
//!
|
||||
//! A tiny wrapper around libc's poll system call.
|
||||
|
||||
use libc;
|
||||
use super::error::*;
|
||||
pub use libc::pollfd;
|
||||
use ::alloc::vec;
|
||||
use ::alloc::vec::Vec;
|
||||
|
||||
bitflags! {
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Flags: ::libc::c_short {
|
||||
const IN = ::libc::POLLIN;
|
||||
const PRI = ::libc::POLLPRI;
|
||||
const OUT = ::libc::POLLOUT;
|
||||
const ERR = ::libc::POLLERR;
|
||||
const HUP = ::libc::POLLHUP;
|
||||
const NVAL = ::libc::POLLNVAL;
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Descriptors {
|
||||
fn count(&self) -> usize;
|
||||
fn fill(&self, _: &mut [pollfd]) -> Result<usize>;
|
||||
fn revents(&self, _: &[pollfd]) -> Result<Flags>;
|
||||
|
||||
/// Wrapper around count and fill - returns an array of pollfds
|
||||
fn get(&self) -> Result<Vec<pollfd>> {
|
||||
let mut v = vec![pollfd { fd: 0, events: 0, revents: 0 }; self.count()];
|
||||
if self.fill(&mut v)? != v.len() { Err(Error::unsupported("did not fill the poll descriptors array")) }
|
||||
else { Ok(v) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Descriptors for pollfd {
|
||||
fn count(&self) -> usize { 1 }
|
||||
fn fill(&self, a: &mut [pollfd]) -> Result<usize> { a[0] = *self; Ok(1) }
|
||||
fn revents(&self, a: &[pollfd]) -> Result<Flags> { Ok(Flags::from_bits_truncate(a[0].revents)) }
|
||||
}
|
||||
|
||||
/// Wrapper around the libc poll call.
|
||||
pub fn poll(fds: &mut[pollfd], timeout: i32) -> Result<usize> {
|
||||
let r = unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as libc::nfds_t, timeout as libc::c_int) };
|
||||
if r >= 0 { Ok(r as usize) } else {
|
||||
#[cfg(feature = "std")]
|
||||
{ from_code(
|
||||
"poll",
|
||||
-std::io::Error::last_os_error().raw_os_error().unwrap_or_default()).map(|_| unreachable!())
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
{ from_code("poll", -unsafe {super::error::errno}).map(|_| unreachable!()) }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a pollfd array, polls it, and returns the poll descriptors which have non-zero revents.
|
||||
pub fn poll_all<'a>(desc: &[&'a dyn Descriptors], timeout: i32) -> Result<Vec<(&'a dyn Descriptors, Flags)>> {
|
||||
|
||||
let mut pollfds: Vec<pollfd> = vec!();
|
||||
let mut indices = vec!();
|
||||
for v2 in desc.iter().map(|q| q.get()) {
|
||||
let v = v2?;
|
||||
indices.push(pollfds.len() .. pollfds.len()+v.len());
|
||||
pollfds.extend(v);
|
||||
};
|
||||
|
||||
poll(&mut pollfds, timeout)?;
|
||||
|
||||
let mut res = vec!();
|
||||
for (i, r) in indices.into_iter().enumerate() {
|
||||
let z = desc[i].revents(&pollfds[r])?;
|
||||
if !z.is_empty() { res.push((desc[i], z)); }
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
223
vendor/alsa/src/rawmidi.rs
vendored
Normal file
223
vendor/alsa/src/rawmidi.rs
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
//! MIDI devices I/O and enumeration
|
||||
|
||||
use libc::{c_int, c_uint, c_void, size_t, c_short, pollfd};
|
||||
use super::ctl_int::{ctl_ptr, Ctl};
|
||||
use super::{Direction, poll};
|
||||
use super::error::*;
|
||||
use crate::alsa;
|
||||
use ::alloc::ffi::CString;
|
||||
use ::alloc::string::{String, ToString};
|
||||
use core::ptr;
|
||||
use core::ffi::CStr;
|
||||
|
||||
/// Iterator over [Rawmidi](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) devices and subdevices
|
||||
#[derive(Debug)]
|
||||
pub struct Iter<'a> {
|
||||
ctl: &'a Ctl,
|
||||
device: c_int,
|
||||
in_count: i32,
|
||||
out_count: i32,
|
||||
current: i32,
|
||||
}
|
||||
|
||||
/// [snd_rawmidi_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct Info(*mut alsa::snd_rawmidi_info_t);
|
||||
|
||||
impl Drop for Info {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_rawmidi_info_free(self.0) }; }
|
||||
}
|
||||
|
||||
impl Info {
|
||||
fn new() -> Result<Info> {
|
||||
let mut p = ptr::null_mut();
|
||||
acheck!(snd_rawmidi_info_malloc(&mut p)).map(|_| Info(p))
|
||||
}
|
||||
|
||||
fn from_iter(c: &Ctl, device: i32, sub: i32, dir: Direction) -> Result<Info> {
|
||||
let r = Info::new()?;
|
||||
unsafe { alsa::snd_rawmidi_info_set_device(r.0, device as c_uint) };
|
||||
let d = match dir {
|
||||
Direction::Playback => alsa::SND_RAWMIDI_STREAM_OUTPUT,
|
||||
Direction::Capture => alsa::SND_RAWMIDI_STREAM_INPUT,
|
||||
};
|
||||
unsafe { alsa::snd_rawmidi_info_set_stream(r.0, d) };
|
||||
unsafe { alsa::snd_rawmidi_info_set_subdevice(r.0, sub as c_uint) };
|
||||
acheck!(snd_ctl_rawmidi_info(ctl_ptr(c), r.0)).map(|_| r)
|
||||
}
|
||||
|
||||
fn subdev_count(c: &Ctl, device: c_int) -> Result<(i32, i32)> {
|
||||
let i = Info::from_iter(c, device, 0, Direction::Capture)?;
|
||||
let o = Info::from_iter(c, device, 0, Direction::Playback)?;
|
||||
Ok((unsafe { alsa::snd_rawmidi_info_get_subdevices_count(o.0) as i32 },
|
||||
unsafe { alsa::snd_rawmidi_info_get_subdevices_count(i.0) as i32 }))
|
||||
}
|
||||
|
||||
pub fn get_device(&self) -> i32 { unsafe { alsa::snd_rawmidi_info_get_device(self.0) as i32 }}
|
||||
pub fn get_subdevice(&self) -> i32 { unsafe { alsa::snd_rawmidi_info_get_subdevice(self.0) as i32 }}
|
||||
pub fn get_stream(&self) -> super::Direction {
|
||||
if unsafe { alsa::snd_rawmidi_info_get_stream(self.0) } == alsa::SND_RAWMIDI_STREAM_OUTPUT { super::Direction::Playback }
|
||||
else { super::Direction::Capture }
|
||||
}
|
||||
|
||||
pub fn get_subdevice_name(&self) -> Result<String> {
|
||||
let c = unsafe { alsa::snd_rawmidi_info_get_subdevice_name(self.0) };
|
||||
from_const("snd_rawmidi_info_get_subdevice_name", c).map(|s| s.to_string())
|
||||
}
|
||||
pub fn get_id(&self) -> Result<String> {
|
||||
let c = unsafe { alsa::snd_rawmidi_info_get_id(self.0) };
|
||||
from_const("snd_rawmidi_info_get_id", c).map(|s| s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// [snd_rawmidi_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct Status(*mut alsa::snd_rawmidi_status_t);
|
||||
|
||||
impl Status {
|
||||
fn new() -> Result<Self> {
|
||||
let mut p = ptr::null_mut();
|
||||
acheck!(snd_rawmidi_status_malloc(&mut p)).map(|_| Status(p))
|
||||
}
|
||||
}
|
||||
|
||||
impl Status {
|
||||
pub fn get_avail(&self) -> usize { unsafe { alsa::snd_rawmidi_status_get_avail(self.0 as *const _) } }
|
||||
pub fn get_xruns(&self) -> usize { unsafe { alsa::snd_rawmidi_status_get_xruns(self.0 as *const _) } }
|
||||
}
|
||||
|
||||
impl Drop for Status {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_rawmidi_status_free(self.0) }; }
|
||||
}
|
||||
|
||||
|
||||
impl<'a> Iter<'a> {
|
||||
pub fn new(c: &'a Ctl) -> Iter<'a> { Iter { ctl: c, device: -1, in_count: 0, out_count: 0, current: 0 }}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = Result<Info>;
|
||||
fn next(&mut self) -> Option<Result<Info>> {
|
||||
if self.current < self.in_count {
|
||||
self.current += 1;
|
||||
return Some(Info::from_iter(self.ctl, self.device, self.current-1, Direction::Capture));
|
||||
}
|
||||
if self.current - self.in_count < self.out_count {
|
||||
self.current += 1;
|
||||
return Some(Info::from_iter(self.ctl, self.device, self.current-1-self.in_count, Direction::Playback));
|
||||
}
|
||||
|
||||
let r = acheck!(snd_ctl_rawmidi_next_device(ctl_ptr(self.ctl), &mut self.device));
|
||||
match r {
|
||||
Err(e) if e.errno() == libc::ENOTTY => return None,
|
||||
Err(e) => return Some(Err(e)),
|
||||
Ok(_) if self.device == -1 => return None,
|
||||
_ => {},
|
||||
}
|
||||
self.current = 0;
|
||||
match Info::subdev_count(self.ctl, self.device) {
|
||||
Err(e) => Some(Err(e)),
|
||||
Ok((oo, ii)) => {
|
||||
self.in_count = ii;
|
||||
self.out_count = oo;
|
||||
self.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [snd_rawmidi_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) wrapper
|
||||
#[derive(Debug)]
|
||||
pub struct Rawmidi(*mut alsa::snd_rawmidi_t);
|
||||
|
||||
unsafe impl Send for Rawmidi {}
|
||||
|
||||
impl Drop for Rawmidi {
|
||||
fn drop(&mut self) { unsafe { alsa::snd_rawmidi_close(self.0) }; }
|
||||
}
|
||||
|
||||
impl Rawmidi {
|
||||
|
||||
/// Wrapper around open that takes a &str instead of a &CStr
|
||||
pub fn new(name: &str, dir: Direction, nonblock: bool) -> Result<Self> {
|
||||
Self::open(&CString::new(name).unwrap(), dir, nonblock)
|
||||
}
|
||||
|
||||
pub fn open(name: &CStr, dir: Direction, nonblock: bool) -> Result<Rawmidi> {
|
||||
let mut h = ptr::null_mut();
|
||||
let flags = if nonblock { 2 } else { 0 }; // FIXME: alsa::SND_RAWMIDI_NONBLOCK does not exist in alsa-sys
|
||||
acheck!(snd_rawmidi_open(
|
||||
if dir == Direction::Capture { &mut h } else { ptr::null_mut() },
|
||||
if dir == Direction::Playback { &mut h } else { ptr::null_mut() },
|
||||
name.as_ptr(), flags))
|
||||
.map(|_| Rawmidi(h))
|
||||
}
|
||||
|
||||
pub fn info(&self) -> Result<Info> {
|
||||
Info::new().and_then(|i| acheck!(snd_rawmidi_info(self.0, i.0)).map(|_| i))
|
||||
}
|
||||
|
||||
pub fn status(&self) -> Result<Status> {
|
||||
Status::new().and_then(|i| acheck!(snd_rawmidi_status(self.0, i.0)).map(|_| i))
|
||||
}
|
||||
|
||||
pub fn drop(&self) -> Result<()> { acheck!(snd_rawmidi_drop(self.0)).map(|_| ()) }
|
||||
pub fn drain(&self) -> Result<()> { acheck!(snd_rawmidi_drain(self.0)).map(|_| ()) }
|
||||
pub fn name(&self) -> Result<String> {
|
||||
let c = unsafe { alsa::snd_rawmidi_name(self.0) };
|
||||
from_const("snd_rawmidi_name", c).map(|s| s.to_string())
|
||||
}
|
||||
|
||||
pub fn io(&self) -> IO<'_> { IO(self) }
|
||||
}
|
||||
|
||||
impl poll::Descriptors for Rawmidi {
|
||||
fn count(&self) -> usize {
|
||||
unsafe { alsa::snd_rawmidi_poll_descriptors_count(self.0) as usize }
|
||||
}
|
||||
fn fill(&self, p: &mut [pollfd]) -> Result<usize> {
|
||||
let z = unsafe { alsa::snd_rawmidi_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) };
|
||||
from_code("snd_rawmidi_poll_descriptors", z).map(|_| z as usize)
|
||||
}
|
||||
fn revents(&self, p: &[pollfd]) -> Result<poll::Flags> {
|
||||
let mut r = 0;
|
||||
let z = unsafe { alsa::snd_rawmidi_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) };
|
||||
from_code("snd_rawmidi_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short))
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements `std::io::Read` and `std::io::Write` for `Rawmidi`
|
||||
#[derive(Debug)]
|
||||
pub struct IO<'a>(&'a Rawmidi);
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<'a> std::io::Read for IO<'a> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let r = unsafe { alsa::snd_rawmidi_read((self.0).0, buf.as_mut_ptr() as *mut c_void, buf.len() as size_t) };
|
||||
if r < 0 { Err(std::io::Error::from_raw_os_error(r as i32)) }
|
||||
else { Ok(r as usize) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<'a> std::io::Write for IO<'a> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let r = unsafe { alsa::snd_rawmidi_write((self.0).0, buf.as_ptr() as *const c_void, buf.len() as size_t) };
|
||||
if r < 0 { Err(std::io::Error::from_raw_os_error(r as i32)) }
|
||||
else { Ok(r as usize) }
|
||||
}
|
||||
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn print_rawmidis() {
|
||||
extern crate std;
|
||||
|
||||
for a in super::card::Iter::new().map(|a| a.unwrap()) {
|
||||
for b in Iter::new(&Ctl::from_card(&a, false).unwrap()).map(|b| b.unwrap()) {
|
||||
std::println!("Rawmidi {:?} (hw:{},{},{}) {} - {}", b.get_stream(), a.get_index(), b.get_device(), b.get_subdevice(),
|
||||
a.get_name().unwrap(), b.get_subdevice_name().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
1595
vendor/alsa/src/seq.rs
vendored
Normal file
1595
vendor/alsa/src/seq.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user