feat: 动态链接到 opus 和 alsa 基础库,完成音频录制播放和编解码
This commit is contained in:
Generated
+115
@@ -2,6 +2,121 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alsa"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d"
|
||||||
|
dependencies = [
|
||||||
|
"alsa-sys",
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alsa-sys"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad7569085a265dd3f607ebecce7458eaab2132a84393534c95b18dcbc3f31e04"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "audiopus_sys"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62314a1546a2064e033665d658e88c620a62904be945f8147e6b16c3db9f8651"
|
||||||
|
dependencies = [
|
||||||
|
"cmake",
|
||||||
|
"log",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.51"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmake"
|
||||||
|
version = "0.1.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hello"
|
name = "hello"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"alsa",
|
||||||
|
"anyhow",
|
||||||
|
"opus",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.178"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opus"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6526409b274a7e98e55ff59d96aafd38e6cd34d46b7dbbc32ce126dffcd75e8e"
|
||||||
|
dependencies = [
|
||||||
|
"audiopus_sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|||||||
@@ -4,3 +4,6 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
alsa = "0.11"
|
||||||
|
opus = "0.3"
|
||||||
|
anyhow = "1.0"
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use opus::{Encoder, Decoder, Application, Bitrate, Channels};
|
||||||
|
use anyhow::{Result, Context};
|
||||||
|
use crate::config::AudioConfig;
|
||||||
|
|
||||||
|
pub struct OpusCodec {
|
||||||
|
encoder: Encoder,
|
||||||
|
decoder: Decoder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpusCodec {
|
||||||
|
pub fn new(config: &AudioConfig) -> Result<Self> {
|
||||||
|
let channels = if config.channels == 1 { Channels::Mono } else { Channels::Stereo };
|
||||||
|
let mut encoder = Encoder::new(config.sample_rate, channels, Application::Voip)
|
||||||
|
.context("Failed to create Opus encoder")?;
|
||||||
|
encoder.set_bitrate(Bitrate::Bits(config.bitrate))?;
|
||||||
|
|
||||||
|
let decoder = Decoder::new(config.sample_rate, channels)
|
||||||
|
.context("Failed to create Opus decoder")?;
|
||||||
|
|
||||||
|
Ok(Self { encoder, decoder })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode(&mut self, pcm: &[i16], out: &mut [u8]) -> Result<usize> {
|
||||||
|
self.encoder.encode(pcm, out).context("Opus encoding failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode(&mut self, opus: &[u8], out: &mut [i16]) -> Result<usize> {
|
||||||
|
self.decoder.decode(opus, out, false).context("Opus decoding failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
pub mod recorder;
|
||||||
|
pub mod player;
|
||||||
|
pub mod codec;
|
||||||
|
|
||||||
|
pub use recorder::AudioRecorder;
|
||||||
|
pub use player::AudioPlayer;
|
||||||
|
pub use codec::OpusCodec;
|
||||||
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
use crate::config::AudioConfig;
|
||||||
|
use alsa::pcm::{Access, Format, HwParams, PCM};
|
||||||
|
use alsa::Direction;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
|
pub struct AudioPlayer {
|
||||||
|
pcm: PCM,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioPlayer {
|
||||||
|
pub fn new(config: &AudioConfig) -> Result<Self> {
|
||||||
|
let pcm = PCM::new(&config.playback_device, Direction::Playback, false)
|
||||||
|
.context("Failed to open playback PCM device")?;
|
||||||
|
|
||||||
|
setup_pcm(&pcm, config.sample_rate, config.channels)?;
|
||||||
|
Ok(Self { pcm })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&self, buffer: &[i16]) -> Result<usize> {
|
||||||
|
let res = self.pcm.io_i16()?.writei(buffer);
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(written) => Ok(written),
|
||||||
|
Err(e) => {
|
||||||
|
// Buffer Underrun,即播放缓冲区的数据被耗尽,导致音频流中断
|
||||||
|
if e.errno() == 32 {
|
||||||
|
// 恢复音频流状态
|
||||||
|
self.pcm.prepare()?;
|
||||||
|
// 重新获取 IO 对象并尝试写入数据
|
||||||
|
self.pcm
|
||||||
|
.io_i16()?
|
||||||
|
.writei(buffer)
|
||||||
|
.context("Failed to write to playback device after recovery")
|
||||||
|
} else {
|
||||||
|
Err(e).context("Failed to write to playback device")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_pcm(pcm: &PCM, sample_rate: u32, channels: u16) -> Result<()> {
|
||||||
|
let hwp = HwParams::any(pcm).context("Failed to get HwParams")?;
|
||||||
|
hwp.set_access(Access::RWInterleaved)?;
|
||||||
|
hwp.set_format(Format::s16())?;
|
||||||
|
hwp.set_rate(sample_rate, alsa::ValueOr::Nearest)?;
|
||||||
|
hwp.set_channels(channels as u32)?;
|
||||||
|
pcm.hw_params(&hwp).context("Failed to set HwParams")?;
|
||||||
|
|
||||||
|
let swp = pcm.sw_params_current()?;
|
||||||
|
pcm.sw_params(&swp)?;
|
||||||
|
pcm.prepare()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use alsa::pcm::{PCM, HwParams, Format, Access};
|
||||||
|
use alsa::Direction;
|
||||||
|
use anyhow::{Result, Context};
|
||||||
|
use crate::config::AudioConfig;
|
||||||
|
|
||||||
|
pub struct AudioRecorder {
|
||||||
|
pcm: PCM,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioRecorder {
|
||||||
|
pub fn new(config: &AudioConfig) -> Result<Self> {
|
||||||
|
let pcm = PCM::new(&config.capture_device, Direction::Capture, false)
|
||||||
|
.context("Failed to open capture PCM device")?;
|
||||||
|
|
||||||
|
setup_pcm(&pcm, config.sample_rate, config.channels)?;
|
||||||
|
Ok(Self { pcm })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self, buffer: &mut [i16]) -> Result<usize> {
|
||||||
|
self.pcm.io_i16()?.readi(buffer).context("Failed to read from capture device")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_pcm(pcm: &PCM, sample_rate: u32, channels: u16) -> Result<()> {
|
||||||
|
let hwp = HwParams::any(pcm).context("Failed to get HwParams")?;
|
||||||
|
hwp.set_access(Access::RWInterleaved)?;
|
||||||
|
hwp.set_format(Format::s16())?;
|
||||||
|
hwp.set_rate(sample_rate, alsa::ValueOr::Nearest)?;
|
||||||
|
hwp.set_channels(channels as u32)?;
|
||||||
|
pcm.hw_params(&hwp).context("Failed to set HwParams")?;
|
||||||
|
|
||||||
|
let swp = pcm.sw_params_current()?;
|
||||||
|
pcm.sw_params(&swp)?;
|
||||||
|
pcm.prepare()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
pub struct AudioConfig {
|
||||||
|
pub capture_device: String,
|
||||||
|
pub playback_device: String,
|
||||||
|
pub sample_rate: u32,
|
||||||
|
pub channels: u16,
|
||||||
|
pub frame_size: usize,
|
||||||
|
pub bitrate: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AudioConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
// 根据 apps/runtime/squashfs-root/etc/asound.conf 分析:
|
||||||
|
// 录音设备对应 pcm.Capture -> hw:0,3 (S32_LE, 48000Hz)
|
||||||
|
// 播放设备对应 pcm.dmixer -> hw:0,2 (S16_LE, 48000Hz)
|
||||||
|
// 使用 "default" 或 "plug" 设备可以由 ALSA 自动处理采样率和格式转换
|
||||||
|
capture_device: "plug:Capture".to_string(),
|
||||||
|
playback_device: "default".to_string(),
|
||||||
|
sample_rate: 16000, // Opus 常用 16k,ALSA plug 会自动从 48k 转换
|
||||||
|
channels: 1,
|
||||||
|
frame_size: 320, // 20ms at 16kHz
|
||||||
|
bitrate: 16000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+61
-7
@@ -1,9 +1,63 @@
|
|||||||
fn main() {
|
mod audio;
|
||||||
println!("Hello from OpenWrt (ARMhf)!");
|
mod config;
|
||||||
println!("System information (via /proc/version):");
|
|
||||||
if let Ok(version) = std::fs::read_to_string("/proc/version") {
|
use crate::audio::{AudioPlayer, AudioRecorder, OpusCodec};
|
||||||
println!("{}", version);
|
use crate::config::AudioConfig;
|
||||||
} else {
|
use anyhow::Result;
|
||||||
println!("Could not read /proc/version (running in simulation?)");
|
use std::collections::VecDeque;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let config = AudioConfig::default();
|
||||||
|
println!("Starting Audio Modular Demo with 1s delay");
|
||||||
|
println!(
|
||||||
|
"Config: capture={}, playback={}, rate={}, channels={}, frame_size={}",
|
||||||
|
config.capture_device,
|
||||||
|
config.playback_device,
|
||||||
|
config.sample_rate,
|
||||||
|
config.channels,
|
||||||
|
config.frame_size
|
||||||
|
);
|
||||||
|
|
||||||
|
let recorder = AudioRecorder::new(&config)?;
|
||||||
|
let player = AudioPlayer::new(&config)?;
|
||||||
|
let mut codec = OpusCodec::new(&config)?;
|
||||||
|
|
||||||
|
// 使用队列存储编码后的 Opus 数据块及录制时间
|
||||||
|
let mut delay_queue: VecDeque<(Instant, Vec<u8>)> = VecDeque::new();
|
||||||
|
let delay_duration = Duration::from_secs(1);
|
||||||
|
|
||||||
|
let mut pcm_in = vec![0i16; config.frame_size * config.channels as usize];
|
||||||
|
let mut opus_buf = vec![0u8; 1024];
|
||||||
|
|
||||||
|
println!("Recording... Delaying playback by 1s. Press Ctrl+C to stop.");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// 1. 录制原始 PCM
|
||||||
|
let read = recorder.read(&mut pcm_in)?;
|
||||||
|
if read == config.frame_size {
|
||||||
|
// 2. 编码并存入队列
|
||||||
|
let opus_len = codec.encode(&pcm_in, &mut opus_buf)?;
|
||||||
|
let opus_data = opus_buf[..opus_len].to_vec();
|
||||||
|
delay_queue.push_back((Instant::now(), opus_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 处理延迟播放:检查队列头部数据是否已等待超过 1s
|
||||||
|
while let Some((timestamp, _)) = delay_queue.front() {
|
||||||
|
if timestamp.elapsed() >= delay_duration {
|
||||||
|
let (_, opus_data) = delay_queue.pop_front().unwrap();
|
||||||
|
|
||||||
|
let mut pcm_out = vec![0i16; config.frame_size * config.channels as usize];
|
||||||
|
let decoded_len = codec.decode(&opus_data, &mut pcm_out)?;
|
||||||
|
|
||||||
|
if decoded_len == config.frame_size {
|
||||||
|
// 4. 播放还原后的 PCM
|
||||||
|
player.write(&pcm_out)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 头部数据还未到 1s,由于队列是按时间排序的,后面的肯定也没到
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+89
-7
@@ -31,17 +31,99 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|||||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||||
RUN rustup target add armv7-unknown-linux-gnueabihf
|
RUN rustup target add armv7-unknown-linux-gnueabihf
|
||||||
|
|
||||||
# 配置 Cargo
|
|
||||||
ENV CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc
|
|
||||||
ENV CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_RUSTFLAGS="-C link-arg=-Wl,-dynamic-linker,/lib/ld-linux-armhf.so.3"
|
|
||||||
|
|
||||||
# 设置 CC
|
|
||||||
ENV CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY run.sh /usr/local/bin/run
|
COPY run.sh /usr/local/bin/run
|
||||||
COPY runtime.sh /usr/local/bin/runtime
|
COPY runtime.sh /usr/local/bin/runtime
|
||||||
RUN chmod +x /usr/local/bin/run /usr/local/bin/runtime
|
RUN chmod +x /usr/local/bin/run /usr/local/bin/runtime
|
||||||
|
|
||||||
|
# 解压 root.squashfs
|
||||||
|
COPY root.squashfs /app/root.squashfs
|
||||||
|
RUN mkdir -p /opt/sysroot \
|
||||||
|
&& unsquashfs -d /opt/sysroot -f /app/root.squashfs \
|
||||||
|
&& rm -rf /app/root.squashfs
|
||||||
|
|
||||||
|
# 创建专门针对 armhf 的源文件
|
||||||
|
# 注意:ubuntu:20.04 默认源里没有 armhf,必须添加 ubuntu-ports 源
|
||||||
|
RUN echo "deb [arch=armhf] http://mirrors.aliyun.com/ubuntu-ports/ focal main restricted universe multiverse" > /etc/apt/sources.list.d/armhf.list && \
|
||||||
|
echo "deb [arch=armhf] http://mirrors.aliyun.com/ubuntu-ports/ focal-updates main restricted universe multiverse" >> /etc/apt/sources.list.d/armhf.list && \
|
||||||
|
# 限制原有的源只针对 amd64,防止冲突
|
||||||
|
sed -i 's/deb http/deb [arch=amd64] http/g' /etc/apt/sources.list
|
||||||
|
|
||||||
|
# 安装 armhf 架构的开发库
|
||||||
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||||
|
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||||
|
dpkg --add-architecture armhf && \
|
||||||
|
apt-get update && apt-get install -y \
|
||||||
|
crossbuild-essential-armhf \
|
||||||
|
libasound2-dev:armhf \
|
||||||
|
libopus-dev:armhf \
|
||||||
|
libssl-dev:armhf
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------------
|
||||||
|
# 构建混合 Sysroot
|
||||||
|
# 目标:结合 OpenWrt 的运行时库 (root.squashfs) 和 Toolchain/Ubuntu 的开发文件
|
||||||
|
# --------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# 修复 OpenWrt sysroot:自动为所有共享库创建 .so 符号链接
|
||||||
|
# OpenWrt 只有 libfoo.so.1,编译链接需要 libfoo.so
|
||||||
|
RUN find /opt/sysroot/lib /opt/sysroot/usr/lib -name "lib*.so.*" | while read src; do \
|
||||||
|
# 获取库名 (例如 libopus.so.0.8.0 -> libopus.so)
|
||||||
|
link_name=$(echo "$src" | sed 's/\.so\..*$/.so/'); \
|
||||||
|
if [ ! -e "$link_name" ]; then \
|
||||||
|
ln -s "$(basename "$src")" "$link_name"; \
|
||||||
|
fi \
|
||||||
|
done
|
||||||
|
|
||||||
|
# 注入 Toolchain 的 C 运行时启动文件 (Scrt1.o, crti.o 等)
|
||||||
|
RUN cp -r /opt/toolchain/arm-linux-gnueabihf/libc/usr/lib/*crt*.o /opt/sysroot/usr/lib/ 2>/dev/null || \
|
||||||
|
cp -r /opt/toolchain/arm-linux-gnueabihf/libc/lib/*crt*.o /opt/sysroot/usr/lib/ 2>/dev/null || true
|
||||||
|
|
||||||
|
# 特殊处理 glibc 的 linker script
|
||||||
|
# OpenWrt rootfs 中没有开发用的 libc.so (linker script) 和 libc_nonshared.a
|
||||||
|
# 我们需要从 toolchain 中复制 libc_nonshared.a,并手动创建 libc.so 脚本
|
||||||
|
# 这样 linker 才能正确处理 libc.so.6 和 ld-linux-armhf.so.3 的依赖关系 (解决 __tls_get_addr 错误)
|
||||||
|
RUN cp /opt/toolchain/arm-linux-gnueabihf/libc/usr/lib/libc_nonshared.a /opt/sysroot/usr/lib/ && \
|
||||||
|
# 删除之前步骤可能自动生成的软链接
|
||||||
|
rm -f /opt/sysroot/usr/lib/libc.so && \
|
||||||
|
# 创建 linker script
|
||||||
|
echo 'OUTPUT_FORMAT(elf32-littlearm)' > /opt/sysroot/usr/lib/libc.so && \
|
||||||
|
echo 'GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux-armhf.so.3 ) )' >> /opt/sysroot/usr/lib/libc.so && \
|
||||||
|
# 同理处理 libpthread.so (如果存在 libpthread.so.0)
|
||||||
|
if [ -f /opt/sysroot/lib/libpthread.so.0 ]; then \
|
||||||
|
cp /opt/toolchain/arm-linux-gnueabihf/libc/usr/lib/libpthread_nonshared.a /opt/sysroot/usr/lib/ 2>/dev/null || true; \
|
||||||
|
rm -f /opt/sysroot/usr/lib/libpthread.so; \
|
||||||
|
echo 'OUTPUT_FORMAT(elf32-littlearm)' > /opt/sysroot/usr/lib/libpthread.so; \
|
||||||
|
echo 'GROUP ( /lib/libpthread.so.0 /usr/lib/libpthread_nonshared.a )' >> /opt/sysroot/usr/lib/libpthread.so; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 注入 Ubuntu 的开发头文件和 pkg-config 定义 (用于 alsa, opus 等)
|
||||||
|
# 我们只取头文件和 .pc 文件,不取库文件,确保链接的是 OpenWrt 的库
|
||||||
|
RUN mkdir -p /opt/sysroot/usr/include /opt/sysroot/usr/lib/pkgconfig && \
|
||||||
|
cp -r /usr/include/alsa /opt/sysroot/usr/include/ 2>/dev/null || true && \
|
||||||
|
cp -r /usr/include/opus /opt/sysroot/usr/include/ 2>/dev/null || true && \
|
||||||
|
cp -r /usr/include/arm-linux-gnueabihf/* /opt/sysroot/usr/include/ 2>/dev/null || true && \
|
||||||
|
cp -r /usr/lib/arm-linux-gnueabihf/pkgconfig/* /opt/sysroot/usr/lib/pkgconfig/ 2>/dev/null || true
|
||||||
|
|
||||||
|
# 优化 Rust 链接参数
|
||||||
|
# 1. 优先使用 -L 指定 sysroot 路径
|
||||||
|
# 2. 使用 -rpath-link 解决库的间接依赖问题
|
||||||
|
# 3. 确保 dynamic-linker 路径正确 (OpenWrt glibc 常用路径)
|
||||||
|
ENV CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc
|
||||||
|
ENV CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_RUSTFLAGS="\
|
||||||
|
-C link-arg=--sysroot=/opt/sysroot \
|
||||||
|
-C link-arg=-Wl,-dynamic-linker,/lib/ld-linux-armhf.so.3 \
|
||||||
|
-C link-arg=-L/opt/sysroot/usr/lib \
|
||||||
|
-C link-arg=-L/opt/sysroot/lib \
|
||||||
|
-C link-arg=-Wl,-rpath-link,/opt/sysroot/usr/lib \
|
||||||
|
-C link-arg=-Wl,-rpath-link,/opt/sysroot/lib"
|
||||||
|
|
||||||
|
# 强制 pkg-config 只在 sysroot 中寻找,忽略宿主机容器路径
|
||||||
|
ENV PKG_CONFIG_ALLOW_CROSS=1
|
||||||
|
ENV PKG_CONFIG_SYSROOT_DIR=/opt/sysroot
|
||||||
|
ENV PKG_CONFIG_LIBDIR=/opt/sysroot/usr/lib/pkgconfig:/opt/sysroot/lib/pkgconfig
|
||||||
|
|
||||||
|
# 设置编译器,强制指定 sysroot
|
||||||
|
ENV CC_armv7_unknown_linux_gnueabihf="arm-linux-gnueabihf-gcc --sysroot=/opt/sysroot"
|
||||||
|
|
||||||
CMD ["/bin/bash"]
|
CMD ["/bin/bash"]
|
||||||
|
|||||||
+8
-14
@@ -12,25 +12,19 @@ PROXY="http://host.docker.internal:7890"
|
|||||||
# --build-arg https_proxy=$PROXY \
|
# --build-arg https_proxy=$PROXY \
|
||||||
# -t open-xiaoai-runtime .
|
# -t open-xiaoai-runtime .
|
||||||
|
|
||||||
# # 2. 编译 hello world demo
|
# 2. 编译 hello world demo
|
||||||
echo "Compiling hello demo..."
|
echo "Compiling hello demo..."
|
||||||
rm -rf ../hello/target # 先清理旧的构建产物,防止缓存干扰
|
|
||||||
docker run --rm -v $(pwd)/../hello:/app/hello open-xiaoai-runtime \
|
docker run --rm -v $(pwd)/../hello:/app/hello open-xiaoai-runtime \
|
||||||
cargo build --manifest-path hello/Cargo.toml --target armv7-unknown-linux-gnueabihf --release
|
cargo build --manifest-path hello/Cargo.toml --target armv7-unknown-linux-gnueabihf --release
|
||||||
|
|
||||||
# # 3. 运行编译出的二进制文件 (通过 QEMU 模拟)
|
# 3. 运行编译出的二进制文件 (通过 QEMU 模拟)
|
||||||
echo "Running compiled binary via QEMU..."
|
# echo "Running compiled binary via QEMU..."
|
||||||
docker run --rm -v $(pwd)/../hello/target/armv7-unknown-linux-gnueabihf/release/hello:/app/hello \
|
# docker run --rm -v $(pwd)/../hello/target/armv7-unknown-linux-gnueabihf/release/hello:/app/hello open-xiaoai-runtime \
|
||||||
-v $(pwd)/root.squashfs:/app/root.squashfs open-xiaoai-runtime \
|
# run /app/hello
|
||||||
run /app/hello
|
|
||||||
|
|
||||||
# 4. 交互模式运行
|
# 4. 交互模式运行
|
||||||
# docker run --rm -it --privileged \
|
# docker run --rm -it --privileged open-xiaoai-runtime run
|
||||||
# -v $(pwd)/root.squashfs:/app/root.squashfs \
|
|
||||||
# open-xiaoai-runtime \
|
|
||||||
# run
|
|
||||||
|
|
||||||
# 5. 上传二进制文件到小爱音箱
|
# 5. 上传二进制文件到小爱音箱
|
||||||
# dd if=target/armv7-unknown-linux-gnueabihf/release/hello \
|
# dd if=apps/hello/target/armv7-unknown-linux-gnueabihf/release/hello \
|
||||||
# | ssh -o HostKeyAlgorithms=+ssh-rsa root@192.168.31.235 "dd of=/data/hello"
|
# | ssh -o HostKeyAlgorithms=+ssh-rsa root@192.168.31.235 "dd of=/data/hello"
|
||||||
# ssh -o HostKeyAlgorithms=+ssh-rsa root@192.168.31.235 "dd if=/data/hello" | hello
|
|
||||||
@@ -5,24 +5,6 @@ set -e
|
|||||||
# 定义 sysroot 目录
|
# 定义 sysroot 目录
|
||||||
SYSROOT_DIR="/opt/sysroot"
|
SYSROOT_DIR="/opt/sysroot"
|
||||||
|
|
||||||
# 如果 /opt/sysroot 已经有内容(通过 -v 挂载了 squashfs-root),则跳过解压
|
|
||||||
if [ -d "$SYSROOT_DIR/bin" ] || [ -d "$SYSROOT_DIR/lib" ]; then
|
|
||||||
echo "Using existing sysroot at $SYSROOT_DIR"
|
|
||||||
else
|
|
||||||
mkdir -p "$SYSROOT_DIR"
|
|
||||||
# 优先使用环境变量指定的 squashfs 文件,否则使用默认的 root.squashfs
|
|
||||||
ROOTFS_FILE="${SYSROOT_FILE:-root.squashfs}"
|
|
||||||
|
|
||||||
if [ -f "$ROOTFS_FILE" ]; then
|
|
||||||
echo -e "\n🔥 Unsquashing $ROOTFS_FILE to $SYSROOT_DIR...\n"
|
|
||||||
unsquashfs -d "$SYSROOT_DIR" -f "$ROOTFS_FILE"
|
|
||||||
echo -e "\n✅ Unsquashed rootfs\n"
|
|
||||||
else
|
|
||||||
echo "Error: Sysroot file $ROOTFS_FILE not found and $SYSROOT_DIR is empty."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查第一个参数。如果在 sysroot 下存在该文件,则自动补全路径
|
# 检查第一个参数。如果在 sysroot 下存在该文件,则自动补全路径
|
||||||
# 比如 `run /bin/sh` 会被转换为 `run /opt/sysroot/bin/sh`
|
# 比如 `run /bin/sh` 会被转换为 `run /opt/sysroot/bin/sh`
|
||||||
if [ -n "$1" ] && [ -f "$SYSROOT_DIR$1" ]; then
|
if [ -n "$1" ] && [ -f "$SYSROOT_DIR$1" ]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user