현지화된 플랫폼에서 직면하는 Linux 오디오 수집 및 함정 (2)

현지화된 플랫폼에서 직면하는 Linux 오디오 수집 및 함정 (2)

ALSA 컬렉션은 막다른 골목에 있기 때문에 다른 방법을 시도해볼 수밖에 없는데, 여기서 PulseAudio의 인터페이스를 통해 국내 플랫폼에서 마이크 및 시스템 사운드를 수집하는 기능을 성공적으로 구현했습니다.

리눅스 PulseAudio 오디오 컬렉션

먼저 PulseAudio와 ALSA의 차이점은 ALSA는 커널 수준에 있는 반면, PulseAudio는 사용자 수준 서비스로 ALSA와 마찬가지로 애플리케이션의 다양한 오디오 입출력을 관리하는 사운드 서버로 사용된다는 점입니다. 대부분의 Linux 배포판에는 기본적으로 PulseAudio가 설치되어 있습니다. 국내에서 생산되는 칩 플랫폼인 갤럭시 기린(Galaxy Kirin)도 당연히 예외는 아닙니다. PulseAudio의 구조 다이어그램은 다음과 같습니다.

여기에 이미지 설명을 삽입하세요.

보시다시피 PulseAudio는 서비스로서 ALSA의 상위 레이어에 위치하며, 여러 애플리케이션이 동시에 PulseAudio를 호출하여 내부적으로 오디오 믹서로 사용할 수 있도록 하여 서로 다른 하드웨어 환경에서 프로그램이 나타나는 것을 방지할 수 있습니다. ALSA의 독점성으로 인해 정상적으로 사용할 수 없는 상황입니다. 애플리케이션과 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);

다음은 호출 방법을 보여주는 간단한 예입니다.

  1. 이벤트 루프를 생성하고, 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;
}
  1. 오디오 데이터 읽기
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을 추가해 주세요. (재인쇄시 작성자와 출처를 꼭 밝혀주세요)


추천

출처blog.csdn.net/haibindev/article/details/128876970