Android Audio Tutorial [Part Four] : PlaybackThread
In the previous tutorial we saw how the PlaybackThread is created
and how the thread looping starts.
Let us analyze further.
PlaybackThread:
When a PlaybackThread enters the threadLoop() the audio
transaction is officially enabled.
If we look closely, we will find that this loop will continue to
call several interfaces that start with "threadLoop_",
such as threadLoop_mix( ), threadLoop_sleepTime( ), threadLoop_standby( ), etc.
Starting the above with such a prefix is because these functions
are invoked in the body of threadLoop which can be said to represent the
various steps that the PlaybackThread needs to complete.
We have already seen in the previous tutorial that when the
program executes to PlaybackThread::onFirstRef( ) it will actually start a
thread just keep running the threadLoop( ) function.
Let us take a look at the processing flow of this loop body. From
the file frameworks/av/services/audioflinger/Threads.cpp.
Uffff , Quite a Long
function.
Let us step back and see
some of the important points of interest in this function.
while (!exitPending()) :
The condition of the
loop is !exitPending() is true.
a. This
function belongs to the Thread class, which mainly determines whether to end
the thread by determining the value of the internal variable mExitPending.
b. The
variable mExitPending is fasle when Thread is initialized.
c. If
someone later requests to exit through requestExit(), requestExitAndWait, etc.,
this value will change, which will cause PlaybackThread to end the loop.
processConfigEvents_l(): PlaybackThread::threadLoop() handles the
config event.
a. When
a configuration change event occurs, PlaybackThread can be notified via
sendConfigEvent.
b. This
function will add events to the mConfigEvents global variable for processing by
processConfigEvents.
c. Configuration
events include the following:
shouldStandby_l(): determine if the current condition
of Standby is met, and if so, call threadLoop_standby.
mMixerStatus = prepareTracks_l(&tracksToRemove):
data preparation using prepareTracks_l .
Wow, Some big function. Let us not get frightened by
this function above and try to see some of the core steps done above.
Get the number of currently active tracks. mActiveTracks is a SortedVector that records the currently active track. It will expand with the addition of the new AudioTrack, and will also remove the corresponding track if necessary (AudioTrack is over, or something goes wrong, etc.).
Loop through each track, which is the core of the
function.
Processing under FastTrack [We will cover Fastmixer
threads later].
This is the most important step in the preparation
work. It is buffering data. Before learning the code details, let us first
understand the underrun conditions that are likely to occur during data
transfer.
In a "producer-consumer" relationship between
two devices or processes, Underrun occurs if the speed of production is not as
fast as the consumer consumes.
Taking audio playback as an example, the sound heard
by the user at this time may be intermittent, or the data in the current buffer
may be repeatedly played (depending on the specific implementation).
Preparing data is divided into the following sub-sections.
minFrames : How much frame data do I need to prepare
before playing back audio?
When track->sharedBuffer() is 0, it indicates that the
data is transmitted at once.
The global variable mSampleRate is obtained via
mOutput->stream->common.get_sample_rate, which is provided by the HAL and
represents the device's Sampling rate.
Check if data is ready.
In the previous steps we had already calculated the
minimum frame value of the data, ie minFrames, and then determine whether the
current situation meets this criterion.
After getting the values of vl, vr, and va we apply
them to AudioMixer.
The real implementation is in threadLoop_mix() for the
volume levels which we will discuss later.
After performing the above processing for each track,
it returns a result at the end, which usually depends on
whether there is activetrack
active track data is ready
The final value returned will affect the next step in
threadLoop.
Let us continue our analysis of prepareTracks_l, we
return to the previous threadLoop.
If the data preparation in the previous step has been
completed (ie, the return value is MIXER_TRACKS_READY), a real mix operation is
started which is threadLoop_mix() else it sleeps for a certain period of time -
so it will cycle until it exits the loop.
Going back, If the data is not yet ready, AudioFlinger will call
threadLoop_sleepTime( ) to calculate how long to sleep (the variable sleepTime),
and perform usleep to sleep at the end of the threadLoop main loop (before the
remove track).
Finally, threadLoop_write() writes data to the HAL and writes
it in chunks to the hardware device.
The above is composed of two situations:
1. If
an NBAIO sink is present, use it to write the normal mixer's submix.
2. otherwise
use the HAL / AudioStreamOut directly.
Coming back to threadLoop, Finally, remove the Tracks
indicated in tracksToRemove.
Whether or not to remove a track is determined in
prepareTracks_l( ).
How does prepareTracks_l( ) decide which tracks to
remove?
1. For
Fast Track, if its status (mState) is STOPPING_2, PAUSED, TERMINATED, STOPPED,
FLUSHED, or the status is ACTIVE but underrun exceeds the limit (mRetryCount),
it will be added to the tracksToRemove list.
2. The
current track data is not ready, and is STATICTRACK or has been stopped/paused,
will also be added to the tracksToRemove list.
3. For
the tracks in the tracksToRemove list, the output associated with it will
receive a stop request (initiated by AudioSystem::stopOutput).
We will continue our analysis of other components in the next tutorial/s.
Comments
Post a Comment