在WebRTC某些业务场景中,需要对实时视频通话中的音视频进行录制并存储,用于后续的质检,由于视频容量太大,采用前端进行音频录制模式是一种较为轻量并且松耦合的方案。
1. Jitsi 中的音频流提取
Jitsi 是一个开源的 WebRTC 实时通信框架,它提供了用于音视频通信的完整解决方案,包括信令、媒体处理、录制等功能。Jitsi 使用 Jitsi Videobridge 作为视频流的路由和转发组件,Jitsi Meet 作为前端应用。音频流通常通过 MediaStream 进行传输和处理,在 Jitsi Meet 中,音频流的处理也会通过相关的 WebRTC API 完成。
1.1 音频流的提取
在 Jitsi 框架中,音频流由 WebRTC 连接中的 MediaStream 对象承载。当用户加入会议时,可以通过 getUserMedia() 或者通过 WebRTC 内部的媒体流进行音频数据的提取。通过 MediaStream 中的音频轨道,我们可以访问音频数据。
要获取正在进行的 Jitsi 视频会议中的音频数据流,首先需要在 Jitsi Meet 中获得对应的视频和音频流。在 Jitsi Meet 的前端代码中,可以通过以下方式获取音频流:
- const localStream = JitsiMeetJS.createLocalTracks({ devices: ['audio', 'video'] })
-
- .then(tracks => {
-
- const audioTrack = tracks.find(track => track.getType() === 'audio');
-
- const videoTrack = tracks.find(track => track.getType() === 'video');
-
- // 我们只需要音频,使用 audioTrack就足够
-
- });
上面的代码片段展示了如何在前端获取音频轨道。Jitsi 提供了 createLocalTracks() 方法来创建本地音视频轨道。此时,我们可以在会话过程中动态地获取当前的音频轨道。
1.2 通过 AudioContext 处理音频数据
通过 AudioContext API,可以获取音频流的原始音频数据,并通过 JavaScript 进行处理。这对于音频数据的分段录制、实时分析等功能非常有用。
- let audioContext = new (window.AudioContext || window.webkitAudioContext)();let analyser = audioContext.createAnalyser();let source = audioContext.createMediaStreamSource(localStream);
-
- source.connect(analyser);
-
- // 配置音频数据的大小(比如每次获取2048个数据点)
-
- analyser.fftSize = 2048;let bufferLength = analyser.frequencyBinCount;let dataArray = new Uint8Array(bufferLength);
-
- // 定时获取音频数据function getAudioData() {
-
- analyser.getByteFrequencyData(dataArray);
-
- // 这里可以做音频数据的处理,比如上传、分析等操作
-
- }setInterval(getAudioData, 100); // 每 100 毫秒获取一次音频数据
2. 音频数据的上传
在 Jitsi 中,音频数据通过 MediaStream 获取后,可以进一步通过 Web Audio API(如上所示的 AudioContext)实时分析。对于存储目的,我们可以将这些音频数据按段上传至后台,并存储在 Ceph 中。
2.1 将音频数据上传到后台
一旦我们从音频流中提取到数据,我们可以通过 Blob 或 FormData 将音频片段发送到后台。每段音频将作为一个单独的文件上传。
- function uploadAudioData(dataArray) {
-
- let audioBlob = new Blob([dataArray], { type: 'audio/wav' });
-
-
-
- let formData = new FormData();
-
- formData.append('audio', audioBlob, 'audio_chunk.wav');
-
-
-
- fetch('/upload-audio', {
-
- method: 'POST',
-
- body: formData
-
- })
-
- .then(response => response.json())
-
- .then(data => {
-
- console.log('音频片段上传成功', data);
-
- })
-
- .catch(error => {
-
- console.error('音频片段上传失败', error);
-
- });
-
- }
在这个方法中,uploadAudioData() 会把音频数据作为一个 Blob 封装,并上传到后台。为了适应大规模的数据量,我们可以将音频数据按片段进行存储和上传。
3. Java 后端与 Ceph 存储
后端处理过程中,我们使用 Java 服务端接收上传的音频数据,并通过 S3 协议将音频片段存储到 Ceph 中。以下是完整的实现步骤。
3.1 Java 后端配置
在 Java 后端,我们使用 AWS S3 客户端(也适用于 Ceph 的 S3 协议)来存储音频文件。我们需要设置 S3 客户端连接到 Ceph,并通过 PutObjectRequest 将音频文件上传。
- <dependency>
-
- <groupId>com.amazonawsgroupId>
-
- <artifactId>aws-java-sdk-s3artifactId>
-
- <version>1.12.84version>
- dependency>
3.2 配置 S3 上传功能
- import com.amazonaws.auth.BasicAWSCredentials;import com.amazonaws.services.s3.AmazonS3;import com.amazonaws.services.s3.AmazonS3Client;import com.amazonaws.services.s3.model.ObjectMetadata;import com.amazonaws.services.s3.model.PutObjectRequest;
-
- import java.io.InputStream;import java.io.ByteArrayInputStream;
-
- public class S3Uploader {
-
-
-
- private static final String ENDPOINT = "http://
:" ; -
- private static final String ACCESS_KEY = "
" ; -
- private static final String SECRET_KEY = "
" ; -
- private static final String BUCKET_NAME = "
" ; -
-
-
- private AmazonS3 s3Client;
-
-
-
- public S3Uploader() {
-
- BasicAWSCredentials credentials = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY);
-
- s3Client = AmazonS3Client.builder()
-
- .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(ENDPOINT, "us-east-1"))
-
- .withCredentials(new AWSStaticCredentialsProvider(credentials))
-
- .build();
-
- }
-
-
-
- public void uploadAudio(String fileName, byte[] audioData) {
-
- InputStream inputStream = new ByteArrayInputStream(audioData);
-
- ObjectMetadata metadata = new ObjectMetadata();
-
- metadata.setContentLength(audioData.length);
-
-
-
- // 上传音频数据到 Ceph 存储
-
- PutObjectRequest putRequest = new PutObjectRequest(BUCKET_NAME, fileName, inputStream, metadata);
-
- s3Client.putObject(putRequest);
-
- System.out.println("音频片段上传成功: " + fileName);
-
- }
-
- }
3.3 处理上传请求
接收来自前端的音频数据,并上传至 Ceph 存储。此处,sessionId 用来区分每个会话的音频数据,确保可以按照会话进行音频存储。
- import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;
-
- @RestController@RequestMapping("/upload-audio")public class AudioUploadController {
-
-
-
- private final S3Uploader s3Uploader = new S3Uploader();
-
-
-
- @PostMapping
-
- public String uploadAudio(@RequestParam("audio") MultipartFile audioFile,
-
- @RequestParam("sessionId") String sessionId) {
-
- try {
-
- byte[] audioData = audioFile.getBytes();
-
- String fileName = "audio/" + sessionId + "_audio_" + System.currentTimeMillis() + ".wav"; // 使用会话 ID 和时间戳生成文件名
-
-
-
- // 上传音频到 Ceph
-
- s3Uploader.uploadAudio(fileName, audioData);
-
-
-
- return "音频上传成功";
-
- } catch (Exception e) {
-
- e.printStackTrace();
-
- return "音频上传失败";
-
- }
-
- }
-
- }
4. 音频分段处理
为了更好地控制存储和上传,音频数据可以根据预定的时长进行分段。例如,每 10 秒一个音频片段进行上传。在前端代码中,可以通过定时任务或特定的时机,将每一段音频数据独立上传至后台。
- let audioDataBuffer = [];let segmentDuration = 10 * 1000; // 每 10 秒一个段
-
- function startAudioCapture() {
-
- setInterval(function() {
-
- if (audioDataBuffer.length > 0) {
-
- uploadAudioData(audioDataBuffer);
-
- audioDataBuffer = []; // 清空缓存
-
- }
-
- }, segmentDuration); // 每 10 秒上传一次
-
- }
5. 总结
通过 Jitsi 框架,我们可以实时获取视频通信中的音频数据,并通过 Web Audio API 进行音频数据提取和处理。每段音频数据可以按预定的时长进行分段
评论记录:
回复评论: