音声フレームを取得する
SkyWay Linux®︎ SDK (以下、Linux SDK ) では、v3.2.0 から音声フレームの取得が行えます。
注意 本ドキュメントはクイックスタートを実施済みの方を対象としております。
利用方法
Subscribeした AudioStream ごとに音声データとして出力することができます。
以降の説明では、表記の簡略化のため、 skyway::media::stream::remote 名前空間を省略します。
音声データは RemoteAudioStream::Listener を継承したクラスを RemoteAudioStream::RegisterListener に登録することで
RemoteAudioStream::Listener::OnTrackData から取得することができます。
OnTrackDataは10ms 周期で呼び出されます。
class AudioTrackListener : public skyway::media::stream::remote::RemoteAudioStream::Listener {
public:
AudioTrackListener();
// データを取得した際、こちらのメソッドがコールバックとして呼ばれます。
// 引数には受信したデータが入ります。
void OnTrackData(const skyway::media::audio::interface::AudioFrame& audio_frame) override;
};ダミースピーカーの利用
注意 音声を取得したい場合は何らかのデバイスをSetPlayoutDeviceで選択する必要があります。
PulseAudioが無効な環境においても、DummySpeakerを選択可能です。
音声の入力とは異なり、PulseAudioが有効な場合においても、デバイスで音声を再生しながら音声データを取得することができます。 一方で、デバイスで音声を再生したくない場合は、以下のようにDummySpeakerを利用できます。
auto devices = skyway::media::DeviceManager::GetPlayoutDevices();
auto device = std::find_if(devices.begin(), devices.end(),
[this](const skyway::media::DeviceManager::AudioDevice& device) {
return device.name == "DummySpeaker";
});
skyway::media::DeviceManager::SetPlayoutDevice(*device);サンプルコード
以下は、クイックスタートで作成するアプリを元にしたサンプルコードです。
このサンプルでは、Subscribeした AudioStream ごとに音声データを取得し、WAVファイルとして保存します。
まず、 RemoteAudioStream::Listener を継承したクラスを作成します。
// pcm_wav_recorder.hpp
#include <memory>
#include <mutex>
#include <string>
#include "skyway/media/audio/interface/audio_frame.hpp"
#include "skyway/media/stream/remote/remote_audio_stream.hpp"
#include "wav_writer.hpp"
class PcmWavRecorder : public skyway::media::stream::remote::RemoteAudioStream::Listener {
public:
PcmWavRecorder(std::string publication_id);
void OnTrackData(const skyway::media::audio::interface::AudioFrame& audio_frame) override;
private:
void EnsureOpened_(const skyway::media::audio::interface::AudioFrame& audio_frame);
private:
mutable std::mutex mtx_;
std::string output_filepath_;
std::unique_ptr<WavWriter> writer_;
bool initialized_ = false;
};// pcm_wav_recorder.cpp
#include "pcm_wav_recorder.hpp"
#include <chrono>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
PcmWavRecorder::PcmWavRecorder(std::string publication_id)
: output_filepath_(publication_id + ".wav") {}
void PcmWavRecorder::OnTrackData(const skyway::media::audio::interface::AudioFrame& frame) {
{
std::lock_guard<std::mutex> lg(mtx_);
if (!initialized_) {
EnsureOpened_(frame);
initialized_ = true;
}
if (!writer_ || !writer_->IsWritable()) {
return;
}
writer_->Write(
frame.buffer,
static_cast<size_t>(frame.samples_per_channel) * static_cast<size_t>(frame.channels));
}
}
void PcmWavRecorder::EnsureOpened_(const skyway::media::audio::interface::AudioFrame& frame) {
if (writer_) {
return;
}
writer_ = std::make_unique<WavWriter>(output_filepath_);
if (!writer_->IsWritable()) {
writer_.reset();
return;
}
WavFormat fmt;
fmt.sample_rate = frame.sample_rate;
fmt.channels = frame.channels;
fmt.bits_per_sample = 16;
writer_->SetOutputWavFormat(fmt);
}次にWAVファイルへ書き出すクラスを作成します。
// wav_writer.hpp
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <string>
inline void WriteLe16(unsigned char* dst, uint16_t v) {
dst[0] = static_cast<unsigned char>(v & 0xFF);
dst[1] = static_cast<unsigned char>((v >> 8) & 0xFF);
}
inline void WriteLe32(unsigned char* dst, uint32_t v) {
dst[0] = static_cast<unsigned char>(v & 0xFF);
dst[1] = static_cast<unsigned char>((v >> 8) & 0xFF);
dst[2] = static_cast<unsigned char>((v >> 16) & 0xFF);
dst[3] = static_cast<unsigned char>((v >> 24) & 0xFF);
}
struct WavFormat {
uint32_t sample_rate;
uint16_t channels;
uint16_t bits_per_sample;
};
class WavWriter {
public:
WavWriter(const std::string& path);
~WavWriter();
bool IsWritable() const { return ofs_.is_open(); };
bool SetOutputWavFormat(const WavFormat& fmt);
bool Write(const void* samples, size_t num_samples);
private:
bool WriteHeaderSkeleton();
void FinalizeHeader();
std::ofstream ofs_;
WavFormat fmt_;
bool header_written_ = false;
uint32_t data_bytes_ = 0;
};// wav_writer.cpp
#include "wav_writer.hpp"
#include <cstring>
#include <iostream>
WavWriter::WavWriter(const std::string& path) {
ofs_.open(path, std::ios::binary);
if (!ofs_) {
std::cerr << "- [Error] WavWriter: failed to open file: " << path << std::endl;
return;
}
}
WavWriter::~WavWriter() {
if (this->IsWritable()) {
FinalizeHeader();
}
}
bool WavWriter::SetOutputWavFormat(const WavFormat& fmt) {
if (!this->IsWritable() || header_written_) {
return false;
}
if (fmt.sample_rate == 0 || fmt.channels == 0 || fmt.bits_per_sample == 0) {
std::cerr << "- [Error] WavWriter: invalid format\n" << std::endl;
return false;
}
fmt_ = fmt;
header_written_ = WriteHeaderSkeleton();
return header_written_;
}
bool WavWriter::Write(const void* samples, size_t num_samples) {
if (!this->IsWritable() || !header_written_ || !samples || num_samples == 0) {
return false;
}
const std::streamsize bytes = static_cast<std::streamsize>(num_samples * sizeof(int16_t));
ofs_.write(reinterpret_cast<const char*>(samples), bytes);
if (!ofs_) {
return false;
}
data_bytes_ += static_cast<uint32_t>(bytes);
return true;
}
bool WavWriter::WriteHeaderSkeleton() {
unsigned char hdr[44] = {0};
std::memcpy(hdr + 0, "RIFF", 4);
std::memcpy(hdr + 8, "WAVE", 4);
std::memcpy(hdr + 12, "fmt ", 4);
WriteLe32(hdr + 16, 16); // Subchunk1Size
WriteLe16(hdr + 20, 1); // AudioFormat
WriteLe16(hdr + 22, fmt_.channels); // NumChannels
WriteLe32(hdr + 24, fmt_.sample_rate); // SampleRate
const uint32_t byte_rate = fmt_.sample_rate * fmt_.channels * (fmt_.bits_per_sample / 8);
WriteLe32(hdr + 28, byte_rate);
const uint16_t block_align = static_cast<uint16_t>(fmt_.channels * (fmt_.bits_per_sample / 8));
WriteLe16(hdr + 32, block_align);
WriteLe16(hdr + 34, fmt_.bits_per_sample);
std::memcpy(hdr + 36, "data", 4);
ofs_.write(reinterpret_cast<const char*>(hdr), static_cast<std::streamsize>(sizeof(hdr)));
return ofs_.good();
}
void WavWriter::FinalizeHeader() {
if (!ofs_ || !header_written_) {
return;
}
unsigned char u32[4];
WriteLe32(u32, data_bytes_);
ofs_.seekp(40, std::ios::beg);
ofs_.write(reinterpret_cast<const char*>(u32), 4);
WriteLe32(u32, 36u + data_bytes_);
ofs_.seekp(4, std::ios::beg);
ofs_.write(reinterpret_cast<const char*>(u32), 4);
ofs_.seekp(0, std::ios::end);
}次に、今回のサンプルで作成したファイルを CMakeLists.txt に追加します。
add_executable を以下に変更してください。
add_executable(app.out main.cpp example_room.cpp pcm_wav_recorder.cpp wav_writer.cpp)次に、クイックスタートで作成した example_room.hpp に以下のincludeディレクティブを追加してください。
#include "pcm_wav_recorder.hpp"その後、作成する PcmWavRecorder を保持するためのメンバ変数として以下を追加してください。
std::unordered_map<std::string,
std::shared_ptr<PcmWavRecorder>> recorders_;次に、 example_room.cpp に以下のincludeディレクティブを追加してください。
#include <skyway/media/stream/remote/remote_audio_stream.hpp>その後、Subscribeした AudioStream から RemoteAudioStream を取得し、上記で作成した PcmWavRecorder を登録します。
AudioのSubscribe処理を以下のように変更してください。
// Subscribe
if (publication->ContentType() == skyway::model::ContentType::kAudio) {
auto devices = skyway::media::DeviceManager::GetPlayoutDevices();
auto device = std::find_if(devices.begin(), devices.end(),
[this](const skyway::media::DeviceManager::AudioDevice& device) {
return device.name == "DummySpeaker";
});
skyway::media::DeviceManager::SetPlayoutDevice(*device);
auto subscription = room_member_->Subscribe(publication->Id(), subscription_options);
auto audio_stream = std::dynamic_pointer_cast<skyway::media::stream::remote::RemoteAudioStream>(subscription->Stream());
auto recorder = std::make_shared<PcmWavRecorder>(publication->Id());
audio_stream->RegisterListener(recorder);
recorders_.emplace(publication->Id(), std::move(recorder));
}RemoteAudioStream::RegisterListener に登録された PcmWavRecorder の OnTrackDataメソッドが
音声データが到着するたびに呼び出され、音声データをWAVファイルとして書き出します。
商標
Linux®︎は、米国およびその他の国における Linus Torvalds の登録商標です。