framework层音频数据抓取:
1.抓取解码出来的原始PCM数据(自定义“config.audioData.afterDecoder”属性开关控制处)
frameworks/av/media/libmedia/AudioTrack.cpp
ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking)
{
if (mTransfer != TRANSFER_SYNC || mIsTimed) {
return INVALID_OPERATION;
}
if (isDirect()) {
AutoMutex lock(mLock);
int32_t flags = android_atomic_and(
~(CBLK_UNDERRUN | CBLK_LOOP_CYCLE | CBLK_LOOP_FINAL | CBLK_BUFFER_END),
&mCblk->mFlags);
if (flags & CBLK_INVALID) {
return DEAD_OBJECT;
}
}
if (ssize_t(userSize) < 0 || (buffer == NULL && userSize != 0)) {
// Sanity-check: user is most-likely passing an error code, and it would
// make the return value ambiguous (actualSize vs error).
ALOGE("AudioTrack::write(buffer=%p, size=%zu (%zd)", buffer, userSize, userSize);
return BAD_VALUE;
}
size_t written = 0;
Buffer audioBuffer;
while (userSize >= mFrameSize) {
audioBuffer.frameCount = userSize / mFrameSize;
status_t err = obtainBuffer(&audioBuffer,
blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
if (err < 0) {
if (written > 0) {
break;
}
return ssize_t(err);
}
size_t toWrite = audioBuffer.size;
memcpy(audioBuffer.i8, buffer, toWrite);
buffer = ((const char *) buffer) + toWrite;
userSize -= toWrite;
written += toWrite;
<span style="color:#ff0000;"><span style="white-space:pre"> </span>char buf[PROPERTY_VALUE_MAX];
property_get("config.audioData.afterDecoder", buf, "");
if( (0 == strcmp("buf","on")) && (toWrite !=0) ){
FILE* fp = fopen("/data/audio/afterDecoder.pcm", "w+");
fwrite(audioBuffer.i8,toWrite,1,fp);
... ...
}</span>
releaseBuffer(&audioBuffer);
}
return written;
}
或者
nsecs_t AudioTrack::processAudioBuffer()
{
// Currently the AudioTrack thread is not created if there are no callbacks.
// Would it ever make sense to run the thread, even without callbacks?
// If so, then replace this by checks at each use for mCbf != NULL.
LOG_ALWAYS_FATAL_IF(mCblk == NULL);
mLock.lock();
if (mAwaitBoost) {
mAwaitBoost = false;
mLock.unlock();
static const int32_t kMaxTries = 5;
int32_t tryCounter = kMaxTries;
uint32_t pollUs = 10000;
do {
int policy = sched_getscheduler(0);
if (policy == SCHED_FIFO || policy == SCHED_RR) {
break;
}
usleep(pollUs);
pollUs <<= 1;
} while (tryCounter-- > 0);
if (tryCounter < 0) {
ALOGE("did not receive expected priority boost on time");
}
// Run again immediately
return 0;
}
// Can only reference mCblk while locked
int32_t flags = android_atomic_and(
~(CBLK_UNDERRUN | CBLK_LOOP_CYCLE | CBLK_LOOP_FINAL | CBLK_BUFFER_END), &mCblk->mFlags);
// Check for track invalidation
if (flags & CBLK_INVALID) {
// for offloaded tracks restoreTrack_l() will just update the sequence and clear
// AudioSystem cache. We should not exit here but after calling the callback so
// that the upper layers can recreate the track
if (!isOffloadedOrDirect_l() || (mSequence == mObservedSequence)) {
status_t status __unused = restoreTrack_l("processAudioBuffer");
// FIXME unused status
// after restoration, continue below to make sure that the loop and buffer events
// are notified because they have been cleared from mCblk->mFlags above.
}
}
bool waitStreamEnd = mState == STATE_STOPPING;
bool active = mState == STATE_ACTIVE;
// Manage underrun callback, must be done under lock to avoid race with releaseBuffer()
bool newUnderrun = false;
if (flags & CBLK_UNDERRUN) {
#if 0
// Currently in shared buffer mode, when the server reaches the end of buffer,
// the track stays active in continuous underrun state. It's up to the application
// to pause or stop the track, or set the position to a new offset within buffer.
// This was some experimental code to auto-pause on underrun. Keeping it here
// in "if 0" so we can re-visit this if we add a real sequencer for shared memory content.
if (mTransfer == TRANSFER_SHARED) {
mState = STATE_PAUSED;
active = false;
}
#endif
if (!mInUnderrun) {
mInUnderrun = true;
newUnderrun = true;
}
}
// Get current position of server
size_t position = updateAndGetPosition_l();
// Manage marker callback
bool markerReached = false;
size_t markerPosition = mMarkerPosition;
// FIXME fails for wraparound, need 64 bits
if (!mMarkerReached && (markerPosition > 0) && (position >= markerPosition)) {
mMarkerReached = markerReached = true;
}
// Determine number of new position callback(s) that will be needed, while locked
size_t newPosCount = 0;
size_t newPosition = mNewPosition;
size_t updatePeriod = mUpdatePeriod;
// FIXME fails for wraparound, need 64 bits
if (updatePeriod > 0 && position >= newPosition) {
newPosCount = ((position - newPosition) / updatePeriod) + 1;
mNewPosition += updatePeriod * newPosCount;
}
// Cache other fields that will be needed soon
uint32_t sampleRate = mSampleRate;
float speed = mPlaybackRate.mSpeed;
const uint32_t notificationFrames = mNotificationFramesAct;
if (mRefreshRemaining) {
mRefreshRemaining = false;
mRemainingFrames = notificationFrames;
mRetryOnPartialBuffer = false;
}
size_t misalignment = mProxy->getMisalignment();
uint32_t sequence = mSequence;
sp<AudioTrackClientProxy> proxy = mProxy;
// Determine the number of new loop callback(s) that will be needed, while locked.
int loopCountNotifications = 0;
uint32_t loopPeriod = 0; // time in frames for next EVENT_LOOP_END or EVENT_BUFFER_END
if (mLoopCount > 0) {
int loopCount;
size_t bufferPosition;
mStaticProxy->getBufferPositionAndLoopCount(&bufferPosition, &loopCount);
loopPeriod = ((loopCount > 0) ? mLoopEnd : mFrameCount) - bufferPosition;
loopCountNotifications = min(mLoopCountNotified - loopCount, kMaxLoopCountNotifications);
mLoopCountNotified = loopCount; // discard any excess notifications
} else if (mLoopCount < 0) {
// FIXME: We're not accurate with notification count and position with infinite looping
// since loopCount from server side will always return -1 (we could decrement it).
size_t bufferPosition = mStaticProxy->getBufferPosition();
loopCountNotifications = int((flags & (CBLK_LOOP_CYCLE | CBLK_LOOP_FINAL)) != 0);
loopPeriod = mLoopEnd - bufferPosition;
} else if (/* mLoopCount == 0 && */ mSharedBuffer != 0) {
size_t bufferPosition = mStaticProxy->getBufferPosition();
loopPeriod = mFrameCount - bufferPosition;
}
// These fields don't need to be cached, because they are assigned only by set():
// mTransfer, mCbf, mUserData, mFormat, mFrameSize, mFlags
// mFlags is also assigned by createTrack_l(), but not the bit we care about.
mLock.unlock();
// get anchor time to account for callbacks.
const nsecs_t timeBeforeCallbacks = systemTime();
if (waitStreamEnd) {
// FIXME: Instead of blocking in proxy->waitStreamEndDone(), Callback thread
// should wait on proxy futex and handle CBLK_STREAM_END_DONE within this function
// (and make sure we don't callback for more data while we're stopping).
// This helps with position, marker notifications, and track invalidation.
struct timespec timeout;
timeout.tv_sec = WAIT_STREAM_END_TIMEOUT_SEC;
timeout.tv_nsec = 0;
status_t status = proxy->waitStreamEndDone(&timeout);
switch (status) {
case NO_ERROR:
case DEAD_OBJECT:
case TIMED_OUT:
if (status != DEAD_OBJECT) {
// for DEAD_OBJECT, we do not send a EVENT_STREAM_END after stop();
// instead, the application should handle the EVENT_NEW_IAUDIOTRACK.
mCbf(EVENT_STREAM_END, mUserData, NULL);
}
{
AutoMutex lock(mLock);
// The previously assigned value of waitStreamEnd is no longer valid,
// since the mutex has been unlocked and either the callback handler
// or another thread could have re-started the AudioTrack during that time.
waitStreamEnd = mState == STATE_STOPPING;
if (waitStreamEnd) {
mState = STATE_STOPPED;
mReleased = 0;
}
}
if (waitStreamEnd && status != DEAD_OBJECT) {
return NS_INACTIVE;
}
break;
}
return 0;
}
// perform callbacks while unlocked
if (newUnderrun) {
mCbf(EVENT_UNDERRUN, mUserData, NULL);
}
while (loopCountNotifications > 0) {
mCbf(EVENT_LOOP_END, mUserData, NULL);
--loopCountNotifications;
}
if (flags & CBLK_BUFFER_END) {
mCbf(EVENT_BUFFER_END, mUserData, NULL);
}
if (markerReached) {
mCbf(EVENT_MARKER, mUserData, &markerPosition);
}
while (newPosCount > 0) {
size_t temp = newPosition;
mCbf(EVENT_NEW_POS, mUserData, &temp);
newPosition += updatePeriod;
newPosCount--;
}
if (mObservedSequence != sequence) {
mObservedSequence = sequence;
mCbf(EVENT_NEW_IAUDIOTRACK, mUserData, NULL);
// for offloaded tracks, just wait for the upper layers to recreate the track
if (isOffloadedOrDirect()) {
return NS_INACTIVE;
}
}
// if inactive, then don't run me again until re-started
if (!active) {
return NS_INACTIVE;
}
// Compute the estimated time until the next timed event (position, markers, loops)
// FIXME only for non-compressed audio
uint32_t minFrames = ~0;
if (!markerReached && position < markerPosition) {
minFrames = markerPosition - position;
}
if (loopPeriod > 0 && loopPeriod < minFrames) {
// loopPeriod is already adjusted for actual position.
minFrames = loopPeriod;
}
if (updatePeriod > 0) {
minFrames = min(minFrames, uint32_t(newPosition - position));
}
// If > 0, poll periodically to recover from a stuck server. A good value is 2.
static const uint32_t kPoll = 0;
if (kPoll > 0 && mTransfer == TRANSFER_CALLBACK && kPoll * notificationFrames < minFrames) {
minFrames = kPoll * notificationFrames;
}
// This "fudge factor" avoids soaking CPU, and compensates for late progress by server
static const nsecs_t kWaitPeriodNs = WAIT_PERIOD_MS * 1000000LL;
const nsecs_t timeAfterCallbacks = systemTime();
// Convert frame units to time units
nsecs_t ns = NS_WHENEVER;
if (minFrames != (uint32_t) ~0) {
ns = framesToNanoseconds(minFrames, sampleRate, speed) + kWaitPeriodNs;
ns -= (timeAfterCallbacks - timeBeforeCallbacks); // account for callback time
// TODO: Should we warn if the callback time is too long?
if (ns < 0) ns = 0;
}
// If not supplying data by EVENT_MORE_DATA, then we're done
if (mTransfer != TRANSFER_CALLBACK) {
return ns;
}
// EVENT_MORE_DATA callback handling.
// Timing for linear pcm audio data formats can be derived directly from the
// buffer fill level.
// Timing for compressed data is not directly available from the buffer fill level,
// rather indirectly from waiting for blocking mode callbacks or waiting for obtain()
// to return a certain fill level.
struct timespec timeout;
const struct timespec *requested = &ClientProxy::kForever;
if (ns != NS_WHENEVER) {
timeout.tv_sec = ns / 1000000000LL;
timeout.tv_nsec = ns % 1000000000LL;
ALOGV("timeout %ld.%03d", timeout.tv_sec, (int) timeout.tv_nsec / 1000000);
requested = &timeout;
}
while (mRemainingFrames > 0) {
Buffer audioBuffer;
audioBuffer.frameCount = mRemainingFrames;
size_t nonContig;
status_t err = obtainBuffer(&audioBuffer, requested, NULL, &nonContig);
LOG_ALWAYS_FATAL_IF((err != NO_ERROR) != (audioBuffer.frameCount == 0),
"obtainBuffer() err=%d frameCount=%zu", err, audioBuffer.frameCount);
requested = &ClientProxy::kNonBlocking;
size_t avail = audioBuffer.frameCount + nonContig;
ALOGV("obtainBuffer(%u) returned %zu = %zu + %zu err %d",
mRemainingFrames, avail, audioBuffer.frameCount, nonContig, err);
if (err != NO_ERROR) {
if (err == TIMED_OUT || err == WOULD_BLOCK || err == -EINTR ||
(isOffloaded() && (err == DEAD_OBJECT))) {
// FIXME bug 25195759
return 1000000;
}
ALOGE("Error %d obtaining an audio buffer, giving up.", err);
return NS_NEVER;
}
if (mRetryOnPartialBuffer && audio_is_linear_pcm(mFormat)) {
mRetryOnPartialBuffer = false;
if (avail < mRemainingFrames) {
if (ns > 0) { // account for obtain time
const nsecs_t timeNow = systemTime();
ns = max((nsecs_t)0, ns - (timeNow - timeAfterCallbacks));
}
nsecs_t myns = framesToNanoseconds(mRemainingFrames - avail, sampleRate, speed);
if (ns < 0 /* NS_WHENEVER */ || myns < ns) {
ns = myns;
}
return ns;
}
}
size_t reqSize = audioBuffer.size;
mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer);
size_t writtenSize = audioBuffer.size;
<span style="color:#ff0000;"> char buf[PROPERTY_VALUE_MAX];
property_get("config.audioData.afterDecoder", buf, "");
if( (0 == strcmp("buf","on")) && (writtenSize !=0)){
FILE* fp = fopen("/data/audio/afterDecoder.pcm", "w+");
fwrite(mUserData,writtenSize,1,fp);</span>
<span style="white-space:pre"> </span>... ...
}</span>
// Sanity check on returned size
if (ssize_t(writtenSize) < 0 || writtenSize > reqSize) {
ALOGE("EVENT_MORE_DATA requested %zu bytes but callback returned %zd bytes",
reqSize, ssize_t(writtenSize));
return NS_NEVER;
}
if (writtenSize == 0) {
// The callback is done filling buffers
// Keep this thread going to handle timed events and
// still try to get more data in intervals of WAIT_PERIOD_MS
// but don't just loop and block the CPU, so wait
// mCbf(EVENT_MORE_DATA, ...) might either
// (1) Block until it can fill the buffer, returning 0 size on EOS.
// (2) Block until it can fill the buffer, returning 0 data (silence) on EOS.
// (3) Return 0 size when no data is available, does not wait for more data.
//
// (1) and (2) occurs with AudioPlayer/AwesomePlayer; (3) occurs with NuPlayer.
// We try to compute the wait time to avoid a tight sleep-wait cycle,
// especially for case (3).
//
// The decision to support (1) and (2) affect the sizing of mRemainingFrames
// and this loop; whereas for case (3) we could simply check once with the full
// buffer size and skip the loop entirely.
nsecs_t myns;
if (audio_is_linear_pcm(mFormat)) {
// time to wait based on buffer occupancy
const nsecs_t datans = mRemainingFrames <= avail ? 0 :
framesToNanoseconds(mRemainingFrames - avail, sampleRate, speed);
// audio flinger thread buffer size (TODO: adjust for fast tracks)
const nsecs_t afns = framesToNanoseconds(mAfFrameCount, mAfSampleRate, speed);
// add a half the AudioFlinger buffer time to avoid soaking CPU if datans is 0.
myns = datans + (afns / 2);
} else {
// FIXME: This could ping quite a bit if the buffer isn't full.
// Note that when mState is stopping we waitStreamEnd, so it never gets here.
myns = kWaitPeriodNs;
}
if (ns > 0) { // account for obtain and callback time
const nsecs_t timeNow = systemTime();
ns = max((nsecs_t)0, ns - (timeNow - timeAfterCallbacks));
}
if (ns < 0 /* NS_WHENEVER */ || myns < ns) {
ns = myns;
}
return ns;
}
size_t releasedFrames = writtenSize / mFrameSize;
audioBuffer.frameCount = releasedFrames;
mRemainingFrames -= releasedFrames;
if (misalignment >= releasedFrames) {
misalignment -= releasedFrames;
} else {
misalignment = 0;
}
releaseBuffer(&audioBuffer);
// FIXME here is where we would repeat EVENT_MORE_DATA again on same advanced buffer
// if callback doesn't like to accept the full chunk
if (writtenSize < reqSize) {
continue;
}
// There could be enough non-contiguous frames available to satisfy the remaining request
if (mRemainingFrames <= nonContig) {
continue;
}
#if 0
// This heuristic tries to collapse a series of EVENT_MORE_DATA that would total to a
// sum <= notificationFrames. It replaces that series by at most two EVENT_MORE_DATA
// that total to a sum == notificationFrames.
if (0 < misalignment && misalignment <= mRemainingFrames) {
mRemainingFrames = misalignment;
return ((double)mRemainingFrames * 1100000000) / ((double)sampleRate * speed);
}
#endif
}
mRemainingFrames = notificationFrames;
mRetryOnPartialBuffer = true;
// A lot has transpired since ns was calculated, so run again immediately and re-calculate
return 0;
}
2.重采样之前的数据("config.audioData.beforeResample"属性开关控制处)
frameworks/av/services/audioflinger/AudioResamplerSinc.cpp
size_t AudioResamplerSinc::resample(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider)
{
const Constants& c(*mConstants);
const size_t headOffset = c.halfNumCoefs*CHANNELS;
int16_t* impulse = mImpulse;
uint32_t vRL = mVolumeRL;
size_t inputIndex = mInputIndex;
uint32_t phaseFraction = mPhaseFraction;
uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
size_t outputSampleCount = outFrameCount * 2;
size_t inFrameCount = getInFrameCountRequired(outFrameCount);
while (outputIndex < outputSampleCount) {
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
provider->getNextBuffer(&mBuffer,calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
goto resample_exit;
}
<span style="color:#ff0000;"><span style="white-space:pre"> </span>char buf[PROPERTY_VALUE_MAX];
property_get("config.audioData.beforeResample", buf, "");
if( 0 == strcmp("buf","on") ){
FILE* fp = fopen("/data/audio/beforeResample.pcm", "w+");
fwrite(mBuffer.i16,toWrite,1,fp);
... ...
}</span>
const uint32_t phaseIndex = phaseFraction >> kNumPhaseBits;
if (phaseIndex == 1) {
// read one frame
read<CHANNELS>(impulse, phaseFraction, mBuffer.i16, inputIndex);
} else if (phaseIndex == 2) {
// read 2 frames
read<CHANNELS>(impulse, phaseFraction, mBuffer.i16, inputIndex);
inputIndex++;
if (inputIndex >= mBuffer.frameCount) {
inputIndex -= mBuffer.frameCount;
provider->releaseBuffer(&mBuffer);
} else {
read<CHANNELS>(impulse, phaseFraction, mBuffer.i16, inputIndex);
}
}
}
int16_t const * const in = mBuffer.i16;
const size_t frameCount = mBuffer.frameCount;
// Always read-in the first samples from the input buffer
int16_t* head = impulse + headOffset;
for (size_t i=0 ; i<CHANNELS ; i++) {
head[i] = in[inputIndex*CHANNELS + i];
}
// handle boundary case
while (CC_LIKELY(outputIndex < outputSampleCount)) {
filterCoefficient<CHANNELS>(&out[outputIndex], phaseFraction, impulse, vRL);
outputIndex += 2;
phaseFraction += phaseIncrement;
const size_t phaseIndex = phaseFraction >> kNumPhaseBits;
for (size_t i=0 ; i<phaseIndex ; i++) {
inputIndex++;
if (inputIndex >= frameCount) {
goto done; // need a new buffer
}
read<CHANNELS>(impulse, phaseFraction, in, inputIndex);
}
}
done:
// if done with buffer, save samples
if (inputIndex >= frameCount) {
inputIndex -= frameCount;
provider->releaseBuffer(&mBuffer);
}
}
resample_exit:
mImpulse = impulse;
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
return outputIndex / CHANNELS;
}
3.重采样之后的数据(“config.audioData.afterResample”属性开关控制处)
frameworks/av/services/threads.cpp
3.重采样之后的数据(“config.audioData.afterResample”属性开关控制处)
frameworks/av/services/threads.cpp
// shared by MIXER and DIRECT, overridden by DUPLICATING
ssize_t AudioFlinger::PlaybackThread::threadLoop_write()
{
// FIXME rewrite to reduce number of system calls
mLastWriteTime = systemTime();
mInWrite = true;
ssize_t bytesWritten;
const size_t offset = mCurrentWriteLength - mBytesRemaining;
// If an NBAIO sink is present, use it to write the normal mixer's submix
if (mNormalSink != 0) {
const size_t count = mBytesRemaining / mFrameSize;
ATRACE_BEGIN("write");
// update the setpoint when AudioFlinger::mScreenState changes
uint32_t screenState = AudioFlinger::mScreenState;
if (screenState != mScreenState) {
mScreenState = screenState;
MonoPipe *pipe = (MonoPipe *)mPipeSink.get();
if (pipe != NULL) {
pipe->setAvgFrames((mScreenState & 1) ?
(pipe->maxFrames() * 7) / 8 : mNormalFrameCount * 2);
}
}
ssize_t framesWritten = mNormalSink->write((char *)mSinkBuffer + offset, count);
<span style="color:#ff0000;">
<span style="white-space:pre"> </span>char buf[PROPERTY_VALUE_MAX];
property_get("config.audioData.afterResample", buf, "");
if( 0 == strcmp("buf","on") ){
FILE* fp = fopen("/data/audio/afterResample.pcm", "w+");
fwrite((char *)mSinkBuffer + offset,framesWritten,1,fp);
... ...
}</span>
ATRACE_END();
if (framesWritten > 0) {
bytesWritten = framesWritten * mFrameSize;
} else {
bytesWritten = framesWritten;
}
mLatchDValid = false;
status_t status = mNormalSink->getTimestamp(mLatchD.mTimestamp);
if (status == NO_ERROR) {
size_t totalFramesWritten = mNormalSink->framesWritten();
if (totalFramesWritten >= mLatchD.mTimestamp.mPosition) {
mLatchD.mUnpresentedFrames = totalFramesWritten - mLatchD.mTimestamp.mPosition;
// mLatchD.mFramesReleased is set immediately before D is clocked into Q
mLatchDValid = true;
}
}
// otherwise use the HAL / AudioStreamOut directly
} else {
// Direct output and offload threads
if (mUseAsyncWrite) {
ALOGW_IF(mWriteAckSequence & 1, "threadLoop_write(): out of sequence write request");
mWriteAckSequence += 2;
mWriteAckSequence |= 1;
ALOG_ASSERT(mCallbackThread != 0);
mCallbackThread->setWriteBlocked(mWriteAckSequence);
}
// FIXME We should have an implementation of timestamps for direct output threads.
// They are used e.g for multichannel PCM playback over HDMI.
bytesWritten = mOutput->write((char *)mSinkBuffer + offset, mBytesRemaining);
if (mUseAsyncWrite &&
((bytesWritten < 0) || (bytesWritten == (ssize_t)mBytesRemaining))) {
// do not wait for async callback in case of error of full write
mWriteAckSequence &= ~1;
ALOG_ASSERT(mCallbackThread != 0);
mCallbackThread->setWriteBlocked(mWriteAckSequence);
}
}
mNumWrites++;
mInWrite = false;
mStandby = false;
return bytesWritten;
}
4.soundEffects音效处理前后音频数据抓取(未完待续)
HAL层音频数据抓取:
1.Ap侧写入pcm设备之前HAL层音频数据("config.audioData.HAL")
hardware/libhardware/modules/audio/audio_hw.c
hardware/libhardware/modules/audio/audio_hw.c
static ssize_t out_write(struct audio_stream_out *stream, const void* buffer,
size_t bytes)
{
ALOGV("out_write: bytes: %d", bytes);
<span style="white-space:pre"> </span>char buf[PROPERTY_VALUE_MAX];
property_get("config.audioData.HAL", buf, "");
if( 0 == strcmp("buf","on") ){
FILE* fp = fopen("/data/audio/HAL.pcm", "w+");
fwrite((buffer,bytes,1,fp);
... ...
}
/* XXX: fake timing for audio output */
usleep((int64_t)bytes * 1000000 / audio_stream_out_frame_size(stream) /
out_get_sample_rate(&stream->common));
return bytes;
}
DSP侧音频数据抓取:(未完待续)