Android의 libnbaio ( Non-Blocking Audio I/O 약어) 라이브러리는 주로 Non-Blocking 오디오 I/O용으로 설계되었지만 이제는 일부 인터페이스의 차단 구현도 포함하므로 주요 역할은 된통합 libnbaio 라이브러리는 주로 audioflinger 및 일부 오디오 HAL 모듈 구현에 사용됩니다. libnbaio 라이브러리 가 제공하는 주요 구성요소 는 단일 Writer를 지원하는 N 과 단일 Writer와 단일 Reader를 지원하는 N Reader Pipe
입니다 .MonoPipe
Pipe
MonoPipe
Pipe
지원되는 기능은 다음과 같습니다.
뮤텍스가 없으면 스레드 간에 SCHED_NORMAL 및 SCHED_FIFO를 사용하는 것이 안전합니다.
-
쓰다:
- 비차단
- 데이터가 충분하지 않으면 전송된 실제 프레임 수를 반환합니다.
- 데이터 소비 속도가 충분하지 않으면 데이터를 덮어씁니다.
-
읽다:
- 비차단
- 데이터가 충분하지 않으면 전송된 실제 프레임 수를 반환합니다.
- 읽기 속도를 따라잡지 못하면 데이터가 손실됩니다
MonoPipe
지원되는 기능은 다음과 같습니다.
뮤텍스가 없으면 스레드 간에 SCHED_NORMAL 및 SCHED_FIFO를 사용하는 것이 안전합니다.
-
쓰다:
- 차단이 선택사항인지 여부
- 차단하도록 구성된 경우 공간이 확보될 때까지 기다렸다가 돌아옵니다.
- 비차단으로 구성된 경우 전송된 실제 프레임 수가 반환되고 데이터를 덮어쓰지 않습니다.
-
읽다:
- 비차단
- 데이터가 충분하지 않으면 전송된 실제 프레임 수를 반환합니다.
- 데이터를 잃지 마세요
libnbaio 라이브러리의 설계와 구현을 살펴보며 , 코드 분석은 android-12.1.0_r27 버전을 기준으로 합니다.
libnbaio 라이브러리 에서 제공하는 주요 추상화 는 NBAIO_Sink
및 NBAIO_Source
오디오 데이터를 쓰는 데 사용되는 및 오디오 데이터를 읽는 데 사용됩니다. libnbaio 라이브러리 각 부분의 상속 계층 구조 는 다음과 같습니다.
LibsndfileSource
및 구현 모드 에서 libsndfile 에 의해 열린 파일 을 LibsndfileSink
각각 캡슐화합니다 . libsndfile 의 호환되지 않는 라이센스 로 인해 이 두 구성 요소는 기본적으로 Android 시스템에 포함되지 않습니다. 안드로이드 시스템 전체에서 사용할 곳이 없고, 안드로이드 시스템의 발전으로 인해 아마도 황폐화되어 사용할 수 없게 된 상태일 것입니다.SFM_READ
SFM_WRITE
NBAIO_Source
NBAIO_Sink
AudioStreamInSource
AudioStreamOutSink
오디오 HAL StreamInHalInterface
과 StreamOutHalInterface
인터페이스 구현을 NBAIO_Source
각각 캡슐화 합니다 NBAIO_Sink
. 다중 스레드로부터 안전하지 않습니다.
Pipe
와 함께 사용하면 PipeReader
각각 오디오 데이터를 쓰고 오디오 데이터를 읽는 데 사용됩니다. MonoPipe
와 함께 사용하면 MonoPipeReader
각각 오디오 데이터를 쓰고 오디오 데이터를 읽는 데 사용됩니다.
SourceAudioBufferProvider
캡슐화되어 NBAIO_Source
궁극적으로 에서 상속되며 libnbaio 라이브러리의 구성 요소를 믹서 등과 같은 Android 시스템의 다른 오디오 모듈에 AudioBufferProvider
연결하는 데 주로 사용됩니다.
MonoPipe
, MonoPipeReader
, Pipe
및 PipeReader
관련 구성 요소의 구조를 아래와 같이 구현합니다.
기본 구조 측면에서 MonoPipe
및 MonoPipeReader
및 Pipe
및 는 PipeReader
기본적으로 동일하지만 각각 다른 읽기 및 쓰기 전략을 구현합니다. Pipe
데이터 교환을 위해 관리 에 메모리를 할당합니다 audio_utils_fifo
. 데이터 읽기 및 쓰기는 궁극적으로 를 통해 수행됩니다 audio_utils_fifo
. 객체를 Pipe
포함하고 , 객체는 오디오 데이터를 쓰는 데 사용되는 객체에 대한 참조를 보유하고, 객체에 대한 참조를 보유하고 , 객체 ; 객체는 의 객체에 대한 참조를 보유하고 에서 오디오 데이터를 읽는 데 사용 되며 , 각각 오디오 데이터의 쓰기 및 판독기 역할을 하는 공유 객체를 통해 데이터를 교환합니다. 및 와 동일한 기본 구조 및 데이터 교환 방법을 갖습니다 . 데이터 흐름 관점에서 이러한 구성 요소의 구조는 다음과 같습니다.audio_utils_fifo_writer
audio_utils_fifo_writer
audio_utils_fifo
audio_utils_fifo_writer
audio_utils_fifo
PipeReader
Pipe
audio_utils_fifo_reader
audio_utils_fifo_reader
Pipe
audio_utils_fifo
audio_utils_fifo
Pipe
PipeReader
audio_utils_fifo
MonoPipe
MonoPipeReader
Pipe
PipeReader
MonoPipe
and MonoPipeReader
등 Pipe
의 구체적인 구현을 살펴보겠습니다 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;
}
AudioStreamInSource
의 StreamInHalInterface
캡슐화는 후자를 통해 데이터를 읽고, 읽은 데이터 형식은 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 스트림의 전체 버퍼 크기입니다.
AudioStreamOutSink
의 StreamOutHalInterface
캡슐화는 후자를 통해 데이터를 쓰고, 쓰여진 데이터 형식은 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 ×tamp)
{
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()
완료.