feat: AudioResampler (#66)
This commit is contained in:
@@ -45,7 +45,7 @@ impl WavWriter {
|
|||||||
* self.header.bit_depth as u32
|
* self.header.bit_depth as u32
|
||||||
* self.header.num_channels as u32);
|
* self.header.num_channels as u32);
|
||||||
|
|
||||||
let block_align = byte_rate / self.header.sample_rate as u32;
|
let block_align = byte_rate as u16 / self.header.sample_rate as u16;
|
||||||
|
|
||||||
self.data.put_slice(b"RIFF");
|
self.data.put_slice(b"RIFF");
|
||||||
self.data.put_u32_le(0); // Placeholder for file size
|
self.data.put_u32_le(0); // Placeholder for file size
|
||||||
@@ -56,7 +56,7 @@ impl WavWriter {
|
|||||||
self.data.put_u16_le(self.header.num_channels);
|
self.data.put_u16_le(self.header.num_channels);
|
||||||
self.data.put_u32_le(self.header.sample_rate);
|
self.data.put_u32_le(self.header.sample_rate);
|
||||||
self.data.put_u32_le(byte_rate);
|
self.data.put_u32_le(byte_rate);
|
||||||
self.data.put_u16_le(32);
|
self.data.put_u16_le(block_align);
|
||||||
self.data.put_u16_le(self.header.bit_depth);
|
self.data.put_u16_le(self.header.bit_depth);
|
||||||
self.data.put_slice(b"data");
|
self.data.put_slice(b"data");
|
||||||
self.data.put_u32_le(0); // Placeholder for data size
|
self.data.put_u32_le(0); // Placeholder for data size
|
||||||
|
|||||||
@@ -36,6 +36,24 @@ message CaptureAudioFrameRequest {
|
|||||||
}
|
}
|
||||||
message CaptureAudioFrameResponse {}
|
message CaptureAudioFrameResponse {}
|
||||||
|
|
||||||
|
// Create a new AudioResampler
|
||||||
|
message NewAudioResamplerRequest {}
|
||||||
|
message NewAudioResamplerResponse {
|
||||||
|
FFIHandleId handle = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remix and resample an audio frame
|
||||||
|
message RemixAndResampleRequest {
|
||||||
|
FFIHandleId resampler_handle = 1;
|
||||||
|
FFIHandleId buffer_handle = 2;
|
||||||
|
uint32 num_channels = 3;
|
||||||
|
uint32 sample_rate = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemixAndResampleResponse {
|
||||||
|
FFIHandleId buffer_handle = 1;
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// AudioFrame buffer ///
|
/// AudioFrame buffer ///
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ message FFIRequest {
|
|||||||
NewAudioStreamRequest new_audio_stream = 16;
|
NewAudioStreamRequest new_audio_stream = 16;
|
||||||
NewAudioSourceRequest new_audio_source = 17;
|
NewAudioSourceRequest new_audio_source = 17;
|
||||||
CaptureAudioFrameRequest capture_audio_frame = 18;
|
CaptureAudioFrameRequest capture_audio_frame = 18;
|
||||||
|
NewAudioResamplerRequest new_audio_resampler = 19;
|
||||||
|
RemixAndResampleRequest remix_and_resample = 20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +74,8 @@ message FFIResponse {
|
|||||||
NewAudioStreamResponse new_audio_stream = 16;
|
NewAudioStreamResponse new_audio_stream = 16;
|
||||||
NewAudioSourceResponse new_audio_source = 17;
|
NewAudioSourceResponse new_audio_source = 17;
|
||||||
CaptureAudioFrameResponse capture_audio_frame = 18;
|
CaptureAudioFrameResponse capture_audio_frame = 18;
|
||||||
|
NewAudioResamplerResponse new_audio_resampler = 19;
|
||||||
|
RemixAndResampleResponse remix_and_resample = 20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::{FfiAsyncId, FfiError, FfiHandle, FfiHandleId, FfiResult};
|
|||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use livekit::prelude::*;
|
use livekit::prelude::*;
|
||||||
use livekit::webrtc::native::yuv_helper;
|
use livekit::webrtc::native::{audio_resampler, yuv_helper};
|
||||||
use livekit::webrtc::prelude::*;
|
use livekit::webrtc::prelude::*;
|
||||||
use livekit::webrtc::video_frame::{native::I420BufferExt, BoxVideoFrameBuffer, I420Buffer};
|
use livekit::webrtc::video_frame::{native::I420BufferExt, BoxVideoFrameBuffer, I420Buffer};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -11,6 +11,7 @@ use prost::Message;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub mod audio_frame;
|
pub mod audio_frame;
|
||||||
pub mod room;
|
pub mod room;
|
||||||
@@ -608,6 +609,83 @@ impl FfiServer {
|
|||||||
Ok(proto::CaptureAudioFrameResponse::default())
|
Ok(proto::CaptureAudioFrameResponse::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_audio_resampler(
|
||||||
|
&'static self,
|
||||||
|
new_resampler: proto::NewAudioResamplerRequest,
|
||||||
|
) -> FfiResult<proto::NewAudioResamplerResponse> {
|
||||||
|
let resampler = audio_resampler::AudioResampler::default();
|
||||||
|
let resampler = Arc::new(Mutex::new(resampler));
|
||||||
|
|
||||||
|
let handle_id = self.next_id() as FfiHandleId;
|
||||||
|
self.ffi_handles.insert(handle_id, Box::new(resampler));
|
||||||
|
|
||||||
|
Ok(proto::NewAudioResamplerResponse {
|
||||||
|
handle: Some(handle_id.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remix_and_resample(
|
||||||
|
&'static self,
|
||||||
|
remix: proto::RemixAndResampleRequest,
|
||||||
|
) -> FfiResult<proto::RemixAndResampleResponse> {
|
||||||
|
let resampler_id = remix
|
||||||
|
.resampler_handle
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(FfiError::InvalidRequest("handle is empty"))?
|
||||||
|
.id as FfiHandleId;
|
||||||
|
|
||||||
|
let resampler = self
|
||||||
|
.ffi_handles
|
||||||
|
.get(&resampler_id)
|
||||||
|
.ok_or(FfiError::InvalidRequest("resampler not found"))?;
|
||||||
|
|
||||||
|
let resampler = resampler
|
||||||
|
.downcast_ref::<Arc<Mutex<audio_resampler::AudioResampler>>>()
|
||||||
|
.ok_or(FfiError::InvalidRequest("handle is not a resampler"))?;
|
||||||
|
|
||||||
|
let resampler = resampler.clone();
|
||||||
|
|
||||||
|
let buffer_id = remix
|
||||||
|
.buffer_handle
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(FfiError::InvalidRequest("handle is empty"))?
|
||||||
|
.id as FfiHandleId;
|
||||||
|
|
||||||
|
let buffer = self
|
||||||
|
.ffi_handles
|
||||||
|
.get(&buffer_id)
|
||||||
|
.ok_or(FfiError::InvalidRequest("buffer not found"))?;
|
||||||
|
|
||||||
|
let buffer = buffer
|
||||||
|
.downcast_ref::<AudioFrame>()
|
||||||
|
.ok_or(FfiError::InvalidRequest("handle is not a buffer"))?;
|
||||||
|
|
||||||
|
let mut resampler = resampler.lock();
|
||||||
|
let data = resampler.remix_and_resample(
|
||||||
|
&buffer.data,
|
||||||
|
buffer.samples_per_channel,
|
||||||
|
buffer.num_channels,
|
||||||
|
buffer.sample_rate,
|
||||||
|
remix.num_channels,
|
||||||
|
remix.sample_rate,
|
||||||
|
);
|
||||||
|
|
||||||
|
let samples_per_channel = data.len() / remix.num_channels as usize;
|
||||||
|
let frame = AudioFrame {
|
||||||
|
data: data.to_owned(), // Copy?
|
||||||
|
num_channels: remix.num_channels,
|
||||||
|
samples_per_channel: samples_per_channel as u32,
|
||||||
|
sample_rate: remix.sample_rate,
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle_id = self.next_id() as FfiHandleId;
|
||||||
|
self.ffi_handles.insert(handle_id, Box::new(frame));
|
||||||
|
|
||||||
|
Ok(proto::RemixAndResampleResponse {
|
||||||
|
buffer_handle: Some(handle_id.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_request(
|
pub fn handle_request(
|
||||||
&'static self,
|
&'static self,
|
||||||
request: proto::FfiRequest,
|
request: proto::FfiRequest,
|
||||||
@@ -672,6 +750,12 @@ impl FfiServer {
|
|||||||
proto::ffi_request::Message::CaptureAudioFrame(push) => {
|
proto::ffi_request::Message::CaptureAudioFrame(push) => {
|
||||||
proto::ffi_response::Message::CaptureAudioFrame(self.on_capture_audio_frame(push)?)
|
proto::ffi_response::Message::CaptureAudioFrame(self.on_capture_audio_frame(push)?)
|
||||||
}
|
}
|
||||||
|
proto::ffi_request::Message::NewAudioResampler(new_res) => {
|
||||||
|
proto::ffi_response::Message::NewAudioResampler(self.new_audio_resampler(new_res)?)
|
||||||
|
}
|
||||||
|
proto::ffi_request::Message::RemixAndResample(remix) => {
|
||||||
|
proto::ffi_response::Message::RemixAndResample(self.remix_and_resample(remix)?)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ pub mod video_stream;
|
|||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub mod native {
|
pub mod native {
|
||||||
|
pub use crate::imp::audio_resampler;
|
||||||
pub use crate::imp::yuv_helper;
|
pub use crate::imp::yuv_helper;
|
||||||
pub use webrtc_sys::webrtc::ffi::create_random_uuid;
|
pub use webrtc_sys::webrtc::ffi::create_random_uuid;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use cxx::UniquePtr;
|
||||||
|
use webrtc_sys::audio_resampler as sys_ar;
|
||||||
|
|
||||||
|
pub struct AudioResampler {
|
||||||
|
sys_handle: UniquePtr<sys_ar::ffi::AudioResampler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AudioResampler {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
sys_handle: sys_ar::ffi::create_audio_resampler(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioResampler {
|
||||||
|
pub fn remix_and_resample(
|
||||||
|
&mut self,
|
||||||
|
src: &[i16],
|
||||||
|
samples_per_channel: u32,
|
||||||
|
num_channels: u32,
|
||||||
|
sample_rate: u32,
|
||||||
|
dst_num_channels: u32,
|
||||||
|
dst_sample_rate: u32,
|
||||||
|
) -> &[i16] {
|
||||||
|
unsafe {
|
||||||
|
let len = self.sys_handle.pin_mut().remix_and_resample(
|
||||||
|
src.as_ptr(),
|
||||||
|
samples_per_channel as usize,
|
||||||
|
num_channels as usize,
|
||||||
|
sample_rate as i32,
|
||||||
|
dst_num_channels as usize,
|
||||||
|
dst_sample_rate as i32,
|
||||||
|
);
|
||||||
|
|
||||||
|
std::slice::from_raw_parts(self.sys_handle.data(), len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod audio_resampler;
|
||||||
pub mod audio_source;
|
pub mod audio_source;
|
||||||
pub mod audio_stream;
|
pub mod audio_stream;
|
||||||
pub mod data_channel;
|
pub mod data_channel;
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
/cmake-build-debug
|
|
||||||
@@ -149,6 +149,7 @@ fn main() {
|
|||||||
"src/video_frame_buffer.rs",
|
"src/video_frame_buffer.rs",
|
||||||
"src/yuv_helper.rs",
|
"src/yuv_helper.rs",
|
||||||
"src/helper.rs",
|
"src/helper.rs",
|
||||||
|
"src/audio_resampler.rs",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
builder.files(&[
|
builder.files(&[
|
||||||
@@ -169,6 +170,7 @@ fn main() {
|
|||||||
"src/video_encoder_factory.cpp",
|
"src/video_encoder_factory.cpp",
|
||||||
"src/video_decoder_factory.cpp",
|
"src/video_decoder_factory.cpp",
|
||||||
"src/audio_device.cpp",
|
"src/audio_device.cpp",
|
||||||
|
"src/audio_resampler.cpp",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for include in includes {
|
for include in includes {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 LiveKit
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the “License”);
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an “AS IS” BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "api/audio/audio_frame.h"
|
||||||
|
#include "api/data_channel_interface.h"
|
||||||
|
#include "common_audio/resampler/include/push_resampler.h"
|
||||||
|
#include "livekit/webrtc.h"
|
||||||
|
#include "rust/cxx.h"
|
||||||
|
|
||||||
|
namespace livekit {
|
||||||
|
|
||||||
|
class AudioResampler {
|
||||||
|
public:
|
||||||
|
size_t remix_and_resample(const int16_t* src,
|
||||||
|
size_t samples_per_channel,
|
||||||
|
size_t num_channels,
|
||||||
|
int sample_rate_hz,
|
||||||
|
size_t dest_num_channels,
|
||||||
|
int dest_sample_rate_hz);
|
||||||
|
|
||||||
|
const int16_t* data() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
webrtc::AudioFrame frame_;
|
||||||
|
webrtc::PushResampler<int16_t> resampler_;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<AudioResampler> create_audio_resampler();
|
||||||
|
|
||||||
|
} // namespace livekit
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 LiveKit
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the “License”);
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an “AS IS” BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "livekit/audio_resampler.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "audio/remix_resample.h"
|
||||||
|
|
||||||
|
namespace livekit {
|
||||||
|
|
||||||
|
size_t AudioResampler::remix_and_resample(const int16_t* src,
|
||||||
|
size_t samples_per_channel,
|
||||||
|
size_t num_channels,
|
||||||
|
int sample_rate,
|
||||||
|
size_t dest_num_channels,
|
||||||
|
int dest_sample_rate) {
|
||||||
|
frame_.num_channels_ = dest_num_channels;
|
||||||
|
frame_.sample_rate_hz_ = dest_sample_rate;
|
||||||
|
webrtc::voe::RemixAndResample(src, samples_per_channel, num_channels,
|
||||||
|
sample_rate, &resampler_, &frame_);
|
||||||
|
|
||||||
|
return frame_.num_channels() * frame_.samples_per_channel();
|
||||||
|
}
|
||||||
|
|
||||||
|
const int16_t* AudioResampler::data() const {
|
||||||
|
return frame_.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AudioResampler> create_audio_resampler() {
|
||||||
|
return std::make_unique<AudioResampler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace livekit
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
use crate::impl_thread_safety;
|
||||||
|
|
||||||
|
#[cxx::bridge(namespace = "livekit")]
|
||||||
|
pub mod ffi {
|
||||||
|
unsafe extern "C++" {
|
||||||
|
include!("livekit/audio_resampler.h");
|
||||||
|
|
||||||
|
type AudioResampler;
|
||||||
|
|
||||||
|
unsafe fn remix_and_resample(
|
||||||
|
self: Pin<&mut AudioResampler>,
|
||||||
|
src: *const i16,
|
||||||
|
samples_per_channel: usize,
|
||||||
|
num_channels: usize,
|
||||||
|
sample_rate: i32,
|
||||||
|
dst_num_channels: usize,
|
||||||
|
dst_sample_rate: i32,
|
||||||
|
) -> usize;
|
||||||
|
|
||||||
|
unsafe fn data(self: &AudioResampler) -> *const i16;
|
||||||
|
|
||||||
|
fn create_audio_resampler() -> UniquePtr<AudioResampler>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_thread_safety!(ffi::AudioResampler, Send + Sync);
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod audio_resampler;
|
||||||
pub mod candidate;
|
pub mod candidate;
|
||||||
pub mod data_channel;
|
pub mod data_channel;
|
||||||
pub mod helper;
|
pub mod helper;
|
||||||
|
|||||||
Reference in New Issue
Block a user