Marco Nelissen e274db1efd b/2353646
Fix music visualizations to also work with audiotrack callbacks, which
stagefright uses. This slightly changes the way the data is stored,
since before we were relying on the buffers being written always
being at least 4K, whereas the callbacks are generally for smaller
amounts of data. Now we append all the data to a big circular buffer,
then return chunks of that buffer for visualization. When there are
multiple things playing at the same time, this will give the wrong
result, but (1) that was the case before as well, and (2) will be
fixed once we start visualizing the mixer output instead of the
mixer inputs.
2010-01-12 10:27:04 -08:00

1686 lines
48 KiB
C++

/*
**
** Copyright 2008, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
// Proxy for media player implementations
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaPlayerService"
#include <utils/Log.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <cutils/atomic.h>
#include <cutils/properties.h> // for property_get
#include <utils/misc.h>
#include <android_runtime/ActivityManager.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/MemoryHeapBase.h>
#include <binder/MemoryBase.h>
#include <utils/Errors.h> // for status_t
#include <utils/String8.h>
#include <utils/SystemClock.h>
#include <utils/Vector.h>
#include <cutils/properties.h>
#include <media/MediaPlayerInterface.h>
#include <media/mediarecorder.h>
#include <media/MediaMetadataRetrieverInterface.h>
#include <media/Metadata.h>
#include <media/AudioTrack.h>
#include "MediaRecorderClient.h"
#include "MediaPlayerService.h"
#include "MetadataRetrieverClient.h"
#include "MidiFile.h"
#include "VorbisPlayer.h"
#include <media/PVPlayer.h>
#include "TestPlayerStub.h"
#include "StagefrightPlayer.h"
#include <OMX.h>
/* desktop Linux needs a little help with gettid() */
#if defined(HAVE_GETTID) && !defined(HAVE_ANDROID_OS)
#define __KERNEL__
# include <linux/unistd.h>
#ifdef _syscall0
_syscall0(pid_t,gettid)
#else
pid_t gettid() { return syscall(__NR_gettid);}
#endif
#undef __KERNEL__
#endif
namespace {
using android::media::Metadata;
using android::status_t;
using android::OK;
using android::BAD_VALUE;
using android::NOT_ENOUGH_DATA;
using android::Parcel;
// Max number of entries in the filter.
const int kMaxFilterSize = 64; // I pulled that out of thin air.
// FIXME: Move all the metadata related function in the Metadata.cpp
// Unmarshall a filter from a Parcel.
// Filter format in a parcel:
//
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | number of entries (n) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | metadata type 1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | metadata type 2 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// ....
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | metadata type n |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// @param p Parcel that should start with a filter.
// @param[out] filter On exit contains the list of metadata type to be
// filtered.
// @param[out] status On exit contains the status code to be returned.
// @return true if the parcel starts with a valid filter.
bool unmarshallFilter(const Parcel& p,
Metadata::Filter *filter,
status_t *status)
{
int32_t val;
if (p.readInt32(&val) != OK)
{
LOGE("Failed to read filter's length");
*status = NOT_ENOUGH_DATA;
return false;
}
if( val > kMaxFilterSize || val < 0)
{
LOGE("Invalid filter len %d", val);
*status = BAD_VALUE;
return false;
}
const size_t num = val;
filter->clear();
filter->setCapacity(num);
size_t size = num * sizeof(Metadata::Type);
if (p.dataAvail() < size)
{
LOGE("Filter too short expected %d but got %d", size, p.dataAvail());
*status = NOT_ENOUGH_DATA;
return false;
}
const Metadata::Type *data =
static_cast<const Metadata::Type*>(p.readInplace(size));
if (NULL == data)
{
LOGE("Filter had no data");
*status = BAD_VALUE;
return false;
}
// TODO: The stl impl of vector would be more efficient here
// because it degenerates into a memcpy on pod types. Try to
// replace later or use stl::set.
for (size_t i = 0; i < num; ++i)
{
filter->add(*data);
++data;
}
*status = OK;
return true;
}
// @param filter Of metadata type.
// @param val To be searched.
// @return true if a match was found.
bool findMetadata(const Metadata::Filter& filter, const int32_t val)
{
// Deal with empty and ANY right away
if (filter.isEmpty()) return false;
if (filter[0] == Metadata::kAny) return true;
return filter.indexOf(val) >= 0;
}
} // anonymous namespace
namespace android {
// TODO: Temp hack until we can register players
typedef struct {
const char *extension;
const player_type playertype;
} extmap;
extmap FILE_EXTS [] = {
{".mid", SONIVOX_PLAYER},
{".midi", SONIVOX_PLAYER},
{".smf", SONIVOX_PLAYER},
{".xmf", SONIVOX_PLAYER},
{".imy", SONIVOX_PLAYER},
{".rtttl", SONIVOX_PLAYER},
{".rtx", SONIVOX_PLAYER},
{".ota", SONIVOX_PLAYER},
{".ogg", VORBIS_PLAYER},
{".oga", VORBIS_PLAYER},
};
// TODO: Find real cause of Audio/Video delay in PV framework and remove this workaround
/* static */ int MediaPlayerService::AudioOutput::mMinBufferCount = 4;
/* static */ bool MediaPlayerService::AudioOutput::mIsOnEmulator = false;
void MediaPlayerService::instantiate() {
defaultServiceManager()->addService(
String16("media.player"), new MediaPlayerService());
}
MediaPlayerService::MediaPlayerService()
{
LOGV("MediaPlayerService created");
mNextConnId = 1;
}
MediaPlayerService::~MediaPlayerService()
{
LOGV("MediaPlayerService destroyed");
}
sp<IMediaRecorder> MediaPlayerService::createMediaRecorder(pid_t pid)
{
#ifndef NO_OPENCORE
sp<MediaRecorderClient> recorder = new MediaRecorderClient(this, pid);
wp<MediaRecorderClient> w = recorder;
Mutex::Autolock lock(mLock);
mMediaRecorderClients.add(w);
#else
sp<MediaRecorderClient> recorder = NULL;
#endif
LOGV("Create new media recorder client from pid %d", pid);
return recorder;
}
void MediaPlayerService::removeMediaRecorderClient(wp<MediaRecorderClient> client)
{
Mutex::Autolock lock(mLock);
mMediaRecorderClients.remove(client);
LOGV("Delete media recorder client");
}
sp<IMediaMetadataRetriever> MediaPlayerService::createMetadataRetriever(pid_t pid)
{
sp<MetadataRetrieverClient> retriever = new MetadataRetrieverClient(pid);
LOGV("Create new media retriever from pid %d", pid);
return retriever;
}
sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url)
{
int32_t connId = android_atomic_inc(&mNextConnId);
sp<Client> c = new Client(this, pid, connId, client);
LOGV("Create new client(%d) from pid %d, url=%s, connId=%d", connId, pid, url, connId);
if (NO_ERROR != c->setDataSource(url))
{
c.clear();
return c;
}
wp<Client> w = c;
Mutex::Autolock lock(mLock);
mClients.add(w);
return c;
}
sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClient>& client,
int fd, int64_t offset, int64_t length)
{
int32_t connId = android_atomic_inc(&mNextConnId);
sp<Client> c = new Client(this, pid, connId, client);
LOGV("Create new client(%d) from pid %d, fd=%d, offset=%lld, length=%lld",
connId, pid, fd, offset, length);
if (NO_ERROR != c->setDataSource(fd, offset, length)) {
c.clear();
} else {
wp<Client> w = c;
Mutex::Autolock lock(mLock);
mClients.add(w);
}
::close(fd);
return c;
}
sp<IOMX> MediaPlayerService::getOMX() {
Mutex::Autolock autoLock(mLock);
if (mOMX.get() == NULL) {
mOMX = new OMX;
}
return mOMX;
}
status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const
{
const size_t SIZE = 256;
char buffer[SIZE];
String8 result;
result.append(" AudioCache\n");
if (mHeap != 0) {
snprintf(buffer, 255, " heap base(%p), size(%d), flags(%d), device(%s)\n",
mHeap->getBase(), mHeap->getSize(), mHeap->getFlags(), mHeap->getDevice());
result.append(buffer);
}
snprintf(buffer, 255, " msec per frame(%f), channel count(%d), format(%d), frame count(%ld)\n",
mMsecsPerFrame, mChannelCount, mFormat, mFrameCount);
result.append(buffer);
snprintf(buffer, 255, " sample rate(%d), size(%d), error(%d), command complete(%s)\n",
mSampleRate, mSize, mError, mCommandComplete?"true":"false");
result.append(buffer);
::write(fd, result.string(), result.size());
return NO_ERROR;
}
status_t MediaPlayerService::AudioOutput::dump(int fd, const Vector<String16>& args) const
{
const size_t SIZE = 256;
char buffer[SIZE];
String8 result;
result.append(" AudioOutput\n");
snprintf(buffer, 255, " stream type(%d), left - right volume(%f, %f)\n",
mStreamType, mLeftVolume, mRightVolume);
result.append(buffer);
snprintf(buffer, 255, " msec per frame(%f), latency (%d)\n",
mMsecsPerFrame, mLatency);
result.append(buffer);
::write(fd, result.string(), result.size());
if (mTrack != 0) {
mTrack->dump(fd, args);
}
return NO_ERROR;
}
status_t MediaPlayerService::Client::dump(int fd, const Vector<String16>& args) const
{
const size_t SIZE = 256;
char buffer[SIZE];
String8 result;
result.append(" Client\n");
snprintf(buffer, 255, " pid(%d), connId(%d), status(%d), looping(%s)\n",
mPid, mConnId, mStatus, mLoop?"true": "false");
result.append(buffer);
write(fd, result.string(), result.size());
if (mAudioOutput != 0) {
mAudioOutput->dump(fd, args);
}
write(fd, "\n", 1);
return NO_ERROR;
}
static int myTid() {
#ifdef HAVE_GETTID
return gettid();
#else
return getpid();
#endif
}
#if defined(__arm__)
extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize,
size_t* infoSize, size_t* totalMemory, size_t* backtraceSize);
extern "C" void free_malloc_leak_info(uint8_t* info);
// Use the String-class below instead of String8 to allocate all memory
// beforehand and not reenter the heap while we are examining it...
struct MyString8 {
static const size_t MAX_SIZE = 256 * 1024;
MyString8()
: mPtr((char *)malloc(MAX_SIZE)) {
*mPtr = '\0';
}
~MyString8() {
free(mPtr);
}
void append(const char *s) {
strcat(mPtr, s);
}
const char *string() const {
return mPtr;
}
size_t size() const {
return strlen(mPtr);
}
private:
char *mPtr;
MyString8(const MyString8 &);
MyString8 &operator=(const MyString8 &);
};
void memStatus(int fd, const Vector<String16>& args)
{
const size_t SIZE = 256;
char buffer[SIZE];
MyString8 result;
typedef struct {
size_t size;
size_t dups;
intptr_t * backtrace;
} AllocEntry;
uint8_t *info = NULL;
size_t overallSize = 0;
size_t infoSize = 0;
size_t totalMemory = 0;
size_t backtraceSize = 0;
get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory, &backtraceSize);
if (info) {
uint8_t *ptr = info;
size_t count = overallSize / infoSize;
snprintf(buffer, SIZE, " Allocation count %i\n", count);
result.append(buffer);
AllocEntry * entries = new AllocEntry[count];
for (size_t i = 0; i < count; i++) {
// Each entry should be size_t, size_t, intptr_t[backtraceSize]
AllocEntry *e = &entries[i];
e->size = *reinterpret_cast<size_t *>(ptr);
ptr += sizeof(size_t);
e->dups = *reinterpret_cast<size_t *>(ptr);
ptr += sizeof(size_t);
e->backtrace = reinterpret_cast<intptr_t *>(ptr);
ptr += sizeof(intptr_t) * backtraceSize;
}
// Now we need to sort the entries. They come sorted by size but
// not by stack trace which causes problems using diff.
bool moved;
do {
moved = false;
for (size_t i = 0; i < (count - 1); i++) {
AllocEntry *e1 = &entries[i];
AllocEntry *e2 = &entries[i+1];
bool swap = e1->size < e2->size;
if (e1->size == e2->size) {
for(size_t j = 0; j < backtraceSize; j++) {
if (e1->backtrace[j] == e2->backtrace[j]) {
continue;
}
swap = e1->backtrace[j] < e2->backtrace[j];
break;
}
}
if (swap) {
AllocEntry t = entries[i];
entries[i] = entries[i+1];
entries[i+1] = t;
moved = true;
}
}
} while (moved);
for (size_t i = 0; i < count; i++) {
AllocEntry *e = &entries[i];
snprintf(buffer, SIZE, "size %8i, dup %4i", e->size, e->dups);
result.append(buffer);
for (size_t ct = 0; (ct < backtraceSize) && e->backtrace[ct]; ct++) {
if (ct) {
result.append(", ");
}
snprintf(buffer, SIZE, "0x%08x", e->backtrace[ct]);
result.append(buffer);
}
result.append("\n");
}
delete[] entries;
free_malloc_leak_info(info);
}
write(fd, result.string(), result.size());
}
#endif
status_t MediaPlayerService::dump(int fd, const Vector<String16>& args)
{
const size_t SIZE = 256;
char buffer[SIZE];
String8 result;
if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
snprintf(buffer, SIZE, "Permission Denial: "
"can't dump MediaPlayerService from pid=%d, uid=%d\n",
IPCThreadState::self()->getCallingPid(),
IPCThreadState::self()->getCallingUid());
result.append(buffer);
} else {
Mutex::Autolock lock(mLock);
for (int i = 0, n = mClients.size(); i < n; ++i) {
sp<Client> c = mClients[i].promote();
if (c != 0) c->dump(fd, args);
}
for (int i = 0, n = mMediaRecorderClients.size(); i < n; ++i) {
result.append(" MediaRecorderClient\n");
sp<MediaRecorderClient> c = mMediaRecorderClients[i].promote();
snprintf(buffer, 255, " pid(%d)\n\n", c->mPid);
result.append(buffer);
}
result.append(" Files opened and/or mapped:\n");
snprintf(buffer, SIZE, "/proc/%d/maps", myTid());
FILE *f = fopen(buffer, "r");
if (f) {
while (!feof(f)) {
fgets(buffer, SIZE, f);
if (strstr(buffer, " /sdcard/") ||
strstr(buffer, " /system/sounds/") ||
strstr(buffer, " /system/media/")) {
result.append(" ");
result.append(buffer);
}
}
fclose(f);
} else {
result.append("couldn't open ");
result.append(buffer);
result.append("\n");
}
snprintf(buffer, SIZE, "/proc/%d/fd", myTid());
DIR *d = opendir(buffer);
if (d) {
struct dirent *ent;
while((ent = readdir(d)) != NULL) {
if (strcmp(ent->d_name,".") && strcmp(ent->d_name,"..")) {
snprintf(buffer, SIZE, "/proc/%d/fd/%s", myTid(), ent->d_name);
struct stat s;
if (lstat(buffer, &s) == 0) {
if ((s.st_mode & S_IFMT) == S_IFLNK) {
char linkto[256];
int len = readlink(buffer, linkto, sizeof(linkto));
if(len > 0) {
if(len > 255) {
linkto[252] = '.';
linkto[253] = '.';
linkto[254] = '.';
linkto[255] = 0;
} else {
linkto[len] = 0;
}
if (strstr(linkto, "/sdcard/") == linkto ||
strstr(linkto, "/system/sounds/") == linkto ||
strstr(linkto, "/system/media/") == linkto) {
result.append(" ");
result.append(buffer);
result.append(" -> ");
result.append(linkto);
result.append("\n");
}
}
} else {
result.append(" unexpected type for ");
result.append(buffer);
result.append("\n");
}
}
}
}
closedir(d);
} else {
result.append("couldn't open ");
result.append(buffer);
result.append("\n");
}
#if defined(__arm__)
bool dumpMem = false;
for (size_t i = 0; i < args.size(); i++) {
if (args[i] == String16("-m")) {
dumpMem = true;
}
}
if (dumpMem) {
memStatus(fd, args);
}
#endif
}
write(fd, result.string(), result.size());
return NO_ERROR;
}
void MediaPlayerService::removeClient(wp<Client> client)
{
Mutex::Autolock lock(mLock);
mClients.remove(client);
}
MediaPlayerService::Client::Client(const sp<MediaPlayerService>& service, pid_t pid,
int32_t connId, const sp<IMediaPlayerClient>& client)
{
LOGV("Client(%d) constructor", connId);
mPid = pid;
mConnId = connId;
mService = service;
mClient = client;
mLoop = false;
mStatus = NO_INIT;
#if CALLBACK_ANTAGONIZER
LOGD("create Antagonizer");
mAntagonizer = new Antagonizer(notify, this);
#endif
}
MediaPlayerService::Client::~Client()
{
LOGV("Client(%d) destructor pid = %d", mConnId, mPid);
mAudioOutput.clear();
wp<Client> client(this);
disconnect();
mService->removeClient(client);
}
void MediaPlayerService::Client::disconnect()
{
LOGV("disconnect(%d) from pid %d", mConnId, mPid);
// grab local reference and clear main reference to prevent future
// access to object
sp<MediaPlayerBase> p;
{
Mutex::Autolock l(mLock);
p = mPlayer;
}
mClient.clear();
mPlayer.clear();
// clear the notification to prevent callbacks to dead client
// and reset the player. We assume the player will serialize
// access to itself if necessary.
if (p != 0) {
p->setNotifyCallback(0, 0);
#if CALLBACK_ANTAGONIZER
LOGD("kill Antagonizer");
mAntagonizer->kill();
#endif
p->reset();
}
IPCThreadState::self()->flushCommands();
}
static player_type getDefaultPlayerType() {
#if BUILD_WITH_FULL_STAGEFRIGHT
char value[PROPERTY_VALUE_MAX];
if (property_get("media.stagefright.enable-player", value, NULL)
&& (!strcmp(value, "1") || !strcasecmp(value, "true"))) {
return STAGEFRIGHT_PLAYER;
}
#endif
return PV_PLAYER;
}
player_type getPlayerType(int fd, int64_t offset, int64_t length)
{
char buf[20];
lseek(fd, offset, SEEK_SET);
read(fd, buf, sizeof(buf));
lseek(fd, offset, SEEK_SET);
long ident = *((long*)buf);
// Ogg vorbis?
if (ident == 0x5367674f) // 'OggS'
return VORBIS_PLAYER;
// Some kind of MIDI?
EAS_DATA_HANDLE easdata;
if (EAS_Init(&easdata) == EAS_SUCCESS) {
EAS_FILE locator;
locator.path = NULL;
locator.fd = fd;
locator.offset = offset;
locator.length = length;
EAS_HANDLE eashandle;
if (EAS_OpenFile(easdata, &locator, &eashandle) == EAS_SUCCESS) {
EAS_CloseFile(easdata, eashandle);
EAS_Shutdown(easdata);
return SONIVOX_PLAYER;
}
EAS_Shutdown(easdata);
}
return getDefaultPlayerType();
}
player_type getPlayerType(const char* url)
{
if (TestPlayerStub::canBeUsed(url)) {
return TEST_PLAYER;
}
// use MidiFile for MIDI extensions
int lenURL = strlen(url);
for (int i = 0; i < NELEM(FILE_EXTS); ++i) {
int len = strlen(FILE_EXTS[i].extension);
int start = lenURL - len;
if (start > 0) {
if (!strncmp(url + start, FILE_EXTS[i].extension, len)) {
return FILE_EXTS[i].playertype;
}
}
}
if (!strncasecmp(url, "http://", 7)) {
char value[PROPERTY_VALUE_MAX];
if (!property_get("media.stagefright.enable-http", value, NULL)
|| (strcmp(value, "1") && strcasecmp(value, "true"))) {
// For now, we're going to use PV for http-based playback
// by default until we can clear up a few more issues.
return PV_PLAYER;
}
}
return getDefaultPlayerType();
}
static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie,
notify_callback_f notifyFunc)
{
sp<MediaPlayerBase> p;
switch (playerType) {
#ifndef NO_OPENCORE
case PV_PLAYER:
LOGV(" create PVPlayer");
p = new PVPlayer();
break;
#endif
case SONIVOX_PLAYER:
LOGV(" create MidiFile");
p = new MidiFile();
break;
case VORBIS_PLAYER:
LOGV(" create VorbisPlayer");
p = new VorbisPlayer();
break;
#if BUILD_WITH_FULL_STAGEFRIGHT
case STAGEFRIGHT_PLAYER:
LOGV(" create StagefrightPlayer");
p = new StagefrightPlayer;
break;
#endif
case TEST_PLAYER:
LOGV("Create Test Player stub");
p = new TestPlayerStub();
break;
}
if (p != NULL) {
if (p->initCheck() == NO_ERROR) {
p->setNotifyCallback(cookie, notifyFunc);
} else {
p.clear();
}
}
if (p == NULL) {
LOGE("Failed to create player object");
}
return p;
}
sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerType)
{
// determine if we have the right player type
sp<MediaPlayerBase> p = mPlayer;
if ((p != NULL) && (p->playerType() != playerType)) {
LOGV("delete player");
p.clear();
}
if (p == NULL) {
p = android::createPlayer(playerType, this, notify);
}
return p;
}
status_t MediaPlayerService::Client::setDataSource(const char *url)
{
LOGV("setDataSource(%s)", url);
if (url == NULL)
return UNKNOWN_ERROR;
if (strncmp(url, "content://", 10) == 0) {
// get a filedescriptor for the content Uri and
// pass it to the setDataSource(fd) method
String16 url16(url);
int fd = android::openContentProviderFile(url16);
if (fd < 0)
{
LOGE("Couldn't open fd for %s", url);
return UNKNOWN_ERROR;
}
setDataSource(fd, 0, 0x7fffffffffLL); // this sets mStatus
close(fd);
return mStatus;
} else {
player_type playerType = getPlayerType(url);
LOGV("player type = %d", playerType);
// create the right type of player
sp<MediaPlayerBase> p = createPlayer(playerType);
if (p == NULL) return NO_INIT;
if (!p->hardwareOutput()) {
mAudioOutput = new AudioOutput();
static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput);
}
// now set data source
LOGV(" setDataSource");
mStatus = p->setDataSource(url);
if (mStatus == NO_ERROR) {
mPlayer = p;
} else {
LOGE(" error: %d", mStatus);
}
return mStatus;
}
}
status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length)
{
LOGV("setDataSource fd=%d, offset=%lld, length=%lld", fd, offset, length);
struct stat sb;
int ret = fstat(fd, &sb);
if (ret != 0) {
LOGE("fstat(%d) failed: %d, %s", fd, ret, strerror(errno));
return UNKNOWN_ERROR;
}
LOGV("st_dev = %llu", sb.st_dev);
LOGV("st_mode = %u", sb.st_mode);
LOGV("st_uid = %lu", sb.st_uid);
LOGV("st_gid = %lu", sb.st_gid);
LOGV("st_size = %llu", sb.st_size);
if (offset >= sb.st_size) {
LOGE("offset error");
::close(fd);
return UNKNOWN_ERROR;
}
if (offset + length > sb.st_size) {
length = sb.st_size - offset;
LOGV("calculated length = %lld", length);
}
player_type playerType = getPlayerType(fd, offset, length);
LOGV("player type = %d", playerType);
// create the right type of player
sp<MediaPlayerBase> p = createPlayer(playerType);
if (p == NULL) return NO_INIT;
if (!p->hardwareOutput()) {
mAudioOutput = new AudioOutput();
static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput);
}
// now set data source
mStatus = p->setDataSource(fd, offset, length);
if (mStatus == NO_ERROR) mPlayer = p;
return mStatus;
}
status_t MediaPlayerService::Client::setVideoSurface(const sp<ISurface>& surface)
{
LOGV("[%d] setVideoSurface(%p)", mConnId, surface.get());
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
return p->setVideoSurface(surface);
}
status_t MediaPlayerService::Client::invoke(const Parcel& request,
Parcel *reply)
{
sp<MediaPlayerBase> p = getPlayer();
if (p == NULL) return UNKNOWN_ERROR;
return p->invoke(request, reply);
}
// This call doesn't need to access the native player.
status_t MediaPlayerService::Client::setMetadataFilter(const Parcel& filter)
{
status_t status;
media::Metadata::Filter allow, drop;
if (unmarshallFilter(filter, &allow, &status) &&
unmarshallFilter(filter, &drop, &status)) {
Mutex::Autolock lock(mLock);
mMetadataAllow = allow;
mMetadataDrop = drop;
}
return status;
}
status_t MediaPlayerService::Client::getMetadata(
bool update_only, bool apply_filter, Parcel *reply)
{
sp<MediaPlayerBase> player = getPlayer();
if (player == 0) return UNKNOWN_ERROR;
status_t status;
// Placeholder for the return code, updated by the caller.
reply->writeInt32(-1);
media::Metadata::Filter ids;
// We don't block notifications while we fetch the data. We clear
// mMetadataUpdated first so we don't lose notifications happening
// during the rest of this call.
{
Mutex::Autolock lock(mLock);
if (update_only) {
ids = mMetadataUpdated;
}
mMetadataUpdated.clear();
}
media::Metadata metadata(reply);
metadata.appendHeader();
status = player->getMetadata(ids, reply);
if (status != OK) {
metadata.resetParcel();
LOGE("getMetadata failed %d", status);
return status;
}
// FIXME: Implement filtering on the result. Not critical since
// filtering takes place on the update notifications already. This
// would be when all the metadata are fetch and a filter is set.
// Everything is fine, update the metadata length.
metadata.updateLength();
return OK;
}
status_t MediaPlayerService::Client::prepareAsync()
{
LOGV("[%d] prepareAsync", mConnId);
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
status_t ret = p->prepareAsync();
#if CALLBACK_ANTAGONIZER
LOGD("start Antagonizer");
if (ret == NO_ERROR) mAntagonizer->start();
#endif
return ret;
}
status_t MediaPlayerService::Client::start()
{
LOGV("[%d] start", mConnId);
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
p->setLooping(mLoop);
return p->start();
}
status_t MediaPlayerService::Client::stop()
{
LOGV("[%d] stop", mConnId);
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
return p->stop();
}
status_t MediaPlayerService::Client::pause()
{
LOGV("[%d] pause", mConnId);
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
return p->pause();
}
status_t MediaPlayerService::Client::isPlaying(bool* state)
{
*state = false;
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
*state = p->isPlaying();
LOGV("[%d] isPlaying: %d", mConnId, *state);
return NO_ERROR;
}
status_t MediaPlayerService::Client::getCurrentPosition(int *msec)
{
LOGV("getCurrentPosition");
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
status_t ret = p->getCurrentPosition(msec);
if (ret == NO_ERROR) {
LOGV("[%d] getCurrentPosition = %d", mConnId, *msec);
} else {
LOGE("getCurrentPosition returned %d", ret);
}
return ret;
}
status_t MediaPlayerService::Client::getDuration(int *msec)
{
LOGV("getDuration");
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
status_t ret = p->getDuration(msec);
if (ret == NO_ERROR) {
LOGV("[%d] getDuration = %d", mConnId, *msec);
} else {
LOGE("getDuration returned %d", ret);
}
return ret;
}
status_t MediaPlayerService::Client::seekTo(int msec)
{
LOGV("[%d] seekTo(%d)", mConnId, msec);
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
return p->seekTo(msec);
}
status_t MediaPlayerService::Client::reset()
{
LOGV("[%d] reset", mConnId);
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
return p->reset();
}
status_t MediaPlayerService::Client::setAudioStreamType(int type)
{
LOGV("[%d] setAudioStreamType(%d)", mConnId, type);
// TODO: for hardware output, call player instead
Mutex::Autolock l(mLock);
if (mAudioOutput != 0) mAudioOutput->setAudioStreamType(type);
return NO_ERROR;
}
status_t MediaPlayerService::Client::setLooping(int loop)
{
LOGV("[%d] setLooping(%d)", mConnId, loop);
mLoop = loop;
sp<MediaPlayerBase> p = getPlayer();
if (p != 0) return p->setLooping(loop);
return NO_ERROR;
}
status_t MediaPlayerService::Client::setVolume(float leftVolume, float rightVolume)
{
LOGV("[%d] setVolume(%f, %f)", mConnId, leftVolume, rightVolume);
// TODO: for hardware output, call player instead
Mutex::Autolock l(mLock);
if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume);
return NO_ERROR;
}
void MediaPlayerService::Client::notify(void* cookie, int msg, int ext1, int ext2)
{
Client* client = static_cast<Client*>(cookie);
if (MEDIA_INFO == msg &&
MEDIA_INFO_METADATA_UPDATE == ext1) {
const media::Metadata::Type metadata_type = ext2;
if(client->shouldDropMetadata(metadata_type)) {
return;
}
// Update the list of metadata that have changed. getMetadata
// also access mMetadataUpdated and clears it.
client->addNewMetadataUpdate(metadata_type);
}
LOGV("[%d] notify (%p, %d, %d, %d)", client->mConnId, cookie, msg, ext1, ext2);
client->mClient->notify(msg, ext1, ext2);
}
bool MediaPlayerService::Client::shouldDropMetadata(media::Metadata::Type code) const
{
Mutex::Autolock lock(mLock);
if (findMetadata(mMetadataDrop, code)) {
return true;
}
if (mMetadataAllow.isEmpty() || findMetadata(mMetadataAllow, code)) {
return false;
} else {
return true;
}
}
void MediaPlayerService::Client::addNewMetadataUpdate(media::Metadata::Type metadata_type) {
Mutex::Autolock lock(mLock);
if (mMetadataUpdated.indexOf(metadata_type) < 0) {
mMetadataUpdated.add(metadata_type);
}
}
#if CALLBACK_ANTAGONIZER
const int Antagonizer::interval = 10000; // 10 msecs
Antagonizer::Antagonizer(notify_callback_f cb, void* client) :
mExit(false), mActive(false), mClient(client), mCb(cb)
{
createThread(callbackThread, this);
}
void Antagonizer::kill()
{
Mutex::Autolock _l(mLock);
mActive = false;
mExit = true;
mCondition.wait(mLock);
}
int Antagonizer::callbackThread(void* user)
{
LOGD("Antagonizer started");
Antagonizer* p = reinterpret_cast<Antagonizer*>(user);
while (!p->mExit) {
if (p->mActive) {
LOGV("send event");
p->mCb(p->mClient, 0, 0, 0);
}
usleep(interval);
}
Mutex::Autolock _l(p->mLock);
p->mCondition.signal();
LOGD("Antagonizer stopped");
return 0;
}
#endif
static size_t kDefaultHeapSize = 1024 * 1024; // 1MB
sp<IMemory> MediaPlayerService::decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat)
{
LOGV("decode(%s)", url);
sp<MemoryBase> mem;
sp<MediaPlayerBase> player;
// Protect our precious, precious DRMd ringtones by only allowing
// decoding of http, but not filesystem paths or content Uris.
// If the application wants to decode those, it should open a
// filedescriptor for them and use that.
if (url != NULL && strncmp(url, "http://", 7) != 0) {
LOGD("Can't decode %s by path, use filedescriptor instead", url);
return mem;
}
player_type playerType = getPlayerType(url);
LOGV("player type = %d", playerType);
// create the right type of player
sp<AudioCache> cache = new AudioCache(url);
player = android::createPlayer(playerType, cache.get(), cache->notify);
if (player == NULL) goto Exit;
if (player->hardwareOutput()) goto Exit;
static_cast<MediaPlayerInterface*>(player.get())->setAudioSink(cache);
// set data source
if (player->setDataSource(url) != NO_ERROR) goto Exit;
LOGV("prepare");
player->prepareAsync();
LOGV("wait for prepare");
if (cache->wait() != NO_ERROR) goto Exit;
LOGV("start");
player->start();
LOGV("wait for playback complete");
if (cache->wait() != NO_ERROR) goto Exit;
mem = new MemoryBase(cache->getHeap(), 0, cache->size());
*pSampleRate = cache->sampleRate();
*pNumChannels = cache->channelCount();
*pFormat = cache->format();
LOGV("return memory @ %p, sampleRate=%u, channelCount = %d, format = %d", mem->pointer(), *pSampleRate, *pNumChannels, *pFormat);
Exit:
if (player != 0) player->reset();
return mem;
}
sp<IMemory> MediaPlayerService::decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat)
{
LOGV("decode(%d, %lld, %lld)", fd, offset, length);
sp<MemoryBase> mem;
sp<MediaPlayerBase> player;
player_type playerType = getPlayerType(fd, offset, length);
LOGV("player type = %d", playerType);
// create the right type of player
sp<AudioCache> cache = new AudioCache("decode_fd");
player = android::createPlayer(playerType, cache.get(), cache->notify);
if (player == NULL) goto Exit;
if (player->hardwareOutput()) goto Exit;
static_cast<MediaPlayerInterface*>(player.get())->setAudioSink(cache);
// set data source
if (player->setDataSource(fd, offset, length) != NO_ERROR) goto Exit;
LOGV("prepare");
player->prepareAsync();
LOGV("wait for prepare");
if (cache->wait() != NO_ERROR) goto Exit;
LOGV("start");
player->start();
LOGV("wait for playback complete");
if (cache->wait() != NO_ERROR) goto Exit;
mem = new MemoryBase(cache->getHeap(), 0, cache->size());
*pSampleRate = cache->sampleRate();
*pNumChannels = cache->channelCount();
*pFormat = cache->format();
LOGV("return memory @ %p, sampleRate=%u, channelCount = %d, format = %d", mem->pointer(), *pSampleRate, *pNumChannels, *pFormat);
Exit:
if (player != 0) player->reset();
::close(fd);
return mem;
}
/*
* Avert your eyes, ugly hack ahead.
* The following is to support music visualizations.
*/
static const int NUMVIZBUF = 32;
static const int VIZBUFFRAMES = 1024;
static const int BUFTIMEMSEC = NUMVIZBUF * VIZBUFFRAMES * 1000 / 44100;
static const int TOTALBUFTIMEMSEC = NUMVIZBUF * BUFTIMEMSEC;
static bool gotMem = false;
static sp<MemoryHeapBase> heap;
static sp<MemoryBase> mem[NUMVIZBUF];
static uint64_t endTime;
static uint64_t lastReadTime;
static uint64_t lastWriteTime;
static int writeIdx = 0;
static void allocVizBufs() {
if (!gotMem) {
heap = new MemoryHeapBase(NUMVIZBUF * VIZBUFFRAMES * 2, 0, "snooper");
for (int i=0;i<NUMVIZBUF;i++) {
mem[i] = new MemoryBase(heap, VIZBUFFRAMES * 2 * i, VIZBUFFRAMES * 2);
}
endTime = 0;
gotMem = true;
}
}
/*
* Get a buffer of audio data that is about to be played.
* We don't synchronize this because in practice the writer
* is ahead of the reader, and even if we did happen to catch
* a buffer while it's being written, it's just a visualization,
* so no harm done.
*/
static sp<MemoryBase> getVizBuffer() {
allocVizBufs();
lastReadTime = uptimeMillis();
// if there is no recent buffer (yet), just return empty handed
if (lastWriteTime + TOTALBUFTIMEMSEC < lastReadTime) {
//LOGI("@@@@ no audio data to look at yet: %d + %d < %d", (int)lastWriteTime, TOTALBUFTIMEMSEC, (int)lastReadTime);
return NULL;
}
int timedelta = endTime - lastReadTime;
if (timedelta < 0) timedelta = 0;
int framedelta = timedelta * 44100 / 1000;
int headIdx = (writeIdx - framedelta) / VIZBUFFRAMES - 1;
while (headIdx < 0) {
headIdx += NUMVIZBUF;
}
return mem[headIdx];
}
// Append the data to the vizualization buffer
static void makeVizBuffers(const char *data, int len, uint64_t time) {
allocVizBufs();
uint64_t startTime = time;
const int frameSize = 4; // 16 bit stereo sample is 4 bytes
int offset = writeIdx;
int maxoff = heap->getSize() / 2; // in shorts
short *base = (short*)heap->getBase();
short *src = (short*)data;
while (len > 0) {
// Degrade quality by mixing to mono and clearing the lowest 3 bits.
// This should still be good enough for a visualization
base[offset++] = ((int(src[0]) + int(src[1])) >> 1) & ~0x7;
src += 2;
len -= frameSize;
if (offset >= maxoff) {
offset = 0;
}
}
writeIdx = offset;
endTime = time + (len / frameSize) / 44;
//LOGI("@@@ stored buffers from %d to %d", uint32_t(startTime), uint32_t(time));
}
sp<IMemory> MediaPlayerService::snoop()
{
sp<MemoryBase> mem = getVizBuffer();
return mem;
}
#undef LOG_TAG
#define LOG_TAG "AudioSink"
MediaPlayerService::AudioOutput::AudioOutput()
: mCallback(NULL),
mCallbackCookie(NULL) {
mTrack = 0;
mStreamType = AudioSystem::MUSIC;
mLeftVolume = 1.0;
mRightVolume = 1.0;
mLatency = 0;
mMsecsPerFrame = 0;
mNumFramesWritten = 0;
setMinBufferCount();
}
MediaPlayerService::AudioOutput::~AudioOutput()
{
close();
}
void MediaPlayerService::AudioOutput::setMinBufferCount()
{
char value[PROPERTY_VALUE_MAX];
if (property_get("ro.kernel.qemu", value, 0)) {
mIsOnEmulator = true;
mMinBufferCount = 12; // to prevent systematic buffer underrun for emulator
}
}
bool MediaPlayerService::AudioOutput::isOnEmulator()
{
setMinBufferCount();
return mIsOnEmulator;
}
int MediaPlayerService::AudioOutput::getMinBufferCount()
{
setMinBufferCount();
return mMinBufferCount;
}
ssize_t MediaPlayerService::AudioOutput::bufferSize() const
{
if (mTrack == 0) return NO_INIT;
return mTrack->frameCount() * frameSize();
}
ssize_t MediaPlayerService::AudioOutput::frameCount() const
{
if (mTrack == 0) return NO_INIT;
return mTrack->frameCount();
}
ssize_t MediaPlayerService::AudioOutput::channelCount() const
{
if (mTrack == 0) return NO_INIT;
return mTrack->channelCount();
}
ssize_t MediaPlayerService::AudioOutput::frameSize() const
{
if (mTrack == 0) return NO_INIT;
return mTrack->frameSize();
}
uint32_t MediaPlayerService::AudioOutput::latency () const
{
return mLatency;
}
float MediaPlayerService::AudioOutput::msecsPerFrame() const
{
return mMsecsPerFrame;
}
status_t MediaPlayerService::AudioOutput::open(
uint32_t sampleRate, int channelCount, int format, int bufferCount,
AudioCallback cb, void *cookie)
{
mCallback = cb;
mCallbackCookie = cookie;
// Check argument "bufferCount" against the mininum buffer count
if (bufferCount < mMinBufferCount) {
LOGD("bufferCount (%d) is too small and increased to %d", bufferCount, mMinBufferCount);
bufferCount = mMinBufferCount;
}
LOGV("open(%u, %d, %d, %d)", sampleRate, channelCount, format, bufferCount);
if (mTrack) close();
int afSampleRate;
int afFrameCount;
int frameCount;
if (AudioSystem::getOutputFrameCount(&afFrameCount, mStreamType) != NO_ERROR) {
return NO_INIT;
}
if (AudioSystem::getOutputSamplingRate(&afSampleRate, mStreamType) != NO_ERROR) {
return NO_INIT;
}
frameCount = (sampleRate*afFrameCount*bufferCount)/afSampleRate;
AudioTrack *t;
if (mCallback != NULL) {
t = new AudioTrack(
mStreamType,
sampleRate,
format,
(channelCount == 2) ? AudioSystem::CHANNEL_OUT_STEREO : AudioSystem::CHANNEL_OUT_MONO,
frameCount,
0 /* flags */,
CallbackWrapper,
this);
} else {
t = new AudioTrack(
mStreamType,
sampleRate,
format,
(channelCount == 2) ? AudioSystem::CHANNEL_OUT_STEREO : AudioSystem::CHANNEL_OUT_MONO,
frameCount);
}
if ((t == 0) || (t->initCheck() != NO_ERROR)) {
LOGE("Unable to create audio track");
delete t;
return NO_INIT;
}
LOGV("setVolume");
t->setVolume(mLeftVolume, mRightVolume);
mMsecsPerFrame = 1.e3 / (float) sampleRate;
mLatency = t->latency();
mTrack = t;
return NO_ERROR;
}
void MediaPlayerService::AudioOutput::start()
{
LOGV("start");
if (mTrack) {
mTrack->setVolume(mLeftVolume, mRightVolume);
mTrack->start();
mTrack->getPosition(&mNumFramesWritten);
}
}
void MediaPlayerService::AudioOutput::snoopWrite(const void* buffer, size_t size) {
// Only make visualization buffers if anyone recently requested visualization data
uint64_t now = uptimeMillis();
if (lastReadTime + TOTALBUFTIMEMSEC >= now) {
// Based on the current play counter, the number of frames written and
// the current real time we can calculate the approximate real start
// time of the buffer we're about to write.
uint32_t pos;
mTrack->getPosition(&pos);
// we're writing ahead by this many frames:
int ahead = mNumFramesWritten - pos;
//LOGI("@@@ written: %d, playpos: %d, latency: %d", mNumFramesWritten, pos, mTrack->latency());
// which is this many milliseconds, assuming 44100 Hz:
ahead /= 44;
makeVizBuffers((const char*)buffer, size, now + ahead + mTrack->latency());
lastWriteTime = now;
}
}
ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size)
{
LOG_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback.");
//LOGV("write(%p, %u)", buffer, size);
if (mTrack) {
snoopWrite(buffer, size);
ssize_t ret = mTrack->write(buffer, size);
mNumFramesWritten += ret / 4; // assume 16 bit stereo
return ret;
}
return NO_INIT;
}
void MediaPlayerService::AudioOutput::stop()
{
LOGV("stop");
if (mTrack) mTrack->stop();
lastWriteTime = 0;
}
void MediaPlayerService::AudioOutput::flush()
{
LOGV("flush");
if (mTrack) mTrack->flush();
}
void MediaPlayerService::AudioOutput::pause()
{
LOGV("pause");
if (mTrack) mTrack->pause();
lastWriteTime = 0;
}
void MediaPlayerService::AudioOutput::close()
{
LOGV("close");
delete mTrack;
mTrack = 0;
}
void MediaPlayerService::AudioOutput::setVolume(float left, float right)
{
LOGV("setVolume(%f, %f)", left, right);
mLeftVolume = left;
mRightVolume = right;
if (mTrack) {
mTrack->setVolume(left, right);
}
}
// static
void MediaPlayerService::AudioOutput::CallbackWrapper(
int event, void *cookie, void *info) {
//LOGV("callbackwrapper");
if (event != AudioTrack::EVENT_MORE_DATA) {
return;
}
AudioOutput *me = (AudioOutput *)cookie;
AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info;
(*me->mCallback)(
me, buffer->raw, buffer->size, me->mCallbackCookie);
me->snoopWrite(buffer->raw, buffer->size);
}
#undef LOG_TAG
#define LOG_TAG "AudioCache"
MediaPlayerService::AudioCache::AudioCache(const char* name) :
mChannelCount(0), mFrameCount(1024), mSampleRate(0), mSize(0),
mError(NO_ERROR), mCommandComplete(false)
{
// create ashmem heap
mHeap = new MemoryHeapBase(kDefaultHeapSize, 0, name);
}
uint32_t MediaPlayerService::AudioCache::latency () const
{
return 0;
}
float MediaPlayerService::AudioCache::msecsPerFrame() const
{
return mMsecsPerFrame;
}
status_t MediaPlayerService::AudioCache::open(
uint32_t sampleRate, int channelCount, int format, int bufferCount,
AudioCallback cb, void *cookie)
{
LOGV("open(%u, %d, %d, %d)", sampleRate, channelCount, format, bufferCount);
if (cb != NULL) {
return UNKNOWN_ERROR; // TODO: implement this.
}
if (mHeap->getHeapID() < 0) {
return NO_INIT;
}
mSampleRate = sampleRate;
mChannelCount = (uint16_t)channelCount;
mFormat = (uint16_t)format;
mMsecsPerFrame = 1.e3 / (float) sampleRate;
return NO_ERROR;
}
ssize_t MediaPlayerService::AudioCache::write(const void* buffer, size_t size)
{
LOGV("write(%p, %u)", buffer, size);
if ((buffer == 0) || (size == 0)) return size;
uint8_t* p = static_cast<uint8_t*>(mHeap->getBase());
if (p == NULL) return NO_INIT;
p += mSize;
LOGV("memcpy(%p, %p, %u)", p, buffer, size);
if (mSize + size > mHeap->getSize()) {
LOGE("Heap size overflow! req size: %d, max size: %d", (mSize + size), mHeap->getSize());
size = mHeap->getSize() - mSize;
}
memcpy(p, buffer, size);
mSize += size;
return size;
}
// call with lock held
status_t MediaPlayerService::AudioCache::wait()
{
Mutex::Autolock lock(mLock);
if (!mCommandComplete) {
mSignal.wait(mLock);
}
mCommandComplete = false;
if (mError == NO_ERROR) {
LOGV("wait - success");
} else {
LOGV("wait - error");
}
return mError;
}
void MediaPlayerService::AudioCache::notify(void* cookie, int msg, int ext1, int ext2)
{
LOGV("notify(%p, %d, %d, %d)", cookie, msg, ext1, ext2);
AudioCache* p = static_cast<AudioCache*>(cookie);
// ignore buffering messages
switch (msg)
{
case MEDIA_ERROR:
LOGE("Error %d, %d occurred", ext1, ext2);
p->mError = ext1;
break;
case MEDIA_PREPARED:
LOGV("prepared");
break;
case MEDIA_PLAYBACK_COMPLETE:
LOGV("playback complete");
break;
default:
LOGV("ignored");
return;
}
// wake up thread
p->mCommandComplete = true;
p->mSignal.signal();
}
} // namespace android