Android에서 libnbaio 라이브러리 설계 및 구현

Android의 libnbaio ( Non-Blocking Audio I/O 약어) 라이브러리는 주로 Non-Blocking 오디오 I/O용으로 설계되었지만 이제는 일부 인터페이스의 차단 구현도 포함하므로 주요 역할은 통합 libnbaio 라이브러리는 주로 audioflinger 및 일부 오디오 HAL 모듈 구현에 사용됩니다. libnbaio 라이브러리 가 제공하는 주요 구성요소 는 단일 Writer를 지원하는 N 과 단일 Writer와 단일 Reader를 지원하는 N Reader Pipe입니다 .MonoPipePipeMonoPipe

Pipe지원되는 기능은 다음과 같습니다.

뮤텍스가 없으면 스레드 간에 SCHED_NORMAL 및 SCHED_FIFO를 사용하는 것이 안전합니다.

  • 쓰다:

    • 비차단
    • 데이터가 충분하지 않으면 전송된 실제 프레임 수를 반환합니다.
    • 데이터 소비 속도가 충분하지 않으면 데이터를 덮어씁니다.
  • 읽다:

    • 비차단
    • 데이터가 충분하지 않으면 전송된 실제 프레임 수를 반환합니다.
    • 읽기 속도를 따라잡지 못하면 데이터가 손실됩니다

MonoPipe지원되는 기능은 다음과 같습니다.

뮤텍스가 없으면 스레드 간에 SCHED_NORMAL 및 SCHED_FIFO를 사용하는 것이 안전합니다.

  • 쓰다:

    • 차단이 선택사항인지 여부
    • 차단하도록 구성된 경우 공간이 확보될 때까지 기다렸다가 돌아옵니다.
    • 비차단으로 구성된 경우 전송된 실제 프레임 수가 반환되고 데이터를 덮어쓰지 않습니다.
  • 읽다:

    • 비차단
    • 데이터가 충분하지 않으면 전송된 실제 프레임 수를 반환합니다.
    • 데이터를 잃지 마세요

libnbaio 라이브러리의 설계와 구현을 살펴보며 , 코드 분석은 android-12.1.0_r27 버전을 기준으로 합니다.

libnbaio 라이브러리 에서 제공하는 주요 추상화 는 NBAIO_SinkNBAIO_Source오디오 데이터를 쓰는 데 사용되는 및 오디오 데이터를 읽는 데 사용됩니다. libnbaio 라이브러리 각 부분의 상속 계층 구조 는 다음과 같습니다.

NBAIO 개체

LibsndfileSource구현 모드 에서 libsndfile 의해 열린 파일 을 LibsndfileSink각각 캡슐화합니다 . libsndfile 의 호환되지 않는 라이센스 로 인해 이 두 구성 요소는 기본적으로 Android 시스템에 포함되지 않습니다. 안드로이드 시스템 전체에서 사용할 곳이 없고, 안드로이드 시스템의 발전으로 인해 아마도 황폐화되어 사용할 수 없게 된 상태일 것입니다.SFM_READSFM_WRITENBAIO_SourceNBAIO_Sink

AudioStreamInSourceAudioStreamOutSink오디오 HAL StreamInHalInterfaceStreamOutHalInterface인터페이스 구현을 NBAIO_Source각각 캡슐화 합니다 NBAIO_Sink. 다중 스레드로부터 안전하지 않습니다.

Pipe와 함께 사용하면 PipeReader각각 오디오 데이터를 쓰고 오디오 데이터를 읽는 데 사용됩니다. MonoPipe와 함께 사용하면 MonoPipeReader각각 오디오 데이터를 쓰고 오디오 데이터를 읽는 데 사용됩니다.

SourceAudioBufferProvider캡슐화되어 NBAIO_Source궁극적으로 에서 상속되며 libnbaio 라이브러리의 구성 요소를 믹서 등과 같은 Android 시스템의 다른 오디오 모듈에 AudioBufferProvider연결하는 데 주로 사용됩니다.

MonoPipe, MonoPipeReader, PipePipeReader관련 구성 요소의 구조를 아래와 같이 구현합니다.

NBAIO 파이프

기본 구조 측면에서 MonoPipeMonoPipeReaderPipe및 는 PipeReader기본적으로 동일하지만 각각 다른 읽기 및 쓰기 전략을 구현합니다. Pipe데이터 교환을 위해 관리 에 메모리를 할당합니다 audio_utils_fifo. 데이터 읽기 및 쓰기는 궁극적으로 를 통해 수행됩니다 audio_utils_fifo. 객체를 Pipe포함하고 , 객체는 오디오 데이터를 쓰는 데 사용되는 객체에 대한 참조를 보유하고, 객체에 대한 참조를 보유하고 , 객체 ; 객체는 의 객체에 대한 참조를 보유하고 에서 오디오 데이터를 읽는 데 사용 되며 , 각각 오디오 데이터의 쓰기 및 판독기 역할을 하는 공유 객체를 통해 데이터를 교환합니다. 동일한 기본 구조 및 데이터 교환 방법을 갖습니다 . 데이터 흐름 관점에서 이러한 구성 요소의 구조는 다음과 같습니다.audio_utils_fifo_writeraudio_utils_fifo_writeraudio_utils_fifoaudio_utils_fifo_writeraudio_utils_fifoPipeReaderPipeaudio_utils_fifo_readeraudio_utils_fifo_readerPipeaudio_utils_fifoaudio_utils_fifoPipePipeReaderaudio_utils_fifoMonoPipeMonoPipeReaderPipePipeReader

파이프 및 파이프 리더의 데이터 스트림

MonoPipeand MonoPipeReaderPipe의 구체적인 구현을 살펴보겠습니다 PipeReader. Pipe클래스 정의(위치 frameworks/av/media/libnbaio/include/media/nbaio/Pipe.h)는 다음과 같습니다.

class Pipe : public NBAIO_Sink {

    friend class PipeReader;

public:
    // maxFrames will be rounded up to a power of 2, and all slots are available. Must be >= 2.
    // buffer is an optional parameter specifying the virtual address of the pipe buffer,
    // which must be of size roundup(maxFrames) * Format_frameSize(format) bytes.
    Pipe(size_t maxFrames, const NBAIO_Format& format, void *buffer = NULL);

    // If a buffer was specified in the constructor, it is not automatically freed by destructor.
    virtual ~Pipe();

    // NBAIO_Port interface

    //virtual ssize_t negotiate(const NBAIO_Format offers[], size_t numOffers,
    //                          NBAIO_Format counterOffers[], size_t& numCounterOffers);
    //virtual NBAIO_Format format() const;

    // NBAIO_Sink interface

    //virtual int64_t framesWritten() const;
    //virtual int64_t framesUnderrun() const;
    //virtual int64_t underruns() const;

    // The write side of a pipe permits overruns; flow control is the caller's responsibility.
    // It doesn't return +infinity because that would guarantee an overrun.
    virtual ssize_t availableToWrite() { return mMaxFrames; }

    virtual ssize_t write(const void *buffer, size_t count);
    //virtual ssize_t writeVia(writeVia_t via, size_t total, void *user, size_t block);

private:
    const size_t    mMaxFrames;     // always a power of 2
    void * const    mBuffer;
    audio_utils_fifo        mFifo;
    audio_utils_fifo_writer mFifoWriter;
    volatile int32_t mReaders;      // number of PipeReader clients currently attached to this Pipe
    const bool      mFreeBufferInDestructor;
};

Pipe객체를 생성할 때 최대 프레임 수, 오디오 데이터 형식 및 데이터 교환을 위한 선택적 버퍼를 전달해야 합니다. 최대 프레임 수는 2의 거듭제곱으로 위쪽으로 정렬됩니다. 데이터 버퍼의 크기 는 상향 정렬 후 최대 프레임 수 roundup(maxFrames) * 프레임 크기 Format_frameSize(format) 바이트 여야 합니다 . Pipe객체가 생성될 때 할당된 메모리 블록이 전달되지 않으면 메모리 조각이 할당되고, Pipe객체가 파괴되면 할당된 메모리가 해제됩니다. Pipe생성자와 소멸자는 frameworks/av/media/libnbaio/Pipe.cpp다음과 같이 정의(위치)됩니다.

Pipe::Pipe(size_t maxFrames, const NBAIO_Format& format, void *buffer) :
        NBAIO_Sink(format),
        // TODO fifo now supports non-power-of-2 buffer sizes, so could remove the roundup
        mMaxFrames(roundup(maxFrames)),
        mBuffer(buffer == NULL ? malloc(mMaxFrames * Format_frameSize(format)) : buffer),
        mFifo(mMaxFrames, Format_frameSize(format), mBuffer, false /*throttlesWriter*/),
        mFifoWriter(mFifo),
        mReaders(0),
        mFreeBufferInDestructor(buffer == NULL)
{
}

Pipe::~Pipe()
{
    ALOG_ASSERT(android_atomic_acquire_load(&mReaders) == 0);
    if (mFreeBufferInDestructor) {
        free(mBuffer);
    }
}

Pipe및 의 읽기 및 쓰기 작업에 대한 스레드로부터 안전한 의미 체계는 PipeReader주로 기본 audio_utils_fifo_reader, audio_utils_fifo_writer및 에 의해 구현됩니다 audio_utils_fifo. Pipe함수 write()구현(위치 frameworks/av/media/libnbaio/Pipe.cpp)은 다음과 같습니다.

ssize_t Pipe::write(const void *buffer, size_t count)
{
    // count == 0 is unlikely and not worth checking for
    if (CC_UNLIKELY(!mNegotiated)) {
        return NEGOTIATE;
    }
    ssize_t actual = mFifoWriter.write(buffer, count);
    ALOG_ASSERT(actual <= count);
    if (actual <= 0) {
        return actual;
    }
    mFramesWritten += (size_t) actual;
    return actual;
}

Pipe쓰기 버퍼에서 데이터를 읽는 데 사용되는 PipeReader클래스의 정의는 frameworks/av/media/libnbaio/include/media/nbaio/PipeReader.h다음과 같습니다.

class PipeReader : public NBAIO_Source {

public:

    // Construct a PipeReader and associate it with a Pipe
    // FIXME make this constructor a factory method of Pipe.
    PipeReader(Pipe& pipe);
    virtual ~PipeReader();

    // NBAIO_Port interface

    //virtual ssize_t negotiate(const NBAIO_Format offers[], size_t numOffers,
    //                          NBAIO_Format counterOffers[], size_t& numCounterOffers);
    //virtual NBAIO_Format format() const;

    // NBAIO_Source interface

    //virtual size_t framesRead() const;
    virtual int64_t framesOverrun() { return mFramesOverrun; }
    virtual int64_t overruns()  { return mOverruns; }

    virtual ssize_t availableToRead();

    virtual ssize_t read(void *buffer, size_t count);

    virtual ssize_t flush();

    // NBAIO_Source end

#if 0   // until necessary
    Pipe& pipe() const { return mPipe; }
#endif

private:
    Pipe&       mPipe;
    audio_utils_fifo_reader mFifoReader;
    int64_t     mFramesOverrun;
    int64_t     mOverruns;
};

PipeReader단일 스레드에만 안전합니다. 서로 다른 스레드는 서로 다른 객체를 통해 PipeReader동일한 데이터를 읽을 수 Pipe있지만, 서로 다른 스레드는 동일한 PipeReader객체를 통해 데이터를 읽을 수 없습니다. PipeReader각 멤버 함수 정의(위치 frameworks/av/media/libnbaio/PipeReader.cpp)는 다음과 같습니다.

PipeReader::PipeReader(Pipe& pipe) :
        NBAIO_Source(pipe.mFormat),
        mPipe(pipe), mFifoReader(mPipe.mFifo, false /*throttlesWriter*/, false /*flush*/),
        mFramesOverrun(0),
        mOverruns(0)
{
    android_atomic_inc(&pipe.mReaders);
}

PipeReader::~PipeReader()
{
#if !LOG_NDEBUG
    int32_t readers =
#else
    (void)
#endif
            android_atomic_dec(&mPipe.mReaders);
    ALOG_ASSERT(readers > 0);
}

ssize_t PipeReader::availableToRead()
{
    if (CC_UNLIKELY(!mNegotiated)) {
        return NEGOTIATE;
    }
    size_t lost;
    ssize_t avail = mFifoReader.available(&lost);
    if (avail == -EOVERFLOW || lost > 0) {
        mFramesOverrun += lost;
        ++mOverruns;
        avail = OVERRUN;
    }
    return avail;
}

