diff --git a/media/libsydneyaudio/src/Makefile.in b/media/libsydneyaudio/src/Makefile.in index 692bbea285e..332eb468e08 100644 --- a/media/libsydneyaudio/src/Makefile.in +++ b/media/libsydneyaudio/src/Makefile.in @@ -52,8 +52,8 @@ CSRCS = \ endif ifeq ($(MOZ_WIDGET_TOOLKIT),gonk) -CSRCS = \ - sydney_audio_gonk.c \ +CPPSRCS = \ + sydney_audio_gonk.cpp \ $(NULL) else ifeq ($(MOZ_WIDGET_TOOLKIT),android) CSRCS = \ diff --git a/media/libsydneyaudio/src/sydney_audio_gonk.c b/media/libsydneyaudio/src/sydney_audio_gonk.cpp similarity index 63% rename from media/libsydneyaudio/src/sydney_audio_gonk.c rename to media/libsydneyaudio/src/sydney_audio_gonk.cpp index 3f3c6e7b27d..5f43f3be1f3 100644 --- a/media/libsydneyaudio/src/sydney_audio_gonk.c +++ b/media/libsydneyaudio/src/sydney_audio_gonk.cpp @@ -36,9 +36,12 @@ #include #include +extern "C" { #include "sydney_audio.h" +} #include "android/log.h" +#include "media/AudioTrack.h" #ifndef ALOG #if defined(DEBUG) || defined(FORCE_ALOG) @@ -46,15 +49,19 @@ #else #define ALOG(args...) #endif -#endif +#endif -/* Gonk implementation based on sydney_audio_mac.c */ -/* XXX This is temporary until we figure out a way to hook ALSA up */ +/* Gonk implementation based on sydney_audio_android.c */ +#define NANOSECONDS_PER_SECOND 1000000000 #define NANOSECONDS_IN_MILLISECOND 1000000 #define MILLISECONDS_PER_SECOND 1000 +using namespace android; + struct sa_stream { + AudioTrack *output_unit; + unsigned int rate; unsigned int channels; unsigned int isPaused; @@ -103,10 +110,11 @@ sa_stream_create_pcm( * Allocate the instance and required resources. */ sa_stream_t *s; - if ((s = malloc(sizeof(sa_stream_t))) == NULL) { + if ((s = (sa_stream_t *)malloc(sizeof(sa_stream_t))) == NULL) { return SA_ERROR_OOM; } + s->output_unit = NULL; s->rate = rate; s->channels = channels; s->isPaused = 0; @@ -115,7 +123,7 @@ sa_stream_create_pcm( s->timePlaying = 0; s->amountWritten = 0; - s->bufferSize = rate * channels; + s->bufferSize = 0; *_s = s; return SA_SUCCESS; @@ -128,8 +136,47 @@ sa_stream_open(sa_stream_t *s) { if (s == NULL) { return SA_ERROR_NO_INIT; } + if (s->output_unit != NULL) { + return SA_ERROR_INVALID; + } - return SA_ERROR_NO_DEVICE; + int32_t chanConfig = s->channels == 1 ? + AudioSystem::CHANNEL_OUT_MONO : AudioSystem::CHANNEL_OUT_STEREO; + + int frameCount; + if (AudioTrack::getMinFrameCount(&frameCount, AudioSystem::DEFAULT, + s->rate) != NO_ERROR) { + return SA_ERROR_INVALID; + } + int minsz = frameCount * s->channels * sizeof(int16_t); + + s->bufferSize = s->rate * s->channels * sizeof(int16_t); + if (s->bufferSize < minsz) { + s->bufferSize = minsz; + } + + AudioTrack *track = + new AudioTrack(AudioSystem::SYSTEM, + s->rate, + AudioSystem::PCM_16_BIT, + chanConfig, + frameCount, + 0, + NULL, NULL, + 0, + 0); + + if (track->initCheck() != NO_ERROR) { + delete track; + return SA_ERROR_INVALID; + } + + s->output_unit = track; + + ALOG("%p - New stream %u %u bsz=%u min=%u", s, s->rate, s->channels, + s->bufferSize, minsz); + + return SA_SUCCESS; } @@ -139,7 +186,20 @@ sa_stream_destroy(sa_stream_t *s) { if (s == NULL) { return SA_ERROR_NO_INIT; } -// XXX + + static bool firstLeaked = 0; + if (s->output_unit) { + s->output_unit->stop(); + s->output_unit->flush(); + // XXX: Figure out why we crash if we don't leak the first AudioTrack + if (firstLeaked) + delete s->output_unit; + else + firstLeaked = true; + } + free(s); + + ALOG("%p - Stream destroyed", s); return SA_SUCCESS; } @@ -153,14 +213,42 @@ sa_stream_destroy(sa_stream_t *s) { int sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) { - if (s == NULL) { + if (s == NULL || s->output_unit == NULL) { return SA_ERROR_NO_INIT; } if (nbytes == 0) { return SA_SUCCESS; } -// XXX - return SA_SUCCESS; + + const char *p = (char *)data; + ssize_t r = 0; + size_t wrote = 0; + do { + size_t towrite = nbytes - wrote; + + r = s->output_unit->write(p, towrite); + if (r < 0) { + ALOG("%p - Write failed %d", s, r); + break; + } + + /* AudioTrack::write is blocking when the AudioTrack is playing. When + it's not playing, it's a non-blocking call that will return a short + write when the buffer is full. Use a short write to indicate a good + time to start the AudioTrack playing. */ + if (r != towrite) { + ALOG("%p - Buffer full, starting playback", s); + sa_stream_resume(s); + } + + p += r; + wrote += r; + } while (wrote < nbytes); + + ALOG("%p - Wrote %u", s, nbytes); + s->amountWritten += nbytes; + + return r < 0 ? SA_ERROR_INVALID : SA_SUCCESS; } @@ -173,11 +261,21 @@ sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) { int sa_stream_get_write_size(sa_stream_t *s, size_t *size) { - if (s == NULL) { + if (s == NULL || s->output_unit == NULL) { return SA_ERROR_NO_INIT; } -// XXX + /* No android API for this, so estimate based on how much we have played and + * how much we have written. */ + *size = s->bufferSize - ((s->timePlaying * s->channels * s->rate * sizeof(int16_t) / + MILLISECONDS_PER_SECOND) - s->amountWritten); + + /* Available buffer space can't exceed bufferSize. */ + if (*size > s->bufferSize) { + *size = s->bufferSize; + } + ALOG("%p - Write Size tp=%lld aw=%u sz=%zu", s, s->timePlaying, s->amountWritten, *size); + return SA_SUCCESS; } @@ -185,11 +283,20 @@ sa_stream_get_write_size(sa_stream_t *s, size_t *size) { int sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) { - if (s == NULL) { + if (s == NULL || s->output_unit == NULL) { return SA_ERROR_NO_INIT; } -// XXX + ALOG("%p - get position", s); + + uint32_t framePosition; + if (s->output_unit->getPosition(&framePosition) != NO_ERROR) + return SA_ERROR_INVALID; + + /* android returns number of frames, so: + position = frames * (PCM_16_BIT == 2 bytes) * channels + */ + *pos = framePosition * s->channels * sizeof(int16_t); return SA_SUCCESS; } @@ -197,11 +304,23 @@ sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) { int sa_stream_pause(sa_stream_t *s) { - if (s == NULL) { + if (s == NULL || s->output_unit == NULL) { return SA_ERROR_NO_INIT; } -// XXX + s->isPaused = 1; + + /* Update stats */ + if (s->lastStartTime != 0) { + /* if lastStartTime is not zero, so playback has started */ + struct timespec current_time; + clock_gettime(CLOCK_REALTIME, ¤t_time); + int64_t ticker = current_time.tv_sec * 1000 + current_time.tv_nsec / 1000000; + s->timePlaying += ticker - s->lastStartTime; + } + ALOG("%p - Pause total time playing: %lld total written: %lld", s, s->timePlaying, s->amountWritten); + + s->output_unit->pause(); return SA_SUCCESS; } @@ -209,11 +328,21 @@ sa_stream_pause(sa_stream_t *s) { int sa_stream_resume(sa_stream_t *s) { - if (s == NULL) { + if (s == NULL || s->output_unit == NULL) { return SA_ERROR_NO_INIT; } -// XXX + ALOG("%p - resume", s); + + s->isPaused = 0; + + /* Update stats */ + struct timespec current_time; + clock_gettime(CLOCK_REALTIME, ¤t_time); + int64_t ticker = current_time.tv_sec * 1000 + current_time.tv_nsec / 1000000; + s->lastStartTime = ticker; + + s->output_unit->start(); return SA_SUCCESS; } @@ -221,11 +350,36 @@ sa_stream_resume(sa_stream_t *s) { int sa_stream_drain(sa_stream_t *s) { - if (s == NULL) { + if (s == NULL || s->output_unit == NULL) { return SA_ERROR_NO_INIT; } -// XXX +/* This is somewhat of a hack (see bug 693131). The AudioTrack documentation + doesn't make it clear how much data must be written before a chunk of data is + played, and experimentation with short streams required filling the entire + allocated buffer. To guarantee that short streams (and the end of longer + streams) are audible, write an entire bufferSize of silence before sleeping. + This guarantees the short write logic in sa_stream_write is hit and the + stream is playing before sleeping. Note that the sleep duration is + calculated from the duration of audio written before writing silence. */ + size_t available; + sa_stream_get_write_size(s, &available); + + void *p = calloc(1, s->bufferSize); + sa_stream_write(s, p, s->bufferSize); + free(p); + + /* There is no way with the Android SDK to determine exactly how + long to playback. So estimate and sleep for that long. */ + unsigned long long x = (s->bufferSize - available) * 1000 / s->channels / s->rate / + sizeof(int16_t) * NANOSECONDS_IN_MILLISECOND; + ALOG("%p - Drain - flush %u, sleep for %llu ns", s, available, x); + + struct timespec ts = {(time_t)(x / NANOSECONDS_PER_SECOND), + (time_t)(x % NANOSECONDS_PER_SECOND)}; + nanosleep(&ts, NULL); + s->output_unit->flush(); + return SA_SUCCESS; } @@ -239,11 +393,11 @@ sa_stream_drain(sa_stream_t *s) int sa_stream_set_volume_abs(sa_stream_t *s, float vol) { - if (s == NULL) { + if (s == NULL || s->output_unit == NULL) { return SA_ERROR_NO_INIT; } -// XXX + s->output_unit->setVolume(vol, vol); return SA_SUCCESS; }