diff --git a/include/media/IHDCP.h b/include/media/IHDCP.h
new file mode 120000
index 0000000000..9d4568eafd
--- /dev/null
+++ b/include/media/IHDCP.h
@@ -0,0 +1 @@
+../../media/libmedia/include/media/IHDCP.h
\ No newline at end of file
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index feca03dde9..6bab8a0510 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -2326,7 +2326,7 @@ PipelineWatcher::Clock::duration CCodecBufferChannel::elapsed() {
size_t outputDelay = mOutput.lock()->outputDelay;
{
Mutexed::Locked input(mInput);
- n = input->inputDelay + input->pipelineDelay + outputDelay;
+ n = input->inputDelay + input->pipelineDelay + outputDelay + kSmoothnessFactor;
}
return mPipelineWatcher.lock()->elapsed(PipelineWatcher::Clock::now(), n);
}
diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp
index 590a7b7b98..53794f4da6 100644
--- a/media/libmedia/Android.bp
+++ b/media/libmedia/Android.bp
@@ -311,6 +311,7 @@ cc_library {
srcs: [
":mediaextractorservice_aidl",
"IDataSource.cpp",
+ "IHDCP.cpp",
"BufferingSettings.cpp",
"mediaplayer.cpp",
"IMediaHTTPConnection.cpp",
@@ -374,6 +375,7 @@ cc_library {
"libcamera_client",
"libstagefright_foundation",
"libgui",
+ "libui",
"libdl",
"libaudioclient",
"libmedia_codeclist",
diff --git a/media/libmedia/IHDCP.cpp b/media/libmedia/IHDCP.cpp
new file mode 100644
index 0000000000..31e37535c2
--- /dev/null
+++ b/media/libmedia/IHDCP.cpp
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2012 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 "IHDCP"
+#include
+
+#include
+#include
+#include
+#include
+
+namespace android {
+
+enum {
+ OBSERVER_NOTIFY = IBinder::FIRST_CALL_TRANSACTION,
+ HDCP_SET_OBSERVER,
+ HDCP_INIT_ASYNC,
+ HDCP_SHUTDOWN_ASYNC,
+ HDCP_GET_CAPS,
+ HDCP_ENCRYPT,
+ HDCP_ENCRYPT_NATIVE,
+ HDCP_DECRYPT,
+};
+
+struct BpHDCPObserver : public BpInterface {
+ explicit BpHDCPObserver(const sp &impl)
+ : BpInterface(impl) {
+ }
+
+ virtual void notify(
+ int msg, int ext1, int ext2, const Parcel *obj) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IHDCPObserver::getInterfaceDescriptor());
+ data.writeInt32(msg);
+ data.writeInt32(ext1);
+ data.writeInt32(ext2);
+ if (obj && obj->dataSize() > 0) {
+ data.appendFrom(const_cast(obj), 0, obj->dataSize());
+ }
+ remote()->transact(OBSERVER_NOTIFY, data, &reply, IBinder::FLAG_ONEWAY);
+ }
+};
+
+DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(HDCPObserver, "android.hardware.IHDCPObserver");
+
+struct BpHDCP : public BpInterface {
+ explicit BpHDCP(const sp &impl)
+ : BpInterface(impl) {
+ }
+
+ virtual status_t setObserver(const sp &observer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IHDCP::getInterfaceDescriptor());
+ data.writeStrongBinder(IInterface::asBinder(observer));
+ remote()->transact(HDCP_SET_OBSERVER, data, &reply);
+ return reply.readInt32();
+ }
+
+ virtual status_t initAsync(const char *host, unsigned port) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IHDCP::getInterfaceDescriptor());
+ data.writeCString(host);
+ data.writeInt32(port);
+ remote()->transact(HDCP_INIT_ASYNC, data, &reply);
+ return reply.readInt32();
+ }
+
+ virtual status_t shutdownAsync() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IHDCP::getInterfaceDescriptor());
+ remote()->transact(HDCP_SHUTDOWN_ASYNC, data, &reply);
+ return reply.readInt32();
+ }
+
+ virtual uint32_t getCaps() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IHDCP::getInterfaceDescriptor());
+ remote()->transact(HDCP_GET_CAPS, data, &reply);
+ return reply.readInt32();
+ }
+
+ virtual status_t encrypt(
+ const void *inData, size_t size, uint32_t streamCTR,
+ uint64_t *outInputCTR, void *outData) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IHDCP::getInterfaceDescriptor());
+ data.writeInt32(size);
+ data.write(inData, size);
+ data.writeInt32(streamCTR);
+ remote()->transact(HDCP_ENCRYPT, data, &reply);
+
+ status_t err = reply.readInt32();
+
+ if (err != OK) {
+ *outInputCTR = 0;
+
+ return err;
+ }
+
+ *outInputCTR = reply.readInt64();
+ reply.read(outData, size);
+
+ return err;
+ }
+
+ virtual status_t encryptNative(
+ const sp &graphicBuffer,
+ size_t offset, size_t size, uint32_t streamCTR,
+ uint64_t *outInputCTR, void *outData) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IHDCP::getInterfaceDescriptor());
+ data.write(*graphicBuffer);
+ data.writeInt32(offset);
+ data.writeInt32(size);
+ data.writeInt32(streamCTR);
+ remote()->transact(HDCP_ENCRYPT_NATIVE, data, &reply);
+
+ status_t err = reply.readInt32();
+
+ if (err != OK) {
+ *outInputCTR = 0;
+ return err;
+ }
+
+ *outInputCTR = reply.readInt64();
+ reply.read(outData, size);
+
+ return err;
+ }
+
+ virtual status_t decrypt(
+ const void *inData, size_t size,
+ uint32_t streamCTR, uint64_t inputCTR,
+ void *outData) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IHDCP::getInterfaceDescriptor());
+ data.writeInt32(size);
+ data.write(inData, size);
+ data.writeInt32(streamCTR);
+ data.writeInt64(inputCTR);
+ remote()->transact(HDCP_DECRYPT, data, &reply);
+
+ status_t err = reply.readInt32();
+
+ if (err != OK) {
+ return err;
+ }
+
+ reply.read(outData, size);
+
+ return err;
+ }
+};
+
+DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(HDCP, "android.hardware.IHDCP");
+
+status_t BnHDCPObserver::onTransact(
+ uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
+ switch (code) {
+ case OBSERVER_NOTIFY:
+ {
+ CHECK_INTERFACE(IHDCPObserver, data, reply);
+
+ int msg = data.readInt32();
+ int ext1 = data.readInt32();
+ int ext2 = data.readInt32();
+
+ Parcel obj;
+ if (data.dataAvail() > 0) {
+ obj.appendFrom(
+ const_cast(&data),
+ data.dataPosition(),
+ data.dataAvail());
+ }
+
+ notify(msg, ext1, ext2, &obj);
+
+ return OK;
+ }
+
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+status_t BnHDCP::onTransact(
+ uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
+ switch (code) {
+ case HDCP_SET_OBSERVER:
+ {
+ CHECK_INTERFACE(IHDCP, data, reply);
+
+ sp observer =
+ interface_cast(data.readStrongBinder());
+
+ reply->writeInt32(setObserver(observer));
+ return OK;
+ }
+
+ case HDCP_INIT_ASYNC:
+ {
+ CHECK_INTERFACE(IHDCP, data, reply);
+
+ const char *host = data.readCString();
+ unsigned port = data.readInt32();
+
+ reply->writeInt32(initAsync(host, port));
+ return OK;
+ }
+
+ case HDCP_SHUTDOWN_ASYNC:
+ {
+ CHECK_INTERFACE(IHDCP, data, reply);
+
+ reply->writeInt32(shutdownAsync());
+ return OK;
+ }
+
+ case HDCP_GET_CAPS:
+ {
+ CHECK_INTERFACE(IHDCP, data, reply);
+
+ reply->writeInt32(getCaps());
+ return OK;
+ }
+
+ case HDCP_ENCRYPT:
+ {
+ CHECK_INTERFACE(IHDCP, data, reply);
+
+ size_t size = data.readInt32();
+ void *inData = NULL;
+ // watch out for overflow
+ if (size <= SIZE_MAX / 2) {
+ inData = malloc(2 * size);
+ }
+ if (inData == NULL) {
+ reply->writeInt32(ERROR_OUT_OF_RANGE);
+ return OK;
+ }
+
+ void *outData = (uint8_t *)inData + size;
+
+ status_t err = data.read(inData, size);
+ if (err != OK) {
+ free(inData);
+ reply->writeInt32(err);
+ return OK;
+ }
+
+ uint32_t streamCTR = data.readInt32();
+ uint64_t inputCTR;
+ err = encrypt(inData, size, streamCTR, &inputCTR, outData);
+
+ reply->writeInt32(err);
+
+ if (err == OK) {
+ reply->writeInt64(inputCTR);
+ reply->write(outData, size);
+ }
+
+ free(inData);
+ inData = outData = NULL;
+
+ return OK;
+ }
+
+ case HDCP_ENCRYPT_NATIVE:
+ {
+ CHECK_INTERFACE(IHDCP, data, reply);
+
+ sp graphicBuffer = new GraphicBuffer();
+ data.read(*graphicBuffer);
+ size_t offset = data.readInt32();
+ size_t size = data.readInt32();
+ uint32_t streamCTR = data.readInt32();
+ void *outData = NULL;
+ uint64_t inputCTR;
+
+ status_t err = ERROR_OUT_OF_RANGE;
+
+ outData = malloc(size);
+
+ if (outData != NULL) {
+ err = encryptNative(graphicBuffer, offset, size,
+ streamCTR, &inputCTR, outData);
+ }
+
+ reply->writeInt32(err);
+
+ if (err == OK) {
+ reply->writeInt64(inputCTR);
+ reply->write(outData, size);
+ }
+
+ free(outData);
+ outData = NULL;
+
+ return OK;
+ }
+
+ case HDCP_DECRYPT:
+ {
+ CHECK_INTERFACE(IHDCP, data, reply);
+
+ size_t size = data.readInt32();
+ size_t bufSize = 2 * size;
+
+ // watch out for overflow
+ void *inData = NULL;
+ if (bufSize > size) {
+ inData = malloc(bufSize);
+ }
+
+ if (inData == NULL) {
+ reply->writeInt32(ERROR_OUT_OF_RANGE);
+ return OK;
+ }
+
+ void *outData = (uint8_t *)inData + size;
+
+ data.read(inData, size);
+
+ uint32_t streamCTR = data.readInt32();
+ uint64_t inputCTR = data.readInt64();
+ status_t err = decrypt(inData, size, streamCTR, inputCTR, outData);
+
+ reply->writeInt32(err);
+
+ if (err == OK) {
+ reply->write(outData, size);
+ }
+
+ free(inData);
+ inData = outData = NULL;
+
+ return OK;
+ }
+
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+} // namespace android
diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp
index 07c0ac5979..f8326b607a 100644
--- a/media/libmedia/IMediaPlayerService.cpp
+++ b/media/libmedia/IMediaPlayerService.cpp
@@ -20,6 +20,7 @@
#include
#include
+#include
#include
#include
#include
@@ -41,6 +42,7 @@ enum {
CREATE = IBinder::FIRST_CALL_TRANSACTION,
CREATE_MEDIA_RECORDER,
CREATE_METADATA_RETRIEVER,
+ MAKE_HDCP,
ADD_BATTERY_DATA,
PULL_BATTERY_DATA,
LISTEN_FOR_REMOTE_DISPLAY,
@@ -85,6 +87,14 @@ class BpMediaPlayerService: public BpInterface
return interface_cast(reply.readStrongBinder());
}
+ virtual sp makeHDCP(bool createEncryptionModule) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
+ data.writeInt32(createEncryptionModule);
+ remote()->transact(MAKE_HDCP, data, &reply);
+ return interface_cast(reply.readStrongBinder());
+ }
+
virtual void addBatteryData(uint32_t params) {
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
@@ -157,6 +167,13 @@ status_t BnMediaPlayerService::onTransact(
reply->writeStrongBinder(IInterface::asBinder(retriever));
return NO_ERROR;
} break;
+ case MAKE_HDCP: {
+ CHECK_INTERFACE(IMediaPlayerService, data, reply);
+ bool createEncryptionModule = data.readInt32();
+ sp hdcp = makeHDCP(createEncryptionModule);
+ reply->writeStrongBinder(IInterface::asBinder(hdcp));
+ return NO_ERROR;
+ } break;
case ADD_BATTERY_DATA: {
CHECK_INTERFACE(IMediaPlayerService, data, reply);
uint32_t params = data.readInt32();
diff --git a/media/libmedia/include/media/IHDCP.h b/media/libmedia/include/media/IHDCP.h
new file mode 100644
index 0000000000..352561ecbb
--- /dev/null
+++ b/media/libmedia/include/media/IHDCP.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include
+#include
+#include
+#include
+
+namespace android {
+
+struct IHDCPObserver : public IInterface {
+ DECLARE_META_INTERFACE(HDCPObserver);
+
+ virtual void notify(
+ int msg, int ext1, int ext2, const Parcel *obj) = 0;
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(IHDCPObserver);
+};
+
+struct IHDCP : public IInterface {
+ DECLARE_META_INTERFACE(HDCP);
+
+ // Called to specify the observer that receives asynchronous notifications
+ // from the HDCP implementation to signal completion/failure of asynchronous
+ // operations (such as initialization) or out of band events.
+ virtual status_t setObserver(const sp &observer) = 0;
+
+ // Request to setup an HDCP session with the specified host listening
+ // on the specified port.
+ virtual status_t initAsync(const char *host, unsigned port) = 0;
+
+ // Request to shutdown the active HDCP session.
+ virtual status_t shutdownAsync() = 0;
+
+ // Returns the capability bitmask of this HDCP session.
+ // Possible return values (please refer to HDCAPAPI.h):
+ // HDCP_CAPS_ENCRYPT: mandatory, meaning the HDCP module can encrypt
+ // from an input byte-array buffer to an output byte-array buffer
+ // HDCP_CAPS_ENCRYPT_NATIVE: the HDCP module supports encryption from
+ // a native buffer to an output byte-array buffer. The format of the
+ // input native buffer is specific to vendor's encoder implementation.
+ // It is the same format as that used by the encoder when
+ // "storeMetaDataInBuffers" extension is enabled on its output port.
+ virtual uint32_t getCaps() = 0;
+
+ // ENCRYPTION only:
+ // Encrypt data according to the HDCP spec. "size" bytes of data are
+ // available at "inData" (virtual address), "size" may not be a multiple
+ // of 128 bits (16 bytes). An equal number of encrypted bytes should be
+ // written to the buffer at "outData" (virtual address).
+ // This operation is to be synchronous, i.e. this call does not return
+ // until outData contains size bytes of encrypted data.
+ // streamCTR will be assigned by the caller (to 0 for the first PES stream,
+ // 1 for the second and so on)
+ // inputCTR _will_be_maintained_by_the_callee_ for each PES stream.
+ virtual status_t encrypt(
+ const void *inData, size_t size, uint32_t streamCTR,
+ uint64_t *outInputCTR, void *outData) = 0;
+
+ // Encrypt data according to the HDCP spec. "size" bytes of data starting
+ // at location "offset" are available in "buffer" (buffer handle). "size"
+ // may not be a multiple of 128 bits (16 bytes). An equal number of
+ // encrypted bytes should be written to the buffer at "outData" (virtual
+ // address). This operation is to be synchronous, i.e. this call does not
+ // return until outData contains size bytes of encrypted data.
+ // streamCTR will be assigned by the caller (to 0 for the first PES stream,
+ // 1 for the second and so on)
+ // inputCTR _will_be_maintained_by_the_callee_ for each PES stream.
+ virtual status_t encryptNative(
+ const sp &graphicBuffer,
+ size_t offset, size_t size, uint32_t streamCTR,
+ uint64_t *outInputCTR, void *outData) = 0;
+
+ // DECRYPTION only:
+ // Decrypt data according to the HDCP spec.
+ // "size" bytes of encrypted data are available at "inData"
+ // (virtual address), "size" may not be a multiple of 128 bits (16 bytes).
+ // An equal number of decrypted bytes should be written to the buffer
+ // at "outData" (virtual address).
+ // This operation is to be synchronous, i.e. this call does not return
+ // until outData contains size bytes of decrypted data.
+ // Both streamCTR and inputCTR will be provided by the caller.
+ virtual status_t decrypt(
+ const void *inData, size_t size,
+ uint32_t streamCTR, uint64_t inputCTR,
+ void *outData) = 0;
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(IHDCP);
+};
+
+struct BnHDCPObserver : public BnInterface {
+ virtual status_t onTransact(
+ uint32_t code, const Parcel &data, Parcel *reply,
+ uint32_t flags = 0);
+};
+
+struct BnHDCP : public BnInterface {
+ virtual status_t onTransact(
+ uint32_t code, const Parcel &data, Parcel *reply,
+ uint32_t flags = 0);
+};
+
+} // namespace android
+
+
diff --git a/media/libmedia/include/media/IMediaPlayerService.h b/media/libmedia/include/media/IMediaPlayerService.h
index 6070673cfa..d35472bc28 100644
--- a/media/libmedia/include/media/IMediaPlayerService.h
+++ b/media/libmedia/include/media/IMediaPlayerService.h
@@ -34,6 +34,7 @@
namespace android {
class IMediaPlayer;
+struct IHDCP;
class IMediaCodecList;
struct IMediaHTTPService;
class IMediaRecorder;
@@ -54,6 +55,7 @@ class IMediaPlayerService: public IInterface
audio_session_t audioSessionId = AUDIO_SESSION_ALLOCATE,
const android::content::AttributionSourceState &attributionSource =
android::content::AttributionSourceState()) = 0;
+ virtual sp makeHDCP(bool createEncryptionModule) = 0;
virtual sp getCodecList() const = 0;
// Connects to a remote display.
diff --git a/media/libmediaplayerservice/Android.bp b/media/libmediaplayerservice/Android.bp
index 266cb17356..77f1552829 100644
--- a/media/libmediaplayerservice/Android.bp
+++ b/media/libmediaplayerservice/Android.bp
@@ -22,11 +22,13 @@ cc_library {
srcs: [
"ActivityManager.cpp",
"DeathNotifier.cpp",
+ "HDCP.cpp",
"MediaPlayerFactory.cpp",
"MediaPlayerService.cpp",
"MediaRecorderClient.cpp",
"MetadataRetrieverClient.cpp",
"StagefrightMetadataRetriever.cpp",
+ "RemoteDisplay.cpp",
"StagefrightRecorder.cpp",
"TestPlayerStub.cpp",
],
@@ -62,6 +64,7 @@ cc_library {
"libnetd_client",
"libpowermanager",
"libstagefright",
+ "libstagefright_wfd",
"libstagefright_foundation",
"libstagefright_httplive",
"libutils",
@@ -92,6 +95,10 @@ cc_library {
"libmediautils_headers",
],
+ include_dirs: [
+ "frameworks/av/media/libstagefright/wifi-display",
+ ],
+
local_include_dirs: ["include"],
export_include_dirs: [
diff --git a/media/libmediaplayerservice/HDCP.cpp b/media/libmediaplayerservice/HDCP.cpp
new file mode 100644
index 0000000000..afe39367fb
--- /dev/null
+++ b/media/libmediaplayerservice/HDCP.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2012 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 "HDCP"
+#include
+
+#include "HDCP.h"
+
+#include
+
+#include
+
+namespace android {
+
+HDCP::HDCP(bool createEncryptionModule)
+ : mIsEncryptionModule(createEncryptionModule),
+ mLibHandle(NULL),
+ mHDCPModule(NULL) {
+ mLibHandle = dlopen("libstagefright_hdcp.so", RTLD_NOW);
+
+ if (mLibHandle == NULL) {
+ ALOGE("Unable to locate libstagefright_hdcp.so");
+ return;
+ }
+
+ typedef HDCPModule *(*CreateHDCPModuleFunc)(
+ void *, HDCPModule::ObserverFunc);
+
+ CreateHDCPModuleFunc createHDCPModule =
+ mIsEncryptionModule
+ ? (CreateHDCPModuleFunc)dlsym(mLibHandle, "createHDCPModule")
+ : (CreateHDCPModuleFunc)dlsym(
+ mLibHandle, "createHDCPModuleForDecryption");
+
+ if (createHDCPModule == NULL) {
+ ALOGE("Unable to find symbol 'createHDCPModule'.");
+ } else if ((mHDCPModule = createHDCPModule(
+ this, &HDCP::ObserveWrapper)) == NULL) {
+ ALOGE("createHDCPModule failed.");
+ }
+}
+
+HDCP::~HDCP() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mHDCPModule != NULL) {
+ delete mHDCPModule;
+ mHDCPModule = NULL;
+ }
+
+ if (mLibHandle != NULL) {
+ dlclose(mLibHandle);
+ mLibHandle = NULL;
+ }
+}
+
+status_t HDCP::setObserver(const sp &observer) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mHDCPModule == NULL) {
+ return NO_INIT;
+ }
+
+ mObserver = observer;
+
+ return OK;
+}
+
+status_t HDCP::initAsync(const char *host, unsigned port) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mHDCPModule == NULL) {
+ return NO_INIT;
+ }
+
+ return mHDCPModule->initAsync(host, port);
+}
+
+status_t HDCP::shutdownAsync() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mHDCPModule == NULL) {
+ return NO_INIT;
+ }
+
+ return mHDCPModule->shutdownAsync();
+}
+
+uint32_t HDCP::getCaps() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mHDCPModule == NULL) {
+ return NO_INIT;
+ }
+
+ return mHDCPModule->getCaps();
+}
+
+status_t HDCP::encrypt(
+ const void *inData, size_t size, uint32_t streamCTR,
+ uint64_t *outInputCTR, void *outData) {
+ Mutex::Autolock autoLock(mLock);
+
+ CHECK(mIsEncryptionModule);
+
+ if (mHDCPModule == NULL) {
+ *outInputCTR = 0;
+
+ return NO_INIT;
+ }
+
+ return mHDCPModule->encrypt(inData, size, streamCTR, outInputCTR, outData);
+}
+
+status_t HDCP::encryptNative(
+ const sp &graphicBuffer,
+ size_t offset, size_t size, uint32_t streamCTR,
+ uint64_t *outInputCTR, void *outData) {
+ Mutex::Autolock autoLock(mLock);
+
+ CHECK(mIsEncryptionModule);
+
+ if (mHDCPModule == NULL) {
+ *outInputCTR = 0;
+
+ return NO_INIT;
+ }
+
+ return mHDCPModule->encryptNative(graphicBuffer->handle,
+ offset, size, streamCTR, outInputCTR, outData);
+}
+
+status_t HDCP::decrypt(
+ const void *inData, size_t size,
+ uint32_t streamCTR, uint64_t outInputCTR, void *outData) {
+ Mutex::Autolock autoLock(mLock);
+
+ CHECK(!mIsEncryptionModule);
+
+ if (mHDCPModule == NULL) {
+ return NO_INIT;
+ }
+
+ return mHDCPModule->decrypt(inData, size, streamCTR, outInputCTR, outData);
+}
+
+// static
+void HDCP::ObserveWrapper(void *me, int msg, int ext1, int ext2) {
+ static_cast(me)->observe(msg, ext1, ext2);
+}
+
+void HDCP::observe(int msg, int ext1, int ext2) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mObserver != NULL) {
+ mObserver->notify(msg, ext1, ext2, NULL /* obj */);
+ }
+}
+
+} // namespace android
+
diff --git a/media/libmediaplayerservice/HDCP.h b/media/libmediaplayerservice/HDCP.h
new file mode 100644
index 0000000000..83c61b56c0
--- /dev/null
+++ b/media/libmediaplayerservice/HDCP.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 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 HDCP_H_
+
+#define HDCP_H_
+
+#include
+#include
+
+namespace android {
+
+struct HDCP : public BnHDCP {
+ explicit HDCP(bool createEncryptionModule);
+ virtual ~HDCP();
+
+ virtual status_t setObserver(const sp &observer);
+ virtual status_t initAsync(const char *host, unsigned port);
+ virtual status_t shutdownAsync();
+ virtual uint32_t getCaps();
+
+ virtual status_t encrypt(
+ const void *inData, size_t size, uint32_t streamCTR,
+ uint64_t *outInputCTR, void *outData);
+
+ virtual status_t encryptNative(
+ const sp &graphicBuffer,
+ size_t offset, size_t size, uint32_t streamCTR,
+ uint64_t *outInputCTR, void *outData);
+
+ virtual status_t decrypt(
+ const void *inData, size_t size,
+ uint32_t streamCTR, uint64_t outInputCTR, void *outData);
+
+private:
+ Mutex mLock;
+
+ bool mIsEncryptionModule;
+
+ void *mLibHandle;
+ HDCPModule *mHDCPModule;
+ sp mObserver;
+
+ static void ObserveWrapper(void *me, int msg, int ext1, int ext2);
+ void observe(int msg, int ext1, int ext2);
+
+ DISALLOW_EVIL_CONSTRUCTORS(HDCP);
+};
+
+} // namespace android
+
+#endif // HDCP_H_
+
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 9e9e9d842a..14bf40c2c8 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -84,6 +84,8 @@
#include "TestPlayerStub.h"
#include
+#include "HDCP.h"
+#include "RemoteDisplay.h"
static const int kDumpLockRetries = 50;
static const int kDumpLockSleepUs = 20000;
@@ -519,13 +521,18 @@ sp MediaPlayerService::getCodecList() const {
return MediaCodecList::getLocalInstance();
}
+sp MediaPlayerService::makeHDCP(bool createEncryptionModule) {
+ return new HDCP(createEncryptionModule);
+}
+
sp MediaPlayerService::listenForRemoteDisplay(
- const String16 &/*opPackageName*/,
- const sp& /*client*/,
- const String8& /*iface*/) {
- ALOGE("listenForRemoteDisplay is no longer supported!");
+ const String16 &opPackageName,
+ const sp& client, const String8& iface) {
+ if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) {
+ return NULL;
+ }
- return NULL;
+ return new RemoteDisplay(opPackageName, client, iface.string());
}
status_t MediaPlayerService::AudioOutput::dump(int fd, const Vector& args) const
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index 52c2f79504..1f33530e34 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -251,6 +251,7 @@ class MediaPlayerService : public BnMediaPlayerService
const AttributionSourceState& attributionSource);
virtual sp getCodecList() const;
+ virtual sp makeHDCP(bool createEncryptionModule);
virtual sp listenForRemoteDisplay(const String16 &opPackageName,
const sp& client, const String8& iface);
diff --git a/media/libmediaplayerservice/RemoteDisplay.cpp b/media/libmediaplayerservice/RemoteDisplay.cpp
new file mode 100644
index 0000000000..80cca265f3
--- /dev/null
+++ b/media/libmediaplayerservice/RemoteDisplay.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012, 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.
+ */
+
+#include "RemoteDisplay.h"
+
+#include "source/WifiDisplaySource.h"
+
+#include
+#include
+#include
+#include
+
+namespace android {
+
+RemoteDisplay::RemoteDisplay(
+ const String16 &opPackageName,
+ const sp &client,
+ const char *iface)
+ : mLooper(new ALooper),
+ mNetSession(new ANetworkSession) {
+ mLooper->setName("wfd_looper");
+
+ mSource = new WifiDisplaySource(opPackageName, mNetSession, client);
+ mLooper->registerHandler(mSource);
+
+ mNetSession->start();
+ mLooper->start();
+
+ mSource->start(iface);
+}
+
+RemoteDisplay::~RemoteDisplay() {
+}
+
+status_t RemoteDisplay::pause() {
+ return mSource->pause();
+}
+
+status_t RemoteDisplay::resume() {
+ return mSource->resume();
+}
+
+status_t RemoteDisplay::dispose() {
+ mSource->stop();
+ mSource.clear();
+
+ mLooper->stop();
+ mNetSession->stop();
+
+ return OK;
+}
+
+} // namespace android
diff --git a/media/libmediaplayerservice/RemoteDisplay.h b/media/libmediaplayerservice/RemoteDisplay.h
new file mode 100644
index 0000000000..d4573e9a39
--- /dev/null
+++ b/media/libmediaplayerservice/RemoteDisplay.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012, 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 REMOTE_DISPLAY_H_
+
+#define REMOTE_DISPLAY_H_
+
+#include
+#include
+#include
+#include
+#include
+
+namespace android {
+
+struct ALooper;
+struct ANetworkSession;
+class IRemoteDisplayClient;
+struct WifiDisplaySource;
+
+struct RemoteDisplay : public BnRemoteDisplay {
+ RemoteDisplay(
+ const String16 &opPackageName,
+ const sp &client,
+ const char *iface);
+
+ virtual status_t pause();
+ virtual status_t resume();
+ virtual status_t dispose();
+
+protected:
+ virtual ~RemoteDisplay();
+
+private:
+ sp mNetLooper;
+ sp mLooper;
+ sp mNetSession;
+ sp mSource;
+
+ DISALLOW_EVIL_CONSTRUCTORS(RemoteDisplay);
+};
+
+} // namespace android
+
+#endif // REMOTE_DISPLAY_H_
+
diff --git a/media/libstagefright/ANetworkSession.cpp b/media/libstagefright/ANetworkSession.cpp
new file mode 100644
index 0000000000..e3b77ec99a
--- /dev/null
+++ b/media/libstagefright/ANetworkSession.cpp
@@ -0,0 +1,1400 @@
+/*
+ * Copyright 2012, 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 "NetworkSession"
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace android {
+
+static const size_t kMaxUDPSize = 1500;
+static const int32_t kMaxUDPRetries = 200;
+
+struct ANetworkSession::NetworkThread : public Thread {
+ explicit NetworkThread(ANetworkSession *session);
+
+protected:
+ virtual ~NetworkThread();
+
+private:
+ ANetworkSession *mSession;
+
+ virtual bool threadLoop();
+
+ DISALLOW_EVIL_CONSTRUCTORS(NetworkThread);
+};
+
+struct ANetworkSession::Session : public RefBase {
+ enum Mode {
+ MODE_RTSP,
+ MODE_DATAGRAM,
+ MODE_WEBSOCKET,
+ };
+
+ enum State {
+ CONNECTING,
+ CONNECTED,
+ LISTENING_RTSP,
+ LISTENING_TCP_DGRAMS,
+ DATAGRAM,
+ };
+
+ Session(int32_t sessionID,
+ State state,
+ int s,
+ const sp ¬ify);
+
+ int32_t sessionID() const;
+ int socket() const;
+ sp getNotificationMessage() const;
+
+ bool isRTSPServer() const;
+ bool isTCPDatagramServer() const;
+
+ bool wantsToRead();
+ bool wantsToWrite();
+
+ status_t readMore();
+ status_t writeMore();
+
+ status_t sendRequest(
+ const void *data, ssize_t size, bool timeValid, int64_t timeUs);
+
+ void setMode(Mode mode);
+
+ status_t switchToWebSocketMode();
+
+protected:
+ virtual ~Session();
+
+private:
+ enum {
+ FRAGMENT_FLAG_TIME_VALID = 1,
+ };
+ struct Fragment {
+ uint32_t mFlags;
+ int64_t mTimeUs;
+ sp mBuffer;
+ };
+
+ int32_t mSessionID;
+ State mState;
+ Mode mMode;
+ int mSocket;
+ sp mNotify;
+ bool mSawReceiveFailure, mSawSendFailure;
+ int32_t mUDPRetries;
+
+ List mOutFragments;
+
+ AString mInBuffer;
+
+ int64_t mLastStallReportUs;
+
+ void notifyError(bool send, status_t err, const char *detail);
+ void notify(NotificationReason reason);
+
+ void dumpFragmentStats(const Fragment &frag);
+
+ DISALLOW_EVIL_CONSTRUCTORS(Session);
+};
+////////////////////////////////////////////////////////////////////////////////
+
+ANetworkSession::NetworkThread::NetworkThread(ANetworkSession *session)
+ : mSession(session) {
+}
+
+ANetworkSession::NetworkThread::~NetworkThread() {
+}
+
+bool ANetworkSession::NetworkThread::threadLoop() {
+ mSession->threadLoop();
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ANetworkSession::Session::Session(
+ int32_t sessionID,
+ State state,
+ int s,
+ const sp ¬ify)
+ : mSessionID(sessionID),
+ mState(state),
+ mMode(MODE_DATAGRAM),
+ mSocket(s),
+ mNotify(notify),
+ mSawReceiveFailure(false),
+ mSawSendFailure(false),
+ mUDPRetries(kMaxUDPRetries),
+ mLastStallReportUs(-1ll) {
+ if (mState == CONNECTED) {
+ struct sockaddr_in localAddr;
+ socklen_t localAddrLen = sizeof(localAddr);
+
+ int res = getsockname(
+ mSocket, (struct sockaddr *)&localAddr, &localAddrLen);
+ CHECK_GE(res, 0);
+
+ struct sockaddr_in remoteAddr;
+ socklen_t remoteAddrLen = sizeof(remoteAddr);
+
+ res = getpeername(
+ mSocket, (struct sockaddr *)&remoteAddr, &remoteAddrLen);
+ CHECK_GE(res, 0);
+
+ in_addr_t addr = ntohl(localAddr.sin_addr.s_addr);
+ AString localAddrString = AStringPrintf(
+ "%d.%d.%d.%d",
+ (addr >> 24),
+ (addr >> 16) & 0xff,
+ (addr >> 8) & 0xff,
+ addr & 0xff);
+
+ addr = ntohl(remoteAddr.sin_addr.s_addr);
+ AString remoteAddrString = AStringPrintf(
+ "%d.%d.%d.%d",
+ (addr >> 24),
+ (addr >> 16) & 0xff,
+ (addr >> 8) & 0xff,
+ addr & 0xff);
+
+ sp msg = mNotify->dup();
+ msg->setInt32("sessionID", mSessionID);
+ msg->setInt32("reason", kWhatClientConnected);
+ msg->setString("server-ip", localAddrString.c_str());
+ msg->setInt32("server-port", ntohs(localAddr.sin_port));
+ msg->setString("client-ip", remoteAddrString.c_str());
+ msg->setInt32("client-port", ntohs(remoteAddr.sin_port));
+ msg->post();
+ }
+}
+
+ANetworkSession::Session::~Session() {
+ ALOGV("Session %d gone", mSessionID);
+
+ close(mSocket);
+ mSocket = -1;
+}
+
+int32_t ANetworkSession::Session::sessionID() const {
+ return mSessionID;
+}
+
+int ANetworkSession::Session::socket() const {
+ return mSocket;
+}
+
+void ANetworkSession::Session::setMode(Mode mode) {
+ mMode = mode;
+}
+
+status_t ANetworkSession::Session::switchToWebSocketMode() {
+ if (mState != CONNECTED || mMode != MODE_RTSP) {
+ return INVALID_OPERATION;
+ }
+
+ mMode = MODE_WEBSOCKET;
+
+ return OK;
+}
+
+sp ANetworkSession::Session::getNotificationMessage() const {
+ return mNotify;
+}
+
+bool ANetworkSession::Session::isRTSPServer() const {
+ return mState == LISTENING_RTSP;
+}
+
+bool ANetworkSession::Session::isTCPDatagramServer() const {
+ return mState == LISTENING_TCP_DGRAMS;
+}
+
+bool ANetworkSession::Session::wantsToRead() {
+ return !mSawReceiveFailure && mState != CONNECTING;
+}
+
+bool ANetworkSession::Session::wantsToWrite() {
+ return !mSawSendFailure
+ && (mState == CONNECTING
+ || (mState == CONNECTED && !mOutFragments.empty())
+ || (mState == DATAGRAM && !mOutFragments.empty()));
+}
+
+status_t ANetworkSession::Session::readMore() {
+ if (mState == DATAGRAM) {
+ CHECK_EQ(mMode, MODE_DATAGRAM);
+
+ status_t err;
+ do {
+ sp buf = new ABuffer(kMaxUDPSize);
+
+ struct sockaddr_in remoteAddr;
+ socklen_t remoteAddrLen = sizeof(remoteAddr);
+
+ ssize_t n;
+ do {
+ n = recvfrom(
+ mSocket, buf->data(), buf->capacity(), 0,
+ (struct sockaddr *)&remoteAddr, &remoteAddrLen);
+ } while (n < 0 && errno == EINTR);
+
+ err = OK;
+ if (n < 0) {
+ err = -errno;
+ } else if (n == 0) {
+ err = -ECONNRESET;
+ } else {
+ buf->setRange(0, n);
+
+ int64_t nowUs = ALooper::GetNowUs();
+ buf->meta()->setInt64("arrivalTimeUs", nowUs);
+
+ sp notify = mNotify->dup();
+ notify->setInt32("sessionID", mSessionID);
+ notify->setInt32("reason", kWhatDatagram);
+
+ uint32_t ip = ntohl(remoteAddr.sin_addr.s_addr);
+ notify->setString(
+ "fromAddr",
+ AStringPrintf(
+ "%u.%u.%u.%u",
+ ip >> 24,
+ (ip >> 16) & 0xff,
+ (ip >> 8) & 0xff,
+ ip & 0xff).c_str());
+
+ notify->setInt32("fromPort", ntohs(remoteAddr.sin_port));
+
+ notify->setBuffer("data", buf);
+ notify->post();
+ }
+ } while (err == OK);
+
+ if (err == -EAGAIN) {
+ err = OK;
+ }
+
+ if (err != OK) {
+ if (!mUDPRetries) {
+ notifyError(false /* send */, err, "Recvfrom failed.");
+ mSawReceiveFailure = true;
+ } else {
+ mUDPRetries--;
+ ALOGE("Recvfrom failed, %d/%d retries left",
+ mUDPRetries, kMaxUDPRetries);
+ err = OK;
+ }
+ } else {
+ mUDPRetries = kMaxUDPRetries;
+ }
+
+ return err;
+ }
+
+ char tmp[512];
+ ssize_t n;
+ do {
+ n = recv(mSocket, tmp, sizeof(tmp), 0);
+ } while (n < 0 && errno == EINTR);
+
+ status_t err = OK;
+
+ if (n > 0) {
+ mInBuffer.append(tmp, n);
+
+#if 0
+ ALOGI("in:");
+ hexdump(tmp, n);
+#endif
+ } else if (n < 0) {
+ err = -errno;
+ } else {
+ err = -ECONNRESET;
+ }
+
+ if (mMode == MODE_DATAGRAM) {
+ // TCP stream carrying 16-bit length-prefixed datagrams.
+
+ while (mInBuffer.size() >= 2) {
+ size_t packetSize = U16_AT((const uint8_t *)mInBuffer.c_str());
+
+ if (mInBuffer.size() < packetSize + 2) {
+ break;
+ }
+
+ sp packet = new ABuffer(packetSize);
+ memcpy(packet->data(), mInBuffer.c_str() + 2, packetSize);
+
+ int64_t nowUs = ALooper::GetNowUs();
+ packet->meta()->setInt64("arrivalTimeUs", nowUs);
+
+ sp notify = mNotify->dup();
+ notify->setInt32("sessionID", mSessionID);
+ notify->setInt32("reason", kWhatDatagram);
+ notify->setBuffer("data", packet);
+ notify->post();
+
+ mInBuffer.erase(0, packetSize + 2);
+ }
+ } else if (mMode == MODE_RTSP) {
+ for (;;) {
+ size_t length;
+
+ if (mInBuffer.size() > 0 && mInBuffer.c_str()[0] == '$') {
+ if (mInBuffer.size() < 4) {
+ break;
+ }
+
+ length = U16_AT((const uint8_t *)mInBuffer.c_str() + 2);
+
+ if (mInBuffer.size() < 4 + length) {
+ break;
+ }
+
+ sp notify = mNotify->dup();
+ notify->setInt32("sessionID", mSessionID);
+ notify->setInt32("reason", kWhatBinaryData);
+ notify->setInt32("channel", mInBuffer.c_str()[1]);
+
+ sp data = new ABuffer(length);
+ memcpy(data->data(), mInBuffer.c_str() + 4, length);
+
+ int64_t nowUs = ALooper::GetNowUs();
+ data->meta()->setInt64("arrivalTimeUs", nowUs);
+
+ notify->setBuffer("data", data);
+ notify->post();
+
+ mInBuffer.erase(0, 4 + length);
+ continue;
+ }
+
+ sp msg =
+ ParsedMessage::Parse(
+ mInBuffer.c_str(), mInBuffer.size(), err != OK, &length);
+
+ if (msg == NULL) {
+ break;
+ }
+
+ sp notify = mNotify->dup();
+ notify->setInt32("sessionID", mSessionID);
+ notify->setInt32("reason", kWhatData);
+ notify->setObject("data", msg);
+ notify->post();
+
+#if 1
+ // XXX The (old) dongle sends the wrong content length header on a
+ // SET_PARAMETER request that signals a "wfd_idr_request".
+ // (17 instead of 19).
+ const char *content = msg->getContent();
+ if (content
+ && !memcmp(content, "wfd_idr_request\r\n", 17)
+ && length >= 19
+ && mInBuffer.c_str()[length] == '\r'
+ && mInBuffer.c_str()[length + 1] == '\n') {
+ length += 2;
+ }
+#endif
+
+ mInBuffer.erase(0, length);
+
+ if (err != OK) {
+ break;
+ }
+ }
+ } else {
+ CHECK_EQ(mMode, MODE_WEBSOCKET);
+
+ const uint8_t *data = (const uint8_t *)mInBuffer.c_str();
+ // hexdump(data, mInBuffer.size());
+
+ while (mInBuffer.size() >= 2) {
+ size_t offset = 2;
+
+ uint64_t payloadLen = data[1] & 0x7f;
+ if (payloadLen == 126) {
+ if (offset + 2 > mInBuffer.size()) {
+ break;
+ }
+
+ payloadLen = U16_AT(&data[offset]);
+ offset += 2;
+ } else if (payloadLen == 127) {
+ if (offset + 8 > mInBuffer.size()) {
+ break;
+ }
+
+ payloadLen = U64_AT(&data[offset]);
+ offset += 8;
+ }
+
+ uint32_t mask = 0;
+ if (data[1] & 0x80) {
+ // MASK==1
+ if (offset + 4 > mInBuffer.size()) {
+ break;
+ }
+
+ mask = U32_AT(&data[offset]);
+ offset += 4;
+ }
+
+ if (payloadLen > mInBuffer.size() || offset > mInBuffer.size() - payloadLen) {
+ break;
+ }
+
+ // We have the full message.
+
+ sp packet = new ABuffer(payloadLen);
+ memcpy(packet->data(), &data[offset], payloadLen);
+
+ if (mask != 0) {
+ for (size_t i = 0; i < payloadLen; ++i) {
+ packet->data()[i] =
+ data[offset + i]
+ ^ ((mask >> (8 * (3 - (i % 4)))) & 0xff);
+ }
+ }
+
+ sp notify = mNotify->dup();
+ notify->setInt32("sessionID", mSessionID);
+ notify->setInt32("reason", kWhatWebSocketMessage);
+ notify->setBuffer("data", packet);
+ notify->setInt32("headerByte", data[0]);
+ notify->post();
+
+ mInBuffer.erase(0, offset + payloadLen);
+ }
+ }
+
+ if (err != OK) {
+ notifyError(false /* send */, err, "Recv failed.");
+ mSawReceiveFailure = true;
+ }
+
+ return err;
+}
+
+void ANetworkSession::Session::dumpFragmentStats(const Fragment & /* frag */) {
+#if 0
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayMs = (nowUs - frag.mTimeUs) / 1000ll;
+
+ static const int64_t kMinDelayMs = 0;
+ static const int64_t kMaxDelayMs = 300;
+
+ const char *kPattern = "########################################";
+ size_t kPatternSize = strlen(kPattern);
+
+ int n = (kPatternSize * (delayMs - kMinDelayMs))
+ / (kMaxDelayMs - kMinDelayMs);
+
+ if (n < 0) {
+ n = 0;
+ } else if ((size_t)n > kPatternSize) {
+ n = kPatternSize;
+ }
+
+ ALOGI("[%lld]: (%4lld ms) %s\n",
+ frag.mTimeUs / 1000,
+ delayMs,
+ kPattern + kPatternSize - n);
+#endif
+}
+
+status_t ANetworkSession::Session::writeMore() {
+ if (mState == DATAGRAM) {
+ CHECK(!mOutFragments.empty());
+
+ status_t err;
+ do {
+ const Fragment &frag = *mOutFragments.begin();
+ const sp &datagram = frag.mBuffer;
+
+ int n;
+ do {
+ n = send(mSocket, datagram->data(), datagram->size(), 0);
+ } while (n < 0 && errno == EINTR);
+
+ err = OK;
+
+ if (n > 0) {
+ if (frag.mFlags & FRAGMENT_FLAG_TIME_VALID) {
+ dumpFragmentStats(frag);
+ }
+
+ mOutFragments.erase(mOutFragments.begin());
+ } else if (n < 0) {
+ err = -errno;
+ } else if (n == 0) {
+ err = -ECONNRESET;
+ }
+ } while (err == OK && !mOutFragments.empty());
+
+ if (err == -EAGAIN) {
+ if (!mOutFragments.empty()) {
+ ALOGI("%zu datagrams remain queued.", mOutFragments.size());
+ }
+ err = OK;
+ }
+
+ if (err != OK) {
+ if (!mUDPRetries) {
+ notifyError(true /* send */, err, "Send datagram failed.");
+ mSawSendFailure = true;
+ } else {
+ mUDPRetries--;
+ ALOGE("Send datagram failed, %d/%d retries left",
+ mUDPRetries, kMaxUDPRetries);
+ err = OK;
+ }
+ } else {
+ mUDPRetries = kMaxUDPRetries;
+ }
+
+ return err;
+ }
+
+ if (mState == CONNECTING) {
+ int err;
+ socklen_t optionLen = sizeof(err);
+ CHECK_EQ(getsockopt(mSocket, SOL_SOCKET, SO_ERROR, &err, &optionLen), 0);
+ CHECK_EQ(optionLen, (socklen_t)sizeof(err));
+
+ if (err != 0) {
+ notifyError(kWhatError, -err, "Connection failed");
+ mSawSendFailure = true;
+
+ return -err;
+ }
+
+ mState = CONNECTED;
+ notify(kWhatConnected);
+
+ return OK;
+ }
+
+ CHECK_EQ(mState, CONNECTED);
+ CHECK(!mOutFragments.empty());
+
+ ssize_t n = -1;
+ while (!mOutFragments.empty()) {
+ const Fragment &frag = *mOutFragments.begin();
+
+ do {
+ n = send(mSocket, frag.mBuffer->data(), frag.mBuffer->size(), 0);
+ } while (n < 0 && errno == EINTR);
+
+ if (n <= 0) {
+ break;
+ }
+
+ frag.mBuffer->setRange(
+ frag.mBuffer->offset() + n, frag.mBuffer->size() - n);
+
+ if (frag.mBuffer->size() > 0) {
+ break;
+ }
+
+ if (frag.mFlags & FRAGMENT_FLAG_TIME_VALID) {
+ dumpFragmentStats(frag);
+ }
+
+ mOutFragments.erase(mOutFragments.begin());
+ }
+
+ status_t err = OK;
+
+ if (n < 0) {
+ err = -errno;
+ } else if (n == 0) {
+ err = -ECONNRESET;
+ }
+
+ if (err != OK) {
+ notifyError(true /* send */, err, "Send failed.");
+ mSawSendFailure = true;
+ }
+
+#if 0
+ int numBytesQueued;
+ int res = ioctl(mSocket, SIOCOUTQ, &numBytesQueued);
+ if (res == 0 && numBytesQueued > 50 * 1024) {
+ if (numBytesQueued > 409600) {
+ ALOGW("!!! numBytesQueued = %d", numBytesQueued);
+ }
+
+ int64_t nowUs = ALooper::GetNowUs();
+
+ if (mLastStallReportUs < 0ll
+ || nowUs > mLastStallReportUs + 100000ll) {
+ sp msg = mNotify->dup();
+ msg->setInt32("sessionID", mSessionID);
+ msg->setInt32("reason", kWhatNetworkStall);
+ msg->setSize("numBytesQueued", numBytesQueued);
+ msg->post();
+
+ mLastStallReportUs = nowUs;
+ }
+ }
+#endif
+
+ return err;
+}
+
+status_t ANetworkSession::Session::sendRequest(
+ const void *data, ssize_t size, bool timeValid, int64_t timeUs) {
+ CHECK(mState == CONNECTED || mState == DATAGRAM);
+
+ if (size < 0) {
+ size = strlen((const char *)data);
+ }
+
+ if (size == 0) {
+ return OK;
+ }
+
+ sp buffer;
+
+ if (mState == CONNECTED && mMode == MODE_DATAGRAM) {
+ CHECK_LE(size, 65535);
+
+ buffer = new ABuffer(size + 2);
+ buffer->data()[0] = size >> 8;
+ buffer->data()[1] = size & 0xff;
+ memcpy(buffer->data() + 2, data, size);
+ } else if (mState == CONNECTED && mMode == MODE_WEBSOCKET) {
+ static const bool kUseMask = false; // Chromium doesn't like it.
+
+ size_t numHeaderBytes = 2 + (kUseMask ? 4 : 0);
+ if (size > 65535) {
+ numHeaderBytes += 8;
+ } else if (size > 125) {
+ numHeaderBytes += 2;
+ }
+
+ buffer = new ABuffer(numHeaderBytes + size);
+ buffer->data()[0] = 0x81; // FIN==1 | opcode=1 (text)
+ buffer->data()[1] = kUseMask ? 0x80 : 0x00;
+
+ if (size > 65535) {
+ buffer->data()[1] |= 127;
+ buffer->data()[2] = 0x00;
+ buffer->data()[3] = 0x00;
+ buffer->data()[4] = 0x00;
+ buffer->data()[5] = 0x00;
+ buffer->data()[6] = (size >> 24) & 0xff;
+ buffer->data()[7] = (size >> 16) & 0xff;
+ buffer->data()[8] = (size >> 8) & 0xff;
+ buffer->data()[9] = size & 0xff;
+ } else if (size > 125) {
+ buffer->data()[1] |= 126;
+ buffer->data()[2] = (size >> 8) & 0xff;
+ buffer->data()[3] = size & 0xff;
+ } else {
+ buffer->data()[1] |= size;
+ }
+
+ if (kUseMask) {
+ uint32_t mask = rand();
+
+ buffer->data()[numHeaderBytes - 4] = (mask >> 24) & 0xff;
+ buffer->data()[numHeaderBytes - 3] = (mask >> 16) & 0xff;
+ buffer->data()[numHeaderBytes - 2] = (mask >> 8) & 0xff;
+ buffer->data()[numHeaderBytes - 1] = mask & 0xff;
+
+ for (size_t i = 0; i < (size_t)size; ++i) {
+ buffer->data()[numHeaderBytes + i] =
+ ((const uint8_t *)data)[i]
+ ^ ((mask >> (8 * (3 - (i % 4)))) & 0xff);
+ }
+ } else {
+ memcpy(buffer->data() + numHeaderBytes, data, size);
+ }
+ } else {
+ buffer = new ABuffer(size);
+ memcpy(buffer->data(), data, size);
+ }
+
+ Fragment frag;
+
+ frag.mFlags = 0;
+ if (timeValid) {
+ frag.mFlags = FRAGMENT_FLAG_TIME_VALID;
+ frag.mTimeUs = timeUs;
+ }
+
+ frag.mBuffer = buffer;
+
+ mOutFragments.push_back(frag);
+
+ return OK;
+}
+
+void ANetworkSession::Session::notifyError(
+ bool send, status_t err, const char *detail) {
+ sp msg = mNotify->dup();
+ msg->setInt32("sessionID", mSessionID);
+ msg->setInt32("reason", kWhatError);
+ msg->setInt32("send", send);
+ msg->setInt32("err", err);
+ msg->setString("detail", detail);
+ msg->post();
+}
+
+void ANetworkSession::Session::notify(NotificationReason reason) {
+ sp msg = mNotify->dup();
+ msg->setInt32("sessionID", mSessionID);
+ msg->setInt32("reason", reason);
+ msg->post();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ANetworkSession::ANetworkSession()
+ : mNextSessionID(1) {
+ mPipeFd[0] = mPipeFd[1] = -1;
+}
+
+ANetworkSession::~ANetworkSession() {
+ stop();
+}
+
+status_t ANetworkSession::start() {
+ if (mThread != NULL) {
+ return INVALID_OPERATION;
+ }
+
+ int res = pipe(mPipeFd);
+ if (res != 0) {
+ mPipeFd[0] = mPipeFd[1] = -1;
+ return -errno;
+ }
+
+ mThread = new NetworkThread(this);
+
+ status_t err = mThread->run("ANetworkSession", ANDROID_PRIORITY_AUDIO);
+
+ if (err != OK) {
+ mThread.clear();
+
+ close(mPipeFd[0]);
+ close(mPipeFd[1]);
+ mPipeFd[0] = mPipeFd[1] = -1;
+
+ return err;
+ }
+
+ return OK;
+}
+
+status_t ANetworkSession::stop() {
+ if (mThread == NULL) {
+ return INVALID_OPERATION;
+ }
+
+ mThread->requestExit();
+ interrupt();
+ mThread->requestExitAndWait();
+
+ mThread.clear();
+
+ close(mPipeFd[0]);
+ close(mPipeFd[1]);
+ mPipeFd[0] = mPipeFd[1] = -1;
+
+ return OK;
+}
+
+status_t ANetworkSession::createRTSPClient(
+ const char *host, unsigned port, const sp ¬ify,
+ int32_t *sessionID) {
+ return createClientOrServer(
+ kModeCreateRTSPClient,
+ NULL /* addr */,
+ 0 /* port */,
+ host,
+ port,
+ notify,
+ sessionID);
+}
+
+status_t ANetworkSession::createRTSPServer(
+ const struct in_addr &addr, unsigned port,
+ const sp ¬ify, int32_t *sessionID) {
+ return createClientOrServer(
+ kModeCreateRTSPServer,
+ &addr,
+ port,
+ NULL /* remoteHost */,
+ 0 /* remotePort */,
+ notify,
+ sessionID);
+}
+
+status_t ANetworkSession::createUDPSession(
+ unsigned localPort, const sp ¬ify, int32_t *sessionID) {
+ return createUDPSession(localPort, NULL, 0, notify, sessionID);
+}
+
+status_t ANetworkSession::createUDPSession(
+ unsigned localPort,
+ const char *remoteHost,
+ unsigned remotePort,
+ const sp ¬ify,
+ int32_t *sessionID) {
+ return createClientOrServer(
+ kModeCreateUDPSession,
+ NULL /* addr */,
+ localPort,
+ remoteHost,
+ remotePort,
+ notify,
+ sessionID);
+}
+
+status_t ANetworkSession::createTCPDatagramSession(
+ const struct in_addr &addr, unsigned port,
+ const sp ¬ify, int32_t *sessionID) {
+ return createClientOrServer(
+ kModeCreateTCPDatagramSessionPassive,
+ &addr,
+ port,
+ NULL /* remoteHost */,
+ 0 /* remotePort */,
+ notify,
+ sessionID);
+}
+
+status_t ANetworkSession::createTCPDatagramSession(
+ unsigned localPort,
+ const char *remoteHost,
+ unsigned remotePort,
+ const sp ¬ify,
+ int32_t *sessionID) {
+ return createClientOrServer(
+ kModeCreateTCPDatagramSessionActive,
+ NULL /* addr */,
+ localPort,
+ remoteHost,
+ remotePort,
+ notify,
+ sessionID);
+}
+
+status_t ANetworkSession::destroySession(int32_t sessionID) {
+ Mutex::Autolock autoLock(mLock);
+
+ ssize_t index = mSessions.indexOfKey(sessionID);
+
+ if (index < 0) {
+ return -ENOENT;
+ }
+
+ mSessions.removeItemsAt(index);
+
+ interrupt();
+
+ return OK;
+}
+
+// static
+status_t ANetworkSession::MakeSocketNonBlocking(int s) {
+ int flags = fcntl(s, F_GETFL, 0);
+ if (flags < 0) {
+ flags = 0;
+ }
+
+ int res = fcntl(s, F_SETFL, flags | O_NONBLOCK);
+ if (res < 0) {
+ return -errno;
+ }
+
+ return OK;
+}
+
+status_t ANetworkSession::createClientOrServer(
+ Mode mode,
+ const struct in_addr *localAddr,
+ unsigned port,
+ const char *remoteHost,
+ unsigned remotePort,
+ const sp ¬ify,
+ int32_t *sessionID) {
+ Mutex::Autolock autoLock(mLock);
+
+ *sessionID = 0;
+ status_t err = OK;
+ int s, res;
+ sp session;
+
+ s = socket(
+ AF_INET,
+ (mode == kModeCreateUDPSession) ? SOCK_DGRAM : SOCK_STREAM,
+ 0);
+
+ if (s < 0) {
+ err = -errno;
+ goto bail;
+ }
+
+ if (mode == kModeCreateRTSPServer
+ || mode == kModeCreateTCPDatagramSessionPassive) {
+ const int yes = 1;
+ res = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
+
+ if (res < 0) {
+ err = -errno;
+ goto bail2;
+ }
+ }
+
+ if (mode == kModeCreateUDPSession) {
+ int size = 256 * 1024;
+
+ res = setsockopt(s, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
+
+ if (res < 0) {
+ err = -errno;
+ goto bail2;
+ }
+
+ res = setsockopt(s, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
+
+ if (res < 0) {
+ err = -errno;
+ goto bail2;
+ }
+ } else if (mode == kModeCreateTCPDatagramSessionActive) {
+ int flag = 1;
+ res = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
+
+ if (res < 0) {
+ err = -errno;
+ goto bail2;
+ }
+
+ int tos = 224; // VOICE
+ res = setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+
+ if (res < 0) {
+ err = -errno;
+ goto bail2;
+ }
+ }
+
+ err = MakeSocketNonBlocking(s);
+
+ if (err != OK) {
+ goto bail2;
+ }
+
+ struct sockaddr_in addr;
+ memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
+ addr.sin_family = AF_INET;
+
+ if (mode == kModeCreateRTSPClient
+ || mode == kModeCreateTCPDatagramSessionActive) {
+ struct hostent *ent= gethostbyname(remoteHost);
+ if (ent == NULL) {
+ err = -h_errno;
+ goto bail2;
+ }
+
+ addr.sin_addr.s_addr = *(in_addr_t *)ent->h_addr;
+ addr.sin_port = htons(remotePort);
+ } else if (localAddr != NULL) {
+ addr.sin_addr = *localAddr;
+ addr.sin_port = htons(port);
+ } else {
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = htons(port);
+ }
+
+ if (mode == kModeCreateRTSPClient
+ || mode == kModeCreateTCPDatagramSessionActive) {
+ in_addr_t x = ntohl(addr.sin_addr.s_addr);
+ ALOGI("connecting socket %d to %d.%d.%d.%d:%d",
+ s,
+ (x >> 24),
+ (x >> 16) & 0xff,
+ (x >> 8) & 0xff,
+ x & 0xff,
+ ntohs(addr.sin_port));
+
+ res = connect(s, (const struct sockaddr *)&addr, sizeof(addr));
+
+ CHECK_LT(res, 0);
+ if (errno == EINPROGRESS) {
+ res = 0;
+ }
+ } else {
+ res = bind(s, (const struct sockaddr *)&addr, sizeof(addr));
+
+ if (res == 0) {
+ if (mode == kModeCreateRTSPServer
+ || mode == kModeCreateTCPDatagramSessionPassive) {
+ res = listen(s, 4);
+ } else {
+ CHECK_EQ(mode, kModeCreateUDPSession);
+
+ if (remoteHost != NULL) {
+ struct sockaddr_in remoteAddr;
+ memset(remoteAddr.sin_zero, 0, sizeof(remoteAddr.sin_zero));
+ remoteAddr.sin_family = AF_INET;
+ remoteAddr.sin_port = htons(remotePort);
+
+ struct hostent *ent= gethostbyname(remoteHost);
+ if (ent == NULL) {
+ err = -h_errno;
+ goto bail2;
+ }
+
+ remoteAddr.sin_addr.s_addr = *(in_addr_t *)ent->h_addr;
+
+ res = connect(
+ s,
+ (const struct sockaddr *)&remoteAddr,
+ sizeof(remoteAddr));
+ }
+ }
+ }
+ }
+
+ if (res < 0) {
+ err = -errno;
+ goto bail2;
+ }
+
+ Session::State state;
+ switch (mode) {
+ case kModeCreateRTSPClient:
+ state = Session::CONNECTING;
+ break;
+
+ case kModeCreateTCPDatagramSessionActive:
+ state = Session::CONNECTING;
+ break;
+
+ case kModeCreateTCPDatagramSessionPassive:
+ state = Session::LISTENING_TCP_DGRAMS;
+ break;
+
+ case kModeCreateRTSPServer:
+ state = Session::LISTENING_RTSP;
+ break;
+
+ default:
+ CHECK_EQ(mode, kModeCreateUDPSession);
+ state = Session::DATAGRAM;
+ break;
+ }
+
+ session = new Session(
+ mNextSessionID++,
+ state,
+ s,
+ notify);
+
+ if (mode == kModeCreateTCPDatagramSessionActive) {
+ session->setMode(Session::MODE_DATAGRAM);
+ } else if (mode == kModeCreateRTSPClient) {
+ session->setMode(Session::MODE_RTSP);
+ }
+
+ mSessions.add(session->sessionID(), session);
+
+ interrupt();
+
+ *sessionID = session->sessionID();
+
+ goto bail;
+
+bail2:
+ close(s);
+ s = -1;
+
+bail:
+ return err;
+}
+
+status_t ANetworkSession::connectUDPSession(
+ int32_t sessionID, const char *remoteHost, unsigned remotePort) {
+ Mutex::Autolock autoLock(mLock);
+
+ ssize_t index = mSessions.indexOfKey(sessionID);
+
+ if (index < 0) {
+ return -ENOENT;
+ }
+
+ const sp session = mSessions.valueAt(index);
+ int s = session->socket();
+
+ struct sockaddr_in remoteAddr;
+ memset(remoteAddr.sin_zero, 0, sizeof(remoteAddr.sin_zero));
+ remoteAddr.sin_family = AF_INET;
+ remoteAddr.sin_port = htons(remotePort);
+
+ status_t err = OK;
+ struct hostent *ent = gethostbyname(remoteHost);
+ if (ent == NULL) {
+ err = -h_errno;
+ } else {
+ remoteAddr.sin_addr.s_addr = *(in_addr_t *)ent->h_addr;
+
+ int res = connect(
+ s,
+ (const struct sockaddr *)&remoteAddr,
+ sizeof(remoteAddr));
+
+ if (res < 0) {
+ err = -errno;
+ }
+ }
+
+ return err;
+}
+
+status_t ANetworkSession::sendRequest(
+ int32_t sessionID, const void *data, ssize_t size,
+ bool timeValid, int64_t timeUs) {
+ Mutex::Autolock autoLock(mLock);
+
+ ssize_t index = mSessions.indexOfKey(sessionID);
+
+ if (index < 0) {
+ return -ENOENT;
+ }
+
+ const sp session = mSessions.valueAt(index);
+
+ status_t err = session->sendRequest(data, size, timeValid, timeUs);
+
+ interrupt();
+
+ return err;
+}
+
+status_t ANetworkSession::switchToWebSocketMode(int32_t sessionID) {
+ Mutex::Autolock autoLock(mLock);
+
+ ssize_t index = mSessions.indexOfKey(sessionID);
+
+ if (index < 0) {
+ return -ENOENT;
+ }
+
+ const sp session = mSessions.valueAt(index);
+ return session->switchToWebSocketMode();
+}
+
+void ANetworkSession::interrupt() {
+ static const char dummy = 0;
+
+ ssize_t n;
+ do {
+ n = write(mPipeFd[1], &dummy, 1);
+ } while (n < 0 && errno == EINTR);
+
+ if (n < 0) {
+ ALOGW("Error writing to pipe (%s)", strerror(errno));
+ }
+}
+
+void ANetworkSession::threadLoop() {
+ fd_set rs, ws;
+ FD_ZERO(&rs);
+ FD_ZERO(&ws);
+
+ FD_SET(mPipeFd[0], &rs);
+ int maxFd = mPipeFd[0];
+
+ {
+ Mutex::Autolock autoLock(mLock);
+
+ for (size_t i = 0; i < mSessions.size(); ++i) {
+ const sp &session = mSessions.valueAt(i);
+
+ int s = session->socket();
+
+ if (s < 0) {
+ continue;
+ }
+
+ if (session->wantsToRead()) {
+ FD_SET(s, &rs);
+ if (s > maxFd) {
+ maxFd = s;
+ }
+ }
+
+ if (session->wantsToWrite()) {
+ FD_SET(s, &ws);
+ if (s > maxFd) {
+ maxFd = s;
+ }
+ }
+ }
+ }
+
+ int res = select(maxFd + 1, &rs, &ws, NULL, NULL /* tv */);
+
+ if (res == 0) {
+ return;
+ }
+
+ if (res < 0) {
+ if (errno == EINTR) {
+ return;
+ }
+
+ ALOGE("select failed w/ error %d (%s)", errno, strerror(errno));
+ return;
+ }
+
+ if (FD_ISSET(mPipeFd[0], &rs)) {
+ char c;
+ ssize_t n;
+ do {
+ n = read(mPipeFd[0], &c, 1);
+ } while (n < 0 && errno == EINTR);
+
+ if (n < 0) {
+ ALOGW("Error reading from pipe (%s)", strerror(errno));
+ }
+
+ --res;
+ }
+
+ {
+ Mutex::Autolock autoLock(mLock);
+
+ List > sessionsToAdd;
+
+ for (size_t i = mSessions.size(); res > 0 && i > 0;) {
+ i--;
+ const sp &session = mSessions.valueAt(i);
+
+ int s = session->socket();
+
+ if (s < 0) {
+ continue;
+ }
+
+ if (FD_ISSET(s, &rs) || FD_ISSET(s, &ws)) {
+ --res;
+ }
+
+ if (FD_ISSET(s, &rs)) {
+ if (session->isRTSPServer() || session->isTCPDatagramServer()) {
+ struct sockaddr_in remoteAddr;
+ socklen_t remoteAddrLen = sizeof(remoteAddr);
+
+ int clientSocket = accept(
+ s, (struct sockaddr *)&remoteAddr, &remoteAddrLen);
+
+ if (clientSocket >= 0) {
+ status_t err = MakeSocketNonBlocking(clientSocket);
+
+ if (err != OK) {
+ ALOGE("Unable to make client socket non blocking, "
+ "failed w/ error %d (%s)",
+ err, strerror(-err));
+
+ close(clientSocket);
+ clientSocket = -1;
+ } else {
+ in_addr_t addr = ntohl(remoteAddr.sin_addr.s_addr);
+
+ ALOGI("incoming connection from %d.%d.%d.%d:%d "
+ "(socket %d)",
+ (addr >> 24),
+ (addr >> 16) & 0xff,
+ (addr >> 8) & 0xff,
+ addr & 0xff,
+ ntohs(remoteAddr.sin_port),
+ clientSocket);
+
+ sp clientSession =
+ new Session(
+ mNextSessionID++,
+ Session::CONNECTED,
+ clientSocket,
+ session->getNotificationMessage());
+
+ clientSession->setMode(
+ session->isRTSPServer()
+ ? Session::MODE_RTSP
+ : Session::MODE_DATAGRAM);
+
+ sessionsToAdd.push_back(clientSession);
+ }
+ } else {
+ ALOGE("accept returned error %d (%s)",
+ errno, strerror(errno));
+ }
+ } else {
+ status_t err = session->readMore();
+ if (err != OK) {
+ ALOGE("readMore on socket %d failed w/ error %d (%s)",
+ s, err, strerror(-err));
+ }
+ }
+ }
+
+ if (FD_ISSET(s, &ws)) {
+ status_t err = session->writeMore();
+ if (err != OK) {
+ ALOGE("writeMore on socket %d failed w/ error %d (%s)",
+ s, err, strerror(-err));
+ }
+ }
+ }
+
+ while (!sessionsToAdd.empty()) {
+ sp session = *sessionsToAdd.begin();
+ sessionsToAdd.erase(sessionsToAdd.begin());
+
+ mSessions.add(session->sessionID(), session);
+
+ ALOGI("added clientSession %d", session->sessionID());
+ }
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index a26fcbe4df..de0c70d202 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -232,6 +232,7 @@ cc_library {
"ACodecBufferChannel.cpp",
"AHierarchicalStateMachine.cpp",
"AMRWriter.cpp",
+ "ANetworkSession.cpp",
"AudioSource.cpp",
"BufferImpl.cpp",
"CallbackDataSource.cpp",
@@ -263,10 +264,12 @@ cc_library {
"OggWriter.cpp",
"OMXClient.cpp",
"OmxInfoBuilder.cpp",
+ "ParsedMessage.cpp",
"RemoteMediaExtractor.cpp",
"RemoteMediaSource.cpp",
"SimpleDecodingSource.cpp",
"StagefrightMediaScanner.cpp",
+ "SurfaceMediaSource.cpp",
"SurfaceUtils.cpp",
"ThrottledSource.cpp",
"Utils.cpp",
diff --git a/media/libstagefright/MediaCodecList.cpp b/media/libstagefright/MediaCodecList.cpp
index 4ad3276743..03bf12e6dc 100644
--- a/media/libstagefright/MediaCodecList.cpp
+++ b/media/libstagefright/MediaCodecList.cpp
@@ -319,6 +319,7 @@ const sp MediaCodecList::getGlobalSettings() const {
//static
bool MediaCodecList::isSoftwareCodec(const AString &componentName) {
return componentName.startsWithIgnoreCase("OMX.google.")
+ || componentName.startsWithIgnoreCase("OMX.dolby.")
|| componentName.startsWithIgnoreCase("c2.android.")
|| (!componentName.startsWithIgnoreCase("OMX.")
&& !componentName.startsWithIgnoreCase("c2."));
diff --git a/media/libstagefright/ParsedMessage.cpp b/media/libstagefright/ParsedMessage.cpp
new file mode 100644
index 0000000000..4bfc454338
--- /dev/null
+++ b/media/libstagefright/ParsedMessage.cpp
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2012, 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.
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+namespace android {
+
+// static
+sp ParsedMessage::Parse(
+ const char *data, size_t size, bool noMoreData, size_t *length) {
+ sp msg = new ParsedMessage;
+ ssize_t res = msg->parse(data, size, noMoreData);
+
+ if (res < 0) {
+ *length = 0;
+ return NULL;
+ }
+
+ *length = res;
+ return msg;
+}
+
+ParsedMessage::ParsedMessage() {
+}
+
+ParsedMessage::~ParsedMessage() {
+}
+
+bool ParsedMessage::findString(const char *name, AString *value) const {
+ AString key = name;
+ key.tolower();
+
+ ssize_t index = mDict.indexOfKey(key);
+
+ if (index < 0) {
+ value->clear();
+
+ return false;
+ }
+
+ *value = mDict.valueAt(index);
+ return true;
+}
+
+bool ParsedMessage::findInt32(const char *name, int32_t *value) const {
+ AString stringValue;
+
+ if (!findString(name, &stringValue)) {
+ return false;
+ }
+
+ char *end;
+ *value = strtol(stringValue.c_str(), &end, 10);
+
+ if (end == stringValue.c_str() || *end != '\0') {
+ *value = 0;
+ return false;
+ }
+
+ return true;
+}
+
+const char *ParsedMessage::getContent() const {
+ return mContent.c_str();
+}
+
+ssize_t ParsedMessage::parse(const char *data, size_t size, bool noMoreData) {
+ if (size == 0) {
+ return -1;
+ }
+
+ ssize_t lastDictIndex = -1;
+
+ size_t offset = 0;
+ bool headersComplete = false;
+ while (offset < size) {
+ size_t lineEndOffset = offset;
+ while (lineEndOffset + 1 < size
+ && (data[lineEndOffset] != '\r'
+ || data[lineEndOffset + 1] != '\n')) {
+ ++lineEndOffset;
+ }
+
+ if (lineEndOffset + 1 >= size) {
+ return -1;
+ }
+
+ AString line(&data[offset], lineEndOffset - offset);
+
+ if (offset == 0) {
+ // Special handling for the request/status line.
+
+ mDict.add(AString("_"), line);
+ offset = lineEndOffset + 2;
+
+ continue;
+ }
+
+ if (lineEndOffset == offset) {
+ // An empty line separates headers from body.
+ headersComplete = true;
+ offset += 2;
+ break;
+ }
+
+ if (line.c_str()[0] == ' ' || line.c_str()[0] == '\t') {
+ // Support for folded header values.
+
+ if (lastDictIndex >= 0) {
+ // Otherwise it's malformed since the first header line
+ // cannot continue anything...
+
+ AString &value = mDict.editValueAt(lastDictIndex);
+ value.append(line);
+ }
+
+ offset = lineEndOffset + 2;
+ continue;
+ }
+
+ ssize_t colonPos = line.find(":");
+ if (colonPos >= 0) {
+ AString key(line, 0, colonPos);
+ key.trim();
+ key.tolower();
+
+ line.erase(0, colonPos + 1);
+
+ lastDictIndex = mDict.add(key, line);
+ }
+
+ offset = lineEndOffset + 2;
+ }
+
+ if (!headersComplete && (!noMoreData || offset == 0)) {
+ // We either saw the empty line separating headers from body
+ // or we saw at least the status line and know that no more data
+ // is going to follow.
+ return -1;
+ }
+
+ for (size_t i = 0; i < mDict.size(); ++i) {
+ mDict.editValueAt(i).trim();
+ }
+
+ int32_t contentLength;
+ if (!findInt32("content-length", &contentLength) || contentLength < 0) {
+ contentLength = 0;
+ }
+
+ size_t totalLength = offset + contentLength;
+
+ if (size < totalLength) {
+ return -1;
+ }
+
+ mContent.setTo(&data[offset], contentLength);
+
+ return totalLength;
+}
+
+bool ParsedMessage::getRequestField(size_t index, AString *field) const {
+ AString line;
+ CHECK(findString("_", &line));
+
+ size_t prevOffset = 0;
+ size_t offset = 0;
+ for (size_t i = 0; i <= index; ++i) {
+ if (offset >= line.size()) {
+ return false;
+ }
+
+ ssize_t spacePos = line.find(" ", offset);
+
+ if (spacePos < 0) {
+ spacePos = line.size();
+ }
+
+ prevOffset = offset;
+ offset = spacePos + 1;
+ }
+
+ field->setTo(line, prevOffset, offset - prevOffset - 1);
+
+ return true;
+}
+
+bool ParsedMessage::getStatusCode(int32_t *statusCode) const {
+ AString statusCodeString;
+ if (!getRequestField(1, &statusCodeString)) {
+ *statusCode = 0;
+ return false;
+ }
+
+ char *end;
+ *statusCode = strtol(statusCodeString.c_str(), &end, 10);
+
+ if (*end != '\0' || end == statusCodeString.c_str()
+ || (*statusCode) < 100 || (*statusCode) > 999) {
+ *statusCode = 0;
+ return false;
+ }
+
+ return true;
+}
+
+AString ParsedMessage::debugString() const {
+ AString line;
+ CHECK(findString("_", &line));
+
+ line.append("\n");
+
+ for (size_t i = 0; i < mDict.size(); ++i) {
+ const AString &key = mDict.keyAt(i);
+ const AString &value = mDict.valueAt(i);
+
+ if (key == AString("_")) {
+ continue;
+ }
+
+ line.append(key);
+ line.append(": ");
+ line.append(value);
+ line.append("\n");
+ }
+
+ line.append("\n");
+ line.append(mContent);
+
+ return line;
+}
+
+// static
+bool ParsedMessage::GetAttribute(
+ const char *s, const char *key, AString *value) {
+ value->clear();
+
+ size_t keyLen = strlen(key);
+
+ for (;;) {
+ while (isspace(*s)) {
+ ++s;
+ }
+
+ const char *colonPos = strchr(s, ';');
+
+ size_t len =
+ (colonPos == NULL) ? strlen(s) : colonPos - s;
+
+ if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) {
+ value->setTo(&s[keyLen + 1], len - keyLen - 1);
+ return true;
+ }
+
+ if (colonPos == NULL) {
+ return false;
+ }
+
+ s = colonPos + 1;
+ }
+}
+
+// static
+bool ParsedMessage::GetInt32Attribute(
+ const char *s, const char *key, int32_t *value) {
+ AString stringValue;
+ if (!GetAttribute(s, key, &stringValue)) {
+ *value = 0;
+ return false;
+ }
+
+ char *end;
+ *value = strtol(stringValue.c_str(), &end, 10);
+
+ if (end == stringValue.c_str() || *end != '\0') {
+ *value = 0;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/SurfaceMediaSource.cpp b/media/libstagefright/SurfaceMediaSource.cpp
new file mode 100644
index 0000000000..d7370551e0
--- /dev/null
+++ b/media/libstagefright/SurfaceMediaSource.cpp
@@ -0,0 +1,485 @@
+/*
+ * 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
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+
+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 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 listener = static_cast(this);
+ sp 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& 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("%zu: %p", i, mPendingBuffers.itemAt(i));
+ }
+#endif
+
+ mMediaBuffersAvailableCondition.wait(mMutex);
+ }
+
+ mMediaBuffersAvailableCondition.signal();
+
+ return mConsumer->consumerDisconnect();
+}
+
+sp SurfaceMediaSource::getFormat()
+{
+ ALOGV("getFormat");
+
+ Mutex::Autolock lock(mMutex);
+ sp 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;
+ VideoNativeMetadata *data = (VideoNativeMetadata *)buffer->data();
+ ANativeWindowBuffer *anwbuffer = (ANativeWindowBuffer *)data->pBuffer;
+ bufferHandle = anwbuffer->handle;
+ return bufferHandle;
+}
+
+void SurfaceMediaSource::signalBufferReturned(MediaBufferBase *buffer) {
+ ALOGV("signalBufferReturned");
+
+ bool foundBuffer = false;
+
+ Mutex::Autolock lock(mMutex);
+
+ buffer_handle_t bufferHandle = getMediaBufferHandle(buffer);
+ ANativeWindowBuffer* curNativeHandle = NULL;
+
+ for (size_t i = 0; i < mCurrentBuffers.size(); i++) {
+ curNativeHandle = mCurrentBuffers[i]->getNativeBuffer();
+ if ((mCurrentBuffers[i]->handle == bufferHandle) ||
+ ((buffer_handle_t)curNativeHandle == 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;
+ }
+
+ curNativeHandle = mSlots[id].mGraphicBuffer->getNativeBuffer();
+
+ if ((bufferHandle == mSlots[id].mGraphicBuffer->handle) ||
+ (bufferHandle == (buffer_handle_t)curNativeHandle)) {
+ 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 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/ANetworkSession.h b/media/libstagefright/include/media/stagefright/ANetworkSession.h
new file mode 100644
index 0000000000..fd3ebaaa28
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/ANetworkSession.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2012, 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 A_NETWORK_SESSION_H_
+
+#define A_NETWORK_SESSION_H_
+
+#include
+#include
+#include
+#include
+
+#include
+
+namespace android {
+
+struct AMessage;
+
+// Helper class to manage a number of live sockets (datagram and stream-based)
+// on a single thread. Clients are notified about activity through AMessages.
+struct ANetworkSession : public RefBase {
+ ANetworkSession();
+
+ status_t start();
+ status_t stop();
+
+ status_t createRTSPClient(
+ const char *host, unsigned port, const sp ¬ify,
+ int32_t *sessionID);
+
+ status_t createRTSPServer(
+ const struct in_addr &addr, unsigned port,
+ const sp ¬ify, int32_t *sessionID);
+
+ status_t createUDPSession(
+ unsigned localPort, const sp ¬ify, int32_t *sessionID);
+
+ status_t createUDPSession(
+ unsigned localPort,
+ const char *remoteHost,
+ unsigned remotePort,
+ const sp ¬ify,
+ int32_t *sessionID);
+
+ status_t connectUDPSession(
+ int32_t sessionID, const char *remoteHost, unsigned remotePort);
+
+ // passive
+ status_t createTCPDatagramSession(
+ const struct in_addr &addr, unsigned port,
+ const sp ¬ify, int32_t *sessionID);
+
+ // active
+ status_t createTCPDatagramSession(
+ unsigned localPort,
+ const char *remoteHost,
+ unsigned remotePort,
+ const sp ¬ify,
+ int32_t *sessionID);
+
+ status_t destroySession(int32_t sessionID);
+
+ status_t sendRequest(
+ int32_t sessionID, const void *data, ssize_t size = -1,
+ bool timeValid = false, int64_t timeUs = -1ll);
+
+ status_t switchToWebSocketMode(int32_t sessionID);
+
+ enum NotificationReason {
+ kWhatError,
+ kWhatConnected,
+ kWhatClientConnected,
+ kWhatData,
+ kWhatDatagram,
+ kWhatBinaryData,
+ kWhatWebSocketMessage,
+ kWhatNetworkStall,
+ };
+
+protected:
+ virtual ~ANetworkSession();
+
+private:
+ struct NetworkThread;
+ struct Session;
+
+ Mutex mLock;
+ sp mThread;
+
+ int32_t mNextSessionID;
+
+ int mPipeFd[2];
+
+ KeyedVector > mSessions;
+
+ enum Mode {
+ kModeCreateUDPSession,
+ kModeCreateTCPDatagramSessionPassive,
+ kModeCreateTCPDatagramSessionActive,
+ kModeCreateRTSPServer,
+ kModeCreateRTSPClient,
+ };
+ status_t createClientOrServer(
+ Mode mode,
+ const struct in_addr *addr,
+ unsigned port,
+ const char *remoteHost,
+ unsigned remotePort,
+ const sp ¬ify,
+ int32_t *sessionID);
+
+ void threadLoop();
+ void interrupt();
+
+ static status_t MakeSocketNonBlocking(int s);
+
+ DISALLOW_EVIL_CONSTRUCTORS(ANetworkSession);
+};
+
+} // namespace android
+
+#endif // A_NETWORK_SESSION_H_
diff --git a/media/libstagefright/include/media/stagefright/ParsedMessage.h b/media/libstagefright/include/media/stagefright/ParsedMessage.h
new file mode 100644
index 0000000000..9d43a93319
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/ParsedMessage.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012, 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.
+ */
+
+#include
+#include
+#include
+#include
+
+namespace android {
+
+// Encapsulates an "HTTP/RTSP style" response, i.e. a status line,
+// key/value pairs making up the headers and an optional body/content.
+struct ParsedMessage : public RefBase {
+ static sp Parse(
+ const char *data, size_t size, bool noMoreData, size_t *length);
+
+ bool findString(const char *name, AString *value) const;
+ bool findInt32(const char *name, int32_t *value) const;
+
+ const char *getContent() const;
+
+ bool getRequestField(size_t index, AString *field) const;
+ bool getStatusCode(int32_t *statusCode) const;
+
+ AString debugString() const;
+
+ static bool GetAttribute(const char *s, const char *key, AString *value);
+
+ static bool GetInt32Attribute(
+ const char *s, const char *key, int32_t *value);
+
+
+protected:
+ virtual ~ParsedMessage();
+
+private:
+ KeyedVector mDict;
+ AString mContent;
+
+ ParsedMessage();
+
+ ssize_t parse(const char *data, size_t size, bool noMoreData);
+
+ DISALLOW_EVIL_CONSTRUCTORS(ParsedMessage);
+};
+
+} // 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..c67defe30b
--- /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
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+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 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& 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 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 mProducer;
+ sp mConsumer;
+
+ struct SlotData {
+ sp 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 > mCurrentBuffers;
+
+ size_t mNumPendingBuffers;
+
+#if DEBUG_PENDING_BUFFERS
+ Vector 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 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/omx/OMXStore.cpp b/media/libstagefright/omx/OMXStore.cpp
index 0906433a8a..d2ff34a2f1 100644
--- a/media/libstagefright/omx/OMXStore.cpp
+++ b/media/libstagefright/omx/OMXStore.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "OMXStore"
#include
#include
+#include
#include
#include
@@ -63,6 +64,21 @@ OMXStore::~OMXStore() {
void OMXStore::addVendorPlugin() {
addPlugin("libstagefrighthw.so");
+
+ // MIUI ADD: DOLBY_ENABLE
+ // NOTE: We do not use FeatureManager::isFeatureEnable here because we can not add shared lib
+ // libmediautils to this module due to this module is vendor_avaiable and vndk is set as true
+ // but libmediautils is not.
+ bool isDolbyEnable = property_get_bool("ro.vendor.audio.dolby.dax.support", false);
+ if (isDolbyEnable) {
+ // !IMPORTANT:
+ // Dolby OMX plugin manages all the Dolby codec components. Customer needs to manage Dolby
+ // codec components in its own OMX plugin (e.g. above libstagefrighthw.so) then removes
+ // all Dolby's modifications in this file to pass Goolge VTS.
+ ALOGD("%s(): Loading Dolby OMX plugin...", __FUNCTION__);
+ addPlugin("libstagefrightdolby.so");
+ }
+ // MIUI END
}
void OMXStore::addPlatformPlugin() {
diff --git a/media/libstagefright/omx/SoftOMXPlugin.cpp b/media/libstagefright/omx/SoftOMXPlugin.cpp
index 8c186c90f8..6258ee08d7 100644
--- a/media/libstagefright/omx/SoftOMXPlugin.cpp
+++ b/media/libstagefright/omx/SoftOMXPlugin.cpp
@@ -61,6 +61,9 @@ static const struct {
{ "OMX.google.flac.decoder", "flacdec", "audio_decoder.flac" },
{ "OMX.google.flac.encoder", "flacenc", "audio_encoder.flac" },
{ "OMX.google.gsm.decoder", "gsmdec", "audio_decoder.gsm" },
+ { "OMX.dolby.ac3.decoder", "ddpdec", "audio_decoder.ac3" },
+ { "OMX.dolby.eac3-joc.decoder", "ddpdec", "audio_decoder.eac3_joc" },
+ { "OMX.dolby.eac3.decoder", "ddpdec", "audio_decoder.eac3" },
};
static const size_t kNumComponents =
diff --git a/media/libstagefright/tests/Android.bp b/media/libstagefright/tests/Android.bp
index 581292e79b..f10f2b4aaa 100644
--- a/media/libstagefright/tests/Android.bp
+++ b/media/libstagefright/tests/Android.bp
@@ -19,6 +19,45 @@ license {
],
}
+cc_test {
+ name: "SurfaceMediaSource_test",
+
+ srcs: [
+ "SurfaceMediaSource_test.cpp",
+ "DummyRecorder.cpp",
+ ],
+
+ shared_libs: [
+ "libEGL",
+ "libGLESv2",
+ "libbinder",
+ "libcutils",
+ "libgui",
+ "libmedia",
+ "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",
diff --git a/media/libstagefright/tests/DummyRecorder.cpp b/media/libstagefright/tests/DummyRecorder.cpp
new file mode 100644
index 0000000000..596b7e6fd7
--- /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
+#include
+#include "DummyRecorder.h"
+
+#include
+
+namespace android {
+
+// static
+void *DummyRecorder::threadWrapper(void *pthis) {
+ ALOGV("ThreadWrapper: %p", pthis);
+ DummyRecorder *writer = static_cast(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(reinterpret_cast(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
+#include
+#include
+
+
+namespace android {
+
+struct MediaSource;
+class MediaBuffer;
+
+class DummyRecorder {
+ public:
+ // The media source from which this will receive frames
+ sp 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 &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
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+#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 window = mSurfaceControl->getSurface();
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
+ window.get(), NULL);
+ } else {
+ ALOGV("No actual display. Choosing EGLSurface based on SurfaceMediaSource");
+ sp sms = (new SurfaceMediaSource(
+ getSurfaceWidth(), getSurfaceHeight()))->getProducer();
+ sp stc = new Surface(sms);
+ sp 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 mComposerClient;
+ sp 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 mSMS;
+ sp mSTC;
+ sp 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 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& mr);
+
+ const int mYuvTexWidth;
+ const int mYuvTexHeight;
+
+ sp mSMS;
+ sp mSTC;
+ sp 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 SurfaceMediaSourceGLTest::setUpMediaRecorder(int fd, int videoSource,
+ int outputFormat, int videoEncoder, int width, int height, int fps) {
+ sp 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& mr) {
+ sp 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 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 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 mSource;
+
+ explicit SimpleDummyRecorder
+ (const sp &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