ssize_t PipeReader::read(void *buffer, size_t count)
{
    size_t lost;
    ssize_t actual = mFifoReader.read(buffer, count, NULL /*timeout*/, &lost);
    ALOG_ASSERT(actual <= count);
    if (actual == -EOVERFLOW || lost > 0) {
        mFramesOverrun += lost;
        ++mOverruns;
        actual = OVERRUN;
    }
    if (actual <= 0) {
        return actual;
    }
    mFramesRead += (size_t) actual;
    return actual;
}

ssize_t PipeReader::flush()
{
    if (CC_UNLIKELY(!mNegotiated)) {
        return NEGOTIATE;
    }
    size_t lost;
    ssize_t flushed = mFifoReader.flush(&lost);
    if (flushed == -EOVERFLOW || lost > 0) {
        mFramesOverrun += lost;
        ++mOverruns;
        flushed = OVERRUN;
    }
    if (flushed <= 0) {
        return flushed;
    }
    mFramesRead += (size_t) flushed;  // we consider flushed frames as read, but not lost frames
    return flushed;
}

PipeReader읽기 작업에서 쓴 데이터가 오버플로된 것을 발견하면 데이터의 일부를 읽어도 오류가 반환되며, 읽을 수 있는 데이터의 양을 구하며 비슷한 의미를 갖습니다 flush().

MonoPipe의 오디오 데이터 쓰기 의미는 write()해당 작업에서 구현되며, 해당 기능은 frameworks/av/media/libnbaio/MonoPipe.cpp다음과 같이 정의(위치)됩니다.

ssize_t MonoPipe::write(const void *buffer, size_t count)
{
    if (CC_UNLIKELY(!mNegotiated)) {
        return NEGOTIATE;
    }
    size_t totalFramesWritten = 0;
    while (count > 0) {
        ssize_t actual = mFifoWriter.write(buffer, count);
        ALOG_ASSERT(actual <= count);
        if (actual < 0) {
            if (totalFramesWritten == 0) {
                return actual;
            }
            break;
        }
        size_t written = (size_t) actual;
        totalFramesWritten += written;
        if (!mWriteCanBlock || mIsShutdown) {
            break;
        }
        count -= written;
        buffer = (char *) buffer + (written * mFrameSize);
        // TODO Replace this whole section by audio_util_fifo's setpoint feature.
        // Simulate blocking I/O by sleeping at different rates, depending on a throttle.
        // The throttle tries to keep the mean pipe depth near the setpoint, with a slight jitter.
        uint32_t ns;
        if (written > 0) {
            ssize_t avail = mFifoWriter.available();
            ALOG_ASSERT(avail <= mMaxFrames);
            if (avail < 0) {
                // don't return avail as status, because totalFramesWritten > 0
                break;
            }
            size_t filled = mMaxFrames - (size_t) avail;
            // FIXME cache these values to avoid re-computation
            if (filled <= mSetpoint / 2) {
                // pipe is (nearly) empty, fill quickly
                ns = written * ( 500000000 / Format_sampleRate(mFormat));
            } else if (filled <= (mSetpoint * 3) / 4) {
                // pipe is below setpoint, fill at slightly faster rate
                ns = written * ( 750000000 / Format_sampleRate(mFormat));
            } else if (filled <= (mSetpoint * 5) / 4) {
                // pipe is at setpoint, fill at nominal rate
                ns = written * (1000000000 / Format_sampleRate(mFormat));
            } else if (filled <= (mSetpoint * 3) / 2) {
                // pipe is above setpoint, fill at slightly slower rate
                ns = written * (1150000000 / Format_sampleRate(mFormat));
            } else if (filled <= (mSetpoint * 7) / 4) {
                // pipe is overflowing, fill slowly
                ns = written * (1350000000 / Format_sampleRate(mFormat));
            } else {
                // pipe is severely overflowing
                ns = written * (1750000000 / Format_sampleRate(mFormat));
            }
        } else {
            ns = count * (1350000000 / Format_sampleRate(mFormat));
        }
        if (ns > 999999999) {
            ns = 999999999;
        }
        struct timespec nowTs;
        bool nowTsValid = !clock_gettime(CLOCK_MONOTONIC, &nowTs);
        // deduct the elapsed time since previous write() completed
        if (nowTsValid && mWriteTsValid) {
            time_t sec = nowTs.tv_sec - mWriteTs.tv_sec;
            long nsec = nowTs.tv_nsec - mWriteTs.tv_nsec;
            ALOGE_IF(sec < 0 || (sec == 0 && nsec < 0),
                    "clock_gettime(CLOCK_MONOTONIC) failed: was %ld.%09ld but now %ld.%09ld",
                    mWriteTs.tv_sec, mWriteTs.tv_nsec, nowTs.tv_sec, nowTs.tv_nsec);
            if (nsec < 0) {
                --sec;
                nsec += 1000000000;
            }
            if (sec == 0) {
                if ((long) ns > nsec) {
                    ns -= nsec;
                } else {
                    ns = 0;
                }
            }
        }
        if (ns > 0) {
            const struct timespec req = {0, static_cast<long>(ns)};
            nanosleep(&req, NULL);
        }
        // record the time that this write() completed
        if (nowTsValid) {
            mWriteTs = nowTs;
            if ((mWriteTs.tv_nsec += ns) >= 1000000000) {
                mWriteTs.tv_nsec -= 1000000000;
                ++mWriteTs.tv_sec;
            }
        }
        mWriteTsValid = nowTsValid;
    }
    mFramesWritten += totalFramesWritten;
    return totalFramesWritten;
}

