diff options
143 files changed, 10493 insertions, 1985 deletions
diff --git a/camera/Camera.cpp b/camera/Camera.cpp index 1b136de..fd78572 100644 --- a/camera/Camera.cpp +++ b/camera/Camera.cpp @@ -255,6 +255,14 @@ void Camera::setPreviewCallbackFlags(int flag) mCamera->setPreviewCallbackFlag(flag); } +status_t Camera::setPreviewCallbackTarget( + const sp<IGraphicBufferProducer>& callbackProducer) +{ + sp <ICamera> c = mCamera; + if (c == 0) return NO_INIT; + return c->setPreviewCallbackTarget(callbackProducer); +} + // callback from camera service void Camera::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) { diff --git a/camera/ICamera.cpp b/camera/ICamera.cpp index 8900867..12356f0 100644 --- a/camera/ICamera.cpp +++ b/camera/ICamera.cpp @@ -31,6 +31,7 @@ enum { DISCONNECT = IBinder::FIRST_CALL_TRANSACTION, SET_PREVIEW_TEXTURE, SET_PREVIEW_CALLBACK_FLAG, + SET_PREVIEW_CALLBACK_TARGET, START_PREVIEW, STOP_PREVIEW, AUTO_FOCUS, @@ -65,6 +66,7 @@ public: Parcel data, reply; data.writeInterfaceToken(ICamera::getInterfaceDescriptor()); remote()->transact(DISCONNECT, data, &reply); + reply.readExceptionCode(); } // pass the buffered IGraphicBufferProducer to the camera service @@ -90,6 +92,18 @@ public: remote()->transact(SET_PREVIEW_CALLBACK_FLAG, data, &reply); } + status_t setPreviewCallbackTarget( + const sp<IGraphicBufferProducer>& callbackProducer) + { + ALOGV("setPreviewCallbackTarget"); + Parcel data, reply; + data.writeInterfaceToken(ICamera::getInterfaceDescriptor()); + sp<IBinder> b(callbackProducer->asBinder()); + data.writeStrongBinder(b); + remote()->transact(SET_PREVIEW_CALLBACK_TARGET, data, &reply); + return reply.readInt32(); + } + // start preview mode, must call setPreviewDisplay first status_t startPreview() { @@ -268,6 +282,7 @@ status_t BnCamera::onTransact( ALOGV("DISCONNECT"); CHECK_INTERFACE(ICamera, data, reply); disconnect(); + reply->writeNoException(); return NO_ERROR; } break; case SET_PREVIEW_TEXTURE: { @@ -285,6 +300,14 @@ status_t BnCamera::onTransact( setPreviewCallbackFlag(callback_flag); return NO_ERROR; } break; + case SET_PREVIEW_CALLBACK_TARGET: { + ALOGV("SET_PREVIEW_CALLBACK_TARGET"); + CHECK_INTERFACE(ICamera, data, reply); + sp<IGraphicBufferProducer> cp = + interface_cast<IGraphicBufferProducer>(data.readStrongBinder()); + reply->writeInt32(setPreviewCallbackTarget(cp)); + return NO_ERROR; + } case START_PREVIEW: { ALOGV("START_PREVIEW"); CHECK_INTERFACE(ICamera, data, reply); diff --git a/camera/ICameraService.cpp b/camera/ICameraService.cpp index 134f7f0..819e410 100644 --- a/camera/ICameraService.cpp +++ b/camera/ICameraService.cpp @@ -15,6 +15,9 @@ ** limitations under the License. */ +#define LOG_TAG "BpCameraService" +#include <utils/Log.h> + #include <stdint.h> #include <sys/types.h> @@ -31,6 +34,53 @@ namespace android { +namespace { + +enum { + EX_SECURITY = -1, + EX_BAD_PARCELABLE = -2, + EX_ILLEGAL_ARGUMENT = -3, + EX_NULL_POINTER = -4, + EX_ILLEGAL_STATE = -5, + EX_HAS_REPLY_HEADER = -128, // special; see below +}; + +static bool readExceptionCode(Parcel& reply) { + int32_t exceptionCode = reply.readExceptionCode(); + + if (exceptionCode != 0) { + const char* errorMsg; + switch(exceptionCode) { + case EX_SECURITY: + errorMsg = "Security"; + break; + case EX_BAD_PARCELABLE: + errorMsg = "BadParcelable"; + break; + case EX_NULL_POINTER: + errorMsg = "NullPointer"; + break; + case EX_ILLEGAL_STATE: + errorMsg = "IllegalState"; + break; + // Binder should be handling this code inside Parcel::readException + // but lets have a to-string here anyway just in case. + case EX_HAS_REPLY_HEADER: + errorMsg = "HasReplyHeader"; + break; + default: + errorMsg = "Unknown"; + } + + ALOGE("Binder transmission error %s (%d)", errorMsg, exceptionCode); + return true; + } + + return false; +} + +}; + class BpCameraService: public BpInterface<ICameraService> { public: @@ -45,6 +95,8 @@ public: Parcel data, reply; data.writeInterfaceToken(ICameraService::getInterfaceDescriptor()); remote()->transact(BnCameraService::GET_NUMBER_OF_CAMERAS, data, &reply); + + if (readExceptionCode(reply)) return 0; return reply.readInt32(); } @@ -55,9 +107,14 @@ public: data.writeInterfaceToken(ICameraService::getInterfaceDescriptor()); data.writeInt32(cameraId); remote()->transact(BnCameraService::GET_CAMERA_INFO, data, &reply); - cameraInfo->facing = reply.readInt32(); - cameraInfo->orientation = reply.readInt32(); - return reply.readInt32(); + + if (readExceptionCode(reply)) return -EPROTO; + status_t result = reply.readInt32(); + if (reply.readInt32() != 0) { + cameraInfo->facing = reply.readInt32(); + cameraInfo->orientation = reply.readInt32(); + } + return result; } // connect to camera service @@ -71,6 +128,8 @@ public: data.writeString16(clientPackageName); data.writeInt32(clientUid); remote()->transact(BnCameraService::CONNECT, data, &reply); + + if (readExceptionCode(reply)) return NULL; return interface_cast<ICamera>(reply.readStrongBinder()); } @@ -85,6 +144,8 @@ public: data.writeString16(clientPackageName); data.writeInt32(clientUid); remote()->transact(BnCameraService::CONNECT_PRO, data, &reply); + + if (readExceptionCode(reply)) return NULL; return interface_cast<IProCameraUser>(reply.readStrongBinder()); } @@ -94,6 +155,8 @@ public: data.writeInterfaceToken(ICameraService::getInterfaceDescriptor()); data.writeStrongBinder(listener->asBinder()); remote()->transact(BnCameraService::ADD_LISTENER, data, &reply); + + if (readExceptionCode(reply)) return -EPROTO; return reply.readInt32(); } @@ -103,6 +166,8 @@ public: data.writeInterfaceToken(ICameraService::getInterfaceDescriptor()); data.writeStrongBinder(listener->asBinder()); remote()->transact(BnCameraService::REMOVE_LISTENER, data, &reply); + + if (readExceptionCode(reply)) return -EPROTO; return reply.readInt32(); } }; @@ -117,17 +182,22 @@ status_t BnCameraService::onTransact( switch(code) { case GET_NUMBER_OF_CAMERAS: { CHECK_INTERFACE(ICameraService, data, reply); + reply->writeNoException(); reply->writeInt32(getNumberOfCameras()); return NO_ERROR; } break; case GET_CAMERA_INFO: { CHECK_INTERFACE(ICameraService, data, reply); - CameraInfo cameraInfo; + CameraInfo cameraInfo = CameraInfo(); memset(&cameraInfo, 0, sizeof(cameraInfo)); status_t result = getCameraInfo(data.readInt32(), &cameraInfo); + reply->writeNoException(); + reply->writeInt32(result); + + // Fake a parcelable object here + reply->writeInt32(1); // means the parcelable is included reply->writeInt32(cameraInfo.facing); reply->writeInt32(cameraInfo.orientation); - reply->writeInt32(result); return NO_ERROR; } break; case CONNECT: { @@ -139,17 +209,20 @@ status_t BnCameraService::onTransact( int32_t clientUid = data.readInt32(); sp<ICamera> camera = connect(cameraClient, cameraId, clientName, clientUid); + reply->writeNoException(); reply->writeStrongBinder(camera->asBinder()); return NO_ERROR; } break; case CONNECT_PRO: { CHECK_INTERFACE(ICameraService, data, reply); - sp<IProCameraCallbacks> cameraClient = interface_cast<IProCameraCallbacks>(data.readStrongBinder()); + sp<IProCameraCallbacks> cameraClient = + interface_cast<IProCameraCallbacks>(data.readStrongBinder()); int32_t cameraId = data.readInt32(); const String16 clientName = data.readString16(); int32_t clientUid = data.readInt32(); sp<IProCameraUser> camera = connect(cameraClient, cameraId, clientName, clientUid); + reply->writeNoException(); reply->writeStrongBinder(camera->asBinder()); return NO_ERROR; } break; @@ -157,6 +230,7 @@ status_t BnCameraService::onTransact( CHECK_INTERFACE(ICameraService, data, reply); sp<ICameraServiceListener> listener = interface_cast<ICameraServiceListener>(data.readStrongBinder()); + reply->writeNoException(); reply->writeInt32(addListener(listener)); return NO_ERROR; } break; @@ -164,6 +238,7 @@ status_t BnCameraService::onTransact( CHECK_INTERFACE(ICameraService, data, reply); sp<ICameraServiceListener> listener = interface_cast<ICameraServiceListener>(data.readStrongBinder()); + reply->writeNoException(); reply->writeInt32(removeListener(listener)); return NO_ERROR; } break; diff --git a/camera/ICameraServiceListener.cpp b/camera/ICameraServiceListener.cpp index 640ee35..b2f1729 100644 --- a/camera/ICameraServiceListener.cpp +++ b/camera/ICameraServiceListener.cpp @@ -54,6 +54,8 @@ public: data, &reply, IBinder::FLAG_ONEWAY); + + reply.readExceptionCode(); } }; @@ -73,6 +75,7 @@ status_t BnCameraServiceListener::onTransact( int32_t cameraId = data.readInt32(); onStatusChanged(status, cameraId); + reply->writeNoException(); return NO_ERROR; } break; diff --git a/camera/IProCameraUser.cpp b/camera/IProCameraUser.cpp index 4c4dec3..015cb5c 100644 --- a/camera/IProCameraUser.cpp +++ b/camera/IProCameraUser.cpp @@ -162,6 +162,7 @@ public: Parcel data, reply; data.writeInterfaceToken(IProCameraUser::getInterfaceDescriptor()); remote()->transact(DISCONNECT, data, &reply); + reply.readExceptionCode(); } virtual status_t connect(const sp<IProCameraCallbacks>& cameraClient) @@ -307,6 +308,7 @@ status_t BnProCameraUser::onTransact( ALOGV("DISCONNECT"); CHECK_INTERFACE(IProCameraUser, data, reply); disconnect(); + reply->writeNoException(); return NO_ERROR; } break; case CONNECT: { diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk index 3844487..1060131 100644 --- a/cmds/stagefright/Android.mk +++ b/cmds/stagefright/Android.mk @@ -19,8 +19,6 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar -LOCAL_MODULE_TAGS := debug - LOCAL_MODULE:= stagefright include $(BUILD_EXECUTABLE) @@ -42,7 +40,7 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE:= record @@ -65,7 +63,7 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE:= recordvideo @@ -89,7 +87,7 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE:= audioloop @@ -112,7 +110,7 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE:= stream @@ -135,7 +133,7 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE:= sf2 @@ -159,7 +157,7 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE:= codec @@ -182,7 +180,7 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE:= muxer diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index 115b07c..924cf6d 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -30,8 +30,6 @@ #include <binder/ProcessState.h> #include <media/IMediaPlayerService.h> #include <media/stagefright/foundation/ALooper.h> -#include <media/stagefright/foundation/AMessage.h> -#include "include/LiveSession.h" #include "include/NuCachedSource2.h" #include <media/stagefright/AudioPlayer.h> #include <media/stagefright/DataSource.h> @@ -678,7 +676,6 @@ int main(int argc, char **argv) { gDisplayHistogram = false; sp<ALooper> looper; - sp<LiveSession> liveSession; int res; while ((res = getopt(argc, argv, "han:lm:b:ptsrow:kxSTd:D:")) >= 0) { @@ -961,9 +958,7 @@ int main(int argc, char **argv) { sp<DataSource> dataSource = DataSource::CreateFromURI(filename); - if (strncasecmp(filename, "sine:", 5) - && strncasecmp(filename, "httplive://", 11) - && dataSource == NULL) { + if (strncasecmp(filename, "sine:", 5) && dataSource == NULL) { fprintf(stderr, "Unable to create data source.\n"); return 1; } @@ -995,44 +990,21 @@ int main(int argc, char **argv) { mediaSources.push(mediaSource); } } else { - sp<MediaExtractor> extractor; + sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource); - if (!strncasecmp("httplive://", filename, 11)) { - String8 uri("http://"); - uri.append(filename + 11); - - if (looper == NULL) { - looper = new ALooper; - looper->start(); - } - liveSession = new LiveSession(NULL /* notify */); - looper->registerHandler(liveSession); - - liveSession->connect(uri.string()); - dataSource = liveSession->getDataSource(); - - extractor = - MediaExtractor::Create( - dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS); - - syncInfoPresent = false; - } else { - extractor = MediaExtractor::Create(dataSource); - - if (extractor == NULL) { - fprintf(stderr, "could not create extractor.\n"); - return -1; - } + if (extractor == NULL) { + fprintf(stderr, "could not create extractor.\n"); + return -1; + } - sp<MetaData> meta = extractor->getMetaData(); + sp<MetaData> meta = extractor->getMetaData(); - if (meta != NULL) { - const char *mime; - CHECK(meta->findCString(kKeyMIMEType, &mime)); + if (meta != NULL) { + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); - if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) { - syncInfoPresent = false; - } + if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) { + syncInfoPresent = false; } } diff --git a/include/camera/Camera.h b/include/camera/Camera.h index 37626a4..c34b3ea 100644 --- a/include/camera/Camera.h +++ b/include/camera/Camera.h @@ -121,7 +121,15 @@ public: void setListener(const sp<CameraListener>& listener); void setRecordingProxyListener(const sp<ICameraRecordingProxyListener>& listener); + + // Configure preview callbacks to app. Only one of the older + // callbacks or the callback surface can be active at the same time; + // enabling one will disable the other if active. Flags can be + // disabled by calling it with CAMERA_FRAME_CALLBACK_FLAG_NOOP, and + // Target by calling it with a NULL interface. void setPreviewCallbackFlags(int preview_callback_flag); + status_t setPreviewCallbackTarget( + const sp<IGraphicBufferProducer>& callbackProducer); sp<ICameraRecordingProxy> getRecordingProxy(); diff --git a/include/camera/ICamera.h b/include/camera/ICamera.h index 2236c1f..f3a186e 100644 --- a/include/camera/ICamera.h +++ b/include/camera/ICamera.h @@ -32,6 +32,9 @@ class Surface; class ICamera: public IInterface { + /** + * Keep up-to-date with ICamera.aidl in frameworks/base + */ public: DECLARE_META_INTERFACE(Camera); @@ -51,8 +54,15 @@ public: const sp<IGraphicBufferProducer>& bufferProducer) = 0; // set the preview callback flag to affect how the received frames from - // preview are handled. + // preview are handled. Enabling preview callback flags disables any active + // preview callback surface set by setPreviewCallbackTarget(). virtual void setPreviewCallbackFlag(int flag) = 0; + // set a buffer interface to use for client-received preview frames instead + // of preview callback buffers. Passing a valid interface here disables any + // active preview callbacks set by setPreviewCallbackFlag(). Passing NULL + // disables the use of the callback target. + virtual status_t setPreviewCallbackTarget( + const sp<IGraphicBufferProducer>& callbackProducer) = 0; // start preview mode, must call setPreviewDisplay first virtual status_t startPreview() = 0; diff --git a/include/camera/ICameraClient.h b/include/camera/ICameraClient.h index b30aa7a..1584dba 100644 --- a/include/camera/ICameraClient.h +++ b/include/camera/ICameraClient.h @@ -28,6 +28,9 @@ namespace android { class ICameraClient: public IInterface { + /** + * Keep up-to-date with ICameraClient.aidl in frameworks/base + */ public: DECLARE_META_INTERFACE(CameraClient); diff --git a/include/camera/ICameraService.h b/include/camera/ICameraService.h index aaf6eb3..3c2e60a 100644 --- a/include/camera/ICameraService.h +++ b/include/camera/ICameraService.h @@ -32,6 +32,9 @@ class ICameraServiceListener; class ICameraService : public IInterface { public: + /** + * Keep up-to-date with ICameraService.aidl in frameworks/base + */ enum { GET_NUMBER_OF_CAMERAS = IBinder::FIRST_CALL_TRANSACTION, GET_CAMERA_INFO, diff --git a/include/camera/ICameraServiceListener.h b/include/camera/ICameraServiceListener.h index f2a11c2..0a0e43a 100644 --- a/include/camera/ICameraServiceListener.h +++ b/include/camera/ICameraServiceListener.h @@ -26,6 +26,9 @@ namespace android { class ICameraServiceListener : public IInterface { + /** + * Keep up-to-date with ICameraServiceListener.aidl in frameworks/base + */ public: /** diff --git a/include/camera/IProCameraCallbacks.h b/include/camera/IProCameraCallbacks.h index 563ec17..c774698 100644 --- a/include/camera/IProCameraCallbacks.h +++ b/include/camera/IProCameraCallbacks.h @@ -30,6 +30,9 @@ namespace android { class IProCameraCallbacks : public IInterface { + /** + * Keep up-to-date with IProCameraCallbacks.aidl in frameworks/base + */ public: DECLARE_META_INTERFACE(ProCameraCallbacks); diff --git a/include/camera/IProCameraUser.h b/include/camera/IProCameraUser.h index 45b818c..2ccc4d2 100644 --- a/include/camera/IProCameraUser.h +++ b/include/camera/IProCameraUser.h @@ -34,6 +34,9 @@ class Surface; class IProCameraUser: public IInterface { + /** + * Keep up-to-date with IProCameraUser.aidl in frameworks/base + */ public: DECLARE_META_INTERFACE(ProCameraUser); diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h index 64f82bb..8dbc9ee 100644 --- a/include/media/AudioTrack.h +++ b/include/media/AudioTrack.h @@ -191,7 +191,9 @@ public: /* Terminates the AudioTrack and unregisters it from AudioFlinger. * Also destroys all resources associated with the AudioTrack. */ - ~AudioTrack(); +protected: + virtual ~AudioTrack(); +public: /* Initialize an uninitialized AudioTrack. * Returned status (from utils/Errors.h) can be: @@ -304,15 +306,24 @@ public: /* Enables looping and sets the start and end points of looping. * Only supported for static buffer mode. * + * FIXME The comments below are for the new planned interpretation which is not yet implemented. + * Currently the legacy behavior is still implemented, where loopStart and loopEnd + * are in wrapping (overflow) frame units like the return value of getPosition(). + * The plan is to fix all callers to use the new version at same time implementation changes. + * * Parameters: * - * loopStart: loop start expressed as the number of PCM frames played since AudioTrack start. - * loopEnd: loop end expressed as the number of PCM frames played since AudioTrack start. + * loopStart: loop start in frames relative to start of buffer. + * loopEnd: loop end in frames relative to start of buffer. * loopCount: number of loops to execute. Calling setLoop() with loopCount == 0 cancels any - * pending or active loop. loopCount = -1 means infinite looping. + * pending or active loop. loopCount == -1 means infinite looping. * * For proper operation the following condition must be respected: - * (loopEnd-loopStart) <= framecount() + * loopCount != 0 implies 0 <= loopStart < loopEnd <= frameCount(). + * + * If the loop period (loopEnd - loopStart) is too small for the implementation to support, + * setLoop() will return BAD_VALUE. + * */ status_t setLoop(uint32_t loopStart, uint32_t loopEnd, int loopCount); @@ -354,18 +365,19 @@ public: status_t setPositionUpdatePeriod(uint32_t updatePeriod); status_t getPositionUpdatePeriod(uint32_t *updatePeriod) const; - /* Sets playback head position within AudioTrack buffer. The new position is specified - * in number of frames. - * This method must be called with the AudioTrack in paused or stopped state. - * Note that the actual position set is <position> modulo the AudioTrack buffer size in frames. - * Therefore using this method makes sense only when playing a "static" audio buffer - * as opposed to streaming. - * The getPosition() method on the other hand returns the total number of frames played since - * playback start. + /* Sets playback head position. + * Only supported for static buffer mode. + * + * FIXME The comments below are for the new planned interpretation which is not yet implemented. + * Currently the legacy behavior is still implemented, where the new position + * is in wrapping (overflow) frame units like the return value of getPosition(). + * The plan is to fix all callers to use the new version at same time implementation changes. * * Parameters: * - * position: New playback head position within AudioTrack buffer. + * position: New playback head position in frames relative to start of buffer. + * 0 <= position <= frameCount(). Note that end of buffer is permitted, + * but will result in an immediate underrun if started. * * Returned status (from utils/Errors.h) can be: * - NO_ERROR: successful operation @@ -381,6 +393,14 @@ public: */ status_t getPosition(uint32_t *position); +#if 0 + /* For static buffer mode only, this returns the current playback position in frames + * relative to start of buffer. It is analogous to the new API for + * setLoop() and setPosition(). After underrun, the position will be at end of buffer. + */ + status_t getBufferPosition(uint32_t *position); +#endif + /* Forces AudioTrack buffer full condition. When playing a static buffer, this method avoids * rewriting the buffer before restarting playback after a stop. * This method must be called with the AudioTrack in paused or stopped state. diff --git a/include/media/IHDCP.h b/include/media/IHDCP.h index 6d27b18..54fefa3 100644 --- a/include/media/IHDCP.h +++ b/include/media/IHDCP.h @@ -17,6 +17,7 @@ #include <binder/IInterface.h> #include <media/hardware/HDCPAPI.h> #include <media/stagefright/foundation/ABase.h> +#include <ui/GraphicBuffer.h> namespace android { @@ -59,6 +60,20 @@ struct IHDCP : public IInterface { const void *inData, size_t size, uint32_t streamCTR, uint64_t *outInputCTR, void *outData) = 0; + // Encrypt data according to the HDCP spec. "size" bytes of data starting + // at location "offset" are available in "buffer" (buffer handle). "size" + // may not be a multiple of 128 bits (16 bytes). An equal number of + // encrypted bytes should be written to the buffer at "outData" (virtual + // address). This operation is to be synchronous, i.e. this call does not + // return until outData contains size bytes of encrypted data. + // streamCTR will be assigned by the caller (to 0 for the first PES stream, + // 1 for the second and so on) + // inputCTR _will_be_maintained_by_the_callee_ for each PES stream. + virtual status_t encryptNative( + const sp<GraphicBuffer> &graphicBuffer, + size_t offset, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData) = 0; + // DECRYPTION only: // Decrypt data according to the HDCP spec. // "size" bytes of encrypted data are available at "inData" diff --git a/include/media/JetPlayer.h b/include/media/JetPlayer.h index 0616bf0..388f767 100644 --- a/include/media/JetPlayer.h +++ b/include/media/JetPlayer.h @@ -88,7 +88,7 @@ private: EAS_DATA_HANDLE mEasData; EAS_FILE_LOCATOR mEasJetFileLoc; EAS_PCM* mAudioBuffer;// EAS renders the MIDI data into this buffer, - AudioTrack* mAudioTrack; // and we play it in this audio track + sp<AudioTrack> mAudioTrack; // and we play it in this audio track int mTrackBufferSize; S_JET_STATUS mJetStatus; S_JET_STATUS mPreviousJetStatus; diff --git a/include/media/SoundPool.h b/include/media/SoundPool.h index 7bf3069..9e5654f 100644 --- a/include/media/SoundPool.h +++ b/include/media/SoundPool.h @@ -118,7 +118,7 @@ protected: class SoundChannel : public SoundEvent { public: enum state { IDLE, RESUMING, STOPPING, PAUSED, PLAYING }; - SoundChannel() : mAudioTrack(NULL), mState(IDLE), mNumChannels(1), + SoundChannel() : mState(IDLE), mNumChannels(1), mPos(0), mToggle(0), mAutoPaused(false) {} ~SoundChannel(); void init(SoundPool* soundPool); @@ -148,7 +148,7 @@ private: bool doStop_l(); SoundPool* mSoundPool; - AudioTrack* mAudioTrack; + sp<AudioTrack> mAudioTrack; SoundEvent mNextEvent; Mutex mLock; int mState; diff --git a/include/media/ToneGenerator.h b/include/media/ToneGenerator.h index 2183fbe..98c4332 100644 --- a/include/media/ToneGenerator.h +++ b/include/media/ToneGenerator.h @@ -160,7 +160,7 @@ public: bool isInited() { return (mState == TONE_IDLE)?false:true;} // returns the audio session this ToneGenerator belongs to or 0 if an error occured. - int getSessionId() { return (mpAudioTrack == NULL) ? 0 : mpAudioTrack->getSessionId(); } + int getSessionId() { return (mpAudioTrack == 0) ? 0 : mpAudioTrack->getSessionId(); } private: @@ -264,7 +264,7 @@ private: unsigned short mLoopCounter; // Current tone loopback count uint32_t mSamplingRate; // AudioFlinger Sampling rate - AudioTrack *mpAudioTrack; // Pointer to audio track used for playback + sp<AudioTrack> mpAudioTrack; // Pointer to audio track used for playback Mutex mLock; // Mutex to control concurent access to ToneGenerator object from audio callback and application API Mutex mCbkCondLock; // Mutex associated to mWaitCbkCond Condition mWaitCbkCond; // condition enabling interface to wait for audio callback completion after a change is requested diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h index df25d7b..8876c9b 100644 --- a/include/media/stagefright/ACodec.h +++ b/include/media/stagefright/ACodec.h @@ -182,7 +182,7 @@ private: bool mSentFormat; bool mIsEncoder; - + bool mUseMetadataOnEncoderOutput; bool mShutdownInProgress; // If "mKeepComponentAllocated" we only transition back to Loaded state diff --git a/include/media/stagefright/AudioPlayer.h b/include/media/stagefright/AudioPlayer.h index 1dc408f..3bf046d 100644 --- a/include/media/stagefright/AudioPlayer.h +++ b/include/media/stagefright/AudioPlayer.h @@ -70,7 +70,7 @@ public: private: friend class VideoEditorAudioPlayer; sp<MediaSource> mSource; - AudioTrack *mAudioTrack; + sp<AudioTrack> mAudioTrack; MediaBuffer *mInputBuffer; diff --git a/include/media/stagefright/SurfaceMediaSource.h b/include/media/stagefright/SurfaceMediaSource.h index 5f21da9..7d40379 100644 --- a/include/media/stagefright/SurfaceMediaSource.h +++ b/include/media/stagefright/SurfaceMediaSource.h @@ -146,9 +146,13 @@ private: // this consumer sp<BufferQueue> mBufferQueue; - // mBufferSlot caches GraphicBuffers from the buffer queue - sp<GraphicBuffer> mBufferSlot[BufferQueue::NUM_BUFFER_SLOTS]; + struct SlotData { + sp<GraphicBuffer> mGraphicBuffer; + uint64_t mFrameNumber; + }; + // mSlots caches GraphicBuffers and frameNumbers from the buffer queue + SlotData mSlots[BufferQueue::NUM_BUFFER_SLOTS]; // The permenent width and height of SMS buffers int mWidth; diff --git a/libvideoeditor/lvpp/VideoEditorAudioPlayer.cpp b/libvideoeditor/lvpp/VideoEditorAudioPlayer.cpp index c111ba8..3fa8b87 100755 --- a/libvideoeditor/lvpp/VideoEditorAudioPlayer.cpp +++ b/libvideoeditor/lvpp/VideoEditorAudioPlayer.cpp @@ -35,8 +35,7 @@ namespace android { VideoEditorAudioPlayer::VideoEditorAudioPlayer( const sp<MediaPlayerBase::AudioSink> &audioSink, PreviewPlayer *observer) - : mAudioTrack(NULL), - mInputBuffer(NULL), + : mInputBuffer(NULL), mSampleRate(0), mLatencyUs(0), mFrameSize(0), @@ -111,8 +110,7 @@ void VideoEditorAudioPlayer::clear() { } else { mAudioTrack->stop(); - delete mAudioTrack; - mAudioTrack = NULL; + mAudioTrack.clear(); } // Make sure to release any buffer we hold onto so that the @@ -538,8 +536,7 @@ status_t VideoEditorAudioPlayer::start(bool sourceAlreadyStarted) { 0, AUDIO_OUTPUT_FLAG_NONE, &AudioCallback, this, 0); if ((err = mAudioTrack->initCheck()) != OK) { - delete mAudioTrack; - mAudioTrack = NULL; + mAudioTrack.clear(); if (mFirstBuffer != NULL) { mFirstBuffer->release(); diff --git a/libvideoeditor/lvpp/VideoEditorAudioPlayer.h b/libvideoeditor/lvpp/VideoEditorAudioPlayer.h index 626df39..a5616c1 100755 --- a/libvideoeditor/lvpp/VideoEditorAudioPlayer.h +++ b/libvideoeditor/lvpp/VideoEditorAudioPlayer.h @@ -91,7 +91,7 @@ private: int64_t mBGAudioStoryBoardCurrentMediaVolumeVal; sp<MediaSource> mSource; - AudioTrack *mAudioTrack; + sp<AudioTrack> mAudioTrack; MediaBuffer *mInputBuffer; diff --git a/libvideoeditor/lvpp/VideoEditorPlayer.cpp b/libvideoeditor/lvpp/VideoEditorPlayer.cpp index 91a4415..4a14b40 100755 --- a/libvideoeditor/lvpp/VideoEditorPlayer.cpp +++ b/libvideoeditor/lvpp/VideoEditorPlayer.cpp @@ -310,7 +310,6 @@ bool VideoEditorPlayer::VeAudioOutput::mIsOnEmulator = false; VideoEditorPlayer::VeAudioOutput::VeAudioOutput() : mCallback(NULL), mCallbackCookie(NULL) { - mTrack = 0; mStreamType = AUDIO_STREAM_MUSIC; mLeftVolume = 1.0; mRightVolume = 1.0; @@ -405,7 +404,7 @@ status_t VideoEditorPlayer::VeAudioOutput::open( } ALOGV("open(%u, %d, %d, %d)", sampleRate, channelCount, format, bufferCount); - if (mTrack) close(); + if (mTrack != 0) close(); uint32_t afSampleRate; size_t afFrameCount; int frameCount; @@ -434,7 +433,7 @@ status_t VideoEditorPlayer::VeAudioOutput::open( } } - AudioTrack *t; + sp<AudioTrack> t; if (mCallback != NULL) { t = new AudioTrack( mStreamType, @@ -457,7 +456,6 @@ status_t VideoEditorPlayer::VeAudioOutput::open( if ((t == 0) || (t->initCheck() != NO_ERROR)) { ALOGE("Unable to create audio track"); - delete t; return NO_INIT; } @@ -472,7 +470,7 @@ status_t VideoEditorPlayer::VeAudioOutput::open( void VideoEditorPlayer::VeAudioOutput::start() { ALOGV("start"); - if (mTrack) { + if (mTrack != 0) { mTrack->setVolume(mLeftVolume, mRightVolume); mTrack->start(); mTrack->getPosition(&mNumFramesWritten); @@ -492,7 +490,7 @@ ssize_t VideoEditorPlayer::VeAudioOutput::write( LOG_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback."); //ALOGV("write(%p, %u)", buffer, size); - if (mTrack) { + if (mTrack != 0) { snoopWrite(buffer, size); ssize_t ret = mTrack->write(buffer, size); mNumFramesWritten += ret / 4; // assume 16 bit stereo @@ -504,26 +502,25 @@ ssize_t VideoEditorPlayer::VeAudioOutput::write( void VideoEditorPlayer::VeAudioOutput::stop() { ALOGV("stop"); - if (mTrack) mTrack->stop(); + if (mTrack != 0) mTrack->stop(); } void VideoEditorPlayer::VeAudioOutput::flush() { ALOGV("flush"); - if (mTrack) mTrack->flush(); + if (mTrack != 0) mTrack->flush(); } void VideoEditorPlayer::VeAudioOutput::pause() { ALOGV("VeAudioOutput::pause"); - if (mTrack) mTrack->pause(); + if (mTrack != 0) mTrack->pause(); } void VideoEditorPlayer::VeAudioOutput::close() { ALOGV("close"); - delete mTrack; - mTrack = 0; + mTrack.clear(); } void VideoEditorPlayer::VeAudioOutput::setVolume(float left, float right) { @@ -531,7 +528,7 @@ void VideoEditorPlayer::VeAudioOutput::setVolume(float left, float right) { ALOGV("setVolume(%f, %f)", left, right); mLeftVolume = left; mRightVolume = right; - if (mTrack) { + if (mTrack != 0) { mTrack->setVolume(left, right); } } diff --git a/libvideoeditor/lvpp/VideoEditorPlayer.h b/libvideoeditor/lvpp/VideoEditorPlayer.h index 77194ab..defc90d 100755 --- a/libvideoeditor/lvpp/VideoEditorPlayer.h +++ b/libvideoeditor/lvpp/VideoEditorPlayer.h @@ -71,7 +71,7 @@ class VideoEditorPlayer : public MediaPlayerInterface { static void CallbackWrapper( int event, void *me, void *info); - AudioTrack* mTrack; + sp<AudioTrack> mTrack; AudioCallback mCallback; void * mCallbackCookie; audio_stream_type_t mStreamType; diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk index 2c0c3a5..96755bb 100644 --- a/media/libmedia/Android.mk +++ b/media/libmedia/Android.mk @@ -53,7 +53,8 @@ LOCAL_SRC_FILES:= \ Visualizer.cpp \ MemoryLeakTrackUtil.cpp \ SoundPool.cpp \ - SoundPoolThread.cpp + SoundPoolThread.cpp \ + StringArray.cpp LOCAL_SRC_FILES += ../libnbaio/roundup.c diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp index 40ff1bf..a2b8ae2 100644 --- a/media/libmedia/AudioRecord.cpp +++ b/media/libmedia/AudioRecord.cpp @@ -563,7 +563,7 @@ create_new_record: } } // read the server count again - start_loop_here: +start_loop_here: framesReady = mProxy->framesReady(); } cblk->lock.unlock(); diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp index 7eeb4f8..77fc6f6 100644 --- a/media/libmedia/AudioTrack.cpp +++ b/media/libmedia/AudioTrack.cpp @@ -561,6 +561,26 @@ status_t AudioTrack::setLoop_l(uint32_t loopStart, uint32_t loopEnd, int loopCou return INVALID_OPERATION; } + if (loopCount < 0 && loopCount != -1) { + return BAD_VALUE; + } + +#if 0 + // This will be for the new interpretation of loopStart and loopEnd + + if (loopCount != 0) { + if (loopStart >= mFrameCount || loopEnd >= mFrameCount || loopStart >= loopEnd) { + return BAD_VALUE; + } + uint32_t periodFrames = loopEnd - loopStart; + if (periodFrames < PERIOD_FRAMES_MIN) { + return BAD_VALUE; + } + } + + // The remainder of this code still uses the old interpretation +#endif + audio_track_cblk_t* cblk = mCblk; Mutex::Autolock _l(cblk->lock); @@ -656,6 +676,16 @@ status_t AudioTrack::setPosition(uint32_t position) return INVALID_OPERATION; } +#if 0 + // This will be for the new interpretation of position + + if (position >= mFrameCount) { + return BAD_VALUE; + } + + // The remainder of this code still uses the old interpretation +#endif + audio_track_cblk_t* cblk = mCblk; Mutex::Autolock _l(cblk->lock); @@ -680,6 +710,21 @@ status_t AudioTrack::getPosition(uint32_t *position) return NO_ERROR; } +#if 0 +status_t AudioTrack::getBufferPosition(uint32_t *position) +{ + if (mSharedBuffer == 0 || mIsTimed) { + return INVALID_OPERATION; + } + if (position == NULL) { + return BAD_VALUE; + } + *position = 0; + + return NO_ERROR; +} +#endif + status_t AudioTrack::reload() { if (mStatus != NO_ERROR) { @@ -816,7 +861,11 @@ status_t AudioTrack::createTrack_l( // Ensure that buffer depth covers at least audio hardware latency uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSampleRate); - if (minBufCount < 2) minBufCount = 2; + ALOGV("afFrameCount=%d, minBufCount=%d, afSampleRate=%u, afLatency=%d", + afFrameCount, minBufCount, afSampleRate, afLatency); + if (minBufCount <= 2) { + minBufCount = sampleRate == afSampleRate ? 2 : 3; + } size_t minFrameCount = (afFrameCount*sampleRate*minBufCount)/afSampleRate; ALOGV("minFrameCount: %u, afFrameCount=%d, minBufCount=%d, sampleRate=%u, afSampleRate=%u" diff --git a/media/libmedia/IHDCP.cpp b/media/libmedia/IHDCP.cpp index f13addc..a46ff91 100644 --- a/media/libmedia/IHDCP.cpp +++ b/media/libmedia/IHDCP.cpp @@ -31,6 +31,7 @@ enum { HDCP_INIT_ASYNC, HDCP_SHUTDOWN_ASYNC, HDCP_ENCRYPT, + HDCP_ENCRYPT_NATIVE, HDCP_DECRYPT, }; @@ -108,6 +109,31 @@ struct BpHDCP : public BpInterface<IHDCP> { return err; } + virtual status_t encryptNative( + const sp<GraphicBuffer> &graphicBuffer, + size_t offset, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData) { + Parcel data, reply; + data.writeInterfaceToken(IHDCP::getInterfaceDescriptor()); + data.write(*graphicBuffer); + data.writeInt32(offset); + data.writeInt32(size); + data.writeInt32(streamCTR); + remote()->transact(HDCP_ENCRYPT_NATIVE, data, &reply); + + status_t err = reply.readInt32(); + + if (err != OK) { + *outInputCTR = 0; + return err; + } + + *outInputCTR = reply.readInt64(); + reply.read(outData, size); + + return err; + } + virtual status_t decrypt( const void *inData, size_t size, uint32_t streamCTR, uint64_t inputCTR, @@ -222,6 +248,34 @@ status_t BnHDCP::onTransact( return OK; } + case HDCP_ENCRYPT_NATIVE: + { + CHECK_INTERFACE(IHDCP, data, reply); + + sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(); + data.read(*graphicBuffer); + size_t offset = data.readInt32(); + size_t size = data.readInt32(); + uint32_t streamCTR = data.readInt32(); + void *outData = malloc(size); + uint64_t inputCTR; + + status_t 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: { size_t size = data.readInt32(); diff --git a/media/libmedia/IMediaDeathNotifier.cpp b/media/libmedia/IMediaDeathNotifier.cpp index 9199db6..9db5b1b 100644 --- a/media/libmedia/IMediaDeathNotifier.cpp +++ b/media/libmedia/IMediaDeathNotifier.cpp @@ -49,10 +49,10 @@ IMediaDeathNotifier::getMediaPlayerService() } while (true); if (sDeathNotifier == NULL) { - sDeathNotifier = new DeathNotifier(); - } - binder->linkToDeath(sDeathNotifier); - sMediaPlayerService = interface_cast<IMediaPlayerService>(binder); + sDeathNotifier = new DeathNotifier(); + } + binder->linkToDeath(sDeathNotifier); + sMediaPlayerService = interface_cast<IMediaPlayerService>(binder); } ALOGE_IF(sMediaPlayerService == 0, "no media player service!?"); return sMediaPlayerService; diff --git a/media/libmedia/JetPlayer.cpp b/media/libmedia/JetPlayer.cpp index 59e538f..8fe5bb3 100644 --- a/media/libmedia/JetPlayer.cpp +++ b/media/libmedia/JetPlayer.cpp @@ -39,7 +39,6 @@ JetPlayer::JetPlayer(void *javaJetPlayer, int maxTracks, int trackBufferSize) : mMaxTracks(maxTracks), mEasData(NULL), mEasJetFileLoc(NULL), - mAudioTrack(NULL), mTrackBufferSize(trackBufferSize) { ALOGV("JetPlayer constructor"); @@ -140,11 +139,10 @@ int JetPlayer::release() free(mEasJetFileLoc); mEasJetFileLoc = NULL; } - if (mAudioTrack) { + if (mAudioTrack != 0) { mAudioTrack->stop(); mAudioTrack->flush(); - delete mAudioTrack; - mAudioTrack = NULL; + mAudioTrack.clear(); } if (mAudioBuffer) { delete mAudioBuffer; diff --git a/media/libmedia/MediaScannerClient.cpp b/media/libmedia/MediaScannerClient.cpp index e1e3348..93a4a4c 100644 --- a/media/libmedia/MediaScannerClient.cpp +++ b/media/libmedia/MediaScannerClient.cpp @@ -16,7 +16,7 @@ #include <media/mediascanner.h> -#include <utils/StringArray.h> +#include "StringArray.h" #include "autodetect.h" #include "unicode/ucnv.h" diff --git a/media/libmedia/SoundPool.cpp b/media/libmedia/SoundPool.cpp index ee70ef7..e1e88ec 100644 --- a/media/libmedia/SoundPool.cpp +++ b/media/libmedia/SoundPool.cpp @@ -547,8 +547,8 @@ void SoundChannel::init(SoundPool* soundPool) void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume, float rightVolume, int priority, int loop, float rate) { - AudioTrack* oldTrack; - AudioTrack* newTrack; + sp<AudioTrack> oldTrack; + sp<AudioTrack> newTrack; status_t status; { // scope for the lock @@ -620,7 +620,7 @@ void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftV ALOGE("Error creating AudioTrack"); goto exit; } - ALOGV("setVolume %p", newTrack); + ALOGV("setVolume %p", newTrack.get()); newTrack->setVolume(leftVolume, rightVolume); newTrack->setLoop(0, frameCount, loop); @@ -643,11 +643,9 @@ void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftV } exit: - ALOGV("delete oldTrack %p", oldTrack); - delete oldTrack; + ALOGV("delete oldTrack %p", oldTrack.get()); if (status != NO_ERROR) { - delete newTrack; - mAudioTrack = NULL; + mAudioTrack.clear(); } } @@ -884,7 +882,7 @@ SoundChannel::~SoundChannel() } // do not call AudioTrack destructor with mLock held as it will wait for the AudioTrack // callback thread to exit which may need to execute process() and acquire the mLock. - delete mAudioTrack; + mAudioTrack.clear(); } void SoundChannel::dump() diff --git a/media/libmedia/StringArray.cpp b/media/libmedia/StringArray.cpp new file mode 100644 index 0000000..5f5b57a --- /dev/null +++ b/media/libmedia/StringArray.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Sortable array of strings. STL-ish, but STL-free. +// + +#include <stdlib.h> +#include <string.h> + +#include "StringArray.h" + +namespace android { + +// +// An expanding array of strings. Add, get, sort, delete. +// +StringArray::StringArray() + : mMax(0), mCurrent(0), mArray(NULL) +{ +} + +StringArray:: ~StringArray() { + for (int i = 0; i < mCurrent; i++) + delete[] mArray[i]; + delete[] mArray; +} + +// +// Add a string. A copy of the string is made. +// +bool StringArray::push_back(const char* str) { + if (mCurrent >= mMax) { + char** tmp; + + if (mMax == 0) + mMax = 16; // initial storage + else + mMax *= 2; + + tmp = new char*[mMax]; + if (tmp == NULL) + return false; + + memcpy(tmp, mArray, mCurrent * sizeof(char*)); + delete[] mArray; + mArray = tmp; + } + + int len = strlen(str); + mArray[mCurrent] = new char[len+1]; + memcpy(mArray[mCurrent], str, len+1); + mCurrent++; + + return true; +} + +// +// Delete an entry. +// +void StringArray::erase(int idx) { + if (idx < 0 || idx >= mCurrent) + return; + delete[] mArray[idx]; + if (idx < mCurrent-1) { + memmove(&mArray[idx], &mArray[idx+1], + (mCurrent-1 - idx) * sizeof(char*)); + } + mCurrent--; +} + +// +// Sort the array. +// +void StringArray::sort(int (*compare)(const void*, const void*)) { + qsort(mArray, mCurrent, sizeof(char*), compare); +} + +// +// Pass this to the sort routine to do an ascending alphabetical sort. +// +int StringArray::cmpAscendingAlpha(const void* pstr1, const void* pstr2) { + return strcmp(*(const char**)pstr1, *(const char**)pstr2); +} + +// +// Set entry N to specified string. +// [should use operator[] here] +// +void StringArray::setEntry(int idx, const char* str) { + if (idx < 0 || idx >= mCurrent) + return; + delete[] mArray[idx]; + int len = strlen(str); + mArray[idx] = new char[len+1]; + memcpy(mArray[idx], str, len+1); +} + + +}; // namespace android diff --git a/media/libmedia/StringArray.h b/media/libmedia/StringArray.h new file mode 100644 index 0000000..ae47085 --- /dev/null +++ b/media/libmedia/StringArray.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Sortable array of strings. STL-ish, but STL-free. +// +#ifndef _LIBS_MEDIA_STRING_ARRAY_H +#define _LIBS_MEDIA_STRING_ARRAY_H + +#include <stdlib.h> +#include <string.h> + +namespace android { + +// +// An expanding array of strings. Add, get, sort, delete. +// +class StringArray { +public: + StringArray(); + virtual ~StringArray(); + + // + // Add a string. A copy of the string is made. + // + bool push_back(const char* str); + + // + // Delete an entry. + // + void erase(int idx); + + // + // Sort the array. + // + void sort(int (*compare)(const void*, const void*)); + + // + // Pass this to the sort routine to do an ascending alphabetical sort. + // + static int cmpAscendingAlpha(const void* pstr1, const void* pstr2); + + // + // Get the #of items in the array. + // + inline int size(void) const { return mCurrent; } + + // + // Return entry N. + // [should use operator[] here] + // + const char* getEntry(int idx) const { + return (unsigned(idx) >= unsigned(mCurrent)) ? NULL : mArray[idx]; + } + + // + // Set entry N to specified string. + // [should use operator[] here] + // + void setEntry(int idx, const char* str); + +private: + int mMax; + int mCurrent; + char** mArray; +}; + +}; // namespace android + +#endif // _LIBS_MEDIA_STRING_ARRAY_H diff --git a/media/libmedia/ToneGenerator.cpp b/media/libmedia/ToneGenerator.cpp index f55b697..ebe1ba1 100644 --- a/media/libmedia/ToneGenerator.cpp +++ b/media/libmedia/ToneGenerator.cpp @@ -803,7 +803,6 @@ ToneGenerator::ToneGenerator(audio_stream_type_t streamType, float volume, bool ALOGV("ToneGenerator constructor: streamType=%d, volume=%f", streamType, volume); mState = TONE_IDLE; - mpAudioTrack = NULL; if (AudioSystem::getOutputSamplingRate(&mSamplingRate, streamType) != NO_ERROR) { ALOGE("Unable to marshal AudioFlinger"); @@ -855,10 +854,10 @@ ToneGenerator::ToneGenerator(audio_stream_type_t streamType, float volume, bool ToneGenerator::~ToneGenerator() { ALOGV("ToneGenerator destructor"); - if (mpAudioTrack != NULL) { + if (mpAudioTrack != 0) { stopTone(); - ALOGV("Delete Track: %p", mpAudioTrack); - delete mpAudioTrack; + ALOGV("Delete Track: %p", mpAudioTrack.get()); + mpAudioTrack.clear(); } } @@ -1047,14 +1046,9 @@ void ToneGenerator::stopTone() { //////////////////////////////////////////////////////////////////////////////// bool ToneGenerator::initAudioTrack() { - if (mpAudioTrack) { - delete mpAudioTrack; - mpAudioTrack = NULL; - } - // Open audio track in mono, PCM 16bit, default sampling rate, default buffer size mpAudioTrack = new AudioTrack(); - ALOGV("Create Track: %p", mpAudioTrack); + ALOGV("Create Track: %p", mpAudioTrack.get()); mpAudioTrack->set(mStreamType, 0, // sampleRate @@ -1081,12 +1075,10 @@ bool ToneGenerator::initAudioTrack() { initAudioTrack_exit: + ALOGV("Init failed: %p", mpAudioTrack.get()); + // Cleanup - if (mpAudioTrack != NULL) { - ALOGV("Delete Track I: %p", mpAudioTrack); - delete mpAudioTrack; - mpAudioTrack = NULL; - } + mpAudioTrack.clear(); return false; } diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk index d87bc7f..8f21632 100644 --- a/media/libmediaplayerservice/Android.mk +++ b/media/libmediaplayerservice/Android.mk @@ -34,6 +34,7 @@ LOCAL_SHARED_LIBRARIES := \ libsonivox \ libstagefright \ libstagefright_foundation \ + libstagefright_httplive \ libstagefright_omx \ libstagefright_wfd \ libutils \ diff --git a/media/libmediaplayerservice/HDCP.cpp b/media/libmediaplayerservice/HDCP.cpp index 469a02e..8a3188c 100644 --- a/media/libmediaplayerservice/HDCP.cpp +++ b/media/libmediaplayerservice/HDCP.cpp @@ -116,6 +116,24 @@ status_t HDCP::encrypt( return mHDCPModule->encrypt(inData, size, streamCTR, outInputCTR, outData); } +status_t HDCP::encryptNative( + const sp<GraphicBuffer> &graphicBuffer, + size_t offset, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData) { + Mutex::Autolock autoLock(mLock); + + CHECK(mIsEncryptionModule); + + if (mHDCPModule == NULL) { + *outInputCTR = 0; + + return NO_INIT; + } + + return mHDCPModule->encryptNative(graphicBuffer->handle, + offset, size, streamCTR, outInputCTR, outData); +} + status_t HDCP::decrypt( const void *inData, size_t size, uint32_t streamCTR, uint64_t outInputCTR, void *outData) { diff --git a/media/libmediaplayerservice/HDCP.h b/media/libmediaplayerservice/HDCP.h index 42e6467..c60c2e0 100644 --- a/media/libmediaplayerservice/HDCP.h +++ b/media/libmediaplayerservice/HDCP.h @@ -35,6 +35,11 @@ struct HDCP : public BnHDCP { const void *inData, size_t size, uint32_t streamCTR, uint64_t *outInputCTR, void *outData); + virtual status_t encryptNative( + const sp<GraphicBuffer> &graphicBuffer, + size_t offset, size_t size, uint32_t streamCTR, + uint64_t *outInputCTR, void *outData); + virtual status_t decrypt( const void *inData, size_t size, uint32_t streamCTR, uint64_t outInputCTR, void *outData); diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index e600a3f..fa1ff36 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -1295,8 +1295,6 @@ MediaPlayerService::AudioOutput::AudioOutput(int sessionId) mSessionId(sessionId), mFlags(AUDIO_OUTPUT_FLAG_NONE) { ALOGV("AudioOutput(%d)", sessionId); - mTrack = 0; - mRecycledTrack = 0; mStreamType = AUDIO_STREAM_MUSIC; mLeftVolume = 1.0; mRightVolume = 1.0; @@ -1311,7 +1309,6 @@ MediaPlayerService::AudioOutput::AudioOutput(int sessionId) MediaPlayerService::AudioOutput::~AudioOutput() { close(); - delete mRecycledTrack; delete mCallbackData; } @@ -1422,7 +1419,7 @@ status_t MediaPlayerService::AudioOutput::open( } } - AudioTrack *t; + sp<AudioTrack> t; CallbackData *newcbd = NULL; if (mCallback != NULL) { newcbd = new CallbackData(this); @@ -1453,13 +1450,12 @@ status_t MediaPlayerService::AudioOutput::open( if ((t == 0) || (t->initCheck() != NO_ERROR)) { ALOGE("Unable to create audio track"); - delete t; delete newcbd; return NO_INIT; } - if (mRecycledTrack) { + if (mRecycledTrack != 0) { // check if the existing track can be reused as-is, or if a new track needs to be created. bool reuse = true; @@ -1484,11 +1480,10 @@ status_t MediaPlayerService::AudioOutput::open( ALOGV("chaining to next output"); close(); mTrack = mRecycledTrack; - mRecycledTrack = NULL; + mRecycledTrack.clear(); if (mCallbackData != NULL) { mCallbackData->setOutput(this); } - delete t; delete newcbd; return OK; } @@ -1499,8 +1494,7 @@ status_t MediaPlayerService::AudioOutput::open( mCallbackData->endTrackSwitch(); } mRecycledTrack->flush(); - delete mRecycledTrack; - mRecycledTrack = NULL; + mRecycledTrack.clear(); delete mCallbackData; mCallbackData = NULL; close(); @@ -1533,7 +1527,7 @@ void MediaPlayerService::AudioOutput::start() if (mCallbackData != NULL) { mCallbackData->endTrackSwitch(); } - if (mTrack) { + if (mTrack != 0) { mTrack->setVolume(mLeftVolume, mRightVolume); mTrack->setAuxEffectSendLevel(mSendLevel); mTrack->start(); @@ -1555,7 +1549,7 @@ void MediaPlayerService::AudioOutput::switchToNextOutput() { mNextOutput->mCallbackData = mCallbackData; mCallbackData = NULL; mNextOutput->mRecycledTrack = mTrack; - mTrack = NULL; + mTrack.clear(); mNextOutput->mSampleRateHz = mSampleRateHz; mNextOutput->mMsecsPerFrame = mMsecsPerFrame; mNextOutput->mBytesWritten = mBytesWritten; @@ -1568,7 +1562,7 @@ ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size) LOG_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback."); //ALOGV("write(%p, %u)", buffer, size); - if (mTrack) { + if (mTrack != 0) { ssize_t ret = mTrack->write(buffer, size); mBytesWritten += ret; return ret; @@ -1579,26 +1573,25 @@ ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size) void MediaPlayerService::AudioOutput::stop() { ALOGV("stop"); - if (mTrack) mTrack->stop(); + if (mTrack != 0) mTrack->stop(); } void MediaPlayerService::AudioOutput::flush() { ALOGV("flush"); - if (mTrack) mTrack->flush(); + if (mTrack != 0) mTrack->flush(); } void MediaPlayerService::AudioOutput::pause() { ALOGV("pause"); - if (mTrack) mTrack->pause(); + if (mTrack != 0) mTrack->pause(); } void MediaPlayerService::AudioOutput::close() { ALOGV("close"); - delete mTrack; - mTrack = 0; + mTrack.clear(); } void MediaPlayerService::AudioOutput::setVolume(float left, float right) @@ -1606,7 +1599,7 @@ void MediaPlayerService::AudioOutput::setVolume(float left, float right) ALOGV("setVolume(%f, %f)", left, right); mLeftVolume = left; mRightVolume = right; - if (mTrack) { + if (mTrack != 0) { mTrack->setVolume(left, right); } } @@ -1615,7 +1608,7 @@ status_t MediaPlayerService::AudioOutput::setPlaybackRatePermille(int32_t ratePe { ALOGV("setPlaybackRatePermille(%d)", ratePermille); status_t res = NO_ERROR; - if (mTrack) { + if (mTrack != 0) { res = mTrack->setSampleRate(ratePermille * mSampleRateHz / 1000); } else { res = NO_INIT; @@ -1631,7 +1624,7 @@ status_t MediaPlayerService::AudioOutput::setAuxEffectSendLevel(float level) { ALOGV("setAuxEffectSendLevel(%f)", level); mSendLevel = level; - if (mTrack) { + if (mTrack != 0) { return mTrack->setAuxEffectSendLevel(level); } return NO_ERROR; @@ -1641,7 +1634,7 @@ status_t MediaPlayerService::AudioOutput::attachAuxEffect(int effectId) { ALOGV("attachAuxEffect(%d)", effectId); mAuxEffectId = effectId; - if (mTrack) { + if (mTrack != 0) { return mTrack->attachAuxEffect(effectId); } return NO_ERROR; diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index b33805d..e586156 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -78,7 +78,7 @@ class MediaPlayerService : public BnMediaPlayerService AudioOutput(int sessionId); virtual ~AudioOutput(); - virtual bool ready() const { return mTrack != NULL; } + virtual bool ready() const { return mTrack != 0; } virtual bool realtime() const { return true; } virtual ssize_t bufferSize() const; virtual ssize_t frameCount() const; @@ -120,8 +120,8 @@ class MediaPlayerService : public BnMediaPlayerService static void CallbackWrapper( int event, void *me, void *info); - AudioTrack* mTrack; - AudioTrack* mRecycledTrack; + sp<AudioTrack> mTrack; + sp<AudioTrack> mRecycledTrack; sp<AudioOutput> mNextOutput; AudioCallback mCallback; void * mCallbackCookie; diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp index 655ee55..c8901ce 100644 --- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp @@ -20,7 +20,6 @@ #include "HTTPLiveSource.h" -#include "ATSParser.h" #include "AnotherPacketSource.h" #include "LiveDataSource.h" #include "LiveSession.h" @@ -62,7 +61,10 @@ NuPlayer::HTTPLiveSource::HTTPLiveSource( NuPlayer::HTTPLiveSource::~HTTPLiveSource() { if (mLiveSession != NULL) { mLiveSession->disconnect(); + mLiveSession.clear(); + mLiveLooper->stop(); + mLiveLooper.clear(); } } @@ -76,112 +78,42 @@ void NuPlayer::HTTPLiveSource::prepareAsync() { mLiveSession = new LiveSession( notify, (mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0, - mUIDValid, mUID); + mUIDValid, + mUID); mLiveLooper->registerHandler(mLiveSession); - mLiveSession->connect( + mLiveSession->connectAsync( mURL.c_str(), mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders); - - mTSParser = new ATSParser; } void NuPlayer::HTTPLiveSource::start() { } -sp<MetaData> NuPlayer::HTTPLiveSource::getFormatMeta(bool audio) { - ATSParser::SourceType type = - audio ? ATSParser::AUDIO : ATSParser::VIDEO; - - sp<AnotherPacketSource> source = - static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get()); +sp<AMessage> NuPlayer::HTTPLiveSource::getFormat(bool audio) { + sp<AMessage> format; + status_t err = mLiveSession->getStreamFormat( + audio ? LiveSession::STREAMTYPE_AUDIO + : LiveSession::STREAMTYPE_VIDEO, + &format); - if (source == NULL) { + if (err != OK) { return NULL; } - return source->getFormat(); + return format; } status_t NuPlayer::HTTPLiveSource::feedMoreTSData() { - if (mFinalResult != OK) { - return mFinalResult; - } - - sp<LiveDataSource> source = - static_cast<LiveDataSource *>(mLiveSession->getDataSource().get()); - - for (int32_t i = 0; i < 50; ++i) { - char buffer[188]; - ssize_t n = source->readAtNonBlocking(mOffset, buffer, sizeof(buffer)); - - if (n == -EWOULDBLOCK) { - break; - } else if (n < 0) { - if (n != ERROR_END_OF_STREAM) { - ALOGI("input data EOS reached, error %ld", n); - } else { - ALOGI("input data EOS reached."); - } - mTSParser->signalEOS(n); - mFinalResult = n; - break; - } else { - if (buffer[0] == 0x00) { - // XXX legacy - - uint8_t type = buffer[1]; - - sp<AMessage> extra = new AMessage; - - if (type & 2) { - int64_t mediaTimeUs; - memcpy(&mediaTimeUs, &buffer[2], sizeof(mediaTimeUs)); - - extra->setInt64(IStreamListener::kKeyMediaTimeUs, mediaTimeUs); - } - - mTSParser->signalDiscontinuity( - ((type & 1) == 0) - ? ATSParser::DISCONTINUITY_SEEK - : ATSParser::DISCONTINUITY_FORMATCHANGE, - extra); - } else { - status_t err = mTSParser->feedTSPacket(buffer, sizeof(buffer)); - - if (err != OK) { - ALOGE("TS Parser returned error %d", err); - mTSParser->signalEOS(err); - mFinalResult = err; - break; - } - } - - mOffset += n; - } - } - return OK; } status_t NuPlayer::HTTPLiveSource::dequeueAccessUnit( bool audio, sp<ABuffer> *accessUnit) { - ATSParser::SourceType type = - audio ? ATSParser::AUDIO : ATSParser::VIDEO; - - sp<AnotherPacketSource> source = - static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get()); - - if (source == NULL) { - return -EWOULDBLOCK; - } - - status_t finalResult; - if (!source->hasBufferAvailable(&finalResult)) { - return finalResult == OK ? -EWOULDBLOCK : finalResult; - } - - return source->dequeueAccessUnit(accessUnit); + return mLiveSession->dequeueAccessUnit( + audio ? LiveSession::STREAMTYPE_AUDIO + : LiveSession::STREAMTYPE_VIDEO, + accessUnit); } status_t NuPlayer::HTTPLiveSource::getDuration(int64_t *durationUs) { @@ -189,15 +121,7 @@ status_t NuPlayer::HTTPLiveSource::getDuration(int64_t *durationUs) { } status_t NuPlayer::HTTPLiveSource::seekTo(int64_t seekTimeUs) { - // We need to make sure we're not seeking until we have seen the very first - // PTS timestamp in the whole stream (from the beginning of the stream). - while (!mTSParser->PTSTimeDeltaEstablished() && feedMoreTSData() == OK) { - usleep(100000); - } - - mLiveSession->seekTo(seekTimeUs); - - return OK; + return mLiveSession->seekTo(seekTimeUs); } void NuPlayer::HTTPLiveSource::onMessageReceived(const sp<AMessage> &msg) { @@ -249,6 +173,32 @@ void NuPlayer::HTTPLiveSource::onSessionNotify(const sp<AMessage> &msg) { break; } + case LiveSession::kWhatStreamsChanged: + { + uint32_t changedMask; + CHECK(msg->findInt32( + "changedMask", (int32_t *)&changedMask)); + + bool audio = changedMask & LiveSession::STREAMTYPE_AUDIO; + bool video = changedMask & LiveSession::STREAMTYPE_VIDEO; + + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + sp<AMessage> notify = dupNotify(); + notify->setInt32("what", kWhatQueueDecoderShutdown); + notify->setInt32("audio", audio); + notify->setInt32("video", video); + notify->setMessage("reply", reply); + notify->post(); + break; + } + + case LiveSession::kWhatError: + { + break; + } + default: TRESPASS(); } diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h index 067d1da..aa9434b 100644 --- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h @@ -23,7 +23,6 @@ namespace android { -struct ATSParser; struct LiveSession; struct NuPlayer::HTTPLiveSource : public NuPlayer::Source { @@ -37,18 +36,16 @@ struct NuPlayer::HTTPLiveSource : public NuPlayer::Source { virtual void prepareAsync(); virtual void start(); - virtual status_t feedMoreTSData(); - virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit); + virtual sp<AMessage> getFormat(bool audio); + virtual status_t feedMoreTSData(); virtual status_t getDuration(int64_t *durationUs); virtual status_t seekTo(int64_t seekTimeUs); protected: virtual ~HTTPLiveSource(); - virtual sp<MetaData> getFormatMeta(bool audio); - virtual void onMessageReceived(const sp<AMessage> &msg); private: @@ -70,7 +67,6 @@ private: off64_t mOffset; sp<ALooper> mLiveLooper; sp<LiveSession> mLiveSession; - sp<ATSParser> mTSParser; void onSessionNotify(const sp<AMessage> &msg); diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp index b89b1c8..7e81035 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp @@ -89,6 +89,38 @@ private: DISALLOW_EVIL_CONSTRUCTORS(SetSurfaceAction); }; +struct NuPlayer::ShutdownDecoderAction : public Action { + ShutdownDecoderAction(bool audio, bool video) + : mAudio(audio), + mVideo(video) { + } + + virtual void execute(NuPlayer *player) { + player->performDecoderShutdown(mAudio, mVideo); + } + +private: + bool mAudio; + bool mVideo; + + DISALLOW_EVIL_CONSTRUCTORS(ShutdownDecoderAction); +}; + +struct NuPlayer::PostMessageAction : public Action { + PostMessageAction(const sp<AMessage> &msg) + : mMessage(msg) { + } + + virtual void execute(NuPlayer *) { + mMessage->post(); + } + +private: + sp<AMessage> mMessage; + + DISALLOW_EVIL_CONSTRUCTORS(PostMessageAction); +}; + // Use this if there's no state necessary to save in order to execute // the action. struct NuPlayer::SimpleAction : public Action { @@ -335,7 +367,8 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { ALOGV("kWhatSetVideoNativeWindow"); mDeferredActions.push_back( - new SimpleAction(&NuPlayer::performDecoderShutdown)); + new ShutdownDecoderAction( + false /* audio */, true /* video */)); sp<RefBase> obj; CHECK(msg->findObject("native-window", &obj)); @@ -712,7 +745,8 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { ALOGV("kWhatReset"); mDeferredActions.push_back( - new SimpleAction(&NuPlayer::performDecoderShutdown)); + new ShutdownDecoderAction( + true /* audio */, true /* video */)); mDeferredActions.push_back( new SimpleAction(&NuPlayer::performReset)); @@ -1023,6 +1057,9 @@ void NuPlayer::notifyListener(int msg, int ext1, int ext2) { } void NuPlayer::flushDecoder(bool audio, bool needShutdown) { + ALOGV("[%s] flushDecoder needShutdown=%d", + audio ? "audio" : "video", needShutdown); + if ((audio && mAudioDecoder == NULL) || (!audio && mVideoDecoder == NULL)) { ALOGI("flushDecoder %s without decoder present", audio ? "audio" : "video"); @@ -1173,20 +1210,29 @@ void NuPlayer::performDecoderFlush() { } } -void NuPlayer::performDecoderShutdown() { - ALOGV("performDecoderShutdown"); +void NuPlayer::performDecoderShutdown(bool audio, bool video) { + ALOGV("performDecoderShutdown audio=%d, video=%d", audio, video); - if (mAudioDecoder == NULL && mVideoDecoder == NULL) { + if ((!audio || mAudioDecoder == NULL) + && (!video || mVideoDecoder == NULL)) { return; } mTimeDiscontinuityPending = true; - if (mAudioDecoder != NULL) { + if (mFlushingAudio == NONE && (!audio || mAudioDecoder == NULL)) { + mFlushingAudio = FLUSHED; + } + + if (mFlushingVideo == NONE && (!video || mVideoDecoder == NULL)) { + mFlushingVideo = FLUSHED; + } + + if (audio && mAudioDecoder != NULL) { flushDecoder(true /* audio */, true /* needShutdown */); } - if (mVideoDecoder != NULL) { + if (video && mVideoDecoder != NULL) { flushDecoder(false /* audio */, true /* needShutdown */); } } @@ -1322,6 +1368,19 @@ void NuPlayer::onSourceNotify(const sp<AMessage> &msg) { break; } + case Source::kWhatQueueDecoderShutdown: + { + int32_t audio, video; + CHECK(msg->findInt32("audio", &audio)); + CHECK(msg->findInt32("video", &video)); + + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + queueDecoderShutdown(audio, video, reply); + break; + } + default: TRESPASS(); } @@ -1355,4 +1414,19 @@ void NuPlayer::Source::onMessageReceived(const sp<AMessage> &msg) { TRESPASS(); } +void NuPlayer::queueDecoderShutdown( + bool audio, bool video, const sp<AMessage> &reply) { + ALOGI("queueDecoderShutdown audio=%d, video=%d", audio, video); + + mDeferredActions.push_back( + new ShutdownDecoderAction(audio, video)); + + mDeferredActions.push_back( + new SimpleAction(&NuPlayer::performScanSources)); + + mDeferredActions.push_back(new PostMessageAction(reply)); + + processDeferredActions(); +} + } // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h index 50d0462..8b6c8c1 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h @@ -80,6 +80,8 @@ private: struct Action; struct SeekAction; struct SetSurfaceAction; + struct ShutdownDecoderAction; + struct PostMessageAction; struct SimpleAction; enum { @@ -172,13 +174,16 @@ private: void performSeek(int64_t seekTimeUs); void performDecoderFlush(); - void performDecoderShutdown(); + void performDecoderShutdown(bool audio, bool video); void performReset(); void performScanSources(); void performSetSurface(const sp<NativeWindowWrapper> &wrapper); void onSourceNotify(const sp<AMessage> &msg); + void queueDecoderShutdown( + bool audio, bool video, const sp<AMessage> &reply); + DISALLOW_EVIL_CONSTRUCTORS(NuPlayer); }; diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp index 404b56f..b543d9d 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp @@ -95,11 +95,11 @@ void NuPlayer::Renderer::flush(bool audio) { } void NuPlayer::Renderer::signalTimeDiscontinuity() { - CHECK(mAudioQueue.empty()); - CHECK(mVideoQueue.empty()); + // CHECK(mAudioQueue.empty()); + // CHECK(mVideoQueue.empty()); mAnchorTimeMediaUs = -1; mAnchorTimeRealUs = -1; - mSyncQueues = mHasAudio && mHasVideo; + mSyncQueues = false; } void NuPlayer::Renderer::pause() { diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h index 1cbf575..81ffd21 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h @@ -42,6 +42,7 @@ struct NuPlayer::Source : public AHandler { kWhatVideoSizeChanged, kWhatBufferingStart, kWhatBufferingEnd, + kWhatQueueDecoderShutdown, }; // The provides message is used to notify the player about various diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp index cf41cf2..bf650b4 100644 --- a/media/libstagefright/ACodec.cpp +++ b/media/libstagefright/ACodec.cpp @@ -359,6 +359,7 @@ ACodec::ACodec() mNode(NULL), mSentFormat(false), mIsEncoder(false), + mUseMetadataOnEncoderOutput(false), mShutdownInProgress(false), mEncoderDelay(0), mEncoderPadding(0), @@ -483,7 +484,8 @@ status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) { ? OMXCodec::kRequiresAllocateBufferOnInputPorts : OMXCodec::kRequiresAllocateBufferOnOutputPorts; - if (portIndex == kPortIndexInput && (mFlags & kFlagIsSecure)) { + if ((portIndex == kPortIndexInput && (mFlags & kFlagIsSecure)) + || mUseMetadataOnEncoderOutput) { mem.clear(); void *ptr; @@ -491,7 +493,10 @@ status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) { mNode, portIndex, def.nBufferSize, &info.mBufferID, &ptr); - info.mData = new ABuffer(ptr, def.nBufferSize); + int32_t bufSize = mUseMetadataOnEncoderOutput ? + (4 + sizeof(buffer_handle_t)) : def.nBufferSize; + + info.mData = new ABuffer(ptr, bufSize); } else if (mQuirks & requiresAllocateBufferBit) { err = mOMX->allocateBufferWithBackup( mNode, portIndex, mem, &info.mBufferID); @@ -912,14 +917,14 @@ status_t ACodec::configureCodec( err = mOMX->storeMetaDataInBuffers(mNode, kPortIndexInput, OMX_TRUE); if (err != OK) { - ALOGE("[%s] storeMetaDataInBuffers failed w/ err %d", - mComponentName.c_str(), err); + ALOGE("[%s] storeMetaDataInBuffers (input) failed w/ err %d", + mComponentName.c_str(), err); - return err; - } - } + return err; + } + } - int32_t prependSPSPPS; + int32_t prependSPSPPS = 0; if (encoder && msg->findInt32("prepend-sps-pps-to-idr-frames", &prependSPSPPS) && prependSPSPPS != 0) { @@ -946,7 +951,27 @@ status_t ACodec::configureCodec( } } - if (!strncasecmp(mime, "video/", 6)) { + // Only enable metadata mode on encoder output if encoder can prepend + // sps/pps to idr frames, since in metadata mode the bitstream is in an + // opaque handle, to which we don't have access. + int32_t video = !strncasecmp(mime, "video/", 6); + if (encoder && video) { + OMX_BOOL enable = (OMX_BOOL) (prependSPSPPS + && msg->findInt32("store-metadata-in-buffers-output", &storeMeta) + && storeMeta != 0); + + err = mOMX->storeMetaDataInBuffers(mNode, kPortIndexOutput, enable); + + if (err != OK) { + ALOGE("[%s] storeMetaDataInBuffers (output) failed w/ err %d", + mComponentName.c_str(), err); + mUseMetadataOnEncoderOutput = 0; + } else { + mUseMetadataOnEncoderOutput = enable; + } + } + + if (video) { if (encoder) { err = setupVideoEncoder(mime, msg); } else { @@ -2321,10 +2346,15 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { ¶ms, sizeof(params)), (status_t)OK); + CHECK_GT(params.nChannels, 0); CHECK(params.nChannels == 1 || params.bInterleaved); CHECK_EQ(params.nBitPerSample, 16u); - CHECK_EQ((int)params.eNumData, (int)OMX_NumericalDataSigned); - CHECK_EQ((int)params.ePCMMode, (int)OMX_AUDIO_PCMModeLinear); + + CHECK_EQ((int)params.eNumData, + (int)OMX_NumericalDataSigned); + + CHECK_EQ((int)params.ePCMMode, + (int)OMX_AUDIO_PCMModeLinear); notify->setString("mime", MEDIA_MIMETYPE_AUDIO_RAW); notify->setInt32("channel-count", params.nChannels); @@ -2334,11 +2364,14 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { if (mSkipCutBuffer != NULL) { size_t prevbufsize = mSkipCutBuffer->size(); if (prevbufsize != 0) { - ALOGW("Replacing SkipCutBuffer holding %d bytes", prevbufsize); + ALOGW("Replacing SkipCutBuffer holding %d " + "bytes", + prevbufsize); } } - mSkipCutBuffer = new SkipCutBuffer(mEncoderDelay * frameSize, - mEncoderPadding * frameSize); + mSkipCutBuffer = new SkipCutBuffer( + mEncoderDelay * frameSize, + mEncoderPadding * frameSize); } if (mChannelMaskPresent) { @@ -3062,7 +3095,15 @@ bool ACodec::BaseState::onOMXFillBufferDone( mCodec->sendFormatChange(reply); } - info->mData->setRange(rangeOffset, rangeLength); + if (mCodec->mUseMetadataOnEncoderOutput) { + native_handle_t* handle = + *(native_handle_t**)(info->mData->data() + 4); + info->mData->meta()->setPointer("handle", handle); + info->mData->meta()->setInt32("rangeOffset", rangeOffset); + info->mData->meta()->setInt32("rangeLength", rangeLength); + } else { + info->mData->setRange(rangeOffset, rangeLength); + } #if 0 if (mCodec->mNativeWindow == NULL) { if (IsIDR(info->mData)) { @@ -3220,6 +3261,7 @@ void ACodec::UninitializedState::stateEntered() { mCodec->mOMX.clear(); mCodec->mQuirks = 0; mCodec->mFlags = 0; + mCodec->mUseMetadataOnEncoderOutput = 0; mCodec->mComponentName.clear(); } diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index acc3abf..9544dbc 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -69,7 +69,6 @@ LOCAL_C_INCLUDES:= \ LOCAL_SHARED_LIBRARIES := \ libbinder \ libcamera_client \ - libcrypto \ libcutils \ libdl \ libdrmframework \ @@ -97,7 +96,6 @@ LOCAL_STATIC_LIBRARIES := \ libvpx \ libwebm \ libstagefright_mpeg2ts \ - libstagefright_httplive \ libstagefright_id3 \ libFLAC \ diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index 4208019..92efae8 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -36,8 +36,7 @@ AudioPlayer::AudioPlayer( const sp<MediaPlayerBase::AudioSink> &audioSink, bool allowDeepBuffering, AwesomePlayer *observer) - : mAudioTrack(NULL), - mInputBuffer(NULL), + : mInputBuffer(NULL), mSampleRate(0), mLatencyUs(0), mFrameSize(0), @@ -166,8 +165,7 @@ status_t AudioPlayer::start(bool sourceAlreadyStarted) { 0, AUDIO_OUTPUT_FLAG_NONE, &AudioCallback, this, 0); if ((err = mAudioTrack->initCheck()) != OK) { - delete mAudioTrack; - mAudioTrack = NULL; + mAudioTrack.clear(); if (mFirstBuffer != NULL) { mFirstBuffer->release(); @@ -235,8 +233,7 @@ void AudioPlayer::reset() { } else { mAudioTrack->stop(); - delete mAudioTrack; - mAudioTrack = NULL; + mAudioTrack.clear(); } // Make sure to release any buffer we hold onto so that the @@ -297,7 +294,7 @@ bool AudioPlayer::reachedEOS(status_t *finalStatus) { status_t AudioPlayer::setPlaybackRatePermille(int32_t ratePermille) { if (mAudioSink.get() != NULL) { return mAudioSink->setPlaybackRatePermille(ratePermille); - } else if (mAudioTrack != NULL){ + } else if (mAudioTrack != 0){ return mAudioTrack->setSampleRate(ratePermille * mSampleRate / 1000); } else { return NO_INIT; diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index bd28118..6c197e2 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -597,7 +597,7 @@ void AwesomePlayer::notifyListener_l(int msg, int ext1, int ext2) { bool AwesomePlayer::getBitrate(int64_t *bitrate) { off64_t size; - if (mDurationUs >= 0 && mCachedSource != NULL + if (mDurationUs > 0 && mCachedSource != NULL && mCachedSource->getSize(&size) == OK) { *bitrate = size * 8000000ll / mDurationUs; // in bits/sec return true; diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index 145869e..42a9c7a 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -341,6 +341,7 @@ MPEG4Extractor::MPEG4Extractor(const sp<DataSource> &source) mDataSource(source), mInitCheck(NO_INIT), mHasVideo(false), + mHeaderTimescale(0), mFirstTrack(NULL), mLastTrack(NULL), mFileMetaData(new MetaData), @@ -817,6 +818,7 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { case FOURCC('i', 'l', 's', 't'): case FOURCC('s', 'i', 'n', 'f'): case FOURCC('s', 'c', 'h', 'i'): + case FOURCC('e', 'd', 't', 's'): { if (chunk_type == FOURCC('s', 't', 'b', 'l')) { ALOGV("sampleTable chunk is %d bytes long.", (size_t)chunk_size); @@ -904,6 +906,68 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { break; } + case FOURCC('e', 'l', 's', 't'): + { + // See 14496-12 8.6.6 + uint8_t version; + if (mDataSource->readAt(data_offset, &version, 1) < 1) { + return ERROR_IO; + } + + uint32_t entry_count; + if (!mDataSource->getUInt32(data_offset + 4, &entry_count)) { + return ERROR_IO; + } + + if (entry_count != 1) { + // we only support a single entry at the moment, for gapless playback + ALOGW("ignoring edit list with %d entries", entry_count); + } else if (mHeaderTimescale == 0) { + ALOGW("ignoring edit list because timescale is 0"); + } else { + off64_t entriesoffset = data_offset + 8; + uint64_t segment_duration; + int64_t media_time; + + if (version == 1) { + if (!mDataSource->getUInt64(entriesoffset, &segment_duration) || + !mDataSource->getUInt64(entriesoffset + 8, (uint64_t*)&media_time)) { + return ERROR_IO; + } + } else if (version == 0) { + uint32_t sd; + int32_t mt; + if (!mDataSource->getUInt32(entriesoffset, &sd) || + !mDataSource->getUInt32(entriesoffset + 4, (uint32_t*)&mt)) { + return ERROR_IO; + } + segment_duration = sd; + media_time = mt; + } else { + return ERROR_IO; + } + + uint64_t halfscale = mHeaderTimescale / 2; + segment_duration = (segment_duration * 1000000 + halfscale)/ mHeaderTimescale; + media_time = (media_time * 1000000 + halfscale) / mHeaderTimescale; + + int64_t duration; + int32_t samplerate; + if (mLastTrack->meta->findInt64(kKeyDuration, &duration) && + mLastTrack->meta->findInt32(kKeySampleRate, &samplerate)) { + + int64_t delay = (media_time * samplerate + 500000) / 1000000; + mLastTrack->meta->setInt32(kKeyEncoderDelay, delay); + + int64_t paddingus = duration - (segment_duration + media_time); + int64_t paddingsamples = (paddingus * samplerate + 500000) / 1000000; + mLastTrack->meta->setInt32(kKeyEncoderPadding, paddingsamples); + } + } + *offset += chunk_size; + break; + } + case FOURCC('f', 'r', 'm', 'a'): { uint32_t original_fourcc; @@ -1564,24 +1628,26 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { case FOURCC('m', 'v', 'h', 'd'): { - if (chunk_data_size < 12) { + if (chunk_data_size < 24) { return ERROR_MALFORMED; } - uint8_t header[12]; + uint8_t header[24]; if (mDataSource->readAt( data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { return ERROR_IO; } - int64_t creationTime; + uint64_t creationTime; if (header[0] == 1) { creationTime = U64_AT(&header[4]); + mHeaderTimescale = U32_AT(&header[20]); } else if (header[0] != 0) { return ERROR_MALFORMED; } else { creationTime = U32_AT(&header[4]); + mHeaderTimescale = U32_AT(&header[12]); } String8 s; diff --git a/media/libstagefright/SurfaceMediaSource.cpp b/media/libstagefright/SurfaceMediaSource.cpp index 409038a..71b6569 100644 --- a/media/libstagefright/SurfaceMediaSource.cpp +++ b/media/libstagefright/SurfaceMediaSource.cpp @@ -305,8 +305,9 @@ status_t SurfaceMediaSource::read( MediaBuffer **buffer, // First time seeing the buffer? Added it to the SMS slot if (item.mGraphicBuffer != NULL) { - mBufferSlot[item.mBuf] = item.mGraphicBuffer; + mSlots[item.mBuf].mGraphicBuffer = item.mGraphicBuffer; } + mSlots[item.mBuf].mFrameNumber = item.mFrameNumber; // check for the timing of this buffer if (mNumFramesReceived == 0 && !mUseAbsoluteTimestamps) { @@ -315,7 +316,8 @@ status_t SurfaceMediaSource::read( MediaBuffer **buffer, if (mStartTimeNs > 0) { if (item.mTimestamp < mStartTimeNs) { // This frame predates start of record, discard - mBufferQueue->releaseBuffer(item.mBuf, EGL_NO_DISPLAY, + mBufferQueue->releaseBuffer( + item.mBuf, item.mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE); continue; } @@ -345,17 +347,18 @@ status_t SurfaceMediaSource::read( MediaBuffer **buffer, // First time seeing the buffer? Added it to the SMS slot if (item.mGraphicBuffer != NULL) { - mBufferSlot[mCurrentSlot] = item.mGraphicBuffer; + mSlots[item.mBuf].mGraphicBuffer = item.mGraphicBuffer; } + mSlots[item.mBuf].mFrameNumber = item.mFrameNumber; - mCurrentBuffers.push_back(mBufferSlot[mCurrentSlot]); + mCurrentBuffers.push_back(mSlots[mCurrentSlot].mGraphicBuffer); int64_t prevTimeStamp = mCurrentTimestamp; mCurrentTimestamp = item.mTimestamp; mNumFramesEncoded++; // Pass the data to the MediaBuffer. Pass in only the metadata - passMetadataBuffer(buffer, mBufferSlot[mCurrentSlot]->handle); + passMetadataBuffer(buffer, mSlots[mCurrentSlot].mGraphicBuffer->handle); (*buffer)->setObserver(this); (*buffer)->add_ref(); @@ -405,15 +408,16 @@ void SurfaceMediaSource::signalBufferReturned(MediaBuffer *buffer) { } for (int id = 0; id < BufferQueue::NUM_BUFFER_SLOTS; id++) { - if (mBufferSlot[id] == NULL) { + if (mSlots[id].mGraphicBuffer == NULL) { continue; } - if (bufferHandle == mBufferSlot[id]->handle) { + if (bufferHandle == mSlots[id].mGraphicBuffer->handle) { ALOGV("Slot %d returned, matches handle = %p", id, - mBufferSlot[id]->handle); + mSlots[id].mGraphicBuffer->handle); - mBufferQueue->releaseBuffer(id, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, + mBufferQueue->releaseBuffer(id, mSlots[id].mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE); buffer->setObserver(0); @@ -469,7 +473,7 @@ void SurfaceMediaSource::onBuffersReleased() { mFrameAvailableCondition.signal(); for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { - mBufferSlot[i] = 0; + mSlots[i].mGraphicBuffer = 0; } } diff --git a/media/libstagefright/codecs/aacdec/SoftAAC2.cpp b/media/libstagefright/codecs/aacdec/SoftAAC2.cpp index cf50dc9..1b20cbb 100644 --- a/media/libstagefright/codecs/aacdec/SoftAAC2.cpp +++ b/media/libstagefright/codecs/aacdec/SoftAAC2.cpp @@ -604,6 +604,9 @@ void SoftAAC2::onReset() { // To make the codec behave the same before and after a reset, we need to invalidate the // streaminfo struct. This does that: mStreamInfo->sampleRate = 0; + + mSignalledError = false; + mOutputPortSettingsChange = NONE; } void SoftAAC2::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) { diff --git a/media/libstagefright/codecs/aacenc/SampleCode/Android.mk b/media/libstagefright/codecs/aacenc/SampleCode/Android.mk index 01016e7..d06dcf6 100644 --- a/media/libstagefright/codecs/aacenc/SampleCode/Android.mk +++ b/media/libstagefright/codecs/aacenc/SampleCode/Android.mk @@ -5,7 +5,7 @@ LOCAL_SRC_FILES := \ AAC_E_SAMPLES.c \ ../../common/cmnMemory.c -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE := AACEncTest diff --git a/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp b/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp index 4d4212f..3320688 100644 --- a/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp +++ b/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp @@ -457,6 +457,11 @@ void SoftAMR::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) { } } +void SoftAMR::onReset() { + mSignalledError = false; + mOutputPortSettingsChange = NONE; +} + } // namespace android android::SoftOMXComponent *createSoftOMXComponent( diff --git a/media/libstagefright/codecs/amrnb/dec/SoftAMR.h b/media/libstagefright/codecs/amrnb/dec/SoftAMR.h index 9a596e5..758d6ac 100644 --- a/media/libstagefright/codecs/amrnb/dec/SoftAMR.h +++ b/media/libstagefright/codecs/amrnb/dec/SoftAMR.h @@ -40,6 +40,7 @@ protected: virtual void onQueueFilled(OMX_U32 portIndex); virtual void onPortFlushCompleted(OMX_U32 portIndex); virtual void onPortEnableCompleted(OMX_U32 portIndex, bool enabled); + virtual void onReset(); private: enum { diff --git a/media/libstagefright/codecs/amrwbenc/SampleCode/Android.mk b/media/libstagefright/codecs/amrwbenc/SampleCode/Android.mk index db34d08..c203f77 100644 --- a/media/libstagefright/codecs/amrwbenc/SampleCode/Android.mk +++ b/media/libstagefright/codecs/amrwbenc/SampleCode/Android.mk @@ -5,7 +5,7 @@ LOCAL_SRC_FILES := \ AMRWB_E_SAMPLE.c \ ../../common/cmnMemory.c -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE := AMRWBEncTest LOCAL_ARM_MODE := arm diff --git a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp index 020cc0a..fb2a430 100644 --- a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp +++ b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp @@ -48,42 +48,32 @@ static const CodecProfileLevel kH263ProfileLevels[] = { { OMX_VIDEO_H263ProfileISWV2, OMX_VIDEO_H263Level45 }, }; -template<class T> -static void InitOMXParams(T *params) { - params->nSize = sizeof(T); - params->nVersion.s.nVersionMajor = 1; - params->nVersion.s.nVersionMinor = 0; - params->nVersion.s.nRevision = 0; - params->nVersion.s.nStep = 0; -} - SoftMPEG4::SoftMPEG4( const char *name, + const char *componentRole, + OMX_VIDEO_CODINGTYPE codingType, + const CodecProfileLevel *profileLevels, + size_t numProfileLevels, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) - : SimpleSoftOMXComponent(name, callbacks, appData, component), - mMode(MODE_MPEG4), + : SoftVideoDecoderOMXComponent( + name, componentRole, codingType, profileLevels, numProfileLevels, + 352 /* width */, 288 /* height */, callbacks, appData, component), + mMode(codingType == OMX_VIDEO_CodingH263 ? MODE_H263 : MODE_MPEG4), mHandle(new tagvideoDecControls), mInputBufferCount(0), - mWidth(352), - mHeight(288), - mCropLeft(0), - mCropTop(0), - mCropRight(mWidth - 1), - mCropBottom(mHeight - 1), mSignalledError(false), mInitialized(false), mFramesConfigured(false), mNumSamplesOutput(0), - mOutputPortSettingsChange(NONE) { - if (!strcmp(name, "OMX.google.h263.decoder")) { - mMode = MODE_H263; - } else { - CHECK(!strcmp(name, "OMX.google.mpeg4.decoder")); - } - - initPorts(); + mPvTime(0) { + initPorts( + kNumInputBuffers, + 8192 /* inputBufferSize */, + kNumOutputBuffers, + (mMode == MODE_MPEG4) + ? MEDIA_MIMETYPE_VIDEO_MPEG4 : MEDIA_MIMETYPE_VIDEO_H263); CHECK_EQ(initDecoder(), (status_t)OK); } @@ -96,219 +86,11 @@ SoftMPEG4::~SoftMPEG4() { mHandle = NULL; } -void SoftMPEG4::initPorts() { - OMX_PARAM_PORTDEFINITIONTYPE def; - InitOMXParams(&def); - - def.nPortIndex = 0; - def.eDir = OMX_DirInput; - def.nBufferCountMin = kNumInputBuffers; - def.nBufferCountActual = def.nBufferCountMin; - def.nBufferSize = 8192; - def.bEnabled = OMX_TRUE; - def.bPopulated = OMX_FALSE; - def.eDomain = OMX_PortDomainVideo; - def.bBuffersContiguous = OMX_FALSE; - def.nBufferAlignment = 1; - - def.format.video.cMIMEType = - (mMode == MODE_MPEG4) - ? const_cast<char *>(MEDIA_MIMETYPE_VIDEO_MPEG4) - : const_cast<char *>(MEDIA_MIMETYPE_VIDEO_H263); - - def.format.video.pNativeRender = NULL; - def.format.video.nFrameWidth = mWidth; - def.format.video.nFrameHeight = mHeight; - def.format.video.nStride = def.format.video.nFrameWidth; - def.format.video.nSliceHeight = def.format.video.nFrameHeight; - def.format.video.nBitrate = 0; - def.format.video.xFramerate = 0; - def.format.video.bFlagErrorConcealment = OMX_FALSE; - - def.format.video.eCompressionFormat = - mMode == MODE_MPEG4 ? OMX_VIDEO_CodingMPEG4 : OMX_VIDEO_CodingH263; - - def.format.video.eColorFormat = OMX_COLOR_FormatUnused; - def.format.video.pNativeWindow = NULL; - - addPort(def); - - def.nPortIndex = 1; - def.eDir = OMX_DirOutput; - def.nBufferCountMin = kNumOutputBuffers; - def.nBufferCountActual = def.nBufferCountMin; - def.bEnabled = OMX_TRUE; - def.bPopulated = OMX_FALSE; - def.eDomain = OMX_PortDomainVideo; - def.bBuffersContiguous = OMX_FALSE; - def.nBufferAlignment = 2; - - def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_RAW); - def.format.video.pNativeRender = NULL; - def.format.video.nFrameWidth = mWidth; - def.format.video.nFrameHeight = mHeight; - def.format.video.nStride = def.format.video.nFrameWidth; - def.format.video.nSliceHeight = def.format.video.nFrameHeight; - def.format.video.nBitrate = 0; - def.format.video.xFramerate = 0; - def.format.video.bFlagErrorConcealment = OMX_FALSE; - def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused; - def.format.video.eColorFormat = OMX_COLOR_FormatYUV420Planar; - def.format.video.pNativeWindow = NULL; - - def.nBufferSize = - (def.format.video.nFrameWidth * def.format.video.nFrameHeight * 3) / 2; - - addPort(def); -} - status_t SoftMPEG4::initDecoder() { memset(mHandle, 0, sizeof(tagvideoDecControls)); return OK; } -OMX_ERRORTYPE SoftMPEG4::internalGetParameter( - OMX_INDEXTYPE index, OMX_PTR params) { - switch (index) { - case OMX_IndexParamVideoPortFormat: - { - OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = - (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params; - - if (formatParams->nPortIndex > 1) { - return OMX_ErrorUndefined; - } - - if (formatParams->nIndex != 0) { - return OMX_ErrorNoMore; - } - - if (formatParams->nPortIndex == 0) { - formatParams->eCompressionFormat = - (mMode == MODE_MPEG4) - ? OMX_VIDEO_CodingMPEG4 : OMX_VIDEO_CodingH263; - - formatParams->eColorFormat = OMX_COLOR_FormatUnused; - formatParams->xFramerate = 0; - } else { - CHECK_EQ(formatParams->nPortIndex, 1u); - - formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused; - formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar; - formatParams->xFramerate = 0; - } - - return OMX_ErrorNone; - } - - case OMX_IndexParamVideoProfileLevelQuerySupported: - { - OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileLevel = - (OMX_VIDEO_PARAM_PROFILELEVELTYPE *) params; - - if (profileLevel->nPortIndex != 0) { // Input port only - ALOGE("Invalid port index: %ld", profileLevel->nPortIndex); - return OMX_ErrorUnsupportedIndex; - } - - size_t index = profileLevel->nProfileIndex; - if (mMode == MODE_H263) { - size_t nProfileLevels = - sizeof(kH263ProfileLevels) / sizeof(kH263ProfileLevels[0]); - if (index >= nProfileLevels) { - return OMX_ErrorNoMore; - } - - profileLevel->eProfile = kH263ProfileLevels[index].mProfile; - profileLevel->eLevel = kH263ProfileLevels[index].mLevel; - } else { - size_t nProfileLevels = - sizeof(kM4VProfileLevels) / sizeof(kM4VProfileLevels[0]); - if (index >= nProfileLevels) { - return OMX_ErrorNoMore; - } - - profileLevel->eProfile = kM4VProfileLevels[index].mProfile; - profileLevel->eLevel = kM4VProfileLevels[index].mLevel; - } - return OMX_ErrorNone; - } - - default: - return SimpleSoftOMXComponent::internalGetParameter(index, params); - } -} - -OMX_ERRORTYPE SoftMPEG4::internalSetParameter( - OMX_INDEXTYPE index, const OMX_PTR params) { - switch (index) { - case OMX_IndexParamStandardComponentRole: - { - const OMX_PARAM_COMPONENTROLETYPE *roleParams = - (const OMX_PARAM_COMPONENTROLETYPE *)params; - - if (mMode == MODE_MPEG4) { - if (strncmp((const char *)roleParams->cRole, - "video_decoder.mpeg4", - OMX_MAX_STRINGNAME_SIZE - 1)) { - return OMX_ErrorUndefined; - } - } else { - if (strncmp((const char *)roleParams->cRole, - "video_decoder.h263", - OMX_MAX_STRINGNAME_SIZE - 1)) { - return OMX_ErrorUndefined; - } - } - - return OMX_ErrorNone; - } - - case OMX_IndexParamVideoPortFormat: - { - OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = - (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params; - - if (formatParams->nPortIndex > 1) { - return OMX_ErrorUndefined; - } - - if (formatParams->nIndex != 0) { - return OMX_ErrorNoMore; - } - - return OMX_ErrorNone; - } - - default: - return SimpleSoftOMXComponent::internalSetParameter(index, params); - } -} - -OMX_ERRORTYPE SoftMPEG4::getConfig( - OMX_INDEXTYPE index, OMX_PTR params) { - switch (index) { - case OMX_IndexConfigCommonOutputCrop: - { - OMX_CONFIG_RECTTYPE *rectParams = (OMX_CONFIG_RECTTYPE *)params; - - if (rectParams->nPortIndex != 1) { - return OMX_ErrorUndefined; - } - - rectParams->nLeft = mCropLeft; - rectParams->nTop = mCropTop; - rectParams->nWidth = mCropRight - mCropLeft + 1; - rectParams->nHeight = mCropBottom - mCropTop + 1; - - return OMX_ErrorNone; - } - - default: - return OMX_ErrorUnsupportedIndex; - } -} - void SoftMPEG4::onQueueFilled(OMX_U32 portIndex) { if (mSignalledError || mOutputPortSettingsChange != NONE) { return; @@ -415,9 +197,14 @@ void SoftMPEG4::onQueueFilled(OMX_U32 portIndex) { uint32_t useExtTimestamp = (inHeader->nOffset == 0); - // decoder deals in ms, OMX in us. - uint32_t timestamp = - useExtTimestamp ? (inHeader->nTimeStamp + 500) / 1000 : 0xFFFFFFFF; + // decoder deals in ms (int32_t), OMX in us (int64_t) + // so use fake timestamp instead + uint32_t timestamp = 0xFFFFFFFF; + if (useExtTimestamp) { + mPvToOmxTimeMap.add(mPvTime, inHeader->nTimeStamp); + timestamp = mPvTime; + mPvTime++; + } int32_t bufferSize = inHeader->nFilledLen; int32_t tmp = bufferSize; @@ -441,7 +228,8 @@ void SoftMPEG4::onQueueFilled(OMX_U32 portIndex) { } // decoder deals in ms, OMX in us. - outHeader->nTimeStamp = timestamp * 1000; + outHeader->nTimeStamp = mPvToOmxTimeMap.valueFor(timestamp); + mPvToOmxTimeMap.removeItem(timestamp); inHeader->nOffset += bufferSize; inHeader->nFilledLen = 0; @@ -482,11 +270,11 @@ void SoftMPEG4::onQueueFilled(OMX_U32 portIndex) { } bool SoftMPEG4::portSettingsChanged() { - int32_t disp_width, disp_height; - PVGetVideoDimensions(mHandle, &disp_width, &disp_height); + uint32_t disp_width, disp_height; + PVGetVideoDimensions(mHandle, (int32 *)&disp_width, (int32 *)&disp_height); - int32_t buf_width, buf_height; - PVGetBufferDimensions(mHandle, &buf_width, &buf_height); + uint32_t buf_width, buf_height; + PVGetBufferDimensions(mHandle, (int32 *)&buf_width, (int32 *)&buf_height); CHECK_LE(disp_width, buf_width); CHECK_LE(disp_height, buf_height); @@ -494,12 +282,12 @@ bool SoftMPEG4::portSettingsChanged() { ALOGV("disp_width = %d, disp_height = %d, buf_width = %d, buf_height = %d", disp_width, disp_height, buf_width, buf_height); - if (mCropRight != disp_width - 1 - || mCropBottom != disp_height - 1) { + if (mCropWidth != disp_width + || mCropHeight != disp_height) { mCropLeft = 0; mCropTop = 0; - mCropRight = disp_width - 1; - mCropBottom = disp_height - 1; + mCropWidth = disp_width; + mCropHeight = disp_height; notify(OMX_EventPortSettingsChanged, 1, @@ -545,45 +333,22 @@ void SoftMPEG4::onPortFlushCompleted(OMX_U32 portIndex) { } } -void SoftMPEG4::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) { - if (portIndex != 1) { - return; - } - - switch (mOutputPortSettingsChange) { - case NONE: - break; - - case AWAITING_DISABLED: - { - CHECK(!enabled); - mOutputPortSettingsChange = AWAITING_ENABLED; - break; - } - - default: - { - CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED); - CHECK(enabled); - mOutputPortSettingsChange = NONE; - break; - } +void SoftMPEG4::onReset() { + SoftVideoDecoderOMXComponent::onReset(); + mPvToOmxTimeMap.clear(); + mSignalledError = false; + mFramesConfigured = false; + if (mInitialized) { + PVCleanUpVideoDecoder(mHandle); + mInitialized = false; } } void SoftMPEG4::updatePortDefinitions() { - OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(0)->mDef; - def->format.video.nFrameWidth = mWidth; - def->format.video.nFrameHeight = mHeight; - def->format.video.nStride = def->format.video.nFrameWidth; - def->format.video.nSliceHeight = def->format.video.nFrameHeight; - - def = &editPortInfo(1)->mDef; - def->format.video.nFrameWidth = mWidth; - def->format.video.nFrameHeight = mHeight; - def->format.video.nStride = def->format.video.nFrameWidth; - def->format.video.nSliceHeight = def->format.video.nFrameHeight; + SoftVideoDecoderOMXComponent::updatePortDefinitions(); + /* We have to align our width and height - this should affect stride! */ + OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kOutputPortIndex)->mDef; def->nBufferSize = (((def->format.video.nFrameWidth + 15) & -16) * ((def->format.video.nFrameHeight + 15) & -16) * 3) / 2; @@ -594,6 +359,19 @@ void SoftMPEG4::updatePortDefinitions() { android::SoftOMXComponent *createSoftOMXComponent( const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) { - return new android::SoftMPEG4(name, callbacks, appData, component); + using namespace android; + if (!strcmp(name, "OMX.google.h263.decoder")) { + return new android::SoftMPEG4( + name, "video_decoder.h263", OMX_VIDEO_CodingH263, + kH263ProfileLevels, ARRAY_SIZE(kH263ProfileLevels), + callbacks, appData, component); + } else if (!strcmp(name, "OMX.google.mpeg4.decoder")) { + return new android::SoftMPEG4( + name, "video_decoder.mpeg4", OMX_VIDEO_CodingMPEG4, + kM4VProfileLevels, ARRAY_SIZE(kM4VProfileLevels), + callbacks, appData, component); + } else { + CHECK(!"Unknown component"); + } } diff --git a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.h b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.h index dff08a7..de14aaf 100644 --- a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.h +++ b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.h @@ -18,14 +18,18 @@ #define SOFT_MPEG4_H_ -#include "SimpleSoftOMXComponent.h" +#include "SoftVideoDecoderOMXComponent.h" struct tagvideoDecControls; namespace android { -struct SoftMPEG4 : public SimpleSoftOMXComponent { +struct SoftMPEG4 : public SoftVideoDecoderOMXComponent { SoftMPEG4(const char *name, + const char *componentRole, + OMX_VIDEO_CODINGTYPE codingType, + const CodecProfileLevel *profileLevels, + size_t numProfileLevels, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component); @@ -33,17 +37,9 @@ struct SoftMPEG4 : public SimpleSoftOMXComponent { protected: virtual ~SoftMPEG4(); - virtual OMX_ERRORTYPE internalGetParameter( - OMX_INDEXTYPE index, OMX_PTR params); - - virtual OMX_ERRORTYPE internalSetParameter( - OMX_INDEXTYPE index, const OMX_PTR params); - - virtual OMX_ERRORTYPE getConfig(OMX_INDEXTYPE index, OMX_PTR params); - virtual void onQueueFilled(OMX_U32 portIndex); virtual void onPortFlushCompleted(OMX_U32 portIndex); - virtual void onPortEnableCompleted(OMX_U32 portIndex, bool enabled); + virtual void onReset(); private: enum { @@ -54,32 +50,23 @@ private: enum { MODE_MPEG4, MODE_H263, - } mMode; tagvideoDecControls *mHandle; size_t mInputBufferCount; - int32_t mWidth, mHeight; - int32_t mCropLeft, mCropTop, mCropRight, mCropBottom; - bool mSignalledError; bool mInitialized; bool mFramesConfigured; int32_t mNumSamplesOutput; + int32_t mPvTime; + KeyedVector<int32_t, OMX_TICKS> mPvToOmxTimeMap; - enum { - NONE, - AWAITING_DISABLED, - AWAITING_ENABLED - } mOutputPortSettingsChange; - - void initPorts(); status_t initDecoder(); - void updatePortDefinitions(); + virtual void updatePortDefinitions(); bool portSettingsChanged(); DISALLOW_EVIL_CONSTRUCTORS(SoftMPEG4); diff --git a/media/libstagefright/codecs/mp3dec/SoftMP3.cpp b/media/libstagefright/codecs/mp3dec/SoftMP3.cpp index 9f25536..7c382fb 100644 --- a/media/libstagefright/codecs/mp3dec/SoftMP3.cpp +++ b/media/libstagefright/codecs/mp3dec/SoftMP3.cpp @@ -361,6 +361,8 @@ void SoftMP3::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) { void SoftMP3::onReset() { pvmp3_InitDecoder(mConfig, mDecoderBuf); mIsFirst = true; + mSignalledError = false; + mOutputPortSettingsChange = NONE; } } // namespace android diff --git a/media/libstagefright/codecs/on2/dec/SoftVPX.cpp b/media/libstagefright/codecs/on2/dec/SoftVPX.cpp index a400b4c..43d0263 100644 --- a/media/libstagefright/codecs/on2/dec/SoftVPX.cpp +++ b/media/libstagefright/codecs/on2/dec/SoftVPX.cpp @@ -29,26 +29,19 @@ namespace android { -template<class T> -static void InitOMXParams(T *params) { - params->nSize = sizeof(T); - params->nVersion.s.nVersionMajor = 1; - params->nVersion.s.nVersionMinor = 0; - params->nVersion.s.nRevision = 0; - params->nVersion.s.nStep = 0; -} - SoftVPX::SoftVPX( const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) - : SimpleSoftOMXComponent(name, callbacks, appData, component), - mCtx(NULL), - mWidth(320), - mHeight(240), - mOutputPortSettingsChange(NONE) { - initPorts(); + : SoftVideoDecoderOMXComponent( + name, "video_decoder.vpx", OMX_VIDEO_CodingVPX, + NULL /* profileLevels */, 0 /* numProfileLevels */, + 320 /* width */, 240 /* height */, callbacks, appData, component), + mCtx(NULL) { + initPorts(kNumBuffers, 768 * 1024 /* inputBufferSize */, + kNumBuffers, MEDIA_MIMETYPE_VIDEO_VPX); + CHECK_EQ(initDecoder(), (status_t)OK); } @@ -58,65 +51,6 @@ SoftVPX::~SoftVPX() { mCtx = NULL; } -void SoftVPX::initPorts() { - OMX_PARAM_PORTDEFINITIONTYPE def; - InitOMXParams(&def); - - def.nPortIndex = 0; - def.eDir = OMX_DirInput; - def.nBufferCountMin = kNumBuffers; - def.nBufferCountActual = def.nBufferCountMin; - def.nBufferSize = 768 * 1024; - def.bEnabled = OMX_TRUE; - def.bPopulated = OMX_FALSE; - def.eDomain = OMX_PortDomainVideo; - def.bBuffersContiguous = OMX_FALSE; - def.nBufferAlignment = 1; - - def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_VPX); - def.format.video.pNativeRender = NULL; - def.format.video.nFrameWidth = mWidth; - def.format.video.nFrameHeight = mHeight; - def.format.video.nStride = def.format.video.nFrameWidth; - def.format.video.nSliceHeight = def.format.video.nFrameHeight; - def.format.video.nBitrate = 0; - def.format.video.xFramerate = 0; - def.format.video.bFlagErrorConcealment = OMX_FALSE; - def.format.video.eCompressionFormat = OMX_VIDEO_CodingVPX; - def.format.video.eColorFormat = OMX_COLOR_FormatUnused; - def.format.video.pNativeWindow = NULL; - - addPort(def); - - def.nPortIndex = 1; - def.eDir = OMX_DirOutput; - def.nBufferCountMin = kNumBuffers; - def.nBufferCountActual = def.nBufferCountMin; - def.bEnabled = OMX_TRUE; - def.bPopulated = OMX_FALSE; - def.eDomain = OMX_PortDomainVideo; - def.bBuffersContiguous = OMX_FALSE; - def.nBufferAlignment = 2; - - def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_RAW); - def.format.video.pNativeRender = NULL; - def.format.video.nFrameWidth = mWidth; - def.format.video.nFrameHeight = mHeight; - def.format.video.nStride = def.format.video.nFrameWidth; - def.format.video.nSliceHeight = def.format.video.nFrameHeight; - def.format.video.nBitrate = 0; - def.format.video.xFramerate = 0; - def.format.video.bFlagErrorConcealment = OMX_FALSE; - def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused; - def.format.video.eColorFormat = OMX_COLOR_FormatYUV420Planar; - def.format.video.pNativeWindow = NULL; - - def.nBufferSize = - (def.format.video.nFrameWidth * def.format.video.nFrameHeight * 3) / 2; - - addPort(def); -} - static int GetCPUCoreCount() { int cpuCoreCount = 1; #if defined(_SC_NPROCESSORS_ONLN) @@ -145,80 +79,6 @@ status_t SoftVPX::initDecoder() { return OK; } -OMX_ERRORTYPE SoftVPX::internalGetParameter( - OMX_INDEXTYPE index, OMX_PTR params) { - switch (index) { - case OMX_IndexParamVideoPortFormat: - { - OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = - (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params; - - if (formatParams->nPortIndex > 1) { - return OMX_ErrorUndefined; - } - - if (formatParams->nIndex != 0) { - return OMX_ErrorNoMore; - } - - if (formatParams->nPortIndex == 0) { - formatParams->eCompressionFormat = OMX_VIDEO_CodingVPX; - formatParams->eColorFormat = OMX_COLOR_FormatUnused; - formatParams->xFramerate = 0; - } else { - CHECK_EQ(formatParams->nPortIndex, 1u); - - formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused; - formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar; - formatParams->xFramerate = 0; - } - - return OMX_ErrorNone; - } - - default: - return SimpleSoftOMXComponent::internalGetParameter(index, params); - } -} - -OMX_ERRORTYPE SoftVPX::internalSetParameter( - OMX_INDEXTYPE index, const OMX_PTR params) { - switch (index) { - case OMX_IndexParamStandardComponentRole: - { - const OMX_PARAM_COMPONENTROLETYPE *roleParams = - (const OMX_PARAM_COMPONENTROLETYPE *)params; - - if (strncmp((const char *)roleParams->cRole, - "video_decoder.vpx", - OMX_MAX_STRINGNAME_SIZE - 1)) { - return OMX_ErrorUndefined; - } - - return OMX_ErrorNone; - } - - case OMX_IndexParamVideoPortFormat: - { - OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = - (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params; - - if (formatParams->nPortIndex > 1) { - return OMX_ErrorUndefined; - } - - if (formatParams->nIndex != 0) { - return OMX_ErrorNoMore; - } - - return OMX_ErrorNone; - } - - default: - return SimpleSoftOMXComponent::internalSetParameter(index, params); - } -} - void SoftVPX::onQueueFilled(OMX_U32 portIndex) { if (mOutputPortSettingsChange != NONE) { return; @@ -226,6 +86,7 @@ void SoftVPX::onQueueFilled(OMX_U32 portIndex) { List<BufferInfo *> &inQueue = getPortQueue(0); List<BufferInfo *> &outQueue = getPortQueue(1); + bool EOSseen = false; while (!inQueue.empty() && !outQueue.empty()) { BufferInfo *inInfo = *inQueue.begin(); @@ -235,17 +96,20 @@ void SoftVPX::onQueueFilled(OMX_U32 portIndex) { OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader; if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) { - inQueue.erase(inQueue.begin()); - inInfo->mOwnedByUs = false; - notifyEmptyBufferDone(inHeader); - - outHeader->nFilledLen = 0; - outHeader->nFlags = OMX_BUFFERFLAG_EOS; - - outQueue.erase(outQueue.begin()); - outInfo->mOwnedByUs = false; - notifyFillBufferDone(outHeader); - return; + EOSseen = true; + if (inHeader->nFilledLen == 0) { + inQueue.erase(inQueue.begin()); + inInfo->mOwnedByUs = false; + notifyEmptyBufferDone(inHeader); + + outHeader->nFilledLen = 0; + outHeader->nFlags = OMX_BUFFERFLAG_EOS; + + outQueue.erase(outQueue.begin()); + outInfo->mOwnedByUs = false; + notifyFillBufferDone(outHeader); + return; + } } if (vpx_codec_decode( @@ -266,8 +130,8 @@ void SoftVPX::onQueueFilled(OMX_U32 portIndex) { if (img != NULL) { CHECK_EQ(img->fmt, IMG_FMT_I420); - int32_t width = img->d_w; - int32_t height = img->d_h; + uint32_t width = img->d_w; + uint32_t height = img->d_h; if (width != mWidth || height != mHeight) { mWidth = width; @@ -282,7 +146,7 @@ void SoftVPX::onQueueFilled(OMX_U32 portIndex) { outHeader->nOffset = 0; outHeader->nFilledLen = (width * height * 3) / 2; - outHeader->nFlags = 0; + outHeader->nFlags = EOSseen ? OMX_BUFFERFLAG_EOS : 0; outHeader->nTimeStamp = inHeader->nTimeStamp; const uint8_t *srcLine = (const uint8_t *)img->planes[PLANE_Y]; @@ -325,53 +189,6 @@ void SoftVPX::onQueueFilled(OMX_U32 portIndex) { } } -void SoftVPX::onPortFlushCompleted(OMX_U32 portIndex) { -} - -void SoftVPX::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) { - if (portIndex != 1) { - return; - } - - switch (mOutputPortSettingsChange) { - case NONE: - break; - - case AWAITING_DISABLED: - { - CHECK(!enabled); - mOutputPortSettingsChange = AWAITING_ENABLED; - break; - } - - default: - { - CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED); - CHECK(enabled); - mOutputPortSettingsChange = NONE; - break; - } - } -} - -void SoftVPX::updatePortDefinitions() { - OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(0)->mDef; - def->format.video.nFrameWidth = mWidth; - def->format.video.nFrameHeight = mHeight; - def->format.video.nStride = def->format.video.nFrameWidth; - def->format.video.nSliceHeight = def->format.video.nFrameHeight; - - def = &editPortInfo(1)->mDef; - def->format.video.nFrameWidth = mWidth; - def->format.video.nFrameHeight = mHeight; - def->format.video.nStride = def->format.video.nFrameWidth; - def->format.video.nSliceHeight = def->format.video.nFrameHeight; - - def->nBufferSize = - (def->format.video.nFrameWidth - * def->format.video.nFrameHeight * 3) / 2; -} - } // namespace android android::SoftOMXComponent *createSoftOMXComponent( diff --git a/media/libstagefright/codecs/on2/dec/SoftVPX.h b/media/libstagefright/codecs/on2/dec/SoftVPX.h index 3e814a2..626307b 100644 --- a/media/libstagefright/codecs/on2/dec/SoftVPX.h +++ b/media/libstagefright/codecs/on2/dec/SoftVPX.h @@ -18,11 +18,11 @@ #define SOFT_VPX_H_ -#include "SimpleSoftOMXComponent.h" +#include "SoftVideoDecoderOMXComponent.h" namespace android { -struct SoftVPX : public SimpleSoftOMXComponent { +struct SoftVPX : public SoftVideoDecoderOMXComponent { SoftVPX(const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, @@ -31,15 +31,7 @@ struct SoftVPX : public SimpleSoftOMXComponent { protected: virtual ~SoftVPX(); - virtual OMX_ERRORTYPE internalGetParameter( - OMX_INDEXTYPE index, OMX_PTR params); - - virtual OMX_ERRORTYPE internalSetParameter( - OMX_INDEXTYPE index, const OMX_PTR params); - virtual void onQueueFilled(OMX_U32 portIndex); - virtual void onPortFlushCompleted(OMX_U32 portIndex); - virtual void onPortEnableCompleted(OMX_U32 portIndex, bool enabled); private: enum { @@ -48,20 +40,8 @@ private: void *mCtx; - int32_t mWidth; - int32_t mHeight; - - enum { - NONE, - AWAITING_DISABLED, - AWAITING_ENABLED - } mOutputPortSettingsChange; - - void initPorts(); status_t initDecoder(); - void updatePortDefinitions(); - DISALLOW_EVIL_CONSTRUCTORS(SoftVPX); }; diff --git a/media/libstagefright/codecs/on2/h264dec/Android.mk b/media/libstagefright/codecs/on2/h264dec/Android.mk index 2539f98..655b2ab 100644 --- a/media/libstagefright/codecs/on2/h264dec/Android.mk +++ b/media/libstagefright/codecs/on2/h264dec/Android.mk @@ -119,7 +119,7 @@ LOCAL_C_INCLUDES := $(LOCAL_PATH)/inc LOCAL_SHARED_LIBRARIES := libstagefright_soft_h264dec -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE := decoder diff --git a/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp index 6e36651..3bd9f47 100644 --- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp +++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp @@ -47,38 +47,28 @@ static const CodecProfileLevel kProfileLevels[] = { { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel51 }, }; -template<class T> -static void InitOMXParams(T *params) { - params->nSize = sizeof(T); - params->nVersion.s.nVersionMajor = 1; - params->nVersion.s.nVersionMinor = 0; - params->nVersion.s.nRevision = 0; - params->nVersion.s.nStep = 0; -} - SoftAVC::SoftAVC( const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) - : SimpleSoftOMXComponent(name, callbacks, appData, component), + : SoftVideoDecoderOMXComponent( + name, "video_decoder.avc", OMX_VIDEO_CodingAVC, + kProfileLevels, ARRAY_SIZE(kProfileLevels), + 320 /* width */, 240 /* height */, callbacks, appData, component), mHandle(NULL), mInputBufferCount(0), - mWidth(320), - mHeight(240), mPictureSize(mWidth * mHeight * 3 / 2), - mCropLeft(0), - mCropTop(0), - mCropWidth(mWidth), - mCropHeight(mHeight), mFirstPicture(NULL), mFirstPictureId(-1), mPicId(0), mHeadersDecoded(false), mEOSStatus(INPUT_DATA_AVAILABLE), - mOutputPortSettingsChange(NONE), mSignalledError(false) { - initPorts(); + initPorts( + kNumInputBuffers, 8192 /* inputBufferSize */, + kNumOutputBuffers, MEDIA_MIMETYPE_VIDEO_AVC); + CHECK_EQ(initDecoder(), (status_t)OK); } @@ -100,65 +90,6 @@ SoftAVC::~SoftAVC() { delete[] mFirstPicture; } -void SoftAVC::initPorts() { - OMX_PARAM_PORTDEFINITIONTYPE def; - InitOMXParams(&def); - - def.nPortIndex = kInputPortIndex; - def.eDir = OMX_DirInput; - def.nBufferCountMin = kNumInputBuffers; - def.nBufferCountActual = def.nBufferCountMin; - def.nBufferSize = 8192; - def.bEnabled = OMX_TRUE; - def.bPopulated = OMX_FALSE; - def.eDomain = OMX_PortDomainVideo; - def.bBuffersContiguous = OMX_FALSE; - def.nBufferAlignment = 1; - - def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_AVC); - def.format.video.pNativeRender = NULL; - def.format.video.nFrameWidth = mWidth; - def.format.video.nFrameHeight = mHeight; - def.format.video.nStride = def.format.video.nFrameWidth; - def.format.video.nSliceHeight = def.format.video.nFrameHeight; - def.format.video.nBitrate = 0; - def.format.video.xFramerate = 0; - def.format.video.bFlagErrorConcealment = OMX_FALSE; - def.format.video.eCompressionFormat = OMX_VIDEO_CodingAVC; - def.format.video.eColorFormat = OMX_COLOR_FormatUnused; - def.format.video.pNativeWindow = NULL; - - addPort(def); - - def.nPortIndex = kOutputPortIndex; - def.eDir = OMX_DirOutput; - def.nBufferCountMin = kNumOutputBuffers; - def.nBufferCountActual = def.nBufferCountMin; - def.bEnabled = OMX_TRUE; - def.bPopulated = OMX_FALSE; - def.eDomain = OMX_PortDomainVideo; - def.bBuffersContiguous = OMX_FALSE; - def.nBufferAlignment = 2; - - def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_RAW); - def.format.video.pNativeRender = NULL; - def.format.video.nFrameWidth = mWidth; - def.format.video.nFrameHeight = mHeight; - def.format.video.nStride = def.format.video.nFrameWidth; - def.format.video.nSliceHeight = def.format.video.nFrameHeight; - def.format.video.nBitrate = 0; - def.format.video.xFramerate = 0; - def.format.video.bFlagErrorConcealment = OMX_FALSE; - def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused; - def.format.video.eColorFormat = OMX_COLOR_FormatYUV420Planar; - def.format.video.pNativeWindow = NULL; - - def.nBufferSize = - (def.format.video.nFrameWidth * def.format.video.nFrameHeight * 3) / 2; - - addPort(def); -} - status_t SoftAVC::initDecoder() { // Force decoder to output buffers in display order. if (H264SwDecInit(&mHandle, 0) == H264SWDEC_OK) { @@ -167,126 +98,6 @@ status_t SoftAVC::initDecoder() { return UNKNOWN_ERROR; } -OMX_ERRORTYPE SoftAVC::internalGetParameter( - OMX_INDEXTYPE index, OMX_PTR params) { - switch (index) { - case OMX_IndexParamVideoPortFormat: - { - OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = - (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params; - - if (formatParams->nPortIndex > kOutputPortIndex) { - return OMX_ErrorUndefined; - } - - if (formatParams->nIndex != 0) { - return OMX_ErrorNoMore; - } - - if (formatParams->nPortIndex == kInputPortIndex) { - formatParams->eCompressionFormat = OMX_VIDEO_CodingAVC; - formatParams->eColorFormat = OMX_COLOR_FormatUnused; - formatParams->xFramerate = 0; - } else { - CHECK(formatParams->nPortIndex == kOutputPortIndex); - - formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused; - formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar; - formatParams->xFramerate = 0; - } - - return OMX_ErrorNone; - } - - case OMX_IndexParamVideoProfileLevelQuerySupported: - { - OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileLevel = - (OMX_VIDEO_PARAM_PROFILELEVELTYPE *) params; - - if (profileLevel->nPortIndex != kInputPortIndex) { - ALOGE("Invalid port index: %ld", profileLevel->nPortIndex); - return OMX_ErrorUnsupportedIndex; - } - - size_t index = profileLevel->nProfileIndex; - size_t nProfileLevels = - sizeof(kProfileLevels) / sizeof(kProfileLevels[0]); - if (index >= nProfileLevels) { - return OMX_ErrorNoMore; - } - - profileLevel->eProfile = kProfileLevels[index].mProfile; - profileLevel->eLevel = kProfileLevels[index].mLevel; - return OMX_ErrorNone; - } - - default: - return SimpleSoftOMXComponent::internalGetParameter(index, params); - } -} - -OMX_ERRORTYPE SoftAVC::internalSetParameter( - OMX_INDEXTYPE index, const OMX_PTR params) { - switch (index) { - case OMX_IndexParamStandardComponentRole: - { - const OMX_PARAM_COMPONENTROLETYPE *roleParams = - (const OMX_PARAM_COMPONENTROLETYPE *)params; - - if (strncmp((const char *)roleParams->cRole, - "video_decoder.avc", - OMX_MAX_STRINGNAME_SIZE - 1)) { - return OMX_ErrorUndefined; - } - - return OMX_ErrorNone; - } - - case OMX_IndexParamVideoPortFormat: - { - OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = - (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params; - - if (formatParams->nPortIndex > kOutputPortIndex) { - return OMX_ErrorUndefined; - } - - if (formatParams->nIndex != 0) { - return OMX_ErrorNoMore; - } - - return OMX_ErrorNone; - } - - default: - return SimpleSoftOMXComponent::internalSetParameter(index, params); - } -} - -OMX_ERRORTYPE SoftAVC::getConfig( - OMX_INDEXTYPE index, OMX_PTR params) { - switch (index) { - case OMX_IndexConfigCommonOutputCrop: - { - OMX_CONFIG_RECTTYPE *rectParams = (OMX_CONFIG_RECTTYPE *)params; - - if (rectParams->nPortIndex != 1) { - return OMX_ErrorUndefined; - } - - rectParams->nLeft = mCropLeft; - rectParams->nTop = mCropTop; - rectParams->nWidth = mCropWidth; - rectParams->nHeight = mCropHeight; - - return OMX_ErrorNone; - } - - default: - return OMX_ErrorUnsupportedIndex; - } -} - void SoftAVC::onQueueFilled(OMX_U32 portIndex) { if (mSignalledError || mOutputPortSettingsChange != NONE) { return; @@ -409,8 +220,6 @@ bool SoftAVC::handlePortSettingChangeEvent(const H264SwDecInfo *info) { mWidth = info->picWidth; mHeight = info->picHeight; mPictureSize = mWidth * mHeight * 3 / 2; - mCropWidth = mWidth; - mCropHeight = mHeight; updatePortDefinitions(); notify(OMX_EventPortSettingsChanged, 1, 0, NULL); mOutputPortSettingsChange = AWAITING_DISABLED; @@ -508,44 +317,9 @@ void SoftAVC::onPortFlushCompleted(OMX_U32 portIndex) { } } -void SoftAVC::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) { - switch (mOutputPortSettingsChange) { - case NONE: - break; - - case AWAITING_DISABLED: - { - CHECK(!enabled); - mOutputPortSettingsChange = AWAITING_ENABLED; - break; - } - - default: - { - CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED); - CHECK(enabled); - mOutputPortSettingsChange = NONE; - break; - } - } -} - -void SoftAVC::updatePortDefinitions() { - OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(0)->mDef; - def->format.video.nFrameWidth = mWidth; - def->format.video.nFrameHeight = mHeight; - def->format.video.nStride = def->format.video.nFrameWidth; - def->format.video.nSliceHeight = def->format.video.nFrameHeight; - - def = &editPortInfo(1)->mDef; - def->format.video.nFrameWidth = mWidth; - def->format.video.nFrameHeight = mHeight; - def->format.video.nStride = def->format.video.nFrameWidth; - def->format.video.nSliceHeight = def->format.video.nFrameHeight; - - def->nBufferSize = - (def->format.video.nFrameWidth - * def->format.video.nFrameHeight * 3) / 2; +void SoftAVC::onReset() { + SoftVideoDecoderOMXComponent::onReset(); + mSignalledError = false; } } // namespace android diff --git a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h index 879b014..0ed7ebe 100644 --- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h +++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h @@ -18,7 +18,7 @@ #define SOFT_AVC_H_ -#include "SimpleSoftOMXComponent.h" +#include "SoftVideoDecoderOMXComponent.h" #include <utils/KeyedVector.h> #include "H264SwDecApi.h" @@ -26,7 +26,7 @@ namespace android { -struct SoftAVC : public SimpleSoftOMXComponent { +struct SoftAVC : public SoftVideoDecoderOMXComponent { SoftAVC(const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, @@ -35,22 +35,12 @@ struct SoftAVC : public SimpleSoftOMXComponent { protected: virtual ~SoftAVC(); - virtual OMX_ERRORTYPE internalGetParameter( - OMX_INDEXTYPE index, OMX_PTR params); - - virtual OMX_ERRORTYPE internalSetParameter( - OMX_INDEXTYPE index, const OMX_PTR params); - - virtual OMX_ERRORTYPE getConfig(OMX_INDEXTYPE index, OMX_PTR params); - virtual void onQueueFilled(OMX_U32 portIndex); virtual void onPortFlushCompleted(OMX_U32 portIndex); - virtual void onPortEnableCompleted(OMX_U32 portIndex, bool enabled); + virtual void onReset(); private: enum { - kInputPortIndex = 0, - kOutputPortIndex = 1, kNumInputBuffers = 8, kNumOutputBuffers = 2, }; @@ -65,9 +55,7 @@ private: size_t mInputBufferCount; - uint32_t mWidth, mHeight, mPictureSize; - uint32_t mCropLeft, mCropTop; - uint32_t mCropWidth, mCropHeight; + uint32_t mPictureSize; uint8_t *mFirstPicture; int32_t mFirstPictureId; @@ -81,18 +69,9 @@ private: EOSStatus mEOSStatus; - enum OutputPortSettingChange { - NONE, - AWAITING_DISABLED, - AWAITING_ENABLED - }; - OutputPortSettingChange mOutputPortSettingsChange; - bool mSignalledError; - void initPorts(); status_t initDecoder(); - void updatePortDefinitions(); bool drainAllOutputBuffers(); void drainOneOutputBuffer(int32_t picId, uint8_t *data); void saveFirstOutputBuffer(int32_t pidId, uint8_t *data); diff --git a/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp b/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp index 4115324..51bb958 100644 --- a/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp +++ b/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp @@ -424,6 +424,8 @@ void SoftVorbis::onReset() { delete mVi; mVi = NULL; } + + mOutputPortSettingsChange = NONE; } void SoftVorbis::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) { diff --git a/media/libstagefright/foundation/AHierarchicalStateMachine.cpp b/media/libstagefright/foundation/AHierarchicalStateMachine.cpp index 40c5a3c..f7a00d8 100644 --- a/media/libstagefright/foundation/AHierarchicalStateMachine.cpp +++ b/media/libstagefright/foundation/AHierarchicalStateMachine.cpp @@ -14,6 +14,10 @@ * limitations under the License. */ +//#define LOG_NDEBUG 0 +#define LOG_TAG "AHierarchicalStateMachine" +#include <utils/Log.h> + #include <media/stagefright/foundation/AHierarchicalStateMachine.h> #include <media/stagefright/foundation/ADebug.h> diff --git a/media/libstagefright/httplive/Android.mk b/media/libstagefright/httplive/Android.mk index a3fa7a3..85bd492 100644 --- a/media/libstagefright/httplive/Android.mk +++ b/media/libstagefright/httplive/Android.mk @@ -6,16 +6,25 @@ LOCAL_SRC_FILES:= \ LiveDataSource.cpp \ LiveSession.cpp \ M3UParser.cpp \ + PlaylistFetcher.cpp \ LOCAL_C_INCLUDES:= \ $(TOP)/frameworks/av/media/libstagefright \ $(TOP)/frameworks/native/include/media/openmax \ $(TOP)/external/openssl/include +LOCAL_SHARED_LIBRARIES := \ + libcrypto \ + libcutils \ + libmedia \ + libstagefright \ + libstagefright_foundation \ + libutils \ + LOCAL_MODULE:= libstagefright_httplive ifeq ($(TARGET_ARCH),arm) LOCAL_CFLAGS += -Wno-psabi endif -include $(BUILD_STATIC_LIBRARY) +include $(BUILD_SHARED_LIBRARY) diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp index 505bdb3..e91c60b 100644 --- a/media/libstagefright/httplive/LiveSession.cpp +++ b/media/libstagefright/httplive/LiveSession.cpp @@ -18,12 +18,13 @@ #define LOG_TAG "LiveSession" #include <utils/Log.h> -#include "include/LiveSession.h" +#include "LiveSession.h" -#include "LiveDataSource.h" +#include "M3UParser.h" +#include "PlaylistFetcher.h" -#include "include/M3UParser.h" #include "include/HTTPBase.h" +#include "mpeg2ts/AnotherPacketSource.h" #include <cutils/properties.h> #include <media/stagefright/foundation/hexdump.h> @@ -33,6 +34,8 @@ #include <media/stagefright/DataSource.h> #include <media/stagefright/FileSource.h> #include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> #include <ctype.h> #include <openssl/aes.h> @@ -47,37 +50,107 @@ LiveSession::LiveSession( mUIDValid(uidValid), mUID(uid), mInPreparationPhase(true), - mDataSource(new LiveDataSource), mHTTPDataSource( HTTPBase::Create( (mFlags & kFlagIncognito) ? HTTPBase::kFlagIncognito : 0)), mPrevBandwidthIndex(-1), - mLastPlaylistFetchTimeUs(-1), - mSeqNumber(-1), - mSeekTimeUs(-1), - mNumRetries(0), - mStartOfPlayback(true), - mDurationUs(-1), - mDurationFixed(false), - mSeekDone(false), - mDisconnectPending(false), - mMonitorQueueGeneration(0), - mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY) { + mStreamMask(0), + mCheckBandwidthGeneration(0), + mLastDequeuedTimeUs(0ll), + mReconfigurationInProgress(false), + mDisconnectReplyID(0) { if (mUIDValid) { mHTTPDataSource->setUID(mUID); } + + mPacketSources.add( + STREAMTYPE_AUDIO, new AnotherPacketSource(NULL /* meta */)); + + mPacketSources.add( + STREAMTYPE_VIDEO, new AnotherPacketSource(NULL /* meta */)); + + mPacketSources.add( + STREAMTYPE_SUBTITLES, new AnotherPacketSource(NULL /* meta */)); } LiveSession::~LiveSession() { } -sp<DataSource> LiveSession::getDataSource() { - return mDataSource; +status_t LiveSession::dequeueAccessUnit( + StreamType stream, sp<ABuffer> *accessUnit) { + if (!(mStreamMask & stream)) { + return UNKNOWN_ERROR; + } + + sp<AnotherPacketSource> packetSource = mPacketSources.valueFor(stream); + + status_t finalResult; + if (!packetSource->hasBufferAvailable(&finalResult)) { + return finalResult == OK ? -EAGAIN : finalResult; + } + + status_t err = packetSource->dequeueAccessUnit(accessUnit); + + const char *streamStr; + switch (stream) { + case STREAMTYPE_AUDIO: + streamStr = "audio"; + break; + case STREAMTYPE_VIDEO: + streamStr = "video"; + break; + case STREAMTYPE_SUBTITLES: + streamStr = "subs"; + break; + default: + TRESPASS(); + } + + if (err == INFO_DISCONTINUITY) { + int32_t type; + CHECK((*accessUnit)->meta()->findInt32("discontinuity", &type)); + + sp<AMessage> extra; + if (!(*accessUnit)->meta()->findMessage("extra", &extra)) { + extra.clear(); + } + + ALOGI("[%s] read discontinuity of type %d, extra = %s", + streamStr, + type, + extra == NULL ? "NULL" : extra->debugString().c_str()); + } else if (err == OK) { + int64_t timeUs; + CHECK((*accessUnit)->meta()->findInt64("timeUs", &timeUs)); + ALOGV("[%s] read buffer at time %lld us", streamStr, timeUs); + + mLastDequeuedTimeUs = timeUs; + } else { + ALOGI("[%s] encountered error %d", streamStr, err); + } + + return err; +} + +status_t LiveSession::getStreamFormat(StreamType stream, sp<AMessage> *format) { + if (!(mStreamMask & stream)) { + return UNKNOWN_ERROR; + } + + sp<AnotherPacketSource> packetSource = mPacketSources.valueFor(stream); + + sp<MetaData> meta = packetSource->getFormat(); + + if (meta == NULL) { + return -EAGAIN; + } + + return convertMetaDataToMessage(meta, format); } -void LiveSession::connect( +void LiveSession::connectAsync( const char *url, const KeyedVector<String8, String8> *headers) { sp<AMessage> msg = new AMessage(kWhatConnect, id()); msg->setString("url", url); @@ -91,55 +164,184 @@ void LiveSession::connect( msg->post(); } -void LiveSession::disconnect() { - Mutex::Autolock autoLock(mLock); - mDisconnectPending = true; +status_t LiveSession::disconnect() { + sp<AMessage> msg = new AMessage(kWhatDisconnect, id()); - mHTTPDataSource->disconnect(); + sp<AMessage> response; + status_t err = msg->postAndAwaitResponse(&response); - (new AMessage(kWhatDisconnect, id()))->post(); + return err; } -void LiveSession::seekTo(int64_t timeUs) { - Mutex::Autolock autoLock(mLock); - mSeekDone = false; - +status_t LiveSession::seekTo(int64_t timeUs) { sp<AMessage> msg = new AMessage(kWhatSeek, id()); msg->setInt64("timeUs", timeUs); - msg->post(); - while (!mSeekDone) { - mCondition.wait(mLock); - } + sp<AMessage> response; + status_t err = msg->postAndAwaitResponse(&response); + + return err; } void LiveSession::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { case kWhatConnect: + { onConnect(msg); break; + } case kWhatDisconnect: - onDisconnect(); + { + CHECK(msg->senderAwaitsResponse(&mDisconnectReplyID)); + + if (mReconfigurationInProgress) { + break; + } + + finishDisconnect(); break; + } - case kWhatMonitorQueue: + case kWhatSeek: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + status_t err = onSeek(msg); + + sp<AMessage> response = new AMessage; + response->setInt32("err", err); + + response->postReply(replyID); + break; + } + + case kWhatFetcherNotify: + { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + switch (what) { + case PlaylistFetcher::kWhatStarted: + break; + case PlaylistFetcher::kWhatPaused: + case PlaylistFetcher::kWhatStopped: + { + if (what == PlaylistFetcher::kWhatStopped) { + AString uri; + CHECK(msg->findString("uri", &uri)); + mFetcherInfos.removeItem(uri); + } + + if (mContinuation != NULL) { + CHECK_GT(mContinuationCounter, 0); + if (--mContinuationCounter == 0) { + mContinuation->post(); + } + } + break; + } + + case PlaylistFetcher::kWhatDurationUpdate: + { + AString uri; + CHECK(msg->findString("uri", &uri)); + + int64_t durationUs; + CHECK(msg->findInt64("durationUs", &durationUs)); + + FetcherInfo *info = &mFetcherInfos.editValueFor(uri); + info->mDurationUs = durationUs; + break; + } + + case PlaylistFetcher::kWhatError: + { + status_t err; + CHECK(msg->findInt32("err", &err)); + + ALOGE("XXX Received error %d from PlaylistFetcher.", err); + + if (mInPreparationPhase) { + postPrepared(err); + } + + mPacketSources.valueFor(STREAMTYPE_AUDIO)->signalEOS(err); + + mPacketSources.valueFor(STREAMTYPE_VIDEO)->signalEOS(err); + + mPacketSources.valueFor( + STREAMTYPE_SUBTITLES)->signalEOS(err); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatError); + notify->setInt32("err", err); + notify->post(); + break; + } + + case PlaylistFetcher::kWhatTemporarilyDoneFetching: + { + AString uri; + CHECK(msg->findString("uri", &uri)); + + FetcherInfo *info = &mFetcherInfos.editValueFor(uri); + info->mIsPrepared = true; + + if (mInPreparationPhase) { + bool allFetchersPrepared = true; + for (size_t i = 0; i < mFetcherInfos.size(); ++i) { + if (!mFetcherInfos.valueAt(i).mIsPrepared) { + allFetchersPrepared = false; + break; + } + } + + if (allFetchersPrepared) { + postPrepared(OK); + } + } + break; + } + + default: + TRESPASS(); + } + + break; + } + + case kWhatCheckBandwidth: { int32_t generation; CHECK(msg->findInt32("generation", &generation)); - if (generation != mMonitorQueueGeneration) { - // Stale event + if (generation != mCheckBandwidthGeneration) { break; } - onMonitorQueue(); + onCheckBandwidth(); break; } - case kWhatSeek: - onSeek(msg); + case kWhatChangeConfiguration2: + { + onChangeConfiguration2(msg); + break; + } + + case kWhatChangeConfiguration3: + { + onChangeConfiguration3(msg); + break; + } + + case kWhatFinishDisconnect2: + { + onFinishDisconnect2(); break; + } default: TRESPASS(); @@ -172,48 +374,127 @@ void LiveSession::onConnect(const sp<AMessage> &msg) { headers = NULL; } +#if 1 ALOGI("onConnect <URL suppressed>"); +#else + ALOGI("onConnect %s", url.c_str()); +#endif mMasterURL = url; bool dummy; - sp<M3UParser> playlist = fetchPlaylist(url.c_str(), &dummy); + mPlaylist = fetchPlaylist(url.c_str(), NULL /* curPlaylistHash */, &dummy); - if (playlist == NULL) { + if (mPlaylist == NULL) { ALOGE("unable to fetch master playlist '%s'.", url.c_str()); - signalEOS(ERROR_IO); + postPrepared(ERROR_IO); return; } - if (playlist->isVariantPlaylist()) { - for (size_t i = 0; i < playlist->size(); ++i) { + // We trust the content provider to make a reasonable choice of preferred + // initial bandwidth by listing it first in the variant playlist. + // At startup we really don't have a good estimate on the available + // network bandwidth since we haven't tranferred any data yet. Once + // we have we can make a better informed choice. + size_t initialBandwidth = 0; + size_t initialBandwidthIndex = 0; + + if (mPlaylist->isVariantPlaylist()) { + for (size_t i = 0; i < mPlaylist->size(); ++i) { BandwidthItem item; + item.mPlaylistIndex = i; + sp<AMessage> meta; - playlist->itemAt(i, &item.mURI, &meta); + AString uri; + mPlaylist->itemAt(i, &uri, &meta); unsigned long bandwidth; CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth)); + if (initialBandwidth == 0) { + initialBandwidth = item.mBandwidth; + } + mBandwidthItems.push(item); } CHECK_GT(mBandwidthItems.size(), 0u); mBandwidthItems.sort(SortByBandwidth); + + for (size_t i = 0; i < mBandwidthItems.size(); ++i) { + if (mBandwidthItems.itemAt(i).mBandwidth == initialBandwidth) { + initialBandwidthIndex = i; + break; + } + } + } else { + // dummy item. + BandwidthItem item; + item.mPlaylistIndex = 0; + item.mBandwidth = 0; + mBandwidthItems.push(item); } - postMonitorQueue(); + changeConfiguration(0ll /* timeUs */, initialBandwidthIndex); } -void LiveSession::onDisconnect() { - ALOGI("onDisconnect"); +void LiveSession::finishDisconnect() { + // No reconfiguration is currently pending, make sure none will trigger + // during disconnection either. + cancelCheckBandwidthEvent(); + + for (size_t i = 0; i < mFetcherInfos.size(); ++i) { + mFetcherInfos.valueAt(i).mFetcher->stopAsync(); + } + + sp<AMessage> msg = new AMessage(kWhatFinishDisconnect2, id()); - signalEOS(ERROR_END_OF_STREAM); + mContinuationCounter = mFetcherInfos.size(); + mContinuation = msg; - Mutex::Autolock autoLock(mLock); - mDisconnectPending = false; + if (mContinuationCounter == 0) { + msg->post(); + } +} + +void LiveSession::onFinishDisconnect2() { + mContinuation.clear(); + + mPacketSources.valueFor(STREAMTYPE_AUDIO)->signalEOS(ERROR_END_OF_STREAM); + mPacketSources.valueFor(STREAMTYPE_VIDEO)->signalEOS(ERROR_END_OF_STREAM); + + mPacketSources.valueFor( + STREAMTYPE_SUBTITLES)->signalEOS(ERROR_END_OF_STREAM); + + sp<AMessage> response = new AMessage; + response->setInt32("err", OK); + + response->postReply(mDisconnectReplyID); + mDisconnectReplyID = 0; +} + +sp<PlaylistFetcher> LiveSession::addFetcher(const char *uri) { + ssize_t index = mFetcherInfos.indexOfKey(uri); + + if (index >= 0) { + return NULL; + } + + sp<AMessage> notify = new AMessage(kWhatFetcherNotify, id()); + notify->setString("uri", uri); + + FetcherInfo info; + info.mFetcher = new PlaylistFetcher(notify, this, uri); + info.mDurationUs = -1ll; + info.mIsPrepared = false; + looper()->registerHandler(info.mFetcher); + + mFetcherInfos.add(uri, info); + + return info.mFetcher; } status_t LiveSession::fetchFile( @@ -229,14 +510,6 @@ status_t LiveSession::fetchFile( && strncasecmp(url, "https://", 8)) { return ERROR_UNSUPPORTED; } else { - { - Mutex::Autolock autoLock(mLock); - - if (mDisconnectPending) { - return ERROR_IO; - } - } - KeyedVector<String8, String8> headers = mExtraHeaders; if (range_offset > 0 || range_length >= 0) { headers.add( @@ -315,7 +588,8 @@ status_t LiveSession::fetchFile( return OK; } -sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) { +sp<M3UParser> LiveSession::fetchPlaylist( + const char *url, uint8_t *curPlaylistHash, bool *unchanged) { ALOGV("fetchPlaylist '%s'", url); *unchanged = false; @@ -339,13 +613,8 @@ sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) { MD5_Final(hash, &m); - if (mPlaylist != NULL && !memcmp(hash, mPlaylistHash, 16)) { + if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) { // playlist unchanged - - if (mRefreshState != THIRD_UNCHANGED_RELOAD_ATTEMPT) { - mRefreshState = (RefreshState)(mRefreshState + 1); - } - *unchanged = true; ALOGV("Playlist unchanged, refresh state is now %d", @@ -354,9 +623,9 @@ sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) { return NULL; } - memcpy(mPlaylistHash, hash, sizeof(hash)); - - mRefreshState = INITIAL_MINIMUM_RELOAD_DELAY; + if (curPlaylistHash != NULL) { + memcpy(curPlaylistHash, hash, sizeof(hash)); + } #endif sp<M3UParser> playlist = @@ -371,37 +640,6 @@ sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) { return playlist; } -int64_t LiveSession::getSegmentStartTimeUs(int32_t seqNumber) const { - CHECK(mPlaylist != NULL); - - int32_t firstSeqNumberInPlaylist; - if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32( - "media-sequence", &firstSeqNumberInPlaylist)) { - firstSeqNumberInPlaylist = 0; - } - - int32_t lastSeqNumberInPlaylist = - firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1; - - CHECK_GE(seqNumber, firstSeqNumberInPlaylist); - CHECK_LE(seqNumber, lastSeqNumberInPlaylist); - - int64_t segmentStartUs = 0ll; - for (int32_t index = 0; - index < seqNumber - firstSeqNumberInPlaylist; ++index) { - sp<AMessage> itemMeta; - CHECK(mPlaylist->itemAt( - index, NULL /* uri */, &itemMeta)); - - int64_t itemDurationUs; - CHECK(itemMeta->findInt64("durationUs", &itemDurationUs)); - - segmentStartUs += itemDurationUs; - } - - return segmentStartUs; -} - static double uniformRand() { return (double)rand() / RAND_MAX; } @@ -412,36 +650,50 @@ size_t LiveSession::getBandwidthIndex() { } #if 1 - int32_t bandwidthBps; - if (mHTTPDataSource != NULL - && mHTTPDataSource->estimateBandwidth(&bandwidthBps)) { - ALOGV("bandwidth estimated at %.2f kbps", bandwidthBps / 1024.0f); - } else { - ALOGV("no bandwidth estimate."); - return 0; // Pick the lowest bandwidth stream by default. - } - char value[PROPERTY_VALUE_MAX]; - if (property_get("media.httplive.max-bw", value, NULL)) { + ssize_t index = -1; + if (property_get("media.httplive.bw-index", value, NULL)) { char *end; - long maxBw = strtoul(value, &end, 10); - if (end > value && *end == '\0') { - if (maxBw > 0 && bandwidthBps > maxBw) { - ALOGV("bandwidth capped to %ld bps", maxBw); - bandwidthBps = maxBw; - } + index = strtol(value, &end, 10); + CHECK(end > value && *end == '\0'); + + if (index >= 0 && (size_t)index >= mBandwidthItems.size()) { + index = mBandwidthItems.size() - 1; } } - // Consider only 80% of the available bandwidth usable. - bandwidthBps = (bandwidthBps * 8) / 10; + if (index < 0) { + int32_t bandwidthBps; + if (mHTTPDataSource != NULL + && mHTTPDataSource->estimateBandwidth(&bandwidthBps)) { + ALOGV("bandwidth estimated at %.2f kbps", bandwidthBps / 1024.0f); + } else { + ALOGV("no bandwidth estimate."); + return 0; // Pick the lowest bandwidth stream by default. + } - // Pick the highest bandwidth stream below or equal to estimated bandwidth. + char value[PROPERTY_VALUE_MAX]; + if (property_get("media.httplive.max-bw", value, NULL)) { + char *end; + long maxBw = strtoul(value, &end, 10); + if (end > value && *end == '\0') { + if (maxBw > 0 && bandwidthBps > maxBw) { + ALOGV("bandwidth capped to %ld bps", maxBw); + bandwidthBps = maxBw; + } + } + } - size_t index = mBandwidthItems.size() - 1; - while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth - > (size_t)bandwidthBps) { - --index; + // Consider only 80% of the available bandwidth usable. + bandwidthBps = (bandwidthBps * 8) / 10; + + // Pick the highest bandwidth stream below or equal to estimated bandwidth. + + index = mBandwidthItems.size() - 1; + while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth + > (size_t)bandwidthBps) { + --index; + } } #elif 0 // Change bandwidth at random() @@ -452,6 +704,8 @@ size_t LiveSession::getBandwidthIndex() { // to lowest) const size_t kMinIndex = 0; + static ssize_t mPrevBandwidthIndex = -1; + size_t index; if (mPrevBandwidthIndex < 0) { index = kMinIndex; @@ -463,6 +717,7 @@ size_t LiveSession::getBandwidthIndex() { index = kMinIndex; } } + mPrevBandwidthIndex = index; #elif 0 // Pick the highest bandwidth stream below or equal to 1.2 Mbit/sec @@ -470,570 +725,381 @@ size_t LiveSession::getBandwidthIndex() { while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth > 1200000) { --index; } +#elif 1 + char value[PROPERTY_VALUE_MAX]; + size_t index; + if (property_get("media.httplive.bw-index", value, NULL)) { + char *end; + index = strtoul(value, &end, 10); + CHECK(end > value && *end == '\0'); + + if (index >= mBandwidthItems.size()) { + index = mBandwidthItems.size() - 1; + } + } else { + index = 0; + } #else size_t index = mBandwidthItems.size() - 1; // Highest bandwidth stream #endif + CHECK_GE(index, 0); + return index; } -bool LiveSession::timeToRefreshPlaylist(int64_t nowUs) const { - if (mPlaylist == NULL) { - CHECK_EQ((int)mRefreshState, (int)INITIAL_MINIMUM_RELOAD_DELAY); - return true; - } - - int32_t targetDurationSecs; - CHECK(mPlaylist->meta()->findInt32("target-duration", &targetDurationSecs)); - - int64_t targetDurationUs = targetDurationSecs * 1000000ll; - - int64_t minPlaylistAgeUs; - - switch (mRefreshState) { - case INITIAL_MINIMUM_RELOAD_DELAY: - { - size_t n = mPlaylist->size(); - if (n > 0) { - sp<AMessage> itemMeta; - CHECK(mPlaylist->itemAt(n - 1, NULL /* uri */, &itemMeta)); - - int64_t itemDurationUs; - CHECK(itemMeta->findInt64("durationUs", &itemDurationUs)); - - minPlaylistAgeUs = itemDurationUs; - break; - } - - // fall through - } - - case FIRST_UNCHANGED_RELOAD_ATTEMPT: - { - minPlaylistAgeUs = targetDurationUs / 2; - break; - } - - case SECOND_UNCHANGED_RELOAD_ATTEMPT: - { - minPlaylistAgeUs = (targetDurationUs * 3) / 2; - break; - } - - case THIRD_UNCHANGED_RELOAD_ATTEMPT: - { - minPlaylistAgeUs = targetDurationUs * 3; - break; - } +status_t LiveSession::onSeek(const sp<AMessage> &msg) { + int64_t timeUs; + CHECK(msg->findInt64("timeUs", &timeUs)); - default: - TRESPASS(); - break; + if (!mReconfigurationInProgress) { + changeConfiguration(timeUs, getBandwidthIndex()); } - return mLastPlaylistFetchTimeUs + minPlaylistAgeUs <= nowUs; + return OK; } -void LiveSession::onDownloadNext() { - size_t bandwidthIndex = getBandwidthIndex(); - -rinse_repeat: - int64_t nowUs = ALooper::GetNowUs(); - - if (mLastPlaylistFetchTimeUs < 0 - || (ssize_t)bandwidthIndex != mPrevBandwidthIndex - || (!mPlaylist->isComplete() && timeToRefreshPlaylist(nowUs))) { - AString url; - if (mBandwidthItems.size() > 0) { - url = mBandwidthItems.editItemAt(bandwidthIndex).mURI; - } else { - url = mMasterURL; - } - - if ((ssize_t)bandwidthIndex != mPrevBandwidthIndex) { - // If we switch bandwidths, do not pay any heed to whether - // playlists changed since the last time... - mPlaylist.clear(); - } - - bool unchanged; - sp<M3UParser> playlist = fetchPlaylist(url.c_str(), &unchanged); - if (playlist == NULL) { - if (unchanged) { - // We succeeded in fetching the playlist, but it was - // unchanged from the last time we tried. - } else { - ALOGE("failed to load playlist at url '%s'", url.c_str()); - signalEOS(ERROR_IO); - - return; - } - } else { - mPlaylist = playlist; - } - - if (!mDurationFixed) { - Mutex::Autolock autoLock(mLock); - - if (!mPlaylist->isComplete() && !mPlaylist->isEvent()) { - mDurationUs = -1; - mDurationFixed = true; - } else { - mDurationUs = 0; - for (size_t i = 0; i < mPlaylist->size(); ++i) { - sp<AMessage> itemMeta; - CHECK(mPlaylist->itemAt( - i, NULL /* uri */, &itemMeta)); - - int64_t itemDurationUs; - CHECK(itemMeta->findInt64("durationUs", &itemDurationUs)); - - mDurationUs += itemDurationUs; - } +status_t LiveSession::getDuration(int64_t *durationUs) const { + int64_t maxDurationUs = 0ll; + for (size_t i = 0; i < mFetcherInfos.size(); ++i) { + int64_t fetcherDurationUs = mFetcherInfos.valueAt(i).mDurationUs; - mDurationFixed = mPlaylist->isComplete(); - } + if (fetcherDurationUs >= 0ll && fetcherDurationUs > maxDurationUs) { + maxDurationUs = fetcherDurationUs; } - - mLastPlaylistFetchTimeUs = ALooper::GetNowUs(); } - int32_t firstSeqNumberInPlaylist; - if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32( - "media-sequence", &firstSeqNumberInPlaylist)) { - firstSeqNumberInPlaylist = 0; - } + *durationUs = maxDurationUs; - bool seekDiscontinuity = false; - bool explicitDiscontinuity = false; - bool bandwidthChanged = false; - - if (mSeekTimeUs >= 0) { - if (mPlaylist->isComplete() || mPlaylist->isEvent()) { - size_t index = 0; - int64_t segmentStartUs = 0; - while (index < mPlaylist->size()) { - sp<AMessage> itemMeta; - CHECK(mPlaylist->itemAt( - index, NULL /* uri */, &itemMeta)); - - int64_t itemDurationUs; - CHECK(itemMeta->findInt64("durationUs", &itemDurationUs)); + return OK; +} - if (mSeekTimeUs < segmentStartUs + itemDurationUs) { - break; - } +bool LiveSession::isSeekable() const { + int64_t durationUs; + return getDuration(&durationUs) == OK && durationUs >= 0; +} - segmentStartUs += itemDurationUs; - ++index; - } +bool LiveSession::hasDynamicDuration() const { + return false; +} - if (index < mPlaylist->size()) { - int32_t newSeqNumber = firstSeqNumberInPlaylist + index; +void LiveSession::changeConfiguration(int64_t timeUs, size_t bandwidthIndex) { + CHECK(!mReconfigurationInProgress); + mReconfigurationInProgress = true; - ALOGI("seeking to seq no %d", newSeqNumber); + mPrevBandwidthIndex = bandwidthIndex; - mSeqNumber = newSeqNumber; + ALOGV("changeConfiguration => timeUs:%lld us, bwIndex:%d", + timeUs, bandwidthIndex); - mDataSource->reset(); + mPlaylist->pickRandomMediaItems(); - // reseting the data source will have had the - // side effect of discarding any previously queued - // bandwidth change discontinuity. - // Therefore we'll need to treat these seek - // discontinuities as involving a bandwidth change - // even if they aren't directly. - seekDiscontinuity = true; - bandwidthChanged = true; - } - } + CHECK_LT(bandwidthIndex, mBandwidthItems.size()); + const BandwidthItem &item = mBandwidthItems.itemAt(bandwidthIndex); - mSeekTimeUs = -1; + uint32_t streamMask = 0; - Mutex::Autolock autoLock(mLock); - mSeekDone = true; - mCondition.broadcast(); + AString audioURI; + if (mPlaylist->getAudioURI(item.mPlaylistIndex, &audioURI)) { + streamMask |= STREAMTYPE_AUDIO; } - const int32_t lastSeqNumberInPlaylist = - firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1; - - if (mSeqNumber < 0) { - if (mPlaylist->isComplete()) { - mSeqNumber = firstSeqNumberInPlaylist; - } else { - // If this is a live session, start 3 segments from the end. - mSeqNumber = lastSeqNumberInPlaylist - 3; - if (mSeqNumber < firstSeqNumberInPlaylist) { - mSeqNumber = firstSeqNumberInPlaylist; - } - } + AString videoURI; + if (mPlaylist->getVideoURI(item.mPlaylistIndex, &videoURI)) { + streamMask |= STREAMTYPE_VIDEO; } - if (mSeqNumber < firstSeqNumberInPlaylist - || mSeqNumber > lastSeqNumberInPlaylist) { - if (mPrevBandwidthIndex != (ssize_t)bandwidthIndex) { - // Go back to the previous bandwidth. + AString subtitleURI; + if (mPlaylist->getSubtitleURI(item.mPlaylistIndex, &subtitleURI)) { + streamMask |= STREAMTYPE_SUBTITLES; + } - ALOGI("new bandwidth does not have the sequence number " - "we're looking for, switching back to previous bandwidth"); + // Step 1, stop and discard fetchers that are no longer needed. + // Pause those that we'll reuse. + for (size_t i = 0; i < mFetcherInfos.size(); ++i) { + const AString &uri = mFetcherInfos.keyAt(i); - mLastPlaylistFetchTimeUs = -1; - bandwidthIndex = mPrevBandwidthIndex; - goto rinse_repeat; - } + bool discardFetcher = true; - if (!mPlaylist->isComplete() && mNumRetries < kMaxNumRetries) { - ++mNumRetries; - - if (mSeqNumber > lastSeqNumberInPlaylist) { - mLastPlaylistFetchTimeUs = -1; - postMonitorQueue(3000000ll); - return; + // If we're seeking all current fetchers are discarded. + if (timeUs < 0ll) { + if (((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) + || ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) + || ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI)) { + discardFetcher = false; } + } - // we've missed the boat, let's start from the lowest sequence - // number available and signal a discontinuity. - - ALOGI("We've missed the boat, restarting playback."); - mSeqNumber = lastSeqNumberInPlaylist; - explicitDiscontinuity = true; - - // fall through + if (discardFetcher) { + mFetcherInfos.valueAt(i).mFetcher->stopAsync(); } else { - ALOGE("Cannot find sequence number %d in playlist " - "(contains %d - %d)", - mSeqNumber, firstSeqNumberInPlaylist, - firstSeqNumberInPlaylist + mPlaylist->size() - 1); - - signalEOS(ERROR_END_OF_STREAM); - return; + mFetcherInfos.valueAt(i).mFetcher->pauseAsync(); } } - mNumRetries = 0; - - AString uri; - sp<AMessage> itemMeta; - CHECK(mPlaylist->itemAt( - mSeqNumber - firstSeqNumberInPlaylist, - &uri, - &itemMeta)); - - int32_t val; - if (itemMeta->findInt32("discontinuity", &val) && val != 0) { - explicitDiscontinuity = true; + sp<AMessage> msg = new AMessage(kWhatChangeConfiguration2, id()); + msg->setInt32("streamMask", streamMask); + msg->setInt64("timeUs", timeUs); + if (streamMask & STREAMTYPE_AUDIO) { + msg->setString("audioURI", audioURI.c_str()); } - - int64_t range_offset, range_length; - if (!itemMeta->findInt64("range-offset", &range_offset) - || !itemMeta->findInt64("range-length", &range_length)) { - range_offset = 0; - range_length = -1; + if (streamMask & STREAMTYPE_VIDEO) { + msg->setString("videoURI", videoURI.c_str()); } - - ALOGV("fetching segment %d from (%d .. %d)", - mSeqNumber, firstSeqNumberInPlaylist, lastSeqNumberInPlaylist); - - sp<ABuffer> buffer; - status_t err = fetchFile(uri.c_str(), &buffer, range_offset, range_length); - if (err != OK) { - ALOGE("failed to fetch .ts segment at url '%s'", uri.c_str()); - signalEOS(err); - return; + if (streamMask & STREAMTYPE_SUBTITLES) { + msg->setString("subtitleURI", subtitleURI.c_str()); } - CHECK(buffer != NULL); - - err = decryptBuffer(mSeqNumber - firstSeqNumberInPlaylist, buffer); + // Every time a fetcher acknowledges the stopAsync or pauseAsync request + // we'll decrement mContinuationCounter, once it reaches zero, i.e. all + // fetchers have completed their asynchronous operation, we'll post + // mContinuation, which then is handled below in onChangeConfiguration2. + mContinuationCounter = mFetcherInfos.size(); + mContinuation = msg; - if (err != OK) { - ALOGE("decryptBuffer failed w/ error %d", err); - - signalEOS(err); - return; + if (mContinuationCounter == 0) { + msg->post(); } +} - if (buffer->size() == 0 || buffer->data()[0] != 0x47) { - // Not a transport stream??? - - ALOGE("This doesn't look like a transport stream..."); - - mBandwidthItems.removeAt(bandwidthIndex); - - if (mBandwidthItems.isEmpty()) { - signalEOS(ERROR_UNSUPPORTED); - return; - } +void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) { + mContinuation.clear(); - ALOGI("Retrying with a different bandwidth stream."); + // All fetchers are either suspended or have been removed now. - mLastPlaylistFetchTimeUs = -1; - bandwidthIndex = getBandwidthIndex(); - mPrevBandwidthIndex = bandwidthIndex; - mSeqNumber = -1; + uint32_t streamMask; + CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask)); - goto rinse_repeat; + AString audioURI, videoURI, subtitleURI; + if (streamMask & STREAMTYPE_AUDIO) { + CHECK(msg->findString("audioURI", &audioURI)); + ALOGV("audioURI = '%s'", audioURI.c_str()); } - - if ((size_t)mPrevBandwidthIndex != bandwidthIndex) { - bandwidthChanged = true; + if (streamMask & STREAMTYPE_VIDEO) { + CHECK(msg->findString("videoURI", &videoURI)); + ALOGV("videoURI = '%s'", videoURI.c_str()); } - - if (mPrevBandwidthIndex < 0) { - // Don't signal a bandwidth change at the very beginning of - // playback. - bandwidthChanged = false; + if (streamMask & STREAMTYPE_SUBTITLES) { + CHECK(msg->findString("subtitleURI", &subtitleURI)); + ALOGV("subtitleURI = '%s'", subtitleURI.c_str()); } - if (mStartOfPlayback) { - seekDiscontinuity = true; - mStartOfPlayback = false; + // Determine which decoders to shutdown on the player side, + // a decoder has to be shutdown if either + // 1) its streamtype was active before but now longer isn't. + // or + // 2) its streamtype was already active and still is but the URI + // has changed. + uint32_t changedMask = 0; + if (((mStreamMask & streamMask & STREAMTYPE_AUDIO) + && !(audioURI == mAudioURI)) + || (mStreamMask & ~streamMask & STREAMTYPE_AUDIO)) { + changedMask |= STREAMTYPE_AUDIO; + } + if (((mStreamMask & streamMask & STREAMTYPE_VIDEO) + && !(videoURI == mVideoURI)) + || (mStreamMask & ~streamMask & STREAMTYPE_VIDEO)) { + changedMask |= STREAMTYPE_VIDEO; } - if (seekDiscontinuity || explicitDiscontinuity || bandwidthChanged) { - // Signal discontinuity. - - ALOGI("queueing discontinuity (seek=%d, explicit=%d, bandwidthChanged=%d)", - seekDiscontinuity, explicitDiscontinuity, bandwidthChanged); + if (changedMask == 0) { + // If nothing changed as far as the audio/video decoders + // are concerned we can proceed. + onChangeConfiguration3(msg); + return; + } - sp<ABuffer> tmp = new ABuffer(188); - memset(tmp->data(), 0, tmp->size()); + // Something changed, inform the player which will shutdown the + // corresponding decoders and will post the reply once that's done. + // Handling the reply will continue executing below in + // onChangeConfiguration3. + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatStreamsChanged); + notify->setInt32("changedMask", changedMask); - // signal a 'hard' discontinuity for explicit or bandwidthChanged. - uint8_t type = (explicitDiscontinuity || bandwidthChanged) ? 1 : 0; + msg->setWhat(kWhatChangeConfiguration3); + msg->setTarget(id()); - if (mPlaylist->isComplete() || mPlaylist->isEvent()) { - // If this was a live event this made no sense since - // we don't have access to all the segment before the current - // one. - int64_t segmentStartTimeUs = getSegmentStartTimeUs(mSeqNumber); - memcpy(tmp->data() + 2, &segmentStartTimeUs, sizeof(segmentStartTimeUs)); + notify->setMessage("reply", msg); + notify->post(); +} - type |= 2; - } +void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) { + // All remaining fetchers are still suspended, the player has shutdown + // any decoders that needed it. - tmp->data()[1] = type; + uint32_t streamMask; + CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask)); - mDataSource->queueBuffer(tmp); + AString audioURI, videoURI, subtitleURI; + if (streamMask & STREAMTYPE_AUDIO) { + CHECK(msg->findString("audioURI", &audioURI)); + } + if (streamMask & STREAMTYPE_VIDEO) { + CHECK(msg->findString("videoURI", &videoURI)); + } + if (streamMask & STREAMTYPE_SUBTITLES) { + CHECK(msg->findString("subtitleURI", &subtitleURI)); } - mDataSource->queueBuffer(buffer); + int64_t timeUs; + CHECK(msg->findInt64("timeUs", &timeUs)); - mPrevBandwidthIndex = bandwidthIndex; - ++mSeqNumber; + if (timeUs < 0ll) { + timeUs = mLastDequeuedTimeUs; + } - postMonitorQueue(); -} + mStreamMask = streamMask; + mAudioURI = audioURI; + mVideoURI = videoURI; + mSubtitleURI = subtitleURI; -void LiveSession::signalEOS(status_t err) { - if (mInPreparationPhase && mNotify != NULL) { - sp<AMessage> notify = mNotify->dup(); + // Resume all existing fetchers and assign them packet sources. + for (size_t i = 0; i < mFetcherInfos.size(); ++i) { + const AString &uri = mFetcherInfos.keyAt(i); - notify->setInt32( - "what", - err == ERROR_END_OF_STREAM - ? kWhatPrepared : kWhatPreparationFailed); + uint32_t resumeMask = 0; - if (err != ERROR_END_OF_STREAM) { - notify->setInt32("err", err); + sp<AnotherPacketSource> audioSource; + if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) { + audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO); + resumeMask |= STREAMTYPE_AUDIO; } - notify->post(); - - mInPreparationPhase = false; - } - - mDataSource->queueEOS(err); -} - -void LiveSession::onMonitorQueue() { - if (mSeekTimeUs >= 0 - || mDataSource->countQueuedBuffers() < kMaxNumQueuedFragments) { - onDownloadNext(); - } else { - if (mInPreparationPhase) { - if (mNotify != NULL) { - sp<AMessage> notify = mNotify->dup(); - notify->setInt32("what", kWhatPrepared); - notify->post(); - } - - mInPreparationPhase = false; + sp<AnotherPacketSource> videoSource; + if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) { + videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO); + resumeMask |= STREAMTYPE_VIDEO; } - postMonitorQueue(1000000ll); - } -} + sp<AnotherPacketSource> subtitleSource; + if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) { + subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES); + resumeMask |= STREAMTYPE_SUBTITLES; + } -status_t LiveSession::decryptBuffer( - size_t playlistIndex, const sp<ABuffer> &buffer) { - sp<AMessage> itemMeta; - bool found = false; - AString method; + CHECK_NE(resumeMask, 0u); - for (ssize_t i = playlistIndex; i >= 0; --i) { - AString uri; - CHECK(mPlaylist->itemAt(i, &uri, &itemMeta)); + ALOGV("resuming fetchers for mask 0x%08x", resumeMask); - if (itemMeta->findString("cipher-method", &method)) { - found = true; - break; - } - } + streamMask &= ~resumeMask; - if (!found) { - method = "NONE"; + mFetcherInfos.valueAt(i).mFetcher->startAsync( + audioSource, videoSource, subtitleSource); } - if (method == "NONE") { - return OK; - } else if (!(method == "AES-128")) { - ALOGE("Unsupported cipher method '%s'", method.c_str()); - return ERROR_UNSUPPORTED; - } + // streamMask now only contains the types that need a new fetcher created. - AString keyURI; - if (!itemMeta->findString("cipher-uri", &keyURI)) { - ALOGE("Missing key uri"); - return ERROR_MALFORMED; + if (streamMask != 0) { + ALOGV("creating new fetchers for mask 0x%08x", streamMask); } - ssize_t index = mAESKeyForURI.indexOfKey(keyURI); - - sp<ABuffer> key; - if (index >= 0) { - key = mAESKeyForURI.valueAt(index); - } else { - key = new ABuffer(16); - - sp<HTTPBase> keySource = - HTTPBase::Create( - (mFlags & kFlagIncognito) - ? HTTPBase::kFlagIncognito - : 0); + while (streamMask != 0) { + StreamType streamType = (StreamType)(streamMask & ~(streamMask - 1)); - if (mUIDValid) { - keySource->setUID(mUID); + AString uri; + switch (streamType) { + case STREAMTYPE_AUDIO: + uri = audioURI; + break; + case STREAMTYPE_VIDEO: + uri = videoURI; + break; + case STREAMTYPE_SUBTITLES: + uri = subtitleURI; + break; + default: + TRESPASS(); } - status_t err = - keySource->connect( - keyURI.c_str(), - mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders); - - if (err == OK) { - size_t offset = 0; - while (offset < 16) { - ssize_t n = keySource->readAt( - offset, key->data() + offset, 16 - offset); - if (n <= 0) { - err = ERROR_IO; - break; - } + sp<PlaylistFetcher> fetcher = addFetcher(uri.c_str()); + CHECK(fetcher != NULL); - offset += n; - } - } + sp<AnotherPacketSource> audioSource; + if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) { + audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO); + audioSource->clear(); - if (err != OK) { - ALOGE("failed to fetch cipher key from '%s'.", keyURI.c_str()); - return ERROR_IO; + streamMask &= ~STREAMTYPE_AUDIO; } - mAESKeyForURI.add(keyURI, key); - } + sp<AnotherPacketSource> videoSource; + if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) { + videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO); + videoSource->clear(); - AES_KEY aes_key; - if (AES_set_decrypt_key(key->data(), 128, &aes_key) != 0) { - ALOGE("failed to set AES decryption key."); - return UNKNOWN_ERROR; - } - - unsigned char aes_ivec[16]; - - AString iv; - if (itemMeta->findString("cipher-iv", &iv)) { - if ((!iv.startsWith("0x") && !iv.startsWith("0X")) - || iv.size() != 16 * 2 + 2) { - ALOGE("malformed cipher IV '%s'.", iv.c_str()); - return ERROR_MALFORMED; + streamMask &= ~STREAMTYPE_VIDEO; } - memset(aes_ivec, 0, sizeof(aes_ivec)); - for (size_t i = 0; i < 16; ++i) { - char c1 = tolower(iv.c_str()[2 + 2 * i]); - char c2 = tolower(iv.c_str()[3 + 2 * i]); - if (!isxdigit(c1) || !isxdigit(c2)) { - ALOGE("malformed cipher IV '%s'.", iv.c_str()); - return ERROR_MALFORMED; - } - uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10; - uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10; + sp<AnotherPacketSource> subtitleSource; + if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) { + subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES); + subtitleSource->clear(); - aes_ivec[i] = nibble1 << 4 | nibble2; + streamMask &= ~STREAMTYPE_SUBTITLES; } - } else { - memset(aes_ivec, 0, sizeof(aes_ivec)); - aes_ivec[15] = mSeqNumber & 0xff; - aes_ivec[14] = (mSeqNumber >> 8) & 0xff; - aes_ivec[13] = (mSeqNumber >> 16) & 0xff; - aes_ivec[12] = (mSeqNumber >> 24) & 0xff; + + fetcher->startAsync(audioSource, videoSource, subtitleSource, timeUs); } - AES_cbc_encrypt( - buffer->data(), buffer->data(), buffer->size(), - &aes_key, aes_ivec, AES_DECRYPT); + // All fetchers have now been started, the configuration change + // has completed. - // hexdump(buffer->data(), buffer->size()); + scheduleCheckBandwidthEvent(); - size_t n = buffer->size(); - CHECK_GT(n, 0u); + ALOGV("XXX configuration change completed."); - size_t pad = buffer->data()[n - 1]; + mReconfigurationInProgress = false; - CHECK_GT(pad, 0u); - CHECK_LE(pad, 16u); - CHECK_GE((size_t)n, pad); - for (size_t i = 0; i < pad; ++i) { - CHECK_EQ((unsigned)buffer->data()[n - 1 - i], pad); + if (mDisconnectReplyID != 0) { + finishDisconnect(); } +} - n -= pad; - - buffer->setRange(buffer->offset(), n); - - return OK; +void LiveSession::scheduleCheckBandwidthEvent() { + sp<AMessage> msg = new AMessage(kWhatCheckBandwidth, id()); + msg->setInt32("generation", mCheckBandwidthGeneration); + msg->post(10000000ll); } -void LiveSession::postMonitorQueue(int64_t delayUs) { - sp<AMessage> msg = new AMessage(kWhatMonitorQueue, id()); - msg->setInt32("generation", ++mMonitorQueueGeneration); - msg->post(delayUs); +void LiveSession::cancelCheckBandwidthEvent() { + ++mCheckBandwidthGeneration; } -void LiveSession::onSeek(const sp<AMessage> &msg) { - int64_t timeUs; - CHECK(msg->findInt64("timeUs", &timeUs)); +void LiveSession::onCheckBandwidth() { + if (mReconfigurationInProgress) { + scheduleCheckBandwidthEvent(); + return; + } + + size_t bandwidthIndex = getBandwidthIndex(); + if (mPrevBandwidthIndex < 0 + || bandwidthIndex != (size_t)mPrevBandwidthIndex) { + changeConfiguration(-1ll /* timeUs */, bandwidthIndex); + } - mSeekTimeUs = timeUs; - postMonitorQueue(); + // Handling the kWhatCheckBandwidth even here does _not_ automatically + // schedule another one on return, only an explicit call to + // scheduleCheckBandwidthEvent will do that. + // This ensures that only one configuration change is ongoing at any + // one time, once that completes it'll schedule another check bandwidth + // event. } -status_t LiveSession::getDuration(int64_t *durationUs) const { - Mutex::Autolock autoLock(mLock); - *durationUs = mDurationUs; +void LiveSession::postPrepared(status_t err) { + CHECK(mInPreparationPhase); - return OK; -} + sp<AMessage> notify = mNotify->dup(); + if (err == OK || err == ERROR_END_OF_STREAM) { + notify->setInt32("what", kWhatPrepared); + } else { + notify->setInt32("what", kWhatPreparationFailed); + notify->setInt32("err", err); + } -bool LiveSession::isSeekable() const { - int64_t durationUs; - return getDuration(&durationUs) == OK && durationUs >= 0; -} + notify->post(); -bool LiveSession::hasDynamicDuration() const { - return !mDurationFixed; + mInPreparationPhase = false; } } // namespace android diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/httplive/LiveSession.h index db44a33..b134725 100644 --- a/media/libstagefright/include/LiveSession.h +++ b/media/libstagefright/httplive/LiveSession.h @@ -25,10 +25,12 @@ namespace android { struct ABuffer; +struct AnotherPacketSource; struct DataSource; +struct HTTPBase; struct LiveDataSource; struct M3UParser; -struct HTTPBase; +struct PlaylistFetcher; struct LiveSession : public AHandler { enum Flags { @@ -39,24 +41,32 @@ struct LiveSession : public AHandler { const sp<AMessage> ¬ify, uint32_t flags = 0, bool uidValid = false, uid_t uid = 0); - sp<DataSource> getDataSource(); + enum StreamType { + STREAMTYPE_AUDIO = 1, + STREAMTYPE_VIDEO = 2, + STREAMTYPE_SUBTITLES = 4, + }; + status_t dequeueAccessUnit(StreamType stream, sp<ABuffer> *accessUnit); + + status_t getStreamFormat(StreamType stream, sp<AMessage> *format); - void connect( + void connectAsync( const char *url, const KeyedVector<String8, String8> *headers = NULL); - void disconnect(); + status_t disconnect(); // Blocks until seek is complete. - void seekTo(int64_t timeUs); + status_t seekTo(int64_t timeUs); status_t getDuration(int64_t *durationUs) const; bool isSeekable() const; bool hasDynamicDuration() const; - // Posted notification's "what" field will carry one of the following: enum { + kWhatStreamsChanged, + kWhatError, kWhatPrepared, kWhatPreparationFailed, }; @@ -67,23 +77,30 @@ protected: virtual void onMessageReceived(const sp<AMessage> &msg); private: - enum { - kMaxNumQueuedFragments = 3, - kMaxNumRetries = 5, - }; + friend struct PlaylistFetcher; enum { - kWhatConnect = 'conn', - kWhatDisconnect = 'disc', - kWhatMonitorQueue = 'moni', - kWhatSeek = 'seek', + kWhatConnect = 'conn', + kWhatDisconnect = 'disc', + kWhatSeek = 'seek', + kWhatFetcherNotify = 'notf', + kWhatCheckBandwidth = 'bndw', + kWhatChangeConfiguration2 = 'chC2', + kWhatChangeConfiguration3 = 'chC3', + kWhatFinishDisconnect2 = 'fin2', }; struct BandwidthItem { - AString mURI; + size_t mPlaylistIndex; unsigned long mBandwidth; }; + struct FetcherInfo { + sp<PlaylistFetcher> mFetcher; + int64_t mDurationUs; + bool mIsPrepared; + }; + sp<AMessage> mNotify; uint32_t mFlags; bool mUIDValid; @@ -91,71 +108,61 @@ private: bool mInPreparationPhase; - sp<LiveDataSource> mDataSource; - sp<HTTPBase> mHTTPDataSource; + KeyedVector<String8, String8> mExtraHeaders; AString mMasterURL; - KeyedVector<String8, String8> mExtraHeaders; Vector<BandwidthItem> mBandwidthItems; - - KeyedVector<AString, sp<ABuffer> > mAESKeyForURI; - ssize_t mPrevBandwidthIndex; - int64_t mLastPlaylistFetchTimeUs; + sp<M3UParser> mPlaylist; - int32_t mSeqNumber; - int64_t mSeekTimeUs; - int32_t mNumRetries; - bool mStartOfPlayback; - - mutable Mutex mLock; - Condition mCondition; - int64_t mDurationUs; - bool mDurationFixed; // Duration has been determined once and for all. - bool mSeekDone; - bool mDisconnectPending; - - int32_t mMonitorQueueGeneration; - - enum RefreshState { - INITIAL_MINIMUM_RELOAD_DELAY, - FIRST_UNCHANGED_RELOAD_ATTEMPT, - SECOND_UNCHANGED_RELOAD_ATTEMPT, - THIRD_UNCHANGED_RELOAD_ATTEMPT - }; - RefreshState mRefreshState; - uint8_t mPlaylistHash[16]; + KeyedVector<AString, FetcherInfo> mFetcherInfos; + AString mAudioURI, mVideoURI, mSubtitleURI; + uint32_t mStreamMask; + + KeyedVector<StreamType, sp<AnotherPacketSource> > mPacketSources; + + int32_t mCheckBandwidthGeneration; + + size_t mContinuationCounter; + sp<AMessage> mContinuation; + + int64_t mLastDequeuedTimeUs; + + bool mReconfigurationInProgress; + uint32_t mDisconnectReplyID; + + sp<PlaylistFetcher> addFetcher(const char *uri); void onConnect(const sp<AMessage> &msg); - void onDisconnect(); - void onDownloadNext(); - void onMonitorQueue(); - void onSeek(const sp<AMessage> &msg); + status_t onSeek(const sp<AMessage> &msg); + void onFinishDisconnect2(); status_t fetchFile( const char *url, sp<ABuffer> *out, int64_t range_offset = 0, int64_t range_length = -1); - sp<M3UParser> fetchPlaylist(const char *url, bool *unchanged); + sp<M3UParser> fetchPlaylist( + const char *url, uint8_t *curPlaylistHash, bool *unchanged); + size_t getBandwidthIndex(); - status_t decryptBuffer( - size_t playlistIndex, const sp<ABuffer> &buffer); + static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *); - void postMonitorQueue(int64_t delayUs = 0); + void changeConfiguration(int64_t timeUs, size_t bandwidthIndex); + void onChangeConfiguration2(const sp<AMessage> &msg); + void onChangeConfiguration3(const sp<AMessage> &msg); - bool timeToRefreshPlaylist(int64_t nowUs) const; + void scheduleCheckBandwidthEvent(); + void cancelCheckBandwidthEvent(); - static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *); + void onCheckBandwidth(); - // Returns the media time in us of the segment specified by seqNumber. - // This is computed by summing the durations of all segments before it. - int64_t getSegmentStartTimeUs(int32_t seqNumber) const; + void finishDisconnect(); - void signalEOS(status_t err); + void postPrepared(status_t err); DISALLOW_EVIL_CONSTRUCTORS(LiveSession); }; diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp index 68bbca2..be66252 100644 --- a/media/libstagefright/httplive/M3UParser.cpp +++ b/media/libstagefright/httplive/M3UParser.cpp @@ -18,14 +18,153 @@ #define LOG_TAG "M3UParser" #include <utils/Log.h> -#include "include/M3UParser.h" +#include "M3UParser.h" +#include <cutils/properties.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/MediaErrors.h> namespace android { +struct M3UParser::MediaGroup : public RefBase { + enum Type { + TYPE_AUDIO, + TYPE_VIDEO, + TYPE_SUBS, + }; + + enum FlagBits { + FLAG_AUTOSELECT = 1, + FLAG_DEFAULT = 2, + FLAG_FORCED = 4, + FLAG_HAS_LANGUAGE = 8, + FLAG_HAS_URI = 16, + }; + + MediaGroup(Type type); + + Type type() const; + + status_t addMedia( + const char *name, + const char *uri, + const char *language, + uint32_t flags); + + bool getActiveURI(AString *uri) const; + + void pickRandomMediaItems(); + +protected: + virtual ~MediaGroup(); + +private: + struct Media { + AString mName; + AString mURI; + AString mLanguage; + uint32_t mFlags; + }; + + Type mType; + Vector<Media> mMediaItems; + + ssize_t mSelectedIndex; + + DISALLOW_EVIL_CONSTRUCTORS(MediaGroup); +}; + +M3UParser::MediaGroup::MediaGroup(Type type) + : mType(type), + mSelectedIndex(-1) { +} + +M3UParser::MediaGroup::~MediaGroup() { +} + +M3UParser::MediaGroup::Type M3UParser::MediaGroup::type() const { + return mType; +} + +status_t M3UParser::MediaGroup::addMedia( + const char *name, + const char *uri, + const char *language, + uint32_t flags) { + mMediaItems.push(); + Media &item = mMediaItems.editItemAt(mMediaItems.size() - 1); + + item.mName = name; + + if (uri) { + item.mURI = uri; + } + + if (language) { + item.mLanguage = language; + } + + item.mFlags = flags; + + return OK; +} + +void M3UParser::MediaGroup::pickRandomMediaItems() { +#if 1 + switch (mType) { + case TYPE_AUDIO: + { + char value[PROPERTY_VALUE_MAX]; + if (property_get("media.httplive.audio-index", value, NULL)) { + char *end; + mSelectedIndex = strtoul(value, &end, 10); + CHECK(end > value && *end == '\0'); + + if (mSelectedIndex >= mMediaItems.size()) { + mSelectedIndex = mMediaItems.size() - 1; + } + } else { + mSelectedIndex = 0; + } + break; + } + + case TYPE_VIDEO: + { + mSelectedIndex = 0; + break; + } + + case TYPE_SUBS: + { + mSelectedIndex = -1; + break; + } + + default: + TRESPASS(); + } +#else + mSelectedIndex = (rand() * mMediaItems.size()) / RAND_MAX; +#endif +} + +bool M3UParser::MediaGroup::getActiveURI(AString *uri) const { + for (size_t i = 0; i < mMediaItems.size(); ++i) { + if (mSelectedIndex >= 0 && i == (size_t)mSelectedIndex) { + const Media &item = mMediaItems.itemAt(i); + + *uri = item.mURI; + return true; + } + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + M3UParser::M3UParser( const char *baseURI, const void *data, size_t size) : mInitCheck(NO_INIT), @@ -92,6 +231,58 @@ bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) { return true; } +void M3UParser::pickRandomMediaItems() { + for (size_t i = 0; i < mMediaGroups.size(); ++i) { + mMediaGroups.valueAt(i)->pickRandomMediaItems(); + } +} + +bool M3UParser::getTypeURI(size_t index, const char *key, AString *uri) const { + if (!mIsVariantPlaylist) { + *uri = mBaseURI; + + // Assume media without any more specific attribute contains + // audio and video, but no subtitles. + return !strcmp("audio", key) || !strcmp("video", key); + } + + CHECK_LT(index, mItems.size()); + + sp<AMessage> meta = mItems.itemAt(index).mMeta; + + AString groupID; + if (!meta->findString(key, &groupID)) { + *uri = mItems.itemAt(index).mURI; + + // Assume media without any more specific attribute contains + // audio and video, but no subtitles. + return !strcmp("audio", key) || !strcmp("video", key); + } + + sp<MediaGroup> group = mMediaGroups.valueFor(groupID); + if (!group->getActiveURI(uri)) { + return false; + } + + if ((*uri).empty()) { + *uri = mItems.itemAt(index).mURI; + } + + return true; +} + +bool M3UParser::getAudioURI(size_t index, AString *uri) const { + return getTypeURI(index, "audio", uri); +} + +bool M3UParser::getVideoURI(size_t index, AString *uri) const { + return getTypeURI(index, "video", uri); +} + +bool M3UParser::getSubtitleURI(size_t index, AString *uri) const { + return getTypeURI(index, "subtitles", uri); +} + static bool MakeURL(const char *baseURL, const char *url, AString *out) { out->clear(); @@ -241,6 +432,8 @@ status_t M3UParser::parse(const void *_data, size_t size) { segmentRangeOffset = offset + length; } + } else if (line.startsWith("#EXT-X-MEDIA")) { + err = parseMedia(line); } if (err != OK) { @@ -322,9 +515,31 @@ status_t M3UParser::parseMetaDataDuration( return OK; } -// static +// Find the next occurence of the character "what" at or after "offset", +// but ignore occurences between quotation marks. +// Return the index of the occurrence or -1 if not found. +static ssize_t FindNextUnquoted( + const AString &line, char what, size_t offset) { + CHECK_NE((int)what, (int)'"'); + + bool quoted = false; + while (offset < line.size()) { + char c = line.c_str()[offset]; + + if (c == '"') { + quoted = !quoted; + } else if (c == what && !quoted) { + return offset; + } + + ++offset; + } + + return -1; +} + status_t M3UParser::parseStreamInf( - const AString &line, sp<AMessage> *meta) { + const AString &line, sp<AMessage> *meta) const { ssize_t colonPos = line.find(":"); if (colonPos < 0) { @@ -334,7 +549,7 @@ status_t M3UParser::parseStreamInf( size_t offset = colonPos + 1; while (offset < line.size()) { - ssize_t end = line.find(",", offset); + ssize_t end = FindNextUnquoted(line, ',', offset); if (end < 0) { end = line.size(); } @@ -371,33 +586,35 @@ status_t M3UParser::parseStreamInf( *meta = new AMessage; } (*meta)->setInt32("bandwidth", x); - } - } + } else if (!strcasecmp("audio", key.c_str()) + || !strcasecmp("video", key.c_str()) + || !strcasecmp("subtitles", key.c_str())) { + if (val.size() < 2 + || val.c_str()[0] != '"' + || val.c_str()[val.size() - 1] != '"') { + ALOGE("Expected quoted string for %s attribute, " + "got '%s' instead.", + key.c_str(), val.c_str()); + + return ERROR_MALFORMED; + } - return OK; -} + AString groupID(val, 1, val.size() - 2); + ssize_t groupIndex = mMediaGroups.indexOfKey(groupID); -// Find the next occurence of the character "what" at or after "offset", -// but ignore occurences between quotation marks. -// Return the index of the occurrence or -1 if not found. -static ssize_t FindNextUnquoted( - const AString &line, char what, size_t offset) { - CHECK_NE((int)what, (int)'"'); + if (groupIndex < 0) { + ALOGE("Undefined media group '%s' referenced in stream info.", + groupID.c_str()); - bool quoted = false; - while (offset < line.size()) { - char c = line.c_str()[offset]; + return ERROR_MALFORMED; + } - if (c == '"') { - quoted = !quoted; - } else if (c == what && !quoted) { - return offset; + key.tolower(); + (*meta)->setString(key.c_str(), groupID.c_str()); } - - ++offset; } - return -1; + return OK; } // static @@ -515,6 +732,234 @@ status_t M3UParser::parseByteRange( return OK; } +status_t M3UParser::parseMedia(const AString &line) { + ssize_t colonPos = line.find(":"); + + if (colonPos < 0) { + return ERROR_MALFORMED; + } + + bool haveGroupType = false; + MediaGroup::Type groupType = MediaGroup::TYPE_AUDIO; + + bool haveGroupID = false; + AString groupID; + + bool haveGroupLanguage = false; + AString groupLanguage; + + bool haveGroupName = false; + AString groupName; + + bool haveGroupAutoselect = false; + bool groupAutoselect = false; + + bool haveGroupDefault = false; + bool groupDefault = false; + + bool haveGroupForced = false; + bool groupForced = false; + + bool haveGroupURI = false; + AString groupURI; + + size_t offset = colonPos + 1; + + while (offset < line.size()) { + ssize_t end = FindNextUnquoted(line, ',', offset); + if (end < 0) { + end = line.size(); + } + + AString attr(line, offset, end - offset); + attr.trim(); + + offset = end + 1; + + ssize_t equalPos = attr.find("="); + if (equalPos < 0) { + continue; + } + + AString key(attr, 0, equalPos); + key.trim(); + + AString val(attr, equalPos + 1, attr.size() - equalPos - 1); + val.trim(); + + ALOGV("key=%s value=%s", key.c_str(), val.c_str()); + + if (!strcasecmp("type", key.c_str())) { + if (!strcasecmp("subtitles", val.c_str())) { + groupType = MediaGroup::TYPE_SUBS; + } else if (!strcasecmp("audio", val.c_str())) { + groupType = MediaGroup::TYPE_AUDIO; + } else if (!strcasecmp("video", val.c_str())) { + groupType = MediaGroup::TYPE_VIDEO; + } else { + ALOGE("Invalid media group type '%s'", val.c_str()); + return ERROR_MALFORMED; + } + + haveGroupType = true; + } else if (!strcasecmp("group-id", key.c_str())) { + if (val.size() < 2 + || val.c_str()[0] != '"' + || val.c_str()[val.size() - 1] != '"') { + ALOGE("Expected quoted string for GROUP-ID, got '%s' instead.", + val.c_str()); + + return ERROR_MALFORMED; + } + + groupID.setTo(val, 1, val.size() - 2); + haveGroupID = true; + } else if (!strcasecmp("language", key.c_str())) { + if (val.size() < 2 + || val.c_str()[0] != '"' + || val.c_str()[val.size() - 1] != '"') { + ALOGE("Expected quoted string for LANGUAGE, got '%s' instead.", + val.c_str()); + + return ERROR_MALFORMED; + } + + groupLanguage.setTo(val, 1, val.size() - 2); + haveGroupLanguage = true; + } else if (!strcasecmp("name", key.c_str())) { + if (val.size() < 2 + || val.c_str()[0] != '"' + || val.c_str()[val.size() - 1] != '"') { + ALOGE("Expected quoted string for NAME, got '%s' instead.", + val.c_str()); + + return ERROR_MALFORMED; + } + + groupName.setTo(val, 1, val.size() - 2); + haveGroupName = true; + } else if (!strcasecmp("autoselect", key.c_str())) { + groupAutoselect = false; + if (!strcasecmp("YES", val.c_str())) { + groupAutoselect = true; + } else if (!strcasecmp("NO", val.c_str())) { + groupAutoselect = false; + } else { + ALOGE("Expected YES or NO for AUTOSELECT attribute, " + "got '%s' instead.", + val.c_str()); + + return ERROR_MALFORMED; + } + + haveGroupAutoselect = true; + } else if (!strcasecmp("default", key.c_str())) { + groupDefault = false; + if (!strcasecmp("YES", val.c_str())) { + groupDefault = true; + } else if (!strcasecmp("NO", val.c_str())) { + groupDefault = false; + } else { + ALOGE("Expected YES or NO for DEFAULT attribute, " + "got '%s' instead.", + val.c_str()); + + return ERROR_MALFORMED; + } + + haveGroupDefault = true; + } else if (!strcasecmp("forced", key.c_str())) { + groupForced = false; + if (!strcasecmp("YES", val.c_str())) { + groupForced = true; + } else if (!strcasecmp("NO", val.c_str())) { + groupForced = false; + } else { + ALOGE("Expected YES or NO for FORCED attribute, " + "got '%s' instead.", + val.c_str()); + + return ERROR_MALFORMED; + } + + haveGroupForced = true; + } else if (!strcasecmp("uri", key.c_str())) { + if (val.size() < 2 + || val.c_str()[0] != '"' + || val.c_str()[val.size() - 1] != '"') { + ALOGE("Expected quoted string for URI, got '%s' instead.", + val.c_str()); + + return ERROR_MALFORMED; + } + + AString tmp(val, 1, val.size() - 2); + + if (!MakeURL(mBaseURI.c_str(), tmp.c_str(), &groupURI)) { + ALOGI("Failed to make absolute URI from '%s'.", tmp.c_str()); + } + + haveGroupURI = true; + } + } + + if (!haveGroupType || !haveGroupID || !haveGroupName) { + ALOGE("Incomplete EXT-X-MEDIA element."); + return ERROR_MALFORMED; + } + + uint32_t flags = 0; + if (haveGroupAutoselect && groupAutoselect) { + flags |= MediaGroup::FLAG_AUTOSELECT; + } + if (haveGroupDefault && groupDefault) { + flags |= MediaGroup::FLAG_DEFAULT; + } + if (haveGroupForced) { + if (groupType != MediaGroup::TYPE_SUBS) { + ALOGE("The FORCED attribute MUST not be present on anything " + "but SUBS media."); + + return ERROR_MALFORMED; + } + + if (groupForced) { + flags |= MediaGroup::FLAG_FORCED; + } + } + if (haveGroupLanguage) { + flags |= MediaGroup::FLAG_HAS_LANGUAGE; + } + if (haveGroupURI) { + flags |= MediaGroup::FLAG_HAS_URI; + } + + ssize_t groupIndex = mMediaGroups.indexOfKey(groupID); + sp<MediaGroup> group; + + if (groupIndex < 0) { + group = new MediaGroup(groupType); + mMediaGroups.add(groupID, group); + } else { + group = mMediaGroups.valueAt(groupIndex); + + if (group->type() != groupType) { + ALOGE("Attempt to put media item under group of different type " + "(groupType = %d, item type = %d", + group->type(), + groupType); + + return ERROR_MALFORMED; + } + } + + return group->addMedia( + groupName.c_str(), + haveGroupURI ? groupURI.c_str() : NULL, + haveGroupLanguage ? groupLanguage.c_str() : NULL, + flags); +} + // static status_t M3UParser::ParseInt32(const char *s, int32_t *x) { char *end; diff --git a/media/libstagefright/include/M3UParser.h b/media/libstagefright/httplive/M3UParser.h index 2d2f50f..abea286 100644 --- a/media/libstagefright/include/M3UParser.h +++ b/media/libstagefright/httplive/M3UParser.h @@ -40,10 +40,18 @@ struct M3UParser : public RefBase { size_t size(); bool itemAt(size_t index, AString *uri, sp<AMessage> *meta = NULL); + void pickRandomMediaItems(); + + bool getAudioURI(size_t index, AString *uri) const; + bool getVideoURI(size_t index, AString *uri) const; + bool getSubtitleURI(size_t index, AString *uri) const; + protected: virtual ~M3UParser(); private: + struct MediaGroup; + struct Item { AString mURI; sp<AMessage> mMeta; @@ -60,6 +68,9 @@ private: sp<AMessage> mMeta; Vector<Item> mItems; + // Media groups keyed by group ID. + KeyedVector<AString, sp<MediaGroup> > mMediaGroups; + status_t parse(const void *data, size_t size); static status_t parseMetaData( @@ -68,8 +79,8 @@ private: static status_t parseMetaDataDuration( const AString &line, sp<AMessage> *meta, const char *key); - static status_t parseStreamInf( - const AString &line, sp<AMessage> *meta); + status_t parseStreamInf( + const AString &line, sp<AMessage> *meta) const; static status_t parseCipherInfo( const AString &line, sp<AMessage> *meta, const AString &baseURI); @@ -78,6 +89,10 @@ private: const AString &line, uint64_t curOffset, uint64_t *length, uint64_t *offset); + status_t parseMedia(const AString &line); + + bool getTypeURI(size_t index, const char *key, AString *uri) const; + static status_t ParseInt32(const char *s, int32_t *x); static status_t ParseDouble(const char *s, double *x); diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp new file mode 100644 index 0000000..8ae70b7 --- /dev/null +++ b/media/libstagefright/httplive/PlaylistFetcher.cpp @@ -0,0 +1,969 @@ +/* + * 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 "PlaylistFetcher" +#include <utils/Log.h> + +#include "PlaylistFetcher.h" + +#include "LiveDataSource.h" +#include "LiveSession.h" +#include "M3UParser.h" + +#include "include/avc_utils.h" +#include "include/HTTPBase.h" +#include "include/ID3.h" +#include "mpeg2ts/AnotherPacketSource.h" + +#include <media/IStreamSource.h> +#include <media/stagefright/foundation/ABitReader.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/FileSource.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +#include <ctype.h> +#include <openssl/aes.h> +#include <openssl/md5.h> + +namespace android { + +// static +const int64_t PlaylistFetcher::kMinBufferedDurationUs = 10000000ll; + +PlaylistFetcher::PlaylistFetcher( + const sp<AMessage> ¬ify, + const sp<LiveSession> &session, + const char *uri) + : mNotify(notify), + mSession(session), + mURI(uri), + mStreamTypeMask(0), + mStartTimeUs(-1ll), + mLastPlaylistFetchTimeUs(-1ll), + mSeqNumber(-1), + mNumRetries(0), + mStartup(true), + mNextPTSTimeUs(-1ll), + mMonitorQueueGeneration(0), + mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY), + mFirstPTSValid(false), + mAbsoluteTimeAnchorUs(0ll) { + memset(mPlaylistHash, 0, sizeof(mPlaylistHash)); +} + +PlaylistFetcher::~PlaylistFetcher() { +} + +int64_t PlaylistFetcher::getSegmentStartTimeUs(int32_t seqNumber) const { + CHECK(mPlaylist != NULL); + + int32_t firstSeqNumberInPlaylist; + if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32( + "media-sequence", &firstSeqNumberInPlaylist)) { + firstSeqNumberInPlaylist = 0; + } + + int32_t lastSeqNumberInPlaylist = + firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1; + + CHECK_GE(seqNumber, firstSeqNumberInPlaylist); + CHECK_LE(seqNumber, lastSeqNumberInPlaylist); + + int64_t segmentStartUs = 0ll; + for (int32_t index = 0; + index < seqNumber - firstSeqNumberInPlaylist; ++index) { + sp<AMessage> itemMeta; + CHECK(mPlaylist->itemAt( + index, NULL /* uri */, &itemMeta)); + + int64_t itemDurationUs; + CHECK(itemMeta->findInt64("durationUs", &itemDurationUs)); + + segmentStartUs += itemDurationUs; + } + + return segmentStartUs; +} + +bool PlaylistFetcher::timeToRefreshPlaylist(int64_t nowUs) const { + if (mPlaylist == NULL) { + CHECK_EQ((int)mRefreshState, (int)INITIAL_MINIMUM_RELOAD_DELAY); + return true; + } + + int32_t targetDurationSecs; + CHECK(mPlaylist->meta()->findInt32("target-duration", &targetDurationSecs)); + + int64_t targetDurationUs = targetDurationSecs * 1000000ll; + + int64_t minPlaylistAgeUs; + + switch (mRefreshState) { + case INITIAL_MINIMUM_RELOAD_DELAY: + { + size_t n = mPlaylist->size(); + if (n > 0) { + sp<AMessage> itemMeta; + CHECK(mPlaylist->itemAt(n - 1, NULL /* uri */, &itemMeta)); + + int64_t itemDurationUs; + CHECK(itemMeta->findInt64("durationUs", &itemDurationUs)); + + minPlaylistAgeUs = itemDurationUs; + break; + } + + // fall through + } + + case FIRST_UNCHANGED_RELOAD_ATTEMPT: + { + minPlaylistAgeUs = targetDurationUs / 2; + break; + } + + case SECOND_UNCHANGED_RELOAD_ATTEMPT: + { + minPlaylistAgeUs = (targetDurationUs * 3) / 2; + break; + } + + case THIRD_UNCHANGED_RELOAD_ATTEMPT: + { + minPlaylistAgeUs = targetDurationUs * 3; + break; + } + + default: + TRESPASS(); + break; + } + + return mLastPlaylistFetchTimeUs + minPlaylistAgeUs <= nowUs; +} + +status_t PlaylistFetcher::decryptBuffer( + size_t playlistIndex, const sp<ABuffer> &buffer) { + sp<AMessage> itemMeta; + bool found = false; + AString method; + + for (ssize_t i = playlistIndex; i >= 0; --i) { + AString uri; + CHECK(mPlaylist->itemAt(i, &uri, &itemMeta)); + + if (itemMeta->findString("cipher-method", &method)) { + found = true; + break; + } + } + + if (!found) { + method = "NONE"; + } + + if (method == "NONE") { + return OK; + } else if (!(method == "AES-128")) { + ALOGE("Unsupported cipher method '%s'", method.c_str()); + return ERROR_UNSUPPORTED; + } + + AString keyURI; + if (!itemMeta->findString("cipher-uri", &keyURI)) { + ALOGE("Missing key uri"); + return ERROR_MALFORMED; + } + + ssize_t index = mAESKeyForURI.indexOfKey(keyURI); + + sp<ABuffer> key; + if (index >= 0) { + key = mAESKeyForURI.valueAt(index); + } else { + status_t err = mSession->fetchFile(keyURI.c_str(), &key); + + if (err != OK) { + ALOGE("failed to fetch cipher key from '%s'.", keyURI.c_str()); + return ERROR_IO; + } else if (key->size() != 16) { + ALOGE("key file '%s' wasn't 16 bytes in size.", keyURI.c_str()); + return ERROR_MALFORMED; + } + + mAESKeyForURI.add(keyURI, key); + } + + AES_KEY aes_key; + if (AES_set_decrypt_key(key->data(), 128, &aes_key) != 0) { + ALOGE("failed to set AES decryption key."); + return UNKNOWN_ERROR; + } + + unsigned char aes_ivec[16]; + + AString iv; + if (itemMeta->findString("cipher-iv", &iv)) { + if ((!iv.startsWith("0x") && !iv.startsWith("0X")) + || iv.size() != 16 * 2 + 2) { + ALOGE("malformed cipher IV '%s'.", iv.c_str()); + return ERROR_MALFORMED; + } + + memset(aes_ivec, 0, sizeof(aes_ivec)); + for (size_t i = 0; i < 16; ++i) { + char c1 = tolower(iv.c_str()[2 + 2 * i]); + char c2 = tolower(iv.c_str()[3 + 2 * i]); + if (!isxdigit(c1) || !isxdigit(c2)) { + ALOGE("malformed cipher IV '%s'.", iv.c_str()); + return ERROR_MALFORMED; + } + uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10; + uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10; + + aes_ivec[i] = nibble1 << 4 | nibble2; + } + } else { + memset(aes_ivec, 0, sizeof(aes_ivec)); + aes_ivec[15] = mSeqNumber & 0xff; + aes_ivec[14] = (mSeqNumber >> 8) & 0xff; + aes_ivec[13] = (mSeqNumber >> 16) & 0xff; + aes_ivec[12] = (mSeqNumber >> 24) & 0xff; + } + + AES_cbc_encrypt( + buffer->data(), buffer->data(), buffer->size(), + &aes_key, aes_ivec, AES_DECRYPT); + + // hexdump(buffer->data(), buffer->size()); + + size_t n = buffer->size(); + CHECK_GT(n, 0u); + + size_t pad = buffer->data()[n - 1]; + + CHECK_GT(pad, 0u); + CHECK_LE(pad, 16u); + CHECK_GE((size_t)n, pad); + for (size_t i = 0; i < pad; ++i) { + CHECK_EQ((unsigned)buffer->data()[n - 1 - i], pad); + } + + n -= pad; + + buffer->setRange(buffer->offset(), n); + + return OK; +} + +void PlaylistFetcher::postMonitorQueue(int64_t delayUs) { + sp<AMessage> msg = new AMessage(kWhatMonitorQueue, id()); + msg->setInt32("generation", mMonitorQueueGeneration); + msg->post(delayUs); +} + +void PlaylistFetcher::cancelMonitorQueue() { + ++mMonitorQueueGeneration; +} + +void PlaylistFetcher::startAsync( + const sp<AnotherPacketSource> &audioSource, + const sp<AnotherPacketSource> &videoSource, + const sp<AnotherPacketSource> &subtitleSource, + int64_t startTimeUs) { + sp<AMessage> msg = new AMessage(kWhatStart, id()); + + uint32_t streamTypeMask = 0ul; + + if (audioSource != NULL) { + msg->setPointer("audioSource", audioSource.get()); + streamTypeMask |= LiveSession::STREAMTYPE_AUDIO; + } + + if (videoSource != NULL) { + msg->setPointer("videoSource", videoSource.get()); + streamTypeMask |= LiveSession::STREAMTYPE_VIDEO; + } + + if (subtitleSource != NULL) { + msg->setPointer("subtitleSource", subtitleSource.get()); + streamTypeMask |= LiveSession::STREAMTYPE_SUBTITLES; + } + + msg->setInt32("streamTypeMask", streamTypeMask); + msg->setInt64("startTimeUs", startTimeUs); + msg->post(); +} + +void PlaylistFetcher::pauseAsync() { + (new AMessage(kWhatPause, id()))->post(); +} + +void PlaylistFetcher::stopAsync() { + (new AMessage(kWhatStop, id()))->post(); +} + +void PlaylistFetcher::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatStart: + { + status_t err = onStart(msg); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatStarted); + notify->setInt32("err", err); + notify->post(); + break; + } + + case kWhatPause: + { + onPause(); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatPaused); + notify->post(); + break; + } + + case kWhatStop: + { + onStop(); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatStopped); + notify->post(); + break; + } + + case kWhatMonitorQueue: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + + if (generation != mMonitorQueueGeneration) { + // Stale event + break; + } + + onMonitorQueue(); + break; + } + + default: + TRESPASS(); + } +} + +status_t PlaylistFetcher::onStart(const sp<AMessage> &msg) { + mPacketSources.clear(); + + uint32_t streamTypeMask; + CHECK(msg->findInt32("streamTypeMask", (int32_t *)&streamTypeMask)); + + int64_t startTimeUs; + CHECK(msg->findInt64("startTimeUs", &startTimeUs)); + + if (streamTypeMask & LiveSession::STREAMTYPE_AUDIO) { + void *ptr; + CHECK(msg->findPointer("audioSource", &ptr)); + + mPacketSources.add( + LiveSession::STREAMTYPE_AUDIO, + static_cast<AnotherPacketSource *>(ptr)); + } + + if (streamTypeMask & LiveSession::STREAMTYPE_VIDEO) { + void *ptr; + CHECK(msg->findPointer("videoSource", &ptr)); + + mPacketSources.add( + LiveSession::STREAMTYPE_VIDEO, + static_cast<AnotherPacketSource *>(ptr)); + } + + if (streamTypeMask & LiveSession::STREAMTYPE_SUBTITLES) { + void *ptr; + CHECK(msg->findPointer("subtitleSource", &ptr)); + + mPacketSources.add( + LiveSession::STREAMTYPE_SUBTITLES, + static_cast<AnotherPacketSource *>(ptr)); + } + + mStreamTypeMask = streamTypeMask; + mStartTimeUs = startTimeUs; + + if (mStartTimeUs >= 0ll) { + mSeqNumber = -1; + mStartup = true; + } + + postMonitorQueue(); + + return OK; +} + +void PlaylistFetcher::onPause() { + cancelMonitorQueue(); + + mPacketSources.clear(); + mStreamTypeMask = 0; +} + +void PlaylistFetcher::onStop() { + cancelMonitorQueue(); + + for (size_t i = 0; i < mPacketSources.size(); ++i) { + mPacketSources.valueAt(i)->clear(); + } + + mPacketSources.clear(); + mStreamTypeMask = 0; +} + +void PlaylistFetcher::notifyError(status_t err) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatError); + notify->setInt32("err", err); + notify->post(); +} + +void PlaylistFetcher::queueDiscontinuity( + ATSParser::DiscontinuityType type, const sp<AMessage> &extra) { + for (size_t i = 0; i < mPacketSources.size(); ++i) { + mPacketSources.valueAt(i)->queueDiscontinuity(type, extra); + } +} + +void PlaylistFetcher::onMonitorQueue() { + bool downloadMore = false; + + status_t finalResult; + if (mStreamTypeMask == LiveSession::STREAMTYPE_SUBTITLES) { + sp<AnotherPacketSource> packetSource = + mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES); + + downloadMore = packetSource->hasBufferAvailable(&finalResult); + } else { + bool first = true; + int64_t minBufferedDurationUs = 0ll; + + for (size_t i = 0; i < mPacketSources.size(); ++i) { + if ((mStreamTypeMask & mPacketSources.keyAt(i)) == 0) { + continue; + } + + int64_t bufferedDurationUs = + mPacketSources.valueAt(i)->getBufferedDurationUs(&finalResult); + + if (first || bufferedDurationUs < minBufferedDurationUs) { + minBufferedDurationUs = bufferedDurationUs; + first = false; + } + } + + downloadMore = + !first && (minBufferedDurationUs < kMinBufferedDurationUs); + } + + if (finalResult == OK && downloadMore) { + onDownloadNext(); + } else { + // Nothing to do yet, try again in a second. + + sp<AMessage> msg = mNotify->dup(); + msg->setInt32("what", kWhatTemporarilyDoneFetching); + msg->post(); + + postMonitorQueue(1000000ll); + } +} + +void PlaylistFetcher::onDownloadNext() { + int64_t nowUs = ALooper::GetNowUs(); + + if (mLastPlaylistFetchTimeUs < 0ll + || (!mPlaylist->isComplete() && timeToRefreshPlaylist(nowUs))) { + bool unchanged; + sp<M3UParser> playlist = mSession->fetchPlaylist( + mURI.c_str(), mPlaylistHash, &unchanged); + + if (playlist == NULL) { + if (unchanged) { + // We succeeded in fetching the playlist, but it was + // unchanged from the last time we tried. + + if (mRefreshState != THIRD_UNCHANGED_RELOAD_ATTEMPT) { + mRefreshState = (RefreshState)(mRefreshState + 1); + } + } else { + ALOGE("failed to load playlist at url '%s'", mURI.c_str()); + notifyError(ERROR_IO); + return; + } + } else { + mRefreshState = INITIAL_MINIMUM_RELOAD_DELAY; + mPlaylist = playlist; + + if (mPlaylist->isComplete() || mPlaylist->isEvent()) { + updateDuration(); + } + } + + mLastPlaylistFetchTimeUs = ALooper::GetNowUs(); + } + + int32_t firstSeqNumberInPlaylist; + if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32( + "media-sequence", &firstSeqNumberInPlaylist)) { + firstSeqNumberInPlaylist = 0; + } + + bool seekDiscontinuity = false; + bool explicitDiscontinuity = false; + + const int32_t lastSeqNumberInPlaylist = + firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1; + + if (mSeqNumber < 0) { + CHECK_GE(mStartTimeUs, 0ll); + + if (mPlaylist->isComplete() || mPlaylist->isEvent()) { + mSeqNumber = getSeqNumberForTime(mStartTimeUs); + } else { + // If this is a live session, start 3 segments from the end. + mSeqNumber = lastSeqNumberInPlaylist - 3; + if (mSeqNumber < firstSeqNumberInPlaylist) { + mSeqNumber = firstSeqNumberInPlaylist; + } + } + + mStartTimeUs = -1ll; + } + + if (mSeqNumber < firstSeqNumberInPlaylist + || mSeqNumber > lastSeqNumberInPlaylist) { + if (!mPlaylist->isComplete() && mNumRetries < kMaxNumRetries) { + ++mNumRetries; + + if (mSeqNumber > lastSeqNumberInPlaylist) { + mLastPlaylistFetchTimeUs = -1; + postMonitorQueue(3000000ll); + return; + } + + // we've missed the boat, let's start from the lowest sequence + // number available and signal a discontinuity. + + ALOGI("We've missed the boat, restarting playback."); + mSeqNumber = lastSeqNumberInPlaylist; + explicitDiscontinuity = true; + + // fall through + } else { + ALOGE("Cannot find sequence number %d in playlist " + "(contains %d - %d)", + mSeqNumber, firstSeqNumberInPlaylist, + firstSeqNumberInPlaylist + mPlaylist->size() - 1); + + notifyError(ERROR_END_OF_STREAM); + return; + } + } + + mNumRetries = 0; + + AString uri; + sp<AMessage> itemMeta; + CHECK(mPlaylist->itemAt( + mSeqNumber - firstSeqNumberInPlaylist, + &uri, + &itemMeta)); + + int32_t val; + if (itemMeta->findInt32("discontinuity", &val) && val != 0) { + explicitDiscontinuity = true; + } + + int64_t range_offset, range_length; + if (!itemMeta->findInt64("range-offset", &range_offset) + || !itemMeta->findInt64("range-length", &range_length)) { + range_offset = 0; + range_length = -1; + } + + ALOGV("fetching segment %d from (%d .. %d)", + mSeqNumber, firstSeqNumberInPlaylist, lastSeqNumberInPlaylist); + + ALOGV("fetching '%s'", uri.c_str()); + + sp<ABuffer> buffer; + status_t err = mSession->fetchFile( + uri.c_str(), &buffer, range_offset, range_length); + + if (err != OK) { + ALOGE("failed to fetch .ts segment at url '%s'", uri.c_str()); + notifyError(err); + return; + } + + CHECK(buffer != NULL); + + err = decryptBuffer(mSeqNumber - firstSeqNumberInPlaylist, buffer); + + if (err != OK) { + ALOGE("decryptBuffer failed w/ error %d", err); + + notifyError(err); + return; + } + + if (mStartup || seekDiscontinuity || explicitDiscontinuity) { + // Signal discontinuity. + + if (mPlaylist->isComplete() || mPlaylist->isEvent()) { + // If this was a live event this made no sense since + // we don't have access to all the segment before the current + // one. + mNextPTSTimeUs = getSegmentStartTimeUs(mSeqNumber); + } + + if (seekDiscontinuity || explicitDiscontinuity) { + ALOGI("queueing discontinuity (seek=%d, explicit=%d)", + seekDiscontinuity, explicitDiscontinuity); + + queueDiscontinuity( + explicitDiscontinuity + ? ATSParser::DISCONTINUITY_FORMATCHANGE + : ATSParser::DISCONTINUITY_SEEK, + NULL /* extra */); + } + } + + err = extractAndQueueAccessUnits(buffer); + + if (err != OK) { + notifyError(err); + return; + } + + ++mSeqNumber; + + postMonitorQueue(); + + mStartup = false; +} + +int32_t PlaylistFetcher::getSeqNumberForTime(int64_t timeUs) const { + int32_t firstSeqNumberInPlaylist; + if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32( + "media-sequence", &firstSeqNumberInPlaylist)) { + firstSeqNumberInPlaylist = 0; + } + + size_t index = 0; + int64_t segmentStartUs = 0; + while (index < mPlaylist->size()) { + sp<AMessage> itemMeta; + CHECK(mPlaylist->itemAt( + index, NULL /* uri */, &itemMeta)); + + int64_t itemDurationUs; + CHECK(itemMeta->findInt64("durationUs", &itemDurationUs)); + + if (timeUs < segmentStartUs + itemDurationUs) { + break; + } + + segmentStartUs += itemDurationUs; + ++index; + } + + if (index >= mPlaylist->size()) { + index = mPlaylist->size() - 1; + } + + return firstSeqNumberInPlaylist + index; +} + +status_t PlaylistFetcher::extractAndQueueAccessUnits( + const sp<ABuffer> &buffer) { + if (buffer->size() > 0 && buffer->data()[0] == 0x47) { + // Let's assume this is an MPEG2 transport stream. + + if ((buffer->size() % 188) != 0) { + ALOGE("MPEG2 transport stream is not an even multiple of 188 " + "bytes in length."); + return ERROR_MALFORMED; + } + + if (mTSParser == NULL) { + mTSParser = new ATSParser; + } + + if (mNextPTSTimeUs >= 0ll) { + sp<AMessage> extra = new AMessage; + extra->setInt64(IStreamListener::kKeyMediaTimeUs, mNextPTSTimeUs); + + mTSParser->signalDiscontinuity( + ATSParser::DISCONTINUITY_SEEK, extra); + + mNextPTSTimeUs = -1ll; + } + + size_t offset = 0; + while (offset < buffer->size()) { + status_t err = mTSParser->feedTSPacket(buffer->data() + offset, 188); + + if (err != OK) { + return err; + } + + offset += 188; + } + + for (size_t i = mPacketSources.size(); i-- > 0;) { + sp<AnotherPacketSource> packetSource = mPacketSources.valueAt(i); + + ATSParser::SourceType type; + switch (mPacketSources.keyAt(i)) { + case LiveSession::STREAMTYPE_VIDEO: + type = ATSParser::VIDEO; + break; + + case LiveSession::STREAMTYPE_AUDIO: + type = ATSParser::AUDIO; + break; + + case LiveSession::STREAMTYPE_SUBTITLES: + { + ALOGE("MPEG2 Transport streams do not contain subtitles."); + return ERROR_MALFORMED; + break; + } + + default: + TRESPASS(); + } + + sp<AnotherPacketSource> source = + static_cast<AnotherPacketSource *>( + mTSParser->getSource(type).get()); + + if (source == NULL) { + ALOGW("MPEG2 Transport stream does not contain %s data.", + type == ATSParser::VIDEO ? "video" : "audio"); + + mStreamTypeMask &= ~mPacketSources.keyAt(i); + mPacketSources.removeItemsAt(i); + continue; + } + + sp<ABuffer> accessUnit; + status_t finalResult; + while (source->hasBufferAvailable(&finalResult) + && source->dequeueAccessUnit(&accessUnit) == OK) { + // Note that we do NOT dequeue any discontinuities. + + packetSource->queueAccessUnit(accessUnit); + } + + if (packetSource->getFormat() == NULL) { + packetSource->setFormat(source->getFormat()); + } + } + + return OK; + } else if (buffer->size() >= 7 && !memcmp("WEBVTT\n", buffer->data(), 7)) { + if (mStreamTypeMask != LiveSession::STREAMTYPE_SUBTITLES) { + ALOGE("This stream only contains subtitles."); + return ERROR_MALFORMED; + } + + const sp<AnotherPacketSource> packetSource = + mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES); + + buffer->meta()->setInt64("timeUs", 0ll); + + packetSource->queueAccessUnit(buffer); + return OK; + } + + if (mNextPTSTimeUs >= 0ll) { + mFirstPTSValid = false; + mAbsoluteTimeAnchorUs = mNextPTSTimeUs; + mNextPTSTimeUs = -1ll; + } + + // This better be an ISO 13818-7 (AAC) or ISO 13818-1 (MPEG) audio + // stream prefixed by an ID3 tag. + + bool firstID3Tag = true; + uint64_t PTS = 0; + + for (;;) { + // Make sure to skip all ID3 tags preceding the audio data. + // At least one must be present to provide the PTS timestamp. + + ID3 id3(buffer->data(), buffer->size(), true /* ignoreV1 */); + if (!id3.isValid()) { + if (firstID3Tag) { + ALOGE("Unable to parse ID3 tag."); + return ERROR_MALFORMED; + } else { + break; + } + } + + if (firstID3Tag) { + bool found = false; + + ID3::Iterator it(id3, "PRIV"); + while (!it.done()) { + size_t length; + const uint8_t *data = it.getData(&length); + + static const char *kMatchName = + "com.apple.streaming.transportStreamTimestamp"; + static const size_t kMatchNameLen = strlen(kMatchName); + + if (length == kMatchNameLen + 1 + 8 + && !strncmp((const char *)data, kMatchName, kMatchNameLen)) { + found = true; + PTS = U64_AT(&data[kMatchNameLen + 1]); + } + + it.next(); + } + + if (!found) { + ALOGE("Unable to extract transportStreamTimestamp from ID3 tag."); + return ERROR_MALFORMED; + } + } + + // skip the ID3 tag + buffer->setRange( + buffer->offset() + id3.rawSize(), buffer->size() - id3.rawSize()); + + firstID3Tag = false; + } + + if (!mFirstPTSValid) { + mFirstPTSValid = true; + mFirstPTS = PTS; + } + PTS -= mFirstPTS; + + int64_t timeUs = (PTS * 100ll) / 9ll + mAbsoluteTimeAnchorUs; + + if (mStreamTypeMask != LiveSession::STREAMTYPE_AUDIO) { + ALOGW("This stream only contains audio data!"); + + mStreamTypeMask &= LiveSession::STREAMTYPE_AUDIO; + + if (mStreamTypeMask == 0) { + return OK; + } + } + + sp<AnotherPacketSource> packetSource = + mPacketSources.valueFor(LiveSession::STREAMTYPE_AUDIO); + + if (packetSource->getFormat() == NULL && buffer->size() >= 7) { + ABitReader bits(buffer->data(), buffer->size()); + + // adts_fixed_header + + CHECK_EQ(bits.getBits(12), 0xfffu); + bits.skipBits(3); // ID, layer + bool protection_absent = bits.getBits(1) != 0; + + unsigned profile = bits.getBits(2); + CHECK_NE(profile, 3u); + unsigned sampling_freq_index = bits.getBits(4); + bits.getBits(1); // private_bit + unsigned channel_configuration = bits.getBits(3); + CHECK_NE(channel_configuration, 0u); + bits.skipBits(2); // original_copy, home + + sp<MetaData> meta = MakeAACCodecSpecificData( + profile, sampling_freq_index, channel_configuration); + + meta->setInt32(kKeyIsADTS, true); + + packetSource->setFormat(meta); + } + + int64_t numSamples = 0ll; + int32_t sampleRate; + CHECK(packetSource->getFormat()->findInt32(kKeySampleRate, &sampleRate)); + + size_t offset = 0; + while (offset < buffer->size()) { + const uint8_t *adtsHeader = buffer->data() + offset; + CHECK_LT(offset + 5, buffer->size()); + + unsigned aac_frame_length = + ((adtsHeader[3] & 3) << 11) + | (adtsHeader[4] << 3) + | (adtsHeader[5] >> 5); + + CHECK_LE(offset + aac_frame_length, buffer->size()); + + sp<ABuffer> unit = new ABuffer(aac_frame_length); + memcpy(unit->data(), adtsHeader, aac_frame_length); + + int64_t unitTimeUs = timeUs + numSamples * 1000000ll / sampleRate; + unit->meta()->setInt64("timeUs", unitTimeUs); + + // Each AAC frame encodes 1024 samples. + numSamples += 1024; + + packetSource->queueAccessUnit(unit); + + offset += aac_frame_length; + } + + return OK; +} + +void PlaylistFetcher::updateDuration() { + int64_t durationUs = 0ll; + for (size_t index = 0; index < mPlaylist->size(); ++index) { + sp<AMessage> itemMeta; + CHECK(mPlaylist->itemAt( + index, NULL /* uri */, &itemMeta)); + + int64_t itemDurationUs; + CHECK(itemMeta->findInt64("durationUs", &itemDurationUs)); + + durationUs += itemDurationUs; + } + + sp<AMessage> msg = mNotify->dup(); + msg->setInt32("what", kWhatDurationUpdate); + msg->setInt64("durationUs", durationUs); + msg->post(); +} + +} // namespace android diff --git a/media/libstagefright/httplive/PlaylistFetcher.h b/media/libstagefright/httplive/PlaylistFetcher.h new file mode 100644 index 0000000..5a2b901 --- /dev/null +++ b/media/libstagefright/httplive/PlaylistFetcher.h @@ -0,0 +1,155 @@ +/* + * 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 PLAYLIST_FETCHER_H_ + +#define PLAYLIST_FETCHER_H_ + +#include <media/stagefright/foundation/AHandler.h> + +#include "mpeg2ts/ATSParser.h" +#include "LiveSession.h" + +namespace android { + +struct ABuffer; +struct AnotherPacketSource; +struct DataSource; +struct HTTPBase; +struct LiveDataSource; +struct M3UParser; +struct String8; + +struct PlaylistFetcher : public AHandler { + enum { + kWhatStarted, + kWhatPaused, + kWhatStopped, + kWhatError, + kWhatDurationUpdate, + kWhatTemporarilyDoneFetching, + kWhatPrepared, + kWhatPreparationFailed, + }; + + PlaylistFetcher( + const sp<AMessage> ¬ify, + const sp<LiveSession> &session, + const char *uri); + + sp<DataSource> getDataSource(); + + void startAsync( + const sp<AnotherPacketSource> &audioSource, + const sp<AnotherPacketSource> &videoSource, + const sp<AnotherPacketSource> &subtitleSource, + int64_t startTimeUs = -1ll); + + void pauseAsync(); + + void stopAsync(); + +protected: + virtual ~PlaylistFetcher(); + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kMaxNumRetries = 5, + }; + + enum { + kWhatStart = 'strt', + kWhatPause = 'paus', + kWhatStop = 'stop', + kWhatMonitorQueue = 'moni', + }; + + static const int64_t kMinBufferedDurationUs; + + sp<AMessage> mNotify; + sp<LiveSession> mSession; + AString mURI; + + uint32_t mStreamTypeMask; + int64_t mStartTimeUs; + + KeyedVector<LiveSession::StreamType, sp<AnotherPacketSource> > + mPacketSources; + + KeyedVector<AString, sp<ABuffer> > mAESKeyForURI; + + int64_t mLastPlaylistFetchTimeUs; + sp<M3UParser> mPlaylist; + int32_t mSeqNumber; + int32_t mNumRetries; + bool mStartup; + int64_t mNextPTSTimeUs; + + int32_t mMonitorQueueGeneration; + + enum RefreshState { + INITIAL_MINIMUM_RELOAD_DELAY, + FIRST_UNCHANGED_RELOAD_ATTEMPT, + SECOND_UNCHANGED_RELOAD_ATTEMPT, + THIRD_UNCHANGED_RELOAD_ATTEMPT + }; + RefreshState mRefreshState; + + uint8_t mPlaylistHash[16]; + + sp<ATSParser> mTSParser; + + bool mFirstPTSValid; + uint64_t mFirstPTS; + int64_t mAbsoluteTimeAnchorUs; + + status_t decryptBuffer( + size_t playlistIndex, const sp<ABuffer> &buffer); + + void postMonitorQueue(int64_t delayUs = 0); + void cancelMonitorQueue(); + + bool timeToRefreshPlaylist(int64_t nowUs) const; + + // Returns the media time in us of the segment specified by seqNumber. + // This is computed by summing the durations of all segments before it. + int64_t getSegmentStartTimeUs(int32_t seqNumber) const; + + status_t onStart(const sp<AMessage> &msg); + void onPause(); + void onStop(); + void onMonitorQueue(); + void onDownloadNext(); + + status_t extractAndQueueAccessUnits(const sp<ABuffer> &buffer); + + void notifyError(status_t err); + + void queueDiscontinuity( + ATSParser::DiscontinuityType type, const sp<AMessage> &extra); + + int32_t getSeqNumberForTime(int64_t timeUs) const; + + void updateDuration(); + + DISALLOW_EVIL_CONSTRUCTORS(PlaylistFetcher); +}; + +} // namespace android + +#endif // PLAYLIST_FETCHER_H_ + diff --git a/media/libstagefright/id3/Android.mk b/media/libstagefright/id3/Android.mk index 80a1a3a..bf6f7bb 100644 --- a/media/libstagefright/id3/Android.mk +++ b/media/libstagefright/id3/Android.mk @@ -21,7 +21,7 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_STATIC_LIBRARIES := \ libstagefright_id3 -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE := testid3 diff --git a/media/libstagefright/id3/ID3.cpp b/media/libstagefright/id3/ID3.cpp index 22c2f5a..8d3013b 100644 --- a/media/libstagefright/id3/ID3.cpp +++ b/media/libstagefright/id3/ID3.cpp @@ -30,12 +30,55 @@ namespace android { static const size_t kMaxMetadataSize = 3 * 1024 * 1024; +struct MemorySource : public DataSource { + MemorySource(const uint8_t *data, size_t size) + : mData(data), + mSize(size) { + } + + virtual status_t initCheck() const { + return OK; + } + + virtual ssize_t readAt(off64_t offset, void *data, size_t size) { + off64_t available = (offset >= mSize) ? 0ll : mSize - offset; + + size_t copy = (available > size) ? size : available; + memcpy(data, mData + offset, copy); + + return copy; + } + +private: + const uint8_t *mData; + size_t mSize; + + DISALLOW_EVIL_CONSTRUCTORS(MemorySource); +}; + ID3::ID3(const sp<DataSource> &source, bool ignoreV1) : mIsValid(false), mData(NULL), mSize(0), mFirstFrameOffset(0), - mVersion(ID3_UNKNOWN) { + mVersion(ID3_UNKNOWN), + mRawSize(0) { + mIsValid = parseV2(source); + + if (!mIsValid && !ignoreV1) { + mIsValid = parseV1(source); + } +} + +ID3::ID3(const uint8_t *data, size_t size, bool ignoreV1) + : mIsValid(false), + mData(NULL), + mSize(0), + mFirstFrameOffset(0), + mVersion(ID3_UNKNOWN), + mRawSize(0) { + sp<MemorySource> source = new MemorySource(data, size); + mIsValid = parseV2(source); if (!mIsValid && !ignoreV1) { @@ -140,6 +183,7 @@ struct id3_header { } mSize = size; + mRawSize = mSize + sizeof(header); if (source->readAt(sizeof(header), mData, mSize) != (ssize_t)mSize) { free(mData); @@ -505,7 +549,7 @@ void ID3::Iterator::getstring(String8 *id, bool otherdata) const { int32_t i = n - 4; while(--i >= 0 && *++frameData != 0) ; int skipped = (frameData - mFrameData); - if (skipped >= n) { + if (skipped >= (int)n) { return; } n -= skipped; diff --git a/media/libstagefright/include/ID3.h b/media/libstagefright/include/ID3.h index 3028f56..cca83ab 100644 --- a/media/libstagefright/include/ID3.h +++ b/media/libstagefright/include/ID3.h @@ -36,6 +36,7 @@ struct ID3 { }; ID3(const sp<DataSource> &source, bool ignoreV1 = false); + ID3(const uint8_t *data, size_t size, bool ignoreV1 = false); ~ID3(); bool isValid() const; @@ -71,6 +72,8 @@ struct ID3 { Iterator &operator=(const Iterator &); }; + size_t rawSize() const { return mRawSize; } + private: bool mIsValid; uint8_t *mData; @@ -78,6 +81,10 @@ private: size_t mFirstFrameOffset; Version mVersion; + // size of the ID3 tag including header before any unsynchronization. + // only valid for IDV2+ + size_t mRawSize; + bool parseV1(const sp<DataSource> &source); bool parseV2(const sp<DataSource> &source); void removeUnsynchronization(); diff --git a/media/libstagefright/include/MPEG2TSExtractor.h b/media/libstagefright/include/MPEG2TSExtractor.h index fe74a42..c5e86a6 100644 --- a/media/libstagefright/include/MPEG2TSExtractor.h +++ b/media/libstagefright/include/MPEG2TSExtractor.h @@ -31,7 +31,6 @@ struct ATSParser; struct DataSource; struct MPEG2TSSource; struct String8; -struct LiveSession; struct MPEG2TSExtractor : public MediaExtractor { MPEG2TSExtractor(const sp<DataSource> &source); @@ -44,16 +43,12 @@ struct MPEG2TSExtractor : public MediaExtractor { virtual uint32_t flags() const; - void setLiveSession(const sp<LiveSession> &liveSession); - void seekTo(int64_t seekTimeUs); - private: friend struct MPEG2TSSource; mutable Mutex mLock; sp<DataSource> mDataSource; - sp<LiveSession> mLiveSession; sp<ATSParser> mParser; diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h index 35eff96..bbec1c4 100644 --- a/media/libstagefright/include/MPEG4Extractor.h +++ b/media/libstagefright/include/MPEG4Extractor.h @@ -82,6 +82,7 @@ private: sp<DataSource> mDataSource; status_t mInitCheck; bool mHasVideo; + uint32_t mHeaderTimescale; Track *mFirstTrack, *mLastTrack; diff --git a/media/libstagefright/include/SoftVideoDecoderOMXComponent.h b/media/libstagefright/include/SoftVideoDecoderOMXComponent.h new file mode 100644 index 0000000..d050fa6 --- /dev/null +++ b/media/libstagefright/include/SoftVideoDecoderOMXComponent.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 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 SOFT_VIDEO_DECODER_OMX_COMPONENT_H_ + +#define SOFT_VIDEO_DECODER_OMX_COMPONENT_H_ + +#include "SimpleSoftOMXComponent.h" + +#include <media/stagefright/foundation/AHandlerReflector.h> +#include <media/IOMX.h> + +#include <utils/RefBase.h> +#include <utils/threads.h> +#include <utils/Vector.h> + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) + +namespace android { + +struct SoftVideoDecoderOMXComponent : public SimpleSoftOMXComponent { + SoftVideoDecoderOMXComponent( + const char *name, + const char *componentRole, + OMX_VIDEO_CODINGTYPE codingType, + const CodecProfileLevel *profileLevels, + size_t numProfileLevels, + int32_t width, + int32_t height, + const OMX_CALLBACKTYPE *callbacks, + OMX_PTR appData, + OMX_COMPONENTTYPE **component); + +protected: + virtual void onPortEnableCompleted(OMX_U32 portIndex, bool enabled); + virtual void onReset(); + + virtual OMX_ERRORTYPE internalGetParameter( + OMX_INDEXTYPE index, OMX_PTR params); + + virtual OMX_ERRORTYPE internalSetParameter( + OMX_INDEXTYPE index, const OMX_PTR params); + + virtual OMX_ERRORTYPE getConfig( + OMX_INDEXTYPE index, OMX_PTR params); + + void initPorts(OMX_U32 numInputBuffers, + OMX_U32 inputBufferSize, + OMX_U32 numOutputBuffers, + const char *mimeType); + + virtual void updatePortDefinitions(); + + enum { + kInputPortIndex = 0, + kOutputPortIndex = 1, + kMaxPortIndex = 1, + }; + + uint32_t mWidth, mHeight; + uint32_t mCropLeft, mCropTop, mCropWidth, mCropHeight; + + enum { + NONE, + AWAITING_DISABLED, + AWAITING_ENABLED + } mOutputPortSettingsChange; + +private: + const char *mComponentRole; + OMX_VIDEO_CODINGTYPE mCodingType; + const CodecProfileLevel *mProfileLevels; + size_t mNumProfileLevels; + + DISALLOW_EVIL_CONSTRUCTORS(SoftVideoDecoderOMXComponent); +}; + +} // namespace android + +#endif // SOFT_VIDEO_DECODER_OMX_COMPONENT_H_ diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp index 3de3a61..3153c8b 100644 --- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp +++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp @@ -32,9 +32,22 @@ const int64_t kNearEOSMarkUs = 2000000ll; // 2 secs AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta) : mIsAudio(false), - mFormat(meta), + mFormat(NULL), mLastQueuedTimeUs(0), mEOSResult(OK) { + setFormat(meta); +} + +void AnotherPacketSource::setFormat(const sp<MetaData> &meta) { + CHECK(mFormat == NULL); + + mIsAudio = false; + + if (meta == NULL) { + return; + } + + mFormat = meta; const char *mime; CHECK(meta->findCString(kKeyMIMEType, &mime)); @@ -45,11 +58,6 @@ AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta) } } -void AnotherPacketSource::setFormat(const sp<MetaData> &meta) { - CHECK(mFormat == NULL); - mFormat = meta; -} - AnotherPacketSource::~AnotherPacketSource() { } @@ -152,6 +160,15 @@ void AnotherPacketSource::queueAccessUnit(const sp<ABuffer> &buffer) { mCondition.signal(); } +void AnotherPacketSource::clear() { + Mutex::Autolock autoLock(mLock); + + mBuffers.clear(); + mEOSResult = OK; + + mFormat = NULL; +} + void AnotherPacketSource::queueDiscontinuity( ATSParser::DiscontinuityType type, const sp<AMessage> &extra) { diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h index 1db4068..e16cf78 100644 --- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h +++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h @@ -41,6 +41,8 @@ struct AnotherPacketSource : public MediaSource { virtual status_t read( MediaBuffer **buffer, const ReadOptions *options = NULL); + void clear(); + bool hasBufferAvailable(status_t *finalResult); // Returns the difference between the last and the first queued diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp index e1589b4..d449c34 100644 --- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp +++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp @@ -19,7 +19,6 @@ #include <utils/Log.h> #include "include/MPEG2TSExtractor.h" -#include "include/LiveSession.h" #include "include/NuCachedSource2.h" #include <media/stagefright/foundation/ADebug.h> @@ -79,15 +78,7 @@ status_t MPEG2TSSource::stop() { } sp<MetaData> MPEG2TSSource::getFormat() { - sp<MetaData> meta = mImpl->getFormat(); - - int64_t durationUs; - if (mExtractor->mLiveSession != NULL - && mExtractor->mLiveSession->getDuration(&durationUs) == OK) { - meta->setInt64(kKeyDuration, durationUs); - } - - return meta; + return mImpl->getFormat(); } status_t MPEG2TSSource::read( @@ -97,7 +88,7 @@ status_t MPEG2TSSource::read( int64_t seekTimeUs; ReadOptions::SeekMode seekMode; if (mSeekable && options && options->getSeekTo(&seekTimeUs, &seekMode)) { - mExtractor->seekTo(seekTimeUs); + return ERROR_UNSUPPORTED; } status_t finalResult; @@ -216,32 +207,8 @@ status_t MPEG2TSExtractor::feedMore() { return mParser->feedTSPacket(packet, kTSPacketSize); } -void MPEG2TSExtractor::setLiveSession(const sp<LiveSession> &liveSession) { - Mutex::Autolock autoLock(mLock); - - mLiveSession = liveSession; -} - -void MPEG2TSExtractor::seekTo(int64_t seekTimeUs) { - Mutex::Autolock autoLock(mLock); - - if (mLiveSession == NULL) { - return; - } - - mLiveSession->seekTo(seekTimeUs); -} - uint32_t MPEG2TSExtractor::flags() const { - Mutex::Autolock autoLock(mLock); - - uint32_t flags = CAN_PAUSE; - - if (mLiveSession != NULL && mLiveSession->isSeekable()) { - flags |= CAN_SEEK_FORWARD | CAN_SEEK_BACKWARD | CAN_SEEK; - } - - return flags; + return CAN_PAUSE; } //////////////////////////////////////////////////////////////////////////////// diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk index a8b4939..cd912e7 100644 --- a/media/libstagefright/omx/Android.mk +++ b/media/libstagefright/omx/Android.mk @@ -9,6 +9,7 @@ LOCAL_SRC_FILES:= \ SimpleSoftOMXComponent.cpp \ SoftOMXComponent.cpp \ SoftOMXPlugin.cpp \ + SoftVideoDecoderOMXComponent.cpp \ LOCAL_C_INCLUDES += \ $(TOP)/frameworks/av/media/libstagefright \ diff --git a/media/libstagefright/omx/GraphicBufferSource.cpp b/media/libstagefright/omx/GraphicBufferSource.cpp index ef27879..b3a8463 100644 --- a/media/libstagefright/omx/GraphicBufferSource.cpp +++ b/media/libstagefright/omx/GraphicBufferSource.cpp @@ -206,24 +206,15 @@ void GraphicBufferSource::codecBufferEmptied(OMX_BUFFERHEADERTYPE* header) { // Find matching entry in our cached copy of the BufferQueue slots. // If we find a match, release that slot. If we don't, the BufferQueue // has dropped that GraphicBuffer, and there's nothing for us to release. - // - // (We could store "id" in CodecBuffer and avoid the slot search.) - int id; - for (id = 0; id < BufferQueue::NUM_BUFFER_SLOTS; id++) { - if (mBufferSlot[id] == NULL) { - continue; - } - - if (mBufferSlot[id]->handle == codecBuffer.mGraphicBuffer->handle) { - ALOGV("cbi %d matches bq slot %d, handle=%p", - cbi, id, mBufferSlot[id]->handle); - - mBufferQueue->releaseBuffer(id, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, - Fence::NO_FENCE); - break; - } - } - if (id == BufferQueue::NUM_BUFFER_SLOTS) { + int id = codecBuffer.mBuf; + if (mBufferSlot[id] != NULL && + mBufferSlot[id]->handle == codecBuffer.mGraphicBuffer->handle) { + ALOGV("cbi %d matches bq slot %d, handle=%p", + cbi, id, mBufferSlot[id]->handle); + + mBufferQueue->releaseBuffer(id, codecBuffer.mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE); + } else { ALOGV("codecBufferEmptied: no match for emptied buffer in cbi %d", cbi); } @@ -287,11 +278,11 @@ bool GraphicBufferSource::fillCodecBuffer_l() { mBufferSlot[item.mBuf] = item.mGraphicBuffer; } - err = submitBuffer_l(mBufferSlot[item.mBuf], item.mTimestamp / 1000, cbi); + err = submitBuffer_l(item, cbi); if (err != OK) { ALOGV("submitBuffer_l failed, releasing bq buf %d", item.mBuf); - mBufferQueue->releaseBuffer(item.mBuf, EGL_NO_DISPLAY, - EGL_NO_SYNC_KHR, Fence::NO_FENCE); + mBufferQueue->releaseBuffer(item.mBuf, item.mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE); } else { ALOGV("buffer submitted (bq %d, cbi %d)", item.mBuf, cbi); } @@ -326,11 +317,13 @@ status_t GraphicBufferSource::signalEndOfInputStream() { return OK; } -status_t GraphicBufferSource::submitBuffer_l(sp<GraphicBuffer>& graphicBuffer, - int64_t timestampUsec, int cbi) { +status_t GraphicBufferSource::submitBuffer_l( + const BufferQueue::BufferItem &item, int cbi) { ALOGV("submitBuffer_l cbi=%d", cbi); CodecBuffer& codecBuffer(mCodecBuffers.editItemAt(cbi)); - codecBuffer.mGraphicBuffer = graphicBuffer; + codecBuffer.mGraphicBuffer = mBufferSlot[item.mBuf]; + codecBuffer.mBuf = item.mBuf; + codecBuffer.mFrameNumber = item.mFrameNumber; OMX_BUFFERHEADERTYPE* header = codecBuffer.mHeader; CHECK(header->nAllocLen >= 4 + sizeof(buffer_handle_t)); @@ -342,7 +335,7 @@ status_t GraphicBufferSource::submitBuffer_l(sp<GraphicBuffer>& graphicBuffer, status_t err = mNodeInstance->emptyDirectBuffer(header, 0, 4 + sizeof(buffer_handle_t), OMX_BUFFERFLAG_ENDOFFRAME, - timestampUsec); + item.mTimestamp / 1000); if (err != OK) { ALOGW("WARNING: emptyDirectBuffer failed: 0x%x", err); codecBuffer.mGraphicBuffer = NULL; @@ -431,8 +424,8 @@ void GraphicBufferSource::onFrameAvailable() { BufferQueue::BufferItem item; status_t err = mBufferQueue->acquireBuffer(&item); if (err == OK) { - mBufferQueue->releaseBuffer(item.mBuf, EGL_NO_DISPLAY, - EGL_NO_SYNC_KHR, item.mFence); + mBufferQueue->releaseBuffer(item.mBuf, item.mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, item.mFence); } return; } diff --git a/media/libstagefright/omx/GraphicBufferSource.h b/media/libstagefright/omx/GraphicBufferSource.h index 562d342..8c6b470 100644 --- a/media/libstagefright/omx/GraphicBufferSource.h +++ b/media/libstagefright/omx/GraphicBufferSource.h @@ -104,6 +104,13 @@ private: // (mGraphicBuffer == NULL) or in use by the codec. struct CodecBuffer { OMX_BUFFERHEADERTYPE* mHeader; + + // buffer producer's frame-number for buffer + uint64_t mFrameNumber; + + // buffer producer's buffer slot for buffer + int mBuf; + sp<GraphicBuffer> mGraphicBuffer; }; @@ -130,8 +137,7 @@ private: // Marks the mCodecBuffers entry as in-use, copies the GraphicBuffer // reference into the codec buffer, and submits the data to the codec. - status_t submitBuffer_l(sp<GraphicBuffer>& graphicBuffer, - int64_t timestampUsec, int cbi); + status_t submitBuffer_l(const BufferQueue::BufferItem &item, int cbi); // Submits an empty buffer, with the EOS flag set. Returns without // doing anything if we don't have a codec buffer available. diff --git a/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp b/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp new file mode 100644 index 0000000..08a3d42 --- /dev/null +++ b/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (C) 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 "SoftVideoDecoderOMXComponent" +#include <utils/Log.h> + +#include "include/SoftVideoDecoderOMXComponent.h" + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaDefs.h> + +namespace android { + +template<class T> +static void InitOMXParams(T *params) { + params->nSize = sizeof(T); + params->nVersion.s.nVersionMajor = 1; + params->nVersion.s.nVersionMinor = 0; + params->nVersion.s.nRevision = 0; + params->nVersion.s.nStep = 0; +} + +SoftVideoDecoderOMXComponent::SoftVideoDecoderOMXComponent( + const char *name, + const char *componentRole, + OMX_VIDEO_CODINGTYPE codingType, + const CodecProfileLevel *profileLevels, + size_t numProfileLevels, + int32_t width, + int32_t height, + const OMX_CALLBACKTYPE *callbacks, + OMX_PTR appData, + OMX_COMPONENTTYPE **component) + : SimpleSoftOMXComponent(name, callbacks, appData, component), + mWidth(width), + mHeight(height), + mCropLeft(0), + mCropTop(0), + mCropWidth(width), + mCropHeight(height), + mOutputPortSettingsChange(NONE), + mComponentRole(componentRole), + mCodingType(codingType), + mProfileLevels(profileLevels), + mNumProfileLevels(numProfileLevels) { +} + +void SoftVideoDecoderOMXComponent::initPorts( + OMX_U32 numInputBuffers, + OMX_U32 inputBufferSize, + OMX_U32 numOutputBuffers, + const char *mimeType) { + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + + def.nPortIndex = kInputPortIndex; + def.eDir = OMX_DirInput; + def.nBufferCountMin = numInputBuffers; + def.nBufferCountActual = def.nBufferCountMin; + def.nBufferSize = inputBufferSize; + def.bEnabled = OMX_TRUE; + def.bPopulated = OMX_FALSE; + def.eDomain = OMX_PortDomainVideo; + def.bBuffersContiguous = OMX_FALSE; + def.nBufferAlignment = 1; + + def.format.video.cMIMEType = const_cast<char *>(mimeType); + def.format.video.pNativeRender = NULL; + /* size is initialized in updatePortDefinitions() */ + def.format.video.nBitrate = 0; + def.format.video.xFramerate = 0; + def.format.video.bFlagErrorConcealment = OMX_FALSE; + def.format.video.eCompressionFormat = mCodingType; + def.format.video.eColorFormat = OMX_COLOR_FormatUnused; + def.format.video.pNativeWindow = NULL; + + addPort(def); + + def.nPortIndex = kOutputPortIndex; + def.eDir = OMX_DirOutput; + def.nBufferCountMin = numOutputBuffers; + def.nBufferCountActual = def.nBufferCountMin; + def.bEnabled = OMX_TRUE; + def.bPopulated = OMX_FALSE; + def.eDomain = OMX_PortDomainVideo; + def.bBuffersContiguous = OMX_FALSE; + def.nBufferAlignment = 2; + + def.format.video.cMIMEType = const_cast<char *>("video/raw"); + def.format.video.pNativeRender = NULL; + /* size is initialized in updatePortDefinitions() */ + def.format.video.nBitrate = 0; + def.format.video.xFramerate = 0; + def.format.video.bFlagErrorConcealment = OMX_FALSE; + def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused; + def.format.video.eColorFormat = OMX_COLOR_FormatYUV420Planar; + def.format.video.pNativeWindow = NULL; + + addPort(def); + + updatePortDefinitions(); +} + +void SoftVideoDecoderOMXComponent::updatePortDefinitions() { + OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kInputPortIndex)->mDef; + def->format.video.nFrameWidth = mWidth; + def->format.video.nFrameHeight = mHeight; + def->format.video.nStride = def->format.video.nFrameWidth; + def->format.video.nSliceHeight = def->format.video.nFrameHeight; + + def = &editPortInfo(kOutputPortIndex)->mDef; + def->format.video.nFrameWidth = mWidth; + def->format.video.nFrameHeight = mHeight; + def->format.video.nStride = def->format.video.nFrameWidth; + def->format.video.nSliceHeight = def->format.video.nFrameHeight; + + def->nBufferSize = + (def->format.video.nFrameWidth * + def->format.video.nFrameHeight * 3) / 2; + + mCropLeft = 0; + mCropTop = 0; + mCropWidth = mWidth; + mCropHeight = mHeight; +} + +OMX_ERRORTYPE SoftVideoDecoderOMXComponent::internalGetParameter( + OMX_INDEXTYPE index, OMX_PTR params) { + switch (index) { + case OMX_IndexParamVideoPortFormat: + { + OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = + (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params; + + if (formatParams->nPortIndex > kMaxPortIndex) { + return OMX_ErrorUndefined; + } + + if (formatParams->nIndex != 0) { + return OMX_ErrorNoMore; + } + + if (formatParams->nPortIndex == kInputPortIndex) { + formatParams->eCompressionFormat = mCodingType; + formatParams->eColorFormat = OMX_COLOR_FormatUnused; + formatParams->xFramerate = 0; + } else { + CHECK_EQ(formatParams->nPortIndex, 1u); + + formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused; + formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar; + formatParams->xFramerate = 0; + } + + return OMX_ErrorNone; + } + + case OMX_IndexParamVideoProfileLevelQuerySupported: + { + OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileLevel = + (OMX_VIDEO_PARAM_PROFILELEVELTYPE *) params; + + if (profileLevel->nPortIndex != kInputPortIndex) { + ALOGE("Invalid port index: %ld", profileLevel->nPortIndex); + return OMX_ErrorUnsupportedIndex; + } + + if (index >= mNumProfileLevels) { + return OMX_ErrorNoMore; + } + + profileLevel->eProfile = mProfileLevels[index].mProfile; + profileLevel->eLevel = mProfileLevels[index].mLevel; + return OMX_ErrorNone; + } + + default: + return SimpleSoftOMXComponent::internalGetParameter(index, params); + } +} + +OMX_ERRORTYPE SoftVideoDecoderOMXComponent::internalSetParameter( + OMX_INDEXTYPE index, const OMX_PTR params) { + switch (index) { + case OMX_IndexParamStandardComponentRole: + { + const OMX_PARAM_COMPONENTROLETYPE *roleParams = + (const OMX_PARAM_COMPONENTROLETYPE *)params; + + if (strncmp((const char *)roleParams->cRole, + mComponentRole, + OMX_MAX_STRINGNAME_SIZE - 1)) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + case OMX_IndexParamVideoPortFormat: + { + OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams = + (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params; + + if (formatParams->nPortIndex > kMaxPortIndex) { + return OMX_ErrorUndefined; + } + + if (formatParams->nIndex != 0) { + return OMX_ErrorNoMore; + } + + return OMX_ErrorNone; + } + + default: + return SimpleSoftOMXComponent::internalSetParameter(index, params); + } +} + +OMX_ERRORTYPE SoftVideoDecoderOMXComponent::getConfig( + OMX_INDEXTYPE index, OMX_PTR params) { + switch (index) { + case OMX_IndexConfigCommonOutputCrop: + { + OMX_CONFIG_RECTTYPE *rectParams = (OMX_CONFIG_RECTTYPE *)params; + + if (rectParams->nPortIndex != kOutputPortIndex) { + return OMX_ErrorUndefined; + } + + rectParams->nLeft = mCropLeft; + rectParams->nTop = mCropTop; + rectParams->nWidth = mCropWidth; + rectParams->nHeight = mCropHeight; + + return OMX_ErrorNone; + } + + default: + return OMX_ErrorUnsupportedIndex; + } +} + +void SoftVideoDecoderOMXComponent::onReset() { + mOutputPortSettingsChange = NONE; +} + +void SoftVideoDecoderOMXComponent::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) { + if (portIndex != kOutputPortIndex) { + return; + } + + switch (mOutputPortSettingsChange) { + case NONE: + break; + + case AWAITING_DISABLED: + { + CHECK(!enabled); + mOutputPortSettingsChange = AWAITING_ENABLED; + break; + } + + default: + { + CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED); + CHECK(enabled); + mOutputPortSettingsChange = NONE; + break; + } + } +} + +} // namespace android diff --git a/media/libstagefright/rtsp/Android.mk b/media/libstagefright/rtsp/Android.mk index 9e2724d..e77c69c 100644 --- a/media/libstagefright/rtsp/Android.mk +++ b/media/libstagefright/rtsp/Android.mk @@ -51,7 +51,7 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar -LOCAL_MODULE_TAGS := debug +LOCAL_MODULE_TAGS := optional LOCAL_MODULE:= rtp_test diff --git a/media/libstagefright/wifi-display/Android.mk b/media/libstagefright/wifi-display/Android.mk index 061ae89..404b41e 100644 --- a/media/libstagefright/wifi-display/Android.mk +++ b/media/libstagefright/wifi-display/Android.mk @@ -4,10 +4,17 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ ANetworkSession.cpp \ + MediaReceiver.cpp \ MediaSender.cpp \ Parameters.cpp \ ParsedMessage.cpp \ + rtp/RTPAssembler.cpp \ + rtp/RTPReceiver.cpp \ rtp/RTPSender.cpp \ + sink/DirectRenderer.cpp \ + sink/WifiDisplaySink.cpp \ + SNTPClient.cpp \ + TimeSyncer.cpp \ source/Converter.cpp \ source/MediaPuller.cpp \ source/PlaybackSession.cpp \ @@ -57,6 +64,67 @@ LOCAL_SHARED_LIBRARIES:= \ LOCAL_MODULE:= wfd -LOCAL_MODULE_TAGS := debug +include $(BUILD_EXECUTABLE) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + udptest.cpp \ + +LOCAL_SHARED_LIBRARIES:= \ + libbinder \ + libgui \ + libmedia \ + libstagefright \ + libstagefright_foundation \ + libstagefright_wfd \ + libutils \ + liblog \ + +LOCAL_MODULE:= udptest + +include $(BUILD_EXECUTABLE) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + rtptest.cpp \ + +LOCAL_SHARED_LIBRARIES:= \ + libbinder \ + libgui \ + libmedia \ + libstagefright \ + libstagefright_foundation \ + libstagefright_wfd \ + libutils \ + liblog \ + +LOCAL_MODULE:= rtptest + +include $(BUILD_EXECUTABLE) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + nettest.cpp \ + +LOCAL_SHARED_LIBRARIES:= \ + libbinder \ + libgui \ + libmedia \ + libstagefright \ + libstagefright_foundation \ + libstagefright_wfd \ + libutils \ + liblog \ + +LOCAL_MODULE:= nettest include $(BUILD_EXECUTABLE) diff --git a/media/libstagefright/wifi-display/MediaReceiver.cpp b/media/libstagefright/wifi-display/MediaReceiver.cpp new file mode 100644 index 0000000..364acb9 --- /dev/null +++ b/media/libstagefright/wifi-display/MediaReceiver.cpp @@ -0,0 +1,328 @@ +/* + * 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 "MediaReceiver" +#include <utils/Log.h> + +#include "MediaReceiver.h" + +#include "ANetworkSession.h" +#include "AnotherPacketSource.h" +#include "rtp/RTPReceiver.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +namespace android { + +MediaReceiver::MediaReceiver( + const sp<ANetworkSession> &netSession, + const sp<AMessage> ¬ify) + : mNetSession(netSession), + mNotify(notify), + mMode(MODE_UNDEFINED), + mGeneration(0), + mInitStatus(OK), + mInitDoneCount(0) { +} + +MediaReceiver::~MediaReceiver() { +} + +ssize_t MediaReceiver::addTrack( + RTPReceiver::TransportMode rtpMode, + RTPReceiver::TransportMode rtcpMode, + int32_t *localRTPPort) { + if (mMode != MODE_UNDEFINED) { + return INVALID_OPERATION; + } + + size_t trackIndex = mTrackInfos.size(); + + TrackInfo info; + + sp<AMessage> notify = new AMessage(kWhatReceiverNotify, id()); + notify->setInt32("generation", mGeneration); + notify->setSize("trackIndex", trackIndex); + + info.mReceiver = new RTPReceiver(mNetSession, notify); + looper()->registerHandler(info.mReceiver); + + info.mReceiver->registerPacketType( + 33, RTPReceiver::PACKETIZATION_TRANSPORT_STREAM); + + info.mReceiver->registerPacketType( + 96, RTPReceiver::PACKETIZATION_AAC); + + info.mReceiver->registerPacketType( + 97, RTPReceiver::PACKETIZATION_H264); + + status_t err = info.mReceiver->initAsync( + rtpMode, + rtcpMode, + localRTPPort); + + if (err != OK) { + looper()->unregisterHandler(info.mReceiver->id()); + info.mReceiver.clear(); + + return err; + } + + mTrackInfos.push_back(info); + + return trackIndex; +} + +status_t MediaReceiver::connectTrack( + size_t trackIndex, + const char *remoteHost, + int32_t remoteRTPPort, + int32_t remoteRTCPPort) { + if (trackIndex >= mTrackInfos.size()) { + return -ERANGE; + } + + TrackInfo *info = &mTrackInfos.editItemAt(trackIndex); + return info->mReceiver->connect(remoteHost, remoteRTPPort, remoteRTCPPort); +} + +status_t MediaReceiver::initAsync(Mode mode) { + if ((mode == MODE_TRANSPORT_STREAM || mode == MODE_TRANSPORT_STREAM_RAW) + && mTrackInfos.size() > 1) { + return INVALID_OPERATION; + } + + sp<AMessage> msg = new AMessage(kWhatInit, id()); + msg->setInt32("mode", mode); + msg->post(); + + return OK; +} + +void MediaReceiver::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatInit: + { + int32_t mode; + CHECK(msg->findInt32("mode", &mode)); + + CHECK_EQ(mMode, MODE_UNDEFINED); + mMode = (Mode)mode; + + if (mInitStatus != OK || mInitDoneCount == mTrackInfos.size()) { + notifyInitDone(mInitStatus); + } + + mTSParser = new ATSParser( + ATSParser::ALIGNED_VIDEO_DATA + | ATSParser::TS_TIMESTAMPS_ARE_ABSOLUTE); + + mFormatKnownMask = 0; + break; + } + + case kWhatReceiverNotify: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + if (generation != mGeneration) { + break; + } + + onReceiverNotify(msg); + break; + } + + default: + TRESPASS(); + } +} + +void MediaReceiver::onReceiverNotify(const sp<AMessage> &msg) { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + switch (what) { + case RTPReceiver::kWhatInitDone: + { + ++mInitDoneCount; + + int32_t err; + CHECK(msg->findInt32("err", &err)); + + if (err != OK) { + mInitStatus = err; + ++mGeneration; + } + + if (mMode != MODE_UNDEFINED) { + if (mInitStatus != OK || mInitDoneCount == mTrackInfos.size()) { + notifyInitDone(mInitStatus); + } + } + break; + } + + case RTPReceiver::kWhatError: + { + int32_t err; + CHECK(msg->findInt32("err", &err)); + + notifyError(err); + break; + } + + case RTPReceiver::kWhatAccessUnit: + { + size_t trackIndex; + CHECK(msg->findSize("trackIndex", &trackIndex)); + + sp<ABuffer> accessUnit; + CHECK(msg->findBuffer("accessUnit", &accessUnit)); + + int32_t followsDiscontinuity; + if (!msg->findInt32( + "followsDiscontinuity", &followsDiscontinuity)) { + followsDiscontinuity = 0; + } + + if (mMode == MODE_TRANSPORT_STREAM) { + if (followsDiscontinuity) { + mTSParser->signalDiscontinuity( + ATSParser::DISCONTINUITY_TIME, NULL /* extra */); + } + + for (size_t offset = 0; + offset < accessUnit->size(); offset += 188) { + status_t err = mTSParser->feedTSPacket( + accessUnit->data() + offset, 188); + + if (err != OK) { + notifyError(err); + break; + } + } + + drainPackets(0 /* trackIndex */, ATSParser::VIDEO); + drainPackets(1 /* trackIndex */, ATSParser::AUDIO); + } else { + postAccessUnit(trackIndex, accessUnit, NULL); + } + break; + } + + case RTPReceiver::kWhatPacketLost: + { + notifyPacketLost(); + break; + } + + default: + TRESPASS(); + } +} + +void MediaReceiver::drainPackets( + size_t trackIndex, ATSParser::SourceType type) { + sp<AnotherPacketSource> source = + static_cast<AnotherPacketSource *>( + mTSParser->getSource(type).get()); + + if (source == NULL) { + return; + } + + sp<AMessage> format; + if (!(mFormatKnownMask & (1ul << trackIndex))) { + sp<MetaData> meta = source->getFormat(); + CHECK(meta != NULL); + + CHECK_EQ((status_t)OK, convertMetaDataToMessage(meta, &format)); + + mFormatKnownMask |= 1ul << trackIndex; + } + + status_t finalResult; + while (source->hasBufferAvailable(&finalResult)) { + sp<ABuffer> accessUnit; + status_t err = source->dequeueAccessUnit(&accessUnit); + if (err == OK) { + postAccessUnit(trackIndex, accessUnit, format); + format.clear(); + } else if (err != INFO_DISCONTINUITY) { + notifyError(err); + } + } + + if (finalResult != OK) { + notifyError(finalResult); + } +} + +void MediaReceiver::notifyInitDone(status_t err) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatInitDone); + notify->setInt32("err", err); + notify->post(); +} + +void MediaReceiver::notifyError(status_t err) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatError); + notify->setInt32("err", err); + notify->post(); +} + +void MediaReceiver::notifyPacketLost() { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatPacketLost); + notify->post(); +} + +void MediaReceiver::postAccessUnit( + size_t trackIndex, + const sp<ABuffer> &accessUnit, + const sp<AMessage> &format) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatAccessUnit); + notify->setSize("trackIndex", trackIndex); + notify->setBuffer("accessUnit", accessUnit); + + if (format != NULL) { + notify->setMessage("format", format); + } + + notify->post(); +} + +status_t MediaReceiver::informSender( + size_t trackIndex, const sp<AMessage> ¶ms) { + if (trackIndex >= mTrackInfos.size()) { + return -ERANGE; + } + + TrackInfo *info = &mTrackInfos.editItemAt(trackIndex); + return info->mReceiver->informSender(params); +} + +} // namespace android + + diff --git a/media/libstagefright/wifi-display/MediaReceiver.h b/media/libstagefright/wifi-display/MediaReceiver.h new file mode 100644 index 0000000..afbb407 --- /dev/null +++ b/media/libstagefright/wifi-display/MediaReceiver.h @@ -0,0 +1,111 @@ +/* + * 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. + */ + +#include <media/stagefright/foundation/AHandler.h> + +#include "ATSParser.h" +#include "rtp/RTPReceiver.h" + +namespace android { + +struct ABuffer; +struct ANetworkSession; +struct AMessage; +struct ATSParser; + +// This class facilitates receiving of media data for one or more tracks +// over RTP. Either a 1:1 track to RTP channel mapping is used or a single +// RTP channel provides the data for a transport stream that is consequently +// demuxed and its track's data provided to the observer. +struct MediaReceiver : public AHandler { + enum { + kWhatInitDone, + kWhatError, + kWhatAccessUnit, + kWhatPacketLost, + }; + + MediaReceiver( + const sp<ANetworkSession> &netSession, + const sp<AMessage> ¬ify); + + ssize_t addTrack( + RTPReceiver::TransportMode rtpMode, + RTPReceiver::TransportMode rtcpMode, + int32_t *localRTPPort); + + status_t connectTrack( + size_t trackIndex, + const char *remoteHost, + int32_t remoteRTPPort, + int32_t remoteRTCPPort); + + enum Mode { + MODE_UNDEFINED, + MODE_TRANSPORT_STREAM, + MODE_TRANSPORT_STREAM_RAW, + MODE_ELEMENTARY_STREAMS, + }; + status_t initAsync(Mode mode); + + status_t informSender(size_t trackIndex, const sp<AMessage> ¶ms); + +protected: + virtual void onMessageReceived(const sp<AMessage> &msg); + virtual ~MediaReceiver(); + +private: + enum { + kWhatInit, + kWhatReceiverNotify, + }; + + struct TrackInfo { + sp<RTPReceiver> mReceiver; + }; + + sp<ANetworkSession> mNetSession; + sp<AMessage> mNotify; + + Mode mMode; + int32_t mGeneration; + + Vector<TrackInfo> mTrackInfos; + + status_t mInitStatus; + size_t mInitDoneCount; + + sp<ATSParser> mTSParser; + uint32_t mFormatKnownMask; + + void onReceiverNotify(const sp<AMessage> &msg); + + void drainPackets(size_t trackIndex, ATSParser::SourceType type); + + void notifyInitDone(status_t err); + void notifyError(status_t err); + void notifyPacketLost(); + + void postAccessUnit( + size_t trackIndex, + const sp<ABuffer> &accessUnit, + const sp<AMessage> &format); + + DISALLOW_EVIL_CONSTRUCTORS(MediaReceiver); +}; + +} // namespace android + diff --git a/media/libstagefright/wifi-display/MediaSender.cpp b/media/libstagefright/wifi-display/MediaSender.cpp index 8a3566f..a230cd8 100644 --- a/media/libstagefright/wifi-display/MediaSender.cpp +++ b/media/libstagefright/wifi-display/MediaSender.cpp @@ -27,9 +27,11 @@ #include "include/avc_utils.h" #include <media/IHDCP.h> +#include <media/stagefright/MediaBuffer.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> +#include <ui/GraphicBuffer.h> namespace android { @@ -341,6 +343,22 @@ void MediaSender::onSenderNotify(const sp<AMessage> &msg) { break; } + case kWhatInformSender: + { + int64_t avgLatencyUs; + CHECK(msg->findInt64("avgLatencyUs", &avgLatencyUs)); + + int64_t maxLatencyUs; + CHECK(msg->findInt64("maxLatencyUs", &maxLatencyUs)); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatInformSender); + notify->setInt64("avgLatencyUs", avgLatencyUs); + notify->setInt64("maxLatencyUs", maxLatencyUs); + notify->post(); + break; + } + default: TRESPASS(); } @@ -392,11 +410,36 @@ status_t MediaSender::packetizeAccessUnit( info.mPacketizerTrackIndex, accessUnit); } - status_t err = mHDCP->encrypt( - accessUnit->data(), accessUnit->size(), - trackIndex /* streamCTR */, - &inputCTR, - accessUnit->data()); + status_t err; + native_handle_t* handle; + if (accessUnit->meta()->findPointer("handle", (void**)&handle) + && handle != NULL) { + int32_t rangeLength, rangeOffset; + sp<AMessage> notify; + CHECK(accessUnit->meta()->findInt32("rangeOffset", &rangeOffset)); + CHECK(accessUnit->meta()->findInt32("rangeLength", &rangeLength)); + CHECK(accessUnit->meta()->findMessage("notify", ¬ify) + && notify != NULL); + CHECK_GE(accessUnit->size(), rangeLength); + + sp<GraphicBuffer> grbuf(new GraphicBuffer( + rangeOffset + rangeLength, 1, HAL_PIXEL_FORMAT_Y8, + GRALLOC_USAGE_HW_VIDEO_ENCODER, rangeOffset + rangeLength, + handle, false)); + + 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)", diff --git a/media/libstagefright/wifi-display/MediaSender.h b/media/libstagefright/wifi-display/MediaSender.h index 64722c5..04538ea 100644 --- a/media/libstagefright/wifi-display/MediaSender.h +++ b/media/libstagefright/wifi-display/MediaSender.h @@ -43,6 +43,7 @@ struct MediaSender : public AHandler { kWhatInitDone, kWhatError, kWhatNetworkStall, + kWhatInformSender, }; MediaSender( diff --git a/media/libstagefright/wifi-display/SNTPClient.cpp b/media/libstagefright/wifi-display/SNTPClient.cpp new file mode 100644 index 0000000..5c0af6a --- /dev/null +++ b/media/libstagefright/wifi-display/SNTPClient.cpp @@ -0,0 +1,174 @@ +/* + * 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. + */ + +#include "SNTPClient.h" + +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/Utils.h> + +#include <arpa/inet.h> +#include <netdb.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <unistd.h> + +namespace android { + +SNTPClient::SNTPClient() { +} + +status_t SNTPClient::requestTime(const char *host) { + struct hostent *ent; + int64_t requestTimeNTP, requestTimeUs; + ssize_t n; + int64_t responseTimeUs, responseTimeNTP; + int64_t originateTimeNTP, receiveTimeNTP, transmitTimeNTP; + int64_t roundTripTimeNTP, clockOffsetNTP; + + status_t err = UNKNOWN_ERROR; + + int s = socket(AF_INET, SOCK_DGRAM, 0); + + if (s < 0) { + err = -errno; + + goto bail; + } + + ent = gethostbyname(host); + + if (ent == NULL) { + err = -ENOENT; + goto bail2; + } + + struct sockaddr_in hostAddr; + memset(hostAddr.sin_zero, 0, sizeof(hostAddr.sin_zero)); + hostAddr.sin_family = AF_INET; + hostAddr.sin_port = htons(kNTPPort); + hostAddr.sin_addr.s_addr = *(in_addr_t *)ent->h_addr; + + uint8_t packet[kNTPPacketSize]; + memset(packet, 0, sizeof(packet)); + + packet[0] = kNTPModeClient | (kNTPVersion << 3); + + requestTimeNTP = getNowNTP(); + requestTimeUs = ALooper::GetNowUs(); + writeTimeStamp(&packet[kNTPTransmitTimeOffset], requestTimeNTP); + + n = sendto( + s, packet, sizeof(packet), 0, + (const struct sockaddr *)&hostAddr, sizeof(hostAddr)); + + if (n < 0) { + err = -errno; + goto bail2; + } + + memset(packet, 0, sizeof(packet)); + + do { + n = recv(s, packet, sizeof(packet), 0); + } while (n < 0 && errno == EINTR); + + if (n < 0) { + err = -errno; + goto bail2; + } + + responseTimeUs = ALooper::GetNowUs(); + + responseTimeNTP = requestTimeNTP + makeNTP(responseTimeUs - requestTimeUs); + + originateTimeNTP = readTimeStamp(&packet[kNTPOriginateTimeOffset]); + receiveTimeNTP = readTimeStamp(&packet[kNTPReceiveTimeOffset]); + transmitTimeNTP = readTimeStamp(&packet[kNTPTransmitTimeOffset]); + + roundTripTimeNTP = + makeNTP(responseTimeUs - requestTimeUs) + - (transmitTimeNTP - receiveTimeNTP); + + clockOffsetNTP = + ((receiveTimeNTP - originateTimeNTP) + + (transmitTimeNTP - responseTimeNTP)) / 2; + + mTimeReferenceNTP = responseTimeNTP + clockOffsetNTP; + mTimeReferenceUs = responseTimeUs; + mRoundTripTimeNTP = roundTripTimeNTP; + + err = OK; + +bail2: + close(s); + s = -1; + +bail: + return err; +} + +int64_t SNTPClient::adjustTimeUs(int64_t timeUs) const { + uint64_t nowNTP = + mTimeReferenceNTP + makeNTP(timeUs - mTimeReferenceUs); + + int64_t nowUs = + (nowNTP >> 32) * 1000000ll + + ((nowNTP & 0xffffffff) * 1000000ll) / (1ll << 32); + + nowUs -= ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll; + + return nowUs; +} + +// static +void SNTPClient::writeTimeStamp(uint8_t *dst, uint64_t ntpTime) { + *dst++ = (ntpTime >> 56) & 0xff; + *dst++ = (ntpTime >> 48) & 0xff; + *dst++ = (ntpTime >> 40) & 0xff; + *dst++ = (ntpTime >> 32) & 0xff; + *dst++ = (ntpTime >> 24) & 0xff; + *dst++ = (ntpTime >> 16) & 0xff; + *dst++ = (ntpTime >> 8) & 0xff; + *dst++ = ntpTime & 0xff; +} + +// static +uint64_t SNTPClient::readTimeStamp(const uint8_t *dst) { + return U64_AT(dst); +} + +// static +uint64_t SNTPClient::getNowNTP() { + struct timeval tv; + gettimeofday(&tv, NULL /* time zone */); + + uint64_t nowUs = tv.tv_sec * 1000000ll + tv.tv_usec; + + nowUs += ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll; + + return makeNTP(nowUs); +} + +// static +uint64_t SNTPClient::makeNTP(uint64_t deltaUs) { + uint64_t hi = deltaUs / 1000000ll; + uint64_t lo = ((1ll << 32) * (deltaUs % 1000000ll)) / 1000000ll; + + return (hi << 32) | lo; +} + +} // namespace android + diff --git a/media/libstagefright/wifi-display/SNTPClient.h b/media/libstagefright/wifi-display/SNTPClient.h new file mode 100644 index 0000000..967d1fc --- /dev/null +++ b/media/libstagefright/wifi-display/SNTPClient.h @@ -0,0 +1,62 @@ +/* + * 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 SNTP_CLIENT_H_ + +#define SNTP_CLIENT_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> + +namespace android { + +// Implementation of the SNTP (Simple Network Time Protocol) +struct SNTPClient { + SNTPClient(); + + status_t requestTime(const char *host); + + // given a time obtained from ALooper::GetNowUs() + // return the number of us elapsed since Jan 1 1970 00:00:00 (UTC). + int64_t adjustTimeUs(int64_t timeUs) const; + +private: + enum { + kNTPPort = 123, + kNTPPacketSize = 48, + kNTPModeClient = 3, + kNTPVersion = 3, + kNTPTransmitTimeOffset = 40, + kNTPOriginateTimeOffset = 24, + kNTPReceiveTimeOffset = 32, + }; + + uint64_t mTimeReferenceNTP; + int64_t mTimeReferenceUs; + int64_t mRoundTripTimeNTP; + + static void writeTimeStamp(uint8_t *dst, uint64_t ntpTime); + static uint64_t readTimeStamp(const uint8_t *dst); + + static uint64_t getNowNTP(); + static uint64_t makeNTP(uint64_t deltaUs); + + DISALLOW_EVIL_CONSTRUCTORS(SNTPClient); +}; + +} // namespace android + +#endif // SNTP_CLIENT_H_ diff --git a/media/libstagefright/wifi-display/TimeSyncer.cpp b/media/libstagefright/wifi-display/TimeSyncer.cpp new file mode 100644 index 0000000..cb429bc --- /dev/null +++ b/media/libstagefright/wifi-display/TimeSyncer.cpp @@ -0,0 +1,338 @@ +/* + * 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_NEBUG 0 +#define LOG_TAG "TimeSyncer" +#include <utils/Log.h> + +#include "TimeSyncer.h" + +#include "ANetworkSession.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AHandler.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/Utils.h> + +namespace android { + +TimeSyncer::TimeSyncer( + const sp<ANetworkSession> &netSession, const sp<AMessage> ¬ify) + : mNetSession(netSession), + mNotify(notify), + mIsServer(false), + mConnected(false), + mUDPSession(0), + mSeqNo(0), + mTotalTimeUs(0.0), + mPendingT1(0ll), + mTimeoutGeneration(0) { +} + +TimeSyncer::~TimeSyncer() { +} + +void TimeSyncer::startServer(unsigned localPort) { + sp<AMessage> msg = new AMessage(kWhatStartServer, id()); + msg->setInt32("localPort", localPort); + msg->post(); +} + +void TimeSyncer::startClient(const char *remoteHost, unsigned remotePort) { + sp<AMessage> msg = new AMessage(kWhatStartClient, id()); + msg->setString("remoteHost", remoteHost); + msg->setInt32("remotePort", remotePort); + msg->post(); +} + +void TimeSyncer::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatStartClient: + { + AString remoteHost; + CHECK(msg->findString("remoteHost", &remoteHost)); + + int32_t remotePort; + CHECK(msg->findInt32("remotePort", &remotePort)); + + sp<AMessage> notify = new AMessage(kWhatUDPNotify, id()); + + CHECK_EQ((status_t)OK, + mNetSession->createUDPSession( + 0 /* localPort */, + remoteHost.c_str(), + remotePort, + notify, + &mUDPSession)); + + postSendPacket(); + break; + } + + case kWhatStartServer: + { + mIsServer = true; + + int32_t localPort; + CHECK(msg->findInt32("localPort", &localPort)); + + sp<AMessage> notify = new AMessage(kWhatUDPNotify, id()); + + CHECK_EQ((status_t)OK, + mNetSession->createUDPSession( + localPort, notify, &mUDPSession)); + + break; + } + + case kWhatSendPacket: + { + if (mHistory.size() == 0) { + ALOGI("starting batch"); + } + + TimeInfo ti; + memset(&ti, 0, sizeof(ti)); + + ti.mT1 = ALooper::GetNowUs(); + + CHECK_EQ((status_t)OK, + mNetSession->sendRequest( + mUDPSession, &ti, sizeof(ti))); + + mPendingT1 = ti.mT1; + postTimeout(); + break; + } + + case kWhatTimedOut: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + + if (generation != mTimeoutGeneration) { + break; + } + + ALOGI("timed out, sending another request"); + postSendPacket(); + break; + } + + case kWhatUDPNotify: + { + 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); + + cancelTimeout(); + + notifyError(err); + break; + } + + case ANetworkSession::kWhatDatagram: + { + int32_t sessionID; + CHECK(msg->findInt32("sessionID", &sessionID)); + + sp<ABuffer> packet; + CHECK(msg->findBuffer("data", &packet)); + + int64_t arrivalTimeUs; + CHECK(packet->meta()->findInt64( + "arrivalTimeUs", &arrivalTimeUs)); + + CHECK_EQ(packet->size(), sizeof(TimeInfo)); + + TimeInfo *ti = (TimeInfo *)packet->data(); + + if (mIsServer) { + if (!mConnected) { + AString fromAddr; + CHECK(msg->findString("fromAddr", &fromAddr)); + + int32_t fromPort; + CHECK(msg->findInt32("fromPort", &fromPort)); + + CHECK_EQ((status_t)OK, + mNetSession->connectUDPSession( + mUDPSession, fromAddr.c_str(), fromPort)); + + mConnected = true; + } + + ti->mT2 = arrivalTimeUs; + ti->mT3 = ALooper::GetNowUs(); + + CHECK_EQ((status_t)OK, + mNetSession->sendRequest( + mUDPSession, ti, sizeof(*ti))); + } else { + if (ti->mT1 != mPendingT1) { + break; + } + + cancelTimeout(); + mPendingT1 = 0; + + ti->mT4 = arrivalTimeUs; + + // One way delay for a packet to travel from client + // to server or back (assumed to be the same either way). + int64_t delay = + (ti->mT2 - ti->mT1 + ti->mT4 - ti->mT3) / 2; + + // Offset between the client clock (T1, T4) and the + // server clock (T2, T3) timestamps. + int64_t offset = + (ti->mT2 - ti->mT1 - ti->mT4 + ti->mT3) / 2; + + mHistory.push_back(*ti); + + ALOGV("delay = %lld us,\toffset %lld us", + delay, + offset); + + if (mHistory.size() < kNumPacketsPerBatch) { + postSendPacket(1000000ll / 30); + } else { + notifyOffset(); + + ALOGI("batch done"); + + mHistory.clear(); + postSendPacket(kBatchDelayUs); + } + } + break; + } + + default: + TRESPASS(); + } + + break; + } + + default: + TRESPASS(); + } +} + +void TimeSyncer::postSendPacket(int64_t delayUs) { + (new AMessage(kWhatSendPacket, id()))->post(delayUs); +} + +void TimeSyncer::postTimeout() { + sp<AMessage> msg = new AMessage(kWhatTimedOut, id()); + msg->setInt32("generation", mTimeoutGeneration); + msg->post(kTimeoutDelayUs); +} + +void TimeSyncer::cancelTimeout() { + ++mTimeoutGeneration; +} + +void TimeSyncer::notifyError(status_t err) { + if (mNotify == NULL) { + looper()->stop(); + return; + } + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatError); + notify->setInt32("err", err); + notify->post(); +} + +// static +int TimeSyncer::CompareRountripTime(const TimeInfo *ti1, const TimeInfo *ti2) { + int64_t rt1 = ti1->mT4 - ti1->mT1; + int64_t rt2 = ti2->mT4 - ti2->mT1; + + if (rt1 < rt2) { + return -1; + } else if (rt1 > rt2) { + return 1; + } + + return 0; +} + +void TimeSyncer::notifyOffset() { + mHistory.sort(CompareRountripTime); + + int64_t sum = 0ll; + size_t count = 0; + + // Only consider the third of the information associated with the best + // (smallest) roundtrip times. + for (size_t i = 0; i < mHistory.size() / 3; ++i) { + const TimeInfo *ti = &mHistory[i]; + +#if 0 + // One way delay for a packet to travel from client + // to server or back (assumed to be the same either way). + int64_t delay = + (ti->mT2 - ti->mT1 + ti->mT4 - ti->mT3) / 2; +#endif + + // Offset between the client clock (T1, T4) and the + // server clock (T2, T3) timestamps. + int64_t offset = + (ti->mT2 - ti->mT1 - ti->mT4 + ti->mT3) / 2; + + ALOGV("(%d) RT: %lld us, offset: %lld us", + i, ti->mT4 - ti->mT1, offset); + + sum += offset; + ++count; + } + + if (mNotify == NULL) { + ALOGI("avg. offset is %lld", sum / count); + return; + } + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatTimeOffset); + notify->setInt64("offset", sum / count); + notify->post(); +} + +} // namespace android diff --git a/media/libstagefright/wifi-display/TimeSyncer.h b/media/libstagefright/wifi-display/TimeSyncer.h new file mode 100644 index 0000000..4e7571f --- /dev/null +++ b/media/libstagefright/wifi-display/TimeSyncer.h @@ -0,0 +1,109 @@ +/* + * 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 TIME_SYNCER_H_ + +#define TIME_SYNCER_H_ + +#include <media/stagefright/foundation/AHandler.h> + +namespace android { + +struct ANetworkSession; + +/* + TimeSyncer allows us to synchronize time between a client and a server. + The client sends a UDP packet containing its send-time to the server, + the server sends that packet back to the client amended with information + about when it was received as well as the time the reply was sent back. + Finally the client receives the reply and has now enough information to + compute the clock offset between client and server assuming that packet + exchange is symmetric, i.e. time for a packet client->server and + server->client is roughly equal. + This exchange is repeated a number of times and the average offset computed + over the 30% of packets that had the lowest roundtrip times. + The offset is determined every 10 secs to account for slight differences in + clock frequency. +*/ +struct TimeSyncer : public AHandler { + enum { + kWhatError, + kWhatTimeOffset, + }; + TimeSyncer( + const sp<ANetworkSession> &netSession, + const sp<AMessage> ¬ify); + + void startServer(unsigned localPort); + void startClient(const char *remoteHost, unsigned remotePort); + +protected: + virtual ~TimeSyncer(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatStartServer, + kWhatStartClient, + kWhatUDPNotify, + kWhatSendPacket, + kWhatTimedOut, + }; + + struct TimeInfo { + int64_t mT1; // client timestamp at send + int64_t mT2; // server timestamp at receive + int64_t mT3; // server timestamp at send + int64_t mT4; // client timestamp at receive + }; + + enum { + kNumPacketsPerBatch = 30, + }; + static const int64_t kTimeoutDelayUs = 500000ll; + static const int64_t kBatchDelayUs = 60000000ll; // every minute + + sp<ANetworkSession> mNetSession; + sp<AMessage> mNotify; + + bool mIsServer; + bool mConnected; + int32_t mUDPSession; + uint32_t mSeqNo; + double mTotalTimeUs; + + Vector<TimeInfo> mHistory; + + int64_t mPendingT1; + int32_t mTimeoutGeneration; + + void postSendPacket(int64_t delayUs = 0ll); + + void postTimeout(); + void cancelTimeout(); + + void notifyError(status_t err); + void notifyOffset(); + + static int CompareRountripTime(const TimeInfo *ti1, const TimeInfo *ti2); + + DISALLOW_EVIL_CONSTRUCTORS(TimeSyncer); +}; + +} // namespace android + +#endif // TIME_SYNCER_H_ diff --git a/media/libstagefright/wifi-display/VideoFormats.cpp b/media/libstagefright/wifi-display/VideoFormats.cpp index 458b163..04e02c1 100644 --- a/media/libstagefright/wifi-display/VideoFormats.cpp +++ b/media/libstagefright/wifi-display/VideoFormats.cpp @@ -24,7 +24,8 @@ namespace android { -VideoFormats::config_t VideoFormats::mConfigs[][32] = { +// static +const VideoFormats::config_t VideoFormats::mResolutionTable[][32] = { { // CEA Resolutions { 640, 480, 60, false, 0, 0}, @@ -133,6 +134,8 @@ VideoFormats::config_t VideoFormats::mConfigs[][32] = { }; VideoFormats::VideoFormats() { + memcpy(mConfigs, mResolutionTable, sizeof(mConfigs)); + for (size_t i = 0; i < kNumResolutionTypes; ++i) { mResolutionEnabled[i] = 0; } @@ -175,6 +178,29 @@ void VideoFormats::enableAll() { } } +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); @@ -182,11 +208,56 @@ void VideoFormats::setResolutionEnabled( 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 %d", + 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); @@ -207,7 +278,7 @@ bool VideoFormats::GetConfiguration( return false; } - const config_t *config = &mConfigs[type][index]; + const config_t *config = &mResolutionTable[type][index]; if (config->width == 0) { return false; @@ -251,9 +322,12 @@ bool VideoFormats::parseH264Codec(const char *spec) { 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; - if (level > mConfigs[i][j].level) - mConfigs[i][j].level = level; + mConfigs[i][j].level = level; + } else if (profile == mConfigs[i][j].profile && + level > mConfigs[i][j].level) { + mConfigs[i][j].level = level; } } } @@ -262,9 +336,51 @@ bool VideoFormats::parseH264Codec(const char *spec) { 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; unsigned res[3]; size_t size = strlen(spec); @@ -320,8 +436,10 @@ AString VideoFormats::getFormatSpec(bool forM4Message) const { // max-vres (none or 2 byte) return StringPrintf( - "%02x 00 02 02 %08x %08x %08x 00 0000 0000 00 none none", + "%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]); @@ -332,7 +450,9 @@ bool VideoFormats::PickBestFormat( const VideoFormats &sinkSupported, const VideoFormats &sourceSupported, ResolutionType *chosenType, - size_t *chosenIndex) { + 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. @@ -412,6 +532,18 @@ bool VideoFormats::PickBestFormat( *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; } diff --git a/media/libstagefright/wifi-display/VideoFormats.h b/media/libstagefright/wifi-display/VideoFormats.h index 01de246..fd38fd1 100644 --- a/media/libstagefright/wifi-display/VideoFormats.h +++ b/media/libstagefright/wifi-display/VideoFormats.h @@ -69,17 +69,33 @@ struct VideoFormats { 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; @@ -87,7 +103,9 @@ struct VideoFormats { const VideoFormats &sinkSupported, const VideoFormats &sourceSupported, ResolutionType *chosenType, - size_t *chosenIndex); + size_t *chosenIndex, + ProfileType *chosenProfile, + LevelType *chosenLevel); private: bool parseH264Codec(const char *spec); @@ -95,7 +113,8 @@ private: size_t mNativeIndex; uint32_t mResolutionEnabled[kNumResolutionTypes]; - static config_t mConfigs[kNumResolutionTypes][32]; + static const config_t mResolutionTable[kNumResolutionTypes][32]; + config_t mConfigs[kNumResolutionTypes][32]; DISALLOW_EVIL_CONSTRUCTORS(VideoFormats); }; diff --git a/media/libstagefright/wifi-display/nettest.cpp b/media/libstagefright/wifi-display/nettest.cpp new file mode 100644 index 0000000..0779bf5 --- /dev/null +++ b/media/libstagefright/wifi-display/nettest.cpp @@ -0,0 +1,400 @@ +/* + * 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_NEBUG 0 +#define LOG_TAG "nettest" +#include <utils/Log.h> + +#include "ANetworkSession.h" +#include "TimeSyncer.h" + +#include <binder/ProcessState.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AHandler.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/NuMediaExtractor.h> +#include <media/stagefright/Utils.h> + +namespace android { + +struct TestHandler : public AHandler { + TestHandler(const sp<ANetworkSession> &netSession); + + void listen(int32_t port); + void connect(const char *host, int32_t port); + +protected: + virtual ~TestHandler(); + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kTimeSyncerPort = 8123, + }; + + enum { + kWhatListen, + kWhatConnect, + kWhatTimeSyncerNotify, + kWhatNetNotify, + kWhatSendMore, + kWhatStop, + }; + + sp<ANetworkSession> mNetSession; + sp<TimeSyncer> mTimeSyncer; + + int32_t mServerSessionID; + int32_t mSessionID; + + int64_t mTimeOffsetUs; + bool mTimeOffsetValid; + + int32_t mCounter; + + int64_t mMaxDelayMs; + + void dumpDelay(int32_t counter, int64_t delayMs); + + DISALLOW_EVIL_CONSTRUCTORS(TestHandler); +}; + +TestHandler::TestHandler(const sp<ANetworkSession> &netSession) + : mNetSession(netSession), + mServerSessionID(0), + mSessionID(0), + mTimeOffsetUs(-1ll), + mTimeOffsetValid(false), + mCounter(0), + mMaxDelayMs(-1ll) { +} + +TestHandler::~TestHandler() { +} + +void TestHandler::listen(int32_t port) { + sp<AMessage> msg = new AMessage(kWhatListen, id()); + msg->setInt32("port", port); + msg->post(); +} + +void TestHandler::connect(const char *host, int32_t port) { + sp<AMessage> msg = new AMessage(kWhatConnect, id()); + msg->setString("host", host); + msg->setInt32("port", port); + msg->post(); +} + +void TestHandler::dumpDelay(int32_t counter, int64_t delayMs) { + static const int64_t kMinDelayMs = 0; + static const int64_t kMaxDelayMs = 300; + + const char *kPattern = "########################################"; + size_t kPatternSize = strlen(kPattern); + + int n = (kPatternSize * (delayMs - kMinDelayMs)) + / (kMaxDelayMs - kMinDelayMs); + + if (n < 0) { + n = 0; + } else if ((size_t)n > kPatternSize) { + n = kPatternSize; + } + + if (delayMs > mMaxDelayMs) { + mMaxDelayMs = delayMs; + } + + ALOGI("[%d] (%4lld ms / %4lld ms) %s", + counter, + delayMs, + mMaxDelayMs, + kPattern + kPatternSize - n); +} + +void TestHandler::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatListen: + { + sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id()); + mTimeSyncer = new TimeSyncer(mNetSession, notify); + looper()->registerHandler(mTimeSyncer); + + notify = new AMessage(kWhatNetNotify, id()); + + int32_t port; + CHECK(msg->findInt32("port", &port)); + + struct in_addr ifaceAddr; + ifaceAddr.s_addr = INADDR_ANY; + + CHECK_EQ((status_t)OK, + mNetSession->createTCPDatagramSession( + ifaceAddr, + port, + notify, + &mServerSessionID)); + break; + } + + case kWhatConnect: + { + sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id()); + mTimeSyncer = new TimeSyncer(mNetSession, notify); + looper()->registerHandler(mTimeSyncer); + mTimeSyncer->startServer(kTimeSyncerPort); + + AString host; + CHECK(msg->findString("host", &host)); + + int32_t port; + CHECK(msg->findInt32("port", &port)); + + notify = new AMessage(kWhatNetNotify, id()); + + CHECK_EQ((status_t)OK, + mNetSession->createTCPDatagramSession( + 0 /* localPort */, + host.c_str(), + port, + notify, + &mSessionID)); + break; + } + + case kWhatNetNotify: + { + int32_t reason; + CHECK(msg->findInt32("reason", &reason)); + + switch (reason) { + case ANetworkSession::kWhatConnected: + { + ALOGI("kWhatConnected"); + + (new AMessage(kWhatSendMore, id()))->post(); + break; + } + + case ANetworkSession::kWhatClientConnected: + { + ALOGI("kWhatClientConnected"); + + CHECK_EQ(mSessionID, 0); + CHECK(msg->findInt32("sessionID", &mSessionID)); + + AString clientIP; + CHECK(msg->findString("client-ip", &clientIP)); + + mTimeSyncer->startClient(clientIP.c_str(), kTimeSyncerPort); + break; + } + + case ANetworkSession::kWhatDatagram: + { + sp<ABuffer> packet; + CHECK(msg->findBuffer("data", &packet)); + + CHECK_EQ(packet->size(), 12u); + + int32_t counter = U32_AT(packet->data()); + int64_t timeUs = U64_AT(packet->data() + 4); + + if (mTimeOffsetValid) { + timeUs -= mTimeOffsetUs; + int64_t nowUs = ALooper::GetNowUs(); + int64_t delayMs = (nowUs - timeUs) / 1000ll; + + dumpDelay(counter, delayMs); + } else { + ALOGI("received %d", counter); + } + break; + } + + case ANetworkSession::kWhatError: + { + ALOGE("kWhatError"); + break; + } + + default: + TRESPASS(); + } + break; + } + + case kWhatTimeSyncerNotify: + { + CHECK(msg->findInt64("offset", &mTimeOffsetUs)); + mTimeOffsetValid = true; + break; + } + + case kWhatSendMore: + { + uint8_t buffer[4 + 8]; + buffer[0] = mCounter >> 24; + buffer[1] = (mCounter >> 16) & 0xff; + buffer[2] = (mCounter >> 8) & 0xff; + buffer[3] = mCounter & 0xff; + + int64_t nowUs = ALooper::GetNowUs(); + + buffer[4] = nowUs >> 56; + buffer[5] = (nowUs >> 48) & 0xff; + buffer[6] = (nowUs >> 40) & 0xff; + buffer[7] = (nowUs >> 32) & 0xff; + buffer[8] = (nowUs >> 24) & 0xff; + buffer[9] = (nowUs >> 16) & 0xff; + buffer[10] = (nowUs >> 8) & 0xff; + buffer[11] = nowUs & 0xff; + + ++mCounter; + + CHECK_EQ((status_t)OK, + mNetSession->sendRequest( + mSessionID, + buffer, + sizeof(buffer), + true /* timeValid */, + nowUs)); + + msg->post(100000ll); + break; + } + + case kWhatStop: + { + if (mSessionID != 0) { + mNetSession->destroySession(mSessionID); + mSessionID = 0; + } + + if (mServerSessionID != 0) { + mNetSession->destroySession(mServerSessionID); + mServerSessionID = 0; + } + + looper()->stop(); + break; + } + + default: + TRESPASS(); + } +} + +} // namespace android + +static void usage(const char *me) { + fprintf(stderr, + "usage: %s -c host:port\tconnect to remote host\n" + " -l port \tlisten\n", + me); +} + +int main(int argc, char **argv) { + using namespace android; + + // srand(time(NULL)); + + ProcessState::self()->startThreadPool(); + + DataSource::RegisterDefaultSniffers(); + + int32_t connectToPort = -1; + AString connectToHost; + + int32_t listenOnPort = -1; + + int res; + while ((res = getopt(argc, argv, "hc:l:")) >= 0) { + switch (res) { + case 'c': + { + const char *colonPos = strrchr(optarg, ':'); + + if (colonPos == NULL) { + usage(argv[0]); + exit(1); + } + + connectToHost.setTo(optarg, colonPos - optarg); + + char *end; + connectToPort = strtol(colonPos + 1, &end, 10); + + if (*end != '\0' || end == colonPos + 1 + || connectToPort < 0 || connectToPort > 65535) { + fprintf(stderr, "Illegal port specified.\n"); + exit(1); + } + break; + } + + case 'l': + { + char *end; + listenOnPort = strtol(optarg, &end, 10); + + if (*end != '\0' || end == optarg + || listenOnPort < 0 || listenOnPort > 65535) { + fprintf(stderr, "Illegal port specified.\n"); + exit(1); + } + break; + } + + case '?': + case 'h': + usage(argv[0]); + exit(1); + } + } + + if ((listenOnPort < 0 && connectToPort < 0) + || (listenOnPort >= 0 && connectToPort >= 0)) { + fprintf(stderr, + "You need to select either client or server mode.\n"); + exit(1); + } + + sp<ANetworkSession> netSession = new ANetworkSession; + netSession->start(); + + sp<ALooper> looper = new ALooper; + + sp<TestHandler> handler = new TestHandler(netSession); + looper->registerHandler(handler); + + if (listenOnPort) { + handler->listen(listenOnPort); + } + + if (connectToPort >= 0) { + handler->connect(connectToHost.c_str(), connectToPort); + } + + looper->start(true /* runOnCallingThread */); + + return 0; +} diff --git a/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp b/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp new file mode 100644 index 0000000..7a96081 --- /dev/null +++ b/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp @@ -0,0 +1,328 @@ +/* + * 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 "RTPAssembler" +#include <utils/Log.h> + +#include "RTPAssembler.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaErrors.h> + +namespace android { + +RTPReceiver::Assembler::Assembler(const sp<AMessage> ¬ify) + : mNotify(notify) { +} + +void RTPReceiver::Assembler::postAccessUnit( + const sp<ABuffer> &accessUnit, bool followsDiscontinuity) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", RTPReceiver::kWhatAccessUnit); + notify->setBuffer("accessUnit", accessUnit); + notify->setInt32("followsDiscontinuity", followsDiscontinuity); + notify->post(); +} +//////////////////////////////////////////////////////////////////////////////// + +RTPReceiver::TSAssembler::TSAssembler(const sp<AMessage> ¬ify) + : Assembler(notify), + mSawDiscontinuity(false) { +} + +void RTPReceiver::TSAssembler::signalDiscontinuity() { + mSawDiscontinuity = true; +} + +status_t RTPReceiver::TSAssembler::processPacket(const sp<ABuffer> &packet) { + int32_t rtpTime; + CHECK(packet->meta()->findInt32("rtp-time", &rtpTime)); + + packet->meta()->setInt64("timeUs", (rtpTime * 100ll) / 9); + + postAccessUnit(packet, mSawDiscontinuity); + + if (mSawDiscontinuity) { + mSawDiscontinuity = false; + } + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +RTPReceiver::H264Assembler::H264Assembler(const sp<AMessage> ¬ify) + : Assembler(notify), + mState(0), + mIndicator(0), + mNALType(0), + mAccessUnitRTPTime(0) { +} + +void RTPReceiver::H264Assembler::signalDiscontinuity() { + reset(); +} + +status_t RTPReceiver::H264Assembler::processPacket(const sp<ABuffer> &packet) { + status_t err = internalProcessPacket(packet); + + if (err != OK) { + reset(); + } + + return err; +} + +status_t RTPReceiver::H264Assembler::internalProcessPacket( + const sp<ABuffer> &packet) { + const uint8_t *data = packet->data(); + size_t size = packet->size(); + + switch (mState) { + case 0: + { + if (size < 1 || (data[0] & 0x80)) { + ALOGV("Malformed H264 RTP packet (empty or F-bit set)"); + return ERROR_MALFORMED; + } + + unsigned nalType = data[0] & 0x1f; + if (nalType >= 1 && nalType <= 23) { + addSingleNALUnit(packet); + ALOGV("added single NAL packet"); + } else if (nalType == 28) { + // FU-A + unsigned indicator = data[0]; + CHECK((indicator & 0x1f) == 28); + + if (size < 2) { + ALOGV("Malformed H264 FU-A packet (single byte)"); + return ERROR_MALFORMED; + } + + if (!(data[1] & 0x80)) { + ALOGV("Malformed H264 FU-A packet (no start bit)"); + return ERROR_MALFORMED; + } + + mIndicator = data[0]; + mNALType = data[1] & 0x1f; + uint32_t nri = (data[0] >> 5) & 3; + + clearAccumulator(); + + uint8_t byte = mNALType | (nri << 5); + appendToAccumulator(&byte, 1); + appendToAccumulator(data + 2, size - 2); + + int32_t rtpTime; + CHECK(packet->meta()->findInt32("rtp-time", &rtpTime)); + mAccumulator->meta()->setInt32("rtp-time", rtpTime); + + if (data[1] & 0x40) { + // Huh? End bit also set on the first buffer. + addSingleNALUnit(mAccumulator); + clearAccumulator(); + + ALOGV("added FU-A"); + break; + } + + mState = 1; + } else if (nalType == 24) { + // STAP-A + + status_t err = addSingleTimeAggregationPacket(packet); + if (err != OK) { + return err; + } + } else { + ALOGV("Malformed H264 packet (unknown type %d)", nalType); + return ERROR_UNSUPPORTED; + } + break; + } + + case 1: + { + if (size < 2 + || data[0] != mIndicator + || (data[1] & 0x1f) != mNALType + || (data[1] & 0x80)) { + ALOGV("Malformed H264 FU-A packet (indicator, " + "type or start bit mismatch)"); + + return ERROR_MALFORMED; + } + + appendToAccumulator(data + 2, size - 2); + + if (data[1] & 0x40) { + addSingleNALUnit(mAccumulator); + + clearAccumulator(); + mState = 0; + + ALOGV("added FU-A"); + } + break; + } + + default: + TRESPASS(); + } + + int32_t marker; + CHECK(packet->meta()->findInt32("M", &marker)); + + if (marker) { + flushAccessUnit(); + } + + return OK; +} + +void RTPReceiver::H264Assembler::reset() { + mNALUnits.clear(); + + clearAccumulator(); + mState = 0; +} + +void RTPReceiver::H264Assembler::clearAccumulator() { + if (mAccumulator != NULL) { + // XXX Too expensive. + mAccumulator.clear(); + } +} + +void RTPReceiver::H264Assembler::appendToAccumulator( + const void *data, size_t size) { + if (mAccumulator == NULL) { + mAccumulator = new ABuffer(size); + memcpy(mAccumulator->data(), data, size); + return; + } + + if (mAccumulator->size() + size > mAccumulator->capacity()) { + sp<ABuffer> buf = new ABuffer(mAccumulator->size() + size); + memcpy(buf->data(), mAccumulator->data(), mAccumulator->size()); + buf->setRange(0, mAccumulator->size()); + + int32_t rtpTime; + if (mAccumulator->meta()->findInt32("rtp-time", &rtpTime)) { + buf->meta()->setInt32("rtp-time", rtpTime); + } + + mAccumulator = buf; + } + + memcpy(mAccumulator->data() + mAccumulator->size(), data, size); + mAccumulator->setRange(0, mAccumulator->size() + size); +} + +void RTPReceiver::H264Assembler::addSingleNALUnit(const sp<ABuffer> &packet) { + if (mNALUnits.empty()) { + int32_t rtpTime; + CHECK(packet->meta()->findInt32("rtp-time", &rtpTime)); + + mAccessUnitRTPTime = rtpTime; + } + + mNALUnits.push_back(packet); +} + +void RTPReceiver::H264Assembler::flushAccessUnit() { + if (mNALUnits.empty()) { + return; + } + + size_t totalSize = 0; + for (List<sp<ABuffer> >::iterator it = mNALUnits.begin(); + it != mNALUnits.end(); ++it) { + totalSize += 4 + (*it)->size(); + } + + sp<ABuffer> accessUnit = new ABuffer(totalSize); + size_t offset = 0; + for (List<sp<ABuffer> >::iterator it = mNALUnits.begin(); + it != mNALUnits.end(); ++it) { + const sp<ABuffer> nalUnit = *it; + + memcpy(accessUnit->data() + offset, "\x00\x00\x00\x01", 4); + + memcpy(accessUnit->data() + offset + 4, + nalUnit->data(), + nalUnit->size()); + + offset += 4 + nalUnit->size(); + } + + mNALUnits.clear(); + + accessUnit->meta()->setInt64("timeUs", mAccessUnitRTPTime * 100ll / 9ll); + postAccessUnit(accessUnit, false /* followsDiscontinuity */); +} + +status_t RTPReceiver::H264Assembler::addSingleTimeAggregationPacket( + const sp<ABuffer> &packet) { + const uint8_t *data = packet->data(); + size_t size = packet->size(); + + if (size < 3) { + ALOGV("Malformed H264 STAP-A packet (too small)"); + return ERROR_MALFORMED; + } + + int32_t rtpTime; + CHECK(packet->meta()->findInt32("rtp-time", &rtpTime)); + + ++data; + --size; + while (size >= 2) { + size_t nalSize = (data[0] << 8) | data[1]; + + if (size < nalSize + 2) { + ALOGV("Malformed H264 STAP-A packet (incomplete NAL unit)"); + return ERROR_MALFORMED; + } + + sp<ABuffer> unit = new ABuffer(nalSize); + memcpy(unit->data(), &data[2], nalSize); + + unit->meta()->setInt32("rtp-time", rtpTime); + + addSingleNALUnit(unit); + + data += 2 + nalSize; + size -= 2 + nalSize; + } + + if (size != 0) { + ALOGV("Unexpected padding at end of STAP-A packet."); + } + + ALOGV("added STAP-A"); + + return OK; +} + +} // namespace android + diff --git a/media/libstagefright/wifi-display/rtp/RTPAssembler.h b/media/libstagefright/wifi-display/rtp/RTPAssembler.h new file mode 100644 index 0000000..e456d32 --- /dev/null +++ b/media/libstagefright/wifi-display/rtp/RTPAssembler.h @@ -0,0 +1,92 @@ +/* + * 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_ASSEMBLER_H_ + +#define RTP_ASSEMBLER_H_ + +#include "RTPReceiver.h" + +namespace android { + +// A helper class to reassemble the payload of RTP packets into access +// units depending on the packetization scheme. +struct RTPReceiver::Assembler : public RefBase { + Assembler(const sp<AMessage> ¬ify); + + virtual void signalDiscontinuity() = 0; + virtual status_t processPacket(const sp<ABuffer> &packet) = 0; + +protected: + virtual ~Assembler() {} + + void postAccessUnit( + const sp<ABuffer> &accessUnit, bool followsDiscontinuity); + +private: + sp<AMessage> mNotify; + + DISALLOW_EVIL_CONSTRUCTORS(Assembler); +}; + +struct RTPReceiver::TSAssembler : public RTPReceiver::Assembler { + TSAssembler(const sp<AMessage> ¬ify); + + virtual void signalDiscontinuity(); + virtual status_t processPacket(const sp<ABuffer> &packet); + +private: + bool mSawDiscontinuity; + + DISALLOW_EVIL_CONSTRUCTORS(TSAssembler); +}; + +struct RTPReceiver::H264Assembler : public RTPReceiver::Assembler { + H264Assembler(const sp<AMessage> ¬ify); + + virtual void signalDiscontinuity(); + virtual status_t processPacket(const sp<ABuffer> &packet); + +private: + int32_t mState; + + uint8_t mIndicator; + uint8_t mNALType; + + sp<ABuffer> mAccumulator; + + List<sp<ABuffer> > mNALUnits; + int32_t mAccessUnitRTPTime; + + status_t internalProcessPacket(const sp<ABuffer> &packet); + + void addSingleNALUnit(const sp<ABuffer> &packet); + status_t addSingleTimeAggregationPacket(const sp<ABuffer> &packet); + + void flushAccessUnit(); + + void clearAccumulator(); + void appendToAccumulator(const void *data, size_t size); + + void reset(); + + DISALLOW_EVIL_CONSTRUCTORS(H264Assembler); +}; + +} // namespace android + +#endif // RTP_ASSEMBLER_H_ + diff --git a/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp b/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp new file mode 100644 index 0000000..2d22e79 --- /dev/null +++ b/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp @@ -0,0 +1,1153 @@ +/* + * 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 "RTPReceiver" +#include <utils/Log.h> + +#include "RTPAssembler.h" +#include "RTPReceiver.h" + +#include "ANetworkSession.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/Utils.h> + +#define TRACK_PACKET_LOSS 0 + +namespace android { + +//////////////////////////////////////////////////////////////////////////////// + +struct RTPReceiver::Source : public AHandler { + Source(RTPReceiver *receiver, uint32_t ssrc); + + void onPacketReceived(uint16_t seq, const sp<ABuffer> &buffer); + + void addReportBlock(uint32_t ssrc, const sp<ABuffer> &buf); + +protected: + virtual ~Source(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatRetransmit, + kWhatDeclareLost, + }; + + static const uint32_t kMinSequential = 2; + static const uint32_t kMaxDropout = 3000; + static const uint32_t kMaxMisorder = 100; + static const uint32_t kRTPSeqMod = 1u << 16; + static const int64_t kReportIntervalUs = 10000000ll; + + RTPReceiver *mReceiver; + uint32_t mSSRC; + bool mFirst; + uint16_t mMaxSeq; + uint32_t mCycles; + uint32_t mBaseSeq; + uint32_t mReceived; + uint32_t mExpectedPrior; + uint32_t mReceivedPrior; + + int64_t mFirstArrivalTimeUs; + int64_t mFirstRTPTimeUs; + + // Ordered by extended seq number. + List<sp<ABuffer> > mPackets; + + enum StatusBits { + STATUS_DECLARED_LOST = 1, + STATUS_REQUESTED_RETRANSMISSION = 2, + STATUS_ARRIVED_LATE = 4, + }; +#if TRACK_PACKET_LOSS + KeyedVector<int32_t, uint32_t> mLostPackets; +#endif + + void modifyPacketStatus(int32_t extSeqNo, uint32_t mask); + + int32_t mAwaitingExtSeqNo; + bool mRequestedRetransmission; + + int32_t mActivePacketType; + sp<Assembler> mActiveAssembler; + + int64_t mNextReportTimeUs; + + int32_t mNumDeclaredLost; + int32_t mNumDeclaredLostPrior; + + int32_t mRetransmitGeneration; + int32_t mDeclareLostGeneration; + bool mDeclareLostTimerPending; + + void queuePacket(const sp<ABuffer> &packet); + void dequeueMore(); + + sp<ABuffer> getNextPacket(); + void resync(); + + void postRetransmitTimer(int64_t delayUs); + void postDeclareLostTimer(int64_t delayUs); + void cancelTimers(); + + DISALLOW_EVIL_CONSTRUCTORS(Source); +}; + +//////////////////////////////////////////////////////////////////////////////// + +RTPReceiver::Source::Source(RTPReceiver *receiver, uint32_t ssrc) + : mReceiver(receiver), + mSSRC(ssrc), + mFirst(true), + mMaxSeq(0), + mCycles(0), + mBaseSeq(0), + mReceived(0), + mExpectedPrior(0), + mReceivedPrior(0), + mFirstArrivalTimeUs(-1ll), + mFirstRTPTimeUs(-1ll), + mAwaitingExtSeqNo(-1), + mRequestedRetransmission(false), + mActivePacketType(-1), + mNextReportTimeUs(-1ll), + mNumDeclaredLost(0), + mNumDeclaredLostPrior(0), + mRetransmitGeneration(0), + mDeclareLostGeneration(0), + mDeclareLostTimerPending(false) { +} + +RTPReceiver::Source::~Source() { +} + +void RTPReceiver::Source::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatRetransmit: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + + if (generation != mRetransmitGeneration) { + break; + } + + mRequestedRetransmission = true; + mReceiver->requestRetransmission(mSSRC, mAwaitingExtSeqNo); + + modifyPacketStatus( + mAwaitingExtSeqNo, STATUS_REQUESTED_RETRANSMISSION); + break; + } + + case kWhatDeclareLost: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + + if (generation != mDeclareLostGeneration) { + break; + } + + cancelTimers(); + + ALOGV("Lost packet extSeqNo %d %s", + mAwaitingExtSeqNo, + mRequestedRetransmission ? "*" : ""); + + mRequestedRetransmission = false; + if (mActiveAssembler != NULL) { + mActiveAssembler->signalDiscontinuity(); + } + + modifyPacketStatus(mAwaitingExtSeqNo, STATUS_DECLARED_LOST); + + // resync(); + ++mAwaitingExtSeqNo; + ++mNumDeclaredLost; + + mReceiver->notifyPacketLost(); + + dequeueMore(); + break; + } + + default: + TRESPASS(); + } +} + +void RTPReceiver::Source::onPacketReceived( + uint16_t seq, const sp<ABuffer> &buffer) { + if (mFirst) { + buffer->setInt32Data(mCycles | seq); + queuePacket(buffer); + + mFirst = false; + mBaseSeq = seq; + mMaxSeq = seq; + ++mReceived; + return; + } + + uint16_t udelta = seq - mMaxSeq; + + if (udelta < kMaxDropout) { + // In order, with permissible gap. + + if (seq < mMaxSeq) { + // Sequence number wrapped - count another 64K cycle + mCycles += kRTPSeqMod; + } + + mMaxSeq = seq; + + ++mReceived; + } else if (udelta <= kRTPSeqMod - kMaxMisorder) { + // The sequence number made a very large jump + return; + } else { + // Duplicate or reordered packet. + } + + buffer->setInt32Data(mCycles | seq); + queuePacket(buffer); +} + +void RTPReceiver::Source::queuePacket(const sp<ABuffer> &packet) { + int32_t newExtendedSeqNo = packet->int32Data(); + + if (mFirstArrivalTimeUs < 0ll) { + mFirstArrivalTimeUs = ALooper::GetNowUs(); + + uint32_t rtpTime; + CHECK(packet->meta()->findInt32("rtp-time", (int32_t *)&rtpTime)); + + mFirstRTPTimeUs = (rtpTime * 100ll) / 9ll; + } + + if (mAwaitingExtSeqNo >= 0 && newExtendedSeqNo < mAwaitingExtSeqNo) { + // We're no longer interested in these. They're old. + ALOGV("dropping stale extSeqNo %d", newExtendedSeqNo); + + modifyPacketStatus(newExtendedSeqNo, STATUS_ARRIVED_LATE); + return; + } + + if (mPackets.empty()) { + mPackets.push_back(packet); + dequeueMore(); + return; + } + + List<sp<ABuffer> >::iterator firstIt = mPackets.begin(); + List<sp<ABuffer> >::iterator it = --mPackets.end(); + for (;;) { + int32_t extendedSeqNo = (*it)->int32Data(); + + if (extendedSeqNo == newExtendedSeqNo) { + // Duplicate packet. + return; + } + + if (extendedSeqNo < newExtendedSeqNo) { + // Insert new packet after the one at "it". + mPackets.insert(++it, packet); + break; + } + + if (it == firstIt) { + // Insert new packet before the first existing one. + mPackets.insert(it, packet); + break; + } + + --it; + } + + dequeueMore(); +} + +void RTPReceiver::Source::dequeueMore() { + int64_t nowUs = ALooper::GetNowUs(); + if (mNextReportTimeUs < 0ll || nowUs >= mNextReportTimeUs) { + if (mNextReportTimeUs >= 0ll) { + uint32_t expected = (mMaxSeq | mCycles) - mBaseSeq + 1; + + uint32_t expectedInterval = expected - mExpectedPrior; + mExpectedPrior = expected; + + uint32_t receivedInterval = mReceived - mReceivedPrior; + mReceivedPrior = mReceived; + + int64_t lostInterval = + (int64_t)expectedInterval - (int64_t)receivedInterval; + + int32_t declaredLostInterval = + mNumDeclaredLost - mNumDeclaredLostPrior; + + mNumDeclaredLostPrior = mNumDeclaredLost; + + if (declaredLostInterval > 0) { + ALOGI("lost %lld packets (%.2f %%), declared %d lost\n", + lostInterval, + 100.0f * lostInterval / expectedInterval, + declaredLostInterval); + } + } + + mNextReportTimeUs = nowUs + kReportIntervalUs; + +#if TRACK_PACKET_LOSS + for (size_t i = 0; i < mLostPackets.size(); ++i) { + int32_t key = mLostPackets.keyAt(i); + uint32_t value = mLostPackets.valueAt(i); + + AString status; + if (value & STATUS_REQUESTED_RETRANSMISSION) { + status.append("retrans "); + } + if (value & STATUS_ARRIVED_LATE) { + status.append("arrived-late "); + } + ALOGI("Packet %d declared lost %s", key, status.c_str()); + } +#endif + } + + sp<ABuffer> packet; + while ((packet = getNextPacket()) != NULL) { + if (mDeclareLostTimerPending) { + cancelTimers(); + } + + CHECK_GE(mAwaitingExtSeqNo, 0); +#if TRACK_PACKET_LOSS + mLostPackets.removeItem(mAwaitingExtSeqNo); +#endif + + int32_t packetType; + CHECK(packet->meta()->findInt32("PT", &packetType)); + + if (packetType != mActivePacketType) { + mActiveAssembler = mReceiver->makeAssembler(packetType); + mActivePacketType = packetType; + } + + if (mActiveAssembler != NULL) { + status_t err = mActiveAssembler->processPacket(packet); + if (err != OK) { + ALOGV("assembler returned error %d", err); + } + } + + ++mAwaitingExtSeqNo; + } + + if (mDeclareLostTimerPending) { + return; + } + + if (mPackets.empty()) { + return; + } + + CHECK_GE(mAwaitingExtSeqNo, 0); + + const sp<ABuffer> &firstPacket = *mPackets.begin(); + + uint32_t rtpTime; + CHECK(firstPacket->meta()->findInt32( + "rtp-time", (int32_t *)&rtpTime)); + + + int64_t rtpUs = (rtpTime * 100ll) / 9ll; + + int64_t maxArrivalTimeUs = + mFirstArrivalTimeUs + rtpUs - mFirstRTPTimeUs; + + nowUs = ALooper::GetNowUs(); + + CHECK_LT(mAwaitingExtSeqNo, firstPacket->int32Data()); + + ALOGV("waiting for %d, comparing against %d, %lld us left", + mAwaitingExtSeqNo, + firstPacket->int32Data(), + maxArrivalTimeUs - nowUs); + + postDeclareLostTimer(maxArrivalTimeUs + kPacketLostAfterUs); + + if (kRequestRetransmissionAfterUs > 0ll) { + postRetransmitTimer( + maxArrivalTimeUs + kRequestRetransmissionAfterUs); + } +} + +sp<ABuffer> RTPReceiver::Source::getNextPacket() { + if (mPackets.empty()) { + return NULL; + } + + int32_t extSeqNo = (*mPackets.begin())->int32Data(); + + if (mAwaitingExtSeqNo < 0) { + mAwaitingExtSeqNo = extSeqNo; + } else if (extSeqNo != mAwaitingExtSeqNo) { + return NULL; + } + + sp<ABuffer> packet = *mPackets.begin(); + mPackets.erase(mPackets.begin()); + + return packet; +} + +void RTPReceiver::Source::resync() { + mAwaitingExtSeqNo = -1; +} + +void RTPReceiver::Source::addReportBlock( + uint32_t ssrc, const sp<ABuffer> &buf) { + uint32_t extMaxSeq = mMaxSeq | mCycles; + uint32_t expected = extMaxSeq - mBaseSeq + 1; + + int64_t lost = (int64_t)expected - (int64_t)mReceived; + if (lost > 0x7fffff) { + lost = 0x7fffff; + } else if (lost < -0x800000) { + lost = -0x800000; + } + + uint32_t expectedInterval = expected - mExpectedPrior; + mExpectedPrior = expected; + + uint32_t receivedInterval = mReceived - mReceivedPrior; + mReceivedPrior = mReceived; + + int64_t lostInterval = expectedInterval - receivedInterval; + + uint8_t fractionLost; + if (expectedInterval == 0 || lostInterval <=0) { + fractionLost = 0; + } else { + fractionLost = (lostInterval << 8) / expectedInterval; + } + + uint8_t *ptr = buf->data() + buf->size(); + + ptr[0] = ssrc >> 24; + ptr[1] = (ssrc >> 16) & 0xff; + ptr[2] = (ssrc >> 8) & 0xff; + ptr[3] = ssrc & 0xff; + + ptr[4] = fractionLost; + + ptr[5] = (lost >> 16) & 0xff; + ptr[6] = (lost >> 8) & 0xff; + ptr[7] = lost & 0xff; + + ptr[8] = extMaxSeq >> 24; + ptr[9] = (extMaxSeq >> 16) & 0xff; + ptr[10] = (extMaxSeq >> 8) & 0xff; + ptr[11] = extMaxSeq & 0xff; + + // XXX TODO: + + ptr[12] = 0x00; // interarrival jitter + ptr[13] = 0x00; + ptr[14] = 0x00; + ptr[15] = 0x00; + + ptr[16] = 0x00; // last SR + ptr[17] = 0x00; + ptr[18] = 0x00; + ptr[19] = 0x00; + + ptr[20] = 0x00; // delay since last SR + ptr[21] = 0x00; + ptr[22] = 0x00; + ptr[23] = 0x00; + + buf->setRange(buf->offset(), buf->size() + 24); +} + +//////////////////////////////////////////////////////////////////////////////// + +RTPReceiver::RTPReceiver( + const sp<ANetworkSession> &netSession, + const sp<AMessage> ¬ify, + uint32_t flags) + : mNetSession(netSession), + mNotify(notify), + mFlags(flags), + mRTPMode(TRANSPORT_UNDEFINED), + mRTCPMode(TRANSPORT_UNDEFINED), + mRTPSessionID(0), + mRTCPSessionID(0), + mRTPConnected(false), + mRTCPConnected(false), + mRTPClientSessionID(0), + mRTCPClientSessionID(0) { +} + +RTPReceiver::~RTPReceiver() { + if (mRTCPClientSessionID != 0) { + mNetSession->destroySession(mRTCPClientSessionID); + mRTCPClientSessionID = 0; + } + + if (mRTPClientSessionID != 0) { + mNetSession->destroySession(mRTPClientSessionID); + mRTPClientSessionID = 0; + } + + if (mRTCPSessionID != 0) { + mNetSession->destroySession(mRTCPSessionID); + mRTCPSessionID = 0; + } + + if (mRTPSessionID != 0) { + mNetSession->destroySession(mRTPSessionID); + mRTPSessionID = 0; + } +} + +status_t RTPReceiver::initAsync( + TransportMode rtpMode, + 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); + + sp<AMessage> rtpNotify = new AMessage(kWhatRTPNotify, id()); + + sp<AMessage> rtcpNotify; + if (rtcpMode != TRANSPORT_NONE) { + rtcpNotify = new AMessage(kWhatRTCPNotify, id()); + } + + CHECK_EQ(mRTPSessionID, 0); + CHECK_EQ(mRTCPSessionID, 0); + + int32_t localRTPPort; + + struct in_addr ifaceAddr; + ifaceAddr.s_addr = INADDR_ANY; + + for (;;) { + localRTPPort = PickRandomRTPPort(); + + status_t err; + if (rtpMode == TRANSPORT_UDP) { + err = mNetSession->createUDPSession( + localRTPPort, + rtpNotify, + &mRTPSessionID); + } else { + CHECK_EQ(rtpMode, TRANSPORT_TCP); + err = mNetSession->createTCPDatagramSession( + ifaceAddr, + localRTPPort, + rtpNotify, + &mRTPSessionID); + } + + if (err != OK) { + continue; + } + + if (rtcpMode == TRANSPORT_NONE) { + break; + } else if (rtcpMode == TRANSPORT_UDP) { + err = mNetSession->createUDPSession( + localRTPPort + 1, + rtcpNotify, + &mRTCPSessionID); + } else { + CHECK_EQ(rtpMode, TRANSPORT_TCP); + err = mNetSession->createTCPDatagramSession( + ifaceAddr, + localRTPPort + 1, + rtcpNotify, + &mRTCPSessionID); + } + + if (err == OK) { + break; + } + + mNetSession->destroySession(mRTPSessionID); + mRTPSessionID = 0; + } + + mRTPMode = rtpMode; + mRTCPMode = rtcpMode; + *outLocalRTPPort = localRTPPort; + + return OK; +} + +status_t RTPReceiver::connect( + const char *remoteHost, int32_t remoteRTPPort, int32_t remoteRTCPPort) { + status_t err; + + if (mRTPMode == TRANSPORT_UDP) { + CHECK(!mRTPConnected); + + err = mNetSession->connectUDPSession( + mRTPSessionID, remoteHost, remoteRTPPort); + + if (err != OK) { + notifyInitDone(err); + return err; + } + + ALOGI("connectUDPSession RTP successful."); + + mRTPConnected = true; + } + + if (mRTCPMode == TRANSPORT_UDP) { + CHECK(!mRTCPConnected); + + err = mNetSession->connectUDPSession( + mRTCPSessionID, remoteHost, remoteRTCPPort); + + if (err != OK) { + notifyInitDone(err); + return err; + } + + scheduleSendRR(); + + ALOGI("connectUDPSession RTCP successful."); + + mRTCPConnected = true; + } + + if (mRTPConnected + && (mRTCPConnected || mRTCPMode == TRANSPORT_NONE)) { + notifyInitDone(OK); + } + + return OK; +} + +status_t RTPReceiver::informSender(const sp<AMessage> ¶ms) { + if (!mRTCPConnected) { + return INVALID_OPERATION; + } + + int64_t avgLatencyUs; + CHECK(params->findInt64("avgLatencyUs", &avgLatencyUs)); + + int64_t maxLatencyUs; + CHECK(params->findInt64("maxLatencyUs", &maxLatencyUs)); + + sp<ABuffer> buf = new ABuffer(28); + + uint8_t *ptr = buf->data(); + ptr[0] = 0x80 | 0; + ptr[1] = 204; // APP + ptr[2] = 0; + + CHECK((buf->size() % 4) == 0u); + ptr[3] = (buf->size() / 4) - 1; + + ptr[4] = kSourceID >> 24; // SSRC + ptr[5] = (kSourceID >> 16) & 0xff; + ptr[6] = (kSourceID >> 8) & 0xff; + ptr[7] = kSourceID & 0xff; + ptr[8] = 'l'; + ptr[9] = 'a'; + ptr[10] = 't'; + ptr[11] = 'e'; + + ptr[12] = avgLatencyUs >> 56; + ptr[13] = (avgLatencyUs >> 48) & 0xff; + ptr[14] = (avgLatencyUs >> 40) & 0xff; + ptr[15] = (avgLatencyUs >> 32) & 0xff; + ptr[16] = (avgLatencyUs >> 24) & 0xff; + ptr[17] = (avgLatencyUs >> 16) & 0xff; + ptr[18] = (avgLatencyUs >> 8) & 0xff; + ptr[19] = avgLatencyUs & 0xff; + + ptr[20] = maxLatencyUs >> 56; + ptr[21] = (maxLatencyUs >> 48) & 0xff; + ptr[22] = (maxLatencyUs >> 40) & 0xff; + ptr[23] = (maxLatencyUs >> 32) & 0xff; + ptr[24] = (maxLatencyUs >> 24) & 0xff; + ptr[25] = (maxLatencyUs >> 16) & 0xff; + ptr[26] = (maxLatencyUs >> 8) & 0xff; + ptr[27] = maxLatencyUs & 0xff; + + mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size()); + + return OK; +} + +void RTPReceiver::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatRTPNotify: + case kWhatRTCPNotify: + onNetNotify(msg->what() == kWhatRTPNotify, msg); + break; + + case kWhatSendRR: + { + onSendRR(); + break; + } + + default: + TRESPASS(); + } +} + +void RTPReceiver::onNetNotify(bool isRTP, const sp<AMessage> &msg) { + int32_t reason; + CHECK(msg->findInt32("reason", &reason)); + + switch (reason) { + case ANetworkSession::kWhatError: + { + int32_t sessionID; + CHECK(msg->findInt32("sessionID", &sessionID)); + + int32_t err; + CHECK(msg->findInt32("err", &err)); + + int32_t errorOccuredDuringSend; + CHECK(msg->findInt32("send", &errorOccuredDuringSend)); + + AString detail; + CHECK(msg->findString("detail", &detail)); + + ALOGE("An error occurred during %s in session %d " + "(%d, '%s' (%s)).", + errorOccuredDuringSend ? "send" : "receive", + sessionID, + err, + detail.c_str(), + strerror(-err)); + + mNetSession->destroySession(sessionID); + + if (sessionID == mRTPSessionID) { + mRTPSessionID = 0; + } else if (sessionID == mRTCPSessionID) { + mRTCPSessionID = 0; + } else if (sessionID == mRTPClientSessionID) { + mRTPClientSessionID = 0; + } else if (sessionID == mRTCPClientSessionID) { + mRTCPClientSessionID = 0; + } + + if (!mRTPConnected + || (mRTCPMode != TRANSPORT_NONE && !mRTCPConnected)) { + notifyInitDone(err); + break; + } + + notifyError(err); + break; + } + + case ANetworkSession::kWhatDatagram: + { + sp<ABuffer> data; + CHECK(msg->findBuffer("data", &data)); + + if (isRTP) { + if (mFlags & FLAG_AUTO_CONNECT) { + AString fromAddr; + CHECK(msg->findString("fromAddr", &fromAddr)); + + int32_t fromPort; + CHECK(msg->findInt32("fromPort", &fromPort)); + + CHECK_EQ((status_t)OK, + connect( + fromAddr.c_str(), fromPort, fromPort + 1)); + + mFlags &= ~FLAG_AUTO_CONNECT; + } + + onRTPData(data); + } else { + onRTCPData(data); + } + break; + } + + case ANetworkSession::kWhatClientConnected: + { + int32_t sessionID; + CHECK(msg->findInt32("sessionID", &sessionID)); + + if (isRTP) { + CHECK_EQ(mRTPMode, TRANSPORT_TCP); + + if (mRTPClientSessionID != 0) { + // We only allow a single client connection. + mNetSession->destroySession(sessionID); + sessionID = 0; + break; + } + + mRTPClientSessionID = sessionID; + mRTPConnected = true; + } else { + CHECK_EQ(mRTCPMode, TRANSPORT_TCP); + + if (mRTCPClientSessionID != 0) { + // We only allow a single client connection. + mNetSession->destroySession(sessionID); + sessionID = 0; + break; + } + + mRTCPClientSessionID = sessionID; + mRTCPConnected = true; + } + + if (mRTPConnected + && (mRTCPConnected || mRTCPMode == TRANSPORT_NONE)) { + notifyInitDone(OK); + } + break; + } + } +} + +void RTPReceiver::notifyInitDone(status_t err) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatInitDone); + notify->setInt32("err", err); + notify->post(); +} + +void RTPReceiver::notifyError(status_t err) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatError); + notify->setInt32("err", err); + notify->post(); +} + +void RTPReceiver::notifyPacketLost() { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatPacketLost); + notify->post(); +} + +status_t RTPReceiver::onRTPData(const sp<ABuffer> &buffer) { + size_t size = buffer->size(); + if (size < 12) { + // Too short to be a valid RTP header. + return ERROR_MALFORMED; + } + + const uint8_t *data = buffer->data(); + + 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; + } + + int numCSRCs = data[0] & 0x0f; + + size_t payloadOffset = 12 + 4 * numCSRCs; + + if (size < payloadOffset) { + // Not enough data to fit the basic header and all the CSRC entries. + return ERROR_MALFORMED; + } + + if (data[0] & 0x10) { + // Header eXtension present. + + if (size < payloadOffset + 4) { + // Not enough data to fit the basic header, all CSRC entries + // and the first 4 bytes of the extension header. + + return ERROR_MALFORMED; + } + + const uint8_t *extensionData = &data[payloadOffset]; + + size_t extensionLength = + 4 * (extensionData[2] << 8 | extensionData[3]); + + if (size < payloadOffset + 4 + extensionLength) { + return ERROR_MALFORMED; + } + + payloadOffset += 4 + extensionLength; + } + + uint32_t srcId = U32_AT(&data[8]); + uint32_t rtpTime = U32_AT(&data[4]); + uint16_t seqNo = U16_AT(&data[2]); + + sp<AMessage> meta = buffer->meta(); + meta->setInt32("ssrc", srcId); + meta->setInt32("rtp-time", rtpTime); + meta->setInt32("PT", data[1] & 0x7f); + meta->setInt32("M", data[1] >> 7); + + buffer->setRange(payloadOffset, size - payloadOffset); + + ssize_t index = mSources.indexOfKey(srcId); + sp<Source> source; + if (index < 0) { + source = new Source(this, srcId); + looper()->registerHandler(source); + + mSources.add(srcId, source); + } else { + source = mSources.valueAt(index); + } + + source->onPacketReceived(seqNo, buffer); + + return OK; +} + +status_t RTPReceiver::onRTCPData(const sp<ABuffer> &data) { + ALOGI("onRTCPData"); + return OK; +} + +void RTPReceiver::addSDES(const sp<ABuffer> &buffer) { + uint8_t *data = buffer->data() + buffer->size(); + data[0] = 0x80 | 1; + data[1] = 202; // SDES + data[4] = kSourceID >> 24; // SSRC + data[5] = (kSourceID >> 16) & 0xff; + data[6] = (kSourceID >> 8) & 0xff; + data[7] = kSourceID & 0xff; + + size_t offset = 8; + + data[offset++] = 1; // CNAME + + AString cname = "stagefright@somewhere"; + data[offset++] = cname.size(); + + memcpy(&data[offset], cname.c_str(), cname.size()); + offset += cname.size(); + + data[offset++] = 6; // TOOL + + AString tool = "stagefright/1.0"; + data[offset++] = tool.size(); + + memcpy(&data[offset], tool.c_str(), tool.size()); + offset += tool.size(); + + data[offset++] = 0; + + if ((offset % 4) > 0) { + size_t count = 4 - (offset % 4); + switch (count) { + case 3: + data[offset++] = 0; + case 2: + data[offset++] = 0; + case 1: + data[offset++] = 0; + } + } + + size_t numWords = (offset / 4) - 1; + data[2] = numWords >> 8; + data[3] = numWords & 0xff; + + buffer->setRange(buffer->offset(), buffer->size() + offset); +} + +void RTPReceiver::scheduleSendRR() { + (new AMessage(kWhatSendRR, id()))->post(5000000ll); +} + +void RTPReceiver::onSendRR() { + sp<ABuffer> buf = new ABuffer(kMaxUDPPacketSize); + buf->setRange(0, 0); + + uint8_t *ptr = buf->data(); + ptr[0] = 0x80 | 0; + ptr[1] = 201; // RR + ptr[2] = 0; + ptr[3] = 1; + ptr[4] = kSourceID >> 24; // SSRC + ptr[5] = (kSourceID >> 16) & 0xff; + ptr[6] = (kSourceID >> 8) & 0xff; + ptr[7] = kSourceID & 0xff; + + buf->setRange(0, 8); + + size_t numReportBlocks = 0; + for (size_t i = 0; i < mSources.size(); ++i) { + uint32_t ssrc = mSources.keyAt(i); + sp<Source> source = mSources.valueAt(i); + + if (numReportBlocks > 31 || buf->size() + 24 > buf->capacity()) { + // Cannot fit another report block. + break; + } + + source->addReportBlock(ssrc, buf); + ++numReportBlocks; + } + + ptr[0] |= numReportBlocks; // 5 bit + + size_t sizeInWordsMinus1 = 1 + 6 * numReportBlocks; + ptr[2] = sizeInWordsMinus1 >> 8; + ptr[3] = sizeInWordsMinus1 & 0xff; + + buf->setRange(0, (sizeInWordsMinus1 + 1) * 4); + + addSDES(buf); + + mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size()); + + scheduleSendRR(); +} + +status_t RTPReceiver::registerPacketType( + uint8_t packetType, PacketizationMode mode) { + mPacketTypes.add(packetType, mode); + + return OK; +} + +sp<RTPReceiver::Assembler> RTPReceiver::makeAssembler(uint8_t packetType) { + ssize_t index = mPacketTypes.indexOfKey(packetType); + if (index < 0) { + return NULL; + } + + PacketizationMode mode = mPacketTypes.valueAt(index); + + switch (mode) { + case PACKETIZATION_NONE: + case PACKETIZATION_TRANSPORT_STREAM: + return new TSAssembler(mNotify); + + case PACKETIZATION_H264: + return new H264Assembler(mNotify); + + default: + return NULL; + } +} + +void RTPReceiver::requestRetransmission(uint32_t senderSSRC, int32_t extSeqNo) { + int32_t blp = 0; + + sp<ABuffer> buf = new ABuffer(16); + buf->setRange(0, 0); + + uint8_t *ptr = buf->data(); + ptr[0] = 0x80 | 1; // generic NACK + ptr[1] = 205; // TSFB + ptr[2] = 0; + ptr[3] = 3; + ptr[8] = (senderSSRC >> 24) & 0xff; + ptr[9] = (senderSSRC >> 16) & 0xff; + ptr[10] = (senderSSRC >> 8) & 0xff; + ptr[11] = (senderSSRC & 0xff); + ptr[8] = (kSourceID >> 24) & 0xff; + ptr[9] = (kSourceID >> 16) & 0xff; + ptr[10] = (kSourceID >> 8) & 0xff; + ptr[11] = (kSourceID & 0xff); + ptr[12] = (extSeqNo >> 8) & 0xff; + ptr[13] = (extSeqNo & 0xff); + ptr[14] = (blp >> 8) & 0xff; + ptr[15] = (blp & 0xff); + + buf->setRange(0, 16); + + mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size()); +} + +void RTPReceiver::Source::modifyPacketStatus(int32_t extSeqNo, uint32_t mask) { +#if TRACK_PACKET_LOSS + ssize_t index = mLostPackets.indexOfKey(extSeqNo); + if (index < 0) { + mLostPackets.add(extSeqNo, mask); + } else { + mLostPackets.editValueAt(index) |= mask; + } +#endif +} + +void RTPReceiver::Source::postRetransmitTimer(int64_t timeUs) { + int64_t delayUs = timeUs - ALooper::GetNowUs(); + sp<AMessage> msg = new AMessage(kWhatRetransmit, id()); + msg->setInt32("generation", mRetransmitGeneration); + msg->post(delayUs); +} + +void RTPReceiver::Source::postDeclareLostTimer(int64_t timeUs) { + CHECK(!mDeclareLostTimerPending); + mDeclareLostTimerPending = true; + + int64_t delayUs = timeUs - ALooper::GetNowUs(); + sp<AMessage> msg = new AMessage(kWhatDeclareLost, id()); + msg->setInt32("generation", mDeclareLostGeneration); + msg->post(delayUs); +} + +void RTPReceiver::Source::cancelTimers() { + ++mRetransmitGeneration; + ++mDeclareLostGeneration; + mDeclareLostTimerPending = false; +} + +} // namespace android + diff --git a/media/libstagefright/wifi-display/rtp/RTPReceiver.h b/media/libstagefright/wifi-display/rtp/RTPReceiver.h new file mode 100644 index 0000000..240ab2e --- /dev/null +++ b/media/libstagefright/wifi-display/rtp/RTPReceiver.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 RTP_RECEIVER_H_ + +#define RTP_RECEIVER_H_ + +#include "RTPBase.h" + +#include <media/stagefright/foundation/AHandler.h> + +namespace android { + +struct ABuffer; +struct ANetworkSession; + +// An object of this class facilitates receiving of media data on 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 RTPReceiver : public RTPBase, public AHandler { + enum { + kWhatInitDone, + kWhatError, + kWhatAccessUnit, + kWhatPacketLost, + }; + + enum Flags { + FLAG_AUTO_CONNECT = 1, + }; + RTPReceiver( + const sp<ANetworkSession> &netSession, + const sp<AMessage> ¬ify, + uint32_t flags = 0); + + status_t registerPacketType( + uint8_t packetType, PacketizationMode mode); + + status_t initAsync( + TransportMode rtpMode, + TransportMode rtcpMode, + int32_t *outLocalRTPPort); + + status_t connect( + const char *remoteHost, + int32_t remoteRTPPort, + int32_t remoteRTCPPort); + + status_t informSender(const sp<AMessage> ¶ms); + +protected: + virtual ~RTPReceiver(); + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatRTPNotify, + kWhatRTCPNotify, + kWhatSendRR, + }; + + enum { + kSourceID = 0xdeadbeef, + kPacketLostAfterUs = 100000, + kRequestRetransmissionAfterUs = -1, + }; + + struct Assembler; + struct H264Assembler; + struct Source; + struct TSAssembler; + + sp<ANetworkSession> mNetSession; + sp<AMessage> mNotify; + uint32_t mFlags; + TransportMode mRTPMode; + TransportMode mRTCPMode; + int32_t mRTPSessionID; + int32_t mRTCPSessionID; + bool mRTPConnected; + bool mRTCPConnected; + + int32_t mRTPClientSessionID; // in TRANSPORT_TCP mode. + int32_t mRTCPClientSessionID; // in TRANSPORT_TCP mode. + + KeyedVector<uint8_t, PacketizationMode> mPacketTypes; + KeyedVector<uint32_t, sp<Source> > mSources; + + void onNetNotify(bool isRTP, const sp<AMessage> &msg); + status_t onRTPData(const sp<ABuffer> &data); + status_t onRTCPData(const sp<ABuffer> &data); + void onSendRR(); + + void scheduleSendRR(); + void addSDES(const sp<ABuffer> &buffer); + + void notifyInitDone(status_t err); + void notifyError(status_t err); + void notifyPacketLost(); + + sp<Assembler> makeAssembler(uint8_t packetType); + + void requestRetransmission(uint32_t senderSSRC, int32_t extSeqNo); + + DISALLOW_EVIL_CONSTRUCTORS(RTPReceiver); +}; + +} // namespace android + +#endif // RTP_RECEIVER_H_ diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.cpp b/media/libstagefright/wifi-display/rtp/RTPSender.cpp index 095fd97..6bbe650 100644 --- a/media/libstagefright/wifi-display/rtp/RTPSender.cpp +++ b/media/libstagefright/wifi-display/rtp/RTPSender.cpp @@ -767,6 +767,17 @@ status_t RTPSender::parseTSFB(const uint8_t *data, size_t size) { } status_t RTPSender::parseAPP(const uint8_t *data, size_t size) { + if (!memcmp("late", &data[8], 4)) { + int64_t avgLatencyUs = (int64_t)U64_AT(&data[12]); + int64_t maxLatencyUs = (int64_t)U64_AT(&data[20]); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatInformSender); + notify->setInt64("avgLatencyUs", avgLatencyUs); + notify->setInt64("maxLatencyUs", maxLatencyUs); + notify->post(); + } + return OK; } diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.h b/media/libstagefright/wifi-display/rtp/RTPSender.h index 7dc138a..fefcab7 100644 --- a/media/libstagefright/wifi-display/rtp/RTPSender.h +++ b/media/libstagefright/wifi-display/rtp/RTPSender.h @@ -37,6 +37,7 @@ struct RTPSender : public RTPBase, public AHandler { kWhatInitDone, kWhatError, kWhatNetworkStall, + kWhatInformSender, }; RTPSender( const sp<ANetworkSession> &netSession, diff --git a/media/libstagefright/wifi-display/rtptest.cpp b/media/libstagefright/wifi-display/rtptest.cpp new file mode 100644 index 0000000..764a38b --- /dev/null +++ b/media/libstagefright/wifi-display/rtptest.cpp @@ -0,0 +1,565 @@ +/* + * 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_NEBUG 0 +#define LOG_TAG "rtptest" +#include <utils/Log.h> + +#include "ANetworkSession.h" +#include "rtp/RTPSender.h" +#include "rtp/RTPReceiver.h" +#include "TimeSyncer.h" + +#include <binder/ProcessState.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AHandler.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/NuMediaExtractor.h> +#include <media/stagefright/Utils.h> + +#define MEDIA_FILENAME "/sdcard/Frame Counter HD 30FPS_1080p.mp4" + +namespace android { + +struct PacketSource : public RefBase { + PacketSource() {} + + virtual sp<ABuffer> getNextAccessUnit() = 0; + +protected: + virtual ~PacketSource() {} + +private: + DISALLOW_EVIL_CONSTRUCTORS(PacketSource); +}; + +struct MediaPacketSource : public PacketSource { + MediaPacketSource() + : mMaxSampleSize(1024 * 1024) { + mExtractor = new NuMediaExtractor; + CHECK_EQ((status_t)OK, + mExtractor->setDataSource(MEDIA_FILENAME)); + + bool haveVideo = false; + for (size_t i = 0; i < mExtractor->countTracks(); ++i) { + sp<AMessage> format; + CHECK_EQ((status_t)OK, mExtractor->getTrackFormat(i, &format)); + + AString mime; + CHECK(format->findString("mime", &mime)); + + if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime.c_str())) { + mExtractor->selectTrack(i); + haveVideo = true; + break; + } + } + + CHECK(haveVideo); + } + + virtual sp<ABuffer> getNextAccessUnit() { + int64_t timeUs; + status_t err = mExtractor->getSampleTime(&timeUs); + + if (err != OK) { + return NULL; + } + + sp<ABuffer> accessUnit = new ABuffer(mMaxSampleSize); + CHECK_EQ((status_t)OK, mExtractor->readSampleData(accessUnit)); + + accessUnit->meta()->setInt64("timeUs", timeUs); + + CHECK_EQ((status_t)OK, mExtractor->advance()); + + return accessUnit; + } + +protected: + virtual ~MediaPacketSource() { + } + +private: + sp<NuMediaExtractor> mExtractor; + size_t mMaxSampleSize; + + DISALLOW_EVIL_CONSTRUCTORS(MediaPacketSource); +}; + +struct SimplePacketSource : public PacketSource { + SimplePacketSource() + : mCounter(0) { + } + + virtual sp<ABuffer> getNextAccessUnit() { + sp<ABuffer> buffer = new ABuffer(4); + uint8_t *dst = buffer->data(); + dst[0] = mCounter >> 24; + dst[1] = (mCounter >> 16) & 0xff; + dst[2] = (mCounter >> 8) & 0xff; + dst[3] = mCounter & 0xff; + + buffer->meta()->setInt64("timeUs", mCounter * 1000000ll / kFrameRate); + + ++mCounter; + + return buffer; + } + +protected: + virtual ~SimplePacketSource() { + } + +private: + enum { + kFrameRate = 30 + }; + + uint32_t mCounter; + + DISALLOW_EVIL_CONSTRUCTORS(SimplePacketSource); +}; + +struct TestHandler : public AHandler { + TestHandler(const sp<ANetworkSession> &netSession); + + void listen(); + void connect(const char *host, int32_t port); + +protected: + virtual ~TestHandler(); + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatListen, + kWhatConnect, + kWhatReceiverNotify, + kWhatSenderNotify, + kWhatSendMore, + kWhatStop, + kWhatTimeSyncerNotify, + }; + +#if 1 + static const RTPBase::TransportMode kRTPMode = RTPBase::TRANSPORT_UDP; + static const RTPBase::TransportMode kRTCPMode = RTPBase::TRANSPORT_UDP; +#else + static const RTPBase::TransportMode kRTPMode = RTPBase::TRANSPORT_TCP; + static const RTPBase::TransportMode kRTCPMode = RTPBase::TRANSPORT_NONE; +#endif + +#if 1 + static const RTPBase::PacketizationMode kPacketizationMode + = RTPBase::PACKETIZATION_H264; +#else + static const RTPBase::PacketizationMode kPacketizationMode + = RTPBase::PACKETIZATION_NONE; +#endif + + sp<ANetworkSession> mNetSession; + sp<PacketSource> mSource; + sp<RTPSender> mSender; + sp<RTPReceiver> mReceiver; + + sp<TimeSyncer> mTimeSyncer; + bool mTimeSyncerStarted; + + int64_t mFirstTimeRealUs; + int64_t mFirstTimeMediaUs; + + int64_t mTimeOffsetUs; + bool mTimeOffsetValid; + + status_t readMore(); + + DISALLOW_EVIL_CONSTRUCTORS(TestHandler); +}; + +TestHandler::TestHandler(const sp<ANetworkSession> &netSession) + : mNetSession(netSession), + mTimeSyncerStarted(false), + mFirstTimeRealUs(-1ll), + mFirstTimeMediaUs(-1ll), + mTimeOffsetUs(-1ll), + mTimeOffsetValid(false) { +} + +TestHandler::~TestHandler() { +} + +void TestHandler::listen() { + sp<AMessage> msg = new AMessage(kWhatListen, id()); + msg->post(); +} + +void TestHandler::connect(const char *host, int32_t port) { + sp<AMessage> msg = new AMessage(kWhatConnect, id()); + msg->setString("host", host); + msg->setInt32("port", port); + msg->post(); +} + +static void dumpDelay(int64_t delayMs) { + static const int64_t kMinDelayMs = 0; + static const int64_t kMaxDelayMs = 300; + + const char *kPattern = "########################################"; + size_t kPatternSize = strlen(kPattern); + + int n = (kPatternSize * (delayMs - kMinDelayMs)) + / (kMaxDelayMs - kMinDelayMs); + + if (n < 0) { + n = 0; + } else if ((size_t)n > kPatternSize) { + n = kPatternSize; + } + + ALOGI("(%4lld ms) %s\n", + delayMs, + kPattern + kPatternSize - n); +} + +void TestHandler::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatListen: + { + sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id()); + mTimeSyncer = new TimeSyncer(mNetSession, notify); + looper()->registerHandler(mTimeSyncer); + + notify = new AMessage(kWhatReceiverNotify, id()); + mReceiver = new RTPReceiver( + mNetSession, notify, RTPReceiver::FLAG_AUTO_CONNECT); + looper()->registerHandler(mReceiver); + + CHECK_EQ((status_t)OK, + mReceiver->registerPacketType(33, kPacketizationMode)); + + int32_t receiverRTPPort; + CHECK_EQ((status_t)OK, + mReceiver->initAsync( + kRTPMode, + kRTCPMode, + &receiverRTPPort)); + + printf("picked receiverRTPPort %d\n", receiverRTPPort); + +#if 0 + CHECK_EQ((status_t)OK, + mReceiver->connect( + "127.0.0.1", senderRTPPort, senderRTPPort + 1)); +#endif + break; + } + + case kWhatConnect: + { + AString host; + CHECK(msg->findString("host", &host)); + + sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id()); + mTimeSyncer = new TimeSyncer(mNetSession, notify); + looper()->registerHandler(mTimeSyncer); + mTimeSyncer->startServer(8123); + + int32_t receiverRTPPort; + CHECK(msg->findInt32("port", &receiverRTPPort)); + +#if 1 + mSource = new MediaPacketSource; +#else + mSource = new SimplePacketSource; +#endif + + notify = new AMessage(kWhatSenderNotify, id()); + mSender = new RTPSender(mNetSession, notify); + + looper()->registerHandler(mSender); + + int32_t senderRTPPort; + CHECK_EQ((status_t)OK, + mSender->initAsync( + host.c_str(), + receiverRTPPort, + kRTPMode, + kRTCPMode == RTPBase::TRANSPORT_NONE + ? -1 : receiverRTPPort + 1, + kRTCPMode, + &senderRTPPort)); + + printf("picked senderRTPPort %d\n", senderRTPPort); + break; + } + + case kWhatSenderNotify: + { + ALOGI("kWhatSenderNotify"); + + int32_t what; + CHECK(msg->findInt32("what", &what)); + + switch (what) { + case RTPSender::kWhatInitDone: + { + int32_t err; + CHECK(msg->findInt32("err", &err)); + + ALOGI("RTPSender::initAsync completed w/ err %d", err); + + if (err == OK) { + err = readMore(); + + if (err != OK) { + (new AMessage(kWhatStop, id()))->post(); + } + } + break; + } + + case RTPSender::kWhatError: + break; + } + break; + } + + case kWhatReceiverNotify: + { + ALOGV("kWhatReceiverNotify"); + + int32_t what; + CHECK(msg->findInt32("what", &what)); + + switch (what) { + case RTPReceiver::kWhatInitDone: + { + int32_t err; + CHECK(msg->findInt32("err", &err)); + + ALOGI("RTPReceiver::initAsync completed w/ err %d", err); + break; + } + + case RTPReceiver::kWhatError: + break; + + case RTPReceiver::kWhatAccessUnit: + { +#if 0 + if (!mTimeSyncerStarted) { + mTimeSyncer->startClient("172.18.41.216", 8123); + mTimeSyncerStarted = true; + } + + sp<ABuffer> accessUnit; + CHECK(msg->findBuffer("accessUnit", &accessUnit)); + + int64_t timeUs; + CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs)); + + if (mTimeOffsetValid) { + timeUs -= mTimeOffsetUs; + int64_t nowUs = ALooper::GetNowUs(); + int64_t delayMs = (nowUs - timeUs) / 1000ll; + + dumpDelay(delayMs); + } +#endif + break; + } + + case RTPReceiver::kWhatPacketLost: + ALOGV("kWhatPacketLost"); + break; + + default: + TRESPASS(); + } + break; + } + + case kWhatSendMore: + { + sp<ABuffer> accessUnit; + CHECK(msg->findBuffer("accessUnit", &accessUnit)); + + CHECK_EQ((status_t)OK, + mSender->queueBuffer( + accessUnit, + 33, + kPacketizationMode)); + + status_t err = readMore(); + + if (err != OK) { + (new AMessage(kWhatStop, id()))->post(); + } + break; + } + + case kWhatStop: + { + if (mReceiver != NULL) { + looper()->unregisterHandler(mReceiver->id()); + mReceiver.clear(); + } + + if (mSender != NULL) { + looper()->unregisterHandler(mSender->id()); + mSender.clear(); + } + + mSource.clear(); + + looper()->stop(); + break; + } + + case kWhatTimeSyncerNotify: + { + CHECK(msg->findInt64("offset", &mTimeOffsetUs)); + mTimeOffsetValid = true; + break; + } + + default: + TRESPASS(); + } +} + +status_t TestHandler::readMore() { + sp<ABuffer> accessUnit = mSource->getNextAccessUnit(); + + if (accessUnit == NULL) { + return ERROR_END_OF_STREAM; + } + + int64_t timeUs; + CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs)); + + int64_t nowUs = ALooper::GetNowUs(); + int64_t whenUs; + + if (mFirstTimeRealUs < 0ll) { + mFirstTimeRealUs = whenUs = nowUs; + mFirstTimeMediaUs = timeUs; + } else { + whenUs = mFirstTimeRealUs + timeUs - mFirstTimeMediaUs; + } + + accessUnit->meta()->setInt64("timeUs", whenUs); + + sp<AMessage> msg = new AMessage(kWhatSendMore, id()); + msg->setBuffer("accessUnit", accessUnit); + msg->post(whenUs - nowUs); + + return OK; +} + +} // namespace android + +static void usage(const char *me) { + fprintf(stderr, + "usage: %s -c host:port\tconnect to remote host\n" + " -l \tlisten\n", + me); +} + +int main(int argc, char **argv) { + using namespace android; + + // srand(time(NULL)); + + ProcessState::self()->startThreadPool(); + + DataSource::RegisterDefaultSniffers(); + + bool listen = false; + int32_t connectToPort = -1; + AString connectToHost; + + int res; + while ((res = getopt(argc, argv, "hc:l")) >= 0) { + switch (res) { + case 'c': + { + const char *colonPos = strrchr(optarg, ':'); + + if (colonPos == NULL) { + usage(argv[0]); + exit(1); + } + + connectToHost.setTo(optarg, colonPos - optarg); + + char *end; + connectToPort = strtol(colonPos + 1, &end, 10); + + if (*end != '\0' || end == colonPos + 1 + || connectToPort < 1 || connectToPort > 65535) { + fprintf(stderr, "Illegal port specified.\n"); + exit(1); + } + break; + } + + case 'l': + { + listen = true; + break; + } + + case '?': + case 'h': + usage(argv[0]); + exit(1); + } + } + + if (!listen && connectToPort < 0) { + fprintf(stderr, + "You need to select either client or server mode.\n"); + exit(1); + } + + sp<ANetworkSession> netSession = new ANetworkSession; + netSession->start(); + + sp<ALooper> looper = new ALooper; + + sp<TestHandler> handler = new TestHandler(netSession); + looper->registerHandler(handler); + + if (listen) { + handler->listen(); + } + + if (connectToPort >= 0) { + handler->connect(connectToHost.c_str(), connectToPort); + } + + looper->start(true /* runOnCallingThread */); + + return 0; +} + diff --git a/media/libstagefright/wifi-display/sink/DirectRenderer.cpp b/media/libstagefright/wifi-display/sink/DirectRenderer.cpp new file mode 100644 index 0000000..15f9c88 --- /dev/null +++ b/media/libstagefright/wifi-display/sink/DirectRenderer.cpp @@ -0,0 +1,625 @@ +/* + * 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 "DirectRenderer" +#include <utils/Log.h> + +#include "DirectRenderer.h" + +#include <gui/SurfaceComposerClient.h> +#include <gui/Surface.h> +#include <media/AudioTrack.h> +#include <media/ICrypto.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaCodec.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +namespace android { + +/* + Drives the decoding process using a MediaCodec instance. Input buffers + queued by calls to "queueInputBuffer" are fed to the decoder as soon + as the decoder is ready for them, the client is notified about output + buffers as the decoder spits them out. +*/ +struct DirectRenderer::DecoderContext : public AHandler { + enum { + kWhatOutputBufferReady, + }; + DecoderContext(const sp<AMessage> ¬ify); + + status_t init( + const sp<AMessage> &format, + const sp<IGraphicBufferProducer> &surfaceTex); + + void queueInputBuffer(const sp<ABuffer> &accessUnit); + + status_t renderOutputBufferAndRelease(size_t index); + status_t releaseOutputBuffer(size_t index); + +protected: + virtual ~DecoderContext(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatDecoderNotify, + }; + + sp<AMessage> mNotify; + sp<ALooper> mDecoderLooper; + sp<MediaCodec> mDecoder; + Vector<sp<ABuffer> > mDecoderInputBuffers; + Vector<sp<ABuffer> > mDecoderOutputBuffers; + List<size_t> mDecoderInputBuffersAvailable; + bool mDecoderNotificationPending; + + List<sp<ABuffer> > mAccessUnits; + + void onDecoderNotify(); + void scheduleDecoderNotification(); + void queueDecoderInputBuffers(); + + void queueOutputBuffer( + size_t index, int64_t timeUs, const sp<ABuffer> &buffer); + + DISALLOW_EVIL_CONSTRUCTORS(DecoderContext); +}; + +//////////////////////////////////////////////////////////////////////////////// + +/* + A "push" audio renderer. The primary function of this renderer is to use + an AudioTrack in push mode and making sure not to block the event loop + be ensuring that calls to AudioTrack::write never block. This is done by + estimating an upper bound of data that can be written to the AudioTrack + buffer without delay. +*/ +struct DirectRenderer::AudioRenderer : public AHandler { + AudioRenderer(const sp<DecoderContext> &decoderContext); + + void queueInputBuffer( + size_t index, int64_t timeUs, const sp<ABuffer> &buffer); + +protected: + virtual ~AudioRenderer(); + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatPushAudio, + }; + + struct BufferInfo { + size_t mIndex; + int64_t mTimeUs; + sp<ABuffer> mBuffer; + }; + + sp<DecoderContext> mDecoderContext; + sp<AudioTrack> mAudioTrack; + + List<BufferInfo> mInputBuffers; + bool mPushPending; + + size_t mNumFramesWritten; + + void schedulePushIfNecessary(); + void onPushAudio(); + + ssize_t writeNonBlocking(const uint8_t *data, size_t size); + + DISALLOW_EVIL_CONSTRUCTORS(AudioRenderer); +}; + +//////////////////////////////////////////////////////////////////////////////// + +DirectRenderer::DecoderContext::DecoderContext(const sp<AMessage> ¬ify) + : mNotify(notify), + mDecoderNotificationPending(false) { +} + +DirectRenderer::DecoderContext::~DecoderContext() { + if (mDecoder != NULL) { + mDecoder->release(); + mDecoder.clear(); + + mDecoderLooper->stop(); + mDecoderLooper.clear(); + } +} + +status_t DirectRenderer::DecoderContext::init( + const sp<AMessage> &format, + const sp<IGraphicBufferProducer> &surfaceTex) { + CHECK(mDecoder == NULL); + + AString mime; + CHECK(format->findString("mime", &mime)); + + mDecoderLooper = new ALooper; + mDecoderLooper->setName("video codec looper"); + + mDecoderLooper->start( + false /* runOnCallingThread */, + false /* canCallJava */, + PRIORITY_DEFAULT); + + mDecoder = MediaCodec::CreateByType( + mDecoderLooper, mime.c_str(), false /* encoder */); + + CHECK(mDecoder != NULL); + + status_t err = mDecoder->configure( + format, + surfaceTex == NULL + ? NULL : new Surface(surfaceTex), + NULL /* crypto */, + 0 /* flags */); + CHECK_EQ(err, (status_t)OK); + + err = mDecoder->start(); + CHECK_EQ(err, (status_t)OK); + + err = mDecoder->getInputBuffers( + &mDecoderInputBuffers); + CHECK_EQ(err, (status_t)OK); + + err = mDecoder->getOutputBuffers( + &mDecoderOutputBuffers); + CHECK_EQ(err, (status_t)OK); + + scheduleDecoderNotification(); + + return OK; +} + +void DirectRenderer::DecoderContext::queueInputBuffer( + const sp<ABuffer> &accessUnit) { + CHECK(mDecoder != NULL); + + mAccessUnits.push_back(accessUnit); + queueDecoderInputBuffers(); +} + +status_t DirectRenderer::DecoderContext::renderOutputBufferAndRelease( + size_t index) { + return mDecoder->renderOutputBufferAndRelease(index); +} + +status_t DirectRenderer::DecoderContext::releaseOutputBuffer(size_t index) { + return mDecoder->releaseOutputBuffer(index); +} + +void DirectRenderer::DecoderContext::queueDecoderInputBuffers() { + if (mDecoder == NULL) { + return; + } + + bool submittedMore = false; + + while (!mAccessUnits.empty() + && !mDecoderInputBuffersAvailable.empty()) { + size_t index = *mDecoderInputBuffersAvailable.begin(); + + mDecoderInputBuffersAvailable.erase( + mDecoderInputBuffersAvailable.begin()); + + sp<ABuffer> srcBuffer = *mAccessUnits.begin(); + mAccessUnits.erase(mAccessUnits.begin()); + + const sp<ABuffer> &dstBuffer = + mDecoderInputBuffers.itemAt(index); + + memcpy(dstBuffer->data(), srcBuffer->data(), srcBuffer->size()); + + int64_t timeUs; + CHECK(srcBuffer->meta()->findInt64("timeUs", &timeUs)); + + status_t err = mDecoder->queueInputBuffer( + index, + 0 /* offset */, + srcBuffer->size(), + timeUs, + 0 /* flags */); + CHECK_EQ(err, (status_t)OK); + + submittedMore = true; + } + + if (submittedMore) { + scheduleDecoderNotification(); + } +} + +void DirectRenderer::DecoderContext::onMessageReceived( + const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatDecoderNotify: + { + onDecoderNotify(); + break; + } + + default: + TRESPASS(); + } +} + +void DirectRenderer::DecoderContext::onDecoderNotify() { + mDecoderNotificationPending = false; + + for (;;) { + size_t index; + status_t err = mDecoder->dequeueInputBuffer(&index); + + if (err == OK) { + mDecoderInputBuffersAvailable.push_back(index); + } else if (err == -EAGAIN) { + break; + } else { + TRESPASS(); + } + } + + queueDecoderInputBuffers(); + + for (;;) { + size_t index; + size_t offset; + size_t size; + int64_t timeUs; + uint32_t flags; + status_t err = mDecoder->dequeueOutputBuffer( + &index, + &offset, + &size, + &timeUs, + &flags); + + if (err == OK) { + queueOutputBuffer( + index, timeUs, mDecoderOutputBuffers.itemAt(index)); + } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) { + err = mDecoder->getOutputBuffers( + &mDecoderOutputBuffers); + CHECK_EQ(err, (status_t)OK); + } else if (err == INFO_FORMAT_CHANGED) { + // We don't care. + } else if (err == -EAGAIN) { + break; + } else { + TRESPASS(); + } + } + + scheduleDecoderNotification(); +} + +void DirectRenderer::DecoderContext::scheduleDecoderNotification() { + if (mDecoderNotificationPending) { + return; + } + + sp<AMessage> notify = + new AMessage(kWhatDecoderNotify, id()); + + mDecoder->requestActivityNotification(notify); + mDecoderNotificationPending = true; +} + +void DirectRenderer::DecoderContext::queueOutputBuffer( + size_t index, int64_t timeUs, const sp<ABuffer> &buffer) { + sp<AMessage> msg = mNotify->dup(); + msg->setInt32("what", kWhatOutputBufferReady); + msg->setSize("index", index); + msg->setInt64("timeUs", timeUs); + msg->setBuffer("buffer", buffer); + msg->post(); +} + +//////////////////////////////////////////////////////////////////////////////// + +DirectRenderer::AudioRenderer::AudioRenderer( + const sp<DecoderContext> &decoderContext) + : mDecoderContext(decoderContext), + mPushPending(false), + mNumFramesWritten(0) { + mAudioTrack = new AudioTrack( + AUDIO_STREAM_DEFAULT, + 48000.0f, + AUDIO_FORMAT_PCM, + AUDIO_CHANNEL_OUT_STEREO, + (int)0 /* frameCount */); + + CHECK_EQ((status_t)OK, mAudioTrack->initCheck()); + + mAudioTrack->start(); +} + +DirectRenderer::AudioRenderer::~AudioRenderer() { +} + +void DirectRenderer::AudioRenderer::queueInputBuffer( + size_t index, int64_t timeUs, const sp<ABuffer> &buffer) { + BufferInfo info; + info.mIndex = index; + info.mTimeUs = timeUs; + info.mBuffer = buffer; + + mInputBuffers.push_back(info); + schedulePushIfNecessary(); +} + +void DirectRenderer::AudioRenderer::onMessageReceived( + const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatPushAudio: + { + onPushAudio(); + break; + } + + default: + break; + } +} + +void DirectRenderer::AudioRenderer::schedulePushIfNecessary() { + if (mPushPending || mInputBuffers.empty()) { + return; + } + + mPushPending = true; + + uint32_t numFramesPlayed; + CHECK_EQ(mAudioTrack->getPosition(&numFramesPlayed), + (status_t)OK); + + uint32_t numFramesPendingPlayout = mNumFramesWritten - numFramesPlayed; + + // This is how long the audio sink will have data to + // play back. + const float msecsPerFrame = 1000.0f / mAudioTrack->getSampleRate(); + + int64_t delayUs = + msecsPerFrame * numFramesPendingPlayout * 1000ll; + + // Let's give it more data after about half that time + // has elapsed. + (new AMessage(kWhatPushAudio, id()))->post(delayUs / 2); +} + +void DirectRenderer::AudioRenderer::onPushAudio() { + mPushPending = false; + + while (!mInputBuffers.empty()) { + const BufferInfo &info = *mInputBuffers.begin(); + + ssize_t n = writeNonBlocking( + info.mBuffer->data(), info.mBuffer->size()); + + if (n < (ssize_t)info.mBuffer->size()) { + CHECK_GE(n, 0); + + info.mBuffer->setRange( + info.mBuffer->offset() + n, info.mBuffer->size() - n); + break; + } + + mDecoderContext->releaseOutputBuffer(info.mIndex); + + mInputBuffers.erase(mInputBuffers.begin()); + } + + schedulePushIfNecessary(); +} + +ssize_t DirectRenderer::AudioRenderer::writeNonBlocking( + const uint8_t *data, size_t size) { + uint32_t numFramesPlayed; + status_t err = mAudioTrack->getPosition(&numFramesPlayed); + if (err != OK) { + return err; + } + + ssize_t numFramesAvailableToWrite = + mAudioTrack->frameCount() - (mNumFramesWritten - numFramesPlayed); + + size_t numBytesAvailableToWrite = + numFramesAvailableToWrite * mAudioTrack->frameSize(); + + if (size > numBytesAvailableToWrite) { + size = numBytesAvailableToWrite; + } + + CHECK_EQ(mAudioTrack->write(data, size), (ssize_t)size); + + size_t numFramesWritten = size / mAudioTrack->frameSize(); + mNumFramesWritten += numFramesWritten; + + return size; +} + +//////////////////////////////////////////////////////////////////////////////// + +DirectRenderer::DirectRenderer( + const sp<IGraphicBufferProducer> &bufferProducer) + : mSurfaceTex(bufferProducer), + mVideoRenderPending(false), + mNumFramesLate(0), + mNumFrames(0) { +} + +DirectRenderer::~DirectRenderer() { +} + +void DirectRenderer::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatDecoderNotify: + { + onDecoderNotify(msg); + break; + } + + case kWhatRenderVideo: + { + onRenderVideo(); + break; + } + + default: + TRESPASS(); + } +} + +void DirectRenderer::setFormat(size_t trackIndex, const sp<AMessage> &format) { + CHECK_LT(trackIndex, 2u); + + CHECK(mDecoderContext[trackIndex] == NULL); + + sp<AMessage> notify = new AMessage(kWhatDecoderNotify, id()); + notify->setSize("trackIndex", trackIndex); + + mDecoderContext[trackIndex] = new DecoderContext(notify); + looper()->registerHandler(mDecoderContext[trackIndex]); + + CHECK_EQ((status_t)OK, + mDecoderContext[trackIndex]->init( + format, trackIndex == 0 ? mSurfaceTex : NULL)); + + if (trackIndex == 1) { + // Audio + mAudioRenderer = new AudioRenderer(mDecoderContext[1]); + looper()->registerHandler(mAudioRenderer); + } +} + +void DirectRenderer::queueAccessUnit( + size_t trackIndex, const sp<ABuffer> &accessUnit) { + CHECK_LT(trackIndex, 2u); + + if (mDecoderContext[trackIndex] == NULL) { + CHECK_EQ(trackIndex, 0u); + + sp<AMessage> format = new AMessage; + format->setString("mime", "video/avc"); + format->setInt32("width", 640); + format->setInt32("height", 360); + + setFormat(trackIndex, format); + } + + mDecoderContext[trackIndex]->queueInputBuffer(accessUnit); +} + +void DirectRenderer::onDecoderNotify(const sp<AMessage> &msg) { + size_t trackIndex; + CHECK(msg->findSize("trackIndex", &trackIndex)); + + int32_t what; + CHECK(msg->findInt32("what", &what)); + + switch (what) { + case DecoderContext::kWhatOutputBufferReady: + { + size_t index; + CHECK(msg->findSize("index", &index)); + + int64_t timeUs; + CHECK(msg->findInt64("timeUs", &timeUs)); + + sp<ABuffer> buffer; + CHECK(msg->findBuffer("buffer", &buffer)); + + queueOutputBuffer(trackIndex, index, timeUs, buffer); + break; + } + + default: + TRESPASS(); + } +} + +void DirectRenderer::queueOutputBuffer( + size_t trackIndex, + size_t index, int64_t timeUs, const sp<ABuffer> &buffer) { + if (trackIndex == 1) { + // Audio + mAudioRenderer->queueInputBuffer(index, timeUs, buffer); + return; + } + + OutputInfo info; + info.mIndex = index; + info.mTimeUs = timeUs; + info.mBuffer = buffer; + mVideoOutputBuffers.push_back(info); + + scheduleVideoRenderIfNecessary(); +} + +void DirectRenderer::scheduleVideoRenderIfNecessary() { + if (mVideoRenderPending || mVideoOutputBuffers.empty()) { + return; + } + + mVideoRenderPending = true; + + int64_t timeUs = (*mVideoOutputBuffers.begin()).mTimeUs; + int64_t nowUs = ALooper::GetNowUs(); + + int64_t delayUs = timeUs - nowUs; + + (new AMessage(kWhatRenderVideo, id()))->post(delayUs); +} + +void DirectRenderer::onRenderVideo() { + mVideoRenderPending = false; + + int64_t nowUs = ALooper::GetNowUs(); + + while (!mVideoOutputBuffers.empty()) { + const OutputInfo &info = *mVideoOutputBuffers.begin(); + + if (info.mTimeUs > nowUs) { + break; + } + + if (info.mTimeUs + 15000ll < nowUs) { + ++mNumFramesLate; + } + ++mNumFrames; + + status_t err = + mDecoderContext[0]->renderOutputBufferAndRelease(info.mIndex); + CHECK_EQ(err, (status_t)OK); + + mVideoOutputBuffers.erase(mVideoOutputBuffers.begin()); + } + + scheduleVideoRenderIfNecessary(); +} + +} // namespace android + diff --git a/media/libstagefright/wifi-display/sink/DirectRenderer.h b/media/libstagefright/wifi-display/sink/DirectRenderer.h new file mode 100644 index 0000000..c5a4a83 --- /dev/null +++ b/media/libstagefright/wifi-display/sink/DirectRenderer.h @@ -0,0 +1,82 @@ +/* + * 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 DIRECT_RENDERER_H_ + +#define DIRECT_RENDERER_H_ + +#include <media/stagefright/foundation/AHandler.h> + +namespace android { + +struct ABuffer; +struct AudioTrack; +struct IGraphicBufferProducer; +struct MediaCodec; + +// Renders audio and video data queued by calls to "queueAccessUnit". +struct DirectRenderer : public AHandler { + DirectRenderer(const sp<IGraphicBufferProducer> &bufferProducer); + + void setFormat(size_t trackIndex, const sp<AMessage> &format); + void queueAccessUnit(size_t trackIndex, const sp<ABuffer> &accessUnit); + +protected: + virtual void onMessageReceived(const sp<AMessage> &msg); + virtual ~DirectRenderer(); + +private: + struct DecoderContext; + struct AudioRenderer; + + enum { + kWhatDecoderNotify, + kWhatRenderVideo, + }; + + struct OutputInfo { + size_t mIndex; + int64_t mTimeUs; + sp<ABuffer> mBuffer; + }; + + sp<IGraphicBufferProducer> mSurfaceTex; + + sp<DecoderContext> mDecoderContext[2]; + List<OutputInfo> mVideoOutputBuffers; + + bool mVideoRenderPending; + + sp<AudioRenderer> mAudioRenderer; + + int32_t mNumFramesLate; + int32_t mNumFrames; + + void onDecoderNotify(const sp<AMessage> &msg); + + void queueOutputBuffer( + size_t trackIndex, + size_t index, int64_t timeUs, const sp<ABuffer> &buffer); + + void scheduleVideoRenderIfNecessary(); + void onRenderVideo(); + + DISALLOW_EVIL_CONSTRUCTORS(DirectRenderer); +}; + +} // namespace android + +#endif // DIRECT_RENDERER_H_ diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp new file mode 100644 index 0000000..5db2099 --- /dev/null +++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp @@ -0,0 +1,917 @@ +/* + * 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 "WifiDisplaySink" +#include <utils/Log.h> + +#include "WifiDisplaySink.h" + +#include "DirectRenderer.h" +#include "MediaReceiver.h" +#include "ParsedMessage.h" +#include "TimeSyncer.h" + +#include <cutils/properties.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/Utils.h> + +namespace android { + +// static +const AString WifiDisplaySink::sUserAgent = MakeUserAgent(); + +WifiDisplaySink::WifiDisplaySink( + uint32_t flags, + const sp<ANetworkSession> &netSession, + const sp<IGraphicBufferProducer> &bufferProducer, + const sp<AMessage> ¬ify) + : mState(UNDEFINED), + mFlags(flags), + mNetSession(netSession), + mSurfaceTex(bufferProducer), + mNotify(notify), + mUsingTCPTransport(false), + mUsingTCPInterleaving(false), + mSessionID(0), + mNextCSeq(1), + mIDRFrameRequestPending(false), + mTimeOffsetUs(0ll), + mTimeOffsetValid(false), + mSetupDeferred(false), + mLatencyCount(0), + mLatencySumUs(0ll), + mLatencyMaxUs(0ll), + mMaxDelayMs(-1ll) { + // We support any and all resolutions, but prefer 720p30 + mSinkSupportedVideoFormats.setNativeResolution( + VideoFormats::RESOLUTION_CEA, 5); // 1280 x 720 p30 + + mSinkSupportedVideoFormats.enableAll(); +} + +WifiDisplaySink::~WifiDisplaySink() { +} + +void WifiDisplaySink::start(const char *sourceHost, int32_t sourcePort) { + sp<AMessage> msg = new AMessage(kWhatStart, id()); + msg->setString("sourceHost", sourceHost); + msg->setInt32("sourcePort", sourcePort); + msg->post(); +} + +void WifiDisplaySink::start(const char *uri) { + sp<AMessage> msg = new AMessage(kWhatStart, id()); + msg->setString("setupURI", uri); + msg->post(); +} + +// static +bool WifiDisplaySink::ParseURL( + const char *url, AString *host, int32_t *port, AString *path, + AString *user, AString *pass) { + host->clear(); + *port = 0; + path->clear(); + user->clear(); + pass->clear(); + + if (strncasecmp("rtsp://", url, 7)) { + return false; + } + + const char *slashPos = strchr(&url[7], '/'); + + if (slashPos == NULL) { + host->setTo(&url[7]); + path->setTo("/"); + } else { + host->setTo(&url[7], slashPos - &url[7]); + path->setTo(slashPos); + } + + ssize_t atPos = host->find("@"); + + if (atPos >= 0) { + // Split of user:pass@ from hostname. + + AString userPass(*host, 0, atPos); + host->erase(0, atPos + 1); + + ssize_t colonPos = userPass.find(":"); + + if (colonPos < 0) { + *user = userPass; + } else { + user->setTo(userPass, 0, colonPos); + pass->setTo(userPass, colonPos + 1, userPass.size() - colonPos - 1); + } + } + + const char *colonPos = strchr(host->c_str(), ':'); + + if (colonPos != NULL) { + char *end; + unsigned long x = strtoul(colonPos + 1, &end, 10); + + if (end == colonPos + 1 || *end != '\0' || x >= 65536) { + return false; + } + + *port = x; + + size_t colonOffset = colonPos - host->c_str(); + size_t trailing = host->size() - colonOffset; + host->erase(colonOffset, trailing); + } else { + *port = 554; + } + + return true; +} + +void WifiDisplaySink::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatStart: + { + sleep(2); // XXX + + int32_t sourcePort; + CHECK(msg->findString("sourceHost", &mRTSPHost)); + CHECK(msg->findInt32("sourcePort", &sourcePort)); + + sp<AMessage> notify = new AMessage(kWhatRTSPNotify, id()); + + status_t err = mNetSession->createRTSPClient( + mRTSPHost.c_str(), sourcePort, notify, &mSessionID); + CHECK_EQ(err, (status_t)OK); + + mState = CONNECTING; + 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)); + + if (sessionID == mSessionID) { + ALOGI("Lost control connection."); + + // The control connection is dead now. + mNetSession->destroySession(mSessionID); + mSessionID = 0; + + if (mNotify == NULL) { + looper()->stop(); + } else { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatDisconnected); + notify->post(); + } + } + break; + } + + case ANetworkSession::kWhatConnected: + { + ALOGI("We're now connected."); + mState = CONNECTED; + + if (mFlags & FLAG_SPECIAL_MODE) { + sp<AMessage> notify = new AMessage( + kWhatTimeSyncerNotify, id()); + + mTimeSyncer = new TimeSyncer(mNetSession, notify); + looper()->registerHandler(mTimeSyncer); + + mTimeSyncer->startClient(mRTSPHost.c_str(), 8123); + } + break; + } + + case ANetworkSession::kWhatData: + { + onReceiveClientData(msg); + break; + } + + default: + TRESPASS(); + } + break; + } + + case kWhatStop: + { + looper()->stop(); + break; + } + + case kWhatMediaReceiverNotify: + { + onMediaReceiverNotify(msg); + break; + } + + case kWhatTimeSyncerNotify: + { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + if (what == TimeSyncer::kWhatTimeOffset) { + CHECK(msg->findInt64("offset", &mTimeOffsetUs)); + mTimeOffsetValid = true; + + if (mSetupDeferred) { + CHECK_EQ((status_t)OK, + sendSetup( + mSessionID, + "rtsp://x.x.x.x:x/wfd1.0/streamid=0")); + + mSetupDeferred = false; + } + } + break; + } + + case kWhatReportLateness: + { + if (mLatencyCount > 0) { + int64_t avgLatencyUs = mLatencySumUs / mLatencyCount; + + ALOGV("avg. latency = %lld ms (max %lld ms)", + avgLatencyUs / 1000ll, + mLatencyMaxUs / 1000ll); + + sp<AMessage> params = new AMessage; + params->setInt64("avgLatencyUs", avgLatencyUs); + params->setInt64("maxLatencyUs", mLatencyMaxUs); + mMediaReceiver->informSender(0 /* trackIndex */, params); + } + + mLatencyCount = 0; + mLatencySumUs = 0ll; + mLatencyMaxUs = 0ll; + + msg->post(kReportLatenessEveryUs); + break; + } + + default: + TRESPASS(); + } +} + +void WifiDisplaySink::dumpDelay(size_t trackIndex, int64_t timeUs) { + int64_t delayMs = (ALooper::GetNowUs() - timeUs) / 1000ll; + + if (delayMs > mMaxDelayMs) { + mMaxDelayMs = delayMs; + } + + static const int64_t kMinDelayMs = 0; + static const int64_t kMaxDelayMs = 300; + + const char *kPattern = "########################################"; + size_t kPatternSize = strlen(kPattern); + + int n = (kPatternSize * (delayMs - kMinDelayMs)) + / (kMaxDelayMs - kMinDelayMs); + + if (n < 0) { + n = 0; + } else if ((size_t)n > kPatternSize) { + n = kPatternSize; + } + + ALOGI("[%lld]: (%4lld ms / %4lld ms) %s", + timeUs / 1000, + delayMs, + mMaxDelayMs, + kPattern + kPatternSize - n); +} + +void WifiDisplaySink::onMediaReceiverNotify(const sp<AMessage> &msg) { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + switch (what) { + case MediaReceiver::kWhatInitDone: + { + status_t err; + CHECK(msg->findInt32("err", &err)); + + ALOGI("MediaReceiver initialization completed w/ err %d", err); + break; + } + + case MediaReceiver::kWhatError: + { + status_t err; + CHECK(msg->findInt32("err", &err)); + + ALOGE("MediaReceiver signaled error %d", err); + break; + } + + case MediaReceiver::kWhatAccessUnit: + { + if (mRenderer == NULL) { + mRenderer = new DirectRenderer(mSurfaceTex); + looper()->registerHandler(mRenderer); + } + + sp<ABuffer> accessUnit; + CHECK(msg->findBuffer("accessUnit", &accessUnit)); + + int64_t timeUs; + CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs)); + + if (!mTimeOffsetValid && !(mFlags & FLAG_SPECIAL_MODE)) { + mTimeOffsetUs = timeUs - ALooper::GetNowUs(); + mTimeOffsetValid = true; + } + + CHECK(mTimeOffsetValid); + + // We are the timesync _client_, + // client time = server time - time offset. + timeUs -= mTimeOffsetUs; + + size_t trackIndex; + CHECK(msg->findSize("trackIndex", &trackIndex)); + + int64_t nowUs = ALooper::GetNowUs(); + int64_t delayUs = nowUs - timeUs; + + mLatencySumUs += delayUs; + if (mLatencyCount == 0 || delayUs > mLatencyMaxUs) { + mLatencyMaxUs = delayUs; + } + ++mLatencyCount; + + // dumpDelay(trackIndex, timeUs); + + timeUs += 220000ll; // Assume 220 ms of latency + accessUnit->meta()->setInt64("timeUs", timeUs); + + sp<AMessage> format; + if (msg->findMessage("format", &format)) { + mRenderer->setFormat(trackIndex, format); + } + + mRenderer->queueAccessUnit(trackIndex, accessUnit); + break; + } + + case MediaReceiver::kWhatPacketLost: + { +#if 0 + if (!mIDRFrameRequestPending) { + ALOGI("requesting IDR frame"); + + sendIDRFrameRequest(mSessionID); + } +#endif + break; + } + + default: + TRESPASS(); + } +} + +void WifiDisplaySink::registerResponseHandler( + int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func) { + ResponseID id; + id.mSessionID = sessionID; + id.mCSeq = cseq; + mResponseHandlers.add(id, func); +} + +status_t WifiDisplaySink::sendM2(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, &WifiDisplaySink::onReceiveM2Response); + + ++mNextCSeq; + + return OK; +} + +status_t WifiDisplaySink::onReceiveM2Response( + int32_t sessionID, const sp<ParsedMessage> &msg) { + int32_t statusCode; + if (!msg->getStatusCode(&statusCode)) { + return ERROR_MALFORMED; + } + + if (statusCode != 200) { + return ERROR_UNSUPPORTED; + } + + return OK; +} + +status_t WifiDisplaySink::onReceiveSetupResponse( + int32_t sessionID, const sp<ParsedMessage> &msg) { + int32_t statusCode; + if (!msg->getStatusCode(&statusCode)) { + return ERROR_MALFORMED; + } + + if (statusCode != 200) { + return ERROR_UNSUPPORTED; + } + + if (!msg->findString("session", &mPlaybackSessionID)) { + return ERROR_MALFORMED; + } + + if (!ParsedMessage::GetInt32Attribute( + mPlaybackSessionID.c_str(), + "timeout", + &mPlaybackSessionTimeoutSecs)) { + mPlaybackSessionTimeoutSecs = -1; + } + + ssize_t colonPos = mPlaybackSessionID.find(";"); + if (colonPos >= 0) { + // Strip any options from the returned session id. + mPlaybackSessionID.erase( + colonPos, mPlaybackSessionID.size() - colonPos); + } + + status_t err = configureTransport(msg); + + if (err != OK) { + return err; + } + + mState = PAUSED; + + return sendPlay( + sessionID, + "rtsp://x.x.x.x:x/wfd1.0/streamid=0"); +} + +status_t WifiDisplaySink::configureTransport(const sp<ParsedMessage> &msg) { + if (mUsingTCPTransport && !(mFlags & FLAG_SPECIAL_MODE)) { + // In "special" mode we still use a UDP RTCP back-channel that + // needs connecting. + return OK; + } + + AString transport; + if (!msg->findString("transport", &transport)) { + ALOGE("Missing 'transport' field in SETUP response."); + return ERROR_MALFORMED; + } + + AString sourceHost; + if (!ParsedMessage::GetAttribute( + transport.c_str(), "source", &sourceHost)) { + sourceHost = mRTSPHost; + } + + AString serverPortStr; + if (!ParsedMessage::GetAttribute( + transport.c_str(), "server_port", &serverPortStr)) { + ALOGE("Missing 'server_port' in Transport field."); + return ERROR_MALFORMED; + } + + int rtpPort, rtcpPort; + if (sscanf(serverPortStr.c_str(), "%d-%d", &rtpPort, &rtcpPort) != 2 + || rtpPort <= 0 || rtpPort > 65535 + || rtcpPort <=0 || rtcpPort > 65535 + || rtcpPort != rtpPort + 1) { + ALOGE("Invalid server_port description '%s'.", + serverPortStr.c_str()); + + return ERROR_MALFORMED; + } + + if (rtpPort & 1) { + ALOGW("Server picked an odd numbered RTP port."); + } + + return mMediaReceiver->connectTrack( + 0 /* trackIndex */, sourceHost.c_str(), rtpPort, rtcpPort); +} + +status_t WifiDisplaySink::onReceivePlayResponse( + int32_t sessionID, const sp<ParsedMessage> &msg) { + int32_t statusCode; + if (!msg->getStatusCode(&statusCode)) { + return ERROR_MALFORMED; + } + + if (statusCode != 200) { + return ERROR_UNSUPPORTED; + } + + mState = PLAYING; + + (new AMessage(kWhatReportLateness, id()))->post(kReportLatenessEveryUs); + + return OK; +} + +status_t WifiDisplaySink::onReceiveIDRFrameRequestResponse( + int32_t sessionID, const sp<ParsedMessage> &msg) { + CHECK(mIDRFrameRequestPending); + mIDRFrameRequestPending = false; + + return OK; +} + +void WifiDisplaySink::onReceiveClientData(const sp<AMessage> &msg) { + int32_t sessionID; + CHECK(msg->findInt32("sessionID", &sessionID)); + + sp<RefBase> obj; + CHECK(msg->findObject("data", &obj)); + + sp<ParsedMessage> data = + static_cast<ParsedMessage *>(obj.get()); + + ALOGV("session %d received '%s'", + sessionID, data->debugString().c_str()); + + AString method; + AString uri; + data->getRequestField(0, &method); + + int32_t cseq; + if (!data->findInt32("cseq", &cseq)) { + sendErrorResponse(sessionID, "400 Bad Request", -1 /* cseq */); + return; + } + + 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; + } + + HandleRTSPResponseFunc func = mResponseHandlers.valueAt(index); + mResponseHandlers.removeItemsAt(index); + + status_t err = (this->*func)(sessionID, data); + CHECK_EQ(err, (status_t)OK); + } else { + AString version; + data->getRequestField(2, &version); + if (!(version == AString("RTSP/1.0"))) { + sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq); + return; + } + + if (method == "OPTIONS") { + onOptionsRequest(sessionID, cseq, data); + } else if (method == "GET_PARAMETER") { + onGetParameterRequest(sessionID, cseq, data); + } else if (method == "SET_PARAMETER") { + onSetParameterRequest(sessionID, cseq, data); + } else { + sendErrorResponse(sessionID, "405 Method Not Allowed", cseq); + } + } +} + +void WifiDisplaySink::onOptionsRequest( + int32_t sessionID, + int32_t cseq, + const sp<ParsedMessage> &data) { + AString response = "RTSP/1.0 200 OK\r\n"; + AppendCommonResponse(&response, cseq); + response.append("Public: org.wfa.wfd1.0, GET_PARAMETER, SET_PARAMETER\r\n"); + response.append("\r\n"); + + status_t err = mNetSession->sendRequest(sessionID, response.c_str()); + CHECK_EQ(err, (status_t)OK); + + err = sendM2(sessionID); + CHECK_EQ(err, (status_t)OK); +} + +void WifiDisplaySink::onGetParameterRequest( + int32_t sessionID, + int32_t cseq, + const sp<ParsedMessage> &data) { + AString body; + + if (mState == CONNECTED) { + mUsingTCPTransport = false; + mUsingTCPInterleaving = false; + + char val[PROPERTY_VALUE_MAX]; + if (property_get("media.wfd-sink.tcp-mode", val, NULL)) { + if (!strcasecmp("true", val) || !strcmp("1", val)) { + ALOGI("Using TCP unicast transport."); + mUsingTCPTransport = true; + mUsingTCPInterleaving = false; + } else if (!strcasecmp("interleaved", val)) { + ALOGI("Using TCP interleaved transport."); + mUsingTCPTransport = true; + mUsingTCPInterleaving = true; + } + } else if (mFlags & FLAG_SPECIAL_MODE) { + mUsingTCPTransport = true; + } + + body = "wfd_video_formats: "; + body.append(mSinkSupportedVideoFormats.getFormatSpec()); + + body.append( + "\r\nwfd_audio_codecs: AAC 0000000F 00\r\n" + "wfd_client_rtp_ports: RTP/AVP/"); + + if (mUsingTCPTransport) { + body.append("TCP;"); + if (mUsingTCPInterleaving) { + body.append("interleaved"); + } else { + body.append("unicast 19000 0"); + } + } else { + body.append("UDP;unicast 19000 0"); + } + + body.append(" mode=play\r\n"); + } + + AString response = "RTSP/1.0 200 OK\r\n"; + AppendCommonResponse(&response, cseq); + response.append("Content-Type: text/parameters\r\n"); + response.append(StringPrintf("Content-Length: %d\r\n", body.size())); + response.append("\r\n"); + response.append(body); + + status_t err = mNetSession->sendRequest(sessionID, response.c_str()); + CHECK_EQ(err, (status_t)OK); +} + +status_t WifiDisplaySink::sendSetup(int32_t sessionID, const char *uri) { + sp<AMessage> notify = new AMessage(kWhatMediaReceiverNotify, id()); + + mMediaReceiverLooper = new ALooper; + mMediaReceiverLooper->setName("media_receiver"); + + mMediaReceiverLooper->start( + false /* runOnCallingThread */, + false /* canCallJava */, + PRIORITY_AUDIO); + + mMediaReceiver = new MediaReceiver(mNetSession, notify); + mMediaReceiverLooper->registerHandler(mMediaReceiver); + + RTPReceiver::TransportMode rtpMode = RTPReceiver::TRANSPORT_UDP; + if (mUsingTCPTransport) { + if (mUsingTCPInterleaving) { + rtpMode = RTPReceiver::TRANSPORT_TCP_INTERLEAVED; + } else { + rtpMode = RTPReceiver::TRANSPORT_TCP; + } + } + + int32_t localRTPPort; + status_t err = mMediaReceiver->addTrack( + rtpMode, RTPReceiver::TRANSPORT_UDP /* rtcpMode */, &localRTPPort); + + if (err == OK) { + err = mMediaReceiver->initAsync(MediaReceiver::MODE_TRANSPORT_STREAM); + } + + if (err != OK) { + mMediaReceiverLooper->unregisterHandler(mMediaReceiver->id()); + mMediaReceiver.clear(); + + mMediaReceiverLooper->stop(); + mMediaReceiverLooper.clear(); + + return err; + } + + AString request = StringPrintf("SETUP %s RTSP/1.0\r\n", uri); + + AppendCommonResponse(&request, mNextCSeq); + + if (rtpMode == RTPReceiver::TRANSPORT_TCP_INTERLEAVED) { + request.append("Transport: RTP/AVP/TCP;interleaved=0-1\r\n"); + } else if (rtpMode == RTPReceiver::TRANSPORT_TCP) { + if (mFlags & FLAG_SPECIAL_MODE) { + // This isn't quite true, since the RTP connection is through TCP + // and the RTCP connection through UDP... + request.append( + StringPrintf( + "Transport: RTP/AVP/TCP;unicast;client_port=%d-%d\r\n", + localRTPPort, localRTPPort + 1)); + } else { + request.append( + StringPrintf( + "Transport: RTP/AVP/TCP;unicast;client_port=%d\r\n", + localRTPPort)); + } + } else { + request.append( + StringPrintf( + "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n", + localRTPPort, + localRTPPort + 1)); + } + + request.append("\r\n"); + + ALOGV("request = '%s'", request.c_str()); + + err = mNetSession->sendRequest(sessionID, request.c_str(), request.size()); + + if (err != OK) { + return err; + } + + registerResponseHandler( + sessionID, mNextCSeq, &WifiDisplaySink::onReceiveSetupResponse); + + ++mNextCSeq; + + return OK; +} + +status_t WifiDisplaySink::sendPlay(int32_t sessionID, const char *uri) { + AString request = StringPrintf("PLAY %s RTSP/1.0\r\n", uri); + + AppendCommonResponse(&request, mNextCSeq); + + request.append(StringPrintf("Session: %s\r\n", mPlaybackSessionID.c_str())); + request.append("\r\n"); + + status_t err = + mNetSession->sendRequest(sessionID, request.c_str(), request.size()); + + if (err != OK) { + return err; + } + + registerResponseHandler( + sessionID, mNextCSeq, &WifiDisplaySink::onReceivePlayResponse); + + ++mNextCSeq; + + return OK; +} + +status_t WifiDisplaySink::sendIDRFrameRequest(int32_t sessionID) { + CHECK(!mIDRFrameRequestPending); + + AString request = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"; + + AppendCommonResponse(&request, mNextCSeq); + + AString content = "wfd_idr_request\r\n"; + + request.append(StringPrintf("Session: %s\r\n", mPlaybackSessionID.c_str())); + request.append(StringPrintf("Content-Length: %d\r\n", content.size())); + request.append("\r\n"); + request.append(content); + + status_t err = + mNetSession->sendRequest(sessionID, request.c_str(), request.size()); + + if (err != OK) { + return err; + } + + registerResponseHandler( + sessionID, + mNextCSeq, + &WifiDisplaySink::onReceiveIDRFrameRequestResponse); + + ++mNextCSeq; + + mIDRFrameRequestPending = true; + + return OK; +} + +void WifiDisplaySink::onSetParameterRequest( + int32_t sessionID, + int32_t cseq, + const sp<ParsedMessage> &data) { + const char *content = data->getContent(); + + if (strstr(content, "wfd_trigger_method: SETUP\r\n") != NULL) { + if ((mFlags & FLAG_SPECIAL_MODE) && !mTimeOffsetValid) { + mSetupDeferred = true; + } else { + status_t err = + sendSetup( + sessionID, + "rtsp://x.x.x.x:x/wfd1.0/streamid=0"); + + CHECK_EQ(err, (status_t)OK); + } + } + + AString response = "RTSP/1.0 200 OK\r\n"; + AppendCommonResponse(&response, cseq); + response.append("\r\n"); + + status_t err = mNetSession->sendRequest(sessionID, response.c_str()); + CHECK_EQ(err, (status_t)OK); +} + +void WifiDisplaySink::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"); + + status_t err = mNetSession->sendRequest(sessionID, response.c_str()); + CHECK_EQ(err, (status_t)OK); +} + +// static +void WifiDisplaySink::AppendCommonResponse(AString *response, int32_t cseq) { + 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(StringPrintf("User-Agent: %s\r\n", sUserAgent.c_str())); + + if (cseq >= 0) { + response->append(StringPrintf("CSeq: %d\r\n", cseq)); + } +} + +} // namespace android diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.h b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h new file mode 100644 index 0000000..adb9d89 --- /dev/null +++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h @@ -0,0 +1,196 @@ +/* + * 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_SINK_H_ + +#define WIFI_DISPLAY_SINK_H_ + +#include "ANetworkSession.h" + +#include "VideoFormats.h" + +#include <gui/Surface.h> +#include <media/stagefright/foundation/AHandler.h> + +namespace android { + +struct AMessage; +struct DirectRenderer; +struct MediaReceiver; +struct ParsedMessage; +struct TimeSyncer; + +// Represents the RTSP client acting as a wifi display sink. +// Connects to a wifi display source and renders the incoming +// transport stream using a MediaPlayer instance. +struct WifiDisplaySink : public AHandler { + enum { + kWhatDisconnected, + }; + + enum Flags { + FLAG_SPECIAL_MODE = 1, + }; + + // If no notification message is specified (notify == NULL) + // the sink will stop its looper() once the session ends, + // otherwise it will post an appropriate notification but leave + // the looper() running. + WifiDisplaySink( + uint32_t flags, + const sp<ANetworkSession> &netSession, + const sp<IGraphicBufferProducer> &bufferProducer = NULL, + const sp<AMessage> ¬ify = NULL); + + void start(const char *sourceHost, int32_t sourcePort); + void start(const char *uri); + +protected: + virtual ~WifiDisplaySink(); + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum State { + UNDEFINED, + CONNECTING, + CONNECTED, + PAUSED, + PLAYING, + }; + + enum { + kWhatStart, + kWhatRTSPNotify, + kWhatStop, + kWhatMediaReceiverNotify, + kWhatTimeSyncerNotify, + kWhatReportLateness, + }; + + 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 (WifiDisplaySink::*HandleRTSPResponseFunc)( + int32_t sessionID, const sp<ParsedMessage> &msg); + + static const int64_t kReportLatenessEveryUs = 1000000ll; + + static const AString sUserAgent; + + State mState; + uint32_t mFlags; + VideoFormats mSinkSupportedVideoFormats; + sp<ANetworkSession> mNetSession; + sp<IGraphicBufferProducer> mSurfaceTex; + sp<AMessage> mNotify; + sp<TimeSyncer> mTimeSyncer; + bool mUsingTCPTransport; + bool mUsingTCPInterleaving; + AString mRTSPHost; + int32_t mSessionID; + + int32_t mNextCSeq; + + KeyedVector<ResponseID, HandleRTSPResponseFunc> mResponseHandlers; + + sp<ALooper> mMediaReceiverLooper; + sp<MediaReceiver> mMediaReceiver; + sp<DirectRenderer> mRenderer; + + AString mPlaybackSessionID; + int32_t mPlaybackSessionTimeoutSecs; + + bool mIDRFrameRequestPending; + + int64_t mTimeOffsetUs; + bool mTimeOffsetValid; + + bool mSetupDeferred; + + size_t mLatencyCount; + int64_t mLatencySumUs; + int64_t mLatencyMaxUs; + + int64_t mMaxDelayMs; + + status_t sendM2(int32_t sessionID); + status_t sendSetup(int32_t sessionID, const char *uri); + status_t sendPlay(int32_t sessionID, const char *uri); + status_t sendIDRFrameRequest(int32_t sessionID); + + status_t onReceiveM2Response( + int32_t sessionID, const sp<ParsedMessage> &msg); + + status_t onReceiveSetupResponse( + int32_t sessionID, const sp<ParsedMessage> &msg); + + status_t configureTransport(const sp<ParsedMessage> &msg); + + status_t onReceivePlayResponse( + int32_t sessionID, const sp<ParsedMessage> &msg); + + status_t onReceiveIDRFrameRequestResponse( + int32_t sessionID, const sp<ParsedMessage> &msg); + + void registerResponseHandler( + int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func); + + void onReceiveClientData(const sp<AMessage> &msg); + + void onOptionsRequest( + int32_t sessionID, + int32_t cseq, + const sp<ParsedMessage> &data); + + void onGetParameterRequest( + int32_t sessionID, + int32_t cseq, + const sp<ParsedMessage> &data); + + void onSetParameterRequest( + int32_t sessionID, + int32_t cseq, + const sp<ParsedMessage> &data); + + void onMediaReceiverNotify(const sp<AMessage> &msg); + + void sendErrorResponse( + int32_t sessionID, + const char *errorDetail, + int32_t cseq); + + static void AppendCommonResponse(AString *response, int32_t cseq); + + bool ParseURL( + const char *url, AString *host, int32_t *port, AString *path, + AString *user, AString *pass); + + void dumpDelay(size_t trackIndex, int64_t timeUs); + + DISALLOW_EVIL_CONSTRUCTORS(WifiDisplaySink); +}; + +} // namespace android + +#endif // WIFI_DISPLAY_SINK_H_ diff --git a/media/libstagefright/wifi-display/source/Converter.cpp b/media/libstagefright/wifi-display/source/Converter.cpp index 5344623..e62505d 100644 --- a/media/libstagefright/wifi-display/source/Converter.cpp +++ b/media/libstagefright/wifi-display/source/Converter.cpp @@ -438,6 +438,17 @@ void Converter::onMessageReceived(const sp<AMessage> &msg) { break; } + case kWhatReleaseOutputBuffer: + { + if (mEncoder != NULL) { + size_t bufferIndex; + CHECK(msg->findInt32("bufferIndex", (int32_t*)&bufferIndex)); + CHECK(bufferIndex < mEncoderOutputBuffers.size()); + mEncoder->releaseOutputBuffer(bufferIndex); + } + break; + } + default: TRESPASS(); } @@ -645,6 +656,7 @@ status_t Converter::doMoreWork() { size_t size; int64_t timeUs; uint32_t flags; + native_handle_t* handle = NULL; err = mEncoder->dequeueOutputBuffer( &bufferIndex, &offset, &size, &timeUs, &flags); @@ -667,18 +679,43 @@ status_t Converter::doMoreWork() { notify->setInt32("what", kWhatEOS); notify->post(); } else { - sp<ABuffer> buffer = new ABuffer(size); + sp<ABuffer> buffer; + sp<ABuffer> outbuf = mEncoderOutputBuffers.itemAt(bufferIndex); + + if (outbuf->meta()->findPointer("handle", (void**)&handle) && + handle != NULL) { + int32_t rangeLength, rangeOffset; + CHECK(outbuf->meta()->findInt32("rangeOffset", &rangeOffset)); + CHECK(outbuf->meta()->findInt32("rangeLength", &rangeLength)); + outbuf->meta()->setPointer("handle", NULL); + + // MediaSender will post the following message when HDCP + // is done, to release the output buffer back to encoder. + sp<AMessage> notify(new AMessage( + kWhatReleaseOutputBuffer, id())); + 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", timeUs, timeUs / 1E6); - memcpy(buffer->data(), - mEncoderOutputBuffers.itemAt(bufferIndex)->base() + offset, - size); + memcpy(buffer->data(), outbuf->base() + offset, size); if (flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) { - mOutputFormat->setBuffer("csd-0", buffer); + if (!handle) { + mOutputFormat->setBuffer("csd-0", buffer); + } } else { sp<AMessage> notify = mNotify->dup(); notify->setInt32("what", kWhatAccessUnit); @@ -687,7 +724,9 @@ status_t Converter::doMoreWork() { } } - mEncoder->releaseOutputBuffer(bufferIndex); + if (!handle) { + mEncoder->releaseOutputBuffer(bufferIndex); + } if (flags & MediaCodec::BUFFER_FLAG_EOS) { break; diff --git a/media/libstagefright/wifi-display/source/Converter.h b/media/libstagefright/wifi-display/source/Converter.h index ba297c4..fceef55 100644 --- a/media/libstagefright/wifi-display/source/Converter.h +++ b/media/libstagefright/wifi-display/source/Converter.h @@ -66,6 +66,7 @@ struct Converter : public AHandler { kWhatMediaPullerNotify, kWhatEncoderActivity, kWhatDropAFrame, + kWhatReleaseOutputBuffer, }; void shutdownAsync(); diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp index 3d7b865..7f0ba96 100644 --- a/media/libstagefright/wifi-display/source/PlaybackSession.cpp +++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp @@ -378,7 +378,9 @@ status_t WifiDisplaySource::PlaybackSession::init( bool usePCMAudio, bool enableVideo, VideoFormats::ResolutionType videoResolutionType, - size_t videoResolutionIndex) { + size_t videoResolutionIndex, + VideoFormats::ProfileType videoProfileType, + VideoFormats::LevelType videoLevelType) { sp<AMessage> notify = new AMessage(kWhatMediaSenderNotify, id()); mMediaSender = new MediaSender(mNetSession, notify); looper()->registerHandler(mMediaSender); @@ -390,7 +392,9 @@ status_t WifiDisplaySource::PlaybackSession::init( usePCMAudio, enableVideo, videoResolutionType, - videoResolutionIndex); + videoResolutionIndex, + videoProfileType, + videoLevelType); if (err == OK) { err = mMediaSender->initAsync( @@ -559,6 +563,8 @@ void WifiDisplaySource::PlaybackSession::onMessageReceived( converter->dropAFrame(); } } + } else if (what == MediaSender::kWhatInformSender) { + onSinkFeedback(msg); } else { TRESPASS(); } @@ -654,6 +660,89 @@ void WifiDisplaySource::PlaybackSession::onMessageReceived( } } +void WifiDisplaySource::PlaybackSession::onSinkFeedback(const sp<AMessage> &msg) { + int64_t avgLatencyUs; + CHECK(msg->findInt64("avgLatencyUs", &avgLatencyUs)); + + int64_t maxLatencyUs; + CHECK(msg->findInt64("maxLatencyUs", &maxLatencyUs)); + + ALOGI("sink reports avg. latency of %lld ms (max %lld ms)", + avgLatencyUs / 1000ll, + maxLatencyUs / 1000ll); + + if (mVideoTrackIndex >= 0) { + const sp<Track> &videoTrack = mTracks.valueFor(mVideoTrackIndex); + sp<Converter> converter = videoTrack->converter(); + + if (converter != NULL) { + int32_t videoBitrate = + Converter::GetInt32Property("media.wfd.video-bitrate", -1); + + char val[PROPERTY_VALUE_MAX]; + if (videoBitrate < 0 + && property_get("media.wfd.video-bitrate", val, NULL) + && !strcasecmp("adaptive", val)) { + videoBitrate = converter->getVideoBitrate(); + + if (avgLatencyUs > 300000ll) { + videoBitrate *= 0.6; + } else if (avgLatencyUs < 100000ll) { + videoBitrate *= 1.1; + } + } + + if (videoBitrate > 0) { + if (videoBitrate < 500000) { + videoBitrate = 500000; + } else if (videoBitrate > 10000000) { + videoBitrate = 10000000; + } + + if (videoBitrate != converter->getVideoBitrate()) { + ALOGI("setting video bitrate to %d bps", videoBitrate); + + converter->setVideoBitrate(videoBitrate); + } + } + } + + sp<RepeaterSource> repeaterSource = videoTrack->repeaterSource(); + if (repeaterSource != NULL) { + double rateHz = + Converter::GetInt32Property( + "media.wfd.video-framerate", -1); + + char val[PROPERTY_VALUE_MAX]; + if (rateHz < 0.0 + && property_get("media.wfd.video-framerate", val, NULL) + && !strcasecmp("adaptive", val)) { + rateHz = repeaterSource->getFrameRate(); + + if (avgLatencyUs > 300000ll) { + rateHz *= 0.9; + } else if (avgLatencyUs < 200000ll) { + rateHz *= 1.1; + } + } + + if (rateHz > 0) { + if (rateHz < 5.0) { + rateHz = 5.0; + } else if (rateHz > 30.0) { + rateHz = 30.0; + } + + if (rateHz != repeaterSource->getFrameRate()) { + ALOGI("setting frame rate to %.2f Hz", rateHz); + + repeaterSource->setFrameRate(rateHz); + } + } + } + } +} + status_t WifiDisplaySource::PlaybackSession::setupMediaPacketizer( bool enableAudio, bool enableVideo) { DataSource::RegisterDefaultSniffers(); @@ -785,7 +874,9 @@ status_t WifiDisplaySource::PlaybackSession::setupPacketizer( bool usePCMAudio, bool enableVideo, VideoFormats::ResolutionType videoResolutionType, - size_t videoResolutionIndex) { + size_t videoResolutionIndex, + VideoFormats::ProfileType videoProfileType, + VideoFormats::LevelType videoLevelType) { CHECK(enableAudio || enableVideo); if (!mMediaPath.empty()) { @@ -794,7 +885,8 @@ status_t WifiDisplaySource::PlaybackSession::setupPacketizer( if (enableVideo) { status_t err = addVideoSource( - videoResolutionType, videoResolutionIndex); + videoResolutionType, videoResolutionIndex, videoProfileType, + videoLevelType); if (err != OK) { return err; @@ -810,9 +902,13 @@ status_t WifiDisplaySource::PlaybackSession::setupPacketizer( status_t WifiDisplaySource::PlaybackSession::addSource( bool isVideo, const sp<MediaSource> &source, bool isRepeaterSource, - bool usePCMAudio, size_t *numInputBuffers) { + bool usePCMAudio, unsigned profileIdc, unsigned levelIdc, + unsigned constraintSet, size_t *numInputBuffers) { CHECK(!usePCMAudio || !isVideo); CHECK(!isRepeaterSource || isVideo); + CHECK(!profileIdc || isVideo); + CHECK(!levelIdc || isVideo); + CHECK(!constraintSet || isVideo); sp<ALooper> pullLooper = new ALooper; pullLooper->setName("pull_looper"); @@ -842,9 +938,12 @@ status_t WifiDisplaySource::PlaybackSession::addSource( if (isVideo) { format->setInt32("store-metadata-in-buffers", true); - + format->setInt32("store-metadata-in-buffers-output", (mHDCP != NULL)); format->setInt32( "color-format", OMX_COLOR_FormatAndroidOpaque); + format->setInt32("profile-idc", profileIdc); + format->setInt32("level-idc", levelIdc); + format->setInt32("constraint-set", constraintSet); } notify = new AMessage(kWhatConverterNotify, id()); @@ -905,7 +1004,9 @@ status_t WifiDisplaySource::PlaybackSession::addSource( status_t WifiDisplaySource::PlaybackSession::addVideoSource( VideoFormats::ResolutionType videoResolutionType, - size_t videoResolutionIndex) { + size_t videoResolutionIndex, + VideoFormats::ProfileType videoProfileType, + VideoFormats::LevelType videoLevelType) { size_t width, height, framesPerSecond; bool interlaced; CHECK(VideoFormats::GetConfiguration( @@ -916,6 +1017,14 @@ status_t WifiDisplaySource::PlaybackSession::addVideoSource( &framesPerSecond, &interlaced)); + unsigned profileIdc, levelIdc, constraintSet; + CHECK(VideoFormats::GetProfileLevel( + videoProfileType, + videoLevelType, + &profileIdc, + &levelIdc, + &constraintSet)); + sp<SurfaceMediaSource> source = new SurfaceMediaSource(width, height); source->setUseAbsoluteTimestamps(); @@ -926,7 +1035,8 @@ status_t WifiDisplaySource::PlaybackSession::addVideoSource( size_t numInputBuffers; status_t err = addSource( true /* isVideo */, videoSource, true /* isRepeaterSource */, - false /* usePCMAudio */, &numInputBuffers); + false /* usePCMAudio */, profileIdc, levelIdc, constraintSet, + &numInputBuffers); if (err != OK) { return err; @@ -949,7 +1059,8 @@ status_t WifiDisplaySource::PlaybackSession::addAudioSource(bool usePCMAudio) { if (audioSource->initCheck() == OK) { return addSource( false /* isVideo */, audioSource, false /* isRepeaterSource */, - usePCMAudio, NULL /* numInputBuffers */); + usePCMAudio, 0 /* profileIdc */, 0 /* levelIdc */, + 0 /* constraintSet */, NULL /* numInputBuffers */); } ALOGW("Unable to instantiate audio source"); diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.h b/media/libstagefright/wifi-display/source/PlaybackSession.h index 39086a1..5c8ee94 100644 --- a/media/libstagefright/wifi-display/source/PlaybackSession.h +++ b/media/libstagefright/wifi-display/source/PlaybackSession.h @@ -53,7 +53,9 @@ struct WifiDisplaySource::PlaybackSession : public AHandler { bool usePCMAudio, bool enableVideo, VideoFormats::ResolutionType videoResolutionType, - size_t videoResolutionIndex); + size_t videoResolutionIndex, + VideoFormats::ProfileType videoProfileType, + VideoFormats::LevelType videoLevelType); void destroyAsync(); @@ -130,18 +132,25 @@ private: bool usePCMAudio, bool enableVideo, VideoFormats::ResolutionType videoResolutionType, - size_t videoResolutionIndex); + size_t videoResolutionIndex, + VideoFormats::ProfileType videoProfileType, + VideoFormats::LevelType videoLevelType); status_t addSource( bool isVideo, const sp<MediaSource> &source, bool isRepeaterSource, bool usePCMAudio, + unsigned profileIdc, + unsigned levelIdc, + unsigned contraintSet, size_t *numInputBuffers); status_t addVideoSource( VideoFormats::ResolutionType videoResolutionType, - size_t videoResolutionIndex); + size_t videoResolutionIndex, + VideoFormats::ProfileType videoProfileType, + VideoFormats::LevelType videoLevelType); status_t addAudioSource(bool usePCMAudio); diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.cpp b/media/libstagefright/wifi-display/source/TSPacketizer.cpp index 2c4a373..c674700 100644 --- a/media/libstagefright/wifi-display/source/TSPacketizer.cpp +++ b/media/libstagefright/wifi-display/source/TSPacketizer.cpp @@ -261,12 +261,24 @@ void TSPacketizer::Track::finalize() { data[0] = 40; // descriptor_tag data[1] = 4; // descriptor_length - CHECK_GE(mCSD.size(), 1u); - const sp<ABuffer> &sps = mCSD.itemAt(0); - CHECK(!memcmp("\x00\x00\x00\x01", sps->data(), 4)); - CHECK_GE(sps->size(), 7u); - // profile_idc, constraint_set*, level_idc - memcpy(&data[2], sps->data() + 4, 3); + if (mCSD.size() > 0) { + CHECK_GE(mCSD.size(), 1u); + const sp<ABuffer> &sps = mCSD.itemAt(0); + CHECK(!memcmp("\x00\x00\x00\x01", sps->data(), 4)); + CHECK_GE(sps->size(), 7u); + // profile_idc, constraint_set*, level_idc + memcpy(&data[2], sps->data() + 4, 3); + } else { + int32_t profileIdc, levelIdc, constraintSet; + CHECK(mFormat->findInt32("profile-idc", &profileIdc)); + CHECK(mFormat->findInt32("level-idc", &levelIdc)); + CHECK(mFormat->findInt32("constraint-set", &constraintSet)); + CHECK_GE(profileIdc, 0u); + CHECK_GE(levelIdc, 0u); + 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; diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp index 22dd0b1..b421b35 100644 --- a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp +++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp @@ -23,6 +23,7 @@ #include "Parameters.h" #include "ParsedMessage.h" #include "rtp/RTPSender.h" +#include "TimeSyncer.h" #include <binder/IServiceManager.h> #include <gui/IGraphicBufferProducer.h> @@ -73,6 +74,12 @@ WifiDisplaySource::WifiDisplaySource( 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() { @@ -164,6 +171,14 @@ void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) { } else { err = -EINVAL; } + } + + if (err == OK) { + sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id()); + mTimeSyncer = new TimeSyncer(mNetSession, notify); + looper()->registerHandler(mTimeSyncer); + + mTimeSyncer->startServer(8123); mState = AWAITING_CLIENT_CONNECTION; } @@ -539,6 +554,11 @@ void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) { break; } + case kWhatTimeSyncerNotify: + { + break; + } + default: TRESPASS(); } @@ -617,6 +637,9 @@ status_t WifiDisplaySource::sendM4(int32_t sessionID) { chosenVideoFormat.disableAll(); chosenVideoFormat.setNativeResolution( mChosenVideoResolutionType, mChosenVideoResolutionIndex); + chosenVideoFormat.setProfileLevel( + mChosenVideoResolutionType, mChosenVideoResolutionIndex, + mChosenVideoProfile, mChosenVideoLevel); body.append(chosenVideoFormat.getFormatSpec(true /* forM4Message */)); body.append("\r\n"); @@ -729,6 +752,8 @@ status_t WifiDisplaySource::sendM16(int32_t sessionID) { ++mNextCSeq; + scheduleKeepAlive(sessionID); + return OK; } @@ -845,7 +870,9 @@ status_t WifiDisplaySource::onReceiveM3Response( mSupportedSinkVideoFormats, mSupportedSourceVideoFormats, &mChosenVideoResolutionType, - &mChosenVideoResolutionIndex)) { + &mChosenVideoResolutionIndex, + &mChosenVideoProfile, + &mChosenVideoLevel)) { ALOGE("Sink and source share no commonly supported video " "formats."); @@ -864,6 +891,9 @@ status_t WifiDisplaySource::onReceiveM3Response( ALOGI("Picked video resolution %u x %u %c%u", width, height, interlaced ? 'i' : 'p', framesPerSecond); + + ALOGI("Picked AVC profile %d, level %d", + mChosenVideoProfile, mChosenVideoLevel); } else { ALOGI("Sink doesn't support video at all."); } @@ -994,8 +1024,6 @@ status_t WifiDisplaySource::onReceiveM16Response( if (mClientInfo.mPlaybackSession != NULL) { mClientInfo.mPlaybackSession->updateLiveness(); - - scheduleKeepAlive(sessionID); } return OK; @@ -1257,7 +1285,9 @@ status_t WifiDisplaySource::onSetupRequest( mUsingPCMAudio, mSinkSupportsVideo, mChosenVideoResolutionType, - mChosenVideoResolutionIndex); + mChosenVideoResolutionIndex, + mChosenVideoProfile, + mChosenVideoLevel); if (err != OK) { looper()->unregisterHandler(playbackSession->id()); @@ -1340,7 +1370,9 @@ status_t WifiDisplaySource::onPlayRequest( return ERROR_MALFORMED; } - if (mState != AWAITING_CLIENT_PLAY) { + if (mState != AWAITING_CLIENT_PLAY + && mState != PAUSED_TO_PLAYING + && mState != PAUSED) { ALOGW("Received PLAY request but we're in state %d", mState); sendErrorResponse( @@ -1367,7 +1399,7 @@ status_t WifiDisplaySource::onPlayRequest( return err; } - if (mState == PAUSED_TO_PLAYING) { + if (mState == PAUSED_TO_PLAYING || mPlaybackSessionEstablished) { mState = PLAYING; return OK; } @@ -1401,7 +1433,7 @@ status_t WifiDisplaySource::onPauseRequest( ALOGI("Received PAUSE request."); - if (mState != PLAYING_TO_PAUSED) { + if (mState != PLAYING_TO_PAUSED && mState != PLAYING) { return INVALID_OPERATION; } diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.h b/media/libstagefright/wifi-display/source/WifiDisplaySource.h index 44d3e4d..64186fc 100644 --- a/media/libstagefright/wifi-display/source/WifiDisplaySource.h +++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.h @@ -30,6 +30,7 @@ namespace android { struct IHDCP; struct IRemoteDisplayClient; struct ParsedMessage; +struct TimeSyncer; // Represents the RTSP server acting as a wifi display source. // Manages incoming connections, sets up Playback sessions as necessary. @@ -82,6 +83,7 @@ private: kWhatHDCPNotify, kWhatFinishStop2, kWhatTeardownTriggerTimedOut, + kWhatTimeSyncerNotify, }; struct ResponseID { @@ -118,6 +120,7 @@ private: sp<ANetworkSession> mNetSession; sp<IRemoteDisplayClient> mClient; AString mMediaPath; + sp<TimeSyncer> mTimeSyncer; struct in_addr mInterfaceAddr; int32_t mSessionID; @@ -131,6 +134,8 @@ private: VideoFormats::ResolutionType mChosenVideoResolutionType; size_t mChosenVideoResolutionIndex; + VideoFormats::ProfileType mChosenVideoProfile; + VideoFormats::LevelType mChosenVideoLevel; bool mSinkSupportsAudio; diff --git a/media/libstagefright/wifi-display/udptest.cpp b/media/libstagefright/wifi-display/udptest.cpp new file mode 100644 index 0000000..111846d --- /dev/null +++ b/media/libstagefright/wifi-display/udptest.cpp @@ -0,0 +1,116 @@ +/* + * 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_NEBUG 0 +#define LOG_TAG "udptest" +#include <utils/Log.h> + +#include "ANetworkSession.h" +#include "TimeSyncer.h" + +#include <binder/ProcessState.h> +#include <media/stagefright/foundation/AMessage.h> + +namespace android { + +} // namespace android + +static void usage(const char *me) { + fprintf(stderr, + "usage: %s -c host[:port]\tconnect to test server\n" + " -l \tcreate a test server\n", + me); +} + +int main(int argc, char **argv) { + using namespace android; + + ProcessState::self()->startThreadPool(); + + int32_t localPort = -1; + int32_t connectToPort = -1; + AString connectToHost; + + int res; + while ((res = getopt(argc, argv, "hc:l:")) >= 0) { + switch (res) { + case 'c': + { + const char *colonPos = strrchr(optarg, ':'); + + if (colonPos == NULL) { + connectToHost = optarg; + connectToPort = 49152; + } else { + connectToHost.setTo(optarg, colonPos - optarg); + + char *end; + connectToPort = strtol(colonPos + 1, &end, 10); + + if (*end != '\0' || end == colonPos + 1 + || connectToPort < 1 || connectToPort > 65535) { + fprintf(stderr, "Illegal port specified.\n"); + exit(1); + } + } + break; + } + + case 'l': + { + char *end; + localPort = strtol(optarg, &end, 10); + + if (*end != '\0' || end == optarg + || localPort < 1 || localPort > 65535) { + fprintf(stderr, "Illegal port specified.\n"); + exit(1); + } + break; + } + + case '?': + case 'h': + usage(argv[0]); + exit(1); + } + } + + if (localPort < 0 && connectToPort < 0) { + fprintf(stderr, + "You need to select either client or server mode.\n"); + exit(1); + } + + sp<ANetworkSession> netSession = new ANetworkSession; + netSession->start(); + + sp<ALooper> looper = new ALooper; + + sp<TimeSyncer> handler = new TimeSyncer(netSession, NULL /* notify */); + looper->registerHandler(handler); + + if (localPort >= 0) { + handler->startServer(localPort); + } else { + handler->startClient(connectToHost.c_str(), connectToPort); + } + + looper->start(true /* runOnCallingThread */); + + return 0; +} + diff --git a/media/libstagefright/wifi-display/wfd.cpp b/media/libstagefright/wifi-display/wfd.cpp index c947765..9fee4d0 100644 --- a/media/libstagefright/wifi-display/wfd.cpp +++ b/media/libstagefright/wifi-display/wfd.cpp @@ -18,6 +18,7 @@ #define LOG_TAG "wfd" #include <utils/Log.h> +#include "sink/WifiDisplaySink.h" #include "source/WifiDisplaySource.h" #include <binder/ProcessState.h> @@ -38,8 +39,12 @@ namespace android { static void usage(const char *me) { fprintf(stderr, "usage:\n" - " %s -l iface[:port]\tcreate a wifi display source\n" - " -f(ilename) \tstream media\n", + " %s -c host[:port]\tconnect to wifi source\n" + " -u uri \tconnect to an rtsp uri\n" + " -l ip[:port] \tlisten on the specified port " + " -f(ilename) \tstream media " + "(create a sink)\n" + " -s(pecial) \trun in 'special' mode\n", me); } @@ -209,14 +214,48 @@ int main(int argc, char **argv) { DataSource::RegisterDefaultSniffers(); + AString connectToHost; + int32_t connectToPort = -1; + AString uri; + AString listenOnAddr; int32_t listenOnPort = -1; AString path; + bool specialMode = false; + int res; - while ((res = getopt(argc, argv, "hl:f:")) >= 0) { + while ((res = getopt(argc, argv, "hc:l:u:f:s")) >= 0) { switch (res) { + case 'c': + { + const char *colonPos = strrchr(optarg, ':'); + + if (colonPos == NULL) { + connectToHost = optarg; + connectToPort = WifiDisplaySource::kWifiDisplayDefaultPort; + } else { + connectToHost.setTo(optarg, colonPos - optarg); + + char *end; + connectToPort = strtol(colonPos + 1, &end, 10); + + if (*end != '\0' || end == colonPos + 1 + || connectToPort < 1 || connectToPort > 65535) { + fprintf(stderr, "Illegal port specified.\n"); + exit(1); + } + } + break; + } + + case 'u': + { + uri = optarg; + break; + } + case 'f': { path = optarg; @@ -245,6 +284,12 @@ int main(int argc, char **argv) { break; } + case 's': + { + specialMode = true; + break; + } + case '?': case 'h': default: @@ -253,6 +298,13 @@ int main(int argc, char **argv) { } } + if (connectToPort >= 0 && listenOnPort >= 0) { + fprintf(stderr, + "You can connect to a source or create one, " + "but not both at the same time.\n"); + exit(1); + } + if (listenOnPort >= 0) { if (path.empty()) { createSource(listenOnAddr, listenOnPort); @@ -263,7 +315,72 @@ int main(int argc, char **argv) { exit(0); } - usage(argv[0]); + if (connectToPort < 0 && uri.empty()) { + fprintf(stderr, + "You need to select either source host or uri.\n"); + + exit(1); + } + + if (connectToPort >= 0 && !uri.empty()) { + fprintf(stderr, + "You need to either connect to a wfd host or an rtsp url, " + "not both.\n"); + exit(1); + } + + sp<SurfaceComposerClient> composerClient = new SurfaceComposerClient; + CHECK_EQ(composerClient->initCheck(), (status_t)OK); + + sp<IBinder> display(SurfaceComposerClient::getBuiltInDisplay( + ISurfaceComposer::eDisplayIdMain)); + DisplayInfo info; + SurfaceComposerClient::getDisplayInfo(display, &info); + ssize_t displayWidth = info.w; + ssize_t displayHeight = info.h; + + ALOGV("display is %d x %d\n", displayWidth, displayHeight); + + sp<SurfaceControl> control = + composerClient->createSurface( + String8("A Surface"), + displayWidth, + displayHeight, + PIXEL_FORMAT_RGB_565, + 0); + + CHECK(control != NULL); + CHECK(control->isValid()); + + SurfaceComposerClient::openGlobalTransaction(); + CHECK_EQ(control->setLayer(INT_MAX), (status_t)OK); + CHECK_EQ(control->show(), (status_t)OK); + SurfaceComposerClient::closeGlobalTransaction(); + + sp<Surface> surface = control->getSurface(); + CHECK(surface != NULL); + + sp<ANetworkSession> session = new ANetworkSession; + session->start(); + + sp<ALooper> looper = new ALooper; + + sp<WifiDisplaySink> sink = new WifiDisplaySink( + specialMode ? WifiDisplaySink::FLAG_SPECIAL_MODE : 0 /* flags */, + session, + surface->getIGraphicBufferProducer()); + + looper->registerHandler(sink); + + if (connectToPort >= 0) { + sink->start(connectToHost.c_str(), connectToPort); + } else { + sink->start(uri.c_str()); + } + + looper->start(true /* runOnCallingThread */); + + composerClient->dispose(); return 0; } diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk index 061a079..714854e 100644 --- a/services/audioflinger/Android.mk +++ b/services/audioflinger/Android.mk @@ -81,6 +81,8 @@ else LOCAL_CFLAGS += -DANDROID_SMP=0 endif +LOCAL_CFLAGS += -fvisibility=hidden + include $(BUILD_SHARED_LIBRARY) # diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 87eb6aa..a6edb77 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -1453,10 +1453,18 @@ audio_io_handle_t AudioFlinger::openOutput(audio_module_handle_t module, } mPlaybackThreads.add(id, thread); - if (pSamplingRate != NULL) *pSamplingRate = config.sample_rate; - if (pFormat != NULL) *pFormat = config.format; - if (pChannelMask != NULL) *pChannelMask = config.channel_mask; - if (pLatencyMs != NULL) *pLatencyMs = thread->latency(); + if (pSamplingRate != NULL) { + *pSamplingRate = config.sample_rate; + } + if (pFormat != NULL) { + *pFormat = config.format; + } + if (pChannelMask != NULL) { + *pChannelMask = config.channel_mask; + } + if (pLatencyMs != NULL) { + *pLatencyMs = thread->latency(); + } // notify client processes of the new output creation thread->audioConfigChanged_l(AudioSystem::OUTPUT_OPENED); @@ -1698,9 +1706,15 @@ audio_io_handle_t AudioFlinger::openInput(audio_module_handle_t module, ); mRecordThreads.add(id, thread); ALOGV("openInput() created record thread: ID %d thread %p", id, thread); - if (pSamplingRate != NULL) *pSamplingRate = reqSamplingRate; - if (pFormat != NULL) *pFormat = config.format; - if (pChannelMask != NULL) *pChannelMask = reqChannels; + if (pSamplingRate != NULL) { + *pSamplingRate = reqSamplingRate; + } + if (pFormat != NULL) { + *pFormat = config.format; + } + if (pChannelMask != NULL) { + *pChannelMask = reqChannels; + } // notify client processes of the new input creation thread->audioConfigChanged_l(AudioSystem::INPUT_OPENED); diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index b0efef6..cf68848 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -24,6 +24,8 @@ #include <common_time/cc_helper.h> +#include <cutils/compiler.h> + #include <media/IAudioFlinger.h> #include <media/IAudioFlingerClient.h> #include <media/IAudioTrack.h> @@ -89,7 +91,7 @@ class AudioFlinger : { friend class BinderService<AudioFlinger>; // for AudioFlinger() public: - static const char* getServiceName() { return "media.audio_flinger"; } + static const char* getServiceName() ANDROID_API { return "media.audio_flinger"; } virtual status_t dump(int fd, const Vector<String16>& args); @@ -278,7 +280,7 @@ private: bool btNrecIsOff() const { return mBtNrecIsOff; } - AudioFlinger(); + AudioFlinger() ANDROID_API; virtual ~AudioFlinger(); // call in any IAudioFlinger method that accesses mPrimaryHardwareDev diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h index 35cf368..53238fa 100644 --- a/services/audioflinger/AudioPolicyService.h +++ b/services/audioflinger/AudioPolicyService.h @@ -19,6 +19,7 @@ #include <cutils/misc.h> #include <cutils/config_utils.h> +#include <cutils/compiler.h> #include <utils/String8.h> #include <utils/Vector.h> #include <utils/SortedVector.h> @@ -44,7 +45,7 @@ class AudioPolicyService : public: // for BinderService - static const char *getServiceName() { return "media.audio_policy"; } + static const char *getServiceName() ANDROID_API { return "media.audio_policy"; } virtual status_t dump(int fd, const Vector<String16>& args); @@ -137,7 +138,7 @@ public: virtual status_t setVoiceVolume(float volume, int delayMs = 0); private: - AudioPolicyService(); + AudioPolicyService() ANDROID_API; virtual ~AudioPolicyService(); status_t dumpInternals(int fd); diff --git a/services/audioflinger/AudioResampler.h b/services/audioflinger/AudioResampler.h index 2b8694f..29dc5b6 100644 --- a/services/audioflinger/AudioResampler.h +++ b/services/audioflinger/AudioResampler.h @@ -19,13 +19,14 @@ #include <stdint.h> #include <sys/types.h> +#include <cutils/compiler.h> #include <media/AudioBufferProvider.h> namespace android { // ---------------------------------------------------------------------------- -class AudioResampler { +class ANDROID_API AudioResampler { public: // Determines quality of SRC. // LOW_QUALITY: linear interpolator (1st order) diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp index 97f66f4..ee52fcb 100644 --- a/services/audioflinger/Threads.cpp +++ b/services/audioflinger/Threads.cpp @@ -495,7 +495,8 @@ void AudioFlinger::ThreadBase::acquireWakeLock_l() sp<IBinder> binder = new BBinder(); status_t status = mPowerManager->acquireWakeLock(POWERMANAGER_PARTIAL_WAKE_LOCK, binder, - String16(mName)); + String16(mName), + String16("media")); if (status == NO_ERROR) { mWakeLockToken = binder; } @@ -1713,7 +1714,7 @@ void AudioFlinger::PlaybackThread::cacheParameters_l() void AudioFlinger::PlaybackThread::invalidateTracks(audio_stream_type_t streamType) { - ALOGV ("MixerThread::invalidateTracks() mixer %p, streamType %d, mTracks.size %d", + ALOGV("MixerThread::invalidateTracks() mixer %p, streamType %d, mTracks.size %d", this, streamType, mTracks.size()); Mutex::Autolock _l(mLock); @@ -3969,6 +3970,7 @@ status_t AudioFlinger::RecordThread::start(RecordThread::RecordTrack* recordTrac ALOGV("Record started OK"); return status; } + startError: AudioSystem::stopInput(mId); clearSyncStartEvent(); diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp index 5ac3129..41a763d 100644 --- a/services/audioflinger/Tracks.cpp +++ b/services/audioflinger/Tracks.cpp @@ -1477,7 +1477,7 @@ bool AudioFlinger::PlaybackThread::OutputTrack::write(int16_t* data, uint32_t fr memset(pInBuffer->raw, 0, startFrames * channelCount * sizeof(int16_t)); mBufferQueue.add(pInBuffer); } else { - ALOGW ("OutputTrack::write() %p no more buffers in queue", this); + ALOGW("OutputTrack::write() %p no more buffers in queue", this); } } } @@ -1499,7 +1499,7 @@ bool AudioFlinger::PlaybackThread::OutputTrack::write(int16_t* data, uint32_t fr mOutBuffer.frameCount = pInBuffer->frameCount; nsecs_t startTime = systemTime(); if (obtainBuffer(&mOutBuffer, waitTimeLeftMs) == (status_t)NO_MORE_BUFFERS) { - ALOGV ("OutputTrack::write() %p thread %p no more output buffers", this, + ALOGV("OutputTrack::write() %p thread %p no more output buffers", this, mThread.unsafe_get()); outputBufferFull = true; break; diff --git a/services/camera/libcameraservice/Camera2Client.cpp b/services/camera/libcameraservice/Camera2Client.cpp index 081fdec..5b8f25a 100644 --- a/services/camera/libcameraservice/Camera2Client.cpp +++ b/services/camera/libcameraservice/Camera2Client.cpp @@ -616,27 +616,94 @@ void Camera2Client::setPreviewCallbackFlagL(Parameters ¶ms, int flag) { params.previewCallbackOneShot = true; } if (params.previewCallbackFlags != (uint32_t)flag) { + + if (flag != CAMERA_FRAME_CALLBACK_FLAG_NOOP) { + // Disable any existing preview callback window when enabling + // preview callback flags + res = mCallbackProcessor->setCallbackWindow(NULL); + if (res != OK) { + ALOGE("%s: Camera %d: Unable to clear preview callback surface:" + " %s (%d)", __FUNCTION__, mCameraId, strerror(-res), res); + return; + } + params.previewCallbackSurface = false; + } + params.previewCallbackFlags = flag; + switch(params.state) { + case Parameters::PREVIEW: + res = startPreviewL(params, true); + break; + case Parameters::RECORD: + case Parameters::VIDEO_SNAPSHOT: + res = startRecordingL(params, true); + break; + default: + break; + } + if (res != OK) { + ALOGE("%s: Camera %d: Unable to refresh request in state %s", + __FUNCTION__, mCameraId, + Parameters::getStateName(params.state)); + } + } + +} + +status_t Camera2Client::setPreviewCallbackTarget( + const sp<IGraphicBufferProducer>& callbackProducer) { + ATRACE_CALL(); + ALOGV("%s: E", __FUNCTION__); + Mutex::Autolock icl(mBinderSerializationLock); + status_t res; + if ( (res = checkPid(__FUNCTION__) ) != OK) return res; + + sp<ANativeWindow> window; + if (callbackProducer != 0) { + window = new Surface(callbackProducer); + } + + res = mCallbackProcessor->setCallbackWindow(window); + if (res != OK) { + ALOGE("%s: Camera %d: Unable to set preview callback surface: %s (%d)", + __FUNCTION__, mCameraId, strerror(-res), res); + return res; + } + + SharedParameters::Lock l(mParameters); + + if (window != NULL) { + // Disable traditional callbacks when a valid callback target is given + l.mParameters.previewCallbackFlags = CAMERA_FRAME_CALLBACK_FLAG_NOOP; + l.mParameters.previewCallbackOneShot = false; + l.mParameters.previewCallbackSurface = true; + } else { + // Disable callback target if given a NULL interface. + l.mParameters.previewCallbackSurface = false; + } + + switch(l.mParameters.state) { case Parameters::PREVIEW: - res = startPreviewL(params, true); + res = startPreviewL(l.mParameters, true); break; case Parameters::RECORD: case Parameters::VIDEO_SNAPSHOT: - res = startRecordingL(params, true); + res = startRecordingL(l.mParameters, true); break; default: break; - } - if (res != OK) { - ALOGE("%s: Camera %d: Unable to refresh request in state %s", - __FUNCTION__, mCameraId, - Parameters::getStateName(params.state)); - } + } + if (res != OK) { + ALOGE("%s: Camera %d: Unable to refresh request in state %s", + __FUNCTION__, mCameraId, + Parameters::getStateName(l.mParameters.state)); } + return OK; } + status_t Camera2Client::startPreview() { ATRACE_CALL(); ALOGV("%s: E", __FUNCTION__); @@ -683,8 +750,10 @@ status_t Camera2Client::startPreviewL(Parameters ¶ms, bool restart) { } Vector<uint8_t> outputStreams; - bool callbacksEnabled = params.previewCallbackFlags & - CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK; + bool callbacksEnabled = (params.previewCallbackFlags & + CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK) || + params.previewCallbackSurface; + if (callbacksEnabled) { res = mCallbackProcessor->updateStream(params); if (res != OK) { @@ -905,8 +974,10 @@ status_t Camera2Client::startRecordingL(Parameters ¶ms, bool restart) { } Vector<uint8_t> outputStreams; - bool callbacksEnabled = params.previewCallbackFlags & - CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK; + bool callbacksEnabled = (params.previewCallbackFlags & + CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK) || + params.previewCallbackSurface; + if (callbacksEnabled) { res = mCallbackProcessor->updateStream(params); if (res != OK) { diff --git a/services/camera/libcameraservice/Camera2Client.h b/services/camera/libcameraservice/Camera2Client.h index 8ab46b1..078e3a3 100644 --- a/services/camera/libcameraservice/Camera2Client.h +++ b/services/camera/libcameraservice/Camera2Client.h @@ -51,6 +51,9 @@ public: virtual status_t setPreviewTexture( const sp<IGraphicBufferProducer>& bufferProducer); virtual void setPreviewCallbackFlag(int flag); + virtual status_t setPreviewCallbackTarget( + const sp<IGraphicBufferProducer>& callbackProducer); + virtual status_t startPreview(); virtual void stopPreview(); virtual bool previewEnabled(); diff --git a/services/camera/libcameraservice/Camera3Device.cpp b/services/camera/libcameraservice/Camera3Device.cpp index bc4db91..58d98ef 100644 --- a/services/camera/libcameraservice/Camera3Device.cpp +++ b/services/camera/libcameraservice/Camera3Device.cpp @@ -128,7 +128,10 @@ status_t Camera3Device::initialize(camera_module_t *module) /** Initialize device with callback functions */ + ATRACE_BEGIN("camera3->initialize"); res = device->ops->initialize(device, this); + ATRACE_END(); + if (res != OK) { SET_ERR_L("Unable to initialize HAL device: %s (%d)", strerror(-res), res); @@ -140,7 +143,9 @@ status_t Camera3Device::initialize(camera_module_t *module) mVendorTagOps.get_camera_vendor_section_name = NULL; + ATRACE_BEGIN("camera3->get_metadata_vendor_tag_ops"); device->ops->get_metadata_vendor_tag_ops(device, &mVendorTagOps); + ATRACE_END(); if (mVendorTagOps.get_camera_vendor_section_name != NULL) { res = set_camera_metadata_vendor_tag_ops(&mVendorTagOps); @@ -726,7 +731,7 @@ status_t Camera3Device::deleteReprocessStream(int id) { status_t Camera3Device::createDefaultRequest(int templateId, CameraMetadata *request) { ATRACE_CALL(); - ALOGV("%s: E", __FUNCTION__); + ALOGV("%s: for template %d", __FUNCTION__, templateId); Mutex::Autolock l(mLock); switch (mStatus) { @@ -746,8 +751,10 @@ status_t Camera3Device::createDefaultRequest(int templateId, } const camera_metadata_t *rawRequest; + ATRACE_BEGIN("camera3->construct_default_request_settings"); rawRequest = mHal3Device->ops->construct_default_request_settings( mHal3Device, templateId); + ATRACE_END(); if (rawRequest == NULL) { SET_ERR_L("HAL is unable to construct default settings for template %d", templateId); @@ -1057,8 +1064,9 @@ status_t Camera3Device::configureStreamsLocked() { // Do the HAL configuration; will potentially touch stream // max_buffers, usage, priv fields. - + ATRACE_BEGIN("camera3->configure_streams"); res = mHal3Device->ops->configure_streams(mHal3Device, &config); + ATRACE_END(); if (res != OK) { SET_ERR_L("Unable to configure streams with HAL: %s (%d)", @@ -1213,6 +1221,7 @@ void Camera3Device::processCaptureResult(const camera3_capture_result *result) { } if (request.haveResultMetadata && request.numBuffersLeft == 0) { + ATRACE_ASYNC_END("frame capture", frameNumber); mInFlightMap.removeItemsAt(idx, 1); } @@ -1263,8 +1272,7 @@ void Camera3Device::processCaptureResult(const camera3_capture_result *result) { if (entry.count == 0) { SET_ERR("No timestamp provided by HAL for frame %d!", frameNumber); - } - if (timestamp != entry.data.i64[0]) { + } else if (timestamp != entry.data.i64[0]) { SET_ERR("Timestamp mismatch between shutter notify and result" " metadata for frame %d (%lld vs %lld respectively)", frameNumber, timestamp, entry.data.i64[0]); @@ -1363,6 +1371,7 @@ void Camera3Device::processCaptureResult(const camera3_capture_result *result) { } void Camera3Device::notify(const camera3_notify_msg *msg) { + ATRACE_CALL(); NotificationListener *listener; { Mutex::Autolock l(mOutputLock); @@ -1383,6 +1392,9 @@ void Camera3Device::notify(const camera3_notify_msg *msg) { msg->message.error.error_stream); streamId = stream->getId(); } + ALOGV("Camera %d: %s: HAL error, frame %d, stream %d: %d", + mId, __FUNCTION__, msg->message.error.frame_number, + streamId, msg->message.error.error_code); if (listener != NULL) { listener->notifyError(msg->message.error.error_code, msg->message.error.frame_number, streamId); @@ -1418,7 +1430,8 @@ void Camera3Device::notify(const camera3_notify_msg *msg) { frameNumber); break; } - + ALOGVV("Camera %d: %s: Shutter fired for frame %d at %lld", + mId, __FUNCTION__, frameNumber, timestamp); // Call listener, if any if (listener != NULL) { listener->notifyShutter(frameNumber, timestamp); @@ -1539,6 +1552,7 @@ void Camera3Device::RequestThread::setPaused(bool paused) { } status_t Camera3Device::RequestThread::waitUntilPaused(nsecs_t timeout) { + ATRACE_CALL(); status_t res; Mutex::Autolock l(mPauseLock); while (!mPaused) { @@ -1685,8 +1699,11 @@ bool Camera3Device::RequestThread::threadLoop() { } // Submit request and block until ready for next one - + ATRACE_ASYNC_BEGIN("frame capture", request.frame_number); + ATRACE_BEGIN("camera3->process_capture_request"); res = mHal3Device->ops->process_capture_request(mHal3Device, &request); + ATRACE_END(); + if (res != OK) { SET_ERR("RequestThread: Unable to submit capture request %d to HAL" " device: %s (%d)", request.frame_number, strerror(-res), res); diff --git a/services/camera/libcameraservice/CameraClient.cpp b/services/camera/libcameraservice/CameraClient.cpp index e577fa3..be78f69 100644 --- a/services/camera/libcameraservice/CameraClient.cpp +++ b/services/camera/libcameraservice/CameraClient.cpp @@ -347,6 +347,12 @@ void CameraClient::setPreviewCallbackFlag(int callback_flag) { } } +status_t CameraClient::setPreviewCallbackTarget( + const sp<IGraphicBufferProducer>& callbackProducer) { + ALOGE("%s: Unimplemented!", __FUNCTION__); + return INVALID_OPERATION; +} + // start preview mode status_t CameraClient::startPreview() { LOG1("startPreview (pid %d)", getCallingPid()); diff --git a/services/camera/libcameraservice/CameraClient.h b/services/camera/libcameraservice/CameraClient.h index 7f0cb29..abde75a 100644 --- a/services/camera/libcameraservice/CameraClient.h +++ b/services/camera/libcameraservice/CameraClient.h @@ -40,6 +40,8 @@ public: virtual status_t setPreviewDisplay(const sp<Surface>& surface); virtual status_t setPreviewTexture(const sp<IGraphicBufferProducer>& bufferProducer); virtual void setPreviewCallbackFlag(int flag); + virtual status_t setPreviewCallbackTarget( + const sp<IGraphicBufferProducer>& callbackProducer); virtual status_t startPreview(); virtual void stopPreview(); virtual bool previewEnabled(); diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h index 710f164..eaa316a 100644 --- a/services/camera/libcameraservice/CameraService.h +++ b/services/camera/libcameraservice/CameraService.h @@ -190,6 +190,8 @@ public: virtual status_t setPreviewDisplay(const sp<Surface>& surface) = 0; virtual status_t setPreviewTexture(const sp<IGraphicBufferProducer>& bufferProducer)=0; virtual void setPreviewCallbackFlag(int flag) = 0; + virtual status_t setPreviewCallbackTarget( + const sp<IGraphicBufferProducer>& callbackProducer) = 0; virtual status_t startPreview() = 0; virtual void stopPreview() = 0; virtual bool previewEnabled() = 0; diff --git a/services/camera/libcameraservice/camera2/CallbackProcessor.cpp b/services/camera/libcameraservice/camera2/CallbackProcessor.cpp index 4987ab6..78210ab 100644 --- a/services/camera/libcameraservice/camera2/CallbackProcessor.cpp +++ b/services/camera/libcameraservice/camera2/CallbackProcessor.cpp @@ -37,6 +37,7 @@ CallbackProcessor::CallbackProcessor(sp<Camera2Client> client): mDevice(client->getCameraDevice()), mId(client->getCameraId()), mCallbackAvailable(false), + mCallbackToApp(false), mCallbackStreamId(NO_STREAM) { } @@ -53,6 +54,35 @@ void CallbackProcessor::onFrameAvailable() { } } +status_t CallbackProcessor::setCallbackWindow( + sp<ANativeWindow> callbackWindow) { + ATRACE_CALL(); + status_t res; + + Mutex::Autolock l(mInputMutex); + + sp<Camera2Client> client = mClient.promote(); + if (client == 0) return OK; + sp<CameraDeviceBase> device = client->getCameraDevice(); + + // If the window is changing, clear out stream if it already exists + if (mCallbackWindow != callbackWindow && mCallbackStreamId != NO_STREAM) { + res = device->deleteStream(mCallbackStreamId); + if (res != OK) { + ALOGE("%s: Camera %d: Unable to delete old stream " + "for callbacks: %s (%d)", __FUNCTION__, + client->getCameraId(), strerror(-res), res); + return res; + } + mCallbackStreamId = NO_STREAM; + mCallbackConsumer.clear(); + } + mCallbackWindow = callbackWindow; + mCallbackToApp = (mCallbackWindow != NULL); + + return OK; +} + status_t CallbackProcessor::updateStream(const Parameters ¶ms) { ATRACE_CALL(); status_t res; @@ -67,14 +97,18 @@ status_t CallbackProcessor::updateStream(const Parameters ¶ms) { // If possible, use the flexible YUV format int32_t callbackFormat = params.previewFormat; - if (params.fastInfo.useFlexibleYuv && + if (mCallbackToApp) { + // TODO: etalvala: This should use the flexible YUV format as well, but + // need to reconcile HAL2/HAL3 requirements. + callbackFormat = HAL_PIXEL_FORMAT_YV12; + } else if(params.fastInfo.useFlexibleYuv && (params.previewFormat == HAL_PIXEL_FORMAT_YCrCb_420_SP || params.previewFormat == HAL_PIXEL_FORMAT_YV12) ) { callbackFormat = HAL_PIXEL_FORMAT_YCbCr_420_888; } - if (mCallbackConsumer == 0) { - // Create CPU buffer queue endpoint + if (!mCallbackToApp && mCallbackConsumer == 0) { + // Create CPU buffer queue endpoint, since app hasn't given us one mCallbackConsumer = new CpuConsumer(kCallbackHeapCount); mCallbackConsumer->setFrameAvailableListener(this); mCallbackConsumer->setName(String8("Camera2Client::CallbackConsumer")); @@ -104,8 +138,8 @@ status_t CallbackProcessor::updateStream(const Parameters ¶ms) { res = device->deleteStream(mCallbackStreamId); if (res != OK) { ALOGE("%s: Camera %d: Unable to delete old output stream " - "for callbacks: %s (%d)", __FUNCTION__, mId, - strerror(-res), res); + "for callbacks: %s (%d)", __FUNCTION__, + mId, strerror(-res), res); return res; } mCallbackStreamId = NO_STREAM; diff --git a/services/camera/libcameraservice/camera2/CallbackProcessor.h b/services/camera/libcameraservice/camera2/CallbackProcessor.h index d851a84..17dcfb1 100644 --- a/services/camera/libcameraservice/camera2/CallbackProcessor.h +++ b/services/camera/libcameraservice/camera2/CallbackProcessor.h @@ -45,6 +45,8 @@ class CallbackProcessor: void onFrameAvailable(); + // Set to NULL to disable the direct-to-app callback window + status_t setCallbackWindow(sp<ANativeWindow> callbackWindow); status_t updateStream(const Parameters ¶ms); status_t deleteStream(); int getStreamId() const; @@ -64,6 +66,9 @@ class CallbackProcessor: NO_STREAM = -1 }; + // True if mCallbackWindow is a remote consumer, false if just the local + // mCallbackConsumer + bool mCallbackToApp; int mCallbackStreamId; static const size_t kCallbackHeapCount = 6; sp<CpuConsumer> mCallbackConsumer; diff --git a/services/camera/libcameraservice/camera2/JpegCompressor.cpp b/services/camera/libcameraservice/camera2/JpegCompressor.cpp index c9af71e..2f0c67d 100644 --- a/services/camera/libcameraservice/camera2/JpegCompressor.cpp +++ b/services/camera/libcameraservice/camera2/JpegCompressor.cpp @@ -210,7 +210,8 @@ boolean JpegCompressor::jpegEmptyOutputBuffer(j_compress_ptr /*cinfo*/) { return true; } -void JpegCompressor::jpegTermDestination(j_compress_ptr /*cinfo*/) { +void JpegCompressor::jpegTermDestination(j_compress_ptr cinfo) { + (void) cinfo; // TODO: clean up ALOGV("%s", __FUNCTION__); ALOGV("%s: Done writing JPEG data. %d bytes left in buffer", __FUNCTION__, cinfo->dest->free_in_buffer); diff --git a/services/camera/libcameraservice/camera2/Parameters.cpp b/services/camera/libcameraservice/camera2/Parameters.cpp index a248b76..a567c15 100644 --- a/services/camera/libcameraservice/camera2/Parameters.cpp +++ b/services/camera/libcameraservice/camera2/Parameters.cpp @@ -292,8 +292,11 @@ status_t Parameters::initialize(const CameraMetadata *info) { CameraParameters::WHITE_BALANCE_AUTO); camera_metadata_ro_entry_t availableWhiteBalanceModes = - staticInfo(ANDROID_CONTROL_AWB_AVAILABLE_MODES); - { + staticInfo(ANDROID_CONTROL_AWB_AVAILABLE_MODES, 0, 0, false); + if (!availableWhiteBalanceModes.count) { + params.set(CameraParameters::KEY_SUPPORTED_WHITE_BALANCE, + CameraParameters::WHITE_BALANCE_AUTO); + } else { String8 supportedWhiteBalance; bool addComma = false; for (size_t i=0; i < availableWhiteBalanceModes.count; i++) { @@ -353,9 +356,11 @@ status_t Parameters::initialize(const CameraMetadata *info) { CameraParameters::EFFECT_NONE); camera_metadata_ro_entry_t availableEffects = - staticInfo(ANDROID_CONTROL_AVAILABLE_EFFECTS); - if (!availableEffects.count) return NO_INIT; - { + staticInfo(ANDROID_CONTROL_AVAILABLE_EFFECTS, 0, 0, false); + if (!availableEffects.count) { + params.set(CameraParameters::KEY_SUPPORTED_EFFECTS, + CameraParameters::EFFECT_NONE); + } else { String8 supportedEffects; bool addComma = false; for (size_t i=0; i < availableEffects.count; i++) { @@ -413,9 +418,11 @@ status_t Parameters::initialize(const CameraMetadata *info) { CameraParameters::ANTIBANDING_AUTO); camera_metadata_ro_entry_t availableAntibandingModes = - staticInfo(ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES); - if (!availableAntibandingModes.count) return NO_INIT; - { + staticInfo(ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, 0, 0, false); + if (!availableAntibandingModes.count) { + params.set(CameraParameters::KEY_SUPPORTED_ANTIBANDING, + CameraParameters::ANTIBANDING_OFF); + } else { String8 supportedAntibanding; bool addComma = false; for (size_t i=0; i < availableAntibandingModes.count; i++) { @@ -455,9 +462,10 @@ status_t Parameters::initialize(const CameraMetadata *info) { CameraParameters::SCENE_MODE_AUTO); camera_metadata_ro_entry_t availableSceneModes = - staticInfo(ANDROID_CONTROL_AVAILABLE_SCENE_MODES); - if (!availableSceneModes.count) return NO_INIT; - { + staticInfo(ANDROID_CONTROL_AVAILABLE_SCENE_MODES, 0, 0, false); + if (!availableSceneModes.count) { + params.remove(CameraParameters::KEY_SCENE_MODE); + } else { String8 supportedSceneModes(CameraParameters::SCENE_MODE_AUTO); bool addComma = true; bool noSceneModes = false; @@ -548,15 +556,17 @@ status_t Parameters::initialize(const CameraMetadata *info) { } } + bool isFlashAvailable = false; camera_metadata_ro_entry_t flashAvailable = - staticInfo(ANDROID_FLASH_INFO_AVAILABLE, 1, 1); - if (!flashAvailable.count) return NO_INIT; + staticInfo(ANDROID_FLASH_INFO_AVAILABLE, 0, 1, false); + if (flashAvailable.count) { + isFlashAvailable = flashAvailable.data.u8[0]; + } camera_metadata_ro_entry_t availableAeModes = - staticInfo(ANDROID_CONTROL_AE_AVAILABLE_MODES); - if (!availableAeModes.count) return NO_INIT; + staticInfo(ANDROID_CONTROL_AE_AVAILABLE_MODES, 0, 0, false); - if (flashAvailable.data.u8[0]) { + if (isFlashAvailable) { flashMode = Parameters::FLASH_MODE_OFF; params.set(CameraParameters::KEY_FLASH_MODE, CameraParameters::FLASH_MODE_OFF); @@ -585,14 +595,12 @@ status_t Parameters::initialize(const CameraMetadata *info) { } camera_metadata_ro_entry_t minFocusDistance = - staticInfo(ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE, 1, 1); - if (!minFocusDistance.count) return NO_INIT; + staticInfo(ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE, 0, 1, false); camera_metadata_ro_entry_t availableAfModes = - staticInfo(ANDROID_CONTROL_AF_AVAILABLE_MODES); - if (!availableAfModes.count) return NO_INIT; + staticInfo(ANDROID_CONTROL_AF_AVAILABLE_MODES, 0, 0, false); - if (minFocusDistance.data.f[0] == 0) { + if (!minFocusDistance.count || minFocusDistance.data.f[0] == 0) { // Fixed-focus lens focusMode = Parameters::FOCUS_MODE_FIXED; params.set(CameraParameters::KEY_FOCUS_MODE, @@ -662,7 +670,7 @@ status_t Parameters::initialize(const CameraMetadata *info) { focusingAreas.add(Parameters::Area(0,0,0,0,0)); camera_metadata_ro_entry_t availableFocalLengths = - staticInfo(ANDROID_LENS_INFO_AVAILABLE_FOCAL_LENGTHS); + staticInfo(ANDROID_LENS_INFO_AVAILABLE_FOCAL_LENGTHS, 0, 0, false); if (!availableFocalLengths.count) return NO_INIT; float minFocalLength = availableFocalLengths.data.f[0]; @@ -768,8 +776,8 @@ status_t Parameters::initialize(const CameraMetadata *info) { CameraParameters::FALSE); camera_metadata_ro_entry_t availableVideoStabilizationModes = - staticInfo(ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES); - if (!availableVideoStabilizationModes.count) return NO_INIT; + staticInfo(ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES, 0, 0, + false); if (availableVideoStabilizationModes.count > 1) { params.set(CameraParameters::KEY_VIDEO_STABILIZATION_SUPPORTED, @@ -794,9 +802,10 @@ status_t Parameters::initialize(const CameraMetadata *info) { previewCallbackFlags = 0; previewCallbackOneShot = false; + previewCallbackSurface = false; camera_metadata_ro_entry_t supportedHardwareLevel = - staticInfo(ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL); + staticInfo(ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL, 0, 0, false); if (!supportedHardwareLevel.count || (supportedHardwareLevel.data.u8[0] == ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED)) { ALOGI("Camera %d: ZSL mode disabled for limited mode HALs", cameraId); @@ -834,8 +843,8 @@ status_t Parameters::buildFastInfo() { int32_t arrayHeight = activeArraySize.data.i32[1]; camera_metadata_ro_entry_t availableFaceDetectModes = - staticInfo(ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES); - if (!availableFaceDetectModes.count) return NO_INIT; + staticInfo(ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES, 0, 0, + false); uint8_t bestFaceDetectMode = ANDROID_STATISTICS_FACE_DETECT_MODE_OFF; @@ -862,19 +871,21 @@ status_t Parameters::buildFastInfo() { } } + int32_t maxFaces = 0; camera_metadata_ro_entry_t maxFacesDetected = - staticInfo(ANDROID_STATISTICS_INFO_MAX_FACE_COUNT, 1, 1); - if (!maxFacesDetected.count) return NO_INIT; - - int32_t maxFaces = maxFacesDetected.data.i32[0]; + staticInfo(ANDROID_STATISTICS_INFO_MAX_FACE_COUNT, 0, 1, false); + if (maxFacesDetected.count) { + maxFaces = maxFacesDetected.data.i32[0]; + } camera_metadata_ro_entry_t availableSceneModes = - staticInfo(ANDROID_CONTROL_AVAILABLE_SCENE_MODES); + staticInfo(ANDROID_CONTROL_AVAILABLE_SCENE_MODES, 0, 0, false); camera_metadata_ro_entry_t sceneModeOverrides = - staticInfo(ANDROID_CONTROL_SCENE_MODE_OVERRIDES); + staticInfo(ANDROID_CONTROL_SCENE_MODE_OVERRIDES, 0, 0, false); camera_metadata_ro_entry_t minFocusDistance = - staticInfo(ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE); - bool fixedLens = (minFocusDistance.data.f[0] == 0); + staticInfo(ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE, 0, 0, false); + bool fixedLens = minFocusDistance.count == 0 || + minFocusDistance.data.f[0] == 0; camera_metadata_ro_entry_t availableFocalLengths = staticInfo(ANDROID_LENS_INFO_AVAILABLE_FOCAL_LENGTHS); @@ -1465,7 +1476,7 @@ status_t Parameters::set(const String8& paramString) { } if (validatedParams.wbMode != wbMode) { camera_metadata_ro_entry_t availableWbModes = - staticInfo(ANDROID_CONTROL_AWB_AVAILABLE_MODES); + staticInfo(ANDROID_CONTROL_AWB_AVAILABLE_MODES, 0, 0, false); for (i = 0; i < availableWbModes.count; i++) { if (validatedParams.wbMode == availableWbModes.data.u8[i]) break; } @@ -1496,8 +1507,9 @@ status_t Parameters::set(const String8& paramString) { validatedParams.currentAfTriggerId = -1; if (validatedParams.focusMode != Parameters::FOCUS_MODE_FIXED) { camera_metadata_ro_entry_t minFocusDistance = - staticInfo(ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE); - if (minFocusDistance.data.f[0] == 0) { + staticInfo(ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE, 0, 0, + false); + if (minFocusDistance.count && minFocusDistance.data.f[0] == 0) { ALOGE("%s: Requested focus mode \"%s\" is not available: " "fixed focus lens", __FUNCTION__, @@ -1617,7 +1629,8 @@ status_t Parameters::set(const String8& paramString) { validatedParams.videoStabilization = boolFromString( newParams.get(CameraParameters::KEY_VIDEO_STABILIZATION) ); camera_metadata_ro_entry_t availableVideoStabilizationModes = - staticInfo(ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES); + staticInfo(ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES, 0, 0, + false); if (validatedParams.videoStabilization && availableVideoStabilizationModes.count == 1) { ALOGE("%s: Video stabilization not supported", __FUNCTION__); @@ -2544,10 +2557,6 @@ status_t Parameters::calculatePictureFovs(float *horizFov, float *vertFov) staticInfo(ANDROID_SENSOR_INFO_PHYSICAL_SIZE, 2, 2); if (!sensorSize.count) return NO_INIT; - camera_metadata_ro_entry_t availableFocalLengths = - staticInfo(ANDROID_LENS_INFO_AVAILABLE_FOCAL_LENGTHS); - if (!availableFocalLengths.count) return NO_INIT; - float arrayAspect = static_cast<float>(fastInfo.arrayWidth) / fastInfo.arrayHeight; float stillAspect = static_cast<float>(pictureWidth) / pictureHeight; diff --git a/services/camera/libcameraservice/camera2/Parameters.h b/services/camera/libcameraservice/camera2/Parameters.h index be05b54..464830c 100644 --- a/services/camera/libcameraservice/camera2/Parameters.h +++ b/services/camera/libcameraservice/camera2/Parameters.h @@ -142,6 +142,7 @@ struct Parameters { uint32_t previewCallbackFlags; bool previewCallbackOneShot; + bool previewCallbackSurface; bool zslMode; diff --git a/services/camera/libcameraservice/camera3/Camera3OutputStream.cpp b/services/camera/libcameraservice/camera3/Camera3OutputStream.cpp index 2efeede..f085443 100644 --- a/services/camera/libcameraservice/camera3/Camera3OutputStream.cpp +++ b/services/camera/libcameraservice/camera3/Camera3OutputStream.cpp @@ -301,8 +301,13 @@ status_t Camera3OutputStream::configureQueueLocked() { return res; } - ALOGV("%s: Consumer wants %d buffers", __FUNCTION__, - maxConsumerBuffers); + ALOGV("%s: Consumer wants %d buffers, HAL wants %d", __FUNCTION__, + maxConsumerBuffers, camera3_stream::max_buffers); + if (camera3_stream::max_buffers == 0) { + ALOGE("%s: Camera HAL requested no max_buffers, requires at least 1", + __FUNCTION__, camera3_stream::max_buffers); + return INVALID_OPERATION; + } mTotalBufferCount = maxConsumerBuffers + camera3_stream::max_buffers; mDequeuedBufferCount = 0; diff --git a/services/camera/libcameraservice/camera3/Camera3Stream.cpp b/services/camera/libcameraservice/camera3/Camera3Stream.cpp index f05658a..ab563df 100644 --- a/services/camera/libcameraservice/camera3/Camera3Stream.cpp +++ b/services/camera/libcameraservice/camera3/Camera3Stream.cpp @@ -312,8 +312,10 @@ status_t Camera3Stream::registerBuffersLocked(camera3_device *hal3Device) { // Got all buffers, register with HAL ALOGV("%s: Registering %d buffers with camera HAL", __FUNCTION__, bufferCount); + ATRACE_BEGIN("camera3->register_stream_buffers"); res = hal3Device->ops->register_stream_buffers(hal3Device, &bufferSet); + ATRACE_END(); } // Return all valid buffers to stream, in ERROR state to indicate diff --git a/services/camera/libcameraservice/gui/RingBufferConsumer.cpp b/services/camera/libcameraservice/gui/RingBufferConsumer.cpp index cd39bad..dfa1066 100644 --- a/services/camera/libcameraservice/gui/RingBufferConsumer.cpp +++ b/services/camera/libcameraservice/gui/RingBufferConsumer.cpp @@ -214,7 +214,11 @@ status_t RingBufferConsumer::releaseOldestBufferLocked(size_t* pinnedFrames) { // In case the object was never pinned, pass the acquire fence // back to the release fence. If the fence was already waited on, // it'll just be a no-op to wait on it again. - err = addReleaseFenceLocked(item.mBuf, item.mFence); + + // item.mGraphicBuffer was populated with the proper graphic-buffer + // at acquire even if it was previously acquired + err = addReleaseFenceLocked(item.mBuf, + item.mGraphicBuffer, item.mFence); if (err != OK) { BI_LOGE("Failed to add release fence to buffer " @@ -226,7 +230,9 @@ status_t RingBufferConsumer::releaseOldestBufferLocked(size_t* pinnedFrames) { BI_LOGV("Attempting to release buffer timestamp %lld, frame %lld", item.mTimestamp, item.mFrameNumber); - err = releaseBufferLocked(item.mBuf, + // item.mGraphicBuffer was populated with the proper graphic-buffer + // at acquire even if it was previously acquired + err = releaseBufferLocked(item.mBuf, item.mGraphicBuffer, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR); if (err != OK) { @@ -310,7 +316,8 @@ void RingBufferConsumer::unpinBuffer(const BufferItem& item) { RingBufferItem& find = *it; if (item.mGraphicBuffer == find.mGraphicBuffer) { - status_t res = addReleaseFenceLocked(item.mBuf, item.mFence); + status_t res = addReleaseFenceLocked(item.mBuf, + item.mGraphicBuffer, item.mFence); if (res != OK) { BI_LOGE("Failed to add release fence to buffer " |