You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
459 lines
17 KiB
459 lines
17 KiB
/*
|
|
* Copyright (C) 2009 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.
|
|
*/
|
|
|
|
//#define LOG_NDEBUG 0
|
|
#define LOG_TAG "BpMediaSource"
|
|
#include <utils/Log.h>
|
|
|
|
#include <inttypes.h>
|
|
#include <stdint.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <binder/Parcel.h>
|
|
#include <media/IMediaSource.h>
|
|
#include <media/stagefright/MediaBuffer.h>
|
|
#include <media/stagefright/MediaBufferGroup.h>
|
|
#include <media/stagefright/MediaSource.h>
|
|
#include <media/stagefright/MetaData.h>
|
|
|
|
namespace android {
|
|
|
|
enum {
|
|
START = IBinder::FIRST_CALL_TRANSACTION,
|
|
STOP,
|
|
PAUSE,
|
|
GETFORMAT,
|
|
// READ, deprecated
|
|
READMULTIPLE,
|
|
RELEASE_BUFFER,
|
|
SUPPORT_NONBLOCKING_READ,
|
|
};
|
|
|
|
enum {
|
|
NULL_BUFFER,
|
|
SHARED_BUFFER,
|
|
INLINE_BUFFER,
|
|
SHARED_BUFFER_INDEX,
|
|
};
|
|
|
|
class RemoteMediaBufferWrapper : public MediaBuffer {
|
|
public:
|
|
RemoteMediaBufferWrapper(const sp<IMemory> &mem)
|
|
: MediaBuffer(mem) {
|
|
ALOGV("RemoteMediaBufferWrapper: creating %p", this);
|
|
}
|
|
|
|
protected:
|
|
virtual ~RemoteMediaBufferWrapper() {
|
|
// Release our interest in the MediaBuffer's shared memory.
|
|
int32_t old = addRemoteRefcount(-1);
|
|
ALOGV("RemoteMediaBufferWrapper: releasing %p, refcount %d", this, old - 1);
|
|
mMemory.clear(); // don't set the dead object flag.
|
|
}
|
|
};
|
|
|
|
class BpMediaSource : public BpInterface<IMediaSource> {
|
|
public:
|
|
explicit BpMediaSource(const sp<IBinder>& impl)
|
|
: BpInterface<IMediaSource>(impl), mBuffersSinceStop(0)
|
|
{
|
|
}
|
|
|
|
virtual status_t start(MetaData *params) {
|
|
ALOGV("start");
|
|
Parcel data, reply;
|
|
data.writeInterfaceToken(BpMediaSource::getInterfaceDescriptor());
|
|
if (params) {
|
|
params->writeToParcel(data);
|
|
}
|
|
status_t ret = remote()->transact(START, data, &reply);
|
|
if (ret == NO_ERROR && params) {
|
|
ALOGW("ignoring potentially modified MetaData from start");
|
|
ALOGW("input:");
|
|
params->dumpToLog();
|
|
sp<MetaData> meta = MetaData::createFromParcel(reply);
|
|
ALOGW("output:");
|
|
meta->dumpToLog();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
virtual status_t stop() {
|
|
ALOGV("stop");
|
|
Parcel data, reply;
|
|
data.writeInterfaceToken(BpMediaSource::getInterfaceDescriptor());
|
|
status_t status = remote()->transact(STOP, data, &reply);
|
|
mMemoryCache.reset();
|
|
mBuffersSinceStop = 0;
|
|
return status;
|
|
}
|
|
|
|
virtual sp<MetaData> getFormat() {
|
|
ALOGV("getFormat");
|
|
Parcel data, reply;
|
|
data.writeInterfaceToken(BpMediaSource::getInterfaceDescriptor());
|
|
status_t ret = remote()->transact(GETFORMAT, data, &reply);
|
|
if (ret == NO_ERROR) {
|
|
AutoMutex _l(mBpLock);
|
|
mMetaData = MetaData::createFromParcel(reply);
|
|
return mMetaData;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
virtual status_t read(MediaBufferBase **buffer,
|
|
const MediaSource::ReadOptions *options) {
|
|
Vector<MediaBufferBase *> buffers;
|
|
status_t ret = readMultiple(&buffers, 1 /* maxNumBuffers */, options);
|
|
*buffer = buffers.size() == 0 ? nullptr : buffers[0];
|
|
ALOGV("read status %d, bufferCount %u, sinceStop %u",
|
|
ret, *buffer != nullptr, mBuffersSinceStop);
|
|
return ret;
|
|
}
|
|
|
|
virtual status_t readMultiple(
|
|
Vector<MediaBufferBase *> *buffers, uint32_t maxNumBuffers,
|
|
const MediaSource::ReadOptions *options) {
|
|
ALOGV("readMultiple");
|
|
if (buffers == NULL || !buffers->isEmpty()) {
|
|
return BAD_VALUE;
|
|
}
|
|
Parcel data, reply;
|
|
data.writeInterfaceToken(BpMediaSource::getInterfaceDescriptor());
|
|
data.writeUint32(maxNumBuffers);
|
|
if (options != nullptr) {
|
|
data.writeByteArray(sizeof(*options), (uint8_t*) options);
|
|
}
|
|
status_t ret = remote()->transact(READMULTIPLE, data, &reply);
|
|
mMemoryCache.gc();
|
|
if (ret != NO_ERROR) {
|
|
return ret;
|
|
}
|
|
// wrap the returned data in a vector of MediaBuffers
|
|
int32_t buftype;
|
|
uint32_t bufferCount = 0;
|
|
while ((buftype = reply.readInt32()) != NULL_BUFFER) {
|
|
LOG_ALWAYS_FATAL_IF(bufferCount >= maxNumBuffers,
|
|
"Received %u+ buffers and requested %u buffers",
|
|
bufferCount + 1, maxNumBuffers);
|
|
MediaBuffer *buf;
|
|
if (buftype == SHARED_BUFFER || buftype == SHARED_BUFFER_INDEX) {
|
|
uint64_t index = reply.readUint64();
|
|
ALOGV("Received %s index %llu",
|
|
buftype == SHARED_BUFFER ? "SHARED_BUFFER" : "SHARED_BUFFER_INDEX",
|
|
(unsigned long long) index);
|
|
sp<IMemory> mem;
|
|
if (buftype == SHARED_BUFFER) {
|
|
sp<IBinder> binder = reply.readStrongBinder();
|
|
mem = interface_cast<IMemory>(binder);
|
|
LOG_ALWAYS_FATAL_IF(mem.get() == nullptr,
|
|
"Received NULL IMemory for shared buffer");
|
|
mMemoryCache.insert(index, mem);
|
|
} else {
|
|
mem = mMemoryCache.lookup(index);
|
|
LOG_ALWAYS_FATAL_IF(mem.get() == nullptr,
|
|
"Received invalid IMemory index for shared buffer: %llu",
|
|
(unsigned long long)index);
|
|
}
|
|
size_t offset = reply.readInt32();
|
|
size_t length = reply.readInt32();
|
|
buf = new RemoteMediaBufferWrapper(mem);
|
|
buf->set_range(offset, length);
|
|
buf->meta_data().updateFromParcel(reply);
|
|
} else { // INLINE_BUFFER
|
|
int32_t len = reply.readInt32();
|
|
ALOGV("INLINE_BUFFER status %d and len %d", ret, len);
|
|
buf = new MediaBuffer(len);
|
|
reply.read(buf->data(), len);
|
|
buf->meta_data().updateFromParcel(reply);
|
|
}
|
|
buffers->push_back(buf);
|
|
++bufferCount;
|
|
++mBuffersSinceStop;
|
|
}
|
|
ret = reply.readInt32();
|
|
ALOGV("readMultiple status %d, bufferCount %u, sinceStop %u",
|
|
ret, bufferCount, mBuffersSinceStop);
|
|
if (bufferCount && ret == WOULD_BLOCK) {
|
|
ret = OK;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Binder proxy adds readMultiple support.
|
|
virtual bool supportReadMultiple() {
|
|
return true;
|
|
}
|
|
|
|
virtual bool supportNonblockingRead() {
|
|
ALOGV("supportNonblockingRead");
|
|
Parcel data, reply;
|
|
data.writeInterfaceToken(BpMediaSource::getInterfaceDescriptor());
|
|
status_t ret = remote()->transact(SUPPORT_NONBLOCKING_READ, data, &reply);
|
|
if (ret == NO_ERROR) {
|
|
return reply.readInt32() != 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
virtual status_t pause() {
|
|
ALOGV("pause");
|
|
Parcel data, reply;
|
|
data.writeInterfaceToken(BpMediaSource::getInterfaceDescriptor());
|
|
return remote()->transact(PAUSE, data, &reply);
|
|
}
|
|
|
|
private:
|
|
|
|
uint32_t mBuffersSinceStop; // Buffer tracking variable
|
|
|
|
// NuPlayer passes pointers-to-metadata around, so we use this to keep the metadata alive
|
|
// XXX: could we use this for caching, or does metadata change on the fly?
|
|
sp<MetaData> mMetaData;
|
|
// ensure synchronize access to mMetaData
|
|
Mutex mBpLock;
|
|
|
|
// Cache all IMemory objects received from MediaExtractor.
|
|
// We gc IMemory objects that are no longer active (referenced by a MediaBuffer).
|
|
|
|
struct MemoryCache {
|
|
sp<IMemory> lookup(uint64_t index) {
|
|
auto p = mIndexToMemory.find(index);
|
|
if (p == mIndexToMemory.end()) {
|
|
ALOGE("cannot find index!");
|
|
return nullptr;
|
|
}
|
|
return p->second;
|
|
}
|
|
|
|
void insert(uint64_t index, const sp<IMemory> &mem) {
|
|
if (mIndexToMemory.find(index) != mIndexToMemory.end()) {
|
|
ALOGE("index %llu already present", (unsigned long long)index);
|
|
return;
|
|
}
|
|
(void)mIndexToMemory.emplace(index, mem);
|
|
}
|
|
|
|
void reset() {
|
|
mIndexToMemory.clear();
|
|
}
|
|
|
|
void gc() {
|
|
for (auto it = mIndexToMemory.begin(); it != mIndexToMemory.end(); ) {
|
|
if (MediaBuffer::isDeadObject(it->second)) {
|
|
it = mIndexToMemory.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
private:
|
|
// C++14 unordered_map erase on iterator is stable; C++11 has no guarantee.
|
|
std::map<uint64_t, sp<IMemory>> mIndexToMemory;
|
|
} mMemoryCache;
|
|
};
|
|
|
|
IMPLEMENT_META_INTERFACE(MediaSource, "android.media.IMediaSource");
|
|
|
|
#undef LOG_TAG
|
|
#define LOG_TAG "BnMediaSource"
|
|
|
|
BnMediaSource::BnMediaSource()
|
|
: mBuffersSinceStop(0)
|
|
, mGroup(new MediaBufferGroup(kBinderMediaBuffers /* growthLimit */)) {
|
|
}
|
|
|
|
BnMediaSource::~BnMediaSource() {
|
|
}
|
|
|
|
status_t BnMediaSource::onTransact(
|
|
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
|
|
{
|
|
switch (code) {
|
|
case START: {
|
|
ALOGV("start");
|
|
CHECK_INTERFACE(IMediaSource, data, reply);
|
|
sp<MetaData> meta;
|
|
if (data.dataAvail()) {
|
|
meta = MetaData::createFromParcel(data);
|
|
}
|
|
status_t ret = start(meta.get());
|
|
if (ret == NO_ERROR && meta != NULL) {
|
|
meta->writeToParcel(*reply);
|
|
}
|
|
return ret;
|
|
}
|
|
case STOP: {
|
|
ALOGV("stop");
|
|
CHECK_INTERFACE(IMediaSource, data, reply);
|
|
mGroup->signalBufferReturned(nullptr);
|
|
status_t status = stop();
|
|
AutoMutex _l(mBnLock);
|
|
mIndexCache.reset();
|
|
mBuffersSinceStop = 0;
|
|
return status;
|
|
}
|
|
case PAUSE: {
|
|
ALOGV("pause");
|
|
CHECK_INTERFACE(IMediaSource, data, reply);
|
|
mGroup->signalBufferReturned(nullptr);
|
|
return pause();
|
|
}
|
|
case GETFORMAT: {
|
|
ALOGV("getFormat");
|
|
CHECK_INTERFACE(IMediaSource, data, reply);
|
|
sp<MetaData> meta = getFormat();
|
|
if (meta != NULL) {
|
|
meta->writeToParcel(*reply);
|
|
return NO_ERROR;
|
|
}
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
case READMULTIPLE: {
|
|
ALOGV("readMultiple");
|
|
CHECK_INTERFACE(IMediaSource, data, reply);
|
|
|
|
// Get max number of buffers to read.
|
|
uint32_t maxNumBuffers;
|
|
data.readUint32(&maxNumBuffers);
|
|
if (maxNumBuffers > kMaxNumReadMultiple) {
|
|
maxNumBuffers = kMaxNumReadMultiple;
|
|
}
|
|
|
|
// Get read options, if any.
|
|
MediaSource::ReadOptions opts;
|
|
uint32_t len;
|
|
const bool useOptions =
|
|
data.readUint32(&len) == NO_ERROR
|
|
&& len == sizeof(opts)
|
|
&& data.read((void *)&opts, len) == NO_ERROR;
|
|
|
|
AutoMutex _l(mBnLock);
|
|
mGroup->signalBufferReturned(nullptr);
|
|
mIndexCache.gc();
|
|
size_t inlineTransferSize = 0;
|
|
status_t ret = NO_ERROR;
|
|
uint32_t bufferCount = 0;
|
|
for (; bufferCount < maxNumBuffers; ++bufferCount, ++mBuffersSinceStop) {
|
|
MediaBuffer *buf = nullptr;
|
|
ret = read((MediaBufferBase **)&buf, useOptions ? &opts : nullptr);
|
|
opts.clearNonPersistent(); // Remove options that only apply to first buffer.
|
|
if (ret != NO_ERROR || buf == nullptr) {
|
|
break;
|
|
}
|
|
|
|
// Even if we're using shared memory, we might not want to use it, since for small
|
|
// sizes it's faster to copy data through the Binder transaction
|
|
// On the other hand, if the data size is large enough, it's better to use shared
|
|
// memory. When data is too large, binder can't handle it.
|
|
//
|
|
// TODO: reduce MediaBuffer::kSharedMemThreshold
|
|
MediaBuffer *transferBuf = nullptr;
|
|
const size_t length = buf->range_length();
|
|
size_t offset = buf->range_offset();
|
|
if (length >= (supportNonblockingRead() && buf->mMemory != nullptr ?
|
|
kTransferSharedAsSharedThreshold : kTransferInlineAsSharedThreshold)) {
|
|
if (buf->mMemory != nullptr) {
|
|
ALOGV("Use shared memory: %zu", length);
|
|
transferBuf = buf;
|
|
} else {
|
|
ALOGV("Large buffer %zu without IMemory!", length);
|
|
ret = mGroup->acquire_buffer(
|
|
(MediaBufferBase **)&transferBuf, false /* nonBlocking */, length);
|
|
if (ret != OK
|
|
|| transferBuf == nullptr
|
|
|| transferBuf->mMemory == nullptr) {
|
|
ALOGV("Failed to acquire shared memory, size %zu, ret %d",
|
|
length, ret);
|
|
if (transferBuf != nullptr) {
|
|
transferBuf->release();
|
|
transferBuf = nullptr;
|
|
}
|
|
// Current buffer transmit inline; no more additional buffers.
|
|
maxNumBuffers = 0;
|
|
} else {
|
|
memcpy(transferBuf->data(), (uint8_t*)buf->data() + offset, length);
|
|
offset = 0;
|
|
if (!mGroup->has_buffers()) {
|
|
maxNumBuffers = 0; // No more MediaBuffers, stop readMultiple.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (transferBuf != nullptr) { // Using shared buffers.
|
|
if (!transferBuf->isObserved() && transferBuf != buf) {
|
|
// Transfer buffer must be part of a MediaBufferGroup.
|
|
ALOGV("adding shared memory buffer %p to local group", transferBuf);
|
|
mGroup->add_buffer(transferBuf);
|
|
transferBuf->add_ref(); // We have already acquired buffer.
|
|
}
|
|
uint64_t index = mIndexCache.lookup(transferBuf->mMemory);
|
|
if (index == 0) {
|
|
index = mIndexCache.insert(transferBuf->mMemory);
|
|
reply->writeInt32(SHARED_BUFFER);
|
|
reply->writeUint64(index);
|
|
reply->writeStrongBinder(IInterface::asBinder(transferBuf->mMemory));
|
|
ALOGV("SHARED_BUFFER(%p) %llu",
|
|
transferBuf, (unsigned long long)index);
|
|
} else {
|
|
reply->writeInt32(SHARED_BUFFER_INDEX);
|
|
reply->writeUint64(index);
|
|
ALOGV("SHARED_BUFFER_INDEX(%p) %llu",
|
|
transferBuf, (unsigned long long)index);
|
|
}
|
|
reply->writeInt32(offset);
|
|
reply->writeInt32(length);
|
|
buf->meta_data().writeToParcel(*reply);
|
|
transferBuf->addRemoteRefcount(1);
|
|
if (transferBuf != buf) {
|
|
transferBuf->release(); // release local ref
|
|
} else if (!supportNonblockingRead()) {
|
|
maxNumBuffers = 0; // stop readMultiple with one shared buffer.
|
|
}
|
|
} else {
|
|
ALOGV_IF(buf->mMemory != nullptr,
|
|
"INLINE(%p) %zu shared mem available, but only %zu used",
|
|
buf, buf->mMemory->size(), length);
|
|
reply->writeInt32(INLINE_BUFFER);
|
|
reply->writeByteArray(length, (uint8_t*)buf->data() + offset);
|
|
buf->meta_data().writeToParcel(*reply);
|
|
inlineTransferSize += length;
|
|
if (inlineTransferSize > kInlineMaxTransfer) {
|
|
maxNumBuffers = 0; // stop readMultiple if inline transfer is too large.
|
|
}
|
|
}
|
|
buf->release();
|
|
}
|
|
reply->writeInt32(NULL_BUFFER); // Indicate no more MediaBuffers.
|
|
reply->writeInt32(ret);
|
|
ALOGV("readMultiple status %d, bufferCount %u, sinceStop %u",
|
|
ret, bufferCount, mBuffersSinceStop);
|
|
return NO_ERROR;
|
|
}
|
|
case SUPPORT_NONBLOCKING_READ: {
|
|
ALOGV("supportNonblockingRead");
|
|
CHECK_INTERFACE(IMediaSource, data, reply);
|
|
reply->writeInt32((int32_t)supportNonblockingRead());
|
|
return NO_ERROR;
|
|
}
|
|
default:
|
|
return BBinder::onTransact(code, data, reply, flags);
|
|
}
|
|
}
|
|
|
|
} // namespace android
|
|
|