동기적으로 기록되어 모든 데이터를 기록할 공간이 부족한 경우 현재 버퍼에 기록된 데이터의 수위를 기준으로 슬립 시간을 계산하여 슬립한 후 대기합니다. 실제로 작성된 오디오 프레임 수가 반환됩니다. MonoPipeReader읽기 작업은 쓰여진 데이터의 오버플로를 고려하지 않으며, 관련 작업은 frameworks/av/media/libnbaio/MonoPipeReader.cpp다음과 같이 구현(위치)됩니다.

ssize_t MonoPipeReader::availableToRead()
{
    if (CC_UNLIKELY(!mNegotiated)) {
        return NEGOTIATE;
    }
    ssize_t ret = mFifoReader.available();
    ALOG_ASSERT(ret <= mPipe->mMaxFrames);
    return ret;
}

ssize_t MonoPipeReader::read(void *buffer, size_t count)
{
    // count == 0 is unlikely and not worth checking for explicitly; will be handled automatically
    ssize_t actual = mFifoReader.read(buffer, count);
    ALOG_ASSERT(actual <= count);
    if (CC_UNLIKELY(actual <= 0)) {
        return actual;
    }
    mFramesRead += (size_t) actual;
    return actual;
}

AudioStreamInSourceStreamInHalInterface캡슐화는 후자를 통해 데이터를 읽고, 읽은 데이터 형식은 StreamInHalInterface에서 얻은 데이터 형식을 따릅니다. AudioStreamInSource각 멤버 함수의 정의(위치)는 frameworks/av/media/libnbaio/AudioStreamInSource.cpp다음과 같습니다.

AudioStreamInSource::AudioStreamInSource(sp<StreamInHalInterface> stream) :
        NBAIO_Source(),
        mStream(stream),
        mStreamBufferSizeBytes(0),
        mFramesOverrun(0),
        mOverruns(0)
{
    ALOG_ASSERT(stream != 0);
}

AudioStreamInSource::~AudioStreamInSource()
{
    mStream.clear();
}

ssize_t AudioStreamInSource::negotiate(const NBAIO_Format offers[], size_t numOffers,
                                      NBAIO_Format counterOffers[], size_t& numCounterOffers)
{
    if (!Format_isValid(mFormat)) {
        status_t result;
        result = mStream->getBufferSize(&mStreamBufferSizeBytes);
        if (result != OK) return result;
        audio_config_base_t config = AUDIO_CONFIG_BASE_INITIALIZER;
        result = mStream->getAudioProperties(&config);
        if (result != OK) return result;
        mFormat = Format_from_SR_C(config.sample_rate,
                audio_channel_count_from_in_mask(config.channel_mask), config.format);
        mFrameSize = Format_frameSize(mFormat);
    }
    return NBAIO_Source::negotiate(offers, numOffers, counterOffers, numCounterOffers);
}

