Merge tag 'android-14.0.0_r61' into leaf-3.2
Android 14.0.0 Release 61 (AP2A.240805.005.F1)
* tag 'android-14.0.0_r61':
Revert^2 "StagefrightRecoder: Disabling B-frame support"
libmediatranscoding: handle death recipient cookie ownership differently
Track DeathNotifier cookie lifetime
Change-Id: Ie4f21012423716516be856e1076c62731900505e
diff --git a/include/media/IHDCP.h b/include/media/IHDCP.h
new file mode 120000
index 0000000..9d4568e
--- /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/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index 0cbf79d..5dc451d 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -278,6 +278,8 @@
// flagsMask is not modified
}
+ flagsMask = AUDIO_FLAG_NONE;
+
switch (spatializationBehavior) {
case AAUDIO_UNSPECIFIED:
case AAUDIO_SPATIALIZATION_BEHAVIOR_AUTO:
diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp
index 590a7b7..53794f4 100644
--- a/media/libmedia/Android.bp
+++ b/media/libmedia/Android.bp
@@ -311,6 +311,7 @@
srcs: [
":mediaextractorservice_aidl",
"IDataSource.cpp",
+ "IHDCP.cpp",
"BufferingSettings.cpp",
"mediaplayer.cpp",
"IMediaHTTPConnection.cpp",
@@ -374,6 +375,7 @@
"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 0000000..31e3753
--- /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 <utils/Log.h>
+
+#include <binder/Parcel.h>
+#include <media/IHDCP.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/foundation/ADebug.h>
+
+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<IHDCPObserver> {
+ explicit BpHDCPObserver(const sp<IBinder> &impl)
+ : BpInterface<IHDCPObserver>(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<Parcel *>(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<IHDCP> {
+ explicit BpHDCP(const sp<IBinder> &impl)
+ : BpInterface<IHDCP>(impl) {
+ }
+
+ virtual status_t setObserver(const sp<IHDCPObserver> &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> &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<Parcel *>(&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<IHDCPObserver> observer =
+ interface_cast<IHDCPObserver>(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> 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 07c0ac5..f8326b6 100644
--- a/media/libmedia/IMediaPlayerService.cpp
+++ b/media/libmedia/IMediaPlayerService.cpp
@@ -20,6 +20,7 @@
#include <binder/Parcel.h>
#include <binder/IMemory.h>
+#include <media/IHDCP.h>
#include <media/IMediaCodecList.h>
#include <media/IMediaHTTPService.h>
#include <media/IMediaPlayerService.h>
@@ -41,6 +42,7 @@
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 @@
return interface_cast<IMediaRecorder>(reply.readStrongBinder());
}
+ virtual sp<IHDCP> makeHDCP(bool createEncryptionModule) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
+ data.writeInt32(createEncryptionModule);
+ remote()->transact(MAKE_HDCP, data, &reply);
+ return interface_cast<IHDCP>(reply.readStrongBinder());
+ }
+
virtual void addBatteryData(uint32_t params) {
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
@@ -157,6 +167,13 @@
reply->writeStrongBinder(IInterface::asBinder(retriever));
return NO_ERROR;
} break;
+ case MAKE_HDCP: {
+ CHECK_INTERFACE(IMediaPlayerService, data, reply);
+ bool createEncryptionModule = data.readInt32();
+ sp<IHDCP> 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 0000000..352561e
--- /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 <binder/IInterface.h>
+#include <media/hardware/HDCPAPI.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <ui/GraphicBuffer.h>
+
+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<IHDCPObserver> &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> &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<IHDCPObserver> {
+ virtual status_t onTransact(
+ uint32_t code, const Parcel &data, Parcel *reply,
+ uint32_t flags = 0);
+};
+
+struct BnHDCP : public BnInterface<IHDCP> {
+ 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 6070673..d35472b 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 @@
audio_session_t audioSessionId = AUDIO_SESSION_ALLOCATE,
const android::content::AttributionSourceState &attributionSource =
android::content::AttributionSourceState()) = 0;
+ virtual sp<IHDCP> makeHDCP(bool createEncryptionModule) = 0;
virtual sp<IMediaCodecList> getCodecList() const = 0;
// Connects to a remote display.
diff --git a/media/libmediaplayerservice/Android.bp b/media/libmediaplayerservice/Android.bp
index 718f782..47afbfa 100644
--- a/media/libmediaplayerservice/Android.bp
+++ b/media/libmediaplayerservice/Android.bp
@@ -23,11 +23,13 @@
srcs: [
"ActivityManager.cpp",
"DeathNotifier.cpp",
+ "HDCP.cpp",
"MediaPlayerFactory.cpp",
"MediaPlayerService.cpp",
"MediaRecorderClient.cpp",
"MetadataRetrieverClient.cpp",
"StagefrightMetadataRetriever.cpp",
+ "RemoteDisplay.cpp",
"StagefrightRecorder.cpp",
"TestPlayerStub.cpp",
],
@@ -72,6 +74,7 @@
"libnetd_client",
"libpowermanager",
"libstagefright",
+ "libstagefright_wfd",
"libstagefright_foundation",
"libstagefright_httplive",
"libutils",
@@ -120,6 +123,10 @@
"libmediautils_headers",
],
+ include_dirs: [
+ "frameworks/av/media/libstagefright/wifi-display",
+ ],
+
export_include_dirs: [
".",
],
diff --git a/media/libmediaplayerservice/HDCP.cpp b/media/libmediaplayerservice/HDCP.cpp
new file mode 100644
index 0000000..afe3936
--- /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 <utils/Log.h>
+
+#include "HDCP.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+
+#include <dlfcn.h>
+
+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<IHDCPObserver> &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> &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<HDCP *>(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 0000000..83c61b5
--- /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 <media/IHDCP.h>
+#include <utils/Mutex.h>
+
+namespace android {
+
+struct HDCP : public BnHDCP {
+ explicit HDCP(bool createEncryptionModule);
+ virtual ~HDCP();
+
+ virtual status_t setObserver(const sp<IHDCPObserver> &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> &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<IHDCPObserver> 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 10a1da7..3d5e9cb 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -84,6 +84,8 @@
#include "TestPlayerStub.h"
#include <nuplayer/NuPlayerDriver.h>
+#include "HDCP.h"
+#include "RemoteDisplay.h"
static const int kDumpLockRetries = 50;
static const int kDumpLockSleepUs = 20000;
@@ -523,13 +525,18 @@
return MediaCodecList::getLocalInstance();
}
-sp<IRemoteDisplay> MediaPlayerService::listenForRemoteDisplay(
- const String16 &/*opPackageName*/,
- const sp<IRemoteDisplayClient>& /*client*/,
- const String8& /*iface*/) {
- ALOGE("listenForRemoteDisplay is no longer supported!");
+sp<IHDCP> MediaPlayerService::makeHDCP(bool createEncryptionModule) {
+ return new HDCP(createEncryptionModule);
+}
- return NULL;
+sp<IRemoteDisplay> MediaPlayerService::listenForRemoteDisplay(
+ const String16 &opPackageName,
+ const sp<IRemoteDisplayClient>& client, const String8& iface) {
+ if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) {
+ return NULL;
+ }
+
+ return new RemoteDisplay(opPackageName, client, iface.c_str());
}
status_t MediaPlayerService::AudioOutput::dump(int fd, const Vector<String16>& args) const
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index cb544bd..1cdb773 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -251,6 +251,7 @@
const AttributionSourceState& attributionSource);
virtual sp<IMediaCodecList> getCodecList() const;
+ virtual sp<IHDCP> makeHDCP(bool createEncryptionModule);
virtual sp<IRemoteDisplay> listenForRemoteDisplay(const String16 &opPackageName,
const sp<IRemoteDisplayClient>& client, const String8& iface);
diff --git a/media/libmediaplayerservice/RemoteDisplay.cpp b/media/libmediaplayerservice/RemoteDisplay.cpp
new file mode 100644
index 0000000..80cca26
--- /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 <media/IRemoteDisplayClient.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/ANetworkSession.h>
+
+namespace android {
+
+RemoteDisplay::RemoteDisplay(
+ const String16 &opPackageName,
+ const sp<IRemoteDisplayClient> &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 0000000..d4573e9
--- /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 <media/IMediaPlayerService.h>
+#include <media/IRemoteDisplay.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ALooper;
+struct ANetworkSession;
+class IRemoteDisplayClient;
+struct WifiDisplaySource;
+
+struct RemoteDisplay : public BnRemoteDisplay {
+ RemoteDisplay(
+ const String16 &opPackageName,
+ const sp<IRemoteDisplayClient> &client,
+ const char *iface);
+
+ virtual status_t pause();
+ virtual status_t resume();
+ virtual status_t dispose();
+
+protected:
+ virtual ~RemoteDisplay();
+
+private:
+ sp<ALooper> mNetLooper;
+ sp<ALooper> mLooper;
+ sp<ANetworkSession> mNetSession;
+ sp<WifiDisplaySource> 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 0000000..e3b77ec
--- /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 <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <linux/tcp.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <media/stagefright/ANetworkSession.h>
+#include <media/stagefright/ParsedMessage.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ByteUtils.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+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<AMessage> ¬ify);
+
+ int32_t sessionID() const;
+ int socket() const;
+ sp<AMessage> 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<ABuffer> mBuffer;
+ };
+
+ int32_t mSessionID;
+ State mState;
+ Mode mMode;
+ int mSocket;
+ sp<AMessage> mNotify;
+ bool mSawReceiveFailure, mSawSendFailure;
+ int32_t mUDPRetries;
+
+ List<Fragment> 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<AMessage> ¬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<AMessage> 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<AMessage> 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<ABuffer> 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<AMessage> 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<ABuffer> packet = new ABuffer(packetSize);
+ memcpy(packet->data(), mInBuffer.c_str() + 2, packetSize);
+
+ int64_t nowUs = ALooper::GetNowUs();
+ packet->meta()->setInt64("arrivalTimeUs", nowUs);
+
+ sp<AMessage> 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<AMessage> notify = mNotify->dup();
+ notify->setInt32("sessionID", mSessionID);
+ notify->setInt32("reason", kWhatBinaryData);
+ notify->setInt32("channel", mInBuffer.c_str()[1]);
+
+ sp<ABuffer> 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<ParsedMessage> msg =
+ ParsedMessage::Parse(
+ mInBuffer.c_str(), mInBuffer.size(), err != OK, &length);
+
+ if (msg == NULL) {
+ break;
+ }
+
+ sp<AMessage> 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<ABuffer> 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<AMessage> 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<ABuffer> &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<AMessage> 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<ABuffer> 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<AMessage> 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<AMessage> 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<AMessage> ¬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<AMessage> ¬ify, int32_t *sessionID) {
+ return createClientOrServer(
+ kModeCreateRTSPServer,
+ &addr,
+ port,
+ NULL /* remoteHost */,
+ 0 /* remotePort */,
+ notify,
+ sessionID);
+}
+
+status_t ANetworkSession::createUDPSession(
+ unsigned localPort, const sp<AMessage> ¬ify, int32_t *sessionID) {
+ return createUDPSession(localPort, NULL, 0, notify, sessionID);
+}
+
+status_t ANetworkSession::createUDPSession(
+ unsigned localPort,
+ const char *remoteHost,
+ unsigned remotePort,
+ const sp<AMessage> ¬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<AMessage> ¬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<AMessage> ¬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<AMessage> ¬ify,
+ int32_t *sessionID) {
+ Mutex::Autolock autoLock(mLock);
+
+ *sessionID = 0;
+ status_t err = OK;
+ int s, res;
+ sp<Session> 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> 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> 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> 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> &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<sp<Session> > sessionsToAdd;
+
+ for (size_t i = mSessions.size(); res > 0 && i > 0;) {
+ i--;
+ const sp<Session> &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<Session> 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> 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 896e021..6e2eef3 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -232,6 +232,7 @@
"ACodecBufferChannel.cpp",
"AHierarchicalStateMachine.cpp",
"AMRWriter.cpp",
+ "ANetworkSession.cpp",
"AudioSource.cpp",
"BufferImpl.cpp",
"CallbackDataSource.cpp",
@@ -263,10 +264,12 @@
"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/ParsedMessage.cpp b/media/libstagefright/ParsedMessage.cpp
new file mode 100644
index 0000000..4bfc454
--- /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 <ctype.h>
+#include <media/stagefright/ParsedMessage.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+namespace android {
+
+// static
+sp<ParsedMessage> ParsedMessage::Parse(
+ const char *data, size_t size, bool noMoreData, size_t *length) {
+ sp<ParsedMessage> 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 0000000..d737055
--- /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 <inttypes.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/SurfaceMediaSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <OMX_IVCommon.h>
+#include <media/hardware/HardwareAPI.h>
+#include <media/hardware/MetadataBufferType.h>
+
+#include <ui/GraphicBuffer.h>
+#include <gui/BufferItem.h>
+#include <gui/ISurfaceComposer.h>
+#include <OMX_Component.h>
+
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include <private/gui/ComposerService.h>
+
+namespace android {
+
+SurfaceMediaSource::SurfaceMediaSource(uint32_t bufferWidth, uint32_t bufferHeight) :
+ mWidth(bufferWidth),
+ mHeight(bufferHeight),
+ mCurrentSlot(BufferQueue::INVALID_BUFFER_SLOT),
+ mNumPendingBuffers(0),
+ mCurrentTimestamp(0),
+ mFrameRate(30),
+ mStarted(false),
+ mNumFramesReceived(0),
+ mNumFramesEncoded(0),
+ mFirstFrameTimestamp(0),
+ mMaxAcquiredBufferCount(4), // XXX double-check the default
+ mUseAbsoluteTimestamps(false) {
+ ALOGV("SurfaceMediaSource");
+
+ if (bufferWidth == 0 || bufferHeight == 0) {
+ ALOGE("Invalid dimensions %dx%d", bufferWidth, bufferHeight);
+ }
+
+ BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+ mConsumer->setDefaultBufferSize(bufferWidth, bufferHeight);
+ mConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_VIDEO_ENCODER |
+ GRALLOC_USAGE_HW_TEXTURE);
+
+ sp<ISurfaceComposer> composer(ComposerService::getComposerService());
+
+ // Note that we can't create an sp<...>(this) in a ctor that will not keep a
+ // reference once the ctor ends, as that would cause the refcount of 'this'
+ // dropping to 0 at the end of the ctor. Since all we need is a wp<...>
+ // that's what we create.
+ wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this);
+ sp<BufferQueue::ProxyConsumerListener> proxy = new BufferQueue::ProxyConsumerListener(listener);
+
+ status_t err = mConsumer->consumerConnect(proxy, false);
+ if (err != NO_ERROR) {
+ ALOGE("SurfaceMediaSource: error connecting to BufferQueue: %s (%d)",
+ strerror(-err), err);
+ }
+}
+
+SurfaceMediaSource::~SurfaceMediaSource() {
+ ALOGV("~SurfaceMediaSource");
+ CHECK(!mStarted);
+}
+
+nsecs_t SurfaceMediaSource::getTimestamp() {
+ ALOGV("getTimestamp");
+ Mutex::Autolock lock(mMutex);
+ return mCurrentTimestamp;
+}
+
+void SurfaceMediaSource::setFrameAvailableListener(
+ const sp<FrameAvailableListener>& listener) {
+ ALOGV("setFrameAvailableListener");
+ Mutex::Autolock lock(mMutex);
+ mFrameAvailableListener = listener;
+}
+
+void SurfaceMediaSource::dumpState(String8& result) const
+{
+ char buffer[1024];
+ dumpState(result, "", buffer, 1024);
+}
+
+void SurfaceMediaSource::dumpState(
+ String8& result,
+ const char* /* prefix */,
+ char* buffer,
+ size_t /* SIZE */) const
+{
+ Mutex::Autolock lock(mMutex);
+
+ result.append(buffer);
+ mConsumer->dumpState(result, "");
+}
+
+status_t SurfaceMediaSource::setFrameRate(int32_t fps)
+{
+ ALOGV("setFrameRate");
+ Mutex::Autolock lock(mMutex);
+ const int MAX_FRAME_RATE = 60;
+ if (fps < 0 || fps > MAX_FRAME_RATE) {
+ return BAD_VALUE;
+ }
+ mFrameRate = fps;
+ return OK;
+}
+
+MetadataBufferType SurfaceMediaSource::metaDataStoredInVideoBuffers() const {
+ ALOGV("isMetaDataStoredInVideoBuffers");
+ return kMetadataBufferTypeANWBuffer;
+}
+
+int32_t SurfaceMediaSource::getFrameRate( ) const {
+ ALOGV("getFrameRate");
+ Mutex::Autolock lock(mMutex);
+ return mFrameRate;
+}
+
+status_t SurfaceMediaSource::start(MetaData *params)
+{
+ ALOGV("start");
+
+ Mutex::Autolock lock(mMutex);
+
+ CHECK(!mStarted);
+
+ mStartTimeNs = 0;
+ int64_t startTimeUs;
+ int32_t bufferCount = 0;
+ if (params) {
+ if (params->findInt64(kKeyTime, &startTimeUs)) {
+ mStartTimeNs = startTimeUs * 1000;
+ }
+
+ if (!params->findInt32(kKeyNumBuffers, &bufferCount)) {
+ ALOGE("Failed to find the advertised buffer count");
+ return UNKNOWN_ERROR;
+ }
+
+ if (bufferCount <= 1) {
+ ALOGE("bufferCount %d is too small", bufferCount);
+ return BAD_VALUE;
+ }
+
+ mMaxAcquiredBufferCount = bufferCount;
+ }
+
+ CHECK_GT(mMaxAcquiredBufferCount, 1u);
+
+ status_t err =
+ mConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBufferCount);
+
+ if (err != OK) {
+ return err;
+ }
+
+ mNumPendingBuffers = 0;
+ mStarted = true;
+
+ return OK;
+}
+
+status_t SurfaceMediaSource::setMaxAcquiredBufferCount(size_t count) {
+ ALOGV("setMaxAcquiredBufferCount(%zu)", count);
+ Mutex::Autolock lock(mMutex);
+
+ CHECK_GT(count, 1u);
+ mMaxAcquiredBufferCount = count;
+
+ return OK;
+}
+
+status_t SurfaceMediaSource::setUseAbsoluteTimestamps() {
+ ALOGV("setUseAbsoluteTimestamps");
+ Mutex::Autolock lock(mMutex);
+ mUseAbsoluteTimestamps = true;
+
+ return OK;
+}
+
+status_t SurfaceMediaSource::stop()
+{
+ ALOGV("stop");
+ Mutex::Autolock lock(mMutex);
+
+ if (!mStarted) {
+ return OK;
+ }
+
+ mStarted = false;
+ mFrameAvailableCondition.signal();
+
+ while (mNumPendingBuffers > 0) {
+ ALOGI("Still waiting for %zu buffers to be returned.",
+ mNumPendingBuffers);
+
+#if DEBUG_PENDING_BUFFERS
+ for (size_t i = 0; i < mPendingBuffers.size(); ++i) {
+ ALOGI("%zu: %p", i, mPendingBuffers.itemAt(i));
+ }
+#endif
+
+ mMediaBuffersAvailableCondition.wait(mMutex);
+ }
+
+ mMediaBuffersAvailableCondition.signal();
+
+ return mConsumer->consumerDisconnect();
+}
+
+sp<MetaData> SurfaceMediaSource::getFormat()
+{
+ ALOGV("getFormat");
+
+ Mutex::Autolock lock(mMutex);
+ sp<MetaData> meta = new MetaData;
+
+ meta->setInt32(kKeyWidth, mWidth);
+ meta->setInt32(kKeyHeight, mHeight);
+ // The encoder format is set as an opaque colorformat
+ // The encoder will later find out the actual colorformat
+ // from the GL Frames itself.
+ meta->setInt32(kKeyColorFormat, OMX_COLOR_FormatAndroidOpaque);
+ meta->setInt32(kKeyStride, mWidth);
+ meta->setInt32(kKeySliceHeight, mHeight);
+ meta->setInt32(kKeyFrameRate, mFrameRate);
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW);
+ return meta;
+}
+
+// Pass the data to the MediaBuffer. Pass in only the metadata
+// Note: Call only when you have the lock
+void SurfaceMediaSource::passMetadataBuffer_l(MediaBufferBase **buffer,
+ ANativeWindowBuffer *bufferHandle) const {
+ *buffer = new MediaBuffer(sizeof(VideoNativeMetadata));
+ VideoNativeMetadata *data = (VideoNativeMetadata *)(*buffer)->data();
+ if (data == NULL) {
+ ALOGE("Cannot allocate memory for metadata buffer!");
+ return;
+ }
+ data->eType = metaDataStoredInVideoBuffers();
+ data->pBuffer = bufferHandle;
+ data->nFenceFd = -1;
+ ALOGV("handle = %p, offset = %zu, length = %zu",
+ bufferHandle, (*buffer)->range_length(), (*buffer)->range_offset());
+}
+
+status_t SurfaceMediaSource::read(
+ MediaBufferBase **buffer, const ReadOptions * /* options */) {
+ ALOGV("read");
+ Mutex::Autolock lock(mMutex);
+
+ *buffer = NULL;
+
+ while (mStarted && mNumPendingBuffers == mMaxAcquiredBufferCount) {
+ mMediaBuffersAvailableCondition.wait(mMutex);
+ }
+
+ // Update the current buffer info
+ // TODO: mCurrentSlot can be made a bufferstate since there
+ // can be more than one "current" slots.
+
+ BufferItem item;
+ // If the recording has started and the queue is empty, then just
+ // wait here till the frames come in from the client side
+ while (mStarted) {
+
+ status_t err = mConsumer->acquireBuffer(&item, 0);
+ if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
+ // wait for a buffer to be queued
+ mFrameAvailableCondition.wait(mMutex);
+ } else if (err == OK) {
+ err = item.mFence->waitForever("SurfaceMediaSource::read");
+ if (err) {
+ ALOGW("read: failed to wait for buffer fence: %d", err);
+ }
+
+ // First time seeing the buffer? Added it to the SMS slot
+ if (item.mGraphicBuffer != NULL) {
+ mSlots[item.mSlot].mGraphicBuffer = item.mGraphicBuffer;
+ }
+ mSlots[item.mSlot].mFrameNumber = item.mFrameNumber;
+
+ // check for the timing of this buffer
+ if (mNumFramesReceived == 0 && !mUseAbsoluteTimestamps) {
+ mFirstFrameTimestamp = item.mTimestamp;
+ // Initial delay
+ if (mStartTimeNs > 0) {
+ if (item.mTimestamp < mStartTimeNs) {
+ // This frame predates start of record, discard
+ mConsumer->releaseBuffer(
+ item.mSlot, item.mFrameNumber, EGL_NO_DISPLAY,
+ EGL_NO_SYNC_KHR, Fence::NO_FENCE);
+ continue;
+ }
+ mStartTimeNs = item.mTimestamp - mStartTimeNs;
+ }
+ }
+ item.mTimestamp = mStartTimeNs + (item.mTimestamp - mFirstFrameTimestamp);
+
+ mNumFramesReceived++;
+
+ break;
+ } else {
+ ALOGE("read: acquire failed with error code %d", err);
+ return ERROR_END_OF_STREAM;
+ }
+
+ }
+
+ // If the loop was exited as a result of stopping the recording,
+ // it is OK
+ if (!mStarted) {
+ ALOGV("Read: SurfaceMediaSource is stopped. Returning ERROR_END_OF_STREAM.");
+ return ERROR_END_OF_STREAM;
+ }
+
+ mCurrentSlot = item.mSlot;
+
+ // First time seeing the buffer? Added it to the SMS slot
+ if (item.mGraphicBuffer != NULL) {
+ mSlots[item.mSlot].mGraphicBuffer = item.mGraphicBuffer;
+ }
+ mSlots[item.mSlot].mFrameNumber = item.mFrameNumber;
+
+ mCurrentBuffers.push_back(mSlots[mCurrentSlot].mGraphicBuffer);
+ int64_t prevTimeStamp = mCurrentTimestamp;
+ mCurrentTimestamp = item.mTimestamp;
+
+ mNumFramesEncoded++;
+ // Pass the data to the MediaBuffer. Pass in only the metadata
+
+ passMetadataBuffer_l(buffer, mSlots[mCurrentSlot].mGraphicBuffer->getNativeBuffer());
+
+ (*buffer)->setObserver(this);
+ (*buffer)->add_ref();
+ (*buffer)->meta_data().setInt64(kKeyTime, mCurrentTimestamp / 1000);
+ ALOGV("Frames encoded = %d, timestamp = %" PRId64 ", time diff = %" PRId64,
+ mNumFramesEncoded, mCurrentTimestamp / 1000,
+ mCurrentTimestamp / 1000 - prevTimeStamp / 1000);
+
+ ++mNumPendingBuffers;
+
+#if DEBUG_PENDING_BUFFERS
+ mPendingBuffers.push_back(*buffer);
+#endif
+
+ ALOGV("returning mbuf %p", *buffer);
+
+ return OK;
+}
+
+static buffer_handle_t getMediaBufferHandle(MediaBufferBase *buffer) {
+ // need to convert to char* for pointer arithmetic and then
+ // copy the byte stream into our handle
+ buffer_handle_t bufferHandle;
+ 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<FrameAvailableListener> listener;
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+ mFrameAvailableCondition.broadcast();
+ listener = mFrameAvailableListener;
+ }
+
+ if (listener != NULL) {
+ ALOGV("actually calling onFrameAvailable");
+ listener->onFrameAvailable();
+ }
+}
+
+// SurfaceMediaSource hijacks this event to assume
+// the prodcuer is disconnecting from the BufferQueue
+// and that it should stop the recording
+void SurfaceMediaSource::onBuffersReleased() {
+ ALOGV("onBuffersReleased");
+
+ Mutex::Autolock lock(mMutex);
+
+ mFrameAvailableCondition.signal();
+
+ for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+ mSlots[i].mGraphicBuffer = 0;
+ }
+}
+
+void SurfaceMediaSource::onSidebandStreamChanged() {
+ ALOG_ASSERT(false, "SurfaceMediaSource can't consume sideband streams");
+}
+
+} // end of namespace android
diff --git a/media/libstagefright/include/media/stagefright/ANetworkSession.h b/media/libstagefright/include/media/stagefright/ANetworkSession.h
new file mode 100644
index 0000000..fd3ebaa
--- /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 <media/stagefright/foundation/ABase.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+#include <utils/Thread.h>
+
+#include <netinet/in.h>
+
+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<AMessage> ¬ify,
+ int32_t *sessionID);
+
+ status_t createRTSPServer(
+ const struct in_addr &addr, unsigned port,
+ const sp<AMessage> ¬ify, int32_t *sessionID);
+
+ status_t createUDPSession(
+ unsigned localPort, const sp<AMessage> ¬ify, int32_t *sessionID);
+
+ status_t createUDPSession(
+ unsigned localPort,
+ const char *remoteHost,
+ unsigned remotePort,
+ const sp<AMessage> ¬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<AMessage> ¬ify, int32_t *sessionID);
+
+ // active
+ status_t createTCPDatagramSession(
+ unsigned localPort,
+ const char *remoteHost,
+ unsigned remotePort,
+ const sp<AMessage> ¬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<Thread> mThread;
+
+ int32_t mNextSessionID;
+
+ int mPipeFd[2];
+
+ KeyedVector<int32_t, sp<Session> > 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<AMessage> ¬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 0000000..9d43a93
--- /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 <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AString.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+
+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<ParsedMessage> 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<AString, AString> 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 0000000..c67defe
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/SurfaceMediaSource.h
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_GUI_SURFACEMEDIASOURCE_H
+#define ANDROID_GUI_SURFACEMEDIASOURCE_H
+
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/BufferQueue.h>
+
+#include <utils/threads.h>
+#include <utils/Vector.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MediaBuffer.h>
+
+#include <media/hardware/MetadataBufferType.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class String8;
+class GraphicBuffer;
+
+// ASSUMPTIONS
+// 1. SurfaceMediaSource is initialized with width*height which
+// can never change. However, deqeueue buffer does not currently
+// enforce this as in BufferQueue, dequeue can be used by Surface
+// which can modify the default width and heght. Also neither the width
+// nor height can be 0.
+// 2. setSynchronousMode is never used (basically no one should call
+// setSynchronousMode(false)
+// 3. setCrop, setTransform, setScalingMode should never be used
+// 4. queueBuffer returns a filled buffer to the SurfaceMediaSource. In addition, a
+// timestamp must be provided for the buffer. The timestamp is in
+// nanoseconds, and must be monotonically increasing. Its other semantics
+// (zero point, etc) are client-dependent and should be documented by the
+// client.
+// 5. Once disconnected, SurfaceMediaSource can be reused (can not
+// connect again)
+// 6. Stop is a hard stop, the last few frames held by the encoder
+// may be dropped. It is possible to wait for the buffers to be
+// returned (but not implemented)
+
+#define DEBUG_PENDING_BUFFERS 0
+
+class SurfaceMediaSource : public MediaSource,
+ public MediaBufferObserver,
+ protected ConsumerListener {
+public:
+ enum { MIN_UNDEQUEUED_BUFFERS = 4};
+
+ struct FrameAvailableListener : public virtual RefBase {
+ // onFrameAvailable() is called from queueBuffer() is the FIFO is
+ // empty. You can use SurfaceMediaSource::getQueuedCount() to
+ // figure out if there are more frames waiting.
+ // This is called without any lock held can be called concurrently by
+ // multiple threads.
+ virtual void onFrameAvailable() = 0;
+ };
+
+ SurfaceMediaSource(uint32_t bufferWidth, uint32_t bufferHeight);
+
+ virtual ~SurfaceMediaSource();
+
+ // For the MediaSource interface for use by StageFrightRecorder:
+ virtual status_t start(MetaData *params = NULL);
+ virtual status_t stop();
+ virtual status_t read(MediaBufferBase **buffer,
+ const ReadOptions *options = NULL);
+ virtual sp<MetaData> getFormat();
+
+ // Get / Set the frame rate used for encoding. Default fps = 30
+ status_t setFrameRate(int32_t fps) ;
+ int32_t getFrameRate( ) const;
+
+ // The call for the StageFrightRecorder to tell us that
+ // it is done using the MediaBuffer data so that its state
+ // can be set to FREE for dequeuing
+ virtual void signalBufferReturned(MediaBufferBase* buffer);
+ // end of MediaSource interface
+
+ // getTimestamp retrieves the timestamp associated with the image
+ // set by the most recent call to read()
+ //
+ // The timestamp is in nanoseconds, and is monotonically increasing. Its
+ // other semantics (zero point, etc) are source-dependent and should be
+ // documented by the source.
+ int64_t getTimestamp();
+
+ // setFrameAvailableListener sets the listener object that will be notified
+ // when a new frame becomes available.
+ void setFrameAvailableListener(const sp<FrameAvailableListener>& listener);
+
+ // dump our state in a String
+ void dumpState(String8& result) const;
+ void dumpState(String8& result, const char* prefix, char* buffer,
+ size_t SIZE) const;
+
+ // metaDataStoredInVideoBuffers tells the encoder what kind of metadata
+ // is passed through the buffers. Currently, it is set to ANWBuffer
+ MetadataBufferType metaDataStoredInVideoBuffers() const;
+
+ sp<IGraphicBufferProducer> getProducer() const { return mProducer; }
+
+ // To be called before start()
+ status_t setMaxAcquiredBufferCount(size_t count);
+
+ // To be called before start()
+ status_t setUseAbsoluteTimestamps();
+
+protected:
+
+ // Implementation of the BufferQueue::ConsumerListener interface. These
+ // calls are used to notify the Surface of asynchronous events in the
+ // BufferQueue.
+ virtual void onFrameAvailable(const BufferItem& item);
+
+ // Used as a hook to BufferQueue::disconnect()
+ // This is called by the client side when it is done
+ // TODO: Currently, this also sets mStopped to true which
+ // is needed for unblocking the encoder which might be
+ // waiting to read more frames. So if on the client side,
+ // the same thread supplies the frames and also calls stop
+ // on the encoder, the client has to call disconnect before
+ // it calls stop.
+ // In the case of the camera,
+ // that need not be required since the thread supplying the
+ // frames is separate than the one calling stop.
+ virtual void onBuffersReleased();
+
+ // SurfaceMediaSource can't handle sideband streams, so this is not expected
+ // to ever be called. Does nothing.
+ virtual void onSidebandStreamChanged();
+
+ static bool isExternalFormat(uint32_t format);
+
+private:
+ // A BufferQueue, represented by these interfaces, is the exchange point
+ // between the producer and this consumer
+ sp<IGraphicBufferProducer> mProducer;
+ sp<IGraphicBufferConsumer> mConsumer;
+
+ struct SlotData {
+ sp<GraphicBuffer> mGraphicBuffer;
+ uint64_t mFrameNumber;
+ };
+
+ // mSlots caches GraphicBuffers and frameNumbers from the buffer queue
+ SlotData mSlots[BufferQueue::NUM_BUFFER_SLOTS];
+
+ // The permenent width and height of SMS buffers
+ int mWidth;
+ int mHeight;
+
+ // mCurrentSlot is the buffer slot index of the buffer that is currently
+ // being used by buffer consumer
+ // (e.g. StageFrightRecorder in the case of SurfaceMediaSource or GLTexture
+ // in the case of Surface).
+ // It is initialized to INVALID_BUFFER_SLOT,
+ // indicating that no buffer slot is currently bound to the texture. Note,
+ // however, that a value of INVALID_BUFFER_SLOT does not necessarily mean
+ // that no buffer is bound to the texture. A call to setBufferCount will
+ // reset mCurrentTexture to INVALID_BUFFER_SLOT.
+ int mCurrentSlot;
+
+ // mCurrentBuffers is a list of the graphic buffers that are being used by
+ // buffer consumer (i.e. the video encoder). It's possible that these
+ // buffers are not associated with any buffer slots, so we must track them
+ // separately. Buffers are added to this list in read, and removed from
+ // this list in signalBufferReturned
+ Vector<sp<GraphicBuffer> > mCurrentBuffers;
+
+ size_t mNumPendingBuffers;
+
+#if DEBUG_PENDING_BUFFERS
+ Vector<MediaBufferBase *> mPendingBuffers;
+#endif
+
+ // mCurrentTimestamp is the timestamp for the current texture. It
+ // gets set to mLastQueuedTimestamp each time updateTexImage is called.
+ int64_t mCurrentTimestamp;
+
+ // mFrameAvailableListener is the listener object that will be called when a
+ // new frame becomes available. If it is not NULL it will be called from
+ // queueBuffer.
+ sp<FrameAvailableListener> mFrameAvailableListener;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of SurfaceMediaSource objects. It must be locked whenever the
+ // member variables are accessed.
+ mutable Mutex mMutex;
+
+ ////////////////////////// For MediaSource
+ // Set to a default of 30 fps if not specified by the client side
+ int32_t mFrameRate;
+
+ // mStarted is a flag to check if the recording is going on
+ bool mStarted;
+
+ // mNumFramesReceived indicates the number of frames recieved from
+ // the client side
+ int mNumFramesReceived;
+ // mNumFramesEncoded indicates the number of frames passed on to the
+ // encoder
+ int mNumFramesEncoded;
+
+ // mFirstFrameTimestamp is the timestamp of the first received frame.
+ // It is used to offset the output timestamps so recording starts at time 0.
+ int64_t mFirstFrameTimestamp;
+ // mStartTimeNs is the start time passed into the source at start, used to
+ // offset timestamps.
+ int64_t mStartTimeNs;
+
+ size_t mMaxAcquiredBufferCount;
+
+ bool mUseAbsoluteTimestamps;
+
+ // mFrameAvailableCondition condition used to indicate whether there
+ // is a frame available for dequeuing
+ Condition mFrameAvailableCondition;
+
+ Condition mMediaBuffersAvailableCondition;
+
+ // Allocate and return a new MediaBuffer and pass the ANW buffer as metadata into it.
+ void passMetadataBuffer_l(MediaBufferBase **buffer, ANativeWindowBuffer *bufferHandle) const;
+
+ // Avoid copying and equating and default constructor
+ DISALLOW_EVIL_CONSTRUCTORS(SurfaceMediaSource);
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_GUI_SURFACEMEDIASOURCE_H
diff --git a/media/libstagefright/tests/Android.bp b/media/libstagefright/tests/Android.bp
index 581292e..f10f2b4 100644
--- a/media/libstagefright/tests/Android.bp
+++ b/media/libstagefright/tests/Android.bp
@@ -20,6 +20,45 @@
}
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",
srcs: ["MediaCodecListOverrides_test.cpp"],
diff --git a/media/libstagefright/tests/DummyRecorder.cpp b/media/libstagefright/tests/DummyRecorder.cpp
new file mode 100644
index 0000000..596b7e6
--- /dev/null
+++ b/media/libstagefright/tests/DummyRecorder.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "DummyRecorder"
+// #define LOG_NDEBUG 0
+
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include "DummyRecorder.h"
+
+#include <utils/Log.h>
+
+namespace android {
+
+// static
+void *DummyRecorder::threadWrapper(void *pthis) {
+ ALOGV("ThreadWrapper: %p", pthis);
+ DummyRecorder *writer = static_cast<DummyRecorder *>(pthis);
+ writer->readFromSource();
+ return NULL;
+}
+
+
+status_t DummyRecorder::start() {
+ ALOGV("Start");
+ mStarted = true;
+
+ mSource->start();
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+ int err = pthread_create(&mThread, &attr, threadWrapper, this);
+ pthread_attr_destroy(&attr);
+
+ if (err) {
+ ALOGE("Error creating thread!");
+ return -ENODEV;
+ }
+ return OK;
+}
+
+
+status_t DummyRecorder::stop() {
+ ALOGV("Stop");
+ mStarted = false;
+
+ mSource->stop();
+ void *dummy;
+ pthread_join(mThread, &dummy);
+ status_t err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
+
+ ALOGV("Ending the reading thread");
+ return err;
+}
+
+// pretend to read the source buffers
+void DummyRecorder::readFromSource() {
+ ALOGV("ReadFromSource");
+ if (!mStarted) {
+ return;
+ }
+
+ status_t err = OK;
+ MediaBufferBase *buffer;
+ ALOGV("A fake writer accessing the frames");
+ while (mStarted && (err = mSource->read(&buffer)) == OK){
+ // if not getting a valid buffer from source, then exit
+ if (buffer == NULL) {
+ return;
+ }
+ buffer->release();
+ buffer = NULL;
+ }
+}
+
+
+} // end of namespace android
diff --git a/media/libstagefright/tests/DummyRecorder.h b/media/libstagefright/tests/DummyRecorder.h
new file mode 100644
index 0000000..0759777
--- /dev/null
+++ b/media/libstagefright/tests/DummyRecorder.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DUMMY_RECORDER_H_
+#define DUMMY_RECORDER_H_
+
+#include <pthread.h>
+#include <utils/String8.h>
+#include <media/stagefright/foundation/ABase.h>
+
+
+namespace android {
+
+struct MediaSource;
+class MediaBuffer;
+
+class DummyRecorder {
+ public:
+ // The media source from which this will receive frames
+ sp<MediaSource> mSource;
+ bool mStarted;
+ pthread_t mThread;
+
+ status_t start();
+ status_t stop();
+
+ // actual entry point for the thread
+ void readFromSource();
+
+ // static function to wrap the actual thread entry point
+ static void *threadWrapper(void *pthis);
+
+ explicit DummyRecorder(const sp<MediaSource> &source) : mSource(source)
+ , mStarted(false) {}
+ ~DummyRecorder( ) {}
+
+ private:
+
+ DISALLOW_EVIL_CONSTRUCTORS(DummyRecorder);
+};
+
+} // end of namespace android
+#endif
+
+
diff --git a/media/libstagefright/tests/SurfaceMediaSource_test.cpp b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
new file mode 100644
index 0000000..1b1c3b8
--- /dev/null
+++ b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
@@ -0,0 +1,944 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SurfaceMediaSource_test"
+
+#include <gtest/gtest.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include <utils/Errors.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <GLES2/gl2.h>
+
+#include <media/stagefright/SurfaceMediaSource.h>
+#include <media/mediarecorder.h>
+
+#include <ui/GraphicBuffer.h>
+#include <gui/Surface.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
+
+#include <binder/ProcessState.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <OMX_Component.h>
+
+#include "DummyRecorder.h"
+
+
+namespace android {
+
+class GLTest : public ::testing::Test {
+protected:
+
+ GLTest():
+ mEglDisplay(EGL_NO_DISPLAY),
+ mEglSurface(EGL_NO_SURFACE),
+ mEglContext(EGL_NO_CONTEXT) {
+ }
+
+ virtual void SetUp() {
+ ALOGV("GLTest::SetUp()");
+ mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);
+
+ EGLint majorVersion;
+ EGLint minorVersion;
+ EXPECT_TRUE(eglInitialize(mEglDisplay, &majorVersion, &minorVersion));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ RecordProperty("EglVersionMajor", majorVersion);
+ RecordProperty("EglVersionMajor", minorVersion);
+
+ EGLint numConfigs = 0;
+ EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &mGlConfig,
+ 1, &numConfigs));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ char* displaySecsEnv = getenv("GLTEST_DISPLAY_SECS");
+ if (displaySecsEnv != NULL) {
+ mDisplaySecs = atoi(displaySecsEnv);
+ if (mDisplaySecs < 0) {
+ mDisplaySecs = 0;
+ }
+ } else {
+ mDisplaySecs = 0;
+ }
+
+ if (mDisplaySecs > 0) {
+ mComposerClient = new SurfaceComposerClient;
+ ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
+
+ mSurfaceControl = mComposerClient->createSurface(
+ String8("Test Surface"),
+ getSurfaceWidth(), getSurfaceHeight(),
+ PIXEL_FORMAT_RGB_888, 0);
+
+ ASSERT_TRUE(mSurfaceControl != NULL);
+ ASSERT_TRUE(mSurfaceControl->isValid());
+
+ SurfaceComposerClient::Transaction{}
+ .setLayer(mSurfaceControl, 0x7FFFFFFF)
+ .show(mSurfaceControl)
+ .apply();
+
+ sp<ANativeWindow> window = mSurfaceControl->getSurface();
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
+ window.get(), NULL);
+ } else {
+ ALOGV("No actual display. Choosing EGLSurface based on SurfaceMediaSource");
+ sp<IGraphicBufferProducer> sms = (new SurfaceMediaSource(
+ getSurfaceWidth(), getSurfaceHeight()))->getProducer();
+ sp<Surface> stc = new Surface(sms);
+ sp<ANativeWindow> window = stc;
+
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
+ window.get(), NULL);
+ }
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_SURFACE, mEglSurface);
+
+ mEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT,
+ getContextAttribs());
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_CONTEXT, mEglContext);
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ EGLint w, h;
+ EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &w));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &h));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ RecordProperty("EglSurfaceWidth", w);
+ RecordProperty("EglSurfaceHeight", h);
+
+ glViewport(0, 0, w, h);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ }
+
+ virtual void TearDown() {
+ // Display the result
+ if (mDisplaySecs > 0 && mEglSurface != EGL_NO_SURFACE) {
+ eglSwapBuffers(mEglDisplay, mEglSurface);
+ sleep(mDisplaySecs);
+ }
+
+ if (mComposerClient != NULL) {
+ mComposerClient->dispose();
+ }
+ if (mEglContext != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEglDisplay, mEglContext);
+ }
+ if (mEglSurface != EGL_NO_SURFACE) {
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ }
+ if (mEglDisplay != EGL_NO_DISPLAY) {
+ eglTerminate(mEglDisplay);
+ }
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ }
+
+ virtual EGLint const* getConfigAttribs() {
+ ALOGV("GLTest getConfigAttribs");
+ static EGLint sDefaultConfigAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_DEPTH_SIZE, 16,
+ EGL_STENCIL_SIZE, 8,
+ EGL_NONE };
+
+ return sDefaultConfigAttribs;
+ }
+
+ virtual EGLint const* getContextAttribs() {
+ static EGLint sDefaultContextAttribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE };
+
+ return sDefaultContextAttribs;
+ }
+
+ virtual EGLint getSurfaceWidth() {
+ return 512;
+ }
+
+ virtual EGLint getSurfaceHeight() {
+ return 512;
+ }
+
+ void loadShader(GLenum shaderType, const char* pSource, GLuint* outShader) {
+ GLuint shader = glCreateShader(shaderType);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ if (shader) {
+ glShaderSource(shader, 1, &pSource, NULL);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ glCompileShader(shader);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ GLint compiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ if (!compiled) {
+ GLint infoLen = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ if (infoLen) {
+ char* buf = (char*) malloc(infoLen);
+ if (buf) {
+ glGetShaderInfoLog(shader, infoLen, NULL, buf);
+ printf("Shader compile log:\n%s\n", buf);
+ free(buf);
+ FAIL();
+ }
+ } else {
+ char* buf = (char*) malloc(0x1000);
+ if (buf) {
+ glGetShaderInfoLog(shader, 0x1000, NULL, buf);
+ printf("Shader compile log:\n%s\n", buf);
+ free(buf);
+ FAIL();
+ }
+ }
+ glDeleteShader(shader);
+ shader = 0;
+ }
+ }
+ ASSERT_TRUE(shader != 0);
+ *outShader = shader;
+ }
+
+ void createProgram(const char* pVertexSource, const char* pFragmentSource,
+ GLuint* outPgm) {
+ GLuint vertexShader, fragmentShader;
+ {
+ SCOPED_TRACE("compiling vertex shader");
+ loadShader(GL_VERTEX_SHADER, pVertexSource, &vertexShader);
+ if (HasFatalFailure()) {
+ return;
+ }
+ }
+ {
+ SCOPED_TRACE("compiling fragment shader");
+ loadShader(GL_FRAGMENT_SHADER, pFragmentSource, &fragmentShader);
+ if (HasFatalFailure()) {
+ return;
+ }
+ }
+
+ GLuint program = glCreateProgram();
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ if (program) {
+ glAttachShader(program, vertexShader);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ glAttachShader(program, fragmentShader);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ glLinkProgram(program);
+ GLint linkStatus = GL_FALSE;
+ glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+ if (linkStatus != GL_TRUE) {
+ GLint bufLength = 0;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
+ if (bufLength) {
+ char* buf = (char*) malloc(bufLength);
+ if (buf) {
+ glGetProgramInfoLog(program, bufLength, NULL, buf);
+ printf("Program link log:\n%s\n", buf);
+ free(buf);
+ FAIL();
+ }
+ }
+ glDeleteProgram(program);
+ program = 0;
+ }
+ }
+ glDeleteShader(vertexShader);
+ glDeleteShader(fragmentShader);
+ ASSERT_TRUE(program != 0);
+ *outPgm = program;
+ }
+
+ static int abs(int value) {
+ return value > 0 ? value : -value;
+ }
+
+ ::testing::AssertionResult checkPixel(int x, int y, int r,
+ int g, int b, int a, int tolerance=2) {
+ GLubyte pixel[4];
+ String8 msg;
+ glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
+ GLenum err = glGetError();
+ if (err != GL_NO_ERROR) {
+ msg += String8::format("error reading pixel: %#x", err);
+ while ((err = glGetError()) != GL_NO_ERROR) {
+ msg += String8::format(", %#x", err);
+ }
+ fprintf(stderr, "pixel check failure: %s\n", msg.string());
+ return ::testing::AssertionFailure(
+ ::testing::Message(msg.string()));
+ }
+ if (r >= 0 && abs(r - int(pixel[0])) > tolerance) {
+ msg += String8::format("r(%d isn't %d)", pixel[0], r);
+ }
+ if (g >= 0 && abs(g - int(pixel[1])) > tolerance) {
+ if (!msg.isEmpty()) {
+ msg += " ";
+ }
+ msg += String8::format("g(%d isn't %d)", pixel[1], g);
+ }
+ if (b >= 0 && abs(b - int(pixel[2])) > tolerance) {
+ if (!msg.isEmpty()) {
+ msg += " ";
+ }
+ msg += String8::format("b(%d isn't %d)", pixel[2], b);
+ }
+ if (a >= 0 && abs(a - int(pixel[3])) > tolerance) {
+ if (!msg.isEmpty()) {
+ msg += " ";
+ }
+ msg += String8::format("a(%d isn't %d)", pixel[3], a);
+ }
+ if (!msg.isEmpty()) {
+ fprintf(stderr, "pixel check failure: %s\n", msg.string());
+ return ::testing::AssertionFailure(
+ ::testing::Message(msg.string()));
+ } else {
+ return ::testing::AssertionSuccess();
+ }
+ }
+
+ int mDisplaySecs;
+ sp<SurfaceComposerClient> mComposerClient;
+ sp<SurfaceControl> mSurfaceControl;
+
+ EGLDisplay mEglDisplay;
+ EGLSurface mEglSurface;
+ EGLContext mEglContext;
+ EGLConfig mGlConfig;
+};
+
+///////////////////////////////////////////////////////////////////////
+// Class for the NON-GL tests
+///////////////////////////////////////////////////////////////////////
+class SurfaceMediaSourceTest : public ::testing::Test {
+public:
+
+ SurfaceMediaSourceTest( ): mYuvTexWidth(176), mYuvTexHeight(144) { }
+ void oneBufferPass(int width, int height );
+ void oneBufferPassNoFill(int width, int height );
+ static void fillYV12Buffer(uint8_t* buf, int w, int h, int stride) ;
+ static void fillYV12BufferRect(uint8_t* buf, int w, int h,
+ int stride, const android_native_rect_t& rect) ;
+protected:
+
+ virtual void SetUp() {
+ android::ProcessState::self()->startThreadPool();
+ mSMS = new SurfaceMediaSource(mYuvTexWidth, mYuvTexHeight);
+ mSTC = new Surface(mSMS->getProducer());
+ mANW = mSTC;
+ }
+
+ virtual void TearDown() {
+ mSMS.clear();
+ mSTC.clear();
+ mANW.clear();
+ }
+
+ const int mYuvTexWidth;
+ const int mYuvTexHeight;
+
+ sp<SurfaceMediaSource> mSMS;
+ sp<Surface> mSTC;
+ sp<ANativeWindow> mANW;
+};
+
+///////////////////////////////////////////////////////////////////////
+// Class for the GL tests
+///////////////////////////////////////////////////////////////////////
+class SurfaceMediaSourceGLTest : public GLTest {
+public:
+
+ SurfaceMediaSourceGLTest( ): mYuvTexWidth(176), mYuvTexHeight(144) { }
+ virtual EGLint const* getConfigAttribs();
+ void oneBufferPassGL(int num = 0);
+ static sp<MediaRecorder> setUpMediaRecorder(int fileDescriptor, int videoSource,
+ int outputFormat, int videoEncoder, int width, int height, int fps);
+protected:
+
+ virtual void SetUp() {
+ ALOGV("SMS-GLTest::SetUp()");
+ android::ProcessState::self()->startThreadPool();
+ mSMS = new SurfaceMediaSource(mYuvTexWidth, mYuvTexHeight);
+ mSTC = new Surface(mSMS->getProducer());
+ mANW = mSTC;
+
+ // Doing the setup related to the GL Side
+ GLTest::SetUp();
+ }
+
+ virtual void TearDown() {
+ mSMS.clear();
+ mSTC.clear();
+ mANW.clear();
+ GLTest::TearDown();
+ }
+
+ void setUpEGLSurfaceFromMediaRecorder(sp<MediaRecorder>& mr);
+
+ const int mYuvTexWidth;
+ const int mYuvTexHeight;
+
+ sp<SurfaceMediaSource> mSMS;
+ sp<Surface> mSTC;
+ sp<ANativeWindow> mANW;
+};
+
+/////////////////////////////////////////////////////////////////////
+// Methods in SurfaceMediaSourceGLTest
+/////////////////////////////////////////////////////////////////////
+EGLint const* SurfaceMediaSourceGLTest::getConfigAttribs() {
+ ALOGV("SurfaceMediaSourceGLTest getConfigAttribs");
+ static EGLint sDefaultConfigAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_RECORDABLE_ANDROID, EGL_TRUE,
+ EGL_NONE };
+
+ return sDefaultConfigAttribs;
+}
+
+// One pass of dequeuing and queuing a GLBuffer
+void SurfaceMediaSourceGLTest::oneBufferPassGL(int num) {
+ int d = num % 50;
+ float f = 0.2f; // 0.1f * d;
+
+ glClearColor(0, 0.3, 0, 0.6);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glEnable(GL_SCISSOR_TEST);
+ glScissor(4 + d, 4 + d, 4, 4);
+ glClearColor(1.0 - f, f, f, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glScissor(24 + d, 48 + d, 4, 4);
+ glClearColor(f, 1.0 - f, f, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glScissor(37 + d, 17 + d, 4, 4);
+ glClearColor(f, f, 1.0 - f, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // The following call dequeues and queues the buffer
+ eglSwapBuffers(mEglDisplay, mEglSurface);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ glDisable(GL_SCISSOR_TEST);
+}
+
+// Set up the MediaRecorder which runs in the same process as mediaserver
+sp<MediaRecorder> SurfaceMediaSourceGLTest::setUpMediaRecorder(int fd, int videoSource,
+ int outputFormat, int videoEncoder, int width, int height, int fps) {
+ sp<MediaRecorder> mr = new MediaRecorder(String16());
+ mr->setVideoSource(videoSource);
+ mr->setOutputFormat(outputFormat);
+ mr->setVideoEncoder(videoEncoder);
+ mr->setOutputFile(fd);
+ mr->setVideoSize(width, height);
+ mr->setVideoFrameRate(fps);
+ mr->prepare();
+ ALOGV("Starting MediaRecorder...");
+ CHECK_EQ((status_t)OK, mr->start());
+ return mr;
+}
+
+// query the mediarecorder for a surfacemeidasource and create an egl surface with that
+void SurfaceMediaSourceGLTest::setUpEGLSurfaceFromMediaRecorder(sp<MediaRecorder>& mr) {
+ sp<IGraphicBufferProducer> iST = mr->querySurfaceMediaSourceFromMediaServer();
+ mSTC = new Surface(iST);
+ mANW = mSTC;
+
+ if (mEglSurface != EGL_NO_SURFACE) {
+ EXPECT_TRUE(eglDestroySurface(mEglDisplay, mEglSurface));
+ mEglSurface = EGL_NO_SURFACE;
+ }
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
+ mANW.get(), NULL);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_SURFACE, mEglSurface) ;
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+}
+
+
+/////////////////////////////////////////////////////////////////////
+// Methods in SurfaceMediaSourceTest
+/////////////////////////////////////////////////////////////////////
+
+// One pass of dequeuing and queuing the buffer. Fill it in with
+// cpu YV12 buffer
+void SurfaceMediaSourceTest::oneBufferPass(int width, int height ) {
+ ANativeWindowBuffer* anb;
+ ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
+ ASSERT_TRUE(anb != NULL);
+
+
+ // Fill the buffer with the a checkerboard pattern
+ uint8_t* img = NULL;
+ sp<GraphicBuffer> buf(GraphicBuffer::from(anb));
+ buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
+ SurfaceMediaSourceTest::fillYV12Buffer(img, width, height, buf->getStride());
+ buf->unlock();
+
+ ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(),
+ -1));
+}
+
+// Dequeuing and queuing the buffer without really filling it in.
+void SurfaceMediaSourceTest::oneBufferPassNoFill(
+ int /* width */, int /* height */) {
+ ANativeWindowBuffer* anb;
+ ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
+ ASSERT_TRUE(anb != NULL);
+
+ // We do not fill the buffer in. Just queue it back.
+ sp<GraphicBuffer> buf(GraphicBuffer::from(anb));
+ ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(),
+ -1));
+}
+
+// Fill a YV12 buffer with a multi-colored checkerboard pattern
+void SurfaceMediaSourceTest::fillYV12Buffer(uint8_t* buf, int w, int h, int stride) {
+ const int blockWidth = w > 16 ? w / 16 : 1;
+ const int blockHeight = h > 16 ? h / 16 : 1;
+ const int yuvTexOffsetY = 0;
+ int yuvTexStrideY = stride;
+ int yuvTexOffsetV = yuvTexStrideY * h;
+ int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf;
+ int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2;
+ int yuvTexStrideU = yuvTexStrideV;
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ int parityX = (x / blockWidth) & 1;
+ int parityY = (y / blockHeight) & 1;
+ unsigned char intensity = (parityX ^ parityY) ? 63 : 191;
+ buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = intensity;
+ if (x < w / 2 && y < h / 2) {
+ buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = intensity;
+ if (x * 2 < w / 2 && y * 2 < h / 2) {
+ buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 0] =
+ buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 1] =
+ buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 0] =
+ buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 1] =
+ intensity;
+ }
+ }
+ }
+ }
+}
+
+// Fill a YV12 buffer with red outside a given rectangle and green inside it.
+void SurfaceMediaSourceTest::fillYV12BufferRect(uint8_t* buf, int w,
+ int h, int stride, const android_native_rect_t& rect) {
+ const int yuvTexOffsetY = 0;
+ int yuvTexStrideY = stride;
+ int yuvTexOffsetV = yuvTexStrideY * h;
+ int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf;
+ int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2;
+ int yuvTexStrideU = yuvTexStrideV;
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ bool inside = rect.left <= x && x < rect.right &&
+ rect.top <= y && y < rect.bottom;
+ buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = inside ? 240 : 64;
+ if (x < w / 2 && y < h / 2) {
+ bool inside = rect.left <= 2*x && 2*x < rect.right &&
+ rect.top <= 2*y && 2*y < rect.bottom;
+ buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = 16;
+ buf[yuvTexOffsetV + (y * yuvTexStrideV) + x] =
+ inside ? 16 : 255;
+ }
+ }
+ }
+} ///////// End of class SurfaceMediaSourceTest
+
+///////////////////////////////////////////////////////////////////
+// Class to imitate the recording /////////////////////////////
+// ////////////////////////////////////////////////////////////////
+struct SimpleDummyRecorder {
+ sp<MediaSource> mSource;
+
+ explicit SimpleDummyRecorder
+ (const sp<MediaSource> &source): mSource(source) {}
+
+ status_t start() { return mSource->start();}
+ status_t stop() { return mSource->stop();}
+
+ // fakes reading from a media source
+ status_t readFromSource() {
+ MediaBufferBase *buffer;
+ status_t err = mSource->read(&buffer);
+ if (err != OK) {
+ return err;
+ }
+ buffer->release();
+ buffer = NULL;
+ return OK;
+ }
+};
+///////////////////////////////////////////////////////////////////
+// TESTS
+// SurfaceMediaSourceTest class contains tests that fill the buffers
+// using the cpu calls
+// SurfaceMediaSourceGLTest class contains tests that fill the buffers
+// using the GL calls.
+// TODO: None of the tests actually verify the encoded images.. so at this point,
+// these are mostly functionality tests + visual inspection
+//////////////////////////////////////////////////////////////////////
+
+// Just pass one buffer from the native_window to the SurfaceMediaSource
+// Dummy Encoder
+static int testId = 1;
+TEST_F(SurfaceMediaSourceTest, DISABLED_DummyEncodingFromCpuFilledYV12BufferNpotOneBufferPass) {
+ ALOGV("Test # %d", testId++);
+ ALOGV("Testing OneBufferPass ******************************");
+
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(),
+ HAL_PIXEL_FORMAT_YV12));
+ oneBufferPass(mYuvTexWidth, mYuvTexHeight);
+}
+
+// Pass the buffer with the wrong height and weight and should not be accepted
+// Dummy Encoder
+TEST_F(SurfaceMediaSourceTest, DISABLED_DummyEncodingFromCpuFilledYV12BufferNpotWrongSizeBufferPass) {
+ ALOGV("Test # %d", testId++);
+ ALOGV("Testing Wrong size BufferPass ******************************");
+
+ // setting the client side buffer size different than the server size
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_dimensions(mANW.get(),
+ 10, 10));
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(),
+ HAL_PIXEL_FORMAT_YV12));
+
+ ANativeWindowBuffer* anb;
+
+ // Note: make sure we get an ERROR back when dequeuing!
+ ASSERT_NE(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
+}
+
+// pass multiple buffers from the native_window the SurfaceMediaSource
+// Dummy Encoder
+TEST_F(SurfaceMediaSourceTest, DISABLED_DummyEncodingFromCpuFilledYV12BufferNpotMultiBufferPass) {
+ ALOGV("Test # %d", testId++);
+ ALOGV("Testing MultiBufferPass, Dummy Recorder *********************");
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(),
+ HAL_PIXEL_FORMAT_YV12));
+
+ SimpleDummyRecorder writer(mSMS);
+ writer.start();
+
+ int32_t nFramesCount = 0;
+ while (nFramesCount < 300) {
+ oneBufferPass(mYuvTexWidth, mYuvTexHeight);
+
+ ASSERT_EQ(NO_ERROR, writer.readFromSource());
+
+ nFramesCount++;
+ }
+ writer.stop();
+}
+
+// Delayed pass of multiple buffers from the native_window the SurfaceMediaSource
+// Dummy Encoder
+TEST_F(SurfaceMediaSourceTest, DummyLagEncodingFromCpuFilledYV12BufferNpotMultiBufferPass) {
+ ALOGV("Test # %d", testId++);
+ ALOGV("Testing MultiBufferPass, Dummy Recorder Lagging **************");
+
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(),
+ HAL_PIXEL_FORMAT_YV12));
+
+ SimpleDummyRecorder writer(mSMS);
+ writer.start();
+
+ int32_t nFramesCount = 1;
+ const int FRAMES_LAG = SurfaceMediaSource::MIN_UNDEQUEUED_BUFFERS;
+
+ while (nFramesCount <= 300) {
+ ALOGV("Frame: %d", nFramesCount);
+ oneBufferPass(mYuvTexWidth, mYuvTexHeight);
+ // Forcing the writer to lag behind a few frames
+ if (nFramesCount > FRAMES_LAG) {
+ ASSERT_EQ(NO_ERROR, writer.readFromSource());
+ }
+ nFramesCount++;
+ }
+ writer.stop();
+}
+
+// pass multiple buffers from the native_window the SurfaceMediaSource
+// A dummy writer (MULTITHREADED) is used to simulate actual MPEG4Writer
+TEST_F(SurfaceMediaSourceTest, DummyThreadedEncodingFromCpuFilledYV12BufferNpotMultiBufferPass) {
+ ALOGV("Test # %d", testId++);
+ ALOGV("Testing MultiBufferPass, Dummy Recorder Multi-Threaded **********");
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(),
+ HAL_PIXEL_FORMAT_YV12));
+
+ DummyRecorder writer(mSMS);
+ writer.start();
+
+ int32_t nFramesCount = 0;
+ while (nFramesCount <= 300) {
+ ALOGV("Frame: %d", nFramesCount);
+ oneBufferPass(mYuvTexWidth, mYuvTexHeight);
+
+ nFramesCount++;
+ }
+ writer.stop();
+}
+
+// Test to examine actual encoding using mediarecorder
+// We use the mediaserver to create a mediarecorder and send
+// it back to us. So SurfaceMediaSource lives in the same process
+// as the mediaserver.
+// Very close to the actual camera, except that the
+// buffers are filled and queueud by the CPU instead of GL.
+TEST_F(SurfaceMediaSourceTest, DISABLED_EncodingFromCpuYV12BufferNpotWriteMediaServer) {
+ ALOGV("Test # %d", testId++);
+ ALOGV("************** Testing the whole pipeline with actual MediaRecorder ***********");
+ ALOGV("************** SurfaceMediaSource is same process as mediaserver ***********");
+
+ const char *fileName = "/sdcard/outputSurfEncMSource.mp4";
+ int fd = open(fileName, O_RDWR | O_CREAT, 0744);
+ if (fd < 0) {
+ ALOGE("ERROR: Could not open the the file %s, fd = %d !!", fileName, fd);
+ }
+ CHECK(fd >= 0);
+
+ sp<MediaRecorder> mr = SurfaceMediaSourceGLTest::setUpMediaRecorder(fd,
+ VIDEO_SOURCE_SURFACE, OUTPUT_FORMAT_MPEG_4, VIDEO_ENCODER_H264,
+ mYuvTexWidth, mYuvTexHeight, 30);
+ // get the reference to the surfacemediasource living in
+ // mediaserver that is created by stagefrightrecorder
+ sp<IGraphicBufferProducer> iST = mr->querySurfaceMediaSourceFromMediaServer();
+ mSTC = new Surface(iST);
+ mANW = mSTC;
+ ASSERT_EQ(NO_ERROR, native_window_api_connect(mANW.get(), NATIVE_WINDOW_API_CPU));
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_format(mANW.get(),
+ HAL_PIXEL_FORMAT_YV12));
+
+ int32_t nFramesCount = 0;
+ while (nFramesCount <= 300) {
+ oneBufferPassNoFill(mYuvTexWidth, mYuvTexHeight);
+ nFramesCount++;
+ ALOGV("framesCount = %d", nFramesCount);
+ }
+
+ ASSERT_EQ(NO_ERROR, native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU));
+ ALOGV("Stopping MediaRecorder...");
+ CHECK_EQ((status_t)OK, mr->stop());
+ mr.clear();
+ close(fd);
+}
+
+//////////////////////////////////////////////////////////////////////
+// GL tests
+/////////////////////////////////////////////////////////////////////
+
+// Test to examine whether we can choose the Recordable Android GLConfig
+// DummyRecorder used- no real encoding here
+TEST_F(SurfaceMediaSourceGLTest, ChooseAndroidRecordableEGLConfigDummyWriter) {
+ ALOGV("Test # %d", testId++);
+ ALOGV("Verify creating a surface w/ right config + dummy writer*********");
+
+ mSMS = new SurfaceMediaSource(mYuvTexWidth, mYuvTexHeight);
+ mSTC = new Surface(mSMS->getProducer());
+ mANW = mSTC;
+
+ DummyRecorder writer(mSMS);
+ writer.start();
+
+ if (mEglSurface != EGL_NO_SURFACE) {
+ EXPECT_TRUE(eglDestroySurface(mEglDisplay, mEglSurface));
+ mEglSurface = EGL_NO_SURFACE;
+ }
+
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
+ mANW.get(), NULL);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_SURFACE, mEglSurface) ;
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ int32_t nFramesCount = 0;
+ while (nFramesCount <= 300) {
+ oneBufferPassGL();
+ nFramesCount++;
+ ALOGV("framesCount = %d", nFramesCount);
+ }
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+
+ writer.stop();
+}
+// Test to examine whether we can render GL buffers in to the surface
+// created with the native window handle
+TEST_F(SurfaceMediaSourceGLTest, RenderingToRecordableEGLSurfaceWorks) {
+ ALOGV("Test # %d", testId++);
+ ALOGV("RenderingToRecordableEGLSurfaceWorks *********************");
+ // Do the producer side of things
+ glClearColor(0.6, 0.6, 0.6, 0.6);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glEnable(GL_SCISSOR_TEST);
+ glScissor(4, 4, 4, 4);
+ glClearColor(1.0, 0.0, 0.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glScissor(24, 48, 4, 4);
+ glClearColor(0.0, 1.0, 0.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glScissor(37, 17, 4, 4);
+ glClearColor(0.0, 0.0, 1.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(63, 63, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 0, 63, 153, 153, 153, 153));
+
+ EXPECT_TRUE(checkPixel( 4, 7, 255, 0, 0, 255));
+ EXPECT_TRUE(checkPixel(25, 51, 0, 255, 0, 255));
+ EXPECT_TRUE(checkPixel(40, 19, 0, 0, 255, 255));
+ EXPECT_TRUE(checkPixel(29, 51, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 5, 32, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(13, 8, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(46, 3, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(30, 33, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 6, 52, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(55, 33, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(16, 29, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 1, 30, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(41, 37, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(46, 29, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(15, 25, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 3, 52, 153, 153, 153, 153));
+}
+
+// Test to examine the actual encoding with GL buffers
+// Actual encoder, Actual GL Buffers Filled SurfaceMediaSource
+// The same pattern is rendered every frame
+TEST_F(SurfaceMediaSourceGLTest, EncodingFromGLRgbaSameImageEachBufNpotWrite) {
+ ALOGV("Test # %d", testId++);
+ ALOGV("************** Testing the whole pipeline with actual Recorder ***********");
+ ALOGV("************** GL Filling the buffers ***********");
+ // Note: No need to set the colorformat for the buffers. The colorformat is
+ // in the GRAlloc buffers itself.
+
+ const char *fileName = "/sdcard/outputSurfEncMSourceGL.mp4";
+ int fd = open(fileName, O_RDWR | O_CREAT, 0744);
+ if (fd < 0) {
+ ALOGE("ERROR: Could not open the the file %s, fd = %d !!", fileName, fd);
+ }
+ CHECK(fd >= 0);
+
+ sp<MediaRecorder> mr = setUpMediaRecorder(fd, VIDEO_SOURCE_SURFACE,
+ OUTPUT_FORMAT_MPEG_4, VIDEO_ENCODER_H264, mYuvTexWidth, mYuvTexHeight, 30);
+
+ // get the reference to the surfacemediasource living in
+ // mediaserver that is created by stagefrightrecorder
+ setUpEGLSurfaceFromMediaRecorder(mr);
+
+ int32_t nFramesCount = 0;
+ while (nFramesCount <= 300) {
+ oneBufferPassGL();
+ nFramesCount++;
+ ALOGV("framesCount = %d", nFramesCount);
+ }
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+
+ ALOGV("Stopping MediaRecorder...");
+ CHECK_EQ((status_t)OK, mr->stop());
+ mr.clear();
+ close(fd);
+}
+
+// Test to examine the actual encoding from the GL Buffers
+// Actual encoder, Actual GL Buffers Filled SurfaceMediaSource
+// A different pattern is rendered every frame
+TEST_F(SurfaceMediaSourceGLTest, EncodingFromGLRgbaDiffImageEachBufNpotWrite) {
+ ALOGV("Test # %d", testId++);
+ ALOGV("************** Testing the whole pipeline with actual Recorder ***********");
+ ALOGV("************** Diff GL Filling the buffers ***********");
+ // Note: No need to set the colorformat for the buffers. The colorformat is
+ // in the GRAlloc buffers itself.
+
+ const char *fileName = "/sdcard/outputSurfEncMSourceGLDiff.mp4";
+ int fd = open(fileName, O_RDWR | O_CREAT, 0744);
+ if (fd < 0) {
+ ALOGE("ERROR: Could not open the the file %s, fd = %d !!", fileName, fd);
+ }
+ CHECK(fd >= 0);
+
+ sp<MediaRecorder> mr = setUpMediaRecorder(fd, VIDEO_SOURCE_SURFACE,
+ OUTPUT_FORMAT_MPEG_4, VIDEO_ENCODER_H264, mYuvTexWidth, mYuvTexHeight, 30);
+
+ // get the reference to the surfacemediasource living in
+ // mediaserver that is created by stagefrightrecorder
+ setUpEGLSurfaceFromMediaRecorder(mr);
+
+ int32_t nFramesCount = 0;
+ while (nFramesCount <= 300) {
+ oneBufferPassGL(nFramesCount);
+ nFramesCount++;
+ ALOGV("framesCount = %d", nFramesCount);
+ }
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+
+ ALOGV("Stopping MediaRecorder...");
+ CHECK_EQ((status_t)OK, mr->stop());
+ mr.clear();
+ close(fd);
+}
+} // namespace android
diff --git a/media/libstagefright/wifi-display/Android.bp b/media/libstagefright/wifi-display/Android.bp
new file mode 100644
index 0000000..bfb3013
--- /dev/null
+++ b/media/libstagefright/wifi-display/Android.bp
@@ -0,0 +1,57 @@
+cc_library_shared {
+ name: "libstagefright_wfd",
+
+ srcs: [
+ "MediaSender.cpp",
+ "Parameters.cpp",
+ "rtp/RTPSender.cpp",
+ "source/Converter.cpp",
+ "source/MediaPuller.cpp",
+ "source/PlaybackSession.cpp",
+ "source/RepeaterSource.cpp",
+ "source/TSPacketizer.cpp",
+ "source/WifiDisplaySource.cpp",
+ "VideoFormats.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/libstagefright",
+ "frameworks/native/include/media/openmax",
+ "frameworks/native/include/media/hardware",
+ "frameworks/av/media/module/mpeg2ts",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "libcutils",
+ "liblog",
+ "libmedia",
+ "libmedia_omx",
+ "libstagefright",
+ "libstagefright_foundation",
+ "libui",
+ "libgui",
+ "libutils",
+ ],
+
+ header_libs: [
+ "libmediadrm_headers",
+ "libmediametrics_headers",
+ ],
+
+ cflags: [
+ "-Wno-multichar",
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+}
diff --git a/media/libstagefright/wifi-display/MediaSender.cpp b/media/libstagefright/wifi-display/MediaSender.cpp
new file mode 100644
index 0000000..6e743b5
--- /dev/null
+++ b/media/libstagefright/wifi-display/MediaSender.cpp
@@ -0,0 +1,519 @@
+/*
+ * Copyright 2013, 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 "MediaSender"
+#include <utils/Log.h>
+
+#include "MediaSender.h"
+
+#include "rtp/RTPSender.h"
+#include "source/TSPacketizer.h"
+
+#include <media/IHDCP.h>
+#include <media/stagefright/foundation/avc_utils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/ANetworkSession.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaBufferBase.h>
+#include <ui/GraphicBuffer.h>
+
+namespace android {
+
+MediaSender::MediaSender(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> ¬ify)
+ : mNetSession(netSession),
+ mNotify(notify),
+ mMode(MODE_UNDEFINED),
+ mGeneration(0),
+ mPrevTimeUs(-1ll),
+ mInitDoneCount(0),
+ mLogFile(NULL) {
+ // mLogFile = fopen("/data/misc/log.ts", "wb");
+}
+
+MediaSender::~MediaSender() {
+ if (mLogFile != NULL) {
+ fclose(mLogFile);
+ mLogFile = NULL;
+ }
+}
+
+status_t MediaSender::setHDCP(const sp<IHDCP> &hdcp) {
+ if (mMode != MODE_UNDEFINED) {
+ return INVALID_OPERATION;
+ }
+
+ mHDCP = hdcp;
+
+ return OK;
+}
+
+ssize_t MediaSender::addTrack(const sp<AMessage> &format, uint32_t flags) {
+ if (mMode != MODE_UNDEFINED) {
+ return INVALID_OPERATION;
+ }
+
+ TrackInfo info;
+ info.mFormat = format;
+ info.mFlags = flags;
+ info.mPacketizerTrackIndex = -1;
+
+ AString mime;
+ CHECK(format->findString("mime", &mime));
+ info.mIsAudio = !strncasecmp("audio/", mime.c_str(), 6);
+
+ size_t index = mTrackInfos.size();
+ mTrackInfos.push_back(info);
+
+ return index;
+}
+
+status_t MediaSender::initAsync(
+ ssize_t trackIndex,
+ const char *remoteHost,
+ int32_t remoteRTPPort,
+ RTPSender::TransportMode rtpMode,
+ int32_t remoteRTCPPort,
+ RTPSender::TransportMode rtcpMode,
+ int32_t *localRTPPort) {
+ if (trackIndex < 0) {
+ if (mMode != MODE_UNDEFINED) {
+ return INVALID_OPERATION;
+ }
+
+ uint32_t flags = 0;
+ if (mHDCP != NULL) {
+ // XXX Determine proper HDCP version.
+ flags |= TSPacketizer::EMIT_HDCP20_DESCRIPTOR;
+ }
+ mTSPacketizer = new TSPacketizer(flags);
+
+ status_t err = OK;
+ for (size_t i = 0; i < mTrackInfos.size(); ++i) {
+ TrackInfo *info = &mTrackInfos.editItemAt(i);
+
+ ssize_t packetizerTrackIndex =
+ mTSPacketizer->addTrack(info->mFormat);
+
+ if (packetizerTrackIndex < 0) {
+ err = packetizerTrackIndex;
+ break;
+ }
+
+ info->mPacketizerTrackIndex = packetizerTrackIndex;
+ }
+
+ if (err == OK) {
+ sp<AMessage> notify = new AMessage(kWhatSenderNotify, this);
+ notify->setInt32("generation", mGeneration);
+ mTSSender = new RTPSender(mNetSession, notify);
+ looper()->registerHandler(mTSSender);
+
+ err = mTSSender->initAsync(
+ remoteHost,
+ remoteRTPPort,
+ rtpMode,
+ remoteRTCPPort,
+ rtcpMode,
+ localRTPPort);
+
+ if (err != OK) {
+ looper()->unregisterHandler(mTSSender->id());
+ mTSSender.clear();
+ }
+ }
+
+ if (err != OK) {
+ for (size_t i = 0; i < mTrackInfos.size(); ++i) {
+ TrackInfo *info = &mTrackInfos.editItemAt(i);
+ info->mPacketizerTrackIndex = -1;
+ }
+
+ mTSPacketizer.clear();
+ return err;
+ }
+
+ mMode = MODE_TRANSPORT_STREAM;
+ mInitDoneCount = 1;
+
+ return OK;
+ }
+
+ if (mMode == MODE_TRANSPORT_STREAM) {
+ return INVALID_OPERATION;
+ }
+
+ if ((size_t)trackIndex >= mTrackInfos.size()) {
+ return -ERANGE;
+ }
+
+ TrackInfo *info = &mTrackInfos.editItemAt(trackIndex);
+
+ if (info->mSender != NULL) {
+ return INVALID_OPERATION;
+ }
+
+ sp<AMessage> notify = new AMessage(kWhatSenderNotify, this);
+ notify->setInt32("generation", mGeneration);
+ notify->setSize("trackIndex", trackIndex);
+
+ info->mSender = new RTPSender(mNetSession, notify);
+ looper()->registerHandler(info->mSender);
+
+ status_t err = info->mSender->initAsync(
+ remoteHost,
+ remoteRTPPort,
+ rtpMode,
+ remoteRTCPPort,
+ rtcpMode,
+ localRTPPort);
+
+ if (err != OK) {
+ looper()->unregisterHandler(info->mSender->id());
+ info->mSender.clear();
+
+ return err;
+ }
+
+ if (mMode == MODE_UNDEFINED) {
+ mInitDoneCount = mTrackInfos.size();
+ }
+
+ mMode = MODE_ELEMENTARY_STREAMS;
+
+ return OK;
+}
+
+status_t MediaSender::queueAccessUnit(
+ size_t trackIndex, const sp<ABuffer> &accessUnit) {
+ if (mMode == MODE_UNDEFINED) {
+ return INVALID_OPERATION;
+ }
+
+ if (trackIndex >= mTrackInfos.size()) {
+ return -ERANGE;
+ }
+
+ if (mMode == MODE_TRANSPORT_STREAM) {
+ TrackInfo *info = &mTrackInfos.editItemAt(trackIndex);
+ info->mAccessUnits.push_back(accessUnit);
+
+ mTSPacketizer->extractCSDIfNecessary(info->mPacketizerTrackIndex);
+
+ for (;;) {
+ ssize_t minTrackIndex = -1;
+ int64_t minTimeUs = -1ll;
+
+ for (size_t i = 0; i < mTrackInfos.size(); ++i) {
+ const TrackInfo &info = mTrackInfos.itemAt(i);
+
+ if (info.mAccessUnits.empty()) {
+ minTrackIndex = -1;
+ minTimeUs = -1ll;
+ break;
+ }
+
+ int64_t timeUs;
+ const sp<ABuffer> &accessUnit = *info.mAccessUnits.begin();
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ if (minTrackIndex < 0 || timeUs < minTimeUs) {
+ minTrackIndex = i;
+ minTimeUs = timeUs;
+ }
+ }
+
+ if (minTrackIndex < 0) {
+ return OK;
+ }
+
+ TrackInfo *info = &mTrackInfos.editItemAt(minTrackIndex);
+ sp<ABuffer> accessUnit = *info->mAccessUnits.begin();
+ info->mAccessUnits.erase(info->mAccessUnits.begin());
+
+ sp<ABuffer> tsPackets;
+ status_t err = packetizeAccessUnit(
+ minTrackIndex, accessUnit, &tsPackets);
+
+ if (err == OK) {
+ if (mLogFile != NULL) {
+ fwrite(tsPackets->data(), 1, tsPackets->size(), mLogFile);
+ }
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+ tsPackets->meta()->setInt64("timeUs", timeUs);
+
+ err = mTSSender->queueBuffer(
+ tsPackets,
+ 33 /* packetType */,
+ RTPSender::PACKETIZATION_TRANSPORT_STREAM);
+ }
+
+ if (err != OK) {
+ return err;
+ }
+ }
+ }
+
+ TrackInfo *info = &mTrackInfos.editItemAt(trackIndex);
+
+ return info->mSender->queueBuffer(
+ accessUnit,
+ info->mIsAudio ? 96 : 97 /* packetType */,
+ info->mIsAudio
+ ? RTPSender::PACKETIZATION_AAC : RTPSender::PACKETIZATION_H264);
+}
+
+void MediaSender::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatSenderNotify:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+ if (generation != mGeneration) {
+ break;
+ }
+
+ onSenderNotify(msg);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void MediaSender::onSenderNotify(const sp<AMessage> &msg) {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case RTPSender::kWhatInitDone:
+ {
+ --mInitDoneCount;
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ if (err != OK) {
+ notifyInitDone(err);
+ ++mGeneration;
+ break;
+ }
+
+ if (mInitDoneCount == 0) {
+ notifyInitDone(OK);
+ }
+ break;
+ }
+
+ case RTPSender::kWhatError:
+ {
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ notifyError(err);
+ break;
+ }
+
+ case kWhatNetworkStall:
+ {
+ size_t numBytesQueued;
+ CHECK(msg->findSize("numBytesQueued", &numBytesQueued));
+
+ notifyNetworkStall(numBytesQueued);
+ break;
+ }
+
+ case kWhatInformSender:
+ {
+ int64_t avgLatencyUs;
+ CHECK(msg->findInt64("avgLatencyUs", &avgLatencyUs));
+
+ int64_t maxLatencyUs;
+ CHECK(msg->findInt64("maxLatencyUs", &maxLatencyUs));
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInformSender);
+ notify->setInt64("avgLatencyUs", avgLatencyUs);
+ notify->setInt64("maxLatencyUs", maxLatencyUs);
+ notify->post();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void MediaSender::notifyInitDone(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInitDone);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void MediaSender::notifyError(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void MediaSender::notifyNetworkStall(size_t numBytesQueued) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatNetworkStall);
+ notify->setSize("numBytesQueued", numBytesQueued);
+ notify->post();
+}
+
+status_t MediaSender::packetizeAccessUnit(
+ size_t trackIndex,
+ sp<ABuffer> accessUnit,
+ sp<ABuffer> *tsPackets) {
+ const TrackInfo &info = mTrackInfos.itemAt(trackIndex);
+
+ uint32_t flags = 0;
+
+ bool isHDCPEncrypted = false;
+ uint64_t inputCTR;
+ uint8_t HDCP_private_data[16];
+
+ bool manuallyPrependSPSPPS =
+ !info.mIsAudio
+ && (info.mFlags & FLAG_MANUALLY_PREPEND_SPS_PPS)
+ && IsIDR(accessUnit->data(), accessUnit->size());
+
+ if (mHDCP != NULL && !info.mIsAudio) {
+ isHDCPEncrypted = true;
+
+ if (manuallyPrependSPSPPS) {
+ accessUnit = mTSPacketizer->prependCSD(
+ info.mPacketizerTrackIndex, accessUnit);
+ }
+
+ status_t err;
+ native_handle_t* handle;
+ if (accessUnit->meta()->findPointer("handle", (void**)&handle)
+ && handle != NULL) {
+ int32_t rangeLength, rangeOffset;
+ sp<AMessage> notify;
+ CHECK(accessUnit->meta()->findInt32("rangeOffset", &rangeOffset));
+ CHECK(accessUnit->meta()->findInt32("rangeLength", &rangeLength));
+ CHECK(accessUnit->meta()->findMessage("notify", ¬ify)
+ && notify != NULL);
+ CHECK_GE((int32_t)accessUnit->size(), rangeLength);
+
+ sp<GraphicBuffer> grbuf(new GraphicBuffer(
+ rangeOffset + rangeLength /* width */, 1 /* height */,
+ HAL_PIXEL_FORMAT_Y8, 1 /* layerCount */,
+ GRALLOC_USAGE_HW_VIDEO_ENCODER,
+ rangeOffset + rangeLength /* stride */, handle,
+ false /* keepOwnership */));
+
+ err = mHDCP->encryptNative(
+ grbuf, rangeOffset, rangeLength,
+ trackIndex /* streamCTR */,
+ &inputCTR,
+ accessUnit->data());
+ notify->post();
+ } else {
+ err = mHDCP->encrypt(
+ accessUnit->data(), accessUnit->size(),
+ trackIndex /* streamCTR */,
+ &inputCTR,
+ accessUnit->data());
+ }
+
+ if (err != OK) {
+ ALOGE("Failed to HDCP-encrypt media data (err %d)",
+ err);
+
+ return err;
+ }
+
+ HDCP_private_data[0] = 0x00;
+
+ HDCP_private_data[1] =
+ (((trackIndex >> 30) & 3) << 1) | 1;
+
+ HDCP_private_data[2] = (trackIndex >> 22) & 0xff;
+
+ HDCP_private_data[3] =
+ (((trackIndex >> 15) & 0x7f) << 1) | 1;
+
+ HDCP_private_data[4] = (trackIndex >> 7) & 0xff;
+
+ HDCP_private_data[5] =
+ ((trackIndex & 0x7f) << 1) | 1;
+
+ HDCP_private_data[6] = 0x00;
+
+ HDCP_private_data[7] =
+ (((inputCTR >> 60) & 0x0f) << 1) | 1;
+
+ HDCP_private_data[8] = (inputCTR >> 52) & 0xff;
+
+ HDCP_private_data[9] =
+ (((inputCTR >> 45) & 0x7f) << 1) | 1;
+
+ HDCP_private_data[10] = (inputCTR >> 37) & 0xff;
+
+ HDCP_private_data[11] =
+ (((inputCTR >> 30) & 0x7f) << 1) | 1;
+
+ HDCP_private_data[12] = (inputCTR >> 22) & 0xff;
+
+ HDCP_private_data[13] =
+ (((inputCTR >> 15) & 0x7f) << 1) | 1;
+
+ HDCP_private_data[14] = (inputCTR >> 7) & 0xff;
+
+ HDCP_private_data[15] =
+ ((inputCTR & 0x7f) << 1) | 1;
+
+ flags |= TSPacketizer::IS_ENCRYPTED;
+ } else if (manuallyPrependSPSPPS) {
+ flags |= TSPacketizer::PREPEND_SPS_PPS_TO_IDR_FRAMES;
+ }
+
+ int64_t timeUs = ALooper::GetNowUs();
+ if (mPrevTimeUs < 0ll || mPrevTimeUs + 100000ll <= timeUs) {
+ flags |= TSPacketizer::EMIT_PCR;
+ flags |= TSPacketizer::EMIT_PAT_AND_PMT;
+
+ mPrevTimeUs = timeUs;
+ }
+
+ mTSPacketizer->packetize(
+ info.mPacketizerTrackIndex,
+ accessUnit,
+ tsPackets,
+ flags,
+ !isHDCPEncrypted ? NULL : HDCP_private_data,
+ !isHDCPEncrypted ? 0 : sizeof(HDCP_private_data),
+ info.mIsAudio ? 2 : 0 /* numStuffingBytes */);
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/MediaSender.h b/media/libstagefright/wifi-display/MediaSender.h
new file mode 100644
index 0000000..04538ea
--- /dev/null
+++ b/media/libstagefright/wifi-display/MediaSender.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2013, 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 MEDIA_SENDER_H_
+
+#define MEDIA_SENDER_H_
+
+#include "rtp/RTPSender.h"
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct ABuffer;
+struct ANetworkSession;
+struct AMessage;
+struct IHDCP;
+struct TSPacketizer;
+
+// This class facilitates sending of data from one or more media tracks
+// through one or more RTP channels, either providing a 1:1 mapping from
+// track to RTP channel or muxing all tracks into a single RTP channel and
+// using transport stream encapsulation.
+// Optionally the (video) data is encrypted using the provided hdcp object.
+struct MediaSender : public AHandler {
+ enum {
+ kWhatInitDone,
+ kWhatError,
+ kWhatNetworkStall,
+ kWhatInformSender,
+ };
+
+ MediaSender(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> ¬ify);
+
+ status_t setHDCP(const sp<IHDCP> &hdcp);
+
+ enum FlagBits {
+ FLAG_MANUALLY_PREPEND_SPS_PPS = 1,
+ };
+ ssize_t addTrack(const sp<AMessage> &format, uint32_t flags);
+
+ // If trackIndex == -1, initialize for transport stream muxing.
+ status_t initAsync(
+ ssize_t trackIndex,
+ const char *remoteHost,
+ int32_t remoteRTPPort,
+ RTPSender::TransportMode rtpMode,
+ int32_t remoteRTCPPort,
+ RTPSender::TransportMode rtcpMode,
+ int32_t *localRTPPort);
+
+ status_t queueAccessUnit(
+ size_t trackIndex, const sp<ABuffer> &accessUnit);
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual ~MediaSender();
+
+private:
+ enum {
+ kWhatSenderNotify,
+ };
+
+ enum Mode {
+ MODE_UNDEFINED,
+ MODE_TRANSPORT_STREAM,
+ MODE_ELEMENTARY_STREAMS,
+ };
+
+ struct TrackInfo {
+ sp<AMessage> mFormat;
+ uint32_t mFlags;
+ sp<RTPSender> mSender;
+ List<sp<ABuffer> > mAccessUnits;
+ ssize_t mPacketizerTrackIndex;
+ bool mIsAudio;
+ };
+
+ sp<ANetworkSession> mNetSession;
+ sp<AMessage> mNotify;
+
+ sp<IHDCP> mHDCP;
+
+ Mode mMode;
+ int32_t mGeneration;
+
+ Vector<TrackInfo> mTrackInfos;
+
+ sp<TSPacketizer> mTSPacketizer;
+ sp<RTPSender> mTSSender;
+ int64_t mPrevTimeUs;
+
+ size_t mInitDoneCount;
+
+ FILE *mLogFile;
+
+ void onSenderNotify(const sp<AMessage> &msg);
+
+ void notifyInitDone(status_t err);
+ void notifyError(status_t err);
+ void notifyNetworkStall(size_t numBytesQueued);
+
+ status_t packetizeAccessUnit(
+ size_t trackIndex,
+ sp<ABuffer> accessUnit,
+ sp<ABuffer> *tsPackets);
+
+ DISALLOW_EVIL_CONSTRUCTORS(MediaSender);
+};
+
+} // namespace android
+
+#endif // MEDIA_SENDER_H_
+
diff --git a/media/libstagefright/wifi-display/Parameters.cpp b/media/libstagefright/wifi-display/Parameters.cpp
new file mode 100644
index 0000000..d2a61ea
--- /dev/null
+++ b/media/libstagefright/wifi-display/Parameters.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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 "Parameters.h"
+
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+// static
+sp<Parameters> Parameters::Parse(const char *data, size_t size) {
+ sp<Parameters> params = new Parameters;
+ status_t err = params->parse(data, size);
+
+ if (err != OK) {
+ return NULL;
+ }
+
+ return params;
+}
+
+Parameters::Parameters() {}
+
+Parameters::~Parameters() {}
+
+status_t Parameters::parse(const char *data, size_t size) {
+ size_t i = 0;
+ while (i < size) {
+ size_t nameStart = i;
+ while (i < size && data[i] != ':') {
+ ++i;
+ }
+
+ if (i == size || i == nameStart) {
+ return ERROR_MALFORMED;
+ }
+
+ AString name(&data[nameStart], i - nameStart);
+ name.trim();
+ name.tolower();
+
+ ++i;
+
+ size_t valueStart = i;
+
+ while (i + 1 < size && (data[i] != '\r' || data[i + 1] != '\n')) {
+ ++i;
+ }
+
+ AString value(&data[valueStart], i - valueStart);
+ value.trim();
+
+ mDict.add(name, value);
+
+ while (i + 1 < size && data[i] == '\r' && data[i + 1] == '\n') {
+ i += 2;
+ }
+ }
+
+ return OK;
+}
+
+bool Parameters::findParameter(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;
+}
+
+} // namespace android
diff --git a/media/libstagefright/wifi-display/Parameters.h b/media/libstagefright/wifi-display/Parameters.h
new file mode 100644
index 0000000..a5e787e
--- /dev/null
+++ b/media/libstagefright/wifi-display/Parameters.h
@@ -0,0 +1,41 @@
+/*
+ * 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 <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AString.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct Parameters : public RefBase {
+ static sp<Parameters> Parse(const char *data, size_t size);
+
+ bool findParameter(const char *name, AString *value) const;
+
+protected:
+ virtual ~Parameters();
+
+private:
+ KeyedVector<AString, AString> mDict;
+
+ Parameters();
+ status_t parse(const char *data, size_t size);
+
+ DISALLOW_EVIL_CONSTRUCTORS(Parameters);
+};
+
+} // namespace android
diff --git a/media/libstagefright/wifi-display/VideoFormats.cpp b/media/libstagefright/wifi-display/VideoFormats.cpp
new file mode 100644
index 0000000..dbc511c
--- /dev/null
+++ b/media/libstagefright/wifi-display/VideoFormats.cpp
@@ -0,0 +1,550 @@
+/*
+ * Copyright 2013, 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 "VideoFormats"
+#include <utils/Log.h>
+
+#include "VideoFormats.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+// static
+const VideoFormats::config_t VideoFormats::mResolutionTable[][32] = {
+ {
+ // CEA Resolutions
+ { 640, 480, 60, false, 0, 0},
+ { 720, 480, 60, false, 0, 0},
+ { 720, 480, 60, true, 0, 0},
+ { 720, 576, 50, false, 0, 0},
+ { 720, 576, 50, true, 0, 0},
+ { 1280, 720, 30, false, 0, 0},
+ { 1280, 720, 60, false, 0, 0},
+ { 1920, 1080, 30, false, 0, 0},
+ { 1920, 1080, 60, false, 0, 0},
+ { 1920, 1080, 60, true, 0, 0},
+ { 1280, 720, 25, false, 0, 0},
+ { 1280, 720, 50, false, 0, 0},
+ { 1920, 1080, 25, false, 0, 0},
+ { 1920, 1080, 50, false, 0, 0},
+ { 1920, 1080, 50, true, 0, 0},
+ { 1280, 720, 24, false, 0, 0},
+ { 1920, 1080, 24, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ },
+ {
+ // VESA Resolutions
+ { 800, 600, 30, false, 0, 0},
+ { 800, 600, 60, false, 0, 0},
+ { 1024, 768, 30, false, 0, 0},
+ { 1024, 768, 60, false, 0, 0},
+ { 1152, 864, 30, false, 0, 0},
+ { 1152, 864, 60, false, 0, 0},
+ { 1280, 768, 30, false, 0, 0},
+ { 1280, 768, 60, false, 0, 0},
+ { 1280, 800, 30, false, 0, 0},
+ { 1280, 800, 60, false, 0, 0},
+ { 1360, 768, 30, false, 0, 0},
+ { 1360, 768, 60, false, 0, 0},
+ { 1366, 768, 30, false, 0, 0},
+ { 1366, 768, 60, false, 0, 0},
+ { 1280, 1024, 30, false, 0, 0},
+ { 1280, 1024, 60, false, 0, 0},
+ { 1400, 1050, 30, false, 0, 0},
+ { 1400, 1050, 60, false, 0, 0},
+ { 1440, 900, 30, false, 0, 0},
+ { 1440, 900, 60, false, 0, 0},
+ { 1600, 900, 30, false, 0, 0},
+ { 1600, 900, 60, false, 0, 0},
+ { 1600, 1200, 30, false, 0, 0},
+ { 1600, 1200, 60, false, 0, 0},
+ { 1680, 1024, 30, false, 0, 0},
+ { 1680, 1024, 60, false, 0, 0},
+ { 1680, 1050, 30, false, 0, 0},
+ { 1680, 1050, 60, false, 0, 0},
+ { 1920, 1200, 30, false, 0, 0},
+ { 1920, 1200, 60, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ },
+ {
+ // HH Resolutions
+ { 800, 480, 30, false, 0, 0},
+ { 800, 480, 60, false, 0, 0},
+ { 854, 480, 30, false, 0, 0},
+ { 854, 480, 60, false, 0, 0},
+ { 864, 480, 30, false, 0, 0},
+ { 864, 480, 60, false, 0, 0},
+ { 640, 360, 30, false, 0, 0},
+ { 640, 360, 60, false, 0, 0},
+ { 960, 540, 30, false, 0, 0},
+ { 960, 540, 60, false, 0, 0},
+ { 848, 480, 30, false, 0, 0},
+ { 848, 480, 60, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ { 0, 0, 0, false, 0, 0},
+ }
+};
+
+VideoFormats::VideoFormats() {
+ memcpy(mConfigs, mResolutionTable, sizeof(mConfigs));
+
+ for (size_t i = 0; i < kNumResolutionTypes; ++i) {
+ mResolutionEnabled[i] = 0;
+ }
+
+ setNativeResolution(RESOLUTION_CEA, 0); // default to 640x480 p60
+}
+
+void VideoFormats::setNativeResolution(ResolutionType type, size_t index) {
+ CHECK_LT(type, kNumResolutionTypes);
+ CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));
+
+ mNativeType = type;
+ mNativeIndex = index;
+
+ setResolutionEnabled(type, index);
+}
+
+void VideoFormats::getNativeResolution(
+ ResolutionType *type, size_t *index) const {
+ *type = mNativeType;
+ *index = mNativeIndex;
+}
+
+void VideoFormats::disableAll() {
+ for (size_t i = 0; i < kNumResolutionTypes; ++i) {
+ mResolutionEnabled[i] = 0;
+ for (size_t j = 0; j < 32; j++) {
+ mConfigs[i][j].profile = mConfigs[i][j].level = 0;
+ }
+ }
+}
+
+void VideoFormats::enableAll() {
+ for (size_t i = 0; i < kNumResolutionTypes; ++i) {
+ mResolutionEnabled[i] = 0xffffffff;
+ for (size_t j = 0; j < 32; j++) {
+ mConfigs[i][j].profile = (1ul << PROFILE_CBP);
+ mConfigs[i][j].level = (1ul << LEVEL_31);
+ }
+ }
+}
+
+void VideoFormats::enableResolutionUpto(
+ ResolutionType type, size_t index,
+ ProfileType profile, LevelType level) {
+ size_t width, height, fps, score;
+ bool interlaced;
+ if (!GetConfiguration(type, index, &width, &height,
+ &fps, &interlaced)) {
+ ALOGE("Maximum resolution not found!");
+ return;
+ }
+ score = width * height * fps * (!interlaced + 1);
+ for (size_t i = 0; i < kNumResolutionTypes; ++i) {
+ for (size_t j = 0; j < 32; j++) {
+ if (GetConfiguration((ResolutionType)i, j,
+ &width, &height, &fps, &interlaced)
+ && score >= width * height * fps * (!interlaced + 1)) {
+ setResolutionEnabled((ResolutionType)i, j);
+ setProfileLevel((ResolutionType)i, j, profile, level);
+ }
+ }
+ }
+}
+
+void VideoFormats::setResolutionEnabled(
+ ResolutionType type, size_t index, bool enabled) {
+ CHECK_LT(type, kNumResolutionTypes);
+ CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));
+
+ if (enabled) {
+ mResolutionEnabled[type] |= (1ul << index);
+ mConfigs[type][index].profile = (1ul << PROFILE_CBP);
+ mConfigs[type][index].level = (1ul << LEVEL_31);
+ } else {
+ mResolutionEnabled[type] &= ~(1ul << index);
+ mConfigs[type][index].profile = 0;
+ mConfigs[type][index].level = 0;
+ }
+}
+
+void VideoFormats::setProfileLevel(
+ ResolutionType type, size_t index,
+ ProfileType profile, LevelType level) {
+ CHECK_LT(type, kNumResolutionTypes);
+ CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));
+
+ mConfigs[type][index].profile = (1ul << profile);
+ mConfigs[type][index].level = (1ul << level);
+}
+
+void VideoFormats::getProfileLevel(
+ ResolutionType type, size_t index,
+ ProfileType *profile, LevelType *level) const{
+ CHECK_LT(type, kNumResolutionTypes);
+ CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));
+
+ int i, bestProfile = -1, bestLevel = -1;
+
+ for (i = 0; i < kNumProfileTypes; ++i) {
+ if (mConfigs[type][index].profile & (1ul << i)) {
+ bestProfile = i;
+ }
+ }
+
+ for (i = 0; i < kNumLevelTypes; ++i) {
+ if (mConfigs[type][index].level & (1ul << i)) {
+ bestLevel = i;
+ }
+ }
+
+ if (bestProfile == -1 || bestLevel == -1) {
+ ALOGE("Profile or level not set for resolution type %d, index %zu",
+ type, index);
+ bestProfile = PROFILE_CBP;
+ bestLevel = LEVEL_31;
+ }
+
+ *profile = (ProfileType) bestProfile;
+ *level = (LevelType) bestLevel;
+}
+
+bool VideoFormats::isResolutionEnabled(
+ ResolutionType type, size_t index) const {
+ CHECK_LT(type, kNumResolutionTypes);
+ CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));
+
+ return mResolutionEnabled[type] & (1ul << index);
+}
+
+// static
+bool VideoFormats::GetConfiguration(
+ ResolutionType type,
+ size_t index,
+ size_t *width, size_t *height, size_t *framesPerSecond,
+ bool *interlaced) {
+ CHECK_LT(type, kNumResolutionTypes);
+
+ if (index >= 32) {
+ return false;
+ }
+
+ const config_t *config = &mResolutionTable[type][index];
+
+ if (config->width == 0) {
+ return false;
+ }
+
+ if (width) {
+ *width = config->width;
+ }
+
+ if (height) {
+ *height = config->height;
+ }
+
+ if (framesPerSecond) {
+ *framesPerSecond = config->framesPerSecond;
+ }
+
+ if (interlaced) {
+ *interlaced = config->interlaced;
+ }
+
+ return true;
+}
+
+bool VideoFormats::parseH264Codec(const char *spec) {
+ unsigned profile, level, res[3];
+
+ if (sscanf(
+ spec,
+ "%02x %02x %08X %08X %08X",
+ &profile,
+ &level,
+ &res[0],
+ &res[1],
+ &res[2]) != 5) {
+ return false;
+ }
+
+ for (size_t i = 0; i < kNumResolutionTypes; ++i) {
+ for (size_t j = 0; j < 32; ++j) {
+ if (res[i] & (1ul << j)){
+ mResolutionEnabled[i] |= (1ul << j);
+ if (profile > mConfigs[i][j].profile) {
+ // prefer higher profile (even if level is lower)
+ mConfigs[i][j].profile = profile;
+ mConfigs[i][j].level = level;
+ } else if (profile == mConfigs[i][j].profile &&
+ level > mConfigs[i][j].level) {
+ mConfigs[i][j].level = level;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+// static
+bool VideoFormats::GetProfileLevel(
+ ProfileType profile, LevelType level, unsigned *profileIdc,
+ unsigned *levelIdc, unsigned *constraintSet) {
+ CHECK_LT(profile, kNumProfileTypes);
+ CHECK_LT(level, kNumLevelTypes);
+
+ static const unsigned kProfileIDC[kNumProfileTypes] = {
+ 66, // PROFILE_CBP
+ 100, // PROFILE_CHP
+ };
+
+ static const unsigned kLevelIDC[kNumLevelTypes] = {
+ 31, // LEVEL_31
+ 32, // LEVEL_32
+ 40, // LEVEL_40
+ 41, // LEVEL_41
+ 42, // LEVEL_42
+ };
+
+ static const unsigned kConstraintSet[kNumProfileTypes] = {
+ 0xc0, // PROFILE_CBP
+ 0x0c, // PROFILE_CHP
+ };
+
+ if (profileIdc) {
+ *profileIdc = kProfileIDC[profile];
+ }
+
+ if (levelIdc) {
+ *levelIdc = kLevelIDC[level];
+ }
+
+ if (constraintSet) {
+ *constraintSet = kConstraintSet[profile];
+ }
+
+ return true;
+}
+
+bool VideoFormats::parseFormatSpec(const char *spec) {
+ CHECK_EQ(kNumResolutionTypes, 3);
+
+ disableAll();
+
+ unsigned native, dummy;
+ size_t size = strlen(spec);
+ size_t offset = 0;
+
+ if (sscanf(spec, "%02x %02x ", &native, &dummy) != 2) {
+ return false;
+ }
+
+ offset += 6; // skip native and preferred-display-mode-supported
+ CHECK_LE(offset + 58, size);
+ while (offset < size) {
+ parseH264Codec(spec + offset);
+ offset += 60; // skip H.264-codec + ", "
+ }
+
+ mNativeIndex = native >> 3;
+ mNativeType = (ResolutionType)(native & 7);
+
+ bool success;
+ if (mNativeType >= kNumResolutionTypes) {
+ success = false;
+ } else {
+ success = GetConfiguration(
+ mNativeType, mNativeIndex, NULL, NULL, NULL, NULL);
+ }
+
+ if (!success) {
+ ALOGW("sink advertised an illegal native resolution, fortunately "
+ "this value is ignored for the time being...");
+ }
+
+ return true;
+}
+
+AString VideoFormats::getFormatSpec(bool forM4Message) const {
+ CHECK_EQ(kNumResolutionTypes, 3);
+
+ // wfd_video_formats:
+ // 1 byte "native"
+ // 1 byte "preferred-display-mode-supported" 0 or 1
+ // one or more avc codec structures
+ // 1 byte profile
+ // 1 byte level
+ // 4 byte CEA mask
+ // 4 byte VESA mask
+ // 4 byte HH mask
+ // 1 byte latency
+ // 2 byte min-slice-slice
+ // 2 byte slice-enc-params
+ // 1 byte framerate-control-support
+ // max-hres (none or 2 byte)
+ // max-vres (none or 2 byte)
+
+ return AStringPrintf(
+ "%02x 00 %02x %02x %08x %08x %08x 00 0000 0000 00 none none",
+ forM4Message ? 0x00 : ((mNativeIndex << 3) | mNativeType),
+ mConfigs[mNativeType][mNativeIndex].profile,
+ mConfigs[mNativeType][mNativeIndex].level,
+ mResolutionEnabled[0],
+ mResolutionEnabled[1],
+ mResolutionEnabled[2]);
+}
+
+// static
+bool VideoFormats::PickBestFormat(
+ const VideoFormats &sinkSupported,
+ const VideoFormats &sourceSupported,
+ ResolutionType *chosenType,
+ size_t *chosenIndex,
+ ProfileType *chosenProfile,
+ LevelType *chosenLevel) {
+#if 0
+ // Support for the native format is a great idea, the spec includes
+ // these features, but nobody supports it and the tests don't validate it.
+
+ ResolutionType nativeType;
+ size_t nativeIndex;
+ sinkSupported.getNativeResolution(&nativeType, &nativeIndex);
+ if (sinkSupported.isResolutionEnabled(nativeType, nativeIndex)) {
+ if (sourceSupported.isResolutionEnabled(nativeType, nativeIndex)) {
+ ALOGI("Choosing sink's native resolution");
+ *chosenType = nativeType;
+ *chosenIndex = nativeIndex;
+ return true;
+ }
+ } else {
+ ALOGW("Sink advertised native resolution that it doesn't "
+ "actually support... ignoring");
+ }
+
+ sourceSupported.getNativeResolution(&nativeType, &nativeIndex);
+ if (sourceSupported.isResolutionEnabled(nativeType, nativeIndex)) {
+ if (sinkSupported.isResolutionEnabled(nativeType, nativeIndex)) {
+ ALOGI("Choosing source's native resolution");
+ *chosenType = nativeType;
+ *chosenIndex = nativeIndex;
+ return true;
+ }
+ } else {
+ ALOGW("Source advertised native resolution that it doesn't "
+ "actually support... ignoring");
+ }
+#endif
+
+ bool first = true;
+ uint32_t bestScore = 0;
+ size_t bestType = 0;
+ size_t bestIndex = 0;
+ for (size_t i = 0; i < kNumResolutionTypes; ++i) {
+ for (size_t j = 0; j < 32; ++j) {
+ size_t width, height, framesPerSecond;
+ bool interlaced;
+ if (!GetConfiguration(
+ (ResolutionType)i,
+ j,
+ &width, &height, &framesPerSecond, &interlaced)) {
+ break;
+ }
+
+ if (!sinkSupported.isResolutionEnabled((ResolutionType)i, j)
+ || !sourceSupported.isResolutionEnabled(
+ (ResolutionType)i, j)) {
+ continue;
+ }
+
+ ALOGV("type %zu, index %zu, %zu x %zu %c%zu supported",
+ i, j, width, height, interlaced ? 'i' : 'p', framesPerSecond);
+
+ uint32_t score = width * height * framesPerSecond;
+ if (!interlaced) {
+ score *= 2;
+ }
+
+ if (first || score > bestScore) {
+ bestScore = score;
+ bestType = i;
+ bestIndex = j;
+
+ first = false;
+ }
+ }
+ }
+
+ if (first) {
+ return false;
+ }
+
+ *chosenType = (ResolutionType)bestType;
+ *chosenIndex = bestIndex;
+
+ // Pick the best profile/level supported by both sink and source.
+ ProfileType srcProfile, sinkProfile;
+ LevelType srcLevel, sinkLevel;
+ sourceSupported.getProfileLevel(
+ (ResolutionType)bestType, bestIndex,
+ &srcProfile, &srcLevel);
+ sinkSupported.getProfileLevel(
+ (ResolutionType)bestType, bestIndex,
+ &sinkProfile, &sinkLevel);
+ *chosenProfile = srcProfile < sinkProfile ? srcProfile : sinkProfile;
+ *chosenLevel = srcLevel < sinkLevel ? srcLevel : sinkLevel;
+
+ return true;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/VideoFormats.h b/media/libstagefright/wifi-display/VideoFormats.h
new file mode 100644
index 0000000..fd38fd1
--- /dev/null
+++ b/media/libstagefright/wifi-display/VideoFormats.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013, 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 VIDEO_FORMATS_H_
+
+#define VIDEO_FORMATS_H_
+
+#include <media/stagefright/foundation/ABase.h>
+
+#include <stdint.h>
+
+namespace android {
+
+struct AString;
+
+// This class encapsulates that video resolution capabilities of a wfd source
+// or sink as outlined in the wfd specs. Currently three sets of resolutions
+// are specified, each of which supports up to 32 resolutions.
+// In addition to its capabilities each sink/source also publishes its
+// "native" resolution, presumably one that is preferred among all others
+// because it wouldn't require any scaling and directly corresponds to the
+// display capabilities/pixels.
+struct VideoFormats {
+ VideoFormats();
+
+ struct config_t {
+ size_t width, height, framesPerSecond;
+ bool interlaced;
+ unsigned char profile, level;
+ };
+
+ enum ProfileType {
+ PROFILE_CBP = 0,
+ PROFILE_CHP,
+ kNumProfileTypes,
+ };
+
+ enum LevelType {
+ LEVEL_31 = 0,
+ LEVEL_32,
+ LEVEL_40,
+ LEVEL_41,
+ LEVEL_42,
+ kNumLevelTypes,
+ };
+
+ enum ResolutionType {
+ RESOLUTION_CEA,
+ RESOLUTION_VESA,
+ RESOLUTION_HH,
+ kNumResolutionTypes,
+ };
+
+ void setNativeResolution(ResolutionType type, size_t index);
+ void getNativeResolution(ResolutionType *type, size_t *index) const;
+
+ void disableAll();
+ void enableAll();
+ void enableResolutionUpto(
+ ResolutionType type, size_t index,
+ ProfileType profile, LevelType level);
+
+ void setResolutionEnabled(
+ ResolutionType type, size_t index, bool enabled = true);
+
+ bool isResolutionEnabled(ResolutionType type, size_t index) const;
+
+ void setProfileLevel(
+ ResolutionType type, size_t index,
+ ProfileType profile, LevelType level);
+
+ void getProfileLevel(
+ ResolutionType type, size_t index,
+ ProfileType *profile, LevelType *level) const;
+
+ static bool GetConfiguration(
+ ResolutionType type, size_t index,
+ size_t *width, size_t *height, size_t *framesPerSecond,
+ bool *interlaced);
+
+ static bool GetProfileLevel(
+ ProfileType profile, LevelType level,
+ unsigned *profileIdc, unsigned *levelIdc,
+ unsigned *constraintSet);
+
+ bool parseFormatSpec(const char *spec);
+ AString getFormatSpec(bool forM4Message = false) const;
+
+ static bool PickBestFormat(
+ const VideoFormats &sinkSupported,
+ const VideoFormats &sourceSupported,
+ ResolutionType *chosenType,
+ size_t *chosenIndex,
+ ProfileType *chosenProfile,
+ LevelType *chosenLevel);
+
+private:
+ bool parseH264Codec(const char *spec);
+ ResolutionType mNativeType;
+ size_t mNativeIndex;
+
+ uint32_t mResolutionEnabled[kNumResolutionTypes];
+ static const config_t mResolutionTable[kNumResolutionTypes][32];
+ config_t mConfigs[kNumResolutionTypes][32];
+
+ DISALLOW_EVIL_CONSTRUCTORS(VideoFormats);
+};
+
+} // namespace android
+
+#endif // VIDEO_FORMATS_H_
+
diff --git a/media/libstagefright/wifi-display/rtp/RTPBase.h b/media/libstagefright/wifi-display/rtp/RTPBase.h
new file mode 100644
index 0000000..194f1ee
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtp/RTPBase.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013, 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 RTP_BASE_H_
+
+#define RTP_BASE_H_
+
+namespace android {
+
+struct RTPBase {
+ enum PacketizationMode {
+ PACKETIZATION_TRANSPORT_STREAM,
+ PACKETIZATION_H264,
+ PACKETIZATION_AAC,
+ PACKETIZATION_NONE,
+ };
+
+ enum TransportMode {
+ TRANSPORT_UNDEFINED,
+ TRANSPORT_NONE,
+ TRANSPORT_UDP,
+ TRANSPORT_TCP,
+ TRANSPORT_TCP_INTERLEAVED,
+ };
+
+ // Really UDP _payload_ size
+ const unsigned int kMaxUDPPacketSize = 1472; // 1472 good, 1473 bad on Android@Home
+
+ static int32_t PickRandomRTPPort();
+};
+
+} // namespace android
+
+#endif // RTP_BASE_H_
+
+
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.cpp b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
new file mode 100644
index 0000000..ba01b74
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
@@ -0,0 +1,808 @@
+/*
+ * Copyright 2013, 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 "RTPSender"
+#include <utils/Log.h>
+
+#include "RTPSender.h"
+
+#include <media/stagefright/foundation/avc_utils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ByteUtils.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/ANetworkSession.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+RTPSender::RTPSender(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> ¬ify)
+ : mNetSession(netSession),
+ mNotify(notify),
+ mRTPMode(TRANSPORT_UNDEFINED),
+ mRTCPMode(TRANSPORT_UNDEFINED),
+ mRTPSessionID(0),
+ mRTCPSessionID(0),
+ mRTPConnected(false),
+ mRTCPConnected(false),
+ mLastNTPTime(0),
+ mLastRTPTime(0),
+ mNumRTPSent(0),
+ mNumRTPOctetsSent(0),
+ mNumSRsSent(0),
+ mRTPSeqNo(0),
+ mHistorySize(0) {
+}
+
+RTPSender::~RTPSender() {
+ if (mRTCPSessionID != 0) {
+ mNetSession->destroySession(mRTCPSessionID);
+ mRTCPSessionID = 0;
+ }
+
+ if (mRTPSessionID != 0) {
+ mNetSession->destroySession(mRTPSessionID);
+ mRTPSessionID = 0;
+ }
+}
+
+// static
+int32_t RTPBase::PickRandomRTPPort() {
+ // Pick an even integer in range [1024, 65534)
+
+ static const size_t kRange = (65534 - 1024) / 2;
+
+ return (int32_t)(((float)(kRange + 1) * rand()) / RAND_MAX) * 2 + 1024;
+}
+
+status_t RTPSender::initAsync(
+ const char *remoteHost,
+ int32_t remoteRTPPort,
+ TransportMode rtpMode,
+ int32_t remoteRTCPPort,
+ TransportMode rtcpMode,
+ int32_t *outLocalRTPPort) {
+ if (mRTPMode != TRANSPORT_UNDEFINED
+ || rtpMode == TRANSPORT_UNDEFINED
+ || rtpMode == TRANSPORT_NONE
+ || rtcpMode == TRANSPORT_UNDEFINED) {
+ return INVALID_OPERATION;
+ }
+
+ CHECK_NE(rtpMode, TRANSPORT_TCP_INTERLEAVED);
+ CHECK_NE(rtcpMode, TRANSPORT_TCP_INTERLEAVED);
+
+ if ((rtcpMode == TRANSPORT_NONE && remoteRTCPPort >= 0)
+ || (rtcpMode != TRANSPORT_NONE && remoteRTCPPort < 0)) {
+ return INVALID_OPERATION;
+ }
+
+ sp<AMessage> rtpNotify = new AMessage(kWhatRTPNotify, this);
+
+ sp<AMessage> rtcpNotify;
+ if (remoteRTCPPort >= 0) {
+ rtcpNotify = new AMessage(kWhatRTCPNotify, this);
+ }
+
+ CHECK_EQ(mRTPSessionID, 0);
+ CHECK_EQ(mRTCPSessionID, 0);
+
+ int32_t localRTPPort;
+
+ for (;;) {
+ localRTPPort = PickRandomRTPPort();
+
+ status_t err;
+ if (rtpMode == TRANSPORT_UDP) {
+ err = mNetSession->createUDPSession(
+ localRTPPort,
+ remoteHost,
+ remoteRTPPort,
+ rtpNotify,
+ &mRTPSessionID);
+ } else {
+ CHECK_EQ(rtpMode, TRANSPORT_TCP);
+ err = mNetSession->createTCPDatagramSession(
+ localRTPPort,
+ remoteHost,
+ remoteRTPPort,
+ rtpNotify,
+ &mRTPSessionID);
+ }
+
+ if (err != OK) {
+ continue;
+ }
+
+ if (remoteRTCPPort < 0) {
+ break;
+ }
+
+ if (rtcpMode == TRANSPORT_UDP) {
+ err = mNetSession->createUDPSession(
+ localRTPPort + 1,
+ remoteHost,
+ remoteRTCPPort,
+ rtcpNotify,
+ &mRTCPSessionID);
+ } else {
+ CHECK_EQ(rtcpMode, TRANSPORT_TCP);
+ err = mNetSession->createTCPDatagramSession(
+ localRTPPort + 1,
+ remoteHost,
+ remoteRTCPPort,
+ rtcpNotify,
+ &mRTCPSessionID);
+ }
+
+ if (err == OK) {
+ break;
+ }
+
+ mNetSession->destroySession(mRTPSessionID);
+ mRTPSessionID = 0;
+ }
+
+ if (rtpMode == TRANSPORT_UDP) {
+ mRTPConnected = true;
+ }
+
+ if (rtcpMode == TRANSPORT_UDP) {
+ mRTCPConnected = true;
+ }
+
+ mRTPMode = rtpMode;
+ mRTCPMode = rtcpMode;
+ *outLocalRTPPort = localRTPPort;
+
+ if (mRTPMode == TRANSPORT_UDP
+ && (mRTCPMode == TRANSPORT_UDP || mRTCPMode == TRANSPORT_NONE)) {
+ notifyInitDone(OK);
+ }
+
+ return OK;
+}
+
+status_t RTPSender::queueBuffer(
+ const sp<ABuffer> &buffer, uint8_t packetType, PacketizationMode mode) {
+ status_t err;
+
+ switch (mode) {
+ case PACKETIZATION_NONE:
+ err = queueRawPacket(buffer, packetType);
+ break;
+
+ case PACKETIZATION_TRANSPORT_STREAM:
+ err = queueTSPackets(buffer, packetType);
+ break;
+
+ case PACKETIZATION_H264:
+ err = queueAVCBuffer(buffer, packetType);
+ break;
+
+ default:
+ TRESPASS();
+ }
+
+ return err;
+}
+
+status_t RTPSender::queueRawPacket(
+ const sp<ABuffer> &packet, uint8_t packetType) {
+ CHECK_LE(packet->size(), kMaxUDPPacketSize - 12);
+
+ int64_t timeUs;
+ CHECK(packet->meta()->findInt64("timeUs", &timeUs));
+
+ sp<ABuffer> udpPacket = new ABuffer(12 + packet->size());
+
+ udpPacket->setInt32Data(mRTPSeqNo);
+
+ uint8_t *rtp = udpPacket->data();
+ rtp[0] = 0x80;
+ rtp[1] = packetType;
+
+ rtp[2] = (mRTPSeqNo >> 8) & 0xff;
+ rtp[3] = mRTPSeqNo & 0xff;
+ ++mRTPSeqNo;
+
+ uint32_t rtpTime = (timeUs * 9) / 100ll;
+
+ rtp[4] = rtpTime >> 24;
+ rtp[5] = (rtpTime >> 16) & 0xff;
+ rtp[6] = (rtpTime >> 8) & 0xff;
+ rtp[7] = rtpTime & 0xff;
+
+ rtp[8] = kSourceID >> 24;
+ rtp[9] = (kSourceID >> 16) & 0xff;
+ rtp[10] = (kSourceID >> 8) & 0xff;
+ rtp[11] = kSourceID & 0xff;
+
+ memcpy(&rtp[12], packet->data(), packet->size());
+
+ return sendRTPPacket(
+ udpPacket,
+ true /* storeInHistory */,
+ true /* timeValid */,
+ ALooper::GetNowUs());
+}
+
+status_t RTPSender::queueTSPackets(
+ const sp<ABuffer> &tsPackets, uint8_t packetType) {
+ CHECK_EQ(0u, tsPackets->size() % 188);
+
+ int64_t timeUs;
+ CHECK(tsPackets->meta()->findInt64("timeUs", &timeUs));
+
+ size_t srcOffset = 0;
+ while (srcOffset < tsPackets->size()) {
+ sp<ABuffer> udpPacket =
+ new ABuffer(12 + kMaxNumTSPacketsPerRTPPacket * 188);
+
+ udpPacket->setInt32Data(mRTPSeqNo);
+
+ uint8_t *rtp = udpPacket->data();
+ rtp[0] = 0x80;
+ rtp[1] = packetType;
+
+ rtp[2] = (mRTPSeqNo >> 8) & 0xff;
+ rtp[3] = mRTPSeqNo & 0xff;
+ ++mRTPSeqNo;
+
+ int64_t nowUs = ALooper::GetNowUs();
+ uint32_t rtpTime = (nowUs * 9) / 100ll;
+
+ rtp[4] = rtpTime >> 24;
+ rtp[5] = (rtpTime >> 16) & 0xff;
+ rtp[6] = (rtpTime >> 8) & 0xff;
+ rtp[7] = rtpTime & 0xff;
+
+ rtp[8] = kSourceID >> 24;
+ rtp[9] = (kSourceID >> 16) & 0xff;
+ rtp[10] = (kSourceID >> 8) & 0xff;
+ rtp[11] = kSourceID & 0xff;
+
+ size_t numTSPackets = (tsPackets->size() - srcOffset) / 188;
+ if (numTSPackets > kMaxNumTSPacketsPerRTPPacket) {
+ numTSPackets = kMaxNumTSPacketsPerRTPPacket;
+ }
+
+ memcpy(&rtp[12], tsPackets->data() + srcOffset, numTSPackets * 188);
+
+ udpPacket->setRange(0, 12 + numTSPackets * 188);
+
+ srcOffset += numTSPackets * 188;
+ bool isLastPacket = (srcOffset == tsPackets->size());
+
+ status_t err = sendRTPPacket(
+ udpPacket,
+ true /* storeInHistory */,
+ isLastPacket /* timeValid */,
+ timeUs);
+
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ return OK;
+}
+
+status_t RTPSender::queueAVCBuffer(
+ const sp<ABuffer> &accessUnit, uint8_t packetType) {
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ uint32_t rtpTime = (timeUs * 9 / 100ll);
+
+ List<sp<ABuffer> > packets;
+
+ sp<ABuffer> out = new ABuffer(kMaxUDPPacketSize);
+ size_t outBytesUsed = 12; // Placeholder for RTP header.
+
+ const uint8_t *data = accessUnit->data();
+ size_t size = accessUnit->size();
+ const uint8_t *nalStart;
+ size_t nalSize;
+ while (getNextNALUnit(
+ &data, &size, &nalStart, &nalSize,
+ true /* startCodeFollows */) == OK) {
+ size_t bytesNeeded = nalSize + 2;
+ if (outBytesUsed == 12) {
+ ++bytesNeeded;
+ }
+
+ if (outBytesUsed + bytesNeeded > out->capacity()) {
+ bool emitSingleNALPacket = false;
+
+ if (outBytesUsed == 12
+ && outBytesUsed + nalSize <= out->capacity()) {
+ // We haven't emitted anything into the current packet yet and
+ // this NAL unit fits into a single-NAL-unit-packet while
+ // it wouldn't have fit as part of a STAP-A packet.
+
+ memcpy(out->data() + outBytesUsed, nalStart, nalSize);
+ outBytesUsed += nalSize;
+
+ emitSingleNALPacket = true;
+ }
+
+ if (outBytesUsed > 12) {
+ out->setRange(0, outBytesUsed);
+ packets.push_back(out);
+ out = new ABuffer(kMaxUDPPacketSize);
+ outBytesUsed = 12; // Placeholder for RTP header
+ }
+
+ if (emitSingleNALPacket) {
+ continue;
+ }
+ }
+
+ if (outBytesUsed + bytesNeeded <= out->capacity()) {
+ uint8_t *dst = out->data() + outBytesUsed;
+
+ if (outBytesUsed == 12) {
+ *dst++ = 24; // STAP-A header
+ }
+
+ *dst++ = (nalSize >> 8) & 0xff;
+ *dst++ = nalSize & 0xff;
+ memcpy(dst, nalStart, nalSize);
+
+ outBytesUsed += bytesNeeded;
+ continue;
+ }
+
+ // This single NAL unit does not fit into a single RTP packet,
+ // we need to emit an FU-A.
+
+ CHECK_EQ(outBytesUsed, 12u);
+
+ uint8_t nalType = nalStart[0] & 0x1f;
+ uint8_t nri = (nalStart[0] >> 5) & 3;
+
+ size_t srcOffset = 1;
+ while (srcOffset < nalSize) {
+ size_t copy = out->capacity() - outBytesUsed - 2;
+ if (copy > nalSize - srcOffset) {
+ copy = nalSize - srcOffset;
+ }
+
+ uint8_t *dst = out->data() + outBytesUsed;
+ dst[0] = (nri << 5) | 28;
+
+ dst[1] = nalType;
+
+ if (srcOffset == 1) {
+ dst[1] |= 0x80;
+ }
+
+ if (srcOffset + copy == nalSize) {
+ dst[1] |= 0x40;
+ }
+
+ memcpy(&dst[2], nalStart + srcOffset, copy);
+ srcOffset += copy;
+
+ out->setRange(0, outBytesUsed + copy + 2);
+
+ packets.push_back(out);
+ out = new ABuffer(kMaxUDPPacketSize);
+ outBytesUsed = 12; // Placeholder for RTP header
+ }
+ }
+
+ if (outBytesUsed > 12) {
+ out->setRange(0, outBytesUsed);
+ packets.push_back(out);
+ }
+
+ while (!packets.empty()) {
+ sp<ABuffer> out = *packets.begin();
+ packets.erase(packets.begin());
+
+ out->setInt32Data(mRTPSeqNo);
+
+ bool last = packets.empty();
+
+ uint8_t *dst = out->data();
+
+ dst[0] = 0x80;
+
+ dst[1] = packetType;
+ if (last) {
+ dst[1] |= 1 << 7; // M-bit
+ }
+
+ dst[2] = (mRTPSeqNo >> 8) & 0xff;
+ dst[3] = mRTPSeqNo & 0xff;
+ ++mRTPSeqNo;
+
+ dst[4] = rtpTime >> 24;
+ dst[5] = (rtpTime >> 16) & 0xff;
+ dst[6] = (rtpTime >> 8) & 0xff;
+ dst[7] = rtpTime & 0xff;
+ dst[8] = kSourceID >> 24;
+ dst[9] = (kSourceID >> 16) & 0xff;
+ dst[10] = (kSourceID >> 8) & 0xff;
+ dst[11] = kSourceID & 0xff;
+
+ status_t err = sendRTPPacket(out, true /* storeInHistory */);
+
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ return OK;
+}
+
+status_t RTPSender::sendRTPPacket(
+ const sp<ABuffer> &buffer, bool storeInHistory,
+ bool timeValid, int64_t timeUs) {
+ CHECK(mRTPConnected);
+
+ status_t err = mNetSession->sendRequest(
+ mRTPSessionID, buffer->data(), buffer->size(),
+ timeValid, timeUs);
+
+ if (err != OK) {
+ return err;
+ }
+
+ mLastNTPTime = GetNowNTP();
+ mLastRTPTime = U32_AT(buffer->data() + 4);
+
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += buffer->size() - 12;
+
+ if (storeInHistory) {
+ if (mHistorySize == kMaxHistorySize) {
+ mHistory.erase(mHistory.begin());
+ } else {
+ ++mHistorySize;
+ }
+ mHistory.push_back(buffer);
+ }
+
+ return OK;
+}
+
+// static
+uint64_t RTPSender::GetNowNTP() {
+ struct timeval tv;
+ gettimeofday(&tv, NULL /* timezone */);
+
+ uint64_t nowUs = tv.tv_sec * 1000000ll + tv.tv_usec;
+
+ nowUs += ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll;
+
+ uint64_t hi = nowUs / 1000000ll;
+ uint64_t lo = ((1ll << 32) * (nowUs % 1000000ll)) / 1000000ll;
+
+ return (hi << 32) | lo;
+}
+
+void RTPSender::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRTPNotify:
+ case kWhatRTCPNotify:
+ onNetNotify(msg->what() == kWhatRTPNotify, msg);
+ break;
+
+ default:
+ TRESPASS();
+ }
+}
+
+void RTPSender::onNetNotify(bool isRTP, const sp<AMessage> &msg) {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatError:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ int32_t errorOccuredDuringSend;
+ CHECK(msg->findInt32("send", &errorOccuredDuringSend));
+
+ AString detail;
+ CHECK(msg->findString("detail", &detail));
+
+ ALOGE("An error occurred during %s in session %d "
+ "(%d, '%s' (%s)).",
+ errorOccuredDuringSend ? "send" : "receive",
+ sessionID,
+ err,
+ detail.c_str(),
+ strerror(-err));
+
+ mNetSession->destroySession(sessionID);
+
+ if (sessionID == mRTPSessionID) {
+ mRTPSessionID = 0;
+ } else if (sessionID == mRTCPSessionID) {
+ mRTCPSessionID = 0;
+ }
+
+ if (!mRTPConnected
+ || (mRTPMode != TRANSPORT_NONE && !mRTCPConnected)) {
+ // We haven't completed initialization, attach the error
+ // to the notification instead.
+ notifyInitDone(err);
+ break;
+ }
+
+ notifyError(err);
+ break;
+ }
+
+ case ANetworkSession::kWhatDatagram:
+ {
+ sp<ABuffer> data;
+ CHECK(msg->findBuffer("data", &data));
+
+ if (isRTP) {
+ ALOGW("Huh? Received data on RTP connection...");
+ } else {
+ onRTCPData(data);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatConnected:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ if (isRTP) {
+ CHECK_EQ(mRTPMode, TRANSPORT_TCP);
+ CHECK_EQ(sessionID, mRTPSessionID);
+ mRTPConnected = true;
+ } else {
+ CHECK_EQ(mRTCPMode, TRANSPORT_TCP);
+ CHECK_EQ(sessionID, mRTCPSessionID);
+ mRTCPConnected = true;
+ }
+
+ if (mRTPConnected
+ && (mRTCPMode == TRANSPORT_NONE || mRTCPConnected)) {
+ notifyInitDone(OK);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatNetworkStall:
+ {
+ size_t numBytesQueued;
+ CHECK(msg->findSize("numBytesQueued", &numBytesQueued));
+
+ notifyNetworkStall(numBytesQueued);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+status_t RTPSender::onRTCPData(const sp<ABuffer> &buffer) {
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ while (size > 0) {
+ if (size < 8) {
+ // Too short to be a valid RTCP header
+ return ERROR_MALFORMED;
+ }
+
+ if ((data[0] >> 6) != 2) {
+ // Unsupported version.
+ return ERROR_UNSUPPORTED;
+ }
+
+ if (data[0] & 0x20) {
+ // Padding present.
+
+ size_t paddingLength = data[size - 1];
+
+ if (paddingLength + 12 > size) {
+ // If we removed this much padding we'd end up with something
+ // that's too short to be a valid RTP header.
+ return ERROR_MALFORMED;
+ }
+
+ size -= paddingLength;
+ }
+
+ size_t headerLength = 4 * (data[2] << 8 | data[3]) + 4;
+
+ if (size < headerLength) {
+ // Only received a partial packet?
+ return ERROR_MALFORMED;
+ }
+
+ switch (data[1]) {
+ case 200:
+ case 201: // RR
+ parseReceiverReport(data, headerLength);
+ break;
+
+ case 202: // SDES
+ case 203:
+ break;
+
+ case 204: // APP
+ parseAPP(data, headerLength);
+ break;
+
+ case 205: // TSFB (transport layer specific feedback)
+ parseTSFB(data, headerLength);
+ break;
+
+ case 206: // PSFB (payload specific feedback)
+ // hexdump(data, headerLength);
+ break;
+
+ default:
+ {
+ ALOGW("Unknown RTCP packet type %u of size %zu",
+ (unsigned)data[1], headerLength);
+ break;
+ }
+ }
+
+ data += headerLength;
+ size -= headerLength;
+ }
+
+ return OK;
+}
+
+status_t RTPSender::parseReceiverReport(
+ const uint8_t *data, size_t /* size */) {
+ float fractionLost = data[12] / 256.0f;
+
+ ALOGI("lost %.2f %% of packets during report interval.",
+ 100.0f * fractionLost);
+
+ return OK;
+}
+
+status_t RTPSender::parseTSFB(const uint8_t *data, size_t size) {
+ if ((data[0] & 0x1f) != 1) {
+ return ERROR_UNSUPPORTED; // We only support NACK for now.
+ }
+
+ uint32_t srcId = U32_AT(&data[8]);
+ if (srcId != kSourceID) {
+ return ERROR_MALFORMED;
+ }
+
+ for (size_t i = 12; i < size; i += 4) {
+ uint16_t seqNo = U16_AT(&data[i]);
+ uint16_t blp = U16_AT(&data[i + 2]);
+
+ List<sp<ABuffer> >::iterator it = mHistory.begin();
+ bool foundSeqNo = false;
+ while (it != mHistory.end()) {
+ const sp<ABuffer> &buffer = *it;
+
+ uint16_t bufferSeqNo = buffer->int32Data() & 0xffff;
+
+ bool retransmit = false;
+ if (bufferSeqNo == seqNo) {
+ retransmit = true;
+ } else if (blp != 0) {
+ for (size_t i = 0; i < 16; ++i) {
+ if ((blp & (1 << i))
+ && (bufferSeqNo == ((seqNo + i + 1) & 0xffff))) {
+ blp &= ~(1 << i);
+ retransmit = true;
+ }
+ }
+ }
+
+ if (retransmit) {
+ ALOGV("retransmitting seqNo %d", bufferSeqNo);
+
+ CHECK_EQ((status_t)OK,
+ sendRTPPacket(buffer, false /* storeInHistory */));
+
+ if (bufferSeqNo == seqNo) {
+ foundSeqNo = true;
+ }
+
+ if (foundSeqNo && blp == 0) {
+ break;
+ }
+ }
+
+ ++it;
+ }
+
+ if (!foundSeqNo || blp != 0) {
+ ALOGI("Some sequence numbers were no longer available for "
+ "retransmission (seqNo = %d, foundSeqNo = %d, blp = 0x%04x)",
+ seqNo, foundSeqNo, blp);
+
+ if (!mHistory.empty()) {
+ int32_t earliest = (*mHistory.begin())->int32Data() & 0xffff;
+ int32_t latest = (*--mHistory.end())->int32Data() & 0xffff;
+
+ ALOGI("have seq numbers from %d - %d", earliest, latest);
+ }
+ }
+ }
+
+ return OK;
+}
+
+status_t RTPSender::parseAPP(const uint8_t *data, size_t size) {
+ static const size_t late_offset = 8;
+ static const char late_string[] = "late";
+ static const size_t avgLatencyUs_offset = late_offset + sizeof(late_string) - 1;
+ static const size_t maxLatencyUs_offset = avgLatencyUs_offset + sizeof(int64_t);
+
+ if ((size >= (maxLatencyUs_offset + sizeof(int64_t)))
+ && !memcmp(late_string, &data[late_offset], sizeof(late_string) - 1)) {
+ int64_t avgLatencyUs = (int64_t)U64_AT(&data[avgLatencyUs_offset]);
+ int64_t maxLatencyUs = (int64_t)U64_AT(&data[maxLatencyUs_offset]);
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInformSender);
+ notify->setInt64("avgLatencyUs", avgLatencyUs);
+ notify->setInt64("maxLatencyUs", maxLatencyUs);
+ notify->post();
+ }
+
+ return OK;
+}
+
+void RTPSender::notifyInitDone(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInitDone);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void RTPSender::notifyError(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void RTPSender::notifyNetworkStall(size_t numBytesQueued) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatNetworkStall);
+ notify->setSize("numBytesQueued", numBytesQueued);
+ notify->post();
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.h b/media/libstagefright/wifi-display/rtp/RTPSender.h
new file mode 100644
index 0000000..bedfd01
--- /dev/null
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2013, 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 RTP_SENDER_H_
+
+#define RTP_SENDER_H_
+
+#include "RTPBase.h"
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ABuffer;
+struct ANetworkSession;
+
+// An object of this class facilitates sending of media data over an RTP
+// channel. The channel is established over a UDP or TCP connection depending
+// on which "TransportMode" was chosen. In addition different RTP packetization
+// schemes are supported such as "Transport Stream Packets over RTP",
+// or "AVC/H.264 encapsulation as specified in RFC 3984 (non-interleaved mode)"
+struct RTPSender : public RTPBase, public AHandler {
+ enum {
+ kWhatInitDone,
+ kWhatError,
+ kWhatNetworkStall,
+ kWhatInformSender,
+ };
+ RTPSender(
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> ¬ify);
+
+ status_t initAsync(
+ const char *remoteHost,
+ int32_t remoteRTPPort,
+ TransportMode rtpMode,
+ int32_t remoteRTCPPort,
+ TransportMode rtcpMode,
+ int32_t *outLocalRTPPort);
+
+ status_t queueBuffer(
+ const sp<ABuffer> &buffer,
+ uint8_t packetType,
+ PacketizationMode mode);
+
+protected:
+ virtual ~RTPSender();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatRTPNotify,
+ kWhatRTCPNotify,
+ };
+
+ const unsigned int kMaxNumTSPacketsPerRTPPacket = (kMaxUDPPacketSize - 12) / 188;
+ const unsigned int kMaxHistorySize = 1024;
+ const unsigned int kSourceID = 0xdeadbeef;
+
+ sp<ANetworkSession> mNetSession;
+ sp<AMessage> mNotify;
+ TransportMode mRTPMode;
+ TransportMode mRTCPMode;
+ int32_t mRTPSessionID;
+ int32_t mRTCPSessionID;
+ bool mRTPConnected;
+ bool mRTCPConnected;
+
+ uint64_t mLastNTPTime;
+ uint32_t mLastRTPTime;
+ uint32_t mNumRTPSent;
+ uint32_t mNumRTPOctetsSent;
+ uint32_t mNumSRsSent;
+
+ uint32_t mRTPSeqNo;
+
+ List<sp<ABuffer> > mHistory;
+ size_t mHistorySize;
+
+ static uint64_t GetNowNTP();
+
+ status_t queueRawPacket(const sp<ABuffer> &tsPackets, uint8_t packetType);
+ status_t queueTSPackets(const sp<ABuffer> &tsPackets, uint8_t packetType);
+ status_t queueAVCBuffer(const sp<ABuffer> &accessUnit, uint8_t packetType);
+
+ status_t sendRTPPacket(
+ const sp<ABuffer> &packet, bool storeInHistory,
+ bool timeValid = false, int64_t timeUs = -1ll);
+
+ void onNetNotify(bool isRTP, const sp<AMessage> &msg);
+
+ status_t onRTCPData(const sp<ABuffer> &data);
+ status_t parseReceiverReport(const uint8_t *data, size_t size);
+ status_t parseTSFB(const uint8_t *data, size_t size);
+ status_t parseAPP(const uint8_t *data, size_t size);
+
+ void notifyInitDone(status_t err);
+ void notifyError(status_t err);
+ void notifyNetworkStall(size_t numBytesQueued);
+
+ DISALLOW_EVIL_CONSTRUCTORS(RTPSender);
+};
+
+} // namespace android
+
+#endif // RTP_SENDER_H_
diff --git a/media/libstagefright/wifi-display/source/Converter.cpp b/media/libstagefright/wifi-display/source/Converter.cpp
new file mode 100644
index 0000000..5502b06
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/Converter.cpp
@@ -0,0 +1,826 @@
+/*
+ * 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 "Converter"
+#include <utils/Log.h>
+
+#include "Converter.h"
+
+#include "MediaPuller.h"
+
+#include <cutils/properties.h>
+#include <gui/Surface.h>
+#include <mediadrm/ICrypto.h>
+#include <media/MediaCodecBuffer.h>
+#include <media/stagefright/foundation/avc_utils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaBufferBase.h>
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/MediaBufferHolder.h>
+
+#include <arpa/inet.h>
+
+#include <OMX_Video.h>
+
+namespace android {
+
+Converter::Converter(
+ const sp<AMessage> ¬ify,
+ const sp<ALooper> &codecLooper,
+ const sp<AMessage> &outputFormat,
+ uint32_t flags)
+ : mNotify(notify),
+ mCodecLooper(codecLooper),
+ mOutputFormat(outputFormat),
+ mFlags(flags),
+ mIsVideo(false),
+ mIsH264(false),
+ mIsPCMAudio(false),
+ mNeedToManuallyPrependSPSPPS(false),
+ mDoMoreWorkPending(false)
+#if ENABLE_SILENCE_DETECTION
+ ,mFirstSilentFrameUs(-1ll)
+ ,mInSilentMode(false)
+#endif
+ ,mPrevVideoBitrate(-1)
+ ,mNumFramesToDrop(0)
+ ,mEncodingSuspended(false)
+ {
+ AString mime;
+ CHECK(mOutputFormat->findString("mime", &mime));
+
+ if (!strncasecmp("video/", mime.c_str(), 6)) {
+ mIsVideo = true;
+
+ mIsH264 = !strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_AVC);
+ } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_RAW, mime.c_str())) {
+ mIsPCMAudio = true;
+ }
+}
+
+void Converter::releaseEncoder() {
+ if (mEncoder == NULL) {
+ return;
+ }
+
+ mEncoder->release();
+ mEncoder.clear();
+
+ mInputBufferQueue.clear();
+ mEncoderInputBuffers.clear();
+ mEncoderOutputBuffers.clear();
+}
+
+Converter::~Converter() {
+ CHECK(mEncoder == NULL);
+}
+
+void Converter::shutdownAsync() {
+ ALOGV("shutdown");
+ (new AMessage(kWhatShutdown, this))->post();
+}
+
+status_t Converter::init() {
+ status_t err = initEncoder();
+
+ if (err != OK) {
+ releaseEncoder();
+ }
+
+ return err;
+}
+
+sp<IGraphicBufferProducer> Converter::getGraphicBufferProducer() {
+ CHECK(mFlags & FLAG_USE_SURFACE_INPUT);
+ return mGraphicBufferProducer;
+}
+
+size_t Converter::getInputBufferCount() const {
+ return mEncoderInputBuffers.size();
+}
+
+sp<AMessage> Converter::getOutputFormat() const {
+ return mOutputFormat;
+}
+
+bool Converter::needToManuallyPrependSPSPPS() const {
+ return mNeedToManuallyPrependSPSPPS;
+}
+
+// static
+int32_t Converter::GetInt32Property(
+ const char *propName, int32_t defaultValue) {
+ char val[PROPERTY_VALUE_MAX];
+ if (property_get(propName, val, NULL)) {
+ char *end;
+ unsigned long x = strtoul(val, &end, 10);
+
+ if (*end == '\0' && end > val && x > 0) {
+ return x;
+ }
+ }
+
+ return defaultValue;
+}
+
+status_t Converter::initEncoder() {
+ AString outputMIME;
+ CHECK(mOutputFormat->findString("mime", &outputMIME));
+
+ bool isAudio = !strncasecmp(outputMIME.c_str(), "audio/", 6);
+
+ if (!mIsPCMAudio) {
+ mEncoder = MediaCodec::CreateByType(
+ mCodecLooper, outputMIME.c_str(), true /* encoder */);
+
+ if (mEncoder == NULL) {
+ return ERROR_UNSUPPORTED;
+ }
+ }
+
+ if (mIsPCMAudio) {
+ return OK;
+ }
+
+ int32_t audioBitrate = GetInt32Property("media.wfd.audio-bitrate", 128000);
+ int32_t videoBitrate = GetInt32Property("media.wfd.video-bitrate", 5000000);
+ mPrevVideoBitrate = videoBitrate;
+
+ ALOGI("using audio bitrate of %d bps, video bitrate of %d bps",
+ audioBitrate, videoBitrate);
+
+ if (isAudio) {
+ mOutputFormat->setInt32("bitrate", audioBitrate);
+ } else {
+ mOutputFormat->setInt32("bitrate", videoBitrate);
+ mOutputFormat->setInt32("bitrate-mode", OMX_Video_ControlRateConstant);
+ mOutputFormat->setInt32("frame-rate", 30);
+ mOutputFormat->setInt32("i-frame-interval", 15); // Iframes every 15 secs
+
+ // Configure encoder to use intra macroblock refresh mode
+ mOutputFormat->setInt32("intra-refresh-mode", OMX_VIDEO_IntraRefreshCyclic);
+
+ int width, height, mbs;
+ if (!mOutputFormat->findInt32("width", &width)
+ || !mOutputFormat->findInt32("height", &height)) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ // Update macroblocks in a cyclic fashion with 10% of all MBs within
+ // frame gets updated at one time. It takes about 10 frames to
+ // completely update a whole video frame. If the frame rate is 30,
+ // it takes about 333 ms in the best case (if next frame is not an IDR)
+ // to recover from a lost/corrupted packet.
+ mbs = (((width + 15) / 16) * ((height + 15) / 16) * 10) / 100;
+ mOutputFormat->setInt32("intra-refresh-CIR-mbs", mbs);
+ }
+
+ ALOGV("output format is '%s'", mOutputFormat->debugString(0).c_str());
+
+ mNeedToManuallyPrependSPSPPS = false;
+
+ status_t err = NO_INIT;
+
+ if (!isAudio) {
+ sp<AMessage> tmp = mOutputFormat->dup();
+ tmp->setInt32("prepend-sps-pps-to-idr-frames", 1);
+
+ err = mEncoder->configure(
+ tmp,
+ NULL /* nativeWindow */,
+ NULL /* crypto */,
+ MediaCodec::CONFIGURE_FLAG_ENCODE);
+
+ if (err == OK) {
+ // Encoder supported prepending SPS/PPS, we don't need to emulate
+ // it.
+ mOutputFormat = tmp;
+ } else {
+ mNeedToManuallyPrependSPSPPS = true;
+
+ ALOGI("We going to manually prepend SPS and PPS to IDR frames.");
+ }
+ }
+
+ if (err != OK) {
+ // We'll get here for audio or if we failed to configure the encoder
+ // to automatically prepend SPS/PPS in the case of video.
+
+ err = mEncoder->configure(
+ mOutputFormat,
+ NULL /* nativeWindow */,
+ NULL /* crypto */,
+ MediaCodec::CONFIGURE_FLAG_ENCODE);
+ }
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (mFlags & FLAG_USE_SURFACE_INPUT) {
+ CHECK(mIsVideo);
+
+ err = mEncoder->createInputSurface(&mGraphicBufferProducer);
+
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ err = mEncoder->start();
+
+ if (err != OK) {
+ return err;
+ }
+
+ err = mEncoder->getInputBuffers(&mEncoderInputBuffers);
+
+ if (err != OK) {
+ return err;
+ }
+
+ err = mEncoder->getOutputBuffers(&mEncoderOutputBuffers);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (mFlags & FLAG_USE_SURFACE_INPUT) {
+ scheduleDoMoreWork();
+ }
+
+ return OK;
+}
+
+void Converter::notifyError(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+// static
+bool Converter::IsSilence(const sp<ABuffer> &accessUnit) {
+ const uint8_t *ptr = accessUnit->data();
+ const uint8_t *end = ptr + accessUnit->size();
+ while (ptr < end) {
+ if (*ptr != 0) {
+ return false;
+ }
+ ++ptr;
+ }
+
+ return true;
+}
+
+void Converter::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatMediaPullerNotify:
+ {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ if (!mIsPCMAudio && mEncoder == NULL) {
+ ALOGV("got msg '%s' after encoder shutdown.",
+ msg->debugString().c_str());
+
+ if (what == MediaPuller::kWhatAccessUnit) {
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ accessUnit->meta()->setObject("mediaBufferHolder", sp<MediaBufferHolder>(nullptr));
+ }
+ break;
+ }
+
+ if (what == MediaPuller::kWhatEOS) {
+ mInputBufferQueue.push_back(NULL);
+
+ feedEncoderInputBuffers();
+
+ scheduleDoMoreWork();
+ } else {
+ CHECK_EQ(what, MediaPuller::kWhatAccessUnit);
+
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ if (mNumFramesToDrop > 0 || mEncodingSuspended) {
+ if (mNumFramesToDrop > 0) {
+ --mNumFramesToDrop;
+ ALOGI("dropping frame.");
+ }
+
+ accessUnit->meta()->setObject("mediaBufferHolder", sp<MediaBufferHolder>(nullptr));
+ break;
+ }
+
+#if 0
+ MediaBuffer *mbuf =
+ (MediaBuffer *)(accessUnit->getMediaBufferBase());
+ if (mbuf != NULL) {
+ ALOGI("queueing mbuf %p", mbuf);
+ mbuf->release();
+ }
+#endif
+
+#if ENABLE_SILENCE_DETECTION
+ if (!mIsVideo) {
+ if (IsSilence(accessUnit)) {
+ if (mInSilentMode) {
+ break;
+ }
+
+ int64_t nowUs = ALooper::GetNowUs();
+
+ if (mFirstSilentFrameUs < 0ll) {
+ mFirstSilentFrameUs = nowUs;
+ } else if (nowUs >= mFirstSilentFrameUs + 10000000ll) {
+ mInSilentMode = true;
+ ALOGI("audio in silent mode now.");
+ break;
+ }
+ } else {
+ if (mInSilentMode) {
+ ALOGI("audio no longer in silent mode.");
+ }
+ mInSilentMode = false;
+ mFirstSilentFrameUs = -1ll;
+ }
+ }
+#endif
+
+ mInputBufferQueue.push_back(accessUnit);
+
+ feedEncoderInputBuffers();
+
+ scheduleDoMoreWork();
+ }
+ break;
+ }
+
+ case kWhatEncoderActivity:
+ {
+#if 0
+ int64_t whenUs;
+ if (msg->findInt64("whenUs", &whenUs)) {
+ int64_t nowUs = ALooper::GetNowUs();
+ ALOGI("[%s] kWhatEncoderActivity after %lld us",
+ mIsVideo ? "video" : "audio", nowUs - whenUs);
+ }
+#endif
+
+ mDoMoreWorkPending = false;
+
+ if (mEncoder == NULL) {
+ break;
+ }
+
+ status_t err = doMoreWork();
+
+ if (err != OK) {
+ notifyError(err);
+ } else {
+ scheduleDoMoreWork();
+ }
+ break;
+ }
+
+ case kWhatRequestIDRFrame:
+ {
+ if (mEncoder == NULL) {
+ break;
+ }
+
+ if (mIsVideo) {
+ ALOGV("requesting IDR frame");
+ mEncoder->requestIDRFrame();
+ }
+ break;
+ }
+
+ case kWhatShutdown:
+ {
+ ALOGI("shutting down %s encoder", mIsVideo ? "video" : "audio");
+
+ releaseEncoder();
+
+ AString mime;
+ CHECK(mOutputFormat->findString("mime", &mime));
+ ALOGI("encoder (%s) shut down.", mime.c_str());
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatShutdownCompleted);
+ notify->post();
+ break;
+ }
+
+ case kWhatDropAFrame:
+ {
+ ++mNumFramesToDrop;
+ break;
+ }
+
+ case kWhatReleaseOutputBuffer:
+ {
+ if (mEncoder != NULL) {
+ size_t bufferIndex;
+ CHECK(msg->findInt32("bufferIndex", (int32_t*)&bufferIndex));
+ CHECK(bufferIndex < mEncoderOutputBuffers.size());
+ mEncoder->releaseOutputBuffer(bufferIndex);
+ }
+ break;
+ }
+
+ case kWhatSuspendEncoding:
+ {
+ int32_t suspend;
+ CHECK(msg->findInt32("suspend", &suspend));
+
+ mEncodingSuspended = suspend;
+
+ if (mFlags & FLAG_USE_SURFACE_INPUT) {
+ sp<AMessage> params = new AMessage;
+ params->setInt32("drop-input-frames",suspend);
+ mEncoder->setParameters(params);
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void Converter::scheduleDoMoreWork() {
+ if (mIsPCMAudio) {
+ // There's no encoder involved in this case.
+ return;
+ }
+
+ if (mDoMoreWorkPending) {
+ return;
+ }
+
+ mDoMoreWorkPending = true;
+
+#if 1
+ if (mEncoderActivityNotify == NULL) {
+ mEncoderActivityNotify = new AMessage(kWhatEncoderActivity, this);
+ }
+ mEncoder->requestActivityNotification(mEncoderActivityNotify->dup());
+#else
+ sp<AMessage> notify = new AMessage(kWhatEncoderActivity, this);
+ notify->setInt64("whenUs", ALooper::GetNowUs());
+ mEncoder->requestActivityNotification(notify);
+#endif
+}
+
+status_t Converter::feedRawAudioInputBuffers() {
+ // Split incoming PCM audio into buffers of 6 AUs of 80 audio frames each
+ // and add a 4 byte header according to the wifi display specs.
+
+ while (!mInputBufferQueue.empty()) {
+ sp<ABuffer> buffer = *mInputBufferQueue.begin();
+ mInputBufferQueue.erase(mInputBufferQueue.begin());
+
+ int16_t *ptr = (int16_t *)buffer->data();
+ int16_t *stop = (int16_t *)(buffer->data() + buffer->size());
+ while (ptr < stop) {
+ *ptr = htons(*ptr);
+ ++ptr;
+ }
+
+ static const size_t kFrameSize = 2 * sizeof(int16_t); // stereo
+ static const size_t kFramesPerAU = 80;
+ static const size_t kNumAUsPerPESPacket = 6;
+
+ if (mPartialAudioAU != NULL) {
+ size_t bytesMissingForFullAU =
+ kNumAUsPerPESPacket * kFramesPerAU * kFrameSize
+ - mPartialAudioAU->size() + 4;
+
+ size_t copy = buffer->size();
+ if(copy > bytesMissingForFullAU) {
+ copy = bytesMissingForFullAU;
+ }
+
+ memcpy(mPartialAudioAU->data() + mPartialAudioAU->size(),
+ buffer->data(),
+ copy);
+
+ mPartialAudioAU->setRange(0, mPartialAudioAU->size() + copy);
+
+ buffer->setRange(buffer->offset() + copy, buffer->size() - copy);
+
+ int64_t timeUs;
+ CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
+
+ int64_t copyUs = (int64_t)((copy / kFrameSize) * 1E6 / 48000.0);
+ timeUs += copyUs;
+ buffer->meta()->setInt64("timeUs", timeUs);
+
+ if (bytesMissingForFullAU == copy) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatAccessUnit);
+ notify->setBuffer("accessUnit", mPartialAudioAU);
+ notify->post();
+
+ mPartialAudioAU.clear();
+ }
+ }
+
+ while (buffer->size() > 0) {
+ sp<ABuffer> partialAudioAU =
+ new ABuffer(
+ 4
+ + kNumAUsPerPESPacket * kFrameSize * kFramesPerAU);
+
+ uint8_t *ptr = partialAudioAU->data();
+ ptr[0] = 0xa0; // 10100000b
+ ptr[1] = kNumAUsPerPESPacket;
+ ptr[2] = 0; // reserved, audio _emphasis_flag = 0
+
+ static const unsigned kQuantizationWordLength = 0; // 16-bit
+ static const unsigned kAudioSamplingFrequency = 2; // 48Khz
+ static const unsigned kNumberOfAudioChannels = 1; // stereo
+
+ ptr[3] = (kQuantizationWordLength << 6)
+ | (kAudioSamplingFrequency << 3)
+ | kNumberOfAudioChannels;
+
+ size_t copy = buffer->size();
+ if (copy > partialAudioAU->size() - 4) {
+ copy = partialAudioAU->size() - 4;
+ }
+
+ memcpy(&ptr[4], buffer->data(), copy);
+
+ partialAudioAU->setRange(0, 4 + copy);
+ buffer->setRange(buffer->offset() + copy, buffer->size() - copy);
+
+ int64_t timeUs;
+ CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
+
+ partialAudioAU->meta()->setInt64("timeUs", timeUs);
+
+ int64_t copyUs = (int64_t)((copy / kFrameSize) * 1E6 / 48000.0);
+ timeUs += copyUs;
+ buffer->meta()->setInt64("timeUs", timeUs);
+
+ if (copy == partialAudioAU->capacity() - 4) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatAccessUnit);
+ notify->setBuffer("accessUnit", partialAudioAU);
+ notify->post();
+
+ partialAudioAU.clear();
+ continue;
+ }
+
+ mPartialAudioAU = partialAudioAU;
+ }
+ }
+
+ return OK;
+}
+
+status_t Converter::feedEncoderInputBuffers() {
+ if (mIsPCMAudio) {
+ return feedRawAudioInputBuffers();
+ }
+
+ while (!mInputBufferQueue.empty()
+ && !mAvailEncoderInputIndices.empty()) {
+ sp<ABuffer> buffer = *mInputBufferQueue.begin();
+ mInputBufferQueue.erase(mInputBufferQueue.begin());
+
+ size_t bufferIndex = *mAvailEncoderInputIndices.begin();
+ mAvailEncoderInputIndices.erase(mAvailEncoderInputIndices.begin());
+
+ int64_t timeUs = 0ll;
+ uint32_t flags = 0;
+
+ if (buffer != NULL) {
+ CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
+
+ memcpy(mEncoderInputBuffers.itemAt(bufferIndex)->data(),
+ buffer->data(),
+ buffer->size());
+
+ MediaBufferBase *mediaBuffer = NULL;
+ sp<RefBase> holder;
+
+ if (buffer->meta()->findObject("mediaBufferHolder", &holder)) {
+ mediaBuffer = (holder != nullptr) ?
+ static_cast<MediaBufferHolder*>(holder.get())->mediaBuffer() : nullptr;
+ }
+ if (mediaBuffer != NULL) {
+ mEncoderInputBuffers.itemAt(bufferIndex)->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mediaBuffer));
+
+ buffer->meta()->setObject("mediaBufferHolder", sp<MediaBufferHolder>(nullptr));
+ }
+ } else {
+ flags = MediaCodec::BUFFER_FLAG_EOS;
+ }
+
+ status_t err = mEncoder->queueInputBuffer(
+ bufferIndex, 0, (buffer == NULL) ? 0 : buffer->size(),
+ timeUs, flags);
+
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ return OK;
+}
+
+sp<ABuffer> Converter::prependCSD(const sp<ABuffer> &accessUnit) const {
+ CHECK(mCSD0 != NULL);
+
+ sp<ABuffer> dup = new ABuffer(accessUnit->size() + mCSD0->size());
+ memcpy(dup->data(), mCSD0->data(), mCSD0->size());
+ memcpy(dup->data() + mCSD0->size(), accessUnit->data(), accessUnit->size());
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ dup->meta()->setInt64("timeUs", timeUs);
+
+ return dup;
+}
+
+status_t Converter::doMoreWork() {
+ status_t err;
+
+ if (!(mFlags & FLAG_USE_SURFACE_INPUT)) {
+ for (;;) {
+ size_t bufferIndex;
+ err = mEncoder->dequeueInputBuffer(&bufferIndex);
+
+ if (err != OK) {
+ break;
+ }
+
+ mAvailEncoderInputIndices.push_back(bufferIndex);
+ }
+
+ feedEncoderInputBuffers();
+ }
+
+ for (;;) {
+ size_t bufferIndex;
+ size_t offset;
+ size_t size;
+ int64_t timeUs;
+ uint32_t flags;
+ native_handle_t* handle = NULL;
+ err = mEncoder->dequeueOutputBuffer(
+ &bufferIndex, &offset, &size, &timeUs, &flags);
+
+ if (err != OK) {
+ if (err == INFO_FORMAT_CHANGED) {
+ continue;
+ } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) {
+ mEncoder->getOutputBuffers(&mEncoderOutputBuffers);
+ continue;
+ }
+
+ if (err == -EAGAIN) {
+ err = OK;
+ }
+ break;
+ }
+
+ if (flags & MediaCodec::BUFFER_FLAG_EOS) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatEOS);
+ notify->post();
+ } else {
+#if 0
+ if (mIsVideo) {
+ int32_t videoBitrate = GetInt32Property(
+ "media.wfd.video-bitrate", 5000000);
+
+ setVideoBitrate(videoBitrate);
+ }
+#endif
+
+ sp<ABuffer> buffer;
+ sp<MediaCodecBuffer> outbuf = mEncoderOutputBuffers.itemAt(bufferIndex);
+
+ if (outbuf->meta()->findPointer("handle", (void**)&handle) &&
+ handle != NULL) {
+ int32_t rangeLength, rangeOffset;
+ CHECK(outbuf->meta()->findInt32("rangeOffset", &rangeOffset));
+ CHECK(outbuf->meta()->findInt32("rangeLength", &rangeLength));
+ outbuf->meta()->setPointer("handle", NULL);
+
+ // MediaSender will post the following message when HDCP
+ // is done, to release the output buffer back to encoder.
+ sp<AMessage> notify(new AMessage(kWhatReleaseOutputBuffer, this));
+ notify->setInt32("bufferIndex", bufferIndex);
+
+ buffer = new ABuffer(
+ rangeLength > (int32_t)size ? rangeLength : size);
+ buffer->meta()->setPointer("handle", handle);
+ buffer->meta()->setInt32("rangeOffset", rangeOffset);
+ buffer->meta()->setInt32("rangeLength", rangeLength);
+ buffer->meta()->setMessage("notify", notify);
+ } else {
+ buffer = new ABuffer(size);
+ }
+
+ buffer->meta()->setInt64("timeUs", timeUs);
+
+ ALOGV("[%s] time %lld us (%.2f secs)",
+ mIsVideo ? "video" : "audio", (long long)timeUs, timeUs / 1E6);
+
+ memcpy(buffer->data(), outbuf->base() + offset, size);
+
+ if (flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) {
+ if (!handle) {
+ if (mIsH264) {
+ mCSD0 = buffer;
+ }
+ mOutputFormat->setBuffer("csd-0", buffer);
+ }
+ } else {
+ if (mNeedToManuallyPrependSPSPPS
+ && mIsH264
+ && (mFlags & FLAG_PREPEND_CSD_IF_NECESSARY)
+ && IsIDR(buffer->data(), buffer->size())) {
+ buffer = prependCSD(buffer);
+ }
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatAccessUnit);
+ notify->setBuffer("accessUnit", buffer);
+ notify->post();
+ }
+ }
+
+ if (!handle) {
+ mEncoder->releaseOutputBuffer(bufferIndex);
+ }
+
+ if (flags & MediaCodec::BUFFER_FLAG_EOS) {
+ break;
+ }
+ }
+
+ return err;
+}
+
+void Converter::requestIDRFrame() {
+ (new AMessage(kWhatRequestIDRFrame, this))->post();
+}
+
+void Converter::dropAFrame() {
+ // Unsupported in surface input mode.
+ CHECK(!(mFlags & FLAG_USE_SURFACE_INPUT));
+
+ (new AMessage(kWhatDropAFrame, this))->post();
+}
+
+void Converter::suspendEncoding(bool suspend) {
+ sp<AMessage> msg = new AMessage(kWhatSuspendEncoding, this);
+ msg->setInt32("suspend", suspend);
+ msg->post();
+}
+
+int32_t Converter::getVideoBitrate() const {
+ return mPrevVideoBitrate;
+}
+
+void Converter::setVideoBitrate(int32_t bitRate) {
+ if (mIsVideo && mEncoder != NULL && bitRate != mPrevVideoBitrate) {
+ sp<AMessage> params = new AMessage;
+ params->setInt32("video-bitrate", bitRate);
+
+ mEncoder->setParameters(params);
+
+ mPrevVideoBitrate = bitRate;
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/wifi-display/source/Converter.h b/media/libstagefright/wifi-display/source/Converter.h
new file mode 100644
index 0000000..ad95ab5
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/Converter.h
@@ -0,0 +1,157 @@
+/*
+ * 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 CONVERTER_H_
+
+#define CONVERTER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct ABuffer;
+class IGraphicBufferProducer;
+struct MediaCodec;
+class MediaCodecBuffer;
+
+#define ENABLE_SILENCE_DETECTION 0
+
+// Utility class that receives media access units and converts them into
+// media access unit of a different format.
+// Right now this'll convert raw video into H.264 and raw audio into AAC.
+struct Converter : public AHandler {
+ enum {
+ kWhatAccessUnit,
+ kWhatEOS,
+ kWhatError,
+ kWhatShutdownCompleted,
+ };
+
+ enum FlagBits {
+ FLAG_USE_SURFACE_INPUT = 1,
+ FLAG_PREPEND_CSD_IF_NECESSARY = 2,
+ };
+ Converter(const sp<AMessage> ¬ify,
+ const sp<ALooper> &codecLooper,
+ const sp<AMessage> &outputFormat,
+ uint32_t flags = 0);
+
+ status_t init();
+
+ sp<IGraphicBufferProducer> getGraphicBufferProducer();
+
+ size_t getInputBufferCount() const;
+
+ sp<AMessage> getOutputFormat() const;
+ bool needToManuallyPrependSPSPPS() const;
+
+ void feedAccessUnit(const sp<ABuffer> &accessUnit);
+ void signalEOS();
+
+ void requestIDRFrame();
+
+ void dropAFrame();
+ void suspendEncoding(bool suspend);
+
+ void shutdownAsync();
+
+ int32_t getVideoBitrate() const;
+ void setVideoBitrate(int32_t bitrate);
+
+ static int32_t GetInt32Property(const char *propName, int32_t defaultValue);
+
+ enum {
+ // MUST not conflict with private enums below.
+ kWhatMediaPullerNotify = 'pulN',
+ };
+
+protected:
+ virtual ~Converter();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatDoMoreWork,
+ kWhatRequestIDRFrame,
+ kWhatSuspendEncoding,
+ kWhatShutdown,
+ kWhatEncoderActivity,
+ kWhatDropAFrame,
+ kWhatReleaseOutputBuffer,
+ };
+
+ sp<AMessage> mNotify;
+ sp<ALooper> mCodecLooper;
+ sp<AMessage> mOutputFormat;
+ uint32_t mFlags;
+ bool mIsVideo;
+ bool mIsH264;
+ bool mIsPCMAudio;
+ bool mNeedToManuallyPrependSPSPPS;
+
+ sp<MediaCodec> mEncoder;
+ sp<AMessage> mEncoderActivityNotify;
+
+ sp<IGraphicBufferProducer> mGraphicBufferProducer;
+
+ Vector<sp<MediaCodecBuffer> > mEncoderInputBuffers;
+ Vector<sp<MediaCodecBuffer> > mEncoderOutputBuffers;
+
+ List<size_t> mAvailEncoderInputIndices;
+
+ List<sp<ABuffer> > mInputBufferQueue;
+
+ sp<ABuffer> mCSD0;
+
+ bool mDoMoreWorkPending;
+
+#if ENABLE_SILENCE_DETECTION
+ int64_t mFirstSilentFrameUs;
+ bool mInSilentMode;
+#endif
+
+ sp<ABuffer> mPartialAudioAU;
+
+ int32_t mPrevVideoBitrate;
+
+ int32_t mNumFramesToDrop;
+ bool mEncodingSuspended;
+
+ status_t initEncoder();
+ void releaseEncoder();
+
+ status_t feedEncoderInputBuffers();
+
+ void scheduleDoMoreWork();
+ status_t doMoreWork();
+
+ void notifyError(status_t err);
+
+ // Packetizes raw PCM audio data available in mInputBufferQueue
+ // into a format suitable for transport stream inclusion and
+ // notifies the observer.
+ status_t feedRawAudioInputBuffers();
+
+ static bool IsSilence(const sp<ABuffer> &accessUnit);
+
+ sp<ABuffer> prependCSD(const sp<ABuffer> &accessUnit) const;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Converter);
+};
+
+} // namespace android
+
+#endif // CONVERTER_H_
diff --git a/media/libstagefright/wifi-display/source/MediaPuller.cpp b/media/libstagefright/wifi-display/source/MediaPuller.cpp
new file mode 100644
index 0000000..5bf893d
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/MediaPuller.cpp
@@ -0,0 +1,227 @@
+/*
+ * 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 "MediaPuller"
+#include <utils/Log.h>
+
+#include "MediaPuller.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaBufferBase.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/MediaBufferHolder.h>
+
+namespace android {
+
+MediaPuller::MediaPuller(
+ const sp<MediaSource> &source, const sp<AMessage> ¬ify)
+ : mSource(source),
+ mNotify(notify),
+ mPullGeneration(0),
+ mIsAudio(false),
+ mPaused(false) {
+ sp<MetaData> meta = source->getFormat();
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+ mIsAudio = !strncasecmp(mime, "audio/", 6);
+}
+
+MediaPuller::~MediaPuller() {
+}
+
+status_t MediaPuller::postSynchronouslyAndReturnError(
+ const sp<AMessage> &msg) {
+ sp<AMessage> response;
+ status_t err = msg->postAndAwaitResponse(&response);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (!response->findInt32("err", &err)) {
+ err = OK;
+ }
+
+ return err;
+}
+
+status_t MediaPuller::start() {
+ return postSynchronouslyAndReturnError(new AMessage(kWhatStart, this));
+}
+
+void MediaPuller::stopAsync(const sp<AMessage> ¬ify) {
+ sp<AMessage> msg = new AMessage(kWhatStop, this);
+ msg->setMessage("notify", notify);
+ msg->post();
+}
+
+void MediaPuller::pause() {
+ (new AMessage(kWhatPause, this))->post();
+}
+
+void MediaPuller::resume() {
+ (new AMessage(kWhatResume, this))->post();
+}
+
+void MediaPuller::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatStart:
+ {
+ status_t err;
+ if (mIsAudio) {
+ // This atrocity causes AudioSource to deliver absolute
+ // systemTime() based timestamps (off by 1 us).
+ sp<MetaData> params = new MetaData;
+ params->setInt64(kKeyTime, 1ll);
+ err = mSource->start(params.get());
+ } else {
+ err = mSource->start();
+ if (err != OK) {
+ ALOGE("source failed to start w/ err %d", err);
+ }
+ }
+
+ if (err == OK) {
+ schedulePull();
+ }
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+ response->postReply(replyID);
+ break;
+ }
+
+ case kWhatStop:
+ {
+ sp<MetaData> meta = mSource->getFormat();
+ const char *tmp;
+ CHECK(meta->findCString(kKeyMIMEType, &tmp));
+ AString mime = tmp;
+
+ ALOGI("MediaPuller(%s) stopping.", mime.c_str());
+ mSource->stop();
+ ALOGI("MediaPuller(%s) stopped.", mime.c_str());
+ ++mPullGeneration;
+
+ sp<AMessage> notify;
+ CHECK(msg->findMessage("notify", ¬ify));
+ notify->post();
+ break;
+ }
+
+ case kWhatPull:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mPullGeneration) {
+ break;
+ }
+
+ MediaBufferBase *mbuf;
+ status_t err = mSource->read(&mbuf);
+
+ if (mPaused) {
+ if (err == OK) {
+ mbuf->release();
+ mbuf = NULL;
+ }
+
+ schedulePull();
+ break;
+ }
+
+ if (err != OK) {
+ if (err == ERROR_END_OF_STREAM) {
+ ALOGI("stream ended.");
+ } else {
+ ALOGE("error %d reading stream.", err);
+ }
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatEOS);
+ notify->post();
+ } else {
+ int64_t timeUs;
+ CHECK(mbuf->meta_data().findInt64(kKeyTime, &timeUs));
+
+ sp<ABuffer> accessUnit = new ABuffer(mbuf->range_length());
+
+ memcpy(accessUnit->data(),
+ (const uint8_t *)mbuf->data() + mbuf->range_offset(),
+ mbuf->range_length());
+
+ accessUnit->meta()->setInt64("timeUs", timeUs);
+
+ if (mIsAudio) {
+ mbuf->release();
+ mbuf = NULL;
+ } else {
+ // video encoder will release MediaBufferBase when done
+ // with underlying data.
+ accessUnit->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mbuf));
+ mbuf->release();
+ mbuf = NULL;
+ }
+
+ sp<AMessage> notify = mNotify->dup();
+
+ notify->setInt32("what", kWhatAccessUnit);
+ notify->setBuffer("accessUnit", accessUnit);
+ notify->post();
+
+ if (mbuf != NULL) {
+ ALOGV("posted mbuf %p", mbuf);
+ }
+
+ schedulePull();
+ }
+ break;
+ }
+
+ case kWhatPause:
+ {
+ mPaused = true;
+ break;
+ }
+
+ case kWhatResume:
+ {
+ mPaused = false;
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void MediaPuller::schedulePull() {
+ sp<AMessage> msg = new AMessage(kWhatPull, this);
+ msg->setInt32("generation", mPullGeneration);
+ msg->post();
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/MediaPuller.h b/media/libstagefright/wifi-display/source/MediaPuller.h
new file mode 100644
index 0000000..1291bb3
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/MediaPuller.h
@@ -0,0 +1,68 @@
+/*
+ * 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 MEDIA_PULLER_H_
+
+#define MEDIA_PULLER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct MediaSource;
+
+struct MediaPuller : public AHandler {
+ enum {
+ kWhatEOS,
+ kWhatAccessUnit
+ };
+
+ MediaPuller(const sp<MediaSource> &source, const sp<AMessage> ¬ify);
+
+ status_t start();
+ void stopAsync(const sp<AMessage> ¬ify);
+
+ void pause();
+ void resume();
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual ~MediaPuller();
+
+private:
+ enum {
+ kWhatStart,
+ kWhatStop,
+ kWhatPull,
+ kWhatPause,
+ kWhatResume,
+ };
+
+ sp<MediaSource> mSource;
+ sp<AMessage> mNotify;
+ int32_t mPullGeneration;
+ bool mIsAudio;
+ bool mPaused;
+
+ status_t postSynchronouslyAndReturnError(const sp<AMessage> &msg);
+ void schedulePull();
+
+ DISALLOW_EVIL_CONSTRUCTORS(MediaPuller);
+};
+
+} // namespace android
+
+#endif // MEDIA_PULLER_H_
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
new file mode 100644
index 0000000..0240d2a
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
@@ -0,0 +1,1116 @@
+/*
+ * 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 "PlaybackSession"
+#include <utils/Log.h>
+
+#include "PlaybackSession.h"
+
+#include "Converter.h"
+#include "MediaPuller.h"
+#include "RepeaterSource.h"
+#include "WifiDisplaySource.h"
+
+#include <binder/IServiceManager.h>
+#include <cutils/properties.h>
+#include <media/IHDCP.h>
+#include <media/IMediaHTTPService.h>
+#include <media/stagefright/foundation/avc_utils.h>
+#include <media/stagefright/foundation/ABitReader.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/AudioSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/NuMediaExtractor.h>
+#include <media/stagefright/SurfaceMediaSource.h>
+#include <media/stagefright/Utils.h>
+#include <media/IOMX.h>
+
+#include <OMX_IVCommon.h>
+
+namespace android {
+
+struct WifiDisplaySource::PlaybackSession::Track : public AHandler {
+ enum {
+ kWhatStopped,
+ };
+
+ Track(const sp<AMessage> ¬ify,
+ const sp<ALooper> &pullLooper,
+ const sp<ALooper> &codecLooper,
+ const sp<MediaPuller> &mediaPuller,
+ const sp<Converter> &converter);
+
+ Track(const sp<AMessage> ¬ify, const sp<AMessage> &format);
+
+ void setRepeaterSource(const sp<RepeaterSource> &source);
+
+ sp<AMessage> getFormat();
+ bool isAudio() const;
+
+ const sp<Converter> &converter() const;
+ const sp<RepeaterSource> &repeaterSource() const;
+
+ ssize_t mediaSenderTrackIndex() const;
+ void setMediaSenderTrackIndex(size_t index);
+
+ status_t start();
+ void stopAsync();
+
+ void pause();
+ void resume();
+
+ void queueAccessUnit(const sp<ABuffer> &accessUnit);
+ sp<ABuffer> dequeueAccessUnit();
+
+ bool hasOutputBuffer(int64_t *timeUs) const;
+ void queueOutputBuffer(const sp<ABuffer> &accessUnit);
+ sp<ABuffer> dequeueOutputBuffer();
+
+#if SUSPEND_VIDEO_IF_IDLE
+ bool isSuspended() const;
+#endif
+
+ size_t countQueuedOutputBuffers() const {
+ return mQueuedOutputBuffers.size();
+ }
+
+ void requestIDRFrame();
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual ~Track();
+
+private:
+ enum {
+ kWhatMediaPullerStopped,
+ };
+
+ sp<AMessage> mNotify;
+ sp<ALooper> mPullLooper;
+ sp<ALooper> mCodecLooper;
+ sp<MediaPuller> mMediaPuller;
+ sp<Converter> mConverter;
+ sp<AMessage> mFormat;
+ bool mStarted;
+ ssize_t mMediaSenderTrackIndex;
+ bool mIsAudio;
+ List<sp<ABuffer> > mQueuedAccessUnits;
+ sp<RepeaterSource> mRepeaterSource;
+ List<sp<ABuffer> > mQueuedOutputBuffers;
+ int64_t mLastOutputBufferQueuedTimeUs;
+
+ static bool IsAudioFormat(const sp<AMessage> &format);
+
+ DISALLOW_EVIL_CONSTRUCTORS(Track);
+};
+
+WifiDisplaySource::PlaybackSession::Track::Track(
+ const sp<AMessage> ¬ify,
+ const sp<ALooper> &pullLooper,
+ const sp<ALooper> &codecLooper,
+ const sp<MediaPuller> &mediaPuller,
+ const sp<Converter> &converter)
+ : mNotify(notify),
+ mPullLooper(pullLooper),
+ mCodecLooper(codecLooper),
+ mMediaPuller(mediaPuller),
+ mConverter(converter),
+ mStarted(false),
+ mIsAudio(IsAudioFormat(mConverter->getOutputFormat())),
+ mLastOutputBufferQueuedTimeUs(-1ll) {
+}
+
+WifiDisplaySource::PlaybackSession::Track::Track(
+ const sp<AMessage> ¬ify, const sp<AMessage> &format)
+ : mNotify(notify),
+ mFormat(format),
+ mStarted(false),
+ mIsAudio(IsAudioFormat(format)),
+ mLastOutputBufferQueuedTimeUs(-1ll) {
+}
+
+WifiDisplaySource::PlaybackSession::Track::~Track() {
+ CHECK(!mStarted);
+}
+
+// static
+bool WifiDisplaySource::PlaybackSession::Track::IsAudioFormat(
+ const sp<AMessage> &format) {
+ AString mime;
+ CHECK(format->findString("mime", &mime));
+
+ return !strncasecmp(mime.c_str(), "audio/", 6);
+}
+
+sp<AMessage> WifiDisplaySource::PlaybackSession::Track::getFormat() {
+ return mFormat != NULL ? mFormat : mConverter->getOutputFormat();
+}
+
+bool WifiDisplaySource::PlaybackSession::Track::isAudio() const {
+ return mIsAudio;
+}
+
+const sp<Converter> &WifiDisplaySource::PlaybackSession::Track::converter() const {
+ return mConverter;
+}
+
+const sp<RepeaterSource> &
+WifiDisplaySource::PlaybackSession::Track::repeaterSource() const {
+ return mRepeaterSource;
+}
+
+ssize_t WifiDisplaySource::PlaybackSession::Track::mediaSenderTrackIndex() const {
+ CHECK_GE(mMediaSenderTrackIndex, 0);
+ return mMediaSenderTrackIndex;
+}
+
+void WifiDisplaySource::PlaybackSession::Track::setMediaSenderTrackIndex(
+ size_t index) {
+ mMediaSenderTrackIndex = index;
+}
+
+status_t WifiDisplaySource::PlaybackSession::Track::start() {
+ ALOGV("Track::start isAudio=%d", mIsAudio);
+
+ CHECK(!mStarted);
+
+ status_t err = OK;
+
+ if (mMediaPuller != NULL) {
+ err = mMediaPuller->start();
+ }
+
+ if (err == OK) {
+ mStarted = true;
+ }
+
+ return err;
+}
+
+void WifiDisplaySource::PlaybackSession::Track::stopAsync() {
+ ALOGV("Track::stopAsync isAudio=%d", mIsAudio);
+
+ if (mConverter != NULL) {
+ mConverter->shutdownAsync();
+ }
+
+ sp<AMessage> msg = new AMessage(kWhatMediaPullerStopped, this);
+
+ if (mStarted && mMediaPuller != NULL) {
+ if (mRepeaterSource != NULL) {
+ // Let's unblock MediaPuller's MediaSource::read().
+ mRepeaterSource->wakeUp();
+ }
+
+ mMediaPuller->stopAsync(msg);
+ } else {
+ mStarted = false;
+ msg->post();
+ }
+}
+
+void WifiDisplaySource::PlaybackSession::Track::pause() {
+ mMediaPuller->pause();
+}
+
+void WifiDisplaySource::PlaybackSession::Track::resume() {
+ mMediaPuller->resume();
+}
+
+void WifiDisplaySource::PlaybackSession::Track::onMessageReceived(
+ const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatMediaPullerStopped:
+ {
+ mConverter.clear();
+
+ mStarted = false;
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatStopped);
+ notify->post();
+
+ ALOGI("kWhatStopped %s posted", mIsAudio ? "audio" : "video");
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void WifiDisplaySource::PlaybackSession::Track::queueAccessUnit(
+ const sp<ABuffer> &accessUnit) {
+ mQueuedAccessUnits.push_back(accessUnit);
+}
+
+sp<ABuffer> WifiDisplaySource::PlaybackSession::Track::dequeueAccessUnit() {
+ if (mQueuedAccessUnits.empty()) {
+ return NULL;
+ }
+
+ sp<ABuffer> accessUnit = *mQueuedAccessUnits.begin();
+ CHECK(accessUnit != NULL);
+
+ mQueuedAccessUnits.erase(mQueuedAccessUnits.begin());
+
+ return accessUnit;
+}
+
+void WifiDisplaySource::PlaybackSession::Track::setRepeaterSource(
+ const sp<RepeaterSource> &source) {
+ mRepeaterSource = source;
+}
+
+void WifiDisplaySource::PlaybackSession::Track::requestIDRFrame() {
+ if (mIsAudio) {
+ return;
+ }
+
+ if (mRepeaterSource != NULL) {
+ mRepeaterSource->wakeUp();
+ }
+
+ mConverter->requestIDRFrame();
+}
+
+bool WifiDisplaySource::PlaybackSession::Track::hasOutputBuffer(
+ int64_t *timeUs) const {
+ *timeUs = 0ll;
+
+ if (mQueuedOutputBuffers.empty()) {
+ return false;
+ }
+
+ const sp<ABuffer> &outputBuffer = *mQueuedOutputBuffers.begin();
+
+ CHECK(outputBuffer->meta()->findInt64("timeUs", timeUs));
+
+ return true;
+}
+
+void WifiDisplaySource::PlaybackSession::Track::queueOutputBuffer(
+ const sp<ABuffer> &accessUnit) {
+ mQueuedOutputBuffers.push_back(accessUnit);
+ mLastOutputBufferQueuedTimeUs = ALooper::GetNowUs();
+}
+
+sp<ABuffer> WifiDisplaySource::PlaybackSession::Track::dequeueOutputBuffer() {
+ CHECK(!mQueuedOutputBuffers.empty());
+
+ sp<ABuffer> outputBuffer = *mQueuedOutputBuffers.begin();
+ mQueuedOutputBuffers.erase(mQueuedOutputBuffers.begin());
+
+ return outputBuffer;
+}
+
+#if SUSPEND_VIDEO_IF_IDLE
+bool WifiDisplaySource::PlaybackSession::Track::isSuspended() const {
+ if (!mQueuedOutputBuffers.empty()) {
+ return false;
+ }
+
+ if (mLastOutputBufferQueuedTimeUs < 0ll) {
+ // We've never seen an output buffer queued, but tracks start
+ // out live, not suspended.
+ return false;
+ }
+
+ // If we've not seen new output data for 60ms or more, we consider
+ // this track suspended for the time being.
+ return (ALooper::GetNowUs() - mLastOutputBufferQueuedTimeUs) > 60000ll;
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+
+WifiDisplaySource::PlaybackSession::PlaybackSession(
+ const String16 &opPackageName,
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> ¬ify,
+ const in_addr &interfaceAddr,
+ const sp<IHDCP> &hdcp,
+ const char *path)
+ : mOpPackageName(opPackageName),
+ mNetSession(netSession),
+ mNotify(notify),
+ mInterfaceAddr(interfaceAddr),
+ mHDCP(hdcp),
+ mLocalRTPPort(-1),
+ mWeAreDead(false),
+ mPaused(false),
+ mLastLifesignUs(),
+ mVideoTrackIndex(-1),
+ mPrevTimeUs(-1ll),
+ mPullExtractorPending(false),
+ mPullExtractorGeneration(0),
+ mFirstSampleTimeRealUs(-1ll),
+ mFirstSampleTimeUs(-1ll) {
+ if (path != NULL) {
+ mMediaPath.setTo(path);
+ }
+}
+
+status_t WifiDisplaySource::PlaybackSession::init(
+ const char *clientIP,
+ int32_t clientRtp,
+ RTPSender::TransportMode rtpMode,
+ int32_t clientRtcp,
+ RTPSender::TransportMode rtcpMode,
+ bool enableAudio,
+ bool usePCMAudio,
+ bool enableVideo,
+ VideoFormats::ResolutionType videoResolutionType,
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType) {
+ sp<AMessage> notify = new AMessage(kWhatMediaSenderNotify, this);
+ mMediaSender = new MediaSender(mNetSession, notify);
+ looper()->registerHandler(mMediaSender);
+
+ mMediaSender->setHDCP(mHDCP);
+
+ status_t err = setupPacketizer(
+ enableAudio,
+ usePCMAudio,
+ enableVideo,
+ videoResolutionType,
+ videoResolutionIndex,
+ videoProfileType,
+ videoLevelType);
+
+ if (err == OK) {
+ err = mMediaSender->initAsync(
+ -1 /* trackIndex */,
+ clientIP,
+ clientRtp,
+ rtpMode,
+ clientRtcp,
+ rtcpMode,
+ &mLocalRTPPort);
+ }
+
+ if (err != OK) {
+ mLocalRTPPort = -1;
+
+ looper()->unregisterHandler(mMediaSender->id());
+ mMediaSender.clear();
+
+ return err;
+ }
+
+ updateLiveness();
+
+ return OK;
+}
+
+WifiDisplaySource::PlaybackSession::~PlaybackSession() {
+}
+
+int32_t WifiDisplaySource::PlaybackSession::getRTPPort() const {
+ return mLocalRTPPort;
+}
+
+int64_t WifiDisplaySource::PlaybackSession::getLastLifesignUs() const {
+ return mLastLifesignUs;
+}
+
+void WifiDisplaySource::PlaybackSession::updateLiveness() {
+ mLastLifesignUs = ALooper::GetNowUs();
+}
+
+status_t WifiDisplaySource::PlaybackSession::play() {
+ updateLiveness();
+
+ (new AMessage(kWhatResume, this))->post();
+
+ return OK;
+}
+
+status_t WifiDisplaySource::PlaybackSession::onMediaSenderInitialized() {
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ CHECK_EQ((status_t)OK, mTracks.editValueAt(i)->start());
+ }
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatSessionEstablished);
+ notify->post();
+
+ return OK;
+}
+
+status_t WifiDisplaySource::PlaybackSession::pause() {
+ updateLiveness();
+
+ (new AMessage(kWhatPause, this))->post();
+
+ return OK;
+}
+
+void WifiDisplaySource::PlaybackSession::destroyAsync() {
+ ALOGI("destroyAsync");
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ mTracks.valueAt(i)->stopAsync();
+ }
+}
+
+void WifiDisplaySource::PlaybackSession::onMessageReceived(
+ const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatConverterNotify:
+ {
+ if (mWeAreDead) {
+ ALOGV("dropping msg '%s' because we're dead",
+ msg->debugString().c_str());
+
+ break;
+ }
+
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ if (what == Converter::kWhatAccessUnit) {
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ const sp<Track> &track = mTracks.valueFor(trackIndex);
+
+ status_t err = mMediaSender->queueAccessUnit(
+ track->mediaSenderTrackIndex(),
+ accessUnit);
+
+ if (err != OK) {
+ notifySessionDead();
+ }
+ break;
+ } else if (what == Converter::kWhatEOS) {
+ CHECK_EQ(what, Converter::kWhatEOS);
+
+ ALOGI("output EOS on track %zu", trackIndex);
+
+ ssize_t index = mTracks.indexOfKey(trackIndex);
+ CHECK_GE(index, 0);
+
+ const sp<Converter> &converter =
+ mTracks.valueAt(index)->converter();
+ looper()->unregisterHandler(converter->id());
+
+ mTracks.removeItemsAt(index);
+
+ if (mTracks.isEmpty()) {
+ ALOGI("Reached EOS");
+ }
+ } else if (what != Converter::kWhatShutdownCompleted) {
+ CHECK_EQ(what, Converter::kWhatError);
+
+ status_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGE("converter signaled error %d", err);
+
+ notifySessionDead();
+ }
+ break;
+ }
+
+ case kWhatMediaSenderNotify:
+ {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ if (what == MediaSender::kWhatInitDone) {
+ status_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ if (err == OK) {
+ onMediaSenderInitialized();
+ } else {
+ notifySessionDead();
+ }
+ } else if (what == MediaSender::kWhatError) {
+ notifySessionDead();
+ } else if (what == MediaSender::kWhatNetworkStall) {
+ size_t numBytesQueued;
+ CHECK(msg->findSize("numBytesQueued", &numBytesQueued));
+
+ if (mVideoTrackIndex >= 0) {
+ const sp<Track> &videoTrack =
+ mTracks.valueFor(mVideoTrackIndex);
+
+ sp<Converter> converter = videoTrack->converter();
+ if (converter != NULL) {
+ converter->dropAFrame();
+ }
+ }
+ } else if (what == MediaSender::kWhatInformSender) {
+ onSinkFeedback(msg);
+ } else {
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatTrackNotify:
+ {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ if (what == Track::kWhatStopped) {
+ ALOGI("Track %zu stopped", trackIndex);
+
+ sp<Track> track = mTracks.valueFor(trackIndex);
+ looper()->unregisterHandler(track->id());
+ mTracks.removeItem(trackIndex);
+ track.clear();
+
+ if (!mTracks.isEmpty()) {
+ ALOGI("not all tracks are stopped yet");
+ break;
+ }
+
+ looper()->unregisterHandler(mMediaSender->id());
+ mMediaSender.clear();
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatSessionDestroyed);
+ notify->post();
+ }
+ break;
+ }
+
+ case kWhatPause:
+ {
+ if (mExtractor != NULL) {
+ ++mPullExtractorGeneration;
+ mFirstSampleTimeRealUs = -1ll;
+ mFirstSampleTimeUs = -1ll;
+ }
+
+ if (mPaused) {
+ break;
+ }
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ mTracks.editValueAt(i)->pause();
+ }
+
+ mPaused = true;
+ break;
+ }
+
+ case kWhatResume:
+ {
+ if (mExtractor != NULL) {
+ schedulePullExtractor();
+ }
+
+ if (!mPaused) {
+ break;
+ }
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ mTracks.editValueAt(i)->resume();
+ }
+
+ mPaused = false;
+ break;
+ }
+
+ case kWhatPullExtractorSample:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mPullExtractorGeneration) {
+ break;
+ }
+
+ mPullExtractorPending = false;
+
+ onPullExtractor();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void WifiDisplaySource::PlaybackSession::onSinkFeedback(const sp<AMessage> &msg) {
+ int64_t avgLatencyUs;
+ CHECK(msg->findInt64("avgLatencyUs", &avgLatencyUs));
+
+ int64_t maxLatencyUs;
+ CHECK(msg->findInt64("maxLatencyUs", &maxLatencyUs));
+
+ ALOGI("sink reports avg. latency of %lld ms (max %lld ms)",
+ avgLatencyUs / 1000ll,
+ maxLatencyUs / 1000ll);
+
+ if (mVideoTrackIndex >= 0) {
+ const sp<Track> &videoTrack = mTracks.valueFor(mVideoTrackIndex);
+ sp<Converter> converter = videoTrack->converter();
+
+ if (converter != NULL) {
+ int32_t videoBitrate =
+ Converter::GetInt32Property("media.wfd.video-bitrate", -1);
+
+ char val[PROPERTY_VALUE_MAX];
+ if (videoBitrate < 0
+ && property_get("media.wfd.video-bitrate", val, NULL)
+ && !strcasecmp("adaptive", val)) {
+ videoBitrate = converter->getVideoBitrate();
+
+ if (avgLatencyUs > 300000ll) {
+ videoBitrate *= 0.6;
+ } else if (avgLatencyUs < 100000ll) {
+ videoBitrate *= 1.1;
+ }
+ }
+
+ if (videoBitrate > 0) {
+ if (videoBitrate < 500000) {
+ videoBitrate = 500000;
+ } else if (videoBitrate > 10000000) {
+ videoBitrate = 10000000;
+ }
+
+ if (videoBitrate != converter->getVideoBitrate()) {
+ ALOGI("setting video bitrate to %d bps", videoBitrate);
+
+ converter->setVideoBitrate(videoBitrate);
+ }
+ }
+ }
+
+ sp<RepeaterSource> repeaterSource = videoTrack->repeaterSource();
+ if (repeaterSource != NULL) {
+ double rateHz =
+ Converter::GetInt32Property(
+ "media.wfd.video-framerate", -1);
+
+ char val[PROPERTY_VALUE_MAX];
+ if (rateHz < 0.0
+ && property_get("media.wfd.video-framerate", val, NULL)
+ && !strcasecmp("adaptive", val)) {
+ rateHz = repeaterSource->getFrameRate();
+
+ if (avgLatencyUs > 300000ll) {
+ rateHz *= 0.9;
+ } else if (avgLatencyUs < 200000ll) {
+ rateHz *= 1.1;
+ }
+ }
+
+ if (rateHz > 0) {
+ if (rateHz < 5.0) {
+ rateHz = 5.0;
+ } else if (rateHz > 30.0) {
+ rateHz = 30.0;
+ }
+
+ if (rateHz != repeaterSource->getFrameRate()) {
+ ALOGI("setting frame rate to %.2f Hz", rateHz);
+
+ repeaterSource->setFrameRate(rateHz);
+ }
+ }
+ }
+ }
+}
+
+status_t WifiDisplaySource::PlaybackSession::setupMediaPacketizer(
+ bool enableAudio, bool enableVideo) {
+ mExtractor = new NuMediaExtractor(NuMediaExtractor::EntryPoint::OTHER);
+
+ status_t err = mExtractor->setDataSource(
+ NULL /* httpService */, mMediaPath.c_str());
+
+ if (err != OK) {
+ return err;
+ }
+
+ size_t n = mExtractor->countTracks();
+ bool haveAudio = false;
+ bool haveVideo = false;
+ for (size_t i = 0; i < n; ++i) {
+ sp<AMessage> format;
+ err = mExtractor->getTrackFormat(i, &format);
+
+ if (err != OK) {
+ continue;
+ }
+
+ AString mime;
+ CHECK(format->findString("mime", &mime));
+
+ bool isAudio = !strncasecmp(mime.c_str(), "audio/", 6);
+ bool isVideo = !strncasecmp(mime.c_str(), "video/", 6);
+
+ if (isAudio && enableAudio && !haveAudio) {
+ haveAudio = true;
+ } else if (isVideo && enableVideo && !haveVideo) {
+ haveVideo = true;
+ } else {
+ continue;
+ }
+
+ err = mExtractor->selectTrack(i);
+
+ size_t trackIndex = mTracks.size();
+
+ sp<AMessage> notify = new AMessage(kWhatTrackNotify, this);
+ notify->setSize("trackIndex", trackIndex);
+
+ sp<Track> track = new Track(notify, format);
+ looper()->registerHandler(track);
+
+ mTracks.add(trackIndex, track);
+
+ mExtractorTrackToInternalTrack.add(i, trackIndex);
+
+ if (isVideo) {
+ mVideoTrackIndex = trackIndex;
+ }
+
+ uint32_t flags = MediaSender::FLAG_MANUALLY_PREPEND_SPS_PPS;
+
+ ssize_t mediaSenderTrackIndex =
+ mMediaSender->addTrack(format, flags);
+ CHECK_GE(mediaSenderTrackIndex, 0);
+
+ track->setMediaSenderTrackIndex(mediaSenderTrackIndex);
+
+ if ((haveAudio || !enableAudio) && (haveVideo || !enableVideo)) {
+ break;
+ }
+ }
+
+ return OK;
+}
+
+void WifiDisplaySource::PlaybackSession::schedulePullExtractor() {
+ if (mPullExtractorPending) {
+ return;
+ }
+
+ int64_t delayUs = 1000000; // default delay is 1 sec
+ int64_t sampleTimeUs;
+ status_t err = mExtractor->getSampleTime(&sampleTimeUs);
+
+ if (err == OK) {
+ int64_t nowUs = ALooper::GetNowUs();
+
+ if (mFirstSampleTimeRealUs < 0ll) {
+ mFirstSampleTimeRealUs = nowUs;
+ mFirstSampleTimeUs = sampleTimeUs;
+ }
+
+ int64_t whenUs = sampleTimeUs - mFirstSampleTimeUs + mFirstSampleTimeRealUs;
+ delayUs = whenUs - nowUs;
+ } else {
+ ALOGW("could not get sample time (%d)", err);
+ }
+
+ sp<AMessage> msg = new AMessage(kWhatPullExtractorSample, this);
+ msg->setInt32("generation", mPullExtractorGeneration);
+ msg->post(delayUs);
+
+ mPullExtractorPending = true;
+}
+
+void WifiDisplaySource::PlaybackSession::onPullExtractor() {
+ sp<ABuffer> accessUnit = new ABuffer(1024 * 1024);
+ status_t err = mExtractor->readSampleData(accessUnit);
+ if (err != OK) {
+ // EOS.
+ return;
+ }
+
+ int64_t timeUs;
+ CHECK_EQ((status_t)OK, mExtractor->getSampleTime(&timeUs));
+
+ accessUnit->meta()->setInt64(
+ "timeUs", mFirstSampleTimeRealUs + timeUs - mFirstSampleTimeUs);
+
+ size_t trackIndex;
+ CHECK_EQ((status_t)OK, mExtractor->getSampleTrackIndex(&trackIndex));
+
+ sp<AMessage> msg = new AMessage(kWhatConverterNotify, this);
+
+ msg->setSize(
+ "trackIndex", mExtractorTrackToInternalTrack.valueFor(trackIndex));
+
+ msg->setInt32("what", Converter::kWhatAccessUnit);
+ msg->setBuffer("accessUnit", accessUnit);
+ msg->post();
+
+ mExtractor->advance();
+
+ schedulePullExtractor();
+}
+
+status_t WifiDisplaySource::PlaybackSession::setupPacketizer(
+ bool enableAudio,
+ bool usePCMAudio,
+ bool enableVideo,
+ VideoFormats::ResolutionType videoResolutionType,
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType) {
+ CHECK(enableAudio || enableVideo);
+
+ if (!mMediaPath.empty()) {
+ return setupMediaPacketizer(enableAudio, enableVideo);
+ }
+
+ if (enableVideo) {
+ status_t err = addVideoSource(
+ videoResolutionType, videoResolutionIndex, videoProfileType,
+ videoLevelType);
+
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ if (!enableAudio) {
+ return OK;
+ }
+
+ return addAudioSource(usePCMAudio);
+}
+
+status_t WifiDisplaySource::PlaybackSession::addSource(
+ bool isVideo, const sp<MediaSource> &source, bool isRepeaterSource,
+ bool usePCMAudio, unsigned profileIdc, unsigned levelIdc,
+ unsigned constraintSet, size_t *numInputBuffers) {
+ CHECK(!usePCMAudio || !isVideo);
+ CHECK(!isRepeaterSource || isVideo);
+ CHECK(!profileIdc || isVideo);
+ CHECK(!levelIdc || isVideo);
+ CHECK(!constraintSet || isVideo);
+
+ sp<ALooper> pullLooper = new ALooper;
+ pullLooper->setName("pull_looper");
+
+ pullLooper->start(
+ false /* runOnCallingThread */,
+ false /* canCallJava */,
+ PRIORITY_AUDIO);
+
+ sp<ALooper> codecLooper = new ALooper;
+ codecLooper->setName("codec_looper");
+
+ codecLooper->start(
+ false /* runOnCallingThread */,
+ false /* canCallJava */,
+ PRIORITY_AUDIO);
+
+ size_t trackIndex;
+
+ sp<AMessage> notify;
+
+ trackIndex = mTracks.size();
+
+ sp<AMessage> format;
+ status_t err = convertMetaDataToMessage(source->getFormat(), &format);
+ CHECK_EQ(err, (status_t)OK);
+
+ if (isVideo) {
+ format->setString("mime", MEDIA_MIMETYPE_VIDEO_AVC);
+ format->setInt32(
+ "android._input-metadata-buffer-type", kMetadataBufferTypeANWBuffer);
+ format->setInt32("android._store-metadata-in-buffers-output", (mHDCP != NULL)
+ && (mHDCP->getCaps() & HDCPModule::HDCP_CAPS_ENCRYPT_NATIVE));
+ format->setInt32(
+ "color-format", OMX_COLOR_FormatAndroidOpaque);
+ format->setInt32("profile-idc", profileIdc);
+ format->setInt32("level-idc", levelIdc);
+ format->setInt32("constraint-set", constraintSet);
+ } else {
+ if (usePCMAudio) {
+ format->setInt32("pcm-encoding", kAudioEncodingPcm16bit);
+ format->setString("mime", MEDIA_MIMETYPE_AUDIO_RAW);
+ } else {
+ format->setString("mime", MEDIA_MIMETYPE_AUDIO_AAC);
+ }
+ }
+
+ notify = new AMessage(kWhatConverterNotify, this);
+ notify->setSize("trackIndex", trackIndex);
+
+ sp<Converter> converter = new Converter(notify, codecLooper, format);
+
+ looper()->registerHandler(converter);
+
+ err = converter->init();
+ if (err != OK) {
+ ALOGE("%s converter returned err %d", isVideo ? "video" : "audio", err);
+
+ looper()->unregisterHandler(converter->id());
+ return err;
+ }
+
+ notify = new AMessage(Converter::kWhatMediaPullerNotify, converter);
+ notify->setSize("trackIndex", trackIndex);
+
+ sp<MediaPuller> puller = new MediaPuller(source, notify);
+ pullLooper->registerHandler(puller);
+
+ if (numInputBuffers != NULL) {
+ *numInputBuffers = converter->getInputBufferCount();
+ }
+
+ notify = new AMessage(kWhatTrackNotify, this);
+ notify->setSize("trackIndex", trackIndex);
+
+ sp<Track> track = new Track(
+ notify, pullLooper, codecLooper, puller, converter);
+
+ if (isRepeaterSource) {
+ track->setRepeaterSource(static_cast<RepeaterSource *>(source.get()));
+ }
+
+ looper()->registerHandler(track);
+
+ mTracks.add(trackIndex, track);
+
+ if (isVideo) {
+ mVideoTrackIndex = trackIndex;
+ }
+
+ uint32_t flags = 0;
+ if (converter->needToManuallyPrependSPSPPS()) {
+ flags |= MediaSender::FLAG_MANUALLY_PREPEND_SPS_PPS;
+ }
+
+ ssize_t mediaSenderTrackIndex =
+ mMediaSender->addTrack(converter->getOutputFormat(), flags);
+ CHECK_GE(mediaSenderTrackIndex, 0);
+
+ track->setMediaSenderTrackIndex(mediaSenderTrackIndex);
+
+ return OK;
+}
+
+status_t WifiDisplaySource::PlaybackSession::addVideoSource(
+ VideoFormats::ResolutionType videoResolutionType,
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType) {
+ size_t width, height, framesPerSecond;
+ bool interlaced;
+ CHECK(VideoFormats::GetConfiguration(
+ videoResolutionType,
+ videoResolutionIndex,
+ &width,
+ &height,
+ &framesPerSecond,
+ &interlaced));
+
+ unsigned profileIdc, levelIdc, constraintSet;
+ CHECK(VideoFormats::GetProfileLevel(
+ videoProfileType,
+ videoLevelType,
+ &profileIdc,
+ &levelIdc,
+ &constraintSet));
+
+ sp<SurfaceMediaSource> source = new SurfaceMediaSource(width, height);
+
+ source->setUseAbsoluteTimestamps();
+
+ sp<RepeaterSource> videoSource =
+ new RepeaterSource(source, framesPerSecond);
+
+ size_t numInputBuffers;
+ status_t err = addSource(
+ true /* isVideo */, videoSource, true /* isRepeaterSource */,
+ false /* usePCMAudio */, profileIdc, levelIdc, constraintSet,
+ &numInputBuffers);
+
+ if (err != OK) {
+ return err;
+ }
+
+ err = source->setMaxAcquiredBufferCount(numInputBuffers);
+ CHECK_EQ(err, (status_t)OK);
+
+ mProducer = source->getProducer();
+
+ return OK;
+}
+
+status_t WifiDisplaySource::PlaybackSession::addAudioSource(bool usePCMAudio) {
+ audio_attributes_t attr = AUDIO_ATTRIBUTES_INITIALIZER;
+ attr.source = AUDIO_SOURCE_REMOTE_SUBMIX;
+
+ sp<AudioSource> audioSource = new AudioSource(
+ &attr,
+ mOpPackageName,
+ 48000 /* sampleRate */,
+ 2 /* channelCount */);
+
+ if (audioSource->initCheck() == OK) {
+ return addSource(
+ false /* isVideo */, audioSource, false /* isRepeaterSource */,
+ usePCMAudio, 0 /* profileIdc */, 0 /* levelIdc */,
+ 0 /* constraintSet */, NULL /* numInputBuffers */);
+ }
+
+ ALOGW("Unable to instantiate audio source");
+
+ return OK;
+}
+
+sp<IGraphicBufferProducer> WifiDisplaySource::PlaybackSession::getSurfaceTexture() {
+ return mProducer;
+}
+
+void WifiDisplaySource::PlaybackSession::requestIDRFrame() {
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ const sp<Track> &track = mTracks.valueAt(i);
+
+ track->requestIDRFrame();
+ }
+}
+
+void WifiDisplaySource::PlaybackSession::notifySessionDead() {
+ // Inform WifiDisplaySource of our premature death (wish).
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatSessionDead);
+ notify->post();
+
+ mWeAreDead = true;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.h b/media/libstagefright/wifi-display/source/PlaybackSession.h
new file mode 100644
index 0000000..f6673df
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.h
@@ -0,0 +1,176 @@
+/*
+ * 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 PLAYBACK_SESSION_H_
+
+#define PLAYBACK_SESSION_H_
+
+#include "MediaSender.h"
+#include "VideoFormats.h"
+#include "WifiDisplaySource.h"
+
+#include <utils/String16.h>
+
+namespace android {
+
+struct ABuffer;
+struct IHDCP;
+class IGraphicBufferProducer;
+struct MediaPuller;
+struct MediaSource;
+struct MediaSender;
+struct NuMediaExtractor;
+
+// Encapsulates the state of an RTP/RTCP session in the context of wifi
+// display.
+struct WifiDisplaySource::PlaybackSession : public AHandler {
+ PlaybackSession(
+ const String16 &opPackageName,
+ const sp<ANetworkSession> &netSession,
+ const sp<AMessage> ¬ify,
+ const struct in_addr &interfaceAddr,
+ const sp<IHDCP> &hdcp,
+ const char *path = NULL);
+
+ status_t init(
+ const char *clientIP,
+ int32_t clientRtp,
+ RTPSender::TransportMode rtpMode,
+ int32_t clientRtcp,
+ RTPSender::TransportMode rtcpMode,
+ bool enableAudio,
+ bool usePCMAudio,
+ bool enableVideo,
+ VideoFormats::ResolutionType videoResolutionType,
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType);
+
+ void destroyAsync();
+
+ int32_t getRTPPort() const;
+
+ int64_t getLastLifesignUs() const;
+ void updateLiveness();
+
+ status_t play();
+ status_t finishPlay();
+ status_t pause();
+
+ sp<IGraphicBufferProducer> getSurfaceTexture();
+
+ void requestIDRFrame();
+
+ enum {
+ kWhatSessionDead,
+ kWhatBinaryData,
+ kWhatSessionEstablished,
+ kWhatSessionDestroyed,
+ };
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual ~PlaybackSession();
+
+private:
+ struct Track;
+
+ enum {
+ kWhatMediaPullerNotify,
+ kWhatConverterNotify,
+ kWhatTrackNotify,
+ kWhatUpdateSurface,
+ kWhatPause,
+ kWhatResume,
+ kWhatMediaSenderNotify,
+ kWhatPullExtractorSample,
+ };
+
+ String16 mOpPackageName;
+
+ sp<ANetworkSession> mNetSession;
+ sp<AMessage> mNotify;
+ in_addr mInterfaceAddr;
+ sp<IHDCP> mHDCP;
+ AString mMediaPath;
+
+ sp<MediaSender> mMediaSender;
+ int32_t mLocalRTPPort;
+
+ bool mWeAreDead;
+ bool mPaused;
+
+ int64_t mLastLifesignUs;
+
+ sp<IGraphicBufferProducer> mProducer;
+
+ KeyedVector<size_t, sp<Track> > mTracks;
+ ssize_t mVideoTrackIndex;
+
+ int64_t mPrevTimeUs;
+
+ sp<NuMediaExtractor> mExtractor;
+ KeyedVector<size_t, size_t> mExtractorTrackToInternalTrack;
+ bool mPullExtractorPending;
+ int32_t mPullExtractorGeneration;
+ int64_t mFirstSampleTimeRealUs;
+ int64_t mFirstSampleTimeUs;
+
+ status_t setupMediaPacketizer(bool enableAudio, bool enableVideo);
+
+ status_t setupPacketizer(
+ bool enableAudio,
+ bool usePCMAudio,
+ bool enableVideo,
+ VideoFormats::ResolutionType videoResolutionType,
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType);
+
+ status_t addSource(
+ bool isVideo,
+ const sp<MediaSource> &source,
+ bool isRepeaterSource,
+ bool usePCMAudio,
+ unsigned profileIdc,
+ unsigned levelIdc,
+ unsigned contraintSet,
+ size_t *numInputBuffers);
+
+ status_t addVideoSource(
+ VideoFormats::ResolutionType videoResolutionType,
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType);
+
+ status_t addAudioSource(bool usePCMAudio);
+
+ status_t onMediaSenderInitialized();
+
+ void notifySessionDead();
+
+ void schedulePullExtractor();
+ void onPullExtractor();
+
+ void onSinkFeedback(const sp<AMessage> &msg);
+
+ DISALLOW_EVIL_CONSTRUCTORS(PlaybackSession);
+};
+
+} // namespace android
+
+#endif // PLAYBACK_SESSION_H_
+
diff --git a/media/libstagefright/wifi-display/source/RepeaterSource.cpp b/media/libstagefright/wifi-display/source/RepeaterSource.cpp
new file mode 100644
index 0000000..e225a02
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/RepeaterSource.cpp
@@ -0,0 +1,219 @@
+//#define LOG_NDEBUG 0
+#define LOG_TAG "RepeaterSource"
+#include <utils/Log.h>
+
+#include "RepeaterSource.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaBufferBase.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+RepeaterSource::RepeaterSource(const sp<MediaSource> &source, double rateHz)
+ : mStarted(false),
+ mSource(source),
+ mRateHz(rateHz),
+ mBuffer(NULL),
+ mResult(OK),
+ mLastBufferUpdateUs(-1ll),
+ mStartTimeUs(-1ll),
+ mFrameCount(0) {
+}
+
+RepeaterSource::~RepeaterSource() {
+ CHECK(!mStarted);
+}
+
+double RepeaterSource::getFrameRate() const {
+ return mRateHz;
+}
+
+void RepeaterSource::setFrameRate(double rateHz) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (rateHz == mRateHz) {
+ return;
+ }
+
+ if (mStartTimeUs >= 0ll) {
+ int64_t nextTimeUs = mStartTimeUs + (mFrameCount * 1000000ll) / mRateHz;
+ mStartTimeUs = nextTimeUs;
+ mFrameCount = 0;
+ }
+ mRateHz = rateHz;
+}
+
+status_t RepeaterSource::start(MetaData *params) {
+ CHECK(!mStarted);
+
+ status_t err = mSource->start(params);
+
+ if (err != OK) {
+ return err;
+ }
+
+ mBuffer = NULL;
+ mResult = OK;
+ mStartTimeUs = -1ll;
+ mFrameCount = 0;
+
+ mLooper = new ALooper;
+ mLooper->setName("repeater_looper");
+ mLooper->start();
+
+ mReflector = new AHandlerReflector<RepeaterSource>(this);
+ mLooper->registerHandler(mReflector);
+
+ postRead();
+
+ mStarted = true;
+
+ return OK;
+}
+
+status_t RepeaterSource::stop() {
+ CHECK(mStarted);
+
+ ALOGV("stopping");
+
+ status_t err = mSource->stop();
+
+ if (mLooper != NULL) {
+ mLooper->stop();
+ mLooper.clear();
+
+ mReflector.clear();
+ }
+
+ if (mBuffer != NULL) {
+ ALOGV("releasing mbuf %p", mBuffer);
+ mBuffer->release();
+ mBuffer = NULL;
+ }
+
+
+ ALOGV("stopped");
+
+ mStarted = false;
+
+ return err;
+}
+
+sp<MetaData> RepeaterSource::getFormat() {
+ return mSource->getFormat();
+}
+
+status_t RepeaterSource::read(
+ MediaBufferBase **buffer, const ReadOptions *options) {
+ int64_t seekTimeUs;
+ ReadOptions::SeekMode seekMode;
+ CHECK(options == NULL || !options->getSeekTo(&seekTimeUs, &seekMode));
+
+ for (;;) {
+ int64_t bufferTimeUs = -1ll;
+
+ if (mStartTimeUs < 0ll) {
+ Mutex::Autolock autoLock(mLock);
+ while ((mLastBufferUpdateUs < 0ll || mBuffer == NULL)
+ && mResult == OK) {
+ mCondition.wait(mLock);
+ }
+
+ ALOGV("now resuming.");
+ mStartTimeUs = ALooper::GetNowUs();
+ bufferTimeUs = mStartTimeUs;
+ } else {
+ bufferTimeUs = mStartTimeUs + (mFrameCount * 1000000ll) / mRateHz;
+
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayUs = bufferTimeUs - nowUs;
+
+ if (delayUs > 0ll) {
+ usleep(delayUs);
+ }
+ }
+
+ bool stale = false;
+
+ {
+ Mutex::Autolock autoLock(mLock);
+ if (mResult != OK) {
+ CHECK(mBuffer == NULL);
+ return mResult;
+ }
+
+#if SUSPEND_VIDEO_IF_IDLE
+ int64_t nowUs = ALooper::GetNowUs();
+ if (nowUs - mLastBufferUpdateUs > 1000000ll) {
+ mLastBufferUpdateUs = -1ll;
+ stale = true;
+ } else
+#endif
+ {
+ mBuffer->add_ref();
+ *buffer = mBuffer;
+ (*buffer)->meta_data().setInt64(kKeyTime, bufferTimeUs);
+ ++mFrameCount;
+ }
+ }
+
+ if (!stale) {
+ break;
+ }
+
+ mStartTimeUs = -1ll;
+ mFrameCount = 0;
+ ALOGV("now dormant");
+ }
+
+ return OK;
+}
+
+void RepeaterSource::postRead() {
+ (new AMessage(kWhatRead, mReflector))->post();
+}
+
+void RepeaterSource::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRead:
+ {
+ MediaBufferBase *buffer;
+ status_t err = mSource->read(&buffer);
+
+ ALOGV("read mbuf %p", buffer);
+
+ Mutex::Autolock autoLock(mLock);
+ if (mBuffer != NULL) {
+ mBuffer->release();
+ mBuffer = NULL;
+ }
+ mBuffer = buffer;
+ mResult = err;
+ mLastBufferUpdateUs = ALooper::GetNowUs();
+
+ mCondition.broadcast();
+
+ if (err == OK) {
+ postRead();
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void RepeaterSource::wakeUp() {
+ ALOGV("wakeUp");
+ Mutex::Autolock autoLock(mLock);
+ if (mLastBufferUpdateUs < 0ll && mBuffer != NULL) {
+ mLastBufferUpdateUs = ALooper::GetNowUs();
+ mCondition.broadcast();
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/wifi-display/source/RepeaterSource.h b/media/libstagefright/wifi-display/source/RepeaterSource.h
new file mode 100644
index 0000000..1647fb1
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/RepeaterSource.h
@@ -0,0 +1,67 @@
+#ifndef REPEATER_SOURCE_H_
+
+#define REPEATER_SOURCE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <media/stagefright/MediaSource.h>
+
+#define SUSPEND_VIDEO_IF_IDLE 0
+
+namespace android {
+
+// This MediaSource delivers frames at a constant rate by repeating buffers
+// if necessary.
+struct RepeaterSource : public MediaSource {
+ RepeaterSource(const sp<MediaSource> &source, double rateHz);
+
+ virtual status_t start(MetaData *params);
+ virtual status_t stop();
+ virtual sp<MetaData> getFormat();
+
+ virtual status_t read(
+ MediaBufferBase **buffer, const ReadOptions *options);
+
+ void onMessageReceived(const sp<AMessage> &msg);
+
+ // If RepeaterSource is currently dormant, because SurfaceFlinger didn't
+ // send updates in a while, this is its wakeup call.
+ void wakeUp();
+
+ double getFrameRate() const;
+ void setFrameRate(double rateHz);
+
+protected:
+ virtual ~RepeaterSource();
+
+private:
+ enum {
+ kWhatRead,
+ };
+
+ Mutex mLock;
+ Condition mCondition;
+
+ bool mStarted;
+
+ sp<MediaSource> mSource;
+ double mRateHz;
+
+ sp<ALooper> mLooper;
+ sp<AHandlerReflector<RepeaterSource> > mReflector;
+
+ MediaBufferBase *mBuffer;
+ status_t mResult;
+ int64_t mLastBufferUpdateUs;
+
+ int64_t mStartTimeUs;
+ int32_t mFrameCount;
+
+ void postRead();
+
+ DISALLOW_EVIL_CONSTRUCTORS(RepeaterSource);
+};
+
+} // namespace android
+
+#endif // REPEATER_SOURCE_H_
diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.cpp b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
new file mode 100644
index 0000000..7d30ae2
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
@@ -0,0 +1,1055 @@
+/*
+ * 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 "TSPacketizer"
+#include <utils/Log.h>
+
+#include "TSPacketizer.h"
+
+#include <media/stagefright/foundation/avc_utils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+
+#include <arpa/inet.h>
+
+namespace android {
+
+struct TSPacketizer::Track : public RefBase {
+ Track(const sp<AMessage> &format,
+ unsigned PID, unsigned streamType, unsigned streamID);
+
+ unsigned PID() const;
+ unsigned streamType() const;
+ unsigned streamID() const;
+
+ // Returns the previous value.
+ unsigned incrementContinuityCounter();
+
+ bool isAudio() const;
+ bool isVideo() const;
+
+ bool isH264() const;
+ bool isAAC() const;
+ bool lacksADTSHeader() const;
+ bool isPCMAudio() const;
+
+ sp<ABuffer> prependCSD(const sp<ABuffer> &accessUnit) const;
+ sp<ABuffer> prependADTSHeader(const sp<ABuffer> &accessUnit) const;
+
+ size_t countDescriptors() const;
+ sp<ABuffer> descriptorAt(size_t index) const;
+
+ void finalize();
+ void extractCSDIfNecessary();
+
+protected:
+ virtual ~Track();
+
+private:
+ sp<AMessage> mFormat;
+
+ unsigned mPID;
+ unsigned mStreamType;
+ unsigned mStreamID;
+ unsigned mContinuityCounter;
+
+ AString mMIME;
+ Vector<sp<ABuffer> > mCSD;
+
+ Vector<sp<ABuffer> > mDescriptors;
+
+ bool mAudioLacksATDSHeaders;
+ bool mFinalized;
+ bool mExtractedCSD;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Track);
+};
+
+TSPacketizer::Track::Track(
+ const sp<AMessage> &format,
+ unsigned PID, unsigned streamType, unsigned streamID)
+ : mFormat(format),
+ mPID(PID),
+ mStreamType(streamType),
+ mStreamID(streamID),
+ mContinuityCounter(0),
+ mAudioLacksATDSHeaders(false),
+ mFinalized(false),
+ mExtractedCSD(false) {
+ CHECK(format->findString("mime", &mMIME));
+}
+
+void TSPacketizer::Track::extractCSDIfNecessary() {
+ if (mExtractedCSD) {
+ return;
+ }
+
+ if (!strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_VIDEO_AVC)
+ || !strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+ for (size_t i = 0;; ++i) {
+ sp<ABuffer> csd;
+ if (!mFormat->findBuffer(AStringPrintf("csd-%d", i).c_str(), &csd)) {
+ break;
+ }
+
+ mCSD.push(csd);
+ }
+
+ if (!strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+ int32_t isADTS;
+ if (!mFormat->findInt32("is-adts", &isADTS) || isADTS == 0) {
+ mAudioLacksATDSHeaders = true;
+ }
+ }
+ }
+
+ mExtractedCSD = true;
+}
+
+TSPacketizer::Track::~Track() {
+}
+
+unsigned TSPacketizer::Track::PID() const {
+ return mPID;
+}
+
+unsigned TSPacketizer::Track::streamType() const {
+ return mStreamType;
+}
+
+unsigned TSPacketizer::Track::streamID() const {
+ return mStreamID;
+}
+
+unsigned TSPacketizer::Track::incrementContinuityCounter() {
+ unsigned prevCounter = mContinuityCounter;
+
+ if (++mContinuityCounter == 16) {
+ mContinuityCounter = 0;
+ }
+
+ return prevCounter;
+}
+
+bool TSPacketizer::Track::isAudio() const {
+ return !strncasecmp("audio/", mMIME.c_str(), 6);
+}
+
+bool TSPacketizer::Track::isVideo() const {
+ return !strncasecmp("video/", mMIME.c_str(), 6);
+}
+
+bool TSPacketizer::Track::isH264() const {
+ return !strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_VIDEO_AVC);
+}
+
+bool TSPacketizer::Track::isAAC() const {
+ return !strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_AUDIO_AAC);
+}
+
+bool TSPacketizer::Track::isPCMAudio() const {
+ return !strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_AUDIO_RAW);
+}
+
+bool TSPacketizer::Track::lacksADTSHeader() const {
+ return mAudioLacksATDSHeaders;
+}
+
+sp<ABuffer> TSPacketizer::Track::prependCSD(
+ const sp<ABuffer> &accessUnit) const {
+ size_t size = 0;
+ for (size_t i = 0; i < mCSD.size(); ++i) {
+ size += mCSD.itemAt(i)->size();
+ }
+
+ sp<ABuffer> dup = new ABuffer(accessUnit->size() + size);
+ size_t offset = 0;
+ for (size_t i = 0; i < mCSD.size(); ++i) {
+ const sp<ABuffer> &csd = mCSD.itemAt(i);
+
+ memcpy(dup->data() + offset, csd->data(), csd->size());
+ offset += csd->size();
+ }
+
+ memcpy(dup->data() + offset, accessUnit->data(), accessUnit->size());
+
+ return dup;
+}
+
+sp<ABuffer> TSPacketizer::Track::prependADTSHeader(
+ const sp<ABuffer> &accessUnit) const {
+ CHECK_EQ(mCSD.size(), 1u);
+
+ const uint8_t *codec_specific_data = mCSD.itemAt(0)->data();
+
+ const uint32_t aac_frame_length = accessUnit->size() + 7;
+
+ sp<ABuffer> dup = new ABuffer(aac_frame_length);
+
+ unsigned profile = (codec_specific_data[0] >> 3) - 1;
+
+ unsigned sampling_freq_index =
+ ((codec_specific_data[0] & 7) << 1)
+ | (codec_specific_data[1] >> 7);
+
+ unsigned channel_configuration =
+ (codec_specific_data[1] >> 3) & 0x0f;
+
+ uint8_t *ptr = dup->data();
+
+ *ptr++ = 0xff;
+ *ptr++ = 0xf9; // b11111001, ID=1(MPEG-2), layer=0, protection_absent=1
+
+ *ptr++ =
+ profile << 6
+ | sampling_freq_index << 2
+ | ((channel_configuration >> 2) & 1); // private_bit=0
+
+ // original_copy=0, home=0, copyright_id_bit=0, copyright_id_start=0
+ *ptr++ =
+ (channel_configuration & 3) << 6
+ | aac_frame_length >> 11;
+ *ptr++ = (aac_frame_length >> 3) & 0xff;
+ *ptr++ = (aac_frame_length & 7) << 5;
+
+ // adts_buffer_fullness=0, number_of_raw_data_blocks_in_frame=0
+ *ptr++ = 0;
+
+ memcpy(ptr, accessUnit->data(), accessUnit->size());
+
+ return dup;
+}
+
+size_t TSPacketizer::Track::countDescriptors() const {
+ return mDescriptors.size();
+}
+
+sp<ABuffer> TSPacketizer::Track::descriptorAt(size_t index) const {
+ CHECK_LT(index, mDescriptors.size());
+ return mDescriptors.itemAt(index);
+}
+
+void TSPacketizer::Track::finalize() {
+ if (mFinalized) {
+ return;
+ }
+
+ if (isH264()) {
+ {
+ // AVC video descriptor (40)
+
+ sp<ABuffer> descriptor = new ABuffer(6);
+ uint8_t *data = descriptor->data();
+ data[0] = 40; // descriptor_tag
+ data[1] = 4; // descriptor_length
+
+ if (mCSD.size() > 0) {
+ CHECK_GE(mCSD.size(), 1u);
+ const sp<ABuffer> &sps = mCSD.itemAt(0);
+ CHECK(!memcmp("\x00\x00\x00\x01", sps->data(), 4));
+ CHECK_GE(sps->size(), 7u);
+ // profile_idc, constraint_set*, level_idc
+ memcpy(&data[2], sps->data() + 4, 3);
+ } else {
+ int32_t profileIdc, levelIdc, constraintSet;
+ CHECK(mFormat->findInt32("profile-idc", &profileIdc));
+ CHECK(mFormat->findInt32("level-idc", &levelIdc));
+ CHECK(mFormat->findInt32("constraint-set", &constraintSet));
+ CHECK_GE(profileIdc, 0);
+ CHECK_GE(levelIdc, 0);
+ data[2] = profileIdc; // profile_idc
+ data[3] = constraintSet; // constraint_set*
+ data[4] = levelIdc; // level_idc
+ }
+
+ // AVC_still_present=0, AVC_24_hour_picture_flag=0, reserved
+ data[5] = 0x3f;
+
+ mDescriptors.push_back(descriptor);
+ }
+
+ {
+ // AVC timing and HRD descriptor (42)
+
+ sp<ABuffer> descriptor = new ABuffer(4);
+ uint8_t *data = descriptor->data();
+ data[0] = 42; // descriptor_tag
+ data[1] = 2; // descriptor_length
+
+ // hrd_management_valid_flag = 0
+ // reserved = 111111b
+ // picture_and_timing_info_present = 0
+
+ data[2] = 0x7e;
+
+ // fixed_frame_rate_flag = 0
+ // temporal_poc_flag = 0
+ // picture_to_display_conversion_flag = 0
+ // reserved = 11111b
+ data[3] = 0x1f;
+
+ mDescriptors.push_back(descriptor);
+ }
+ } else if (isPCMAudio()) {
+ // LPCM audio stream descriptor (0x83)
+
+ int32_t channelCount;
+ CHECK(mFormat->findInt32("channel-count", &channelCount));
+ CHECK_EQ(channelCount, 2);
+
+ int32_t sampleRate;
+ CHECK(mFormat->findInt32("sample-rate", &sampleRate));
+ CHECK(sampleRate == 44100 || sampleRate == 48000);
+
+ sp<ABuffer> descriptor = new ABuffer(4);
+ uint8_t *data = descriptor->data();
+ data[0] = 0x83; // descriptor_tag
+ data[1] = 2; // descriptor_length
+
+ unsigned sampling_frequency = (sampleRate == 44100) ? 1 : 2;
+
+ data[2] = (sampling_frequency << 5)
+ | (3 /* reserved */ << 1)
+ | 0 /* emphasis_flag */;
+
+ data[3] =
+ (1 /* number_of_channels = stereo */ << 5)
+ | 0xf /* reserved */;
+
+ mDescriptors.push_back(descriptor);
+ }
+
+ mFinalized = true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSPacketizer::TSPacketizer(uint32_t flags)
+ : mFlags(flags),
+ mPATContinuityCounter(0),
+ mPMTContinuityCounter(0) {
+ initCrcTable();
+
+ if (flags & (EMIT_HDCP20_DESCRIPTOR | EMIT_HDCP21_DESCRIPTOR)) {
+ int32_t hdcpVersion;
+ if (flags & EMIT_HDCP20_DESCRIPTOR) {
+ CHECK(!(flags & EMIT_HDCP21_DESCRIPTOR));
+ hdcpVersion = 0x20;
+ } else {
+ CHECK(!(flags & EMIT_HDCP20_DESCRIPTOR));
+
+ // HDCP2.0 _and_ HDCP 2.1 specs say to set the version
+ // inside the HDCP descriptor to 0x20!!!
+ hdcpVersion = 0x20;
+ }
+
+ // HDCP descriptor
+ sp<ABuffer> descriptor = new ABuffer(7);
+ uint8_t *data = descriptor->data();
+ data[0] = 0x05; // descriptor_tag
+ data[1] = 5; // descriptor_length
+ data[2] = 'H';
+ data[3] = 'D';
+ data[4] = 'C';
+ data[5] = 'P';
+ data[6] = hdcpVersion;
+
+ mProgramInfoDescriptors.push_back(descriptor);
+ }
+}
+
+TSPacketizer::~TSPacketizer() {
+}
+
+ssize_t TSPacketizer::addTrack(const sp<AMessage> &format) {
+ AString mime;
+ CHECK(format->findString("mime", &mime));
+
+ unsigned PIDStart;
+ bool isVideo = !strncasecmp("video/", mime.c_str(), 6);
+ bool isAudio = !strncasecmp("audio/", mime.c_str(), 6);
+
+ if (isVideo) {
+ PIDStart = 0x1011;
+ } else if (isAudio) {
+ PIDStart = 0x1100;
+ } else {
+ return ERROR_UNSUPPORTED;
+ }
+
+ unsigned streamType;
+ unsigned streamIDStart;
+ unsigned streamIDStop;
+
+ if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_AVC)) {
+ streamType = 0x1b;
+ streamIDStart = 0xe0;
+ streamIDStop = 0xef;
+ } else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+ streamType = 0x0f;
+ streamIDStart = 0xc0;
+ streamIDStop = 0xdf;
+ } else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_RAW)) {
+ streamType = 0x83;
+ streamIDStart = 0xbd;
+ streamIDStop = 0xbd;
+ } else {
+ return ERROR_UNSUPPORTED;
+ }
+
+ size_t numTracksOfThisType = 0;
+ unsigned PID = PIDStart;
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ const sp<Track> &track = mTracks.itemAt(i);
+
+ if (track->streamType() == streamType) {
+ ++numTracksOfThisType;
+ }
+
+ if ((isAudio && track->isAudio()) || (isVideo && track->isVideo())) {
+ ++PID;
+ }
+ }
+
+ unsigned streamID = streamIDStart + numTracksOfThisType;
+ if (streamID > streamIDStop) {
+ return -ERANGE;
+ }
+
+ sp<Track> track = new Track(format, PID, streamType, streamID);
+ return mTracks.add(track);
+}
+
+status_t TSPacketizer::extractCSDIfNecessary(size_t trackIndex) {
+ if (trackIndex >= mTracks.size()) {
+ return -ERANGE;
+ }
+
+ const sp<Track> &track = mTracks.itemAt(trackIndex);
+ track->extractCSDIfNecessary();
+
+ return OK;
+}
+
+status_t TSPacketizer::packetize(
+ size_t trackIndex,
+ const sp<ABuffer> &_accessUnit,
+ sp<ABuffer> *packets,
+ uint32_t flags,
+ const uint8_t *PES_private_data, size_t PES_private_data_len,
+ size_t numStuffingBytes) {
+ sp<ABuffer> accessUnit = _accessUnit;
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ packets->clear();
+
+ if (trackIndex >= mTracks.size()) {
+ return -ERANGE;
+ }
+
+ const sp<Track> &track = mTracks.itemAt(trackIndex);
+
+ if (track->isH264() && (flags & PREPEND_SPS_PPS_TO_IDR_FRAMES)
+ && IsIDR(accessUnit->data(), accessUnit->size())) {
+ // prepend codec specific data, i.e. SPS and PPS.
+ accessUnit = track->prependCSD(accessUnit);
+ } else if (track->isAAC() && track->lacksADTSHeader()) {
+ CHECK(!(flags & IS_ENCRYPTED));
+ accessUnit = track->prependADTSHeader(accessUnit);
+ }
+
+ // 0x47
+ // transport_error_indicator = b0
+ // payload_unit_start_indicator = b1
+ // transport_priority = b0
+ // PID
+ // transport_scrambling_control = b00
+ // adaptation_field_control = b??
+ // continuity_counter = b????
+ // -- payload follows
+ // packet_startcode_prefix = 0x000001
+ // stream_id
+ // PES_packet_length = 0x????
+ // reserved = b10
+ // PES_scrambling_control = b00
+ // PES_priority = b0
+ // data_alignment_indicator = b1
+ // copyright = b0
+ // original_or_copy = b0
+ // PTS_DTS_flags = b10 (PTS only)
+ // ESCR_flag = b0
+ // ES_rate_flag = b0
+ // DSM_trick_mode_flag = b0
+ // additional_copy_info_flag = b0
+ // PES_CRC_flag = b0
+ // PES_extension_flag = b0
+ // PES_header_data_length = 0x05
+ // reserved = b0010 (PTS)
+ // PTS[32..30] = b???
+ // reserved = b1
+ // PTS[29..15] = b??? ???? ???? ???? (15 bits)
+ // reserved = b1
+ // PTS[14..0] = b??? ???? ???? ???? (15 bits)
+ // reserved = b1
+ // the first fragment of "buffer" follows
+
+ // Each transport packet (except for the last one contributing to the PES
+ // payload) must contain a multiple of 16 bytes of payload per HDCP spec.
+ bool alignPayload =
+ (mFlags & (EMIT_HDCP20_DESCRIPTOR | EMIT_HDCP21_DESCRIPTOR));
+
+ /*
+ a) The very first PES transport stream packet contains
+
+ 4 bytes of TS header
+ ... padding
+ 14 bytes of static PES header
+ PES_private_data_len + 1 bytes (only if PES_private_data_len > 0)
+ numStuffingBytes bytes
+
+ followed by the payload
+
+ b) Subsequent PES transport stream packets contain
+
+ 4 bytes of TS header
+ ... padding
+
+ followed by the payload
+ */
+
+ size_t PES_packet_length = accessUnit->size() + 8 + numStuffingBytes;
+ if (PES_private_data_len > 0) {
+ PES_packet_length += PES_private_data_len + 1;
+ }
+
+ size_t numTSPackets = 1;
+
+ {
+ // Make sure the PES header fits into a single TS packet:
+ size_t PES_header_size = 14 + numStuffingBytes;
+ if (PES_private_data_len > 0) {
+ PES_header_size += PES_private_data_len + 1;
+ }
+
+ CHECK_LE(PES_header_size, 188u - 4u);
+
+ size_t sizeAvailableForPayload = 188 - 4 - PES_header_size;
+ size_t numBytesOfPayload = accessUnit->size();
+
+ if (numBytesOfPayload > sizeAvailableForPayload) {
+ numBytesOfPayload = sizeAvailableForPayload;
+
+ if (alignPayload && numBytesOfPayload > 16) {
+ numBytesOfPayload -= (numBytesOfPayload % 16);
+ }
+ }
+
+ size_t numPaddingBytes = sizeAvailableForPayload - numBytesOfPayload;
+ ALOGV("packet 1 contains %zd padding bytes and %zd bytes of payload",
+ numPaddingBytes, numBytesOfPayload);
+
+ size_t numBytesOfPayloadRemaining = accessUnit->size() - numBytesOfPayload;
+
+#if 0
+ // The following hopefully illustrates the logic that led to the
+ // more efficient computation in the #else block...
+
+ while (numBytesOfPayloadRemaining > 0) {
+ size_t sizeAvailableForPayload = 188 - 4;
+
+ size_t numBytesOfPayload = numBytesOfPayloadRemaining;
+
+ if (numBytesOfPayload > sizeAvailableForPayload) {
+ numBytesOfPayload = sizeAvailableForPayload;
+
+ if (alignPayload && numBytesOfPayload > 16) {
+ numBytesOfPayload -= (numBytesOfPayload % 16);
+ }
+ }
+
+ size_t numPaddingBytes = sizeAvailableForPayload - numBytesOfPayload;
+ ALOGI("packet %zd contains %zd padding bytes and %zd bytes of payload",
+ numTSPackets + 1, numPaddingBytes, numBytesOfPayload);
+
+ numBytesOfPayloadRemaining -= numBytesOfPayload;
+ ++numTSPackets;
+ }
+#else
+ // This is how many bytes of payload each subsequent TS packet
+ // can contain at most.
+ sizeAvailableForPayload = 188 - 4;
+ size_t sizeAvailableForAlignedPayload = sizeAvailableForPayload;
+ if (alignPayload) {
+ // We're only going to use a subset of the available space
+ // since we need to make each fragment a multiple of 16 in size.
+ sizeAvailableForAlignedPayload -=
+ (sizeAvailableForAlignedPayload % 16);
+ }
+
+ size_t numFullTSPackets =
+ numBytesOfPayloadRemaining / sizeAvailableForAlignedPayload;
+
+ numTSPackets += numFullTSPackets;
+
+ numBytesOfPayloadRemaining -=
+ numFullTSPackets * sizeAvailableForAlignedPayload;
+
+ // numBytesOfPayloadRemaining < sizeAvailableForAlignedPayload
+ if (numFullTSPackets == 0 && numBytesOfPayloadRemaining > 0) {
+ // There wasn't enough payload left to form a full aligned payload,
+ // the last packet doesn't have to be aligned.
+ ++numTSPackets;
+ } else if (numFullTSPackets > 0
+ && numBytesOfPayloadRemaining
+ + sizeAvailableForAlignedPayload > sizeAvailableForPayload) {
+ // The last packet emitted had a full aligned payload and together
+ // with the bytes remaining does exceed the unaligned payload
+ // size, so we need another packet.
+ ++numTSPackets;
+ }
+#endif
+ }
+
+ if (flags & EMIT_PAT_AND_PMT) {
+ numTSPackets += 2;
+ }
+
+ if (flags & EMIT_PCR) {
+ ++numTSPackets;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(numTSPackets * 188);
+ uint8_t *packetDataStart = buffer->data();
+
+ if (flags & EMIT_PAT_AND_PMT) {
+ // Program Association Table (PAT):
+ // 0x47
+ // transport_error_indicator = b0
+ // payload_unit_start_indicator = b1
+ // transport_priority = b0
+ // PID = b0000000000000 (13 bits)
+ // transport_scrambling_control = b00
+ // adaptation_field_control = b01 (no adaptation field, payload only)
+ // continuity_counter = b????
+ // skip = 0x00
+ // --- payload follows
+ // table_id = 0x00
+ // section_syntax_indicator = b1
+ // must_be_zero = b0
+ // reserved = b11
+ // section_length = 0x00d
+ // transport_stream_id = 0x0000
+ // reserved = b11
+ // version_number = b00001
+ // current_next_indicator = b1
+ // section_number = 0x00
+ // last_section_number = 0x00
+ // one program follows:
+ // program_number = 0x0001
+ // reserved = b111
+ // program_map_PID = kPID_PMT (13 bits!)
+ // CRC = 0x????????
+
+ if (++mPATContinuityCounter == 16) {
+ mPATContinuityCounter = 0;
+ }
+
+ uint8_t *ptr = packetDataStart;
+ *ptr++ = 0x47;
+ *ptr++ = 0x40;
+ *ptr++ = 0x00;
+ *ptr++ = 0x10 | mPATContinuityCounter;
+ *ptr++ = 0x00;
+
+ uint8_t *crcDataStart = ptr;
+ *ptr++ = 0x00;
+ *ptr++ = 0xb0;
+ *ptr++ = 0x0d;
+ *ptr++ = 0x00;
+ *ptr++ = 0x00;
+ *ptr++ = 0xc3;
+ *ptr++ = 0x00;
+ *ptr++ = 0x00;
+ *ptr++ = 0x00;
+ *ptr++ = 0x01;
+ *ptr++ = 0xe0 | (kPID_PMT >> 8);
+ *ptr++ = kPID_PMT & 0xff;
+
+ CHECK_EQ(ptr - crcDataStart, 12);
+ uint32_t crc = htonl(crc32(crcDataStart, ptr - crcDataStart));
+ memcpy(ptr, &crc, 4);
+ ptr += 4;
+
+ size_t sizeLeft = packetDataStart + 188 - ptr;
+ memset(ptr, 0xff, sizeLeft);
+
+ packetDataStart += 188;
+
+ // Program Map (PMT):
+ // 0x47
+ // transport_error_indicator = b0
+ // payload_unit_start_indicator = b1
+ // transport_priority = b0
+ // PID = kPID_PMT (13 bits)
+ // transport_scrambling_control = b00
+ // adaptation_field_control = b01 (no adaptation field, payload only)
+ // continuity_counter = b????
+ // skip = 0x00
+ // -- payload follows
+ // table_id = 0x02
+ // section_syntax_indicator = b1
+ // must_be_zero = b0
+ // reserved = b11
+ // section_length = 0x???
+ // program_number = 0x0001
+ // reserved = b11
+ // version_number = b00001
+ // current_next_indicator = b1
+ // section_number = 0x00
+ // last_section_number = 0x00
+ // reserved = b111
+ // PCR_PID = kPCR_PID (13 bits)
+ // reserved = b1111
+ // program_info_length = 0x???
+ // program_info_descriptors follow
+ // one or more elementary stream descriptions follow:
+ // stream_type = 0x??
+ // reserved = b111
+ // elementary_PID = b? ???? ???? ???? (13 bits)
+ // reserved = b1111
+ // ES_info_length = 0x000
+ // CRC = 0x????????
+
+ if (++mPMTContinuityCounter == 16) {
+ mPMTContinuityCounter = 0;
+ }
+
+ ptr = packetDataStart;
+ *ptr++ = 0x47;
+ *ptr++ = 0x40 | (kPID_PMT >> 8);
+ *ptr++ = kPID_PMT & 0xff;
+ *ptr++ = 0x10 | mPMTContinuityCounter;
+ *ptr++ = 0x00;
+
+ crcDataStart = ptr;
+ *ptr++ = 0x02;
+
+ *ptr++ = 0x00; // section_length to be filled in below.
+ *ptr++ = 0x00;
+
+ *ptr++ = 0x00;
+ *ptr++ = 0x01;
+ *ptr++ = 0xc3;
+ *ptr++ = 0x00;
+ *ptr++ = 0x00;
+ *ptr++ = 0xe0 | (kPID_PCR >> 8);
+ *ptr++ = kPID_PCR & 0xff;
+
+ size_t program_info_length = 0;
+ for (size_t i = 0; i < mProgramInfoDescriptors.size(); ++i) {
+ program_info_length += mProgramInfoDescriptors.itemAt(i)->size();
+ }
+
+ CHECK_LT(program_info_length, 0x400u);
+ *ptr++ = 0xf0 | (program_info_length >> 8);
+ *ptr++ = (program_info_length & 0xff);
+
+ for (size_t i = 0; i < mProgramInfoDescriptors.size(); ++i) {
+ const sp<ABuffer> &desc = mProgramInfoDescriptors.itemAt(i);
+ memcpy(ptr, desc->data(), desc->size());
+ ptr += desc->size();
+ }
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ const sp<Track> &track = mTracks.itemAt(i);
+
+ // Make sure all the decriptors have been added.
+ track->finalize();
+
+ *ptr++ = track->streamType();
+ *ptr++ = 0xe0 | (track->PID() >> 8);
+ *ptr++ = track->PID() & 0xff;
+
+ size_t ES_info_length = 0;
+ for (size_t i = 0; i < track->countDescriptors(); ++i) {
+ ES_info_length += track->descriptorAt(i)->size();
+ }
+ CHECK_LE(ES_info_length, 0xfffu);
+
+ *ptr++ = 0xf0 | (ES_info_length >> 8);
+ *ptr++ = (ES_info_length & 0xff);
+
+ for (size_t i = 0; i < track->countDescriptors(); ++i) {
+ const sp<ABuffer> &descriptor = track->descriptorAt(i);
+ memcpy(ptr, descriptor->data(), descriptor->size());
+ ptr += descriptor->size();
+ }
+ }
+
+ size_t section_length = ptr - (crcDataStart + 3) + 4 /* CRC */;
+
+ crcDataStart[1] = 0xb0 | (section_length >> 8);
+ crcDataStart[2] = section_length & 0xff;
+
+ crc = htonl(crc32(crcDataStart, ptr - crcDataStart));
+ memcpy(ptr, &crc, 4);
+ ptr += 4;
+
+ sizeLeft = packetDataStart + 188 - ptr;
+ memset(ptr, 0xff, sizeLeft);
+
+ packetDataStart += 188;
+ }
+
+ if (flags & EMIT_PCR) {
+ // PCR stream
+ // 0x47
+ // transport_error_indicator = b0
+ // payload_unit_start_indicator = b1
+ // transport_priority = b0
+ // PID = kPCR_PID (13 bits)
+ // transport_scrambling_control = b00
+ // adaptation_field_control = b10 (adaptation field only, no payload)
+ // continuity_counter = b0000 (does not increment)
+ // adaptation_field_length = 183
+ // discontinuity_indicator = b0
+ // random_access_indicator = b0
+ // elementary_stream_priority_indicator = b0
+ // PCR_flag = b1
+ // OPCR_flag = b0
+ // splicing_point_flag = b0
+ // transport_private_data_flag = b0
+ // adaptation_field_extension_flag = b0
+ // program_clock_reference_base = b?????????????????????????????????
+ // reserved = b111111
+ // program_clock_reference_extension = b?????????
+
+ int64_t nowUs = ALooper::GetNowUs();
+
+ uint64_t PCR = nowUs * 27; // PCR based on a 27MHz clock
+ uint64_t PCR_base = PCR / 300;
+ uint32_t PCR_ext = PCR % 300;
+
+ uint8_t *ptr = packetDataStart;
+ *ptr++ = 0x47;
+ *ptr++ = 0x40 | (kPID_PCR >> 8);
+ *ptr++ = kPID_PCR & 0xff;
+ *ptr++ = 0x20;
+ *ptr++ = 0xb7; // adaptation_field_length
+ *ptr++ = 0x10;
+ *ptr++ = (PCR_base >> 25) & 0xff;
+ *ptr++ = (PCR_base >> 17) & 0xff;
+ *ptr++ = (PCR_base >> 9) & 0xff;
+ *ptr++ = ((PCR_base & 1) << 7) | 0x7e | ((PCR_ext >> 8) & 1);
+ *ptr++ = (PCR_ext & 0xff);
+
+ size_t sizeLeft = packetDataStart + 188 - ptr;
+ memset(ptr, 0xff, sizeLeft);
+
+ packetDataStart += 188;
+ }
+
+ uint64_t PTS = (timeUs * 9ll) / 100ll;
+
+ if (PES_packet_length >= 65536) {
+ // This really should only happen for video.
+ CHECK(track->isVideo());
+
+ // It's valid to set this to 0 for video according to the specs.
+ PES_packet_length = 0;
+ }
+
+ size_t sizeAvailableForPayload = 188 - 4 - 14 - numStuffingBytes;
+ if (PES_private_data_len > 0) {
+ sizeAvailableForPayload -= PES_private_data_len + 1;
+ }
+
+ size_t copy = accessUnit->size();
+
+ if (copy > sizeAvailableForPayload) {
+ copy = sizeAvailableForPayload;
+
+ if (alignPayload && copy > 16) {
+ copy -= (copy % 16);
+ }
+ }
+
+ size_t numPaddingBytes = sizeAvailableForPayload - copy;
+
+ uint8_t *ptr = packetDataStart;
+ *ptr++ = 0x47;
+ *ptr++ = 0x40 | (track->PID() >> 8);
+ *ptr++ = track->PID() & 0xff;
+
+ *ptr++ = (numPaddingBytes > 0 ? 0x30 : 0x10)
+ | track->incrementContinuityCounter();
+
+ if (numPaddingBytes > 0) {
+ *ptr++ = numPaddingBytes - 1;
+ if (numPaddingBytes >= 2) {
+ *ptr++ = 0x00;
+ memset(ptr, 0xff, numPaddingBytes - 2);
+ ptr += numPaddingBytes - 2;
+ }
+ }
+
+ *ptr++ = 0x00;
+ *ptr++ = 0x00;
+ *ptr++ = 0x01;
+ *ptr++ = track->streamID();
+ *ptr++ = PES_packet_length >> 8;
+ *ptr++ = PES_packet_length & 0xff;
+ *ptr++ = 0x84;
+ *ptr++ = (PES_private_data_len > 0) ? 0x81 : 0x80;
+
+ size_t headerLength = 0x05 + numStuffingBytes;
+ if (PES_private_data_len > 0) {
+ headerLength += 1 + PES_private_data_len;
+ }
+
+ *ptr++ = headerLength;
+
+ *ptr++ = 0x20 | (((PTS >> 30) & 7) << 1) | 1;
+ *ptr++ = (PTS >> 22) & 0xff;
+ *ptr++ = (((PTS >> 15) & 0x7f) << 1) | 1;
+ *ptr++ = (PTS >> 7) & 0xff;
+ *ptr++ = ((PTS & 0x7f) << 1) | 1;
+
+ if (PES_private_data_len > 0) {
+ *ptr++ = 0x8e; // PES_private_data_flag, reserved.
+ memcpy(ptr, PES_private_data, PES_private_data_len);
+ ptr += PES_private_data_len;
+ }
+
+ for (size_t i = 0; i < numStuffingBytes; ++i) {
+ *ptr++ = 0xff;
+ }
+
+ memcpy(ptr, accessUnit->data(), copy);
+ ptr += copy;
+
+ CHECK_EQ(ptr, packetDataStart + 188);
+ packetDataStart += 188;
+
+ size_t offset = copy;
+ while (offset < accessUnit->size()) {
+ // for subsequent fragments of "buffer":
+ // 0x47
+ // transport_error_indicator = b0
+ // payload_unit_start_indicator = b0
+ // transport_priority = b0
+ // PID = b0 0001 1110 ???? (13 bits) [0x1e0 + 1 + sourceIndex]
+ // transport_scrambling_control = b00
+ // adaptation_field_control = b??
+ // continuity_counter = b????
+ // the fragment of "buffer" follows.
+
+ size_t sizeAvailableForPayload = 188 - 4;
+
+ size_t copy = accessUnit->size() - offset;
+
+ if (copy > sizeAvailableForPayload) {
+ copy = sizeAvailableForPayload;
+
+ if (alignPayload && copy > 16) {
+ copy -= (copy % 16);
+ }
+ }
+
+ size_t numPaddingBytes = sizeAvailableForPayload - copy;
+
+ uint8_t *ptr = packetDataStart;
+ *ptr++ = 0x47;
+ *ptr++ = 0x00 | (track->PID() >> 8);
+ *ptr++ = track->PID() & 0xff;
+
+ *ptr++ = (numPaddingBytes > 0 ? 0x30 : 0x10)
+ | track->incrementContinuityCounter();
+
+ if (numPaddingBytes > 0) {
+ *ptr++ = numPaddingBytes - 1;
+ if (numPaddingBytes >= 2) {
+ *ptr++ = 0x00;
+ memset(ptr, 0xff, numPaddingBytes - 2);
+ ptr += numPaddingBytes - 2;
+ }
+ }
+
+ memcpy(ptr, accessUnit->data() + offset, copy);
+ ptr += copy;
+ CHECK_EQ(ptr, packetDataStart + 188);
+
+ offset += copy;
+ packetDataStart += 188;
+ }
+
+ CHECK(packetDataStart == buffer->data() + buffer->capacity());
+
+ *packets = buffer;
+
+ return OK;
+}
+
+void TSPacketizer::initCrcTable() {
+ uint32_t poly = 0x04C11DB7;
+
+ for (int i = 0; i < 256; i++) {
+ uint32_t crc = i << 24;
+ for (int j = 0; j < 8; j++) {
+ crc = (crc << 1) ^ ((crc & 0x80000000) ? (poly) : 0);
+ }
+ mCrcTable[i] = crc;
+ }
+}
+
+uint32_t TSPacketizer::crc32(const uint8_t *start, size_t size) const {
+ uint32_t crc = 0xFFFFFFFF;
+ const uint8_t *p;
+
+ for (p = start; p < start + size; ++p) {
+ crc = (crc << 8) ^ mCrcTable[((crc >> 24) ^ *p) & 0xFF];
+ }
+
+ return crc;
+}
+
+sp<ABuffer> TSPacketizer::prependCSD(
+ size_t trackIndex, const sp<ABuffer> &accessUnit) const {
+ CHECK_LT(trackIndex, mTracks.size());
+
+ const sp<Track> &track = mTracks.itemAt(trackIndex);
+ CHECK(track->isH264() && IsIDR(accessUnit->data(), accessUnit->size()));
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ sp<ABuffer> accessUnit2 = track->prependCSD(accessUnit);
+
+ accessUnit2->meta()->setInt64("timeUs", timeUs);
+
+ return accessUnit2;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.h b/media/libstagefright/wifi-display/source/TSPacketizer.h
new file mode 100644
index 0000000..0dcb179
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/TSPacketizer.h
@@ -0,0 +1,94 @@
+/*
+ * 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 TS_PACKETIZER_H_
+
+#define TS_PACKETIZER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+
+// Forms the packets of a transport stream given access units.
+// Emits metadata tables (PAT and PMT) and timestamp stream (PCR) based
+// on flags.
+struct TSPacketizer : public RefBase {
+ enum {
+ EMIT_HDCP20_DESCRIPTOR = 1,
+ EMIT_HDCP21_DESCRIPTOR = 2,
+ };
+ explicit TSPacketizer(uint32_t flags);
+
+ // Returns trackIndex or error.
+ ssize_t addTrack(const sp<AMessage> &format);
+
+ enum {
+ EMIT_PAT_AND_PMT = 1,
+ EMIT_PCR = 2,
+ IS_ENCRYPTED = 4,
+ PREPEND_SPS_PPS_TO_IDR_FRAMES = 8,
+ };
+ status_t packetize(
+ size_t trackIndex, const sp<ABuffer> &accessUnit,
+ sp<ABuffer> *packets,
+ uint32_t flags,
+ const uint8_t *PES_private_data, size_t PES_private_data_len,
+ size_t numStuffingBytes = 0);
+
+ status_t extractCSDIfNecessary(size_t trackIndex);
+
+ // XXX to be removed once encoder config option takes care of this for
+ // encrypted mode.
+ sp<ABuffer> prependCSD(
+ size_t trackIndex, const sp<ABuffer> &accessUnit) const;
+
+protected:
+ virtual ~TSPacketizer();
+
+private:
+ enum {
+ kPID_PMT = 0x100,
+ kPID_PCR = 0x1000,
+ };
+
+ struct Track;
+
+ uint32_t mFlags;
+ Vector<sp<Track> > mTracks;
+
+ Vector<sp<ABuffer> > mProgramInfoDescriptors;
+
+ unsigned mPATContinuityCounter;
+ unsigned mPMTContinuityCounter;
+
+ uint32_t mCrcTable[256];
+
+ void initCrcTable();
+ uint32_t crc32(const uint8_t *start, size_t size) const;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TSPacketizer);
+};
+
+} // namespace android
+
+#endif // TS_PACKETIZER_H_
+
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
new file mode 100644
index 0000000..96aceb1
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
@@ -0,0 +1,1738 @@
+/*
+ * 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 "WifiDisplaySource"
+#include <utils/Log.h>
+
+#include "WifiDisplaySource.h"
+#include "PlaybackSession.h"
+#include "Parameters.h"
+#include "rtp/RTPSender.h"
+#include <media/stagefright/FoundationUtils.h>
+
+#include <binder/IServiceManager.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <media/IHDCP.h>
+#include <media/IMediaPlayerService.h>
+#include <media/IRemoteDisplayClient.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/ParsedMessage.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+
+#include <arpa/inet.h>
+#include <cutils/properties.h>
+
+#include <ctype.h>
+
+namespace android {
+
+// static
+const int64_t WifiDisplaySource::kReaperIntervalUs;
+const int64_t WifiDisplaySource::kTeardownTriggerTimeouSecs;
+const int64_t WifiDisplaySource::kPlaybackSessionTimeoutSecs;
+const int64_t WifiDisplaySource::kPlaybackSessionTimeoutUs;
+const AString WifiDisplaySource::sUserAgent = MakeUserAgent();
+
+WifiDisplaySource::WifiDisplaySource(
+ const String16 &opPackageName,
+ const sp<ANetworkSession> &netSession,
+ const sp<IRemoteDisplayClient> &client,
+ const char *path)
+ : mOpPackageName(opPackageName),
+ mState(INITIALIZED),
+ mNetSession(netSession),
+ mClient(client),
+ mSessionID(0),
+ mStopReplyID(NULL),
+ mChosenRTPPort(-1),
+ mUsingPCMAudio(false),
+ mClientSessionID(0),
+ mReaperPending(false),
+ mNextCSeq(1),
+ mUsingHDCP(false),
+ mIsHDCP2_0(false),
+ mHDCPPort(0),
+ mHDCPInitializationComplete(false),
+ mSetupTriggerDeferred(false),
+ mPlaybackSessionEstablished(false) {
+ if (path != NULL) {
+ mMediaPath.setTo(path);
+ }
+
+ mSupportedSourceVideoFormats.disableAll();
+
+ mSupportedSourceVideoFormats.setNativeResolution(
+ VideoFormats::RESOLUTION_CEA, 5); // 1280x720 p30
+
+ // Enable all resolutions up to 1280x720p30
+ mSupportedSourceVideoFormats.enableResolutionUpto(
+ VideoFormats::RESOLUTION_CEA, 5,
+ VideoFormats::PROFILE_CHP, // Constrained High Profile
+ VideoFormats::LEVEL_32); // Level 3.2
+}
+
+WifiDisplaySource::~WifiDisplaySource() {
+}
+
+static status_t PostAndAwaitResponse(
+ const sp<AMessage> &msg, sp<AMessage> *response) {
+ status_t err = msg->postAndAwaitResponse(response);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (response == NULL || !(*response)->findInt32("err", &err)) {
+ err = OK;
+ }
+
+ return err;
+}
+
+status_t WifiDisplaySource::start(const char *iface) {
+ CHECK_EQ(mState, INITIALIZED);
+
+ sp<AMessage> msg = new AMessage(kWhatStart, this);
+ msg->setString("iface", iface);
+
+ sp<AMessage> response;
+ return PostAndAwaitResponse(msg, &response);
+}
+
+status_t WifiDisplaySource::stop() {
+ sp<AMessage> msg = new AMessage(kWhatStop, this);
+
+ sp<AMessage> response;
+ return PostAndAwaitResponse(msg, &response);
+}
+
+status_t WifiDisplaySource::pause() {
+ sp<AMessage> msg = new AMessage(kWhatPause, this);
+
+ sp<AMessage> response;
+ return PostAndAwaitResponse(msg, &response);
+}
+
+status_t WifiDisplaySource::resume() {
+ sp<AMessage> msg = new AMessage(kWhatResume, this);
+
+ sp<AMessage> response;
+ return PostAndAwaitResponse(msg, &response);
+}
+
+void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatStart:
+ {
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ AString iface;
+ CHECK(msg->findString("iface", &iface));
+
+ status_t err = OK;
+
+ ssize_t colonPos = iface.find(":");
+
+ unsigned long port;
+
+ if (colonPos >= 0) {
+ const char *s = iface.c_str() + colonPos + 1;
+
+ char *end;
+ port = strtoul(s, &end, 10);
+
+ if (end == s || *end != '\0' || port > 65535) {
+ err = -EINVAL;
+ } else {
+ iface.erase(colonPos, iface.size() - colonPos);
+ }
+ } else {
+ port = kWifiDisplayDefaultPort;
+ }
+
+ if (err == OK) {
+ if (inet_aton(iface.c_str(), &mInterfaceAddr) != 0) {
+ sp<AMessage> notify = new AMessage(kWhatRTSPNotify, this);
+
+ err = mNetSession->createRTSPServer(
+ mInterfaceAddr, port, notify, &mSessionID);
+ } else {
+ err = -EINVAL;
+ }
+ }
+
+ mState = AWAITING_CLIENT_CONNECTION;
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+ response->postReply(replyID);
+ break;
+ }
+
+ case kWhatRTSPNotify:
+ {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatError:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ AString detail;
+ CHECK(msg->findString("detail", &detail));
+
+ ALOGE("An error occurred in session %d (%d, '%s/%s').",
+ sessionID,
+ err,
+ detail.c_str(),
+ strerror(-err));
+
+ mNetSession->destroySession(sessionID);
+
+ if (sessionID == mClientSessionID) {
+ mClientSessionID = 0;
+
+ mClient->onDisplayError(
+ IRemoteDisplayClient::kDisplayErrorUnknown);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatClientConnected:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ if (mClientSessionID > 0) {
+ ALOGW("A client tried to connect, but we already "
+ "have one.");
+
+ mNetSession->destroySession(sessionID);
+ break;
+ }
+
+ CHECK_EQ(mState, AWAITING_CLIENT_CONNECTION);
+
+ CHECK(msg->findString("client-ip", &mClientInfo.mRemoteIP));
+ CHECK(msg->findString("server-ip", &mClientInfo.mLocalIP));
+
+ if (mClientInfo.mRemoteIP == mClientInfo.mLocalIP) {
+ // Disallow connections from the local interface
+ // for security reasons.
+ mNetSession->destroySession(sessionID);
+ break;
+ }
+
+ CHECK(msg->findInt32(
+ "server-port", &mClientInfo.mLocalPort));
+ mClientInfo.mPlaybackSessionID = -1;
+
+ mClientSessionID = sessionID;
+
+ ALOGI("We now have a client (%d) connected.", sessionID);
+
+ mState = AWAITING_CLIENT_SETUP;
+
+ status_t err = sendM1(sessionID);
+ CHECK_EQ(err, (status_t)OK);
+ break;
+ }
+
+ case ANetworkSession::kWhatData:
+ {
+ status_t err = onReceiveClientData(msg);
+
+ if (err != OK) {
+ mClient->onDisplayError(
+ IRemoteDisplayClient::kDisplayErrorUnknown);
+ }
+
+#if 0
+ // testing only.
+ char val[PROPERTY_VALUE_MAX];
+ if (property_get("media.wfd.trigger", val, NULL)) {
+ if (!strcasecmp(val, "pause") && mState == PLAYING) {
+ mState = PLAYING_TO_PAUSED;
+ sendTrigger(mClientSessionID, TRIGGER_PAUSE);
+ } else if (!strcasecmp(val, "play")
+ && mState == PAUSED) {
+ mState = PAUSED_TO_PLAYING;
+ sendTrigger(mClientSessionID, TRIGGER_PLAY);
+ }
+ }
+#endif
+ break;
+ }
+
+ case ANetworkSession::kWhatNetworkStall:
+ {
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatStop:
+ {
+ CHECK(msg->senderAwaitsResponse(&mStopReplyID));
+
+ CHECK_LT(mState, AWAITING_CLIENT_TEARDOWN);
+
+ if (mState >= AWAITING_CLIENT_PLAY) {
+ // We have a session, i.e. a previous SETUP succeeded.
+
+ status_t err = sendTrigger(
+ mClientSessionID, TRIGGER_TEARDOWN);
+
+ if (err == OK) {
+ mState = AWAITING_CLIENT_TEARDOWN;
+
+ (new AMessage(kWhatTeardownTriggerTimedOut, this))->post(
+ kTeardownTriggerTimeouSecs * 1000000ll);
+
+ break;
+ }
+
+ // fall through.
+ }
+
+ finishStop();
+ break;
+ }
+
+ case kWhatPause:
+ {
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ status_t err = OK;
+
+ if (mState != PLAYING) {
+ err = INVALID_OPERATION;
+ } else {
+ mState = PLAYING_TO_PAUSED;
+ sendTrigger(mClientSessionID, TRIGGER_PAUSE);
+ }
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+ response->postReply(replyID);
+ break;
+ }
+
+ case kWhatResume:
+ {
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ status_t err = OK;
+
+ if (mState != PAUSED) {
+ err = INVALID_OPERATION;
+ } else {
+ mState = PAUSED_TO_PLAYING;
+ sendTrigger(mClientSessionID, TRIGGER_PLAY);
+ }
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+ response->postReply(replyID);
+ break;
+ }
+
+ case kWhatReapDeadClients:
+ {
+ mReaperPending = false;
+
+ if (mClientSessionID == 0
+ || mClientInfo.mPlaybackSession == NULL) {
+ break;
+ }
+
+ if (mClientInfo.mPlaybackSession->getLastLifesignUs()
+ + kPlaybackSessionTimeoutUs < ALooper::GetNowUs()) {
+ ALOGI("playback session timed out, reaping.");
+
+ mNetSession->destroySession(mClientSessionID);
+ mClientSessionID = 0;
+
+ mClient->onDisplayError(
+ IRemoteDisplayClient::kDisplayErrorUnknown);
+ } else {
+ scheduleReaper();
+ }
+ break;
+ }
+
+ case kWhatPlaybackSessionNotify:
+ {
+ int32_t playbackSessionID;
+ CHECK(msg->findInt32("playbackSessionID", &playbackSessionID));
+
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ if (what == PlaybackSession::kWhatSessionDead) {
+ ALOGI("playback session wants to quit.");
+
+ mClient->onDisplayError(
+ IRemoteDisplayClient::kDisplayErrorUnknown);
+ } else if (what == PlaybackSession::kWhatSessionEstablished) {
+ mPlaybackSessionEstablished = true;
+
+ if (mClient != NULL) {
+ if (!mSinkSupportsVideo) {
+ mClient->onDisplayConnected(
+ NULL, // SurfaceTexture
+ 0, // width,
+ 0, // height,
+ mUsingHDCP
+ ? IRemoteDisplayClient::kDisplayFlagSecure
+ : 0,
+ 0);
+ } else {
+ size_t width, height;
+
+ CHECK(VideoFormats::GetConfiguration(
+ mChosenVideoResolutionType,
+ mChosenVideoResolutionIndex,
+ &width,
+ &height,
+ NULL /* framesPerSecond */,
+ NULL /* interlaced */));
+
+ mClient->onDisplayConnected(
+ mClientInfo.mPlaybackSession
+ ->getSurfaceTexture(),
+ width,
+ height,
+ mUsingHDCP
+ ? IRemoteDisplayClient::kDisplayFlagSecure
+ : 0,
+ playbackSessionID);
+ }
+ }
+
+ finishPlay();
+
+ if (mState == ABOUT_TO_PLAY) {
+ mState = PLAYING;
+ }
+ } else if (what == PlaybackSession::kWhatSessionDestroyed) {
+ disconnectClient2();
+ } else {
+ CHECK_EQ(what, PlaybackSession::kWhatBinaryData);
+
+ int32_t channel;
+ CHECK(msg->findInt32("channel", &channel));
+
+ sp<ABuffer> data;
+ CHECK(msg->findBuffer("data", &data));
+
+ CHECK_LE(channel, 0xff);
+ CHECK_LE(data->size(), 0xffffu);
+
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ char header[4];
+ header[0] = '$';
+ header[1] = channel;
+ header[2] = data->size() >> 8;
+ header[3] = data->size() & 0xff;
+
+ mNetSession->sendRequest(
+ sessionID, header, sizeof(header));
+
+ mNetSession->sendRequest(
+ sessionID, data->data(), data->size());
+ }
+ break;
+ }
+
+ case kWhatKeepAlive:
+ {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ if (mClientSessionID != sessionID) {
+ // Obsolete event, client is already gone.
+ break;
+ }
+
+ sendM16(sessionID);
+ break;
+ }
+
+ case kWhatTeardownTriggerTimedOut:
+ {
+ if (mState == AWAITING_CLIENT_TEARDOWN) {
+ ALOGI("TEARDOWN trigger timed out, forcing disconnection.");
+
+ CHECK(mStopReplyID != NULL);
+ finishStop();
+ break;
+ }
+ break;
+ }
+
+ case kWhatHDCPNotify:
+ {
+ int32_t msgCode, ext1, ext2;
+ CHECK(msg->findInt32("msg", &msgCode));
+ CHECK(msg->findInt32("ext1", &ext1));
+ CHECK(msg->findInt32("ext2", &ext2));
+
+ ALOGI("Saw HDCP notification code %d, ext1 %d, ext2 %d",
+ msgCode, ext1, ext2);
+
+ switch (msgCode) {
+ case HDCPModule::HDCP_INITIALIZATION_COMPLETE:
+ {
+ mHDCPInitializationComplete = true;
+
+ if (mSetupTriggerDeferred) {
+ mSetupTriggerDeferred = false;
+
+ sendTrigger(mClientSessionID, TRIGGER_SETUP);
+ }
+ break;
+ }
+
+ case HDCPModule::HDCP_SHUTDOWN_COMPLETE:
+ case HDCPModule::HDCP_SHUTDOWN_FAILED:
+ {
+ // Ugly hack to make sure that the call to
+ // HDCPObserver::notify is completely handled before
+ // we clear the HDCP instance and unload the shared
+ // library :(
+ (new AMessage(kWhatFinishStop2, this))->post(300000ll);
+ break;
+ }
+
+ default:
+ {
+ ALOGE("HDCP failure, shutting down.");
+
+ mClient->onDisplayError(
+ IRemoteDisplayClient::kDisplayErrorUnknown);
+ break;
+ }
+ }
+ break;
+ }
+
+ case kWhatFinishStop2:
+ {
+ finishStop2();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void WifiDisplaySource::registerResponseHandler(
+ int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func) {
+ ResponseID id;
+ id.mSessionID = sessionID;
+ id.mCSeq = cseq;
+ mResponseHandlers.add(id, func);
+}
+
+status_t WifiDisplaySource::sendM1(int32_t sessionID) {
+ AString request = "OPTIONS * RTSP/1.0\r\n";
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append(
+ "Require: org.wfa.wfd1.0\r\n"
+ "\r\n");
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM1Response);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySource::sendM3(int32_t sessionID) {
+ AString body =
+ "wfd_content_protection\r\n"
+ "wfd_video_formats\r\n"
+ "wfd_audio_codecs\r\n"
+ "wfd_client_rtp_ports\r\n";
+
+ AString request = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append("Content-Type: text/parameters\r\n");
+ request.append(AStringPrintf("Content-Length: %d\r\n", body.size()));
+ request.append("\r\n");
+ request.append(body);
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM3Response);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySource::sendM4(int32_t sessionID) {
+ CHECK_EQ(sessionID, mClientSessionID);
+
+ AString body;
+
+ if (mSinkSupportsVideo) {
+ body.append("wfd_video_formats: ");
+
+ VideoFormats chosenVideoFormat;
+ chosenVideoFormat.disableAll();
+ chosenVideoFormat.setNativeResolution(
+ mChosenVideoResolutionType, mChosenVideoResolutionIndex);
+ chosenVideoFormat.setProfileLevel(
+ mChosenVideoResolutionType, mChosenVideoResolutionIndex,
+ mChosenVideoProfile, mChosenVideoLevel);
+
+ body.append(chosenVideoFormat.getFormatSpec(true /* forM4Message */));
+ body.append("\r\n");
+ }
+
+ if (mSinkSupportsAudio) {
+ body.append(
+ AStringPrintf("wfd_audio_codecs: %s\r\n",
+ (mUsingPCMAudio
+ ? "LPCM 00000002 00" // 2 ch PCM 48kHz
+ : "AAC 00000001 00"))); // 2 ch AAC 48kHz
+ }
+
+ body.append(
+ AStringPrintf(
+ "wfd_presentation_URL: rtsp://%s/wfd1.0/streamid=0 none\r\n",
+ mClientInfo.mLocalIP.c_str()));
+
+ body.append(
+ AStringPrintf(
+ "wfd_client_rtp_ports: %s\r\n", mWfdClientRtpPorts.c_str()));
+
+ AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append("Content-Type: text/parameters\r\n");
+ request.append(AStringPrintf("Content-Length: %d\r\n", body.size()));
+ request.append("\r\n");
+ request.append(body);
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM4Response);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySource::sendTrigger(
+ int32_t sessionID, TriggerType triggerType) {
+ AString body = "wfd_trigger_method: ";
+ switch (triggerType) {
+ case TRIGGER_SETUP:
+ body.append("SETUP");
+ break;
+ case TRIGGER_TEARDOWN:
+ ALOGI("Sending TEARDOWN trigger.");
+ body.append("TEARDOWN");
+ break;
+ case TRIGGER_PAUSE:
+ body.append("PAUSE");
+ break;
+ case TRIGGER_PLAY:
+ body.append("PLAY");
+ break;
+ default:
+ TRESPASS();
+ }
+
+ body.append("\r\n");
+
+ AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+ AppendCommonResponse(&request, mNextCSeq);
+
+ request.append("Content-Type: text/parameters\r\n");
+ request.append(AStringPrintf("Content-Length: %d\r\n", body.size()));
+ request.append("\r\n");
+ request.append(body);
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM5Response);
+
+ ++mNextCSeq;
+
+ return OK;
+}
+
+status_t WifiDisplaySource::sendM16(int32_t sessionID) {
+ AString request = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+ AppendCommonResponse(&request, mNextCSeq);
+
+ CHECK_EQ(sessionID, mClientSessionID);
+ request.append(
+ AStringPrintf("Session: %d\r\n", mClientInfo.mPlaybackSessionID));
+ request.append("\r\n"); // Empty body
+
+ status_t err =
+ mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+ if (err != OK) {
+ return err;
+ }
+
+ registerResponseHandler(
+ sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM16Response);
+
+ ++mNextCSeq;
+
+ scheduleKeepAlive(sessionID);
+
+ return OK;
+}
+
+status_t WifiDisplaySource::onReceiveM1Response(
+ int32_t /* sessionID */, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ return OK;
+}
+
+// sink_audio_list := ("LPCM"|"AAC"|"AC3" HEXDIGIT*8 HEXDIGIT*2)
+// (", " sink_audio_list)*
+static void GetAudioModes(const char *s, const char *prefix, uint32_t *modes) {
+ *modes = 0;
+
+ size_t prefixLen = strlen(prefix);
+
+ while (*s != '0') {
+ if (!strncmp(s, prefix, prefixLen) && s[prefixLen] == ' ') {
+ unsigned latency;
+ if (sscanf(&s[prefixLen + 1], "%08x %02x", modes, &latency) != 2) {
+ *modes = 0;
+ }
+
+ return;
+ }
+
+ const char *commaPos = strchr(s, ',');
+ if (commaPos != NULL) {
+ s = commaPos + 1;
+
+ while (isspace(*s)) {
+ ++s;
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+status_t WifiDisplaySource::onReceiveM3Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ sp<Parameters> params =
+ Parameters::Parse(msg->getContent(), strlen(msg->getContent()));
+
+ if (params == NULL) {
+ return ERROR_MALFORMED;
+ }
+
+ AString value;
+ if (!params->findParameter("wfd_client_rtp_ports", &value)) {
+ ALOGE("Sink doesn't report its choice of wfd_client_rtp_ports.");
+ return ERROR_MALFORMED;
+ }
+
+ unsigned port0 = 0, port1 = 0;
+ if (sscanf(value.c_str(),
+ "RTP/AVP/UDP;unicast %u %u mode=play",
+ &port0,
+ &port1) == 2
+ || sscanf(value.c_str(),
+ "RTP/AVP/TCP;unicast %u %u mode=play",
+ &port0,
+ &port1) == 2) {
+ if (port0 == 0 || port0 > 65535 || port1 != 0) {
+ ALOGE("Sink chose its wfd_client_rtp_ports poorly (%s)",
+ value.c_str());
+
+ return ERROR_MALFORMED;
+ }
+ } else if (strcmp(value.c_str(), "RTP/AVP/TCP;interleaved mode=play")) {
+ ALOGE("Unsupported value for wfd_client_rtp_ports (%s)",
+ value.c_str());
+
+ return ERROR_UNSUPPORTED;
+ }
+
+ mWfdClientRtpPorts = value;
+ mChosenRTPPort = port0;
+
+ if (!params->findParameter("wfd_video_formats", &value)) {
+ ALOGE("Sink doesn't report its choice of wfd_video_formats.");
+ return ERROR_MALFORMED;
+ }
+
+ mSinkSupportsVideo = false;
+
+ if (!(value == "none")) {
+ mSinkSupportsVideo = true;
+ if (!mSupportedSinkVideoFormats.parseFormatSpec(value.c_str())) {
+ ALOGE("Failed to parse sink provided wfd_video_formats (%s)",
+ value.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ if (!VideoFormats::PickBestFormat(
+ mSupportedSinkVideoFormats,
+ mSupportedSourceVideoFormats,
+ &mChosenVideoResolutionType,
+ &mChosenVideoResolutionIndex,
+ &mChosenVideoProfile,
+ &mChosenVideoLevel)) {
+ ALOGE("Sink and source share no commonly supported video "
+ "formats.");
+
+ return ERROR_UNSUPPORTED;
+ }
+
+ size_t width, height, framesPerSecond;
+ bool interlaced;
+ CHECK(VideoFormats::GetConfiguration(
+ mChosenVideoResolutionType,
+ mChosenVideoResolutionIndex,
+ &width,
+ &height,
+ &framesPerSecond,
+ &interlaced));
+
+ ALOGI("Picked video resolution %zu x %zu %c%zu",
+ width, height, interlaced ? 'i' : 'p', framesPerSecond);
+
+ ALOGI("Picked AVC profile %d, level %d",
+ mChosenVideoProfile, mChosenVideoLevel);
+ } else {
+ ALOGI("Sink doesn't support video at all.");
+ }
+
+ if (!params->findParameter("wfd_audio_codecs", &value)) {
+ ALOGE("Sink doesn't report its choice of wfd_audio_codecs.");
+ return ERROR_MALFORMED;
+ }
+
+ mSinkSupportsAudio = false;
+
+ if (!(value == "none")) {
+ mSinkSupportsAudio = true;
+
+ uint32_t modes;
+ GetAudioModes(value.c_str(), "AAC", &modes);
+
+ bool supportsAAC = (modes & 1) != 0; // AAC 2ch 48kHz
+
+ GetAudioModes(value.c_str(), "LPCM", &modes);
+
+ bool supportsPCM = (modes & 2) != 0; // LPCM 2ch 48kHz
+
+ if (supportsPCM
+ && property_get_bool("media.wfd.use-pcm-audio", false)) {
+ ALOGI("Using PCM audio.");
+ mUsingPCMAudio = true;
+ } else if (supportsAAC) {
+ ALOGI("Using AAC audio.");
+ mUsingPCMAudio = false;
+ } else if (supportsPCM) {
+ ALOGI("Using PCM audio.");
+ mUsingPCMAudio = true;
+ } else {
+ ALOGI("Sink doesn't support an audio format we do.");
+ return ERROR_UNSUPPORTED;
+ }
+ } else {
+ ALOGI("Sink doesn't support audio at all.");
+ }
+
+ if (!mSinkSupportsVideo && !mSinkSupportsAudio) {
+ ALOGE("Sink supports neither video nor audio...");
+ return ERROR_UNSUPPORTED;
+ }
+
+ mUsingHDCP = false;
+ if (!params->findParameter("wfd_content_protection", &value)) {
+ ALOGI("Sink doesn't appear to support content protection.");
+ } else if (value == "none") {
+ ALOGI("Sink does not support content protection.");
+ } else {
+ mUsingHDCP = true;
+
+ bool isHDCP2_0 = false;
+ if (value.startsWith("HDCP2.0 ")) {
+ isHDCP2_0 = true;
+ } else if (!value.startsWith("HDCP2.1 ")) {
+ ALOGE("malformed wfd_content_protection: '%s'", value.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ int32_t hdcpPort;
+ if (!ParsedMessage::GetInt32Attribute(
+ value.c_str() + 8, "port", &hdcpPort)
+ || hdcpPort < 1 || hdcpPort > 65535) {
+ return ERROR_MALFORMED;
+ }
+
+ mIsHDCP2_0 = isHDCP2_0;
+ mHDCPPort = hdcpPort;
+
+ status_t err = makeHDCP();
+ if (err != OK) {
+ ALOGE("Unable to instantiate HDCP component. "
+ "Not using HDCP after all.");
+
+ mUsingHDCP = false;
+ }
+ }
+
+ return sendM4(sessionID);
+}
+
+status_t WifiDisplaySource::onReceiveM4Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ if (mUsingHDCP && !mHDCPInitializationComplete) {
+ ALOGI("Deferring SETUP trigger until HDCP initialization completes.");
+
+ mSetupTriggerDeferred = true;
+ return OK;
+ }
+
+ return sendTrigger(sessionID, TRIGGER_SETUP);
+}
+
+status_t WifiDisplaySource::onReceiveM5Response(
+ int32_t /* sessionID */, const sp<ParsedMessage> &msg) {
+ int32_t statusCode;
+ if (!msg->getStatusCode(&statusCode)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (statusCode != 200) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ return OK;
+}
+
+status_t WifiDisplaySource::onReceiveM16Response(
+ int32_t sessionID, const sp<ParsedMessage> & /* msg */) {
+ // If only the response was required to include a "Session:" header...
+
+ CHECK_EQ(sessionID, mClientSessionID);
+
+ if (mClientInfo.mPlaybackSession != NULL) {
+ mClientInfo.mPlaybackSession->updateLiveness();
+ }
+
+ return OK;
+}
+
+void WifiDisplaySource::scheduleReaper() {
+ if (mReaperPending) {
+ return;
+ }
+
+ mReaperPending = true;
+ (new AMessage(kWhatReapDeadClients, this))->post(kReaperIntervalUs);
+}
+
+void WifiDisplaySource::scheduleKeepAlive(int32_t sessionID) {
+ // We need to send updates at least 5 secs before the timeout is set to
+ // expire, make sure the timeout is greater than 5 secs to begin with.
+ CHECK_GT(kPlaybackSessionTimeoutUs, 5000000ll);
+
+ sp<AMessage> msg = new AMessage(kWhatKeepAlive, this);
+ msg->setInt32("sessionID", sessionID);
+ msg->post(kPlaybackSessionTimeoutUs - 5000000ll);
+}
+
+status_t WifiDisplaySource::onReceiveClientData(const sp<AMessage> &msg) {
+ int32_t sessionID;
+ CHECK(msg->findInt32("sessionID", &sessionID));
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("data", &obj));
+
+ sp<ParsedMessage> data =
+ static_cast<ParsedMessage *>(obj.get());
+
+ ALOGV("session %d received '%s'",
+ sessionID, data->debugString().c_str());
+
+ AString method;
+ AString uri;
+ data->getRequestField(0, &method);
+
+ int32_t cseq;
+ if (!data->findInt32("cseq", &cseq)) {
+ sendErrorResponse(sessionID, "400 Bad Request", -1 /* cseq */);
+ return ERROR_MALFORMED;
+ }
+
+ if (method.startsWith("RTSP/")) {
+ // This is a response.
+
+ ResponseID id;
+ id.mSessionID = sessionID;
+ id.mCSeq = cseq;
+
+ ssize_t index = mResponseHandlers.indexOfKey(id);
+
+ if (index < 0) {
+ ALOGW("Received unsolicited server response, cseq %d", cseq);
+ return ERROR_MALFORMED;
+ }
+
+ HandleRTSPResponseFunc func = mResponseHandlers.valueAt(index);
+ mResponseHandlers.removeItemsAt(index);
+
+ status_t err = (this->*func)(sessionID, data);
+
+ if (err != OK) {
+ ALOGW("Response handler for session %d, cseq %d returned "
+ "err %d (%s)",
+ sessionID, cseq, err, strerror(-err));
+
+ return err;
+ }
+
+ return OK;
+ }
+
+ AString version;
+ data->getRequestField(2, &version);
+ if (!(version == AString("RTSP/1.0"))) {
+ sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq);
+ return ERROR_UNSUPPORTED;
+ }
+
+ status_t err;
+ if (method == "OPTIONS") {
+ err = onOptionsRequest(sessionID, cseq, data);
+ } else if (method == "SETUP") {
+ err = onSetupRequest(sessionID, cseq, data);
+ } else if (method == "PLAY") {
+ err = onPlayRequest(sessionID, cseq, data);
+ } else if (method == "PAUSE") {
+ err = onPauseRequest(sessionID, cseq, data);
+ } else if (method == "TEARDOWN") {
+ err = onTeardownRequest(sessionID, cseq, data);
+ } else if (method == "GET_PARAMETER") {
+ err = onGetParameterRequest(sessionID, cseq, data);
+ } else if (method == "SET_PARAMETER") {
+ err = onSetParameterRequest(sessionID, cseq, data);
+ } else {
+ sendErrorResponse(sessionID, "405 Method Not Allowed", cseq);
+
+ err = ERROR_UNSUPPORTED;
+ }
+
+ return err;
+}
+
+status_t WifiDisplaySource::onOptionsRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int32_t playbackSessionID;
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession != NULL) {
+ playbackSession->updateLiveness();
+ }
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq);
+
+ response.append(
+ "Public: org.wfa.wfd1.0, SETUP, TEARDOWN, PLAY, PAUSE, "
+ "GET_PARAMETER, SET_PARAMETER\r\n");
+
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+
+ if (err == OK) {
+ err = sendM3(sessionID);
+ }
+
+ return err;
+}
+
+status_t WifiDisplaySource::onSetupRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ CHECK_EQ(sessionID, mClientSessionID);
+ if (mClientInfo.mPlaybackSessionID != -1) {
+ // We only support a single playback session per client.
+ // This is due to the reversed keep-alive design in the wfd specs...
+ sendErrorResponse(sessionID, "400 Bad Request", cseq);
+ return ERROR_MALFORMED;
+ }
+
+ AString transport;
+ if (!data->findString("transport", &transport)) {
+ sendErrorResponse(sessionID, "400 Bad Request", cseq);
+ return ERROR_MALFORMED;
+ }
+
+ RTPSender::TransportMode rtpMode = RTPSender::TRANSPORT_UDP;
+
+ int clientRtp, clientRtcp;
+ if (transport.startsWith("RTP/AVP/TCP;")) {
+ AString interleaved;
+ if (ParsedMessage::GetAttribute(
+ transport.c_str(), "interleaved", &interleaved)
+ && sscanf(interleaved.c_str(), "%d-%d",
+ &clientRtp, &clientRtcp) == 2) {
+ rtpMode = RTPSender::TRANSPORT_TCP_INTERLEAVED;
+ } else {
+ bool badRequest = false;
+
+ AString clientPort;
+ if (!ParsedMessage::GetAttribute(
+ transport.c_str(), "client_port", &clientPort)) {
+ badRequest = true;
+ } else if (sscanf(clientPort.c_str(), "%d-%d",
+ &clientRtp, &clientRtcp) == 2) {
+ } else if (sscanf(clientPort.c_str(), "%d", &clientRtp) == 1) {
+ // No RTCP.
+ clientRtcp = -1;
+ } else {
+ badRequest = true;
+ }
+
+ if (badRequest) {
+ sendErrorResponse(sessionID, "400 Bad Request", cseq);
+ return ERROR_MALFORMED;
+ }
+
+ rtpMode = RTPSender::TRANSPORT_TCP;
+ }
+ } else if (transport.startsWith("RTP/AVP;unicast;")
+ || transport.startsWith("RTP/AVP/UDP;unicast;")) {
+ bool badRequest = false;
+
+ AString clientPort;
+ if (!ParsedMessage::GetAttribute(
+ transport.c_str(), "client_port", &clientPort)) {
+ badRequest = true;
+ } else if (sscanf(clientPort.c_str(), "%d-%d",
+ &clientRtp, &clientRtcp) == 2) {
+ } else if (sscanf(clientPort.c_str(), "%d", &clientRtp) == 1) {
+ // No RTCP.
+ clientRtcp = -1;
+ } else {
+ badRequest = true;
+ }
+
+ if (badRequest) {
+ sendErrorResponse(sessionID, "400 Bad Request", cseq);
+ return ERROR_MALFORMED;
+ }
+#if 1
+ // The older LG dongles doesn't specify client_port=xxx apparently.
+ } else if (transport == "RTP/AVP/UDP;unicast") {
+ clientRtp = 19000;
+ clientRtcp = -1;
+#endif
+ } else {
+ sendErrorResponse(sessionID, "461 Unsupported Transport", cseq);
+ return ERROR_UNSUPPORTED;
+ }
+
+ int32_t playbackSessionID = makeUniquePlaybackSessionID();
+
+ sp<AMessage> notify = new AMessage(kWhatPlaybackSessionNotify, this);
+ notify->setInt32("playbackSessionID", playbackSessionID);
+ notify->setInt32("sessionID", sessionID);
+
+ sp<PlaybackSession> playbackSession =
+ new PlaybackSession(
+ mOpPackageName, mNetSession, notify, mInterfaceAddr, mHDCP, mMediaPath.c_str());
+
+ looper()->registerHandler(playbackSession);
+
+ AString uri;
+ data->getRequestField(1, &uri);
+
+ if (strncasecmp("rtsp://", uri.c_str(), 7)) {
+ sendErrorResponse(sessionID, "400 Bad Request", cseq);
+ return ERROR_MALFORMED;
+ }
+
+ if (!(uri.startsWith("rtsp://") && uri.endsWith("/wfd1.0/streamid=0"))) {
+ sendErrorResponse(sessionID, "404 Not found", cseq);
+ return ERROR_MALFORMED;
+ }
+
+ RTPSender::TransportMode rtcpMode = RTPSender::TRANSPORT_UDP;
+ if (clientRtcp < 0) {
+ rtcpMode = RTPSender::TRANSPORT_NONE;
+ }
+
+ status_t err = playbackSession->init(
+ mClientInfo.mRemoteIP.c_str(),
+ clientRtp,
+ rtpMode,
+ clientRtcp,
+ rtcpMode,
+ mSinkSupportsAudio,
+ mUsingPCMAudio,
+ mSinkSupportsVideo,
+ mChosenVideoResolutionType,
+ mChosenVideoResolutionIndex,
+ mChosenVideoProfile,
+ mChosenVideoLevel);
+
+ if (err != OK) {
+ looper()->unregisterHandler(playbackSession->id());
+ playbackSession.clear();
+ }
+
+ switch (err) {
+ case OK:
+ break;
+ case -ENOENT:
+ sendErrorResponse(sessionID, "404 Not Found", cseq);
+ return err;
+ default:
+ sendErrorResponse(sessionID, "403 Forbidden", cseq);
+ return err;
+ }
+
+ mClientInfo.mPlaybackSessionID = playbackSessionID;
+ mClientInfo.mPlaybackSession = playbackSession;
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+
+ if (rtpMode == RTPSender::TRANSPORT_TCP_INTERLEAVED) {
+ response.append(
+ AStringPrintf(
+ "Transport: RTP/AVP/TCP;interleaved=%d-%d;",
+ clientRtp, clientRtcp));
+ } else {
+ int32_t serverRtp = playbackSession->getRTPPort();
+
+ AString transportString = "UDP";
+ if (rtpMode == RTPSender::TRANSPORT_TCP) {
+ transportString = "TCP";
+ }
+
+ if (clientRtcp >= 0) {
+ response.append(
+ AStringPrintf(
+ "Transport: RTP/AVP/%s;unicast;client_port=%d-%d;"
+ "server_port=%d-%d\r\n",
+ transportString.c_str(),
+ clientRtp, clientRtcp, serverRtp, serverRtp + 1));
+ } else {
+ response.append(
+ AStringPrintf(
+ "Transport: RTP/AVP/%s;unicast;client_port=%d;"
+ "server_port=%d\r\n",
+ transportString.c_str(),
+ clientRtp, serverRtp));
+ }
+ }
+
+ response.append("\r\n");
+
+ err = mNetSession->sendRequest(sessionID, response.c_str());
+
+ if (err != OK) {
+ return err;
+ }
+
+ mState = AWAITING_CLIENT_PLAY;
+
+ scheduleReaper();
+ scheduleKeepAlive(sessionID);
+
+ return OK;
+}
+
+status_t WifiDisplaySource::onPlayRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int32_t playbackSessionID;
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession == NULL) {
+ sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+ return ERROR_MALFORMED;
+ }
+
+ if (mState != AWAITING_CLIENT_PLAY
+ && mState != PAUSED_TO_PLAYING
+ && mState != PAUSED) {
+ ALOGW("Received PLAY request but we're in state %d", mState);
+
+ sendErrorResponse(
+ sessionID, "455 Method Not Valid in This State", cseq);
+
+ return INVALID_OPERATION;
+ }
+
+ ALOGI("Received PLAY request.");
+ if (mPlaybackSessionEstablished) {
+ finishPlay();
+ } else {
+ ALOGI("deferring PLAY request until session established.");
+ }
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+ response.append("Range: npt=now-\r\n");
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (mState == PAUSED_TO_PLAYING || mPlaybackSessionEstablished) {
+ mState = PLAYING;
+ return OK;
+ }
+
+ CHECK_EQ(mState, AWAITING_CLIENT_PLAY);
+ mState = ABOUT_TO_PLAY;
+
+ return OK;
+}
+
+void WifiDisplaySource::finishPlay() {
+ const sp<PlaybackSession> &playbackSession =
+ mClientInfo.mPlaybackSession;
+
+ status_t err = playbackSession->play();
+ CHECK_EQ(err, (status_t)OK);
+}
+
+status_t WifiDisplaySource::onPauseRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int32_t playbackSessionID;
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession == NULL) {
+ sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+ return ERROR_MALFORMED;
+ }
+
+ ALOGI("Received PAUSE request.");
+
+ if (mState != PLAYING_TO_PAUSED && mState != PLAYING) {
+ return INVALID_OPERATION;
+ }
+
+ status_t err = playbackSession->pause();
+ CHECK_EQ(err, (status_t)OK);
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+ response.append("\r\n");
+
+ err = mNetSession->sendRequest(sessionID, response.c_str());
+
+ if (err != OK) {
+ return err;
+ }
+
+ mState = PAUSED;
+
+ return err;
+}
+
+status_t WifiDisplaySource::onTeardownRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ ALOGI("Received TEARDOWN request.");
+
+ int32_t playbackSessionID;
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession == NULL) {
+ sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+ return ERROR_MALFORMED;
+ }
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+ response.append("Connection: close\r\n");
+ response.append("\r\n");
+
+ mNetSession->sendRequest(sessionID, response.c_str());
+
+ if (mState == AWAITING_CLIENT_TEARDOWN) {
+ CHECK(mStopReplyID != NULL);
+ finishStop();
+ } else {
+ mClient->onDisplayError(IRemoteDisplayClient::kDisplayErrorUnknown);
+ }
+
+ return OK;
+}
+
+void WifiDisplaySource::finishStop() {
+ ALOGV("finishStop");
+
+ mState = STOPPING;
+
+ disconnectClientAsync();
+}
+
+void WifiDisplaySource::finishStopAfterDisconnectingClient() {
+ ALOGV("finishStopAfterDisconnectingClient");
+
+ if (mHDCP != NULL) {
+ ALOGI("Initiating HDCP shutdown.");
+ mHDCP->shutdownAsync();
+ return;
+ }
+
+ finishStop2();
+}
+
+void WifiDisplaySource::finishStop2() {
+ ALOGV("finishStop2");
+
+ if (mHDCP != NULL) {
+ mHDCP->setObserver(NULL);
+ mHDCPObserver.clear();
+ mHDCP.clear();
+ }
+
+ if (mSessionID != 0) {
+ mNetSession->destroySession(mSessionID);
+ mSessionID = 0;
+ }
+
+ ALOGI("We're stopped.");
+ mState = STOPPED;
+
+ status_t err = OK;
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+ response->postReply(mStopReplyID);
+}
+
+status_t WifiDisplaySource::onGetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int32_t playbackSessionID;
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession == NULL) {
+ sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+ return ERROR_MALFORMED;
+ }
+
+ playbackSession->updateLiveness();
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ return err;
+}
+
+status_t WifiDisplaySource::onSetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data) {
+ int32_t playbackSessionID;
+ sp<PlaybackSession> playbackSession =
+ findPlaybackSession(data, &playbackSessionID);
+
+ if (playbackSession == NULL) {
+ sendErrorResponse(sessionID, "454 Session Not Found", cseq);
+ return ERROR_MALFORMED;
+ }
+
+ if (strstr(data->getContent(), "wfd_idr_request\r\n")) {
+ playbackSession->requestIDRFrame();
+ }
+
+ playbackSession->updateLiveness();
+
+ AString response = "RTSP/1.0 200 OK\r\n";
+ AppendCommonResponse(&response, cseq, playbackSessionID);
+ response.append("\r\n");
+
+ status_t err = mNetSession->sendRequest(sessionID, response.c_str());
+ return err;
+}
+
+// static
+void WifiDisplaySource::AppendCommonResponse(
+ AString *response, int32_t cseq, int32_t playbackSessionID) {
+ time_t now = time(NULL);
+ struct tm *now2 = gmtime(&now);
+ char buf[128];
+ strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %z", now2);
+
+ response->append("Date: ");
+ response->append(buf);
+ response->append("\r\n");
+
+ response->append(AStringPrintf("Server: %s\r\n", sUserAgent.c_str()));
+
+ if (cseq >= 0) {
+ response->append(AStringPrintf("CSeq: %d\r\n", cseq));
+ }
+
+ if (playbackSessionID >= 0ll) {
+ response->append(
+ AStringPrintf(
+ "Session: %d;timeout=%lld\r\n",
+ playbackSessionID, kPlaybackSessionTimeoutSecs));
+ }
+}
+
+void WifiDisplaySource::sendErrorResponse(
+ int32_t sessionID,
+ const char *errorDetail,
+ int32_t cseq) {
+ AString response;
+ response.append("RTSP/1.0 ");
+ response.append(errorDetail);
+ response.append("\r\n");
+
+ AppendCommonResponse(&response, cseq);
+
+ response.append("\r\n");
+
+ mNetSession->sendRequest(sessionID, response.c_str());
+}
+
+int32_t WifiDisplaySource::makeUniquePlaybackSessionID() const {
+ return rand();
+}
+
+sp<WifiDisplaySource::PlaybackSession> WifiDisplaySource::findPlaybackSession(
+ const sp<ParsedMessage> &data, int32_t *playbackSessionID) const {
+ if (!data->findInt32("session", playbackSessionID)) {
+ // XXX the older dongles do not always include a "Session:" header.
+ *playbackSessionID = mClientInfo.mPlaybackSessionID;
+ return mClientInfo.mPlaybackSession;
+ }
+
+ if (*playbackSessionID != mClientInfo.mPlaybackSessionID) {
+ return NULL;
+ }
+
+ return mClientInfo.mPlaybackSession;
+}
+
+void WifiDisplaySource::disconnectClientAsync() {
+ ALOGV("disconnectClient");
+
+ if (mClientInfo.mPlaybackSession == NULL) {
+ disconnectClient2();
+ return;
+ }
+
+ if (mClientInfo.mPlaybackSession != NULL) {
+ ALOGV("Destroying PlaybackSession");
+ mClientInfo.mPlaybackSession->destroyAsync();
+ }
+}
+
+void WifiDisplaySource::disconnectClient2() {
+ ALOGV("disconnectClient2");
+
+ if (mClientInfo.mPlaybackSession != NULL) {
+ looper()->unregisterHandler(mClientInfo.mPlaybackSession->id());
+ mClientInfo.mPlaybackSession.clear();
+ }
+
+ if (mClientSessionID != 0) {
+ mNetSession->destroySession(mClientSessionID);
+ mClientSessionID = 0;
+ }
+
+ mClient->onDisplayDisconnected();
+
+ finishStopAfterDisconnectingClient();
+}
+
+struct WifiDisplaySource::HDCPObserver : public BnHDCPObserver {
+ explicit HDCPObserver(const sp<AMessage> ¬ify);
+
+ virtual void notify(
+ int msg, int ext1, int ext2, const Parcel *obj);
+
+private:
+ sp<AMessage> mNotify;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HDCPObserver);
+};
+
+WifiDisplaySource::HDCPObserver::HDCPObserver(
+ const sp<AMessage> ¬ify)
+ : mNotify(notify) {
+}
+
+void WifiDisplaySource::HDCPObserver::notify(
+ int msg, int ext1, int ext2, const Parcel * /* obj */) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("msg", msg);
+ notify->setInt32("ext1", ext1);
+ notify->setInt32("ext2", ext2);
+ notify->post();
+}
+
+status_t WifiDisplaySource::makeHDCP() {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder = sm->getService(String16("media.player"));
+
+ sp<IMediaPlayerService> service =
+ interface_cast<IMediaPlayerService>(binder);
+
+ CHECK(service != NULL);
+
+ mHDCP = service->makeHDCP(true /* createEncryptionModule */);
+
+ if (mHDCP == NULL) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ sp<AMessage> notify = new AMessage(kWhatHDCPNotify, this);
+ mHDCPObserver = new HDCPObserver(notify);
+
+ status_t err = mHDCP->setObserver(mHDCPObserver);
+
+ if (err != OK) {
+ ALOGE("Failed to set HDCP observer.");
+
+ mHDCPObserver.clear();
+ mHDCP.clear();
+
+ return err;
+ }
+
+ ALOGI("Initiating HDCP negotiation w/ host %s:%d",
+ mClientInfo.mRemoteIP.c_str(), mHDCPPort);
+
+ err = mHDCP->initAsync(mClientInfo.mRemoteIP.c_str(), mHDCPPort);
+
+ if (err != OK) {
+ return err;
+ }
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.h b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
new file mode 100644
index 0000000..47047f8
--- /dev/null
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
@@ -0,0 +1,278 @@
+/*
+ * 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 WIFI_DISPLAY_SOURCE_H_
+
+#define WIFI_DISPLAY_SOURCE_H_
+
+#include "VideoFormats.h"
+
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/ANetworkSession.h>
+
+#include <netinet/in.h>
+
+#include <utils/String16.h>
+
+namespace android {
+
+struct AReplyToken;
+struct IHDCP;
+class IRemoteDisplayClient;
+struct ParsedMessage;
+
+// Represents the RTSP server acting as a wifi display source.
+// Manages incoming connections, sets up Playback sessions as necessary.
+struct WifiDisplaySource : public AHandler {
+ static const unsigned kWifiDisplayDefaultPort = 7236;
+
+ WifiDisplaySource(
+ const String16 &opPackageName,
+ const sp<ANetworkSession> &netSession,
+ const sp<IRemoteDisplayClient> &client,
+ const char *path = NULL);
+
+ status_t start(const char *iface);
+ status_t stop();
+
+ status_t pause();
+ status_t resume();
+
+protected:
+ virtual ~WifiDisplaySource();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ struct PlaybackSession;
+ struct HDCPObserver;
+
+ enum State {
+ INITIALIZED,
+ AWAITING_CLIENT_CONNECTION,
+ AWAITING_CLIENT_SETUP,
+ AWAITING_CLIENT_PLAY,
+ ABOUT_TO_PLAY,
+ PLAYING,
+ PLAYING_TO_PAUSED,
+ PAUSED,
+ PAUSED_TO_PLAYING,
+ AWAITING_CLIENT_TEARDOWN,
+ STOPPING,
+ STOPPED,
+ };
+
+ enum {
+ kWhatStart,
+ kWhatRTSPNotify,
+ kWhatStop,
+ kWhatPause,
+ kWhatResume,
+ kWhatReapDeadClients,
+ kWhatPlaybackSessionNotify,
+ kWhatKeepAlive,
+ kWhatHDCPNotify,
+ kWhatFinishStop2,
+ kWhatTeardownTriggerTimedOut,
+ };
+
+ struct ResponseID {
+ int32_t mSessionID;
+ int32_t mCSeq;
+
+ bool operator<(const ResponseID &other) const {
+ return mSessionID < other.mSessionID
+ || (mSessionID == other.mSessionID
+ && mCSeq < other.mCSeq);
+ }
+ };
+
+ typedef status_t (WifiDisplaySource::*HandleRTSPResponseFunc)(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ static const int64_t kReaperIntervalUs = 1000000ll;
+
+ // We request that the dongle send us a "TEARDOWN" in order to
+ // perform an orderly shutdown. We're willing to wait up to 2 secs
+ // for this message to arrive, after that we'll force a disconnect
+ // instead.
+ static const int64_t kTeardownTriggerTimeouSecs = 2;
+
+ static const int64_t kPlaybackSessionTimeoutSecs = 30;
+
+ static const int64_t kPlaybackSessionTimeoutUs =
+ kPlaybackSessionTimeoutSecs * 1000000ll;
+
+ static const AString sUserAgent;
+
+ String16 mOpPackageName;
+
+ State mState;
+ VideoFormats mSupportedSourceVideoFormats;
+ sp<ANetworkSession> mNetSession;
+ sp<IRemoteDisplayClient> mClient;
+ AString mMediaPath;
+ struct in_addr mInterfaceAddr;
+ int32_t mSessionID;
+
+ sp<AReplyToken> mStopReplyID;
+
+ AString mWfdClientRtpPorts;
+ int32_t mChosenRTPPort; // extracted from "wfd_client_rtp_ports"
+
+ bool mSinkSupportsVideo;
+ VideoFormats mSupportedSinkVideoFormats;
+
+ VideoFormats::ResolutionType mChosenVideoResolutionType;
+ size_t mChosenVideoResolutionIndex;
+ VideoFormats::ProfileType mChosenVideoProfile;
+ VideoFormats::LevelType mChosenVideoLevel;
+
+ bool mSinkSupportsAudio;
+
+ bool mUsingPCMAudio;
+ int32_t mClientSessionID;
+
+ struct ClientInfo {
+ AString mRemoteIP;
+ AString mLocalIP;
+ int32_t mLocalPort;
+ int32_t mPlaybackSessionID;
+ sp<PlaybackSession> mPlaybackSession;
+ };
+ ClientInfo mClientInfo;
+
+ bool mReaperPending;
+
+ int32_t mNextCSeq;
+
+ KeyedVector<ResponseID, HandleRTSPResponseFunc> mResponseHandlers;
+
+ // HDCP specific section >>>>
+ bool mUsingHDCP;
+ bool mIsHDCP2_0;
+ int32_t mHDCPPort;
+ sp<IHDCP> mHDCP;
+ sp<HDCPObserver> mHDCPObserver;
+
+ bool mHDCPInitializationComplete;
+ bool mSetupTriggerDeferred;
+
+ bool mPlaybackSessionEstablished;
+
+ status_t makeHDCP();
+ // <<<< HDCP specific section
+
+ status_t sendM1(int32_t sessionID);
+ status_t sendM3(int32_t sessionID);
+ status_t sendM4(int32_t sessionID);
+
+ enum TriggerType {
+ TRIGGER_SETUP,
+ TRIGGER_TEARDOWN,
+ TRIGGER_PAUSE,
+ TRIGGER_PLAY,
+ };
+
+ // M5
+ status_t sendTrigger(int32_t sessionID, TriggerType triggerType);
+
+ status_t sendM16(int32_t sessionID);
+
+ status_t onReceiveM1Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveM3Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveM4Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveM5Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ status_t onReceiveM16Response(
+ int32_t sessionID, const sp<ParsedMessage> &msg);
+
+ void registerResponseHandler(
+ int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func);
+
+ status_t onReceiveClientData(const sp<AMessage> &msg);
+
+ status_t onOptionsRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ status_t onSetupRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ status_t onPlayRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ status_t onPauseRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ status_t onTeardownRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ status_t onGetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ status_t onSetParameterRequest(
+ int32_t sessionID,
+ int32_t cseq,
+ const sp<ParsedMessage> &data);
+
+ void sendErrorResponse(
+ int32_t sessionID,
+ const char *errorDetail,
+ int32_t cseq);
+
+ static void AppendCommonResponse(
+ AString *response, int32_t cseq, int32_t playbackSessionID = -1ll);
+
+ void scheduleReaper();
+ void scheduleKeepAlive(int32_t sessionID);
+
+ int32_t makeUniquePlaybackSessionID() const;
+
+ sp<PlaybackSession> findPlaybackSession(
+ const sp<ParsedMessage> &data, int32_t *playbackSessionID) const;
+
+ void finishStop();
+ void disconnectClientAsync();
+ void disconnectClient2();
+ void finishStopAfterDisconnectingClient();
+ void finishStop2();
+
+ void finishPlay();
+
+ DISALLOW_EVIL_CONSTRUCTORS(WifiDisplaySource);
+};
+
+} // namespace android
+
+#endif // WIFI_DISPLAY_SOURCE_H_
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
index d1819fd..40c3911 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
@@ -426,30 +426,6 @@
uid_t uid, audio_session_t session) {
if (mix->mMixType == MIX_TYPE_PLAYERS) {
- // Loopback render mixes are created from a public API and thus restricted
- // to non sensible audio that have not opted out.
- if (is_mix_loopback_render(mix->mRouteFlags)) {
- if (!(attributes.usage == AUDIO_USAGE_UNKNOWN ||
- attributes.usage == AUDIO_USAGE_MEDIA ||
- attributes.usage == AUDIO_USAGE_GAME ||
- attributes.usage == AUDIO_USAGE_VOICE_COMMUNICATION)) {
- return false;
- }
- auto hasFlag = [](auto flags, auto flag) { return (flags & flag) == flag; };
- if (hasFlag(attributes.flags, AUDIO_FLAG_NO_SYSTEM_CAPTURE)) {
- return false;
- }
-
- if (attributes.usage == AUDIO_USAGE_VOICE_COMMUNICATION) {
- if (!mix->mVoiceCommunicationCaptureAllowed) {
- return false;
- }
- } else if (!mix->mAllowPrivilegedMediaPlaybackCapture &&
- hasFlag(attributes.flags, AUDIO_FLAG_NO_MEDIA_PROJECTION)) {
- return false;
- }
- }
-
// Permit match only if requested format and mix format are PCM and can be format
// adapted by the mixer, or are the same (compressed) format.
if (!is_mix_loopback(mix->mRouteFlags) &&
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index 2a4c069..1951045 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -636,7 +636,8 @@
// type is API_INPUT_MIX_EXT_POLICY_REROUTE and by AudioService if a media projection
// is used and input type is API_INPUT_MIX_PUBLIC_CAPTURE_PLAYBACK
// - ECHO_REFERENCE source is controlled by captureAudioOutputAllowed()
- if (!(recordingAllowed(attributionSource, inputSource)
+ if (!isAudioServerOrMediaServerUid(attributionSource.uid)
+ && !(recordingAllowed(attributionSource, inputSource)
|| inputSource == AUDIO_SOURCE_FM_TUNER
|| inputSource == AUDIO_SOURCE_REMOTE_SUBMIX
|| inputSource == AUDIO_SOURCE_ECHO_REFERENCE)) {
@@ -719,7 +720,7 @@
// FIXME: use the same permission as for remote submix for now.
FALLTHROUGH_INTENDED;
case AudioPolicyInterface::API_INPUT_MIX_CAPTURE:
- if (!canCaptureOutput) {
+ if (!isAudioServerOrMediaServerUid(attributionSource.uid) && !canCaptureOutput) {
ALOGE("%s permission denied: capture not allowed", __func__);
status = PERMISSION_DENIED;
}
@@ -807,7 +808,8 @@
msg << "Audio recording on session " << client->session;
// check calling permissions
- if (!(startRecording(client->attributionSource, String16(msg.str().c_str()),
+ if (!isAudioServerOrMediaServerUid(client->attributionSource.uid)
+ && !(startRecording(client->attributionSource, String16(msg.str().c_str()),
client->attributes.source)
|| client->attributes.source == AUDIO_SOURCE_FM_TUNER
|| client->attributes.source == AUDIO_SOURCE_REMOTE_SUBMIX