| /* |
| * 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, |
| RELEASE_BUFFER |
| }; |
| |
| enum { |
| NULL_BUFFER, |
| SHARED_BUFFER, |
| INLINE_BUFFER |
| }; |
| |
| class RemoteMediaBufferReleaser : public BBinder { |
| public: |
| RemoteMediaBufferReleaser(MediaBuffer *buf, sp<BnMediaSource> owner) { |
| mBuf = buf; |
| mOwner = owner; |
| } |
| ~RemoteMediaBufferReleaser() { |
| if (mBuf) { |
| ALOGW("RemoteMediaBufferReleaser dtor called while still holding buffer"); |
| mBuf->release(); |
| } |
| } |
| virtual status_t onTransact( uint32_t code, |
| const Parcel& data, |
| Parcel* reply, |
| uint32_t flags = 0) { |
| if (code == RELEASE_BUFFER) { |
| mBuf->release(); |
| mBuf = NULL; |
| return OK; |
| } else { |
| return BBinder::onTransact(code, data, reply, flags); |
| } |
| } |
| private: |
| MediaBuffer *mBuf; |
| // Keep a ref to ensure MediaBuffer is released before the owner, i.e., BnMediaSource, |
| // because BnMediaSource needs to delete MediaBufferGroup in its dtor and |
| // MediaBufferGroup dtor requires all MediaBuffer's have 0 ref count. |
| sp<BnMediaSource> mOwner; |
| }; |
| |
| |
| class RemoteMediaBufferWrapper : public MediaBuffer { |
| public: |
| RemoteMediaBufferWrapper(sp<IMemory> mem, sp<IBinder> source); |
| protected: |
| virtual ~RemoteMediaBufferWrapper(); |
| private: |
| sp<IMemory> mMemory; |
| sp<IBinder> mRemoteSource; |
| }; |
| |
| RemoteMediaBufferWrapper::RemoteMediaBufferWrapper(sp<IMemory> mem, sp<IBinder> source) |
| : MediaBuffer(mem->pointer(), mem->size()) { |
| mMemory = mem; |
| mRemoteSource = source; |
| } |
| |
| RemoteMediaBufferWrapper::~RemoteMediaBufferWrapper() { |
| mMemory.clear(); |
| // Explicitly ask the remote side to release the buffer. We could also just clear |
| // mRemoteSource, but that doesn't immediately release the reference on the remote side. |
| Parcel data, reply; |
| mRemoteSource->transact(RELEASE_BUFFER, data, &reply); |
| mRemoteSource.clear(); |
| } |
| |
| class BpMediaSource : public BpInterface<IMediaSource> { |
| public: |
| explicit BpMediaSource(const sp<IBinder>& impl) |
| : BpInterface<IMediaSource>(impl) |
| { |
| } |
| |
| 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()); |
| return remote()->transact(STOP, data, &reply); |
| } |
| |
| 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) { |
| mMetaData = MetaData::createFromParcel(reply); |
| return mMetaData; |
| } |
| return NULL; |
| } |
| |
| virtual status_t read(MediaBuffer **buffer, const ReadOptions *options) { |
| ALOGV("read"); |
| Parcel data, reply; |
| data.writeInterfaceToken(BpMediaSource::getInterfaceDescriptor()); |
| if (options) { |
| data.writeByteArray(sizeof(*options), (uint8_t*) options); |
| } |
| status_t ret = remote()->transact(READ, data, &reply); |
| if (ret != NO_ERROR) { |
| return ret; |
| } |
| // wrap the returned data in a MediaBuffer |
| ret = reply.readInt32(); |
| int32_t buftype = reply.readInt32(); |
| if (buftype == SHARED_BUFFER) { |
| sp<IBinder> remote = reply.readStrongBinder(); |
| sp<IBinder> binder = reply.readStrongBinder(); |
| sp<IMemory> mem = interface_cast<IMemory>(binder); |
| if (mem == NULL) { |
| ALOGE("received NULL IMemory for shared buffer"); |
| } |
| size_t offset = reply.readInt32(); |
| size_t length = reply.readInt32(); |
| MediaBuffer *buf = new RemoteMediaBufferWrapper(mem, remote); |
| buf->set_range(offset, length); |
| buf->meta_data()->updateFromParcel(reply); |
| *buffer = buf; |
| } else if (buftype == NULL_BUFFER) { |
| ALOGV("got status %d and NULL buffer", ret); |
| *buffer = NULL; |
| } else { |
| int32_t len = reply.readInt32(); |
| ALOGV("got status %d and len %d", ret, len); |
| *buffer = new MediaBuffer(len); |
| reply.read((*buffer)->data(), len); |
| (*buffer)->meta_data()->updateFromParcel(reply); |
| } |
| return ret; |
| } |
| |
| virtual status_t pause() { |
| ALOGV("pause"); |
| Parcel data, reply; |
| data.writeInterfaceToken(BpMediaSource::getInterfaceDescriptor()); |
| return remote()->transact(PAUSE, data, &reply); |
| } |
| |
| virtual status_t setBuffers(const Vector<MediaBuffer *> & buffers __unused) { |
| ALOGV("setBuffers NOT IMPLEMENTED"); |
| return ERROR_UNSUPPORTED; // default |
| } |
| |
| private: |
| // 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; |
| |
| }; |
| |
| IMPLEMENT_META_INTERFACE(MediaSource, "android.media.IMediaSource"); |
| |
| #undef LOG_TAG |
| #define LOG_TAG "BnMediaSource" |
| |
| BnMediaSource::BnMediaSource() |
| : mGroup(NULL) { |
| } |
| |
| BnMediaSource::~BnMediaSource() { |
| delete mGroup; |
| mGroup = NULL; |
| } |
| |
| 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); |
| return stop(); |
| } |
| case PAUSE: { |
| ALOGV("pause"); |
| CHECK_INTERFACE(IMediaSource, data, reply); |
| 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 READ: { |
| ALOGV("read"); |
| CHECK_INTERFACE(IMediaSource, data, reply); |
| status_t ret; |
| MediaBuffer *buf = NULL; |
| ReadOptions opts; |
| uint32_t len; |
| if (data.readUint32(&len) == NO_ERROR && |
| len == sizeof(opts) && data.read((void*)&opts, len) == NO_ERROR) { |
| ret = read(&buf, &opts); |
| } else { |
| ret = read(&buf, NULL); |
| } |
| |
| reply->writeInt32(ret); |
| if (buf != NULL) { |
| size_t usedSize = buf->range_length(); |
| // 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. |
| if (usedSize >= MediaBuffer::kSharedMemThreshold) { |
| ALOGV("use shared memory: %zu", usedSize); |
| |
| MediaBuffer *transferBuf = buf; |
| size_t offset = buf->range_offset(); |
| if (transferBuf->mMemory == NULL) { |
| if (mGroup == NULL) { |
| mGroup = new MediaBufferGroup; |
| size_t allocateSize = usedSize; |
| if (usedSize < SIZE_MAX / 3) { |
| allocateSize = usedSize * 3 / 2; |
| } |
| mGroup->add_buffer(new MediaBuffer(allocateSize)); |
| } |
| |
| MediaBuffer *newBuf = NULL; |
| ret = mGroup->acquire_buffer( |
| &newBuf, false /* nonBlocking */, usedSize); |
| if (ret != OK || newBuf == NULL || newBuf->mMemory == NULL) { |
| ALOGW("failed to acquire shared memory, ret %d", ret); |
| buf->release(); |
| if (newBuf != NULL) { |
| newBuf->release(); |
| } |
| reply->writeInt32(NULL_BUFFER); |
| return NO_ERROR; |
| } |
| transferBuf = newBuf; |
| memcpy(transferBuf->data(), (uint8_t*)buf->data() + buf->range_offset(), |
| buf->range_length()); |
| offset = 0; |
| } |
| |
| reply->writeInt32(SHARED_BUFFER); |
| RemoteMediaBufferReleaser *wrapper = |
| new RemoteMediaBufferReleaser(transferBuf, this); |
| reply->writeStrongBinder(wrapper); |
| reply->writeStrongBinder(IInterface::asBinder(transferBuf->mMemory)); |
| reply->writeInt32(offset); |
| reply->writeInt32(usedSize); |
| buf->meta_data()->writeToParcel(*reply); |
| if (buf->mMemory == NULL) { |
| buf->release(); |
| } |
| } else { |
| // buffer is small: copy it |
| if (buf->mMemory != NULL) { |
| ALOGV("%zu shared mem available, but only %zu used", buf->mMemory->size(), buf->range_length()); |
| } |
| reply->writeInt32(INLINE_BUFFER); |
| reply->writeByteArray(buf->range_length(), (uint8_t*)buf->data() + buf->range_offset()); |
| buf->meta_data()->writeToParcel(*reply); |
| buf->release(); |
| } |
| } else { |
| ALOGV("ret %d, buf %p", ret, buf); |
| reply->writeInt32(NULL_BUFFER); |
| } |
| return NO_ERROR; |
| } |
| default: |
| return BBinder::onTransact(code, data, reply, flags); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| IMediaSource::ReadOptions::ReadOptions() { |
| reset(); |
| } |
| |
| void IMediaSource::ReadOptions::reset() { |
| mOptions = 0; |
| mSeekTimeUs = 0; |
| mLatenessUs = 0; |
| mNonBlocking = false; |
| } |
| |
| void IMediaSource::ReadOptions::setNonBlocking() { |
| mNonBlocking = true; |
| } |
| |
| void IMediaSource::ReadOptions::clearNonBlocking() { |
| mNonBlocking = false; |
| } |
| |
| bool IMediaSource::ReadOptions::getNonBlocking() const { |
| return mNonBlocking; |
| } |
| |
| void IMediaSource::ReadOptions::setSeekTo(int64_t time_us, SeekMode mode) { |
| mOptions |= kSeekTo_Option; |
| mSeekTimeUs = time_us; |
| mSeekMode = mode; |
| } |
| |
| void IMediaSource::ReadOptions::clearSeekTo() { |
| mOptions &= ~kSeekTo_Option; |
| mSeekTimeUs = 0; |
| mSeekMode = SEEK_CLOSEST_SYNC; |
| } |
| |
| bool IMediaSource::ReadOptions::getSeekTo( |
| int64_t *time_us, SeekMode *mode) const { |
| *time_us = mSeekTimeUs; |
| *mode = mSeekMode; |
| return (mOptions & kSeekTo_Option) != 0; |
| } |
| |
| void IMediaSource::ReadOptions::setLateBy(int64_t lateness_us) { |
| mLatenessUs = lateness_us; |
| } |
| |
| int64_t IMediaSource::ReadOptions::getLateBy() const { |
| return mLatenessUs; |
| } |
| |
| |
| } // namespace android |
| |