implemented synchronous audio capture
Added the infrastructure to support the synchronization of playback and
capture actions on specific events.
The first requirement for this feature is to synchronize the audio capture
start with the full rendering of a given audio content.
The applications can further be extended to other use cases
(synchronized playback start...) by adding new synchronization events and
new synchronous control methods on player or recorders.
Also added a method to query the audio session from a ToneGenerator.
Change-Id: I51f1167290d9cafdf2fbcdf9e4785156973af44c
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index c5ad0f5..8662cb5 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -513,6 +513,17 @@
Mutex::Autolock _sl(effectThread->mLock);
moveEffectChain_l(lSessionId, effectThread, thread, true);
}
+
+ // Look for sync events awaiting for a session to be used.
+ for (int i = 0; i < (int)mPendingSyncEvents.size(); i++) {
+ if (mPendingSyncEvents[i]->triggerSession() == lSessionId) {
+ if (thread->isValidSyncEvent(mPendingSyncEvents[i])) {
+ track->setSyncEvent(mPendingSyncEvents[i]);
+ mPendingSyncEvents.removeAt(i);
+ i--;
+ }
+ }
+ }
}
if (lStatus == NO_ERROR) {
trackHandle = new TrackHandle(track);
@@ -1933,6 +1944,36 @@
}
}
+status_t AudioFlinger::PlaybackThread::setSyncEvent(const sp<SyncEvent>& event)
+{
+ if (!isValidSyncEvent(event)) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock _l(mLock);
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ sp<Track> track = mTracks[i];
+ if (event->triggerSession() == track->sessionId()) {
+ track->setSyncEvent(event);
+ return NO_ERROR;
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
+bool AudioFlinger::PlaybackThread::isValidSyncEvent(const sp<SyncEvent>& event)
+{
+ switch (event->type()) {
+ case AudioSystem::SYNC_EVENT_PRESENTATION_COMPLETE:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
// ----------------------------------------------------------------------------
AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output,
@@ -2530,7 +2571,15 @@
if (track->isTerminated() || track->isStopped() || track->isPaused()) {
// We have consumed all the buffers of this track.
// Remove it from the list of active tracks.
- tracksToRemove->add(track);
+ // TODO: use actual buffer filling status instead of latency when available from
+ // audio HAL
+ size_t audioHALFrames =
+ (mOutput->stream->get_latency(mOutput->stream)*mSampleRate) / 1000;
+ size_t framesWritten =
+ mBytesWritten / audio_stream_frame_size(&mOutput->stream->common);
+ if (track->presentationComplete(framesWritten, audioHALFrames)) {
+ tracksToRemove->add(track);
+ }
} else {
// No buffers for this track. Give it a few chances to
// fill a buffer, then remove it from active list.
@@ -2909,7 +2958,14 @@
if (track->isTerminated() || track->isStopped() || track->isPaused()) {
// We have consumed all the buffers of this track.
// Remove it from the list of active tracks.
- trackToRemove = track;
+ // TODO: implement behavior for compressed audio
+ size_t audioHALFrames =
+ (mOutput->stream->get_latency(mOutput->stream)*mSampleRate) / 1000;
+ size_t framesWritten =
+ mBytesWritten / audio_stream_frame_size(&mOutput->stream->common);
+ if (track->presentationComplete(framesWritten, audioHALFrames)) {
+ trackToRemove = track;
+ }
} else {
// No buffers for this track. Give it a few chances to
// fill a buffer, then remove it from active list.
@@ -3466,6 +3522,12 @@
return bufferStart;
}
+status_t AudioFlinger::ThreadBase::TrackBase::setSyncEvent(const sp<SyncEvent>& event)
+{
+ mSyncEvents.add(event);
+ return NO_ERROR;
+}
+
// ----------------------------------------------------------------------------
// Track constructor must be called with AudioFlinger::mLock and ThreadBase::mLock held
@@ -3488,7 +3550,8 @@
mName(-1), // see note below
mMainBuffer(thread->mixBuffer()),
mAuxBuffer(NULL),
- mAuxEffectId(0), mHasVolumeController(false)
+ mAuxEffectId(0), mHasVolumeController(false),
+ mPresentationCompleteFrames(0)
{
if (mCblk != NULL) {
// NOTE: audio_track_cblk_t::frameSize for 8 bit PCM data is based on a sample size of
@@ -3627,7 +3690,9 @@
return false;
}
-status_t AudioFlinger::PlaybackThread::Track::start(pid_t tid)
+status_t AudioFlinger::PlaybackThread::Track::start(pid_t tid,
+ AudioSystem::sync_event_t event,
+ int triggerSession)
{
status_t status = NO_ERROR;
ALOGV("start(%d), calling pid %d session %d tid %d",
@@ -3756,6 +3821,7 @@
android_atomic_or(CBLK_UNDERRUN_ON, &mCblk->flags);
mFillingUpStatus = FS_FILLING;
mResetDone = true;
+ mPresentationCompleteFrames = 0;
}
}
@@ -3781,6 +3847,39 @@
mAuxBuffer = buffer;
}
+bool AudioFlinger::PlaybackThread::Track::presentationComplete(size_t framesWritten,
+ size_t audioHalFrames)
+{
+ // a track is considered presented when the total number of frames written to audio HAL
+ // corresponds to the number of frames written when presentationComplete() is called for the
+ // first time (mPresentationCompleteFrames == 0) plus the buffer filling status at that time.
+ if (mPresentationCompleteFrames == 0) {
+ mPresentationCompleteFrames = framesWritten + audioHalFrames;
+ ALOGV("presentationComplete() reset: mPresentationCompleteFrames %d audioHalFrames %d",
+ mPresentationCompleteFrames, audioHalFrames);
+ }
+ if (framesWritten >= mPresentationCompleteFrames) {
+ ALOGV("presentationComplete() session %d complete: framesWritten %d",
+ mSessionId, framesWritten);
+ triggerEvents(AudioSystem::SYNC_EVENT_PRESENTATION_COMPLETE);
+ mPresentationCompleteFrames = 0;
+ return true;
+ }
+ return false;
+}
+
+void AudioFlinger::PlaybackThread::Track::triggerEvents(AudioSystem::sync_event_t type)
+{
+ for (int i = 0; i < (int)mSyncEvents.size(); i++) {
+ if (mSyncEvents[i]->type() == type) {
+ mSyncEvents[i]->trigger();
+ mSyncEvents.removeAt(i);
+ i--;
+ }
+ }
+}
+
+
// timed audio tracks
sp<AudioFlinger::PlaybackThread::TimedTrack>
@@ -4241,12 +4340,14 @@
return NOT_ENOUGH_DATA;
}
-status_t AudioFlinger::RecordThread::RecordTrack::start(pid_t tid)
+status_t AudioFlinger::RecordThread::RecordTrack::start(pid_t tid,
+ AudioSystem::sync_event_t event,
+ int triggerSession)
{
sp<ThreadBase> thread = mThread.promote();
if (thread != 0) {
RecordThread *recordThread = (RecordThread *)thread.get();
- return recordThread->start(this, tid);
+ return recordThread->start(this, tid, event, triggerSession);
} else {
return BAD_VALUE;
}
@@ -4312,9 +4413,11 @@
clearBufferQueue();
}
-status_t AudioFlinger::PlaybackThread::OutputTrack::start(pid_t tid)
+status_t AudioFlinger::PlaybackThread::OutputTrack::start(pid_t tid,
+ AudioSystem::sync_event_t event,
+ int triggerSession)
{
- status_t status = Track::start(tid);
+ status_t status = Track::start(tid, event, triggerSession);
if (status != NO_ERROR) {
return status;
}
@@ -4757,9 +4860,9 @@
return mRecordTrack->getCblk();
}
-status_t AudioFlinger::RecordHandle::start(pid_t tid) {
+status_t AudioFlinger::RecordHandle::start(pid_t tid, int event, int triggerSession) {
ALOGV("RecordHandle::start()");
- return mRecordTrack->start(tid);
+ return mRecordTrack->start(tid, (AudioSystem::sync_event_t)event, triggerSession);
}
void AudioFlinger::RecordHandle::stop() {
@@ -4968,7 +5071,16 @@
}
}
- mActiveTrack->releaseBuffer(&buffer);
+ if (mFramestoDrop == 0) {
+ mActiveTrack->releaseBuffer(&buffer);
+ } else {
+ if (mFramestoDrop > 0) {
+ mFramestoDrop -= buffer.frameCount;
+ if (mFramestoDrop < 0) {
+ mFramestoDrop = 0;
+ }
+ }
+ }
mActiveTrack->overflow();
}
// client isn't retrieving buffers fast enough
@@ -5050,11 +5162,26 @@
return track;
}
-status_t AudioFlinger::RecordThread::start(RecordThread::RecordTrack* recordTrack, pid_t tid)
+status_t AudioFlinger::RecordThread::start(RecordThread::RecordTrack* recordTrack,
+ pid_t tid, AudioSystem::sync_event_t event,
+ int triggerSession)
{
- ALOGV("RecordThread::start tid=%d", tid);
+ ALOGV("RecordThread::start tid=%d, event %d, triggerSession %d", tid, event, triggerSession);
sp<ThreadBase> strongMe = this;
status_t status = NO_ERROR;
+
+ if (event == AudioSystem::SYNC_EVENT_NONE) {
+ mSyncStartEvent.clear();
+ mFramestoDrop = 0;
+ } else if (event != AudioSystem::SYNC_EVENT_SAME) {
+ mSyncStartEvent = mAudioFlinger->createSyncEvent(event,
+ triggerSession,
+ recordTrack->sessionId(),
+ syncStartEventCallback,
+ this);
+ mFramestoDrop = -1;
+ }
+
{
AutoMutex lock(mLock);
if (mActiveTrack != 0) {
@@ -5073,6 +5200,7 @@
mLock.lock();
if (status != NO_ERROR) {
mActiveTrack.clear();
+ clearSyncStartEvent();
return status;
}
mRsmpInIndex = mFrameCount;
@@ -5101,9 +5229,44 @@
}
startError:
AudioSystem::stopInput(mId);
+ clearSyncStartEvent();
return status;
}
+void AudioFlinger::RecordThread::clearSyncStartEvent()
+{
+ if (mSyncStartEvent != 0) {
+ mSyncStartEvent->cancel();
+ }
+ mSyncStartEvent.clear();
+}
+
+void AudioFlinger::RecordThread::syncStartEventCallback(const wp<SyncEvent>& event)
+{
+ sp<SyncEvent> strongEvent = event.promote();
+
+ if (strongEvent != 0) {
+ RecordThread *me = (RecordThread *)strongEvent->cookie();
+ me->handleSyncStartEvent(strongEvent);
+ }
+}
+
+void AudioFlinger::RecordThread::handleSyncStartEvent(const sp<SyncEvent>& event)
+{
+ ALOGV("handleSyncStartEvent() mActiveTrack %p session %d event->listenerSession() %d",
+ mActiveTrack.get(),
+ mActiveTrack.get() ? mActiveTrack->sessionId() : 0,
+ event->listenerSession());
+
+ if (mActiveTrack != 0 &&
+ event == mSyncStartEvent) {
+ // TODO: use actual buffer filling status instead of 2 buffers when info is available
+ // from audio HAL
+ mFramestoDrop = mFrameCount * 2;
+ mSyncStartEvent.clear();
+ }
+}
+
void AudioFlinger::RecordThread::stop(RecordThread::RecordTrack* recordTrack) {
ALOGV("RecordThread::stop");
sp<ThreadBase> strongMe = this;
@@ -5127,6 +5290,26 @@
}
}
+bool AudioFlinger::RecordThread::isValidSyncEvent(const sp<SyncEvent>& event)
+{
+ return false;
+}
+
+status_t AudioFlinger::RecordThread::setSyncEvent(const sp<SyncEvent>& event)
+{
+ if (!isValidSyncEvent(event)) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock _l(mLock);
+
+ if (mTrack != NULL && event->triggerSession() == mTrack->sessionId()) {
+ mTrack->setSyncEvent(event);
+ return NO_ERROR;
+ }
+ return NAME_NOT_FOUND;
+}
+
status_t AudioFlinger::RecordThread::dump(int fd, const Vector<String16>& args)
{
const size_t SIZE = 256;
@@ -5899,6 +6082,37 @@
return thread->device();
}
+sp<AudioFlinger::SyncEvent> AudioFlinger::createSyncEvent(AudioSystem::sync_event_t type,
+ int triggerSession,
+ int listenerSession,
+ sync_event_callback_t callBack,
+ void *cookie)
+{
+ Mutex::Autolock _l(mLock);
+
+ sp<SyncEvent> event = new SyncEvent(type, triggerSession, listenerSession, callBack, cookie);
+ status_t playStatus = NAME_NOT_FOUND;
+ status_t recStatus = NAME_NOT_FOUND;
+ for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
+ playStatus = mPlaybackThreads.valueAt(i)->setSyncEvent(event);
+ if (playStatus == NO_ERROR) {
+ return event;
+ }
+ }
+ for (size_t i = 0; i < mRecordThreads.size(); i++) {
+ recStatus = mRecordThreads.valueAt(i)->setSyncEvent(event);
+ if (recStatus == NO_ERROR) {
+ return event;
+ }
+ }
+ if (playStatus == NAME_NOT_FOUND || recStatus == NAME_NOT_FOUND) {
+ mPendingSyncEvents.add(event);
+ } else {
+ ALOGV("createSyncEvent() invalid event %d", event->type());
+ event.clear();
+ }
+ return event;
+}
// ----------------------------------------------------------------------------
// Effect management
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 795807d..2376aff 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -204,6 +204,44 @@
// end of IAudioFlinger interface
+ class SyncEvent;
+
+ typedef void (*sync_event_callback_t)(const wp<SyncEvent>& event) ;
+
+ class SyncEvent : public RefBase {
+ public:
+ SyncEvent(AudioSystem::sync_event_t type,
+ int triggerSession,
+ int listenerSession,
+ sync_event_callback_t callBack,
+ void *cookie)
+ : mType(type), mTriggerSession(triggerSession), mListenerSession(listenerSession),
+ mCallback(callBack), mCookie(cookie)
+ {}
+
+ virtual ~SyncEvent() {}
+
+ void trigger() { Mutex::Autolock _l(mLock); if (mCallback) mCallback(this); }
+ void cancel() {Mutex::Autolock _l(mLock); mCallback = NULL; }
+ AudioSystem::sync_event_t type() const { return mType; }
+ int triggerSession() const { return mTriggerSession; }
+ int listenerSession() const { return mListenerSession; }
+ void *cookie() const { return mCookie; }
+
+ private:
+ const AudioSystem::sync_event_t mType;
+ const int mTriggerSession;
+ const int mListenerSession;
+ sync_event_callback_t mCallback;
+ void * const mCookie;
+ Mutex mLock;
+ };
+
+ sp<SyncEvent> createSyncEvent(AudioSystem::sync_event_t type,
+ int triggerSession,
+ int listenerSession,
+ sync_event_callback_t callBack,
+ void *cookie);
private:
audio_mode_t getMode() const { return mMode; }
@@ -334,11 +372,14 @@
int sessionId);
virtual ~TrackBase();
- virtual status_t start(pid_t tid) = 0;
+ virtual status_t start(pid_t tid,
+ AudioSystem::sync_event_t event = AudioSystem::SYNC_EVENT_NONE,
+ int triggerSession = 0) = 0;
virtual void stop() = 0;
sp<IMemory> getCblk() const { return mCblkMemory; }
audio_track_cblk_t* cblk() const { return mCblk; }
int sessionId() const { return mSessionId; }
+ virtual status_t setSyncEvent(const sp<SyncEvent>& event);
protected:
TrackBase(const TrackBase&);
@@ -385,6 +426,7 @@
const int mSessionId;
uint8_t mChannelCount;
uint32_t mChannelMask;
+ Vector < sp<SyncEvent> >mSyncEvents;
};
class ConfigEvent {
@@ -499,6 +541,11 @@
void checkSuspendOnEffectEnabled_l(const sp<EffectModule>& effect,
bool enabled,
int sessionId = AUDIO_SESSION_OUTPUT_MIX);
+
+ virtual status_t setSyncEvent(const sp<SyncEvent>& event) = 0;
+ virtual bool isValidSyncEvent(const sp<SyncEvent>& event) = 0;
+
+
mutable Mutex mLock;
protected:
@@ -619,7 +666,9 @@
virtual ~Track();
void dump(char* buffer, size_t size);
- virtual status_t start(pid_t tid);
+ virtual status_t start(pid_t tid,
+ AudioSystem::sync_event_t event = AudioSystem::SYNC_EVENT_NONE,
+ int triggerSession = 0);
virtual void stop();
void pause();
@@ -670,6 +719,9 @@
return (mStreamType == AUDIO_STREAM_CNT);
}
+ bool presentationComplete(size_t framesWritten, size_t audioHalFrames);
+ void triggerEvents(AudioSystem::sync_event_t type);
+
public:
virtual bool isTimedTrack() const { return false; }
protected:
@@ -688,6 +740,8 @@
int32_t *mAuxBuffer;
int mAuxEffectId;
bool mHasVolumeController;
+ size_t mPresentationCompleteFrames; // number of frames written to the audio HAL
+ // when this track will be fully rendered
}; // end of Track
class TimedTrack : public Track {
@@ -782,7 +836,9 @@
int frameCount);
virtual ~OutputTrack();
- virtual status_t start(pid_t tid);
+ virtual status_t start(pid_t tid,
+ AudioSystem::sync_event_t event = AudioSystem::SYNC_EVENT_NONE,
+ int triggerSession = 0);
virtual void stop();
bool write(int16_t* data, uint32_t frames);
bool bufferQueueEmpty() const { return mBufferQueue.size() == 0; }
@@ -885,6 +941,9 @@
void setStreamValid(audio_stream_type_t streamType, bool valid);
+ virtual status_t setSyncEvent(const sp<SyncEvent>& event);
+ virtual bool isValidSyncEvent(const sp<SyncEvent>& event);
+
protected:
int16_t* mMixBuffer;
uint32_t mSuspended; // suspend count, > 0 means suspended
@@ -1145,7 +1204,9 @@
int sessionId);
virtual ~RecordTrack();
- virtual status_t start(pid_t tid);
+ virtual status_t start(pid_t tid,
+ AudioSystem::sync_event_t event = AudioSystem::SYNC_EVENT_NONE,
+ int triggerSession = 0);
virtual void stop();
bool overflow() { bool tmp = mOverflow; mOverflow = false; return tmp; }
@@ -1192,8 +1253,9 @@
int sessionId,
status_t *status);
- status_t start(RecordTrack* recordTrack);
- status_t start(RecordTrack* recordTrack, pid_t tid);
+ status_t start(RecordTrack* recordTrack, pid_t tid,
+ AudioSystem::sync_event_t event,
+ int triggerSession);
void stop(RecordTrack* recordTrack);
status_t dump(int fd, const Vector<String16>& args);
AudioStreamIn* getInput() const;
@@ -1215,7 +1277,15 @@
virtual uint32_t hasAudioSession(int sessionId);
RecordTrack* track();
+ virtual status_t setSyncEvent(const sp<SyncEvent>& event);
+ virtual bool isValidSyncEvent(const sp<SyncEvent>& event);
+
+ static void syncStartEventCallback(const wp<SyncEvent>& event);
+ void handleSyncStartEvent(const sp<SyncEvent>& event);
+
private:
+ void clearSyncStartEvent();
+
RecordThread();
AudioStreamIn *mInput;
RecordTrack* mTrack;
@@ -1229,6 +1299,11 @@
const int mReqChannelCount;
const uint32_t mReqSampleRate;
ssize_t mBytesRead;
+ // sync event triggering actual audio capture. Frames read before this event will
+ // be dropped and therefore not read by the application.
+ sp<SyncEvent> mSyncStartEvent;
+ // number of captured frames to drop after the start sync event has been received.
+ ssize_t mFramestoDrop;
};
// server side of the client's IAudioRecord
@@ -1237,7 +1312,7 @@
RecordHandle(const sp<RecordThread::RecordTrack>& recordTrack);
virtual ~RecordHandle();
virtual sp<IMemory> getCblk() const;
- virtual status_t start(pid_t tid);
+ virtual status_t start(pid_t tid, int event, int triggerSession);
virtual void stop();
virtual status_t onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
@@ -1676,6 +1751,9 @@
float masterVolumeSW_l() const { return mMasterVolumeSW; }
bool masterMute_l() const { return mMasterMute; }
+ Vector < sp<SyncEvent> > mPendingSyncEvents; // sync events awaiting for a session
+ // to be created
+
private:
sp<Client> registerPid_l(pid_t pid); // always returns non-0