0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【新機能レポート】Zoom Video SDK for Webで「音声ピッチシフト」してみた 〜Raw Data Processorの可能性〜

Posted at

こんにちは🌱
今年に入って、Zoom Video SDK for Web のバージョン 2.1.5 から Raw Data Processor 機能が加わりました。
このアップデートで、いままで Web SDK でやりたかったことが、もっと身近にできるようになっています☺️

今回は音声(オーディオ)ピッチシフトを例に、「どんな拡張ができるのか?」を、実コードとGitHubサンプルを交えてご紹介します。(映像のリアルタイム加工も、同じ仕組みで実装可能です。新しい可能性が広がりますね✨)

できるようになったこと🪄

  • オーディオ🎤:リアルタイムで声のピッチを変えたり、ノイズを除去したり、さまざまなエフェクトをかけることができます
  • ビデオ🎥:ウォーターマークや映像加工、モザイク、色味の補正も自由に行えます(公式サンプルはウォーターマークのみですが、自作エフェクトも追加できます)

これらがすべてWebブラウザだけで完結します💻

使い方の概要📝

カスタムProcessor用のJSファイルを用意します。クライアントJからcreateProcessor/addProcessorでストリームに組み込みます。

Raw Dataって?カスタムプロセッサーって?🧐

Raw Dataとは…

Zoom Video SDK セッション内の音声や映像を「圧縮される前の生データ(PCMや画像フレーム)」として直接扱える仕組みのことです。これにより、フレームごと・音サンプルごとに好きな処理を加えることができるようになりました。

カスタムプロセッサーとは...

Zoom SDK から渡された Raw Data を自分の好きなロジックで加工するクラスのこと。

  • AudioProcessor(音声)、VideoProcessor(映像)クラスを継承し
  • process()メソッドで、毎フレームinputsからデータを受け取ってoutputsに書き出します

実際の動作イメージはWebAudioのAudioWorkletProcessorに近いですが、Zoom SDK独自のサンドボックス実行環境で安全・安定に分離されています🍀

シンプルなピッチシフトのサンプル🎙️

まずは初心者向けの最小サンプルとして、VanillaなJSで完結したカスタムピッチシフターを紹介します💪

class PitchShiftProcessor extends AudioProcessor {
  constructor(port, options) {
    super(port, options);
    //音声データを一時的に保存するリングバッファ(循環バッファ)のサイズ
    //44100Hzの場合11025サンプルは約0.25秒ぶん
    this.bufferSize = 11025;
    //バッファサイズ分だけの実際の音声データ記憶用配列(型はFloat32の配列)
    this.buffer = new Float32Array(this.bufferSize);
    //次に音声データを書き込むバッファの位置(インデックス)
    this.writePos = 0;
    //ピッチシフト処理時に読み出す現在のバッファ位置 小数点も含む(補間用)
    this.readPos = 0.0;
    //ピッチの倍率 1.0だと原音 大きいほど高い音に
    this.pitchRatio = options?.pitchRatio || 1.3;
    //フォルマント比(声の音色や響き)
    //ピッチだけ上げるとロボ声になりやすいがフォルマントも調整すると「自然な声質」に
    this.formantRatio = options?.formantRatio || 1.1;
    //0だとエフェクトなしの原音のみ、1.0だと完全にエフェクト音のみ
    this.dryWet = options?.dryWet || 0.7;
    //簡易ハイパスフィルタ alphaはフィルタ強度のパラメータ
    this.hpf = { prevIn: 0, prevOut: 0, alpha: 0.86 };
  }

  process(inputs, outputs) {
    const input = inputs[0];
    const output = outputs[0];
    if (input.length === 0 || !input[0]) return true;
    const inputChannel = input[0];
    const outputChannel = output[0];

    // write buffer
    for (let i = 0; i < inputChannel.length; i++) {
      this.buffer[this.writePos] = inputChannel[i];
      this.writePos = (this.writePos + 1) % this.bufferSize;
    }
    // pitch shift process
    for (let i = 0; i < outputChannel.length; i++) {
      let readPos = this.readPos % this.bufferSize;
      if (readPos < 0) readPos += this.bufferSize;
      const intPos = Math.floor(readPos);
      const frac = readPos - intPos;
      const nextPos = (intPos + 1) % this.bufferSize;
      const raw = this.buffer[intPos] * (1 - frac) + this.buffer[nextPos] * frac;

      // simple HPF
      const filtered = raw - this.hpf.prevIn + this.hpf.alpha * this.hpf.prevOut;
      this.hpf.prevIn = raw;
      this.hpf.prevOut = filtered;

      outputChannel[i] = filtered * this.dryWet + raw * (1 - this.dryWet);

      this.readPos += this.pitchRatio;
      if (this.readPos >= this.bufferSize) {
        this.readPos -= this.bufferSize;
        this.writePos = 0;
      }
    }
    return true;
  }
}
registerProcessor('pitch-shift-processor', PitchShiftProcessor);

Zoom SDKでProcessorを使う手順🫰🏼

  1. 上記ファイルをpublic/pitch-shift-processor.jsとして配置
  2. クライアントJSファイルなどから以下のように呼び出します
const processorParams = {
  name: 'pitch-shift-processor',
  type: 'audio',
  url: window.location.origin + '/pitch-shift-processor.js',//パスを合わせる
  options: { pitchRatio: 1.3 }
};
const processor = await stream.createProcessor(processorParams);
await stream.addProcessor(processor);

これだけで、「Video SDK で話す声が、ピッチシフトされた状態」で他参加者に届きます😊

Github サンプル

ここまでの内容を、こちらのサンプルでまとめましたのでよかったらCloneして試してみてください。ローカルでも試せます。(当然ですが、声を聞くには2拠点必要です😆)

応用編:importを使った本格的な開発手順

Video SDK Web の Raw Data Processor ではサンドボックス環境での実行が前提となっています。
そのため外部ライブラリ、例えば npm パッケージを使ってより本格的な加工などを行いたい場合、外部からの import ができないので、Webpack や Vite などを使って1ファイルにバンドルした上で読み込ませる必要があります。

構成例

my-zoom-processor-project/
├── package.json
├── vite.config.ts
├── processors/
│   └── pitch-shift-processor.ts
├── public/
│   └── pitch-shift-processor.js
├── src/
│   └── index.js

ステップ

1. 初期化・必要なパッケージの導入

npm init -y
npm install vite @soundtouchjs/audio-worklet

2. TypeScript/JSでProcessorを実装(例: processors/pitch-shift-processor.ts)

import { SoundTouch } from 'soundtouchjs';

class PitchShiftProcessor extends AudioProcessor {
  constructor(port, options) {
    super(port, options);
    this.soundTouch = new SoundTouch();
    // ...
  }
  process(inputs, outputs) {
    // SoundTouchで処理
  }
}
registerProcessor('pitch-shift-processor', PitchShiftProcessor);

3. Vite や Webpack でバンドル

// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
  build: {
    lib: {
      entry: 'processors/pitch-shift-processor.ts',
      formats: ['es'],
      fileName: () => 'pitch-shift-processor.js',
    },
    outDir: 'public',
    emptyOutDir: false,
    minify: false,
  },
});
npx vite build

Zoom 公式サンプルのビデオ加工

Zoom 公式の Github では、より具体的なビデオの加工も紹介しています。

このサンプルでは、@mediapipe/tasks-vision(Googleの顔検出AIライブラリ)をnpm importしてビルドし、「顔の位置を検出→ビデオストリームにマスクやウォーターマークを描画」など、映像の高度なリアルタイム加工も実装されています。

Screenshot 2025-06-04 at 17.42.18.png

ZoomDualMaskVideoProcessor.ts

import { FaceDetector, FilesetResolver } from '@mediapipe/tasks-vision';

のように、import を使って顔認識→canvas描画を行い、Viteで1ファイルにバンドルしてpublic配下でProcessorとして動かしています。

注意

  • ProcessorのJSは最終的に「自己完結型1ファイル」にバンドルする必要があります
  • 一部npmパッケージ(native依存やWebWorker系など)は動かないことがあるので注意です
  • OSSを組み込む場合はライセンス表記も忘れずに

まとめ

Zoom Video SDK for WebのRaw Data Processorのおかげで、Web会議でも音声・映像に自由な拡張やAI活用ができるようになりました✨
初心者の方も、npmパッケージを活用したい方も、この仕組みを使って新しいコミュニケーション体験を一緒に広げていきましょう。

困った時はお気軽にコメントやDMで相談してくださいね🍀

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?