The API functions
sm_play_tone(),
sm_play_cptone(),
and sm_play_digits()
have a wait_for_completion
parameter field which can
make them wait until the play operation has completed. In some
situations this method of waiting is unsuitable because it also
blocks any other use of the same channel. For example, if you want to
play a tone while recording speech on the same channel, the collection
of recorded data cannot proceed while the tone is playing.
Since this is caused by the guarantee of thread-safety provided by the Prosody API, if the play is split into several Prosody API calls, the maximum delay is reduced to the time of the longest such call. Using code similar to this:
int evsafewait_sm_play_digits(SM_PLAY_DIGITS_PARMS *pp, tSMEventId ev) { int e; pp->wait_for_completion = 0; e = sm_play_digits(pp); if (e) return e; for (;;) { SM_PLAY_DIGITS_STATUS sp; e = smd_ev_wait(ev); if (e) return e; memset(&sp, 0, sizeof(sp)); sp.channel = pp->channel; e = sm_play_digits_status(&sp); if (e) return e; if (sp.status == kSMPlayDigitsStatusComplete) return 0; } }
it is possible to wait for the play to finish without blocking other operations. This is because, once sm_play_digits() has started the play, the only Prosody API call which operates on the channel is sm_play_digits_status(), which does not block.
Obviously, equivalent functions would be needed to wait for sm_play_tone(), and sm_play_cptone().
This method of waiting is not atomic with respect to Prosody API
functions, so it is possible that, while this thread is trying to
wait, another thread can call
sm_play_digits_status(),
seeing the kSMPlayDigitsStatusComplete
status, and
possiby making this thread get the error
ERR_SM_WRONG_CHANNEL_STATE
when it next tries to check
the status. If the event is a non-idle event, then this thread will
get stuck waiting on an event that will never be signalled (since
non-idle events are not signalled while no operation is in progress).
Consequently, if this method is used, the application must
co-ordinate access to the channel to ensure that only one thread at a
time attempts to play digits or tones on a channel.
If no event has been associated with the channel, one must be allocated for waiting on, which can be done like this:
// if channel does not have a suitable event already allocated int safewait_sm_play_digits(SM_PLAY_DIGITS_PARMS *pp) { SM_CHANNEL_SET_EVENT_PARMS ep; int e, e2; memset(&ep, 0, sizeof(ep)); ep.channel = pp->channel; ep.event_type = kSMEventTypeWriteData; ep.issue_events = kSMChannelSpecificEvent; e = smd_ev_create(&ep.event, ep.channel, ep.event_type, ep.issue_events); if (e) return e; e = sm_channel_set_event(&ep); if (!e) { SM_CHANNEL_SET_EVENT_PARMS up; int e3; memset(&up, 0, sizeof(up)); e = evsafewait_sm_play_digits(pp, ep.event); up.channel = pp->channel; up.event_type = kSMEventTypeWriteData; up.issue_events = kSMChannelNoEvent; e3 = sm_channel_set_event(&up); if (!e) e = e3; // earlier error has priority } e2 = smd_ev_free(ep.event); if (!e) e = e2; // we lose the error e2 if we got an earlier one return e; }
Obviously, this imposes further requirements on the application to
avoid thread-safety problems. For example, the application cannot
call this function in one thread while doing anything in another
thread on the same channel which sets or uses the write-event, however
typical applications usually fall into one of two catagories: those
which assign the event to a channel as soon as the channel is allocated,
which can use the above evsafewait_sm_play_digits()
directly, and those which only ever assign an event to a channel,
which can use the above safewait_sm_play_digits()
. Other
types of applications will need the above code to be adapted to suit
them.