Bug 710051 - Port the android libsydneyaudio backend to gonk, r=kinetik a=gonk-only

This commit is contained in:
Michael Wu 2011-12-12 17:35:52 -08:00
parent 40de82fbfe
commit fe94f50942
2 changed files with 178 additions and 24 deletions

View File

@ -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 = \

View File

@ -36,9 +36,12 @@
#include <stdlib.h>
#include <time.h>
extern "C" {
#include "sydney_audio.h"
}
#include "android/log.h"
#include "media/AudioTrack.h"
#ifndef ALOG
#if defined(DEBUG) || defined(FORCE_ALOG)
@ -48,13 +51,17 @@
#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, &current_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, &current_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;
}