diff options
author | Vladimir Oltean <olteanv@gmail.com> | 2019-01-09 02:50:08 +0200 |
---|---|---|
committer | Luca Stefani <luca.stefani.ge1@gmail.com> | 2021-09-03 22:15:11 +0200 |
commit | 9904747886e7f806e2b798af40b3021aac6d3916 (patch) | |
tree | d4a91b54d5e6a1dd09e0b67c988320225faf87b1 | |
parent | 41f3df68de3b2075027070a7555679ad004794fb (diff) |
Revert "Removed unused class and its test"
This adds back the SurfaceMediaSource class, needed for WFD.
This reverts commit e885915204f252c93a072ba1a8802f5811e40b3d.
Change-Id: I3f67d01f18441e49205e2e263d20f0fb6fc91fe6
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
Signed-off-by: DennySPb <dennyspb@gmail.com>
-rw-r--r-- | media/libstagefright/Android.bp | 1 | ||||
-rw-r--r-- | media/libstagefright/SurfaceMediaSource.cpp | 477 | ||||
-rw-r--r-- | media/libstagefright/include/media/stagefright/SurfaceMediaSource.h | 248 | ||||
-rw-r--r-- | media/libstagefright/tests/Android.bp | 40 | ||||
-rw-r--r-- | media/libstagefright/tests/DummyRecorder.cpp | 91 | ||||
-rw-r--r-- | media/libstagefright/tests/DummyRecorder.h | 58 | ||||
-rw-r--r-- | media/libstagefright/tests/SurfaceMediaSource_test.cpp | 944 |
7 files changed, 1859 insertions, 0 deletions
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp index 3bccb7be7a..640191d67d 100644 --- a/media/libstagefright/Android.bp +++ b/media/libstagefright/Android.bp @@ -241,6 +241,7 @@ cc_library { "RemoteMediaSource.cpp", "SimpleDecodingSource.cpp", "StagefrightMediaScanner.cpp", + "SurfaceMediaSource.cpp", "SurfaceUtils.cpp", "ThrottledSource.cpp", "Utils.cpp", diff --git a/media/libstagefright/SurfaceMediaSource.cpp b/media/libstagefright/SurfaceMediaSource.cpp new file mode 100644 index 0000000000..4b3076a30d --- /dev/null +++ b/media/libstagefright/SurfaceMediaSource.cpp @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2011 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 "SurfaceMediaSource" + +#include <inttypes.h> + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/SurfaceMediaSource.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <OMX_IVCommon.h> +#include <media/hardware/HardwareAPI.h> +#include <media/hardware/MetadataBufferType.h> + +#include <ui/GraphicBuffer.h> +#include <gui/BufferItem.h> +#include <gui/ISurfaceComposer.h> +#include <OMX_Component.h> + +#include <utils/Log.h> +#include <utils/String8.h> + +#include <private/gui/ComposerService.h> + +namespace android { + +SurfaceMediaSource::SurfaceMediaSource(uint32_t bufferWidth, uint32_t bufferHeight) : + mWidth(bufferWidth), + mHeight(bufferHeight), + mCurrentSlot(BufferQueue::INVALID_BUFFER_SLOT), + mNumPendingBuffers(0), + mCurrentTimestamp(0), + mFrameRate(30), + mStarted(false), + mNumFramesReceived(0), + mNumFramesEncoded(0), + mFirstFrameTimestamp(0), + mMaxAcquiredBufferCount(4), // XXX double-check the default + mUseAbsoluteTimestamps(false) { + ALOGV("SurfaceMediaSource"); + + if (bufferWidth == 0 || bufferHeight == 0) { + ALOGE("Invalid dimensions %dx%d", bufferWidth, bufferHeight); + } + + BufferQueue::createBufferQueue(&mProducer, &mConsumer); + mConsumer->setDefaultBufferSize(bufferWidth, bufferHeight); + mConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_VIDEO_ENCODER | + GRALLOC_USAGE_HW_TEXTURE); + + sp<ISurfaceComposer> composer(ComposerService::getComposerService()); + + // Note that we can't create an sp<...>(this) in a ctor that will not keep a + // reference once the ctor ends, as that would cause the refcount of 'this' + // dropping to 0 at the end of the ctor. Since all we need is a wp<...> + // that's what we create. + wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this); + sp<BufferQueue::ProxyConsumerListener> proxy = new BufferQueue::ProxyConsumerListener(listener); + + status_t err = mConsumer->consumerConnect(proxy, false); + if (err != NO_ERROR) { + ALOGE("SurfaceMediaSource: error connecting to BufferQueue: %s (%d)", + strerror(-err), err); + } +} + +SurfaceMediaSource::~SurfaceMediaSource() { + ALOGV("~SurfaceMediaSource"); + CHECK(!mStarted); +} + +nsecs_t SurfaceMediaSource::getTimestamp() { + ALOGV("getTimestamp"); + Mutex::Autolock lock(mMutex); + return mCurrentTimestamp; +} + +void SurfaceMediaSource::setFrameAvailableListener( + const sp<FrameAvailableListener>& listener) { + ALOGV("setFrameAvailableListener"); + Mutex::Autolock lock(mMutex); + mFrameAvailableListener = listener; +} + +void SurfaceMediaSource::dumpState(String8& result) const +{ + char buffer[1024]; + dumpState(result, "", buffer, 1024); +} + +void SurfaceMediaSource::dumpState( + String8& result, + const char* /* prefix */, + char* buffer, + size_t /* SIZE */) const +{ + Mutex::Autolock lock(mMutex); + + result.append(buffer); + mConsumer->dumpState(result, ""); +} + +status_t SurfaceMediaSource::setFrameRate(int32_t fps) +{ + ALOGV("setFrameRate"); + Mutex::Autolock lock(mMutex); + const int MAX_FRAME_RATE = 60; + if (fps < 0 || fps > MAX_FRAME_RATE) { + return BAD_VALUE; + } + mFrameRate = fps; + return OK; +} + +MetadataBufferType SurfaceMediaSource::metaDataStoredInVideoBuffers() const { + ALOGV("isMetaDataStoredInVideoBuffers"); + return kMetadataBufferTypeANWBuffer; +} + +int32_t SurfaceMediaSource::getFrameRate( ) const { + ALOGV("getFrameRate"); + Mutex::Autolock lock(mMutex); + return mFrameRate; +} + +status_t SurfaceMediaSource::start(MetaData *params) +{ + ALOGV("start"); + + Mutex::Autolock lock(mMutex); + + CHECK(!mStarted); + + mStartTimeNs = 0; + int64_t startTimeUs; + int32_t bufferCount = 0; + if (params) { + if (params->findInt64(kKeyTime, &startTimeUs)) { + mStartTimeNs = startTimeUs * 1000; + } + + if (!params->findInt32(kKeyNumBuffers, &bufferCount)) { + ALOGE("Failed to find the advertised buffer count"); + return UNKNOWN_ERROR; + } + + if (bufferCount <= 1) { + ALOGE("bufferCount %d is too small", bufferCount); + return BAD_VALUE; + } + + mMaxAcquiredBufferCount = bufferCount; + } + + CHECK_GT(mMaxAcquiredBufferCount, 1u); + + status_t err = + mConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBufferCount); + + if (err != OK) { + return err; + } + + mNumPendingBuffers = 0; + mStarted = true; + + return OK; +} + +status_t SurfaceMediaSource::setMaxAcquiredBufferCount(size_t count) { + ALOGV("setMaxAcquiredBufferCount(%zu)", count); + Mutex::Autolock lock(mMutex); + + CHECK_GT(count, 1u); + mMaxAcquiredBufferCount = count; + + return OK; +} + +status_t SurfaceMediaSource::setUseAbsoluteTimestamps() { + ALOGV("setUseAbsoluteTimestamps"); + Mutex::Autolock lock(mMutex); + mUseAbsoluteTimestamps = true; + + return OK; +} + +status_t SurfaceMediaSource::stop() +{ + ALOGV("stop"); + Mutex::Autolock lock(mMutex); + + if (!mStarted) { + return OK; + } + + mStarted = false; + mFrameAvailableCondition.signal(); + + while (mNumPendingBuffers > 0) { + ALOGI("Still waiting for %zu buffers to be returned.", + mNumPendingBuffers); + +#if DEBUG_PENDING_BUFFERS + for (size_t i = 0; i < mPendingBuffers.size(); ++i) { + ALOGI("%d: %p", i, mPendingBuffers.itemAt(i)); + } +#endif + + mMediaBuffersAvailableCondition.wait(mMutex); + } + + mMediaBuffersAvailableCondition.signal(); + + return mConsumer->consumerDisconnect(); +} + +sp<MetaData> SurfaceMediaSource::getFormat() +{ + ALOGV("getFormat"); + + Mutex::Autolock lock(mMutex); + sp<MetaData> meta = new MetaData; + + meta->setInt32(kKeyWidth, mWidth); + meta->setInt32(kKeyHeight, mHeight); + // The encoder format is set as an opaque colorformat + // The encoder will later find out the actual colorformat + // from the GL Frames itself. + meta->setInt32(kKeyColorFormat, OMX_COLOR_FormatAndroidOpaque); + meta->setInt32(kKeyStride, mWidth); + meta->setInt32(kKeySliceHeight, mHeight); + meta->setInt32(kKeyFrameRate, mFrameRate); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW); + return meta; +} + +// Pass the data to the MediaBuffer. Pass in only the metadata +// Note: Call only when you have the lock +void SurfaceMediaSource::passMetadataBuffer_l(MediaBufferBase **buffer, + ANativeWindowBuffer *bufferHandle) const { + *buffer = new MediaBuffer(sizeof(VideoNativeMetadata)); + VideoNativeMetadata *data = (VideoNativeMetadata *)(*buffer)->data(); + if (data == NULL) { + ALOGE("Cannot allocate memory for metadata buffer!"); + return; + } + data->eType = metaDataStoredInVideoBuffers(); + data->pBuffer = bufferHandle; + data->nFenceFd = -1; + ALOGV("handle = %p, offset = %zu, length = %zu", + bufferHandle, (*buffer)->range_length(), (*buffer)->range_offset()); +} + +status_t SurfaceMediaSource::read( + MediaBufferBase **buffer, const ReadOptions * /* options */) { + ALOGV("read"); + Mutex::Autolock lock(mMutex); + + *buffer = NULL; + + while (mStarted && mNumPendingBuffers == mMaxAcquiredBufferCount) { + mMediaBuffersAvailableCondition.wait(mMutex); + } + + // Update the current buffer info + // TODO: mCurrentSlot can be made a bufferstate since there + // can be more than one "current" slots. + + BufferItem item; + // If the recording has started and the queue is empty, then just + // wait here till the frames come in from the client side + while (mStarted) { + + status_t err = mConsumer->acquireBuffer(&item, 0); + if (err == BufferQueue::NO_BUFFER_AVAILABLE) { + // wait for a buffer to be queued + mFrameAvailableCondition.wait(mMutex); + } else if (err == OK) { + err = item.mFence->waitForever("SurfaceMediaSource::read"); + if (err) { + ALOGW("read: failed to wait for buffer fence: %d", err); + } + + // First time seeing the buffer? Added it to the SMS slot + if (item.mGraphicBuffer != NULL) { + mSlots[item.mSlot].mGraphicBuffer = item.mGraphicBuffer; + } + mSlots[item.mSlot].mFrameNumber = item.mFrameNumber; + + // check for the timing of this buffer + if (mNumFramesReceived == 0 && !mUseAbsoluteTimestamps) { + mFirstFrameTimestamp = item.mTimestamp; + // Initial delay + if (mStartTimeNs > 0) { + if (item.mTimestamp < mStartTimeNs) { + // This frame predates start of record, discard + mConsumer->releaseBuffer( + item.mSlot, item.mFrameNumber, EGL_NO_DISPLAY, + EGL_NO_SYNC_KHR, Fence::NO_FENCE); + continue; + } + mStartTimeNs = item.mTimestamp - mStartTimeNs; + } + } + item.mTimestamp = mStartTimeNs + (item.mTimestamp - mFirstFrameTimestamp); + + mNumFramesReceived++; + + break; + } else { + ALOGE("read: acquire failed with error code %d", err); + return ERROR_END_OF_STREAM; + } + + } + + // If the loop was exited as a result of stopping the recording, + // it is OK + if (!mStarted) { + ALOGV("Read: SurfaceMediaSource is stopped. Returning ERROR_END_OF_STREAM."); + return ERROR_END_OF_STREAM; + } + + mCurrentSlot = item.mSlot; + + // First time seeing the buffer? Added it to the SMS slot + if (item.mGraphicBuffer != NULL) { + mSlots[item.mSlot].mGraphicBuffer = item.mGraphicBuffer; + } + mSlots[item.mSlot].mFrameNumber = item.mFrameNumber; + + mCurrentBuffers.push_back(mSlots[mCurrentSlot].mGraphicBuffer); + int64_t prevTimeStamp = mCurrentTimestamp; + mCurrentTimestamp = item.mTimestamp; + + mNumFramesEncoded++; + // Pass the data to the MediaBuffer. Pass in only the metadata + + passMetadataBuffer_l(buffer, mSlots[mCurrentSlot].mGraphicBuffer->getNativeBuffer()); + + (*buffer)->setObserver(this); + (*buffer)->add_ref(); + (*buffer)->meta_data()->setInt64(kKeyTime, mCurrentTimestamp / 1000); + ALOGV("Frames encoded = %d, timestamp = %" PRId64 ", time diff = %" PRId64, + mNumFramesEncoded, mCurrentTimestamp / 1000, + mCurrentTimestamp / 1000 - prevTimeStamp / 1000); + + ++mNumPendingBuffers; + +#if DEBUG_PENDING_BUFFERS + mPendingBuffers.push_back(*buffer); +#endif + + ALOGV("returning mbuf %p", *buffer); + + return OK; +} + +static buffer_handle_t getMediaBufferHandle(MediaBufferBase *buffer) { + // need to convert to char* for pointer arithmetic and then + // copy the byte stream into our handle + buffer_handle_t bufferHandle; + memcpy(&bufferHandle, (char*)(buffer->data()) + 4, sizeof(buffer_handle_t)); + return bufferHandle; +} + +void SurfaceMediaSource::signalBufferReturned(MediaBufferBase *buffer) { + ALOGV("signalBufferReturned"); + + bool foundBuffer = false; + + Mutex::Autolock lock(mMutex); + + buffer_handle_t bufferHandle = getMediaBufferHandle(buffer); + + for (size_t i = 0; i < mCurrentBuffers.size(); i++) { + if (mCurrentBuffers[i]->handle == bufferHandle) { + mCurrentBuffers.removeAt(i); + foundBuffer = true; + break; + } + } + + if (!foundBuffer) { + ALOGW("returned buffer was not found in the current buffer list"); + } + + for (int id = 0; id < BufferQueue::NUM_BUFFER_SLOTS; id++) { + if (mSlots[id].mGraphicBuffer == NULL) { + continue; + } + + if (bufferHandle == mSlots[id].mGraphicBuffer->handle) { + ALOGV("Slot %d returned, matches handle = %p", id, + mSlots[id].mGraphicBuffer->handle); + + mConsumer->releaseBuffer(id, mSlots[id].mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, + Fence::NO_FENCE); + + buffer->setObserver(0); + buffer->release(); + + foundBuffer = true; + break; + } + } + + if (!foundBuffer) { + CHECK(!"signalBufferReturned: bogus buffer"); + } + +#if DEBUG_PENDING_BUFFERS + for (size_t i = 0; i < mPendingBuffers.size(); ++i) { + if (mPendingBuffers.itemAt(i) == buffer) { + mPendingBuffers.removeAt(i); + break; + } + } +#endif + + --mNumPendingBuffers; + mMediaBuffersAvailableCondition.broadcast(); +} + +// Part of the BufferQueue::ConsumerListener +void SurfaceMediaSource::onFrameAvailable(const BufferItem& /* item */) { + ALOGV("onFrameAvailable"); + + sp<FrameAvailableListener> listener; + { // scope for the lock + Mutex::Autolock lock(mMutex); + mFrameAvailableCondition.broadcast(); + listener = mFrameAvailableListener; + } + + if (listener != NULL) { + ALOGV("actually calling onFrameAvailable"); + listener->onFrameAvailable(); + } +} + +// SurfaceMediaSource hijacks this event to assume +// the prodcuer is disconnecting from the BufferQueue +// and that it should stop the recording +void SurfaceMediaSource::onBuffersReleased() { + ALOGV("onBuffersReleased"); + + Mutex::Autolock lock(mMutex); + + mFrameAvailableCondition.signal(); + + for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { + mSlots[i].mGraphicBuffer = 0; + } +} + +void SurfaceMediaSource::onSidebandStreamChanged() { + ALOG_ASSERT(false, "SurfaceMediaSource can't consume sideband streams"); +} + +} // end of namespace android diff --git a/media/libstagefright/include/media/stagefright/SurfaceMediaSource.h b/media/libstagefright/include/media/stagefright/SurfaceMediaSource.h new file mode 100644 index 0000000000..d49e44cd8a --- /dev/null +++ b/media/libstagefright/include/media/stagefright/SurfaceMediaSource.h @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef ANDROID_GUI_SURFACEMEDIASOURCE_H +#define ANDROID_GUI_SURFACEMEDIASOURCE_H + +#include <gui/IGraphicBufferProducer.h> +#include <gui/BufferQueue.h> + +#include <utils/threads.h> +#include <utils/Vector.h> +#include <media/MediaSource.h> +#include <media/stagefright/MediaBuffer.h> + +#include <media/hardware/MetadataBufferType.h> + +#include "foundation/ABase.h" + +namespace android { +// ---------------------------------------------------------------------------- + +class String8; +class GraphicBuffer; + +// ASSUMPTIONS +// 1. SurfaceMediaSource is initialized with width*height which +// can never change. However, deqeueue buffer does not currently +// enforce this as in BufferQueue, dequeue can be used by Surface +// which can modify the default width and heght. Also neither the width +// nor height can be 0. +// 2. setSynchronousMode is never used (basically no one should call +// setSynchronousMode(false) +// 3. setCrop, setTransform, setScalingMode should never be used +// 4. queueBuffer returns a filled buffer to the SurfaceMediaSource. In addition, a +// timestamp must be provided for the buffer. The timestamp is in +// nanoseconds, and must be monotonically increasing. Its other semantics +// (zero point, etc) are client-dependent and should be documented by the +// client. +// 5. Once disconnected, SurfaceMediaSource can be reused (can not +// connect again) +// 6. Stop is a hard stop, the last few frames held by the encoder +// may be dropped. It is possible to wait for the buffers to be +// returned (but not implemented) + +#define DEBUG_PENDING_BUFFERS 0 + +class SurfaceMediaSource : public MediaSource, + public MediaBufferObserver, + protected ConsumerListener { +public: + enum { MIN_UNDEQUEUED_BUFFERS = 4}; + + struct FrameAvailableListener : public virtual RefBase { + // onFrameAvailable() is called from queueBuffer() is the FIFO is + // empty. You can use SurfaceMediaSource::getQueuedCount() to + // figure out if there are more frames waiting. + // This is called without any lock held can be called concurrently by + // multiple threads. + virtual void onFrameAvailable() = 0; + }; + + SurfaceMediaSource(uint32_t bufferWidth, uint32_t bufferHeight); + + virtual ~SurfaceMediaSource(); + + // For the MediaSource interface for use by StageFrightRecorder: + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + virtual status_t read(MediaBufferBase **buffer, + const ReadOptions *options = NULL); + virtual sp<MetaData> getFormat(); + + // Get / Set the frame rate used for encoding. Default fps = 30 + status_t setFrameRate(int32_t fps) ; + int32_t getFrameRate( ) const; + + // The call for the StageFrightRecorder to tell us that + // it is done using the MediaBuffer data so that its state + // can be set to FREE for dequeuing + virtual void signalBufferReturned(MediaBufferBase* buffer); + // end of MediaSource interface + + // getTimestamp retrieves the timestamp associated with the image + // set by the most recent call to read() + // + // The timestamp is in nanoseconds, and is monotonically increasing. Its + // other semantics (zero point, etc) are source-dependent and should be + // documented by the source. + int64_t getTimestamp(); + + // setFrameAvailableListener sets the listener object that will be notified + // when a new frame becomes available. + void setFrameAvailableListener(const sp<FrameAvailableListener>& listener); + + // dump our state in a String + void dumpState(String8& result) const; + void dumpState(String8& result, const char* prefix, char* buffer, + size_t SIZE) const; + + // metaDataStoredInVideoBuffers tells the encoder what kind of metadata + // is passed through the buffers. Currently, it is set to ANWBuffer + MetadataBufferType metaDataStoredInVideoBuffers() const; + + sp<IGraphicBufferProducer> getProducer() const { return mProducer; } + + // To be called before start() + status_t setMaxAcquiredBufferCount(size_t count); + + // To be called before start() + status_t setUseAbsoluteTimestamps(); + +protected: + + // Implementation of the BufferQueue::ConsumerListener interface. These + // calls are used to notify the Surface of asynchronous events in the + // BufferQueue. + virtual void onFrameAvailable(const BufferItem& item); + + // Used as a hook to BufferQueue::disconnect() + // This is called by the client side when it is done + // TODO: Currently, this also sets mStopped to true which + // is needed for unblocking the encoder which might be + // waiting to read more frames. So if on the client side, + // the same thread supplies the frames and also calls stop + // on the encoder, the client has to call disconnect before + // it calls stop. + // In the case of the camera, + // that need not be required since the thread supplying the + // frames is separate than the one calling stop. + virtual void onBuffersReleased(); + + // SurfaceMediaSource can't handle sideband streams, so this is not expected + // to ever be called. Does nothing. + virtual void onSidebandStreamChanged(); + + static bool isExternalFormat(uint32_t format); + +private: + // A BufferQueue, represented by these interfaces, is the exchange point + // between the producer and this consumer + sp<IGraphicBufferProducer> mProducer; + sp<IGraphicBufferConsumer> mConsumer; + + struct SlotData { + sp<GraphicBuffer> mGraphicBuffer; + uint64_t mFrameNumber; + }; + + // mSlots caches GraphicBuffers and frameNumbers from the buffer queue + SlotData mSlots[BufferQueue::NUM_BUFFER_SLOTS]; + + // The permenent width and height of SMS buffers + int mWidth; + int mHeight; + + // mCurrentSlot is the buffer slot index of the buffer that is currently + // being used by buffer consumer + // (e.g. StageFrightRecorder in the case of SurfaceMediaSource or GLTexture + // in the case of Surface). + // It is initialized to INVALID_BUFFER_SLOT, + // indicating that no buffer slot is currently bound to the texture. Note, + // however, that a value of INVALID_BUFFER_SLOT does not necessarily mean + // that no buffer is bound to the texture. A call to setBufferCount will + // reset mCurrentTexture to INVALID_BUFFER_SLOT. + int mCurrentSlot; + + // mCurrentBuffers is a list of the graphic buffers that are being used by + // buffer consumer (i.e. the video encoder). It's possible that these + // buffers are not associated with any buffer slots, so we must track them + // separately. Buffers are added to this list in read, and removed from + // this list in signalBufferReturned + Vector<sp<GraphicBuffer> > mCurrentBuffers; + + size_t mNumPendingBuffers; + +#if DEBUG_PENDING_BUFFERS + Vector<MediaBuffer *> mPendingBuffers; +#endif + + // mCurrentTimestamp is the timestamp for the current texture. It + // gets set to mLastQueuedTimestamp each time updateTexImage is called. + int64_t mCurrentTimestamp; + + // mFrameAvailableListener is the listener object that will be called when a + // new frame becomes available. If it is not NULL it will be called from + // queueBuffer. + sp<FrameAvailableListener> mFrameAvailableListener; + + // mMutex is the mutex used to prevent concurrent access to the member + // variables of SurfaceMediaSource objects. It must be locked whenever the + // member variables are accessed. + mutable Mutex mMutex; + + ////////////////////////// For MediaSource + // Set to a default of 30 fps if not specified by the client side + int32_t mFrameRate; + + // mStarted is a flag to check if the recording is going on + bool mStarted; + + // mNumFramesReceived indicates the number of frames recieved from + // the client side + int mNumFramesReceived; + // mNumFramesEncoded indicates the number of frames passed on to the + // encoder + int mNumFramesEncoded; + + // mFirstFrameTimestamp is the timestamp of the first received frame. + // It is used to offset the output timestamps so recording starts at time 0. + int64_t mFirstFrameTimestamp; + // mStartTimeNs is the start time passed into the source at start, used to + // offset timestamps. + int64_t mStartTimeNs; + + size_t mMaxAcquiredBufferCount; + + bool mUseAbsoluteTimestamps; + + // mFrameAvailableCondition condition used to indicate whether there + // is a frame available for dequeuing + Condition mFrameAvailableCondition; + + Condition mMediaBuffersAvailableCondition; + + // Allocate and return a new MediaBuffer and pass the ANW buffer as metadata into it. + void passMetadataBuffer_l(MediaBufferBase **buffer, ANativeWindowBuffer *bufferHandle) const; + + // Avoid copying and equating and default constructor + DISALLOW_EVIL_CONSTRUCTORS(SurfaceMediaSource); +}; + +// ---------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_GUI_SURFACEMEDIASOURCE_H diff --git a/media/libstagefright/tests/Android.bp b/media/libstagefright/tests/Android.bp index a7f94c1365..88fadc35fe 100644 --- a/media/libstagefright/tests/Android.bp +++ b/media/libstagefright/tests/Android.bp @@ -1,6 +1,46 @@ // Build the unit tests. cc_test { + name: "SurfaceMediaSource_test", + + srcs: [ + "SurfaceMediaSource_test.cpp", + "DummyRecorder.cpp", + ], + + shared_libs: [ + "libEGL", + "libGLESv2", + "libbinder", + "libcutils", + "libgui", + "libmedia", + "libmediaextractor", + "libstagefright", + "libstagefright_foundation", + "libstagefright_omx", + "libsync", + "libui", + "libutils", + "liblog", + ], + + include_dirs: [ + "frameworks/av/media/libstagefright", + "frameworks/av/media/libstagefright/include", + "frameworks/native/include/media/openmax", + "frameworks/native/include/media/hardware", + ], + + cflags: [ + "-Werror", + "-Wall", + ], + + compile_multilib: "32", +} + +cc_test { name: "MediaCodecListOverrides_test", srcs: ["MediaCodecListOverrides_test.cpp"], diff --git a/media/libstagefright/tests/DummyRecorder.cpp b/media/libstagefright/tests/DummyRecorder.cpp new file mode 100644 index 0000000000..c79e6b1a9c --- /dev/null +++ b/media/libstagefright/tests/DummyRecorder.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2011 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_TAG "DummyRecorder" +// #define LOG_NDEBUG 0 + +#include <media/MediaSource.h> +#include <media/stagefright/MediaBuffer.h> +#include "DummyRecorder.h" + +#include <utils/Log.h> + +namespace android { + +// static +void *DummyRecorder::threadWrapper(void *pthis) { + ALOGV("ThreadWrapper: %p", pthis); + DummyRecorder *writer = static_cast<DummyRecorder *>(pthis); + writer->readFromSource(); + return NULL; +} + + +status_t DummyRecorder::start() { + ALOGV("Start"); + mStarted = true; + + mSource->start(); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + int err = pthread_create(&mThread, &attr, threadWrapper, this); + pthread_attr_destroy(&attr); + + if (err) { + ALOGE("Error creating thread!"); + return -ENODEV; + } + return OK; +} + + +status_t DummyRecorder::stop() { + ALOGV("Stop"); + mStarted = false; + + mSource->stop(); + void *dummy; + pthread_join(mThread, &dummy); + status_t err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy)); + + ALOGV("Ending the reading thread"); + return err; +} + +// pretend to read the source buffers +void DummyRecorder::readFromSource() { + ALOGV("ReadFromSource"); + if (!mStarted) { + return; + } + + status_t err = OK; + MediaBufferBase *buffer; + ALOGV("A fake writer accessing the frames"); + while (mStarted && (err = mSource->read(&buffer)) == OK){ + // if not getting a valid buffer from source, then exit + if (buffer == NULL) { + return; + } + buffer->release(); + buffer = NULL; + } +} + + +} // end of namespace android diff --git a/media/libstagefright/tests/DummyRecorder.h b/media/libstagefright/tests/DummyRecorder.h new file mode 100644 index 0000000000..075977784f --- /dev/null +++ b/media/libstagefright/tests/DummyRecorder.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef DUMMY_RECORDER_H_ +#define DUMMY_RECORDER_H_ + +#include <pthread.h> +#include <utils/String8.h> +#include <media/stagefright/foundation/ABase.h> + + +namespace android { + +struct MediaSource; +class MediaBuffer; + +class DummyRecorder { + public: + // The media source from which this will receive frames + sp<MediaSource> mSource; + bool mStarted; + pthread_t mThread; + + status_t start(); + status_t stop(); + + // actual entry point for the thread + void readFromSource(); + + // static function to wrap the actual thread entry point + static void *threadWrapper(void *pthis); + + explicit DummyRecorder(const sp<MediaSource> &source) : mSource(source) + , mStarted(false) {} + ~DummyRecorder( ) {} + + private: + + DISALLOW_EVIL_CONSTRUCTORS(DummyRecorder); +}; + +} // end of namespace android +#endif + + diff --git a/media/libstagefright/tests/SurfaceMediaSource_test.cpp b/media/libstagefright/tests/SurfaceMediaSource_test.cpp new file mode 100644 index 0000000000..1b1c3b8cdb --- /dev/null +++ b/media/libstagefright/tests/SurfaceMediaSource_test.cpp @@ -0,0 +1,944 @@ +/* + * Copyright (C) 2011 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 "SurfaceMediaSource_test" + +#include <gtest/gtest.h> +#include <utils/String8.h> +#include <utils/String16.h> +#include <utils/Errors.h> +#include <fcntl.h> +#include <unistd.h> + +#include <GLES2/gl2.h> + +#include <media/stagefright/SurfaceMediaSource.h> +#include <media/mediarecorder.h> + +#include <ui/GraphicBuffer.h> +#include <gui/Surface.h> +#include <gui/ISurfaceComposer.h> +#include <gui/Surface.h> +#include <gui/SurfaceComposerClient.h> + +#include <binder/ProcessState.h> + +#include <media/stagefright/foundation/ADebug.h> +#include <OMX_Component.h> + +#include "DummyRecorder.h" + + +namespace android { + +class GLTest : public ::testing::Test { +protected: + + GLTest(): + mEglDisplay(EGL_NO_DISPLAY), + mEglSurface(EGL_NO_SURFACE), + mEglContext(EGL_NO_CONTEXT) { + } + + virtual void SetUp() { + ALOGV("GLTest::SetUp()"); + mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay); + + EGLint majorVersion; + EGLint minorVersion; + EXPECT_TRUE(eglInitialize(mEglDisplay, &majorVersion, &minorVersion)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + RecordProperty("EglVersionMajor", majorVersion); + RecordProperty("EglVersionMajor", minorVersion); + + EGLint numConfigs = 0; + EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &mGlConfig, + 1, &numConfigs)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + char* displaySecsEnv = getenv("GLTEST_DISPLAY_SECS"); + if (displaySecsEnv != NULL) { + mDisplaySecs = atoi(displaySecsEnv); + if (mDisplaySecs < 0) { + mDisplaySecs = 0; + } + } else { + mDisplaySecs = 0; + } + + if (mDisplaySecs > 0) { + mComposerClient = new SurfaceComposerClient; + ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); + + mSurfaceControl = mComposerClient->createSurface( + String8("Test Surface"), + getSurfaceWidth(), getSurfaceHeight(), + PIXEL_FORMAT_RGB_888, 0); + + ASSERT_TRUE(mSurfaceControl != NULL); + ASSERT_TRUE(mSurfaceControl->isValid()); + + SurfaceComposerClient::Transaction{} + .setLayer(mSurfaceControl, 0x7FFFFFFF) + .show(mSurfaceControl) + .apply(); + + sp<ANativeWindow> window = mSurfaceControl->getSurface(); + mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, + window.get(), NULL); + } else { + ALOGV("No actual display. Choosing EGLSurface based on SurfaceMediaSource"); + sp<IGraphicBufferProducer> sms = (new SurfaceMediaSource( + getSurfaceWidth(), getSurfaceHeight()))->getProducer(); + sp<Surface> stc = new Surface(sms); + sp<ANativeWindow> window = stc; + + mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, + window.get(), NULL); + } + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_SURFACE, mEglSurface); + + mEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, + getContextAttribs()); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_CONTEXT, mEglContext); + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + EGLint w, h; + EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &w)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &h)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + RecordProperty("EglSurfaceWidth", w); + RecordProperty("EglSurfaceHeight", h); + + glViewport(0, 0, w, h); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + } + + virtual void TearDown() { + // Display the result + if (mDisplaySecs > 0 && mEglSurface != EGL_NO_SURFACE) { + eglSwapBuffers(mEglDisplay, mEglSurface); + sleep(mDisplaySecs); + } + + if (mComposerClient != NULL) { + mComposerClient->dispose(); + } + if (mEglContext != EGL_NO_CONTEXT) { + eglDestroyContext(mEglDisplay, mEglContext); + } + if (mEglSurface != EGL_NO_SURFACE) { + eglDestroySurface(mEglDisplay, mEglSurface); + } + if (mEglDisplay != EGL_NO_DISPLAY) { + eglTerminate(mEglDisplay); + } + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + } + + virtual EGLint const* getConfigAttribs() { + ALOGV("GLTest getConfigAttribs"); + static EGLint sDefaultConfigAttribs[] = { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 16, + EGL_STENCIL_SIZE, 8, + EGL_NONE }; + + return sDefaultConfigAttribs; + } + + virtual EGLint const* getContextAttribs() { + static EGLint sDefaultContextAttribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE }; + + return sDefaultContextAttribs; + } + + virtual EGLint getSurfaceWidth() { + return 512; + } + + virtual EGLint getSurfaceHeight() { + return 512; + } + + void loadShader(GLenum shaderType, const char* pSource, GLuint* outShader) { + GLuint shader = glCreateShader(shaderType); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + if (shader) { + glShaderSource(shader, 1, &pSource, NULL); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glCompileShader(shader); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + if (!compiled) { + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + if (infoLen) { + char* buf = (char*) malloc(infoLen); + if (buf) { + glGetShaderInfoLog(shader, infoLen, NULL, buf); + printf("Shader compile log:\n%s\n", buf); + free(buf); + FAIL(); + } + } else { + char* buf = (char*) malloc(0x1000); + if (buf) { + glGetShaderInfoLog(shader, 0x1000, NULL, buf); + printf("Shader compile log:\n%s\n", buf); + free(buf); + FAIL(); + } + } + glDeleteShader(shader); + shader = 0; + } + } + ASSERT_TRUE(shader != 0); + *outShader = shader; + } + + void createProgram(const char* pVertexSource, const char* pFragmentSource, + GLuint* outPgm) { + GLuint vertexShader, fragmentShader; + { + SCOPED_TRACE("compiling vertex shader"); + loadShader(GL_VERTEX_SHADER, pVertexSource, &vertexShader); + if (HasFatalFailure()) { + return; + } + } + { + SCOPED_TRACE("compiling fragment shader"); + loadShader(GL_FRAGMENT_SHADER, pFragmentSource, &fragmentShader); + if (HasFatalFailure()) { + return; + } + } + + GLuint program = glCreateProgram(); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + if (program) { + glAttachShader(program, vertexShader); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glAttachShader(program, fragmentShader); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glLinkProgram(program); + GLint linkStatus = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + if (linkStatus != GL_TRUE) { + GLint bufLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); + if (bufLength) { + char* buf = (char*) malloc(bufLength); + if (buf) { + glGetProgramInfoLog(program, bufLength, NULL, buf); + printf("Program link log:\n%s\n", buf); + free(buf); + FAIL(); + } + } + glDeleteProgram(program); + program = 0; + } + } + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + ASSERT_TRUE(program != 0); + *outPgm = program; + } + + static int abs(int value) { + return value > 0 ? value : -value; + } + + ::testing::AssertionResult checkPixel(int x, int y, int r, + int g, int b, int a, int tolerance=2) { + GLubyte pixel[4]; + String8 msg; + glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + msg += String8::format("error reading pixel: %#x", err); + while ((err = glGetError()) != GL_NO_ERROR) { + msg += String8::format(", %#x", err); + } + fprintf(stderr, "pixel check failure: %s\n", msg.string()); + return ::testing::AssertionFailure( + ::testing::Message(msg.string())); + } + if (r >= 0 && abs(r - int(pixel[0])) > tolerance) { + msg += String8::format("r(%d isn't %d)", pixel[0], r); + } + if (g >= 0 && abs(g - int(pixel[1])) > tolerance) { + if (!msg.isEmpty()) { + msg += " "; + } + msg += String8::format("g(%d isn't %d)", pixel[1], g); + } + if (b >= 0 && abs(b - int(pixel[2])) > tolerance) { + if (!msg.isEmpty()) { + msg += " "; + } + msg += String8::format("b(%d isn't %d)", pixel[2], b); + } + if (a >= 0 && abs(a - int(pixel[3])) > tolerance) { + if (!msg.isEmpty()) { + msg += " "; + } + msg += String8::format("a(%d isn't %d)", pixel[3], a); + } + if (!msg.isEmpty()) { + fprintf(stderr, "pixel check failure: %s\n", msg.string()); + return ::testing::AssertionFailure( + ::testing::Message(msg.string())); + } else { + return ::testing::AssertionSuccess(); + } + } + + int mDisplaySecs; + sp<SurfaceComposerClient> mComposerClient; + sp<SurfaceControl> mSurfaceControl; + + EGLDisplay mEglDisplay; + EGLSurface mEglSurface; + EGLContext mEglContext; + EGLConfig mGlConfig; +}; + +/////////////////////////////////////////////////////////////////////// +// Class for the NON-GL tests +/////////////////////////////////////////////////////////////////////// +class SurfaceMediaSourceTest : public ::testing::Test { +public: + + SurfaceMediaSourceTest( ): mYuvTexWidth(176), mYuvTexHeight(144) { } + void oneBufferPass(int width, int height ); + void oneBufferPassNoFill(int width, int height ); + static void fillYV12Buffer(uint8_t* buf, int w, int h, int stride) ; + static void fillYV12BufferRect(uint8_t* buf, int w, int h, + int stride, const android_native_rect_t& rect) ; +protected: + + virtual void SetUp() { + android::ProcessState::self()->startThreadPool(); + mSMS = new SurfaceMediaSource(mYuvTexWidth, mYuvTexHeight); + mSTC = new Surface(mSMS->getProducer()); + mANW = mSTC; + } + + virtual void TearDown() { + mSMS.clear(); + mSTC.clear(); + mANW.clear(); + } + + const int mYuvTexWidth; + const int mYuvTexHeight; + + sp<SurfaceMediaSource> mSMS; + sp<Surface> mSTC; + sp<ANativeWindow> mANW; +}; + +/////////////////////////////////////////////////////////////////////// +// Class for the GL tests +/////////////////////////////////////////////////////////////////////// +class SurfaceMediaSourceGLTest : public GLTest { +public: + + SurfaceMediaSourceGLTest( ): mYuvTexWidth(176), mYuvTexHeight(144) { } + virtual EGLint const* getConfigAttribs(); + void oneBufferPassGL(int num = 0); + static sp<MediaRecorder> setUpMediaRecorder(int fileDescriptor, int videoSource, + int outputFormat, int videoEncoder, int width, int height, int fps); +protected: + + virtual void SetUp() { + ALOGV("SMS-GLTest::SetUp()"); + android::ProcessState::self()->startThreadPool(); + mSMS = new SurfaceMediaSource(mYuvTexWidth, mYuvTexHeight); + mSTC = new Surface(mSMS->getProducer()); + mANW = mSTC; + + // Doing the setup related to the GL Side + GLTest::SetUp(); + } + + virtual void TearDown() { + mSMS.clear(); + mSTC.clear(); + mANW.clear(); + GLTest::TearDown(); + } + + void setUpEGLSurfaceFromMediaRecorder(sp<MediaRecorder>& mr); + + const int mYuvTexWidth; + const int mYuvTexHeight; + + sp<SurfaceMediaSource> mSMS; + sp<Surface> mSTC; + sp<ANativeWindow> mANW; +}; + +///////////////////////////////////////////////////////////////////// +// Methods in SurfaceMediaSourceGLTest +///////////////////////////////////////////////////////////////////// +EGLint const* SurfaceMediaSourceGLTest::getConfigAttribs() { + ALOGV("SurfaceMediaSourceGLTest getConfigAttribs"); + static EGLint sDefaultConfigAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_RECORDABLE_ANDROID, EGL_TRUE, + EGL_NONE }; + + return sDefaultConfigAttribs; +} + +// One pass of dequeuing and queuing a GLBuffer +void SurfaceMediaSourceGLTest::oneBufferPassGL(int num) { + int d = num % 50; + float f = 0.2f; // 0.1f * d; + + glClearColor(0, 0.3, 0, 0.6); + glClear(GL_COLOR_BUFFER_BIT); + + glEnable(GL_SCISSOR_TEST); + glScissor(4 + d, 4 + d, 4, 4); + glClearColor(1.0 - f, f, f, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glScissor(24 + d, 48 + d, 4, 4); + glClearColor(f, 1.0 - f, f, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glScissor(37 + d, 17 + d, 4, 4); + glClearColor(f, f, 1.0 - f, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + // The following call dequeues and queues the buffer + eglSwapBuffers(mEglDisplay, mEglSurface); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + glDisable(GL_SCISSOR_TEST); +} + +// Set up the MediaRecorder which runs in the same process as mediaserver +sp<MediaRecorder> SurfaceMediaSourceGLTest::setUpMediaRecorder(int fd, int videoSource, + int outputFormat, int videoEncoder, int width, int height, int fps) { + sp<MediaRecorder> mr = new MediaRecorder(String16()); + mr->setVideoSource(videoSource); + mr->setOutputFormat(outputFormat); + mr->setVideoEncoder(videoEncoder); + mr->setOutputFile(fd); + mr->setVideoSize(width, height); + mr->setVideoFrameRate(fps); + mr->prepare(); + ALOGV("Starting MediaRecorder..."); + CHECK_EQ((status_t)OK, mr->start()); + return mr; +} + +// query the mediarecorder for a surfacemeidasource and create an egl surface with that +void SurfaceMediaSourceGLTest::setUpEGLSurfaceFromMediaRecorder(sp<MediaRecorder>& mr) { + sp<IGraphicBufferProducer> iST = mr->querySurfaceMediaSourceFromMediaServer(); + mSTC = new Surface(iST); + mANW = mSTC; + + if (mEglSurface != EGL_NO_SURFACE) { + EXPECT_TRUE(eglDestroySurface(mEglDisplay, mEglSurface)); + mEglSurface = EGL_NO_SURFACE; + } + mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, + mANW.get(), NULL); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_SURFACE, mEglSurface) ; + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); +} + + +///////////////////////////////////////////////////////////////////// +// Methods in SurfaceMediaSourceTest +///////////////////////////////////////////////////////////////////// + +// One pass of dequeuing and queuing the buffer. Fill it in with +// cpu YV12 buffer +void SurfaceMediaSourceTest::oneBufferPass(int width, int height ) { + ANativeWindowBuffer* anb; + ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); + ASSERT_TRUE(anb != NULL); + + + // Fill the buffer with the a checkerboard pattern + uint8_t* img = NULL; + sp<GraphicBuffer> buf(GraphicBuffer::from(anb)); + buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); + SurfaceMediaSourceTest::fillYV12Buffer(img, width, height, buf->getStride()); + buf->unlock(); + + ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), + -1)); +} + +// Dequeuing and queuing the buffer without really filling it in. +void SurfaceMediaSourceTest::oneBufferPassNoFill( + int /* width */, int /* height */) { + ANativeWindowBuffer* anb; + ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); + ASSERT_TRUE(anb != NULL); + + // We do not fill the buffer in. Just queue it back. + sp<GraphicBuffer> buf(GraphicBuffer::from(anb)); + ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), + -1)); +} + +// Fill a YV12 buffer with a multi-colored checkerboard pattern +void SurfaceMediaSourceTest::fillYV12Buffer(uint8_t* buf, int w, int h, int stride) { + const int blockWidth = w > 16 ? w / 16 : 1; + const int blockHeight = h > 16 ? h / 16 : 1; + const int yuvTexOffsetY = 0; + int yuvTexStrideY = stride; + int yuvTexOffsetV = yuvTexStrideY * h; + int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; + int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2; + int yuvTexStrideU = yuvTexStrideV; + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + int parityX = (x / blockWidth) & 1; + int parityY = (y / blockHeight) & 1; + unsigned char intensity = (parityX ^ parityY) ? 63 : 191; + buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = intensity; + if (x < w / 2 && y < h / 2) { + buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = intensity; + if (x * 2 < w / 2 && y * 2 < h / 2) { + buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 0] = + buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 1] = + buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 0] = + buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 1] = + intensity; + } + } + } + } +} + +// Fill a YV12 buffer with red outside a given rectangle and green inside it. +void SurfaceMediaSourceTest::fillYV12BufferRect(uint8_t* buf, int w, + int h, int stride, const android_native_rect_t& rect) { + const int yuvTexOffsetY = 0; + int yuvTexStrideY = stride; + int yuvTexOffsetV = yuvTexStrideY * h; + int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; + int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2; + int yuvTexStrideU = yuvTexStrideV; + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + bool inside = rect.left <= x && x < rect.right && + rect.top <= y && y < rect.bottom; + buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = inside ? 240 : 64; + if (x < w / 2 && y < h / 2) { + bool inside = rect.left <= 2*x && 2*x < rect.right && + rect.top <= 2*y && 2*y < rect.bottom; + buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = 16; + buf[yuvTexOffsetV + (y * yuvTexStrideV) + x] = + inside ? 16 : 255; + } + } + } +} ///////// End of class SurfaceMediaSourceTest + +/////////////////////////////////////////////////////////////////// +// Class to imitate the recording ///////////////////////////// +// //////////////////////////////////////////////////////////////// +struct SimpleDummyRecorder { + sp<MediaSource> mSource; + + explicit SimpleDummyRecorder + (const sp<MediaSource> &source): mSource(source) {} + + status_t start() { return mSource->start();} + status_t stop() { return mSource->stop();} + + // fakes reading from a media source + status_t readFromSource() { + MediaBufferBase *buffer; + status_t err = mSource->read(&buffer); + if (err != OK) { + return err; + } + buffer->release(); + buffer = NULL; + return OK; + } +}; +/////////////////////////////////////////////////////////////////// +// TESTS +// SurfaceMediaSourceTest class contains tests that fill the buffers +// using the cpu calls +// SurfaceMediaSourceGLTest class contains tests that fill the buffers +// using the GL calls. +// TODO: None of the tests actually verify the encoded images.. so at this point, +// these are mostly functionality tests + visual inspection +////////////////////////////////////////////////////////////////////// + +// Just pass one buffer from the native_window to the SurfaceMediaSource +// Dummy Encoder +static int testId = 1; +TEST_F(SurfaceMediaSourceTest, DISABLED_DummyEncodingFromCpuFilledYV12BufferNpotOneBufferPass) { + ALOGV("Test # %d", testId++); + ALOGV("Testing OneBufferPass ******************************"); + + ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(), + HAL_PIXEL_FORMAT_YV12)); + oneBufferPass(mYuvTexWidth, mYuvTexHeight); +} + +// Pass the buffer with the wrong height and weight and should not be accepted +// Dummy Encoder +TEST_F(SurfaceMediaSourceTest, DISABLED_DummyEncodingFromCpuFilledYV12BufferNpotWrongSizeBufferPass) { + ALOGV("Test # %d", testId++); + ALOGV("Testing Wrong size BufferPass ******************************"); + + // setting the client side buffer size different than the server size + ASSERT_EQ(NO_ERROR, native_window_set_buffers_dimensions(mANW.get(), + 10, 10)); + ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(), + HAL_PIXEL_FORMAT_YV12)); + + ANativeWindowBuffer* anb; + + // Note: make sure we get an ERROR back when dequeuing! + ASSERT_NE(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); +} + +// pass multiple buffers from the native_window the SurfaceMediaSource +// Dummy Encoder +TEST_F(SurfaceMediaSourceTest, DISABLED_DummyEncodingFromCpuFilledYV12BufferNpotMultiBufferPass) { + ALOGV("Test # %d", testId++); + ALOGV("Testing MultiBufferPass, Dummy Recorder *********************"); + ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(), + HAL_PIXEL_FORMAT_YV12)); + + SimpleDummyRecorder writer(mSMS); + writer.start(); + + int32_t nFramesCount = 0; + while (nFramesCount < 300) { + oneBufferPass(mYuvTexWidth, mYuvTexHeight); + + ASSERT_EQ(NO_ERROR, writer.readFromSource()); + + nFramesCount++; + } + writer.stop(); +} + +// Delayed pass of multiple buffers from the native_window the SurfaceMediaSource +// Dummy Encoder +TEST_F(SurfaceMediaSourceTest, DummyLagEncodingFromCpuFilledYV12BufferNpotMultiBufferPass) { + ALOGV("Test # %d", testId++); + ALOGV("Testing MultiBufferPass, Dummy Recorder Lagging **************"); + + ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(), + HAL_PIXEL_FORMAT_YV12)); + + SimpleDummyRecorder writer(mSMS); + writer.start(); + + int32_t nFramesCount = 1; + const int FRAMES_LAG = SurfaceMediaSource::MIN_UNDEQUEUED_BUFFERS; + + while (nFramesCount <= 300) { + ALOGV("Frame: %d", nFramesCount); + oneBufferPass(mYuvTexWidth, mYuvTexHeight); + // Forcing the writer to lag behind a few frames + if (nFramesCount > FRAMES_LAG) { + ASSERT_EQ(NO_ERROR, writer.readFromSource()); + } + nFramesCount++; + } + writer.stop(); +} + +// pass multiple buffers from the native_window the SurfaceMediaSource +// A dummy writer (MULTITHREADED) is used to simulate actual MPEG4Writer +TEST_F(SurfaceMediaSourceTest, DummyThreadedEncodingFromCpuFilledYV12BufferNpotMultiBufferPass) { + ALOGV("Test # %d", testId++); + ALOGV("Testing MultiBufferPass, Dummy Recorder Multi-Threaded **********"); + ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(), + HAL_PIXEL_FORMAT_YV12)); + + DummyRecorder writer(mSMS); + writer.start(); + + int32_t nFramesCount = 0; + while (nFramesCount <= 300) { + ALOGV("Frame: %d", nFramesCount); + oneBufferPass(mYuvTexWidth, mYuvTexHeight); + + nFramesCount++; + } + writer.stop(); +} + +// Test to examine actual encoding using mediarecorder +// We use the mediaserver to create a mediarecorder and send +// it back to us. So SurfaceMediaSource lives in the same process +// as the mediaserver. +// Very close to the actual camera, except that the +// buffers are filled and queueud by the CPU instead of GL. +TEST_F(SurfaceMediaSourceTest, DISABLED_EncodingFromCpuYV12BufferNpotWriteMediaServer) { + ALOGV("Test # %d", testId++); + ALOGV("************** Testing the whole pipeline with actual MediaRecorder ***********"); + ALOGV("************** SurfaceMediaSource is same process as mediaserver ***********"); + + const char *fileName = "/sdcard/outputSurfEncMSource.mp4"; + int fd = open(fileName, O_RDWR | O_CREAT, 0744); + if (fd < 0) { + ALOGE("ERROR: Could not open the the file %s, fd = %d !!", fileName, fd); + } + CHECK(fd >= 0); + + sp<MediaRecorder> mr = SurfaceMediaSourceGLTest::setUpMediaRecorder(fd, + VIDEO_SOURCE_SURFACE, OUTPUT_FORMAT_MPEG_4, VIDEO_ENCODER_H264, + mYuvTexWidth, mYuvTexHeight, 30); + // get the reference to the surfacemediasource living in + // mediaserver that is created by stagefrightrecorder + sp<IGraphicBufferProducer> iST = mr->querySurfaceMediaSourceFromMediaServer(); + mSTC = new Surface(iST); + mANW = mSTC; + ASSERT_EQ(NO_ERROR, native_window_api_connect(mANW.get(), NATIVE_WINDOW_API_CPU)); + ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(), + HAL_PIXEL_FORMAT_YV12)); + + int32_t nFramesCount = 0; + while (nFramesCount <= 300) { + oneBufferPassNoFill(mYuvTexWidth, mYuvTexHeight); + nFramesCount++; + ALOGV("framesCount = %d", nFramesCount); + } + + ASSERT_EQ(NO_ERROR, native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU)); + ALOGV("Stopping MediaRecorder..."); + CHECK_EQ((status_t)OK, mr->stop()); + mr.clear(); + close(fd); +} + +////////////////////////////////////////////////////////////////////// +// GL tests +///////////////////////////////////////////////////////////////////// + +// Test to examine whether we can choose the Recordable Android GLConfig +// DummyRecorder used- no real encoding here +TEST_F(SurfaceMediaSourceGLTest, ChooseAndroidRecordableEGLConfigDummyWriter) { + ALOGV("Test # %d", testId++); + ALOGV("Verify creating a surface w/ right config + dummy writer*********"); + + mSMS = new SurfaceMediaSource(mYuvTexWidth, mYuvTexHeight); + mSTC = new Surface(mSMS->getProducer()); + mANW = mSTC; + + DummyRecorder writer(mSMS); + writer.start(); + + if (mEglSurface != EGL_NO_SURFACE) { + EXPECT_TRUE(eglDestroySurface(mEglDisplay, mEglSurface)); + mEglSurface = EGL_NO_SURFACE; + } + + mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, + mANW.get(), NULL); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_SURFACE, mEglSurface) ; + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + int32_t nFramesCount = 0; + while (nFramesCount <= 300) { + oneBufferPassGL(); + nFramesCount++; + ALOGV("framesCount = %d", nFramesCount); + } + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + eglDestroySurface(mEglDisplay, mEglSurface); + mEglSurface = EGL_NO_SURFACE; + + writer.stop(); +} +// Test to examine whether we can render GL buffers in to the surface +// created with the native window handle +TEST_F(SurfaceMediaSourceGLTest, RenderingToRecordableEGLSurfaceWorks) { + ALOGV("Test # %d", testId++); + ALOGV("RenderingToRecordableEGLSurfaceWorks *********************"); + // Do the producer side of things + glClearColor(0.6, 0.6, 0.6, 0.6); + glClear(GL_COLOR_BUFFER_BIT); + + glEnable(GL_SCISSOR_TEST); + glScissor(4, 4, 4, 4); + glClearColor(1.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glScissor(24, 48, 4, 4); + glClearColor(0.0, 1.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glScissor(37, 17, 4, 4); + glClearColor(0.0, 0.0, 1.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(63, 63, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 0, 63, 153, 153, 153, 153)); + + EXPECT_TRUE(checkPixel( 4, 7, 255, 0, 0, 255)); + EXPECT_TRUE(checkPixel(25, 51, 0, 255, 0, 255)); + EXPECT_TRUE(checkPixel(40, 19, 0, 0, 255, 255)); + EXPECT_TRUE(checkPixel(29, 51, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 5, 32, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(13, 8, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(46, 3, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(30, 33, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 6, 52, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(55, 33, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(16, 29, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 1, 30, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(41, 37, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(46, 29, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(15, 25, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 3, 52, 153, 153, 153, 153)); +} + +// Test to examine the actual encoding with GL buffers +// Actual encoder, Actual GL Buffers Filled SurfaceMediaSource +// The same pattern is rendered every frame +TEST_F(SurfaceMediaSourceGLTest, EncodingFromGLRgbaSameImageEachBufNpotWrite) { + ALOGV("Test # %d", testId++); + ALOGV("************** Testing the whole pipeline with actual Recorder ***********"); + ALOGV("************** GL Filling the buffers ***********"); + // Note: No need to set the colorformat for the buffers. The colorformat is + // in the GRAlloc buffers itself. + + const char *fileName = "/sdcard/outputSurfEncMSourceGL.mp4"; + int fd = open(fileName, O_RDWR | O_CREAT, 0744); + if (fd < 0) { + ALOGE("ERROR: Could not open the the file %s, fd = %d !!", fileName, fd); + } + CHECK(fd >= 0); + + sp<MediaRecorder> mr = setUpMediaRecorder(fd, VIDEO_SOURCE_SURFACE, + OUTPUT_FORMAT_MPEG_4, VIDEO_ENCODER_H264, mYuvTexWidth, mYuvTexHeight, 30); + + // get the reference to the surfacemediasource living in + // mediaserver that is created by stagefrightrecorder + setUpEGLSurfaceFromMediaRecorder(mr); + + int32_t nFramesCount = 0; + while (nFramesCount <= 300) { + oneBufferPassGL(); + nFramesCount++; + ALOGV("framesCount = %d", nFramesCount); + } + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + eglDestroySurface(mEglDisplay, mEglSurface); + mEglSurface = EGL_NO_SURFACE; + + ALOGV("Stopping MediaRecorder..."); + CHECK_EQ((status_t)OK, mr->stop()); + mr.clear(); + close(fd); +} + +// Test to examine the actual encoding from the GL Buffers +// Actual encoder, Actual GL Buffers Filled SurfaceMediaSource +// A different pattern is rendered every frame +TEST_F(SurfaceMediaSourceGLTest, EncodingFromGLRgbaDiffImageEachBufNpotWrite) { + ALOGV("Test # %d", testId++); + ALOGV("************** Testing the whole pipeline with actual Recorder ***********"); + ALOGV("************** Diff GL Filling the buffers ***********"); + // Note: No need to set the colorformat for the buffers. The colorformat is + // in the GRAlloc buffers itself. + + const char *fileName = "/sdcard/outputSurfEncMSourceGLDiff.mp4"; + int fd = open(fileName, O_RDWR | O_CREAT, 0744); + if (fd < 0) { + ALOGE("ERROR: Could not open the the file %s, fd = %d !!", fileName, fd); + } + CHECK(fd >= 0); + + sp<MediaRecorder> mr = setUpMediaRecorder(fd, VIDEO_SOURCE_SURFACE, + OUTPUT_FORMAT_MPEG_4, VIDEO_ENCODER_H264, mYuvTexWidth, mYuvTexHeight, 30); + + // get the reference to the surfacemediasource living in + // mediaserver that is created by stagefrightrecorder + setUpEGLSurfaceFromMediaRecorder(mr); + + int32_t nFramesCount = 0; + while (nFramesCount <= 300) { + oneBufferPassGL(nFramesCount); + nFramesCount++; + ALOGV("framesCount = %d", nFramesCount); + } + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + eglDestroySurface(mEglDisplay, mEglSurface); + mEglSurface = EGL_NO_SURFACE; + + ALOGV("Stopping MediaRecorder..."); + CHECK_EQ((status_t)OK, mr->stop()); + mr.clear(); + close(fd); +} +} // namespace android |