Linux オーディオ コレクションとローカライズされたプラットフォームで遭遇する落とし穴 (2)
ALSA収集は行き詰まっているので他の方法を試すしかありませんが、ここでは国内プラットフォーム上でマイクとシステムサウンドを収集する機能をPulseAudioのインターフェースを通じて実現することに成功しました。
linux PulseAudio オーディオ コレクション
まず、PulseAudio と ALSA の違いは、ALSA がカーネル レベルであるのに対し、PulseAudio はユーザー レベルのサービスであり、ALSA と同様に、アプリケーションのさまざまなオーディオ入出力を管理するサウンド サーバーとして使用されることです。ほとんどの Linux ディストリビューションには、デフォルトで PulseAudio がインストールされています。国産チッププラットフォームであるGalaxy Kirinも当然例外ではありません。PulseAudio の構造図は次のようになります。
ご覧のとおり、PulseAudio はサービスとして ALSA の上位層にあり、複数のアプリケーションが同時に PulseAudio を呼び出すことができ、内部でオーディオ ミキサーとして使用できます。これにより、異なるハードウェア環境でプログラムが表示されることを回避できます。 ALSAの独占性により、正常に使用できない状況となっております。アプリケーションと PulseAudio の間の呼び出し関係は次のとおりです。
通常、システムには PulseAudio 開発パッケージがプリインストールされていませんが、コード内でインターフェイスを呼び出せるようにするために、この時点で PulseAudio 開発パッケージをインストールする必要があります。
sudo apt-get インストール libpulse-dev
PulseAudio オーディオ コレクションは、明らかに ALSA よりもはるかに複雑です。各アプリケーションは PulseAudio クライアントとみなされ、システムの PulseAudio サービスに接続し、データ転送用の循環キューとしてスレッドを維持する必要があります。以下は、race で使用されるいくつかの関数のリストです。
#include <pulse/pulseaudio.h>
/***
申请一个包含线程的事件循环
*/
pa_threaded_mainloop* pa_threaded_mainloop_new();
/***
开启事件循环
@return: 0表示成功,小于0表示错误码
*/
int pa_threaded_mainloop_start(pa_threaded_mainloop* m);
/***
终止事件循环,在调用此函数前,必须确保事件循环已经解锁
*/
void pa_threaded_mainloop_stop(pa_threaded_mainloop* m);
/***
阻塞并等待事件循环中消息被触发,注意,该函数返回并不一定是因为调用了pa_threaded_mainloop_signal()
需要甄别这一点
*/
void pa_threaded_mainloop_wait(pa_threaded_mainloop* m);
/***
触发消息
*/
void pa_threaded_mainloop_signal(pa_threaded_mainloop* m, int wait_for_accept);
#include <pulse/pulseaudio.h>
/***
创建PulseAudio连接上下文
*/
pa_context* pa_context_new(pa_mainloop_api *mainloop, const char *name);
/***
将context连接到指定的PulseAudio服务,如果server为NULL,则连接到系统默认服务。
@return: 小于0表示错误
*/
int pa_context_connect(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api);
/***
终止事件循环,在调用此函数前,必须确保事件循环已经解锁
*/
void pa_context_disconnect(pa_context* c);
/***
引用计数减1
*/
void pa_context_unref(pa_context* c);
/***
返回当前上下文状态
*/
pa_context_state_t pa_context_get_state(const pa_context* c);
#include <pulse/pulseaudio.h>
/***
在当前PulseAudio连接上,创建一个stream,用于输入或输出音频数据
*/
pa_stream* pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map);
/***
将context连接到指定的PulseAudio服务,如果server为NULL,则连接到系统默认服务。
@return: 小于0表示错误
*/
int pa_stream_connect_record(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api);
/***
从缓冲区中读取下一个采集的音频片段
*/
int pa_stream_peek(pa_stream *p, const void **data, size_t *nbytes);
/***
放弃当前输入(采集)的音频片段
*/
void pa_stream_drop(pa_stream* s);
/***
关闭输入输出流
*/
void pa_stream_disconnect(pa_stream* s);
/***
引用计数减1
*/
void pa_stream_unref(pa_stream* s);
/***
返回当前stream状态
*/
pa_context_state_t pa_stream_get_state(const pa_stream* s);
以下に、呼び出し方法を示す簡単な例を示します。
- イベント ループを作成し、PulseAudio サーバーに接続し、ストリームを作成してパラメータを設定します。より直感的に見えるようにするために、ここでは誤って判断されたコードをいくつか削除しました。
bool PulseAudioCapture::Start(Observer* ob)
{
observer_ = ob;
SIMPLE_LOG("try open %s\n", device_name_.c_str());
int ret = 0;
const char* name = "HbsPulse";
const char* stream_name = "HbsPulseStream";
char* device = NULL;
if (false == device_name_.empty())
{
device = (char*)device_name_.c_str();
}
const struct pa_sample_spec *pss = nullptr;
pa_sample_format_t sam_fmt = AV_NE(PA_SAMPLE_S16BE, PA_SAMPLE_S16LE);
const pa_sample_spec ss = {
sam_fmt, sample_rate_, channel_count_ };
pa_buffer_attr attr = {
(uint32_t)-1 };
pa_channel_map cmap;
const pa_buffer_attr *queried_attr = nullptr;
int stream_flag = 0;
pa_channel_map_init_extend(&cmap, channel_count_, PA_CHANNEL_MAP_WAVEEX);
mainloop_ = pa_threaded_mainloop_new();
context_ = pa_context_new(pa_threaded_mainloop_get_api(mainloop_), name);
pa_context_set_state_callback(context_, context_state_cb, this);
pa_context_connect(context_, pulse_server_, /*0*/PA_CONTEXT_NOFLAGS, NULL);
pa_threaded_mainloop_lock(mainloop_);
pa_threaded_mainloop_start(mainloop_);
for (;;)
{
pa_context_state_t state = pa_context_get_state(context_);
if (state == PA_CONTEXT_READY)
break;
if (!PA_CONTEXT_IS_GOOD(state))
{
int ec = pa_context_errno(context_);
SIMPLE_LOG("pulse context state bad: %d, err: %d\n", state, ec);
goto unlock_and_fail;
}
/* Wait until the context is ready */
pa_threaded_mainloop_wait(mainloop_);
}
SIMPLE_LOG("pulse context ready!\n");
stream_ = pa_stream_new(context_, stream_name, &ss, &cmap);
pa_stream_set_state_callback(stream_, stream_state_cb, this);
pa_stream_set_read_callback(stream_, stream_read_cb, this);
pa_stream_set_write_callback(stream_, stream_write_cb, this);
pa_stream_set_latency_update_callback(stream_, stream_latency_update_cb, this);
ret = pa_stream_connect_record(stream_, device, &attr,
PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE);
for (;;)
{
pa_stream_state_t state = pa_stream_get_state(stream_);
if (state == PA_STREAM_READY)
break;
if (!PA_STREAM_IS_GOOD(state))
{
int ec = pa_context_errno(context_);
SIMPLE_LOG("pulse stream state bad: %d, err: %d\n", state, ec);
goto unlock_and_fail;
}
/* Wait until the stream is ready */
pa_threaded_mainloop_wait(mainloop_);
}
pa_threaded_mainloop_unlock(mainloop_);
SIMPLE_LOG("pulse audio start ok, fragsize: %d, framesize: %d\n", fragment_size_, pa_frame_size_);
ThreadStart();
return true;
unlock_and_fail:
pa_threaded_mainloop_unlock(mainloop_);
ClosePulse();
return false;
}
- 音声データの読み取り
bool PulseAudioCapture::ReadData()
{
int ret;
size_t read_length;
const void *read_data = NULL;
pa_usec_t latency;
int negative;
ptrdiff_t pos = 0;
pa_threaded_mainloop_lock(mainloop_);
if (IsPulseDead())
{
SIMPLE_LOG("pulse is dead\n");
goto unlock_and_fail;
}
while (pos < fragment_size_)
{
int r = pa_stream_peek(stream_, &read_data, &read_length);
if (r != 0)
{
SIMPLE_LOG("pa_stream_peek: %d\n", r);
goto unlock_and_fail;
}
if (read_length <= 0)
{
pa_threaded_mainloop_wait(mainloop_);
if (IsPulseDead())
{
SIMPLE_LOG("pulse is dead\n");
goto unlock_and_fail;
}
}
else if (!read_data)
{
/* There's a hole in the stream, skip it. We could generate
* silence, but that wouldn't work for compressed streams. */
r = pa_stream_drop(stream_);
if (r != 0)
{
SIMPLE_LOG("null data, pa_stream_drop: %d\n", r);
goto unlock_and_fail;
}
}
else
{
if (!pos)
{
if (pcm_buf_.empty())
{
pcm_buf_.resize(fragment_size_);
}
//pcm_dts_ = av_gettime();
pa_operation_unref(pa_stream_update_timing_info(stream_, NULL, NULL));
if (pa_stream_get_latency(stream_, &latency, &negative) >= 0)
{
if (negative)
{
pcm_dts_ += latency;
}
else
pcm_dts_ -= latency;
}
else
{
SIMPLE_LOG("pa_stream_get_latency() failed\n");
}
}
if (pcm_buf_.size() - pos < read_length)
{
if (pos)
break;
pa_stream_drop(stream_);
/* Oversized fragment??? */
SIMPLE_LOG("Oversized fragment\n");
goto unlock_and_fail;
}
memcpy(pcm_buf_.data() + pos, read_data, read_length);
pos += read_length;
pa_stream_drop(stream_);
}
}
SIMPLE_LOG("read pos: %d\n", pos);
pa_threaded_mainloop_unlock(mainloop_);
return true;
unlock_and_fail:
pa_threaded_mainloop_unlock(mainloop_);
return false;
}
オーディオ デバイスを選択するときは、PulseAudio 関連インターフェイスを介してオーディオ デバイス名を照会する必要があります。オーディオ収集デバイスの場合は、pa_context_get_source_info_list() 関数を呼び出すことができます。実験の結果、PulseAudioを使用して音声を収集し、国内プラットフォームのKirinシステム上でマイクとシステムサウンドを収集する機能を実現することに成功し、以前のマルチサウンドカード環境でのALSAコードの使用で発生したさまざまなトラブルを回避しました。 。
さらに、PulseAudio を通じて収集されたデータのサイズはエンコードに必要ない場合があり、データのバッファリングも必要であることに注意してください。
ご協力のために作者 hbstream を追加してください。(転載の際は作者と出典を明記してください)