From f151cafa599925298484b8879f690d78288dbe03 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Wed, 9 Jan 2019 01:13:10 +0200 Subject: [PATCH] Revert "stagefright: remove Miracast sender code" This reverts commit d0a98fa05f0f6719b93d000c4638230af06e0b99. Change-Id: I0554b92c290c1ebbd1a40fc2edb43573a97d4f6a --- include/media/IHDCP.h | 1 + media/libmedia/Android.bp | 1 + media/libmedia/IHDCP.cpp | 359 ++++ media/libmedia/IMediaPlayerService.cpp | 17 + media/libmedia/include/media/IHDCP.h | 120 ++ .../include/media/IMediaPlayerService.h | 2 + media/libmediaplayerservice/Android.bp | 4 + media/libmediaplayerservice/HDCP.cpp | 175 ++ media/libmediaplayerservice/HDCP.h | 66 + .../MediaPlayerService.cpp | 17 +- .../MediaPlayerService.h | 1 + media/libmediaplayerservice/RemoteDisplay.cpp | 66 + media/libmediaplayerservice/RemoteDisplay.h | 59 + media/libstagefright/Android.bp | 1 + media/libstagefright/wifi-display/Android.bp | 51 + .../wifi-display/MediaSender.cpp | 519 +++++ .../libstagefright/wifi-display/MediaSender.h | 132 ++ .../wifi-display/Parameters.cpp | 92 + .../libstagefright/wifi-display/Parameters.h | 41 + .../wifi-display/VideoFormats.cpp | 550 ++++++ .../wifi-display/VideoFormats.h | 125 ++ .../libstagefright/wifi-display/rtp/RTPBase.h | 49 + .../wifi-display/rtp/RTPSender.cpp | 808 ++++++++ .../wifi-display/rtp/RTPSender.h | 119 ++ .../wifi-display/source/Converter.cpp | 821 ++++++++ .../wifi-display/source/Converter.h | 157 ++ .../wifi-display/source/MediaPuller.cpp | 224 +++ .../wifi-display/source/MediaPuller.h | 68 + .../wifi-display/source/PlaybackSession.cpp | 1112 +++++++++++ .../wifi-display/source/PlaybackSession.h | 176 ++ .../wifi-display/source/RepeaterSource.cpp | 219 +++ .../wifi-display/source/RepeaterSource.h | 67 + .../wifi-display/source/TSPacketizer.cpp | 1055 ++++++++++ .../wifi-display/source/TSPacketizer.h | 94 + .../wifi-display/source/WifiDisplaySource.cpp | 1737 +++++++++++++++++ .../wifi-display/source/WifiDisplaySource.h | 278 +++ 36 files changed, 9378 insertions(+), 5 deletions(-) create mode 120000 include/media/IHDCP.h create mode 100644 media/libmedia/IHDCP.cpp create mode 100644 media/libmedia/include/media/IHDCP.h create mode 100644 media/libmediaplayerservice/HDCP.cpp create mode 100644 media/libmediaplayerservice/HDCP.h create mode 100644 media/libmediaplayerservice/RemoteDisplay.cpp create mode 100644 media/libmediaplayerservice/RemoteDisplay.h create mode 100644 media/libstagefright/wifi-display/Android.bp create mode 100644 media/libstagefright/wifi-display/MediaSender.cpp create mode 100644 media/libstagefright/wifi-display/MediaSender.h create mode 100644 media/libstagefright/wifi-display/Parameters.cpp create mode 100644 media/libstagefright/wifi-display/Parameters.h create mode 100644 media/libstagefright/wifi-display/VideoFormats.cpp create mode 100644 media/libstagefright/wifi-display/VideoFormats.h create mode 100644 media/libstagefright/wifi-display/rtp/RTPBase.h create mode 100644 media/libstagefright/wifi-display/rtp/RTPSender.cpp create mode 100644 media/libstagefright/wifi-display/rtp/RTPSender.h create mode 100644 media/libstagefright/wifi-display/source/Converter.cpp create mode 100644 media/libstagefright/wifi-display/source/Converter.h create mode 100644 media/libstagefright/wifi-display/source/MediaPuller.cpp create mode 100644 media/libstagefright/wifi-display/source/MediaPuller.h create mode 100644 media/libstagefright/wifi-display/source/PlaybackSession.cpp create mode 100644 media/libstagefright/wifi-display/source/PlaybackSession.h create mode 100644 media/libstagefright/wifi-display/source/RepeaterSource.cpp create mode 100644 media/libstagefright/wifi-display/source/RepeaterSource.h create mode 100644 media/libstagefright/wifi-display/source/TSPacketizer.cpp create mode 100644 media/libstagefright/wifi-display/source/TSPacketizer.h create mode 100644 media/libstagefright/wifi-display/source/WifiDisplaySource.cpp create mode 100644 media/libstagefright/wifi-display/source/WifiDisplaySource.h diff --git a/include/media/IHDCP.h b/include/media/IHDCP.h new file mode 120000 index 0000000000..9d4568eafd --- /dev/null +++ b/include/media/IHDCP.h @@ -0,0 +1 @@ +../../media/libmedia/include/media/IHDCP.h \ No newline at end of file diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp index 1a1d6b3f12..378bb6043b 100644 --- a/media/libmedia/Android.bp +++ b/media/libmedia/Android.bp @@ -154,6 +154,7 @@ cc_library { srcs: [ ":mediaupdateservice_aidl", "IDataSource.cpp", + "IHDCP.cpp", "BufferingSettings.cpp", "mediaplayer.cpp", "IMediaHTTPConnection.cpp", diff --git a/media/libmedia/IHDCP.cpp b/media/libmedia/IHDCP.cpp new file mode 100644 index 0000000000..a46017ff9d --- /dev/null +++ b/media/libmedia/IHDCP.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "IHDCP" +#include + +#include +#include +#include +#include + +namespace android { + +enum { + OBSERVER_NOTIFY = IBinder::FIRST_CALL_TRANSACTION, + HDCP_SET_OBSERVER, + HDCP_INIT_ASYNC, + HDCP_SHUTDOWN_ASYNC, + HDCP_GET_CAPS, + HDCP_ENCRYPT, + HDCP_ENCRYPT_NATIVE, + HDCP_DECRYPT, +}; + +struct BpHDCPObserver : public BpInterface { + explicit BpHDCPObserver(const sp &impl) + : BpInterface(impl) { + } + + virtual void notify( + int msg, int ext1, int ext2, const Parcel *obj) { + Parcel data, reply; + data.writeInterfaceToken(IHDCPObserver::getInterfaceDescriptor()); + data.writeInt32(msg); + data.writeInt32(ext1); + data.writeInt32(ext2); + if (obj && obj->dataSize() > 0) { + data.appendFrom(const_cast(obj), 0, obj->dataSize()); + } + remote()->transact(OBSERVER_NOTIFY, data, &reply, IBinder::FLAG_ONEWAY); + } +}; + +IMPLEMENT_META_INTERFACE(HDCPObserver, "android.hardware.IHDCPObserver"); + +struct BpHDCP : public BpInterface { + explicit BpHDCP(const sp &impl) + : BpInterface(impl) { + } + + virtual status_t setObserver(const sp &observer) { + Parcel data, reply; + data.writeInterfaceToken(IHDCP::getInterfaceDescriptor()); + data.writeStrongBinder(IInterface::asBinder(observer)); + remote()->transact(HDCP_SET_OBSERVER, data, &reply); + return reply.readInt32(); + } + + virtual status_t initAsync(const char *host, unsigned port) { + Parcel data, reply; + data.writeInterfaceToken(IHDCP::getInterfaceDescriptor()); + data.writeCString(host); + data.writeInt32(port); + remote()->transact(HDCP_INIT_ASYNC, data, &reply); + return reply.readInt32(); + } + + virtual status_t shutdownAsync() { + Parcel data, reply; + data.writeInterfaceToken(IHDCP::getInterfaceDescriptor()); + remote()->transact(HDCP_SHUTDOWN_ASYNC, data, &reply); + return reply.readInt32(); + } + + virtual uint32_t getCaps() { + Parcel data, reply; + data.writeInterfaceToken(IHDCP::getInterfaceDescriptor()); + remote()->transact(HDCP_GET_CAPS, data, &reply); + return reply.readInt32(); + } + + virtual status_t encrypt( + const void *inData, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData) { + Parcel data, reply; + data.writeInterfaceToken(IHDCP::getInterfaceDescriptor()); + data.writeInt32(size); + data.write(inData, size); + data.writeInt32(streamCTR); + remote()->transact(HDCP_ENCRYPT, data, &reply); + + status_t err = reply.readInt32(); + + if (err != OK) { + *outInputCTR = 0; + + return err; + } + + *outInputCTR = reply.readInt64(); + reply.read(outData, size); + + return err; + } + + virtual status_t encryptNative( + const sp &graphicBuffer, + size_t offset, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData) { + Parcel data, reply; + data.writeInterfaceToken(IHDCP::getInterfaceDescriptor()); + data.write(*graphicBuffer); + data.writeInt32(offset); + data.writeInt32(size); + data.writeInt32(streamCTR); + remote()->transact(HDCP_ENCRYPT_NATIVE, data, &reply); + + status_t err = reply.readInt32(); + + if (err != OK) { + *outInputCTR = 0; + return err; + } + + *outInputCTR = reply.readInt64(); + reply.read(outData, size); + + return err; + } + + virtual status_t decrypt( + const void *inData, size_t size, + uint32_t streamCTR, uint64_t inputCTR, + void *outData) { + Parcel data, reply; + data.writeInterfaceToken(IHDCP::getInterfaceDescriptor()); + data.writeInt32(size); + data.write(inData, size); + data.writeInt32(streamCTR); + data.writeInt64(inputCTR); + remote()->transact(HDCP_DECRYPT, data, &reply); + + status_t err = reply.readInt32(); + + if (err != OK) { + return err; + } + + reply.read(outData, size); + + return err; + } +}; + +IMPLEMENT_META_INTERFACE(HDCP, "android.hardware.IHDCP"); + +status_t BnHDCPObserver::onTransact( + uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) { + switch (code) { + case OBSERVER_NOTIFY: + { + CHECK_INTERFACE(IHDCPObserver, data, reply); + + int msg = data.readInt32(); + int ext1 = data.readInt32(); + int ext2 = data.readInt32(); + + Parcel obj; + if (data.dataAvail() > 0) { + obj.appendFrom( + const_cast(&data), + data.dataPosition(), + data.dataAvail()); + } + + notify(msg, ext1, ext2, &obj); + + return OK; + } + + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +status_t BnHDCP::onTransact( + uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) { + switch (code) { + case HDCP_SET_OBSERVER: + { + CHECK_INTERFACE(IHDCP, data, reply); + + sp observer = + interface_cast(data.readStrongBinder()); + + reply->writeInt32(setObserver(observer)); + return OK; + } + + case HDCP_INIT_ASYNC: + { + CHECK_INTERFACE(IHDCP, data, reply); + + const char *host = data.readCString(); + unsigned port = data.readInt32(); + + reply->writeInt32(initAsync(host, port)); + return OK; + } + + case HDCP_SHUTDOWN_ASYNC: + { + CHECK_INTERFACE(IHDCP, data, reply); + + reply->writeInt32(shutdownAsync()); + return OK; + } + + case HDCP_GET_CAPS: + { + CHECK_INTERFACE(IHDCP, data, reply); + + reply->writeInt32(getCaps()); + return OK; + } + + case HDCP_ENCRYPT: + { + CHECK_INTERFACE(IHDCP, data, reply); + + size_t size = data.readInt32(); + void *inData = NULL; + // watch out for overflow + if (size <= SIZE_MAX / 2) { + inData = malloc(2 * size); + } + if (inData == NULL) { + reply->writeInt32(ERROR_OUT_OF_RANGE); + return OK; + } + + void *outData = (uint8_t *)inData + size; + + status_t err = data.read(inData, size); + if (err != OK) { + free(inData); + reply->writeInt32(err); + return OK; + } + + uint32_t streamCTR = data.readInt32(); + uint64_t inputCTR; + err = encrypt(inData, size, streamCTR, &inputCTR, outData); + + reply->writeInt32(err); + + if (err == OK) { + reply->writeInt64(inputCTR); + reply->write(outData, size); + } + + free(inData); + inData = outData = NULL; + + return OK; + } + + case HDCP_ENCRYPT_NATIVE: + { + CHECK_INTERFACE(IHDCP, data, reply); + + sp graphicBuffer = new GraphicBuffer(); + data.read(*graphicBuffer); + size_t offset = data.readInt32(); + size_t size = data.readInt32(); + uint32_t streamCTR = data.readInt32(); + void *outData = NULL; + uint64_t inputCTR; + + status_t err = ERROR_OUT_OF_RANGE; + + outData = malloc(size); + + if (outData != NULL) { + err = encryptNative(graphicBuffer, offset, size, + streamCTR, &inputCTR, outData); + } + + reply->writeInt32(err); + + if (err == OK) { + reply->writeInt64(inputCTR); + reply->write(outData, size); + } + + free(outData); + outData = NULL; + + return OK; + } + + case HDCP_DECRYPT: + { + CHECK_INTERFACE(IHDCP, data, reply); + + size_t size = data.readInt32(); + size_t bufSize = 2 * size; + + // watch out for overflow + void *inData = NULL; + if (bufSize > size) { + inData = malloc(bufSize); + } + + if (inData == NULL) { + reply->writeInt32(ERROR_OUT_OF_RANGE); + return OK; + } + + void *outData = (uint8_t *)inData + size; + + data.read(inData, size); + + uint32_t streamCTR = data.readInt32(); + uint64_t inputCTR = data.readInt64(); + status_t err = decrypt(inData, size, streamCTR, inputCTR, outData); + + reply->writeInt32(err); + + if (err == OK) { + reply->write(outData, size); + } + + free(inData); + inData = outData = NULL; + + return OK; + } + + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +} // namespace android diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp index aca7ad9979..e49dd32d1f 100644 --- a/media/libmedia/IMediaPlayerService.cpp +++ b/media/libmedia/IMediaPlayerService.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,7 @@ enum { CREATE = IBinder::FIRST_CALL_TRANSACTION, CREATE_MEDIA_RECORDER, CREATE_METADATA_RETRIEVER, + MAKE_HDCP, ADD_BATTERY_DATA, PULL_BATTERY_DATA, LISTEN_FOR_REMOTE_DISPLAY, @@ -80,6 +82,14 @@ class BpMediaPlayerService: public BpInterface return interface_cast(reply.readStrongBinder()); } + virtual sp makeHDCP(bool createEncryptionModule) { + Parcel data, reply; + data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); + data.writeInt32(createEncryptionModule); + remote()->transact(MAKE_HDCP, data, &reply); + return interface_cast(reply.readStrongBinder()); + } + virtual void addBatteryData(uint32_t params) { Parcel data, reply; data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); @@ -143,6 +153,13 @@ status_t BnMediaPlayerService::onTransact( reply->writeStrongBinder(IInterface::asBinder(retriever)); return NO_ERROR; } break; + case MAKE_HDCP: { + CHECK_INTERFACE(IMediaPlayerService, data, reply); + bool createEncryptionModule = data.readInt32(); + sp hdcp = makeHDCP(createEncryptionModule); + reply->writeStrongBinder(IInterface::asBinder(hdcp)); + return NO_ERROR; + } break; case ADD_BATTERY_DATA: { CHECK_INTERFACE(IMediaPlayerService, data, reply); uint32_t params = data.readInt32(); diff --git a/media/libmedia/include/media/IHDCP.h b/media/libmedia/include/media/IHDCP.h new file mode 100644 index 0000000000..352561ecbb --- /dev/null +++ b/media/libmedia/include/media/IHDCP.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +namespace android { + +struct IHDCPObserver : public IInterface { + DECLARE_META_INTERFACE(HDCPObserver); + + virtual void notify( + int msg, int ext1, int ext2, const Parcel *obj) = 0; + +private: + DISALLOW_EVIL_CONSTRUCTORS(IHDCPObserver); +}; + +struct IHDCP : public IInterface { + DECLARE_META_INTERFACE(HDCP); + + // Called to specify the observer that receives asynchronous notifications + // from the HDCP implementation to signal completion/failure of asynchronous + // operations (such as initialization) or out of band events. + virtual status_t setObserver(const sp &observer) = 0; + + // Request to setup an HDCP session with the specified host listening + // on the specified port. + virtual status_t initAsync(const char *host, unsigned port) = 0; + + // Request to shutdown the active HDCP session. + virtual status_t shutdownAsync() = 0; + + // Returns the capability bitmask of this HDCP session. + // Possible return values (please refer to HDCAPAPI.h): + // HDCP_CAPS_ENCRYPT: mandatory, meaning the HDCP module can encrypt + // from an input byte-array buffer to an output byte-array buffer + // HDCP_CAPS_ENCRYPT_NATIVE: the HDCP module supports encryption from + // a native buffer to an output byte-array buffer. The format of the + // input native buffer is specific to vendor's encoder implementation. + // It is the same format as that used by the encoder when + // "storeMetaDataInBuffers" extension is enabled on its output port. + virtual uint32_t getCaps() = 0; + + // ENCRYPTION only: + // Encrypt data according to the HDCP spec. "size" bytes of data are + // available at "inData" (virtual address), "size" may not be a multiple + // of 128 bits (16 bytes). An equal number of encrypted bytes should be + // written to the buffer at "outData" (virtual address). + // This operation is to be synchronous, i.e. this call does not return + // until outData contains size bytes of encrypted data. + // streamCTR will be assigned by the caller (to 0 for the first PES stream, + // 1 for the second and so on) + // inputCTR _will_be_maintained_by_the_callee_ for each PES stream. + virtual status_t encrypt( + const void *inData, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData) = 0; + + // Encrypt data according to the HDCP spec. "size" bytes of data starting + // at location "offset" are available in "buffer" (buffer handle). "size" + // may not be a multiple of 128 bits (16 bytes). An equal number of + // encrypted bytes should be written to the buffer at "outData" (virtual + // address). This operation is to be synchronous, i.e. this call does not + // return until outData contains size bytes of encrypted data. + // streamCTR will be assigned by the caller (to 0 for the first PES stream, + // 1 for the second and so on) + // inputCTR _will_be_maintained_by_the_callee_ for each PES stream. + virtual status_t encryptNative( + const sp &graphicBuffer, + size_t offset, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData) = 0; + + // DECRYPTION only: + // Decrypt data according to the HDCP spec. + // "size" bytes of encrypted data are available at "inData" + // (virtual address), "size" may not be a multiple of 128 bits (16 bytes). + // An equal number of decrypted bytes should be written to the buffer + // at "outData" (virtual address). + // This operation is to be synchronous, i.e. this call does not return + // until outData contains size bytes of decrypted data. + // Both streamCTR and inputCTR will be provided by the caller. + virtual status_t decrypt( + const void *inData, size_t size, + uint32_t streamCTR, uint64_t inputCTR, + void *outData) = 0; + +private: + DISALLOW_EVIL_CONSTRUCTORS(IHDCP); +}; + +struct BnHDCPObserver : public BnInterface { + virtual status_t onTransact( + uint32_t code, const Parcel &data, Parcel *reply, + uint32_t flags = 0); +}; + +struct BnHDCP : public BnInterface { + virtual status_t onTransact( + uint32_t code, const Parcel &data, Parcel *reply, + uint32_t flags = 0); +}; + +} // namespace android + + diff --git a/media/libmedia/include/media/IMediaPlayerService.h b/media/libmedia/include/media/IMediaPlayerService.h index 217de14ee8..0deaa85220 100644 --- a/media/libmedia/include/media/IMediaPlayerService.h +++ b/media/libmedia/include/media/IMediaPlayerService.h @@ -31,6 +31,7 @@ namespace android { +struct IHDCP; class IMediaCodecList; struct IMediaHTTPService; class IMediaRecorder; @@ -48,6 +49,7 @@ class IMediaPlayerService: public IInterface virtual sp createMetadataRetriever() = 0; virtual sp create(const sp& client, audio_session_t audioSessionId = AUDIO_SESSION_ALLOCATE) = 0; + virtual sp makeHDCP(bool createEncryptionModule) = 0; virtual sp getCodecList() const = 0; // Connects to a remote display. diff --git a/media/libmediaplayerservice/Android.bp b/media/libmediaplayerservice/Android.bp index a37973b8ed..e64032b7c0 100644 --- a/media/libmediaplayerservice/Android.bp +++ b/media/libmediaplayerservice/Android.bp @@ -2,10 +2,12 @@ cc_library_shared { srcs: [ "ActivityManager.cpp", + "HDCP.cpp", "MediaPlayerFactory.cpp", "MediaPlayerService.cpp", "MediaRecorderClient.cpp", "MetadataRetrieverClient.cpp", + "RemoteDisplay.cpp", "StagefrightRecorder.cpp", "TestPlayerStub.cpp", ], @@ -31,6 +33,7 @@ cc_library_shared { "libmemunreachable", "libpowermanager", "libstagefright", + "libstagefright_wfd", "libstagefright_foundation", "libstagefright_httplive", "libutils", @@ -51,6 +54,7 @@ cc_library_shared { include_dirs: [ "frameworks/av/media/libstagefright/rtsp", "frameworks/av/media/libstagefright/webm", + "frameworks/av/media/libstagefright/wifi-display", ], local_include_dirs: ["include"], diff --git a/media/libmediaplayerservice/HDCP.cpp b/media/libmediaplayerservice/HDCP.cpp new file mode 100644 index 0000000000..afe39367fb --- /dev/null +++ b/media/libmediaplayerservice/HDCP.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "HDCP" +#include + +#include "HDCP.h" + +#include + +#include + +namespace android { + +HDCP::HDCP(bool createEncryptionModule) + : mIsEncryptionModule(createEncryptionModule), + mLibHandle(NULL), + mHDCPModule(NULL) { + mLibHandle = dlopen("libstagefright_hdcp.so", RTLD_NOW); + + if (mLibHandle == NULL) { + ALOGE("Unable to locate libstagefright_hdcp.so"); + return; + } + + typedef HDCPModule *(*CreateHDCPModuleFunc)( + void *, HDCPModule::ObserverFunc); + + CreateHDCPModuleFunc createHDCPModule = + mIsEncryptionModule + ? (CreateHDCPModuleFunc)dlsym(mLibHandle, "createHDCPModule") + : (CreateHDCPModuleFunc)dlsym( + mLibHandle, "createHDCPModuleForDecryption"); + + if (createHDCPModule == NULL) { + ALOGE("Unable to find symbol 'createHDCPModule'."); + } else if ((mHDCPModule = createHDCPModule( + this, &HDCP::ObserveWrapper)) == NULL) { + ALOGE("createHDCPModule failed."); + } +} + +HDCP::~HDCP() { + Mutex::Autolock autoLock(mLock); + + if (mHDCPModule != NULL) { + delete mHDCPModule; + mHDCPModule = NULL; + } + + if (mLibHandle != NULL) { + dlclose(mLibHandle); + mLibHandle = NULL; + } +} + +status_t HDCP::setObserver(const sp &observer) { + Mutex::Autolock autoLock(mLock); + + if (mHDCPModule == NULL) { + return NO_INIT; + } + + mObserver = observer; + + return OK; +} + +status_t HDCP::initAsync(const char *host, unsigned port) { + Mutex::Autolock autoLock(mLock); + + if (mHDCPModule == NULL) { + return NO_INIT; + } + + return mHDCPModule->initAsync(host, port); +} + +status_t HDCP::shutdownAsync() { + Mutex::Autolock autoLock(mLock); + + if (mHDCPModule == NULL) { + return NO_INIT; + } + + return mHDCPModule->shutdownAsync(); +} + +uint32_t HDCP::getCaps() { + Mutex::Autolock autoLock(mLock); + + if (mHDCPModule == NULL) { + return NO_INIT; + } + + return mHDCPModule->getCaps(); +} + +status_t HDCP::encrypt( + const void *inData, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData) { + Mutex::Autolock autoLock(mLock); + + CHECK(mIsEncryptionModule); + + if (mHDCPModule == NULL) { + *outInputCTR = 0; + + return NO_INIT; + } + + return mHDCPModule->encrypt(inData, size, streamCTR, outInputCTR, outData); +} + +status_t HDCP::encryptNative( + const sp &graphicBuffer, + size_t offset, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData) { + Mutex::Autolock autoLock(mLock); + + CHECK(mIsEncryptionModule); + + if (mHDCPModule == NULL) { + *outInputCTR = 0; + + return NO_INIT; + } + + return mHDCPModule->encryptNative(graphicBuffer->handle, + offset, size, streamCTR, outInputCTR, outData); +} + +status_t HDCP::decrypt( + const void *inData, size_t size, + uint32_t streamCTR, uint64_t outInputCTR, void *outData) { + Mutex::Autolock autoLock(mLock); + + CHECK(!mIsEncryptionModule); + + if (mHDCPModule == NULL) { + return NO_INIT; + } + + return mHDCPModule->decrypt(inData, size, streamCTR, outInputCTR, outData); +} + +// static +void HDCP::ObserveWrapper(void *me, int msg, int ext1, int ext2) { + static_cast(me)->observe(msg, ext1, ext2); +} + +void HDCP::observe(int msg, int ext1, int ext2) { + Mutex::Autolock autoLock(mLock); + + if (mObserver != NULL) { + mObserver->notify(msg, ext1, ext2, NULL /* obj */); + } +} + +} // namespace android + diff --git a/media/libmediaplayerservice/HDCP.h b/media/libmediaplayerservice/HDCP.h new file mode 100644 index 0000000000..83c61b56c0 --- /dev/null +++ b/media/libmediaplayerservice/HDCP.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HDCP_H_ + +#define HDCP_H_ + +#include +#include + +namespace android { + +struct HDCP : public BnHDCP { + explicit HDCP(bool createEncryptionModule); + virtual ~HDCP(); + + virtual status_t setObserver(const sp &observer); + virtual status_t initAsync(const char *host, unsigned port); + virtual status_t shutdownAsync(); + virtual uint32_t getCaps(); + + virtual status_t encrypt( + const void *inData, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData); + + virtual status_t encryptNative( + const sp &graphicBuffer, + size_t offset, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData); + + virtual status_t decrypt( + const void *inData, size_t size, + uint32_t streamCTR, uint64_t outInputCTR, void *outData); + +private: + Mutex mLock; + + bool mIsEncryptionModule; + + void *mLibHandle; + HDCPModule *mHDCPModule; + sp mObserver; + + static void ObserveWrapper(void *me, int msg, int ext1, int ext2); + void observe(int msg, int ext1, int ext2); + + DISALLOW_EVIL_CONSTRUCTORS(HDCP); +}; + +} // namespace android + +#endif // HDCP_H_ + diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 9bcfc8377a..ebdf82d71b 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -78,7 +78,9 @@ #include "TestPlayerStub.h" #include "nuplayer/NuPlayerDriver.h" +#include "HDCP.h" #include "HTTPBase.h" +#include "RemoteDisplay.h" static const int kDumpLockRetries = 50; static const int kDumpLockSleepUs = 20000; @@ -335,13 +337,18 @@ sp MediaPlayerService::getCodecList() const { return MediaCodecList::getLocalInstance(); } +sp MediaPlayerService::makeHDCP(bool createEncryptionModule) { + return new HDCP(createEncryptionModule); +} + sp MediaPlayerService::listenForRemoteDisplay( - const String16 &/*opPackageName*/, - const sp& /*client*/, - const String8& /*iface*/) { - ALOGE("listenForRemoteDisplay is no longer supported!"); + const String16 &opPackageName, + const sp& client, const String8& iface) { + if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) { + return NULL; + } - return NULL; + return new RemoteDisplay(opPackageName, client, iface.string()); } status_t MediaPlayerService::AudioOutput::dump(int fd, const Vector& args) const diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index bfb7cc2574..a1bcc24357 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -239,6 +239,7 @@ class MediaPlayerService : public BnMediaPlayerService audio_session_t audioSessionId); virtual sp getCodecList() const; + virtual sp makeHDCP(bool createEncryptionModule); virtual sp listenForRemoteDisplay(const String16 &opPackageName, const sp& client, const String8& iface); diff --git a/media/libmediaplayerservice/RemoteDisplay.cpp b/media/libmediaplayerservice/RemoteDisplay.cpp new file mode 100644 index 0000000000..0eb4b5dad0 --- /dev/null +++ b/media/libmediaplayerservice/RemoteDisplay.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "RemoteDisplay.h" + +#include "source/WifiDisplaySource.h" + +#include +#include +#include +#include + +namespace android { + +RemoteDisplay::RemoteDisplay( + const String16 &opPackageName, + const sp &client, + const char *iface) + : mLooper(new ALooper), + mNetSession(new ANetworkSession) { + mLooper->setName("wfd_looper"); + + mSource = new WifiDisplaySource(opPackageName, mNetSession, client); + mLooper->registerHandler(mSource); + + mNetSession->start(); + mLooper->start(); + + mSource->start(iface); +} + +RemoteDisplay::~RemoteDisplay() { +} + +status_t RemoteDisplay::pause() { + return mSource->pause(); +} + +status_t RemoteDisplay::resume() { + return mSource->resume(); +} + +status_t RemoteDisplay::dispose() { + mSource->stop(); + mSource.clear(); + + mLooper->stop(); + mNetSession->stop(); + + return OK; +} + +} // namespace android diff --git a/media/libmediaplayerservice/RemoteDisplay.h b/media/libmediaplayerservice/RemoteDisplay.h new file mode 100644 index 0000000000..d4573e9a39 --- /dev/null +++ b/media/libmediaplayerservice/RemoteDisplay.h @@ -0,0 +1,59 @@ +/* + * Copyright 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef REMOTE_DISPLAY_H_ + +#define REMOTE_DISPLAY_H_ + +#include +#include +#include +#include +#include + +namespace android { + +struct ALooper; +struct ANetworkSession; +class IRemoteDisplayClient; +struct WifiDisplaySource; + +struct RemoteDisplay : public BnRemoteDisplay { + RemoteDisplay( + const String16 &opPackageName, + const sp &client, + const char *iface); + + virtual status_t pause(); + virtual status_t resume(); + virtual status_t dispose(); + +protected: + virtual ~RemoteDisplay(); + +private: + sp mNetLooper; + sp mLooper; + sp mNetSession; + sp mSource; + + DISALLOW_EVIL_CONSTRUCTORS(RemoteDisplay); +}; + +} // namespace android + +#endif // REMOTE_DISPLAY_H_ + diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp index 884ddfd9bf..924d460845 100644 --- a/media/libstagefright/Android.bp +++ b/media/libstagefright/Android.bp @@ -327,5 +327,6 @@ subdirs = [ "tests", "timedtext", "webm", + "wifi-display", "xmlparser", ] diff --git a/media/libstagefright/wifi-display/Android.bp b/media/libstagefright/wifi-display/Android.bp new file mode 100644 index 0000000000..fb08c5b072 --- /dev/null +++ b/media/libstagefright/wifi-display/Android.bp @@ -0,0 +1,51 @@ +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/libstagefright/mpeg2ts", + ], + + shared_libs: [ + "libbinder", + "libcutils", + "liblog", + "libmedia", + "libstagefright", + "libstagefright_foundation", + "libui", + "libgui", + "libutils", + ], + + 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 0000000000..cc412f5f46 --- /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 + +#include "MediaSender.h" + +#include "rtp/RTPSender.h" +#include "source/TSPacketizer.h" + +#include "include/avc_utils.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +MediaSender::MediaSender( + const sp &netSession, + const sp ¬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 &hdcp) { + if (mMode != MODE_UNDEFINED) { + return INVALID_OPERATION; + } + + mHDCP = hdcp; + + return OK; +} + +ssize_t MediaSender::addTrack(const sp &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 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 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 &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 &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 accessUnit = *info->mAccessUnits.begin(); + info->mAccessUnits.erase(info->mAccessUnits.begin()); + + sp 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 &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 &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 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 notify = mNotify->dup(); + notify->setInt32("what", kWhatInitDone); + notify->setInt32("err", err); + notify->post(); +} + +void MediaSender::notifyError(status_t err) { + sp notify = mNotify->dup(); + notify->setInt32("what", kWhatError); + notify->setInt32("err", err); + notify->post(); +} + +void MediaSender::notifyNetworkStall(size_t numBytesQueued) { + sp notify = mNotify->dup(); + notify->setInt32("what", kWhatNetworkStall); + notify->setSize("numBytesQueued", numBytesQueued); + notify->post(); +} + +status_t MediaSender::packetizeAccessUnit( + size_t trackIndex, + sp accessUnit, + sp *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); + + 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 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 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 0000000000..04538ea1f6 --- /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 +#include +#include +#include + +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 &netSession, + const sp ¬ify); + + status_t setHDCP(const sp &hdcp); + + enum FlagBits { + FLAG_MANUALLY_PREPEND_SPS_PPS = 1, + }; + ssize_t addTrack(const sp &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 &accessUnit); + +protected: + virtual void onMessageReceived(const sp &msg); + virtual ~MediaSender(); + +private: + enum { + kWhatSenderNotify, + }; + + enum Mode { + MODE_UNDEFINED, + MODE_TRANSPORT_STREAM, + MODE_ELEMENTARY_STREAMS, + }; + + struct TrackInfo { + sp mFormat; + uint32_t mFlags; + sp mSender; + List > mAccessUnits; + ssize_t mPacketizerTrackIndex; + bool mIsAudio; + }; + + sp mNetSession; + sp mNotify; + + sp mHDCP; + + Mode mMode; + int32_t mGeneration; + + Vector mTrackInfos; + + sp mTSPacketizer; + sp mTSSender; + int64_t mPrevTimeUs; + + size_t mInitDoneCount; + + FILE *mLogFile; + + void onSenderNotify(const sp &msg); + + void notifyInitDone(status_t err); + void notifyError(status_t err); + void notifyNetworkStall(size_t numBytesQueued); + + status_t packetizeAccessUnit( + size_t trackIndex, + sp accessUnit, + sp *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 0000000000..d2a61ea464 --- /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 + +namespace android { + +// static +sp Parameters::Parse(const char *data, size_t size) { + sp 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 0000000000..a5e787e234 --- /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 +#include +#include +#include + +namespace android { + +struct Parameters : public RefBase { + static sp Parse(const char *data, size_t size); + + bool findParameter(const char *name, AString *value) const; + +protected: + virtual ~Parameters(); + +private: + KeyedVector 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 0000000000..dbc511caa7 --- /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 + +#include "VideoFormats.h" + +#include + +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 0000000000..fd38fd192e --- /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 + +#include + +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 0000000000..194f1ee13b --- /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 0000000000..ca9fdd2bd7 --- /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 + +#include "RTPSender.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "include/avc_utils.h" + +namespace android { + +RTPSender::RTPSender( + const sp &netSession, + const sp ¬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 rtpNotify = new AMessage(kWhatRTPNotify, this); + + sp 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 &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 &packet, uint8_t packetType) { + CHECK_LE(packet->size(), kMaxUDPPacketSize - 12); + + int64_t timeUs; + CHECK(packet->meta()->findInt64("timeUs", &timeUs)); + + sp 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 &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 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 &accessUnit, uint8_t packetType) { + int64_t timeUs; + CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs)); + + uint32_t rtpTime = (timeUs * 9 / 100ll); + + List > packets; + + sp 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 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 &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 &msg) { + switch (msg->what()) { + case kWhatRTPNotify: + case kWhatRTCPNotify: + onNetNotify(msg->what() == kWhatRTPNotify, msg); + break; + + default: + TRESPASS(); + } +} + +void RTPSender::onNetNotify(bool isRTP, const sp &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 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 &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 >::iterator it = mHistory.begin(); + bool foundSeqNo = false; + while (it != mHistory.end()) { + const sp &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 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 notify = mNotify->dup(); + notify->setInt32("what", kWhatInitDone); + notify->setInt32("err", err); + notify->post(); +} + +void RTPSender::notifyError(status_t err) { + sp notify = mNotify->dup(); + notify->setInt32("what", kWhatError); + notify->setInt32("err", err); + notify->post(); +} + +void RTPSender::notifyNetworkStall(size_t numBytesQueued) { + sp 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 0000000000..bedfd01a8a --- /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 + +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 &netSession, + const sp ¬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 &buffer, + uint8_t packetType, + PacketizationMode mode); + +protected: + virtual ~RTPSender(); + virtual void onMessageReceived(const sp &msg); + +private: + enum { + kWhatRTPNotify, + kWhatRTCPNotify, + }; + + const unsigned int kMaxNumTSPacketsPerRTPPacket = (kMaxUDPPacketSize - 12) / 188; + const unsigned int kMaxHistorySize = 1024; + const unsigned int kSourceID = 0xdeadbeef; + + sp mNetSession; + sp 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 > mHistory; + size_t mHistorySize; + + static uint64_t GetNowNTP(); + + status_t queueRawPacket(const sp &tsPackets, uint8_t packetType); + status_t queueTSPackets(const sp &tsPackets, uint8_t packetType); + status_t queueAVCBuffer(const sp &accessUnit, uint8_t packetType); + + status_t sendRTPPacket( + const sp &packet, bool storeInHistory, + bool timeValid = false, int64_t timeUs = -1ll); + + void onNetNotify(bool isRTP, const sp &msg); + + status_t onRTCPData(const sp &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 0000000000..273af18d62 --- /dev/null +++ b/media/libstagefright/wifi-display/source/Converter.cpp @@ -0,0 +1,821 @@ +/* + * 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 + +#include "Converter.h" + +#include "MediaPuller.h" +#include "include/avc_utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace android { + +Converter::Converter( + const sp ¬ify, + const sp &codecLooper, + const sp &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 Converter::getGraphicBufferProducer() { + CHECK(mFlags & FLAG_USE_SURFACE_INPUT); + return mGraphicBufferProducer; +} + +size_t Converter::getInputBufferCount() const { + return mEncoderInputBuffers.size(); +} + +sp 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 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 notify = mNotify->dup(); + notify->setInt32("what", kWhatError); + notify->setInt32("err", err); + notify->post(); +} + +// static +bool Converter::IsSilence(const sp &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 &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 accessUnit; + CHECK(msg->findBuffer("accessUnit", &accessUnit)); + + accessUnit->setMediaBufferBase(NULL); + } + break; + } + + if (what == MediaPuller::kWhatEOS) { + mInputBufferQueue.push_back(NULL); + + feedEncoderInputBuffers(); + + scheduleDoMoreWork(); + } else { + CHECK_EQ(what, MediaPuller::kWhatAccessUnit); + + sp accessUnit; + CHECK(msg->findBuffer("accessUnit", &accessUnit)); + + if (mNumFramesToDrop > 0 || mEncodingSuspended) { + if (mNumFramesToDrop > 0) { + --mNumFramesToDrop; + ALOGI("dropping frame."); + } + + accessUnit->setMediaBufferBase(NULL); + 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 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 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 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 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 notify = mNotify->dup(); + notify->setInt32("what", kWhatAccessUnit); + notify->setBuffer("accessUnit", mPartialAudioAU); + notify->post(); + + mPartialAudioAU.clear(); + } + } + + while (buffer->size() > 0) { + sp 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 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 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()); + + MediaBuffer *mediaBuffer = + (MediaBuffer *)(buffer->getMediaBufferBase()); + if (mediaBuffer != NULL) { + mEncoderInputBuffers.itemAt(bufferIndex)->setMediaBufferBase( + mediaBuffer); + + buffer->setMediaBufferBase(NULL); + } + } 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 Converter::prependCSD(const sp &accessUnit) const { + CHECK(mCSD0 != NULL); + + sp 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 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 buffer; + sp 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 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)) { + buffer = prependCSD(buffer); + } + + sp 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 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 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 0000000000..ad95ab5ce9 --- /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 + +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 ¬ify, + const sp &codecLooper, + const sp &outputFormat, + uint32_t flags = 0); + + status_t init(); + + sp getGraphicBufferProducer(); + + size_t getInputBufferCount() const; + + sp getOutputFormat() const; + bool needToManuallyPrependSPSPPS() const; + + void feedAccessUnit(const sp &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 &msg); + +private: + enum { + kWhatDoMoreWork, + kWhatRequestIDRFrame, + kWhatSuspendEncoding, + kWhatShutdown, + kWhatEncoderActivity, + kWhatDropAFrame, + kWhatReleaseOutputBuffer, + }; + + sp mNotify; + sp mCodecLooper; + sp mOutputFormat; + uint32_t mFlags; + bool mIsVideo; + bool mIsH264; + bool mIsPCMAudio; + bool mNeedToManuallyPrependSPSPPS; + + sp mEncoder; + sp mEncoderActivityNotify; + + sp mGraphicBufferProducer; + + Vector > mEncoderInputBuffers; + Vector > mEncoderOutputBuffers; + + List mAvailEncoderInputIndices; + + List > mInputBufferQueue; + + sp mCSD0; + + bool mDoMoreWorkPending; + +#if ENABLE_SILENCE_DETECTION + int64_t mFirstSilentFrameUs; + bool mInSilentMode; +#endif + + sp 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 &accessUnit); + + sp prependCSD(const sp &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 0000000000..ce07a4ec4c --- /dev/null +++ b/media/libstagefright/wifi-display/source/MediaPuller.cpp @@ -0,0 +1,224 @@ +/* + * 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 + +#include "MediaPuller.h" + +#include +#include +#include +#include +#include +#include + +namespace android { + +MediaPuller::MediaPuller( + const sp &source, const sp ¬ify) + : mSource(source), + mNotify(notify), + mPullGeneration(0), + mIsAudio(false), + mPaused(false) { + sp meta = source->getFormat(); + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + mIsAudio = !strncasecmp(mime, "audio/", 6); +} + +MediaPuller::~MediaPuller() { +} + +status_t MediaPuller::postSynchronouslyAndReturnError( + const sp &msg) { + sp 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 ¬ify) { + sp 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 &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 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 response = new AMessage; + response->setInt32("err", err); + + sp replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + response->postReply(replyID); + break; + } + + case kWhatStop: + { + sp 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 notify; + CHECK(msg->findMessage("notify", ¬ify)); + notify->post(); + break; + } + + case kWhatPull: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + + if (generation != mPullGeneration) { + break; + } + + MediaBuffer *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 notify = mNotify->dup(); + notify->setInt32("what", kWhatEOS); + notify->post(); + } else { + int64_t timeUs; + CHECK(mbuf->meta_data()->findInt64(kKeyTime, &timeUs)); + + sp 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 MediaBuffer when done + // with underlying data. + accessUnit->setMediaBufferBase(mbuf); + } + + sp 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 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 0000000000..1291bb3f9e --- /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 + +namespace android { + +struct MediaSource; + +struct MediaPuller : public AHandler { + enum { + kWhatEOS, + kWhatAccessUnit + }; + + MediaPuller(const sp &source, const sp ¬ify); + + status_t start(); + void stopAsync(const sp ¬ify); + + void pause(); + void resume(); + +protected: + virtual void onMessageReceived(const sp &msg); + virtual ~MediaPuller(); + +private: + enum { + kWhatStart, + kWhatStop, + kWhatPull, + kWhatPause, + kWhatResume, + }; + + sp mSource; + sp mNotify; + int32_t mPullGeneration; + bool mIsAudio; + bool mPaused; + + status_t postSynchronouslyAndReturnError(const sp &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 0000000000..f1ecca0515 --- /dev/null +++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp @@ -0,0 +1,1112 @@ +/* + * 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 + +#include "PlaybackSession.h" + +#include "Converter.h" +#include "MediaPuller.h" +#include "RepeaterSource.h" +#include "include/avc_utils.h" +#include "WifiDisplaySource.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace android { + +struct WifiDisplaySource::PlaybackSession::Track : public AHandler { + enum { + kWhatStopped, + }; + + Track(const sp ¬ify, + const sp &pullLooper, + const sp &codecLooper, + const sp &mediaPuller, + const sp &converter); + + Track(const sp ¬ify, const sp &format); + + void setRepeaterSource(const sp &source); + + sp getFormat(); + bool isAudio() const; + + const sp &converter() const; + const sp &repeaterSource() const; + + ssize_t mediaSenderTrackIndex() const; + void setMediaSenderTrackIndex(size_t index); + + status_t start(); + void stopAsync(); + + void pause(); + void resume(); + + void queueAccessUnit(const sp &accessUnit); + sp dequeueAccessUnit(); + + bool hasOutputBuffer(int64_t *timeUs) const; + void queueOutputBuffer(const sp &accessUnit); + sp 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 &msg); + virtual ~Track(); + +private: + enum { + kWhatMediaPullerStopped, + }; + + sp mNotify; + sp mPullLooper; + sp mCodecLooper; + sp mMediaPuller; + sp mConverter; + sp mFormat; + bool mStarted; + ssize_t mMediaSenderTrackIndex; + bool mIsAudio; + List > mQueuedAccessUnits; + sp mRepeaterSource; + List > mQueuedOutputBuffers; + int64_t mLastOutputBufferQueuedTimeUs; + + static bool IsAudioFormat(const sp &format); + + DISALLOW_EVIL_CONSTRUCTORS(Track); +}; + +WifiDisplaySource::PlaybackSession::Track::Track( + const sp ¬ify, + const sp &pullLooper, + const sp &codecLooper, + const sp &mediaPuller, + const sp &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 ¬ify, const sp &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 &format) { + AString mime; + CHECK(format->findString("mime", &mime)); + + return !strncasecmp(mime.c_str(), "audio/", 6); +} + +sp WifiDisplaySource::PlaybackSession::Track::getFormat() { + return mFormat != NULL ? mFormat : mConverter->getOutputFormat(); +} + +bool WifiDisplaySource::PlaybackSession::Track::isAudio() const { + return mIsAudio; +} + +const sp &WifiDisplaySource::PlaybackSession::Track::converter() const { + return mConverter; +} + +const sp & +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 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 &msg) { + switch (msg->what()) { + case kWhatMediaPullerStopped: + { + mConverter.clear(); + + mStarted = false; + + sp 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 &accessUnit) { + mQueuedAccessUnits.push_back(accessUnit); +} + +sp WifiDisplaySource::PlaybackSession::Track::dequeueAccessUnit() { + if (mQueuedAccessUnits.empty()) { + return NULL; + } + + sp accessUnit = *mQueuedAccessUnits.begin(); + CHECK(accessUnit != NULL); + + mQueuedAccessUnits.erase(mQueuedAccessUnits.begin()); + + return accessUnit; +} + +void WifiDisplaySource::PlaybackSession::Track::setRepeaterSource( + const sp &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 &outputBuffer = *mQueuedOutputBuffers.begin(); + + CHECK(outputBuffer->meta()->findInt64("timeUs", timeUs)); + + return true; +} + +void WifiDisplaySource::PlaybackSession::Track::queueOutputBuffer( + const sp &accessUnit) { + mQueuedOutputBuffers.push_back(accessUnit); + mLastOutputBufferQueuedTimeUs = ALooper::GetNowUs(); +} + +sp WifiDisplaySource::PlaybackSession::Track::dequeueOutputBuffer() { + CHECK(!mQueuedOutputBuffers.empty()); + + sp 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 &netSession, + const sp ¬ify, + const in_addr &interfaceAddr, + const sp &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 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 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 &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 accessUnit; + CHECK(msg->findBuffer("accessUnit", &accessUnit)); + + const sp &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 = + 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 &videoTrack = + mTracks.valueFor(mVideoTrackIndex); + + sp 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 = 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 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 &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 &videoTrack = mTracks.valueFor(mVideoTrackIndex); + sp 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 = 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; + + 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 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 notify = new AMessage(kWhatTrackNotify, this); + notify->setSize("trackIndex", trackIndex); + + sp 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 msg = new AMessage(kWhatPullExtractorSample, this); + msg->setInt32("generation", mPullExtractorGeneration); + msg->post(delayUs); + + mPullExtractorPending = true; +} + +void WifiDisplaySource::PlaybackSession::onPullExtractor() { + sp 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 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 &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 pullLooper = new ALooper; + pullLooper->setName("pull_looper"); + + pullLooper->start( + false /* runOnCallingThread */, + false /* canCallJava */, + PRIORITY_AUDIO); + + sp codecLooper = new ALooper; + codecLooper->setName("codec_looper"); + + codecLooper->start( + false /* runOnCallingThread */, + false /* canCallJava */, + PRIORITY_AUDIO); + + size_t trackIndex; + + sp notify; + + trackIndex = mTracks.size(); + + sp 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 = 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 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 = new Track( + notify, pullLooper, codecLooper, puller, converter); + + if (isRepeaterSource) { + track->setRepeaterSource(static_cast(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 source = new SurfaceMediaSource(width, height); + + source->setUseAbsoluteTimestamps(); + + sp 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) { + sp audioSource = new AudioSource( + AUDIO_SOURCE_REMOTE_SUBMIX, + 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 WifiDisplaySource::PlaybackSession::getSurfaceTexture() { + return mProducer; +} + +void WifiDisplaySource::PlaybackSession::requestIDRFrame() { + for (size_t i = 0; i < mTracks.size(); ++i) { + const sp &track = mTracks.valueAt(i); + + track->requestIDRFrame(); + } +} + +void WifiDisplaySource::PlaybackSession::notifySessionDead() { + // Inform WifiDisplaySource of our premature death (wish). + sp 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 0000000000..f6673df541 --- /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 + +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 &netSession, + const sp ¬ify, + const struct in_addr &interfaceAddr, + const sp &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 getSurfaceTexture(); + + void requestIDRFrame(); + + enum { + kWhatSessionDead, + kWhatBinaryData, + kWhatSessionEstablished, + kWhatSessionDestroyed, + }; + +protected: + virtual void onMessageReceived(const sp &msg); + virtual ~PlaybackSession(); + +private: + struct Track; + + enum { + kWhatMediaPullerNotify, + kWhatConverterNotify, + kWhatTrackNotify, + kWhatUpdateSurface, + kWhatPause, + kWhatResume, + kWhatMediaSenderNotify, + kWhatPullExtractorSample, + }; + + String16 mOpPackageName; + + sp mNetSession; + sp mNotify; + in_addr mInterfaceAddr; + sp mHDCP; + AString mMediaPath; + + sp mMediaSender; + int32_t mLocalRTPPort; + + bool mWeAreDead; + bool mPaused; + + int64_t mLastLifesignUs; + + sp mProducer; + + KeyedVector > mTracks; + ssize_t mVideoTrackIndex; + + int64_t mPrevTimeUs; + + sp mExtractor; + KeyedVector 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 &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 &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 0000000000..af6b66337d --- /dev/null +++ b/media/libstagefright/wifi-display/source/RepeaterSource.cpp @@ -0,0 +1,219 @@ +//#define LOG_NDEBUG 0 +#define LOG_TAG "RepeaterSource" +#include + +#include "RepeaterSource.h" + +#include +#include +#include +#include +#include + +namespace android { + +RepeaterSource::RepeaterSource(const sp &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(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 RepeaterSource::getFormat() { + return mSource->getFormat(); +} + +status_t RepeaterSource::read( + MediaBuffer **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 &msg) { + switch (msg->what()) { + case kWhatRead: + { + MediaBuffer *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 0000000000..8d414fd538 --- /dev/null +++ b/media/libstagefright/wifi-display/source/RepeaterSource.h @@ -0,0 +1,67 @@ +#ifndef REPEATER_SOURCE_H_ + +#define REPEATER_SOURCE_H_ + +#include +#include +#include + +#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 &source, double rateHz); + + virtual status_t start(MetaData *params); + virtual status_t stop(); + virtual sp getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options); + + void onMessageReceived(const sp &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 mSource; + double mRateHz; + + sp mLooper; + sp > mReflector; + + MediaBuffer *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 0000000000..865ba94e14 --- /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 + +#include "TSPacketizer.h" +#include "include/avc_utils.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace android { + +struct TSPacketizer::Track : public RefBase { + Track(const sp &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 prependCSD(const sp &accessUnit) const; + sp prependADTSHeader(const sp &accessUnit) const; + + size_t countDescriptors() const; + sp descriptorAt(size_t index) const; + + void finalize(); + void extractCSDIfNecessary(); + +protected: + virtual ~Track(); + +private: + sp mFormat; + + unsigned mPID; + unsigned mStreamType; + unsigned mStreamID; + unsigned mContinuityCounter; + + AString mMIME; + Vector > mCSD; + + Vector > mDescriptors; + + bool mAudioLacksATDSHeaders; + bool mFinalized; + bool mExtractedCSD; + + DISALLOW_EVIL_CONSTRUCTORS(Track); +}; + +TSPacketizer::Track::Track( + const sp &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 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 TSPacketizer::Track::prependCSD( + const sp &accessUnit) const { + size_t size = 0; + for (size_t i = 0; i < mCSD.size(); ++i) { + size += mCSD.itemAt(i)->size(); + } + + sp dup = new ABuffer(accessUnit->size() + size); + size_t offset = 0; + for (size_t i = 0; i < mCSD.size(); ++i) { + const sp &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 TSPacketizer::Track::prependADTSHeader( + const sp &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 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 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 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 &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 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 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 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 &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 = 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 = 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 = mTracks.itemAt(trackIndex); + track->extractCSDIfNecessary(); + + return OK; +} + +status_t TSPacketizer::packetize( + size_t trackIndex, + const sp &_accessUnit, + sp *packets, + uint32_t flags, + const uint8_t *PES_private_data, size_t PES_private_data_len, + size_t numStuffingBytes) { + sp accessUnit = _accessUnit; + + int64_t timeUs; + CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs)); + + packets->clear(); + + if (trackIndex >= mTracks.size()) { + return -ERANGE; + } + + const sp &track = mTracks.itemAt(trackIndex); + + if (track->isH264() && (flags & PREPEND_SPS_PPS_TO_IDR_FRAMES) + && IsIDR(accessUnit)) { + // 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 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 &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 = 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 &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 TSPacketizer::prependCSD( + size_t trackIndex, const sp &accessUnit) const { + CHECK_LT(trackIndex, mTracks.size()); + + const sp &track = mTracks.itemAt(trackIndex); + CHECK(track->isH264() && IsIDR(accessUnit)); + + int64_t timeUs; + CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs)); + + sp 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 0000000000..0dcb179551 --- /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 +#include +#include +#include + +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 &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 &accessUnit, + sp *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 prependCSD( + size_t trackIndex, const sp &accessUnit) const; + +protected: + virtual ~TSPacketizer(); + +private: + enum { + kPID_PMT = 0x100, + kPID_PCR = 0x1000, + }; + + struct Track; + + uint32_t mFlags; + Vector > mTracks; + + Vector > 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 0000000000..4695e5d289 --- /dev/null +++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp @@ -0,0 +1,1737 @@ +/* + * 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 + +#include "WifiDisplaySource.h" +#include "PlaybackSession.h" +#include "Parameters.h" +#include "rtp/RTPSender.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +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 &netSession, + const sp &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 &msg, sp *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 msg = new AMessage(kWhatStart, this); + msg->setString("iface", iface); + + sp response; + return PostAndAwaitResponse(msg, &response); +} + +status_t WifiDisplaySource::stop() { + sp msg = new AMessage(kWhatStop, this); + + sp response; + return PostAndAwaitResponse(msg, &response); +} + +status_t WifiDisplaySource::pause() { + sp msg = new AMessage(kWhatPause, this); + + sp response; + return PostAndAwaitResponse(msg, &response); +} + +status_t WifiDisplaySource::resume() { + sp msg = new AMessage(kWhatResume, this); + + sp response; + return PostAndAwaitResponse(msg, &response); +} + +void WifiDisplaySource::onMessageReceived(const sp &msg) { + switch (msg->what()) { + case kWhatStart: + { + sp 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 notify = new AMessage(kWhatRTSPNotify, this); + + err = mNetSession->createRTSPServer( + mInterfaceAddr, port, notify, &mSessionID); + } else { + err = -EINVAL; + } + } + + mState = AWAITING_CLIENT_CONNECTION; + + sp 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 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 response = new AMessage; + response->setInt32("err", err); + response->postReply(replyID); + break; + } + + case kWhatResume: + { + sp 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 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 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 &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 &msg) { + int32_t statusCode; + if (!msg->getStatusCode(&statusCode)) { + return ERROR_MALFORMED; + } + + if (statusCode != 200) { + return ERROR_UNSUPPORTED; + } + + sp 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 &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 &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 & /* 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 msg = new AMessage(kWhatKeepAlive, this); + msg->setInt32("sessionID", sessionID); + msg->post(kPlaybackSessionTimeoutUs - 5000000ll); +} + +status_t WifiDisplaySource::onReceiveClientData(const sp &msg) { + int32_t sessionID; + CHECK(msg->findInt32("sessionID", &sessionID)); + + sp obj; + CHECK(msg->findObject("data", &obj)); + + sp data = + static_cast(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 &data) { + int32_t playbackSessionID; + sp 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 &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 notify = new AMessage(kWhatPlaybackSessionNotify, this); + notify->setInt32("playbackSessionID", playbackSessionID); + notify->setInt32("sessionID", sessionID); + + sp 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 &data) { + int32_t playbackSessionID; + sp 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 = + 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 &data) { + int32_t playbackSessionID; + sp 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 &data) { + ALOGI("Received TEARDOWN request."); + + int32_t playbackSessionID; + sp 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 response = new AMessage; + response->setInt32("err", err); + response->postReply(mStopReplyID); +} + +status_t WifiDisplaySource::onGetParameterRequest( + int32_t sessionID, + int32_t cseq, + const sp &data) { + int32_t playbackSessionID; + sp 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 &data) { + int32_t playbackSessionID; + sp 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::findPlaybackSession( + const sp &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 ¬ify); + + virtual void notify( + int msg, int ext1, int ext2, const Parcel *obj); + +private: + sp mNotify; + + DISALLOW_EVIL_CONSTRUCTORS(HDCPObserver); +}; + +WifiDisplaySource::HDCPObserver::HDCPObserver( + const sp ¬ify) + : mNotify(notify) { +} + +void WifiDisplaySource::HDCPObserver::notify( + int msg, int ext1, int ext2, const Parcel * /* obj */) { + sp notify = mNotify->dup(); + notify->setInt32("msg", msg); + notify->setInt32("ext1", ext1); + notify->setInt32("ext2", ext2); + notify->post(); +} + +status_t WifiDisplaySource::makeHDCP() { + sp sm = defaultServiceManager(); + sp binder = sm->getService(String16("media.player")); + + sp service = + interface_cast(binder); + + CHECK(service != NULL); + + mHDCP = service->makeHDCP(true /* createEncryptionModule */); + + if (mHDCP == NULL) { + return ERROR_UNSUPPORTED; + } + + sp 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 0000000000..c25a675ee2 --- /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 +#include + +#include + +#include + +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 &netSession, + const sp &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 &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 &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 mNetSession; + sp mClient; + AString mMediaPath; + struct in_addr mInterfaceAddr; + int32_t mSessionID; + + sp 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 mPlaybackSession; + }; + ClientInfo mClientInfo; + + bool mReaperPending; + + int32_t mNextCSeq; + + KeyedVector mResponseHandlers; + + // HDCP specific section >>>> + bool mUsingHDCP; + bool mIsHDCP2_0; + int32_t mHDCPPort; + sp mHDCP; + sp 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 &msg); + + status_t onReceiveM3Response( + int32_t sessionID, const sp &msg); + + status_t onReceiveM4Response( + int32_t sessionID, const sp &msg); + + status_t onReceiveM5Response( + int32_t sessionID, const sp &msg); + + status_t onReceiveM16Response( + int32_t sessionID, const sp &msg); + + void registerResponseHandler( + int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func); + + status_t onReceiveClientData(const sp &msg); + + status_t onOptionsRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + status_t onSetupRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + status_t onPlayRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + status_t onPauseRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + status_t onTeardownRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + status_t onGetParameterRequest( + int32_t sessionID, + int32_t cseq, + const sp &data); + + status_t onSetParameterRequest( + int32_t sessionID, + int32_t cseq, + const sp &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 findPlaybackSession( + const sp &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_