int64_t AudioStreamInSource::framesOverrun()
{
    uint32_t framesOverrun;
    status_t result = mStream->getInputFramesLost(&framesOverrun);
    if (result == OK && framesOverrun > 0) {
        mFramesOverrun += framesOverrun;
        // FIXME only increment for contiguous ranges
        ++mOverruns;
    } else if (result != OK) {
        ALOGE("Error when retrieving lost frames count from HAL: %d", result);
    }
    return mFramesOverrun;
}

ssize_t AudioStreamInSource::read(void *buffer, size_t count)
{
    if (CC_UNLIKELY(!Format_isValid(mFormat))) {
        return NEGOTIATE;
    }
    size_t bytesRead;
    status_t result = mStream->read(buffer, count * mFrameSize, &bytesRead);
    if (result == OK && bytesRead > 0) {
        size_t framesRead = bytesRead / mFrameSize;
        mFramesRead += framesRead;
        return framesRead;
    } else {
        ALOGE_IF(result != OK, "Error while reading data from HAL: %d", result);
        return bytesRead;
    }
}

AudioStreamInSource오디오 HAL에서 오버플로 데이터 수를 가져오기 위한 인터페이스를 제공합니다. AudioStreamInSource쓰기 가능한 데이터의 양은 항상 오디오 HAL 스트림의 전체 버퍼 크기입니다.

AudioStreamOutSinkStreamOutHalInterface캡슐화는 후자를 통해 데이터를 쓰고, 쓰여진 데이터 형식은 StreamOutHalInterface에서 얻은 데이터 형식을 따릅니다. AudioStreamOutSink각 멤버 함수의 정의(위치)는 frameworks/av/media/libnbaio/AudioStreamOutSink.cpp다음과 같습니다.

AudioStreamOutSink::AudioStreamOutSink(sp<StreamOutHalInterface> stream) :
        NBAIO_Sink(),
        mStream(stream),
        mStreamBufferSizeBytes(0)
{
    ALOG_ASSERT(stream != 0);
}

AudioStreamOutSink::~AudioStreamOutSink()
{
    mStream.clear();
}

ssize_t AudioStreamOutSink::negotiate(const NBAIO_Format offers[], size_t numOffers,
                                      NBAIO_Format counterOffers[], size_t& numCounterOffers)
{
    if (!Format_isValid(mFormat)) {
        status_t result;
        result = mStream->getBufferSize(&mStreamBufferSizeBytes);
        if (result != OK) return result;
        audio_config_base_t config = AUDIO_CONFIG_BASE_INITIALIZER;
        result = mStream->getAudioProperties(&config);
        if (result != OK) return result;
        mFormat = Format_from_SR_C(config.sample_rate,
                audio_channel_count_from_out_mask(config.channel_mask), config.format);
        mFrameSize = Format_frameSize(mFormat);
    }
    return NBAIO_Sink::negotiate(offers, numOffers, counterOffers, numCounterOffers);
}

ssize_t AudioStreamOutSink::write(const void *buffer, size_t count)
{
    if (!mNegotiated) {
        return NEGOTIATE;
    }
    ALOG_ASSERT(Format_isValid(mFormat));
    size_t written;
    status_t ret = mStream->write(buffer, count * mFrameSize, &written);
    if (ret == OK && written > 0) {
        written /= mFrameSize;
        mFramesWritten += written;
        return written;
    } else {
        // FIXME verify HAL implementations are returning the correct error codes e.g. WOULD_BLOCK
        ALOGE_IF(ret != OK, "Error while writing data to HAL: %d", ret);
        return ret;
    }
}

status_t AudioStreamOutSink::getTimestamp(ExtendedTimestamp &timestamp)
{
    uint64_t position64;
    struct timespec time;
    if (mStream->getPresentationPosition(&position64, &time) != OK) {
        return INVALID_OPERATION;
    }
    timestamp.mPosition[ExtendedTimestamp::LOCATION_KERNEL] = position64;
    timestamp.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL] = audio_utils_ns_from_timespec(&time);
    return OK;
}

AudioStreamInSource일부 초기화 작업을 완료하고 사용된 특정 오디오 데이터 형식을 사용자와 협상하는 데 주로 사용되는 작업을 AudioStreamOutSink제공합니다 .negotiate()

완료.

추천

출처blog.csdn.net/tq08g2z/article/details/129938679