Merge "Issue 2667802: [Audio Effect Framework] AudioEffect base class and JNI." into kraken
diff --git a/camera/Camera.cpp b/camera/Camera.cpp
index 2fbddd5..0037399 100644
--- a/camera/Camera.cpp
+++ b/camera/Camera.cpp
@@ -107,6 +107,13 @@
return cs->getNumberOfCameras();
}
+status_t Camera::getCameraInfo(int cameraId,
+ struct CameraInfo* cameraInfo) {
+ const sp<ICameraService>& cs = getCameraService();
+ if (cs == 0) return UNKNOWN_ERROR;
+ return cs->getCameraInfo(cameraId, cameraInfo);
+}
+
sp<Camera> Camera::connect(int cameraId)
{
LOGV("connect");
diff --git a/camera/ICameraService.cpp b/camera/ICameraService.cpp
index db1dca6..85f1a29 100644
--- a/camera/ICameraService.cpp
+++ b/camera/ICameraService.cpp
@@ -43,6 +43,18 @@
return reply.readInt32();
}
+ // get information about a camera
+ virtual status_t getCameraInfo(int cameraId,
+ struct CameraInfo* cameraInfo) {
+ Parcel data, reply;
+ 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();
+ }
+
// connect to camera service
virtual sp<ICamera> connect(const sp<ICameraClient>& cameraClient, int cameraId)
{
@@ -68,6 +80,16 @@
reply->writeInt32(getNumberOfCameras());
return NO_ERROR;
} break;
+ case GET_CAMERA_INFO: {
+ CHECK_INTERFACE(ICameraService, data, reply);
+ CameraInfo cameraInfo;
+ memset(&cameraInfo, 0, sizeof(cameraInfo));
+ status_t result = getCameraInfo(data.readInt32(), &cameraInfo);
+ reply->writeInt32(cameraInfo.facing);
+ reply->writeInt32(cameraInfo.orientation);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
case CONNECT: {
CHECK_INTERFACE(ICameraService, data, reply);
sp<ICameraClient> cameraClient = interface_cast<ICameraClient>(data.readStrongBinder());
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk
index 34648b5..80d0d2b 100644
--- a/cmds/stagefright/Android.mk
+++ b/cmds/stagefright/Android.mk
@@ -14,6 +14,7 @@
LOCAL_C_INCLUDES:= \
$(JNI_H_INCLUDE) \
frameworks/base/media/libstagefright \
+ frameworks/base/media/libstagefright/include \
$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include
LOCAL_CFLAGS += -Wno-multichar
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index 94086fa..e2ad9e6 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -26,12 +26,11 @@
#include <binder/ProcessState.h>
#include <media/IMediaPlayerService.h>
#include <media/stagefright/AudioPlayer.h>
-#include <media/stagefright/CachingDataSource.h>
-#include <media/stagefright/FileSource.h>
-#include <media/stagefright/HTTPDataSource.h>
+#include <media/stagefright/DataSource.h>
#include <media/stagefright/JPEGSource.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaExtractor.h>
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MetaData.h>
@@ -482,17 +481,7 @@
for (int k = 0; k < argc; ++k) {
const char *filename = argv[k];
- sp<DataSource> dataSource;
- if (!strncasecmp("http://", filename, 7)) {
- dataSource = new HTTPDataSource(filename);
- if (((HTTPDataSource *)dataSource.get())->connect() != OK) {
- fprintf(stderr, "failed to connect to HTTP server.\n");
- return -1;
- }
- dataSource = new CachingDataSource(dataSource, 32 * 1024, 20);
- } else {
- dataSource = new FileSource(filename);
- }
+ sp<DataSource> dataSource = DataSource::CreateFromURI(filename);
if (dataSource == NULL) {
fprintf(stderr, "Unable to create data source.\n");
diff --git a/include/camera/Camera.h b/include/camera/Camera.h
index 1beac27..9974f2f 100644
--- a/include/camera/Camera.h
+++ b/include/camera/Camera.h
@@ -93,6 +93,32 @@
CAMERA_ERROR_SERVER_DIED = 100
};
+enum {
+ CAMERA_FACING_BACK = 0,
+ CAMERA_FACING_FRONT = 1 /* The camera faces to the user */
+};
+
+struct CameraInfo {
+
+ /**
+ * The direction that the camera faces to. It should be
+ * CAMERA_FACING_BACK or CAMERA_FACING_FRONT.
+ */
+ int facing;
+
+ /**
+ * The orientation of the camera image. The value is the angle that the
+ * camera image needs to be rotated clockwise so it shows correctly on
+ * the display in its natural orientation. It should be 0, 90, 180, or 270.
+ *
+ * For example, suppose a device has a naturally tall screen, but the camera
+ * sensor is mounted in landscape. If the top side of the camera sensor is
+ * aligned with the right edge of the display in natural orientation, the
+ * value should be 90.
+ */
+ int orientation;
+};
+
class ICameraService;
class ICamera;
class Surface;
@@ -114,6 +140,8 @@
// construct a camera client from an existing remote
static sp<Camera> create(const sp<ICamera>& camera);
static int32_t getNumberOfCameras();
+ static status_t getCameraInfo(int cameraId,
+ struct CameraInfo* cameraInfo);
static sp<Camera> connect(int cameraId);
~Camera();
void init();
diff --git a/include/camera/CameraHardwareInterface.h b/include/camera/CameraHardwareInterface.h
index d877c74..1529db7 100644
--- a/include/camera/CameraHardwareInterface.h
+++ b/include/camera/CameraHardwareInterface.h
@@ -213,8 +213,15 @@
virtual status_t dump(int fd, const Vector<String16>& args) const = 0;
};
-/** factory function to instantiate a camera hardware object */
-extern "C" sp<CameraHardwareInterface> openCameraHardware();
+/**
+ * The functions need to be provided by the camera HAL.
+ *
+ * If getNumberOfCameras() returns N, the valid cameraId for getCameraInfo()
+ * and openCameraHardware() is 0 to N-1.
+ */
+extern "C" int HAL_getNumberOfCameras();
+extern "C" void HAL_getCameraInfo(int cameraId, struct CameraInfo* cameraInfo);
+extern "C" sp<CameraHardwareInterface> HAL_openCameraHardware(int cameraId);
}; // namespace android
diff --git a/include/camera/ICameraService.h b/include/camera/ICameraService.h
index dcd434f..7d70c1e 100644
--- a/include/camera/ICameraService.h
+++ b/include/camera/ICameraService.h
@@ -31,6 +31,7 @@
public:
enum {
GET_NUMBER_OF_CAMERAS = IBinder::FIRST_CALL_TRANSACTION,
+ GET_CAMERA_INFO,
CONNECT
};
@@ -38,6 +39,8 @@
DECLARE_META_INTERFACE(CameraService);
virtual int32_t getNumberOfCameras() = 0;
+ virtual status_t getCameraInfo(int cameraId,
+ struct CameraInfo* cameraInfo) = 0;
virtual sp<ICamera> connect(const sp<ICameraClient>& cameraClient,
int cameraId) = 0;
};
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index 7fad1b7..dc783ce 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -93,6 +93,11 @@
// The video is too complex for the decoder: it can't decode frames fast
// enough. Possibly only the audio plays fine at this stage.
MEDIA_INFO_VIDEO_TRACK_LAGGING = 700,
+ // MediaPlayer is temporarily pausing playback internally in order to
+ // buffer more data.
+ MEDIA_INFO_BUFFERING_START = 701,
+ // MediaPlayer is resuming playback after filling buffers.
+ MEDIA_INFO_BUFFERING_END = 702,
// 8xx
// Bad interleaving means that a media has been improperly interleaved or not
// interleaved at all, e.g has all the video samples first then all the audio
diff --git a/include/media/stagefright/AMRWriter.h b/include/media/stagefright/AMRWriter.h
index dd11809..b0eaba4 100644
--- a/include/media/stagefright/AMRWriter.h
+++ b/include/media/stagefright/AMRWriter.h
@@ -37,6 +37,7 @@
virtual bool reachedEOS();
virtual status_t start();
virtual void stop();
+ virtual void pause();
protected:
virtual ~AMRWriter();
@@ -46,6 +47,8 @@
status_t mInitCheck;
sp<MediaSource> mSource;
bool mStarted;
+ volatile bool mPaused;
+ volatile bool mResumed;
volatile bool mDone;
volatile bool mReachedEOS;
pthread_t mThread;
diff --git a/include/media/stagefright/AudioSource.h b/include/media/stagefright/AudioSource.h
index 2519379..860384e 100644
--- a/include/media/stagefright/AudioSource.h
+++ b/include/media/stagefright/AudioSource.h
@@ -51,6 +51,12 @@
AudioRecord *mRecord;
status_t mInitCheck;
bool mStarted;
+
+ bool mCollectStats;
+ int64_t mTotalReadTimeUs;
+ int64_t mTotalReadBytes;
+ int64_t mTotalReads;
+
MediaBufferGroup *mGroup;
AudioSource(const AudioSource &);
diff --git a/include/media/stagefright/CachingDataSource.h b/include/media/stagefright/CachingDataSource.h
deleted file mode 100644
index 42d50e5..0000000
--- a/include/media/stagefright/CachingDataSource.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef CACHING_DATASOURCE_H_
-
-#define CACHING_DATASOURCE_H_
-
-#include <media/stagefright/DataSource.h>
-#include <media/stagefright/MediaErrors.h>
-#include <utils/threads.h>
-
-namespace android {
-
-class CachingDataSource : public DataSource {
-public:
- CachingDataSource(
- const sp<DataSource> &source, size_t pageSize, int numPages);
-
- virtual status_t initCheck() const;
-
- virtual ssize_t readAt(off_t offset, void *data, size_t size);
-
- virtual status_t getSize(off_t *size);
-
- virtual uint32_t flags();
-
-protected:
- virtual ~CachingDataSource();
-
-private:
- struct Page {
- Page *mPrev, *mNext;
- off_t mOffset;
- size_t mLength;
- void *mData;
- };
-
- sp<DataSource> mSource;
- void *mData;
- size_t mPageSize;
- Page *mFirst, *mLast;
-
- Page *allocate_page();
-
- Mutex mLock;
-
- CachingDataSource(const CachingDataSource &);
- CachingDataSource &operator=(const CachingDataSource &);
-};
-
-} // namespace android
-
-#endif // CACHING_DATASOURCE_H_
diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h
index 0a7023a..b2134b4 100644
--- a/include/media/stagefright/CameraSource.h
+++ b/include/media/stagefright/CameraSource.h
@@ -65,6 +65,7 @@
int32_t mNumFramesReceived;
int32_t mNumFramesEncoded;
int32_t mNumFramesDropped;
+ bool mCollectStats;
bool mStarted;
CameraSource(const sp<Camera> &camera);
diff --git a/include/media/stagefright/HTTPDataSource.h b/include/media/stagefright/HTTPDataSource.h
deleted file mode 100644
index 0400bea..0000000
--- a/include/media/stagefright/HTTPDataSource.h
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef HTTP_DATASOURCE_H_
-
-#define HTTP_DATASOURCE_H_
-
-#include <media/stagefright/DataSource.h>
-#include <utils/String8.h>
-#include <utils/threads.h>
-
-namespace android {
-
-class HTTPStream;
-
-class HTTPDataSource : public DataSource {
-public:
- HTTPDataSource(
- const char *host, int port, const char *path,
- const KeyedVector<String8, String8> *headers = NULL);
-
- HTTPDataSource(
- const char *uri,
- const KeyedVector<String8, String8> *headers = NULL);
-
- status_t connect();
- void disconnect();
-
- virtual status_t initCheck() const;
-
- virtual ssize_t readAt(off_t offset, void *data, size_t size);
-
- virtual status_t getSize(off_t *size);
-
- virtual uint32_t flags();
-
-protected:
- virtual ~HTTPDataSource();
-
-private:
- enum {
- kBufferSize = 64 * 1024,
-
- // If we encounter a socket-read error we'll try reconnecting
- // and restarting the read for at most this many times.
- kMaxNumRetries = 3,
- };
-
- enum State {
- DISCONNECTED,
- CONNECTING,
- CONNECTED
- };
-
- State mState;
- mutable Mutex mStateLock;
-
- String8 mHeaders;
-
- String8 mStartingHost;
- String8 mStartingPath;
- int mStartingPort;
-
- HTTPStream *mHttp;
-
- void *mBuffer;
- size_t mBufferLength;
- off_t mBufferOffset;
-
- bool mContentLengthValid;
- unsigned long long mContentLength;
-
- int32_t mNumRetriesLeft;
-
- void init(const KeyedVector<String8, String8> *headers);
-
- ssize_t sendRangeRequest(size_t offset);
- void initHeaders(const KeyedVector<String8, String8> *overrides);
-
- status_t connectWithRedirectsAndRange(off_t rangeStart);
- void applyTimeoutResponse();
-
- HTTPDataSource(const HTTPDataSource &);
- HTTPDataSource &operator=(const HTTPDataSource &);
-};
-
-} // namespace android
-
-#endif // HTTP_DATASOURCE_H_
-
diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index 3c85eca..3d90434 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -39,6 +39,7 @@
virtual status_t start();
virtual bool reachedEOS();
virtual void stop();
+ virtual void pause();
void beginBox(const char *fourcc);
void writeInt8(int8_t x);
@@ -59,6 +60,8 @@
class Track;
FILE *mFile;
+ bool mPaused;
+ bool mStarted;
off_t mOffset;
off_t mMdatOffset;
uint8_t *mMoovBoxBuffer;
@@ -77,6 +80,7 @@
void setStartTimestamp(int64_t timeUs);
int64_t getStartTimestamp(); // Not const
+ status_t startTracks();
void lock();
void unlock();
diff --git a/include/media/stagefright/MediaSource.h b/include/media/stagefright/MediaSource.h
index 96d57e7..9cc94c8 100644
--- a/include/media/stagefright/MediaSource.h
+++ b/include/media/stagefright/MediaSource.h
@@ -20,6 +20,7 @@
#include <sys/types.h>
+#include <media/stagefright/MediaErrors.h>
#include <utils/RefBase.h>
namespace android {
@@ -83,6 +84,13 @@
int64_t mLatenessUs;
};
+ // Causes this source to suspend pulling data from its upstream source
+ // until a subsequent read-with-seek. Currently only supported by
+ // OMXCodec.
+ virtual status_t pause() {
+ return ERROR_UNSUPPORTED;
+ }
+
protected:
virtual ~MediaSource();
diff --git a/include/media/stagefright/MediaWriter.h b/include/media/stagefright/MediaWriter.h
index b15f69c..8528203 100644
--- a/include/media/stagefright/MediaWriter.h
+++ b/include/media/stagefright/MediaWriter.h
@@ -32,6 +32,7 @@
virtual bool reachedEOS() = 0;
virtual status_t start() = 0;
virtual void stop() = 0;
+ virtual void pause() = 0;
virtual void setMaxFileSize(int64_t bytes) { mMaxFileSizeLimitBytes = bytes; }
virtual void setMaxFileDuration(int64_t durationUs) { mMaxFileDurationLimitUs = durationUs; }
virtual void setListener(const sp<IMediaPlayerClient>& listener) {
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index dc2bd50..6a20602 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -30,10 +30,13 @@
// The following keys map to int32_t data unless indicated otherwise.
enum {
kKeyMIMEType = 'mime', // cstring
- kKeyWidth = 'widt',
- kKeyHeight = 'heig',
- kKeyChannelCount = '#chn',
- kKeySampleRate = 'srte',
+ kKeyWidth = 'widt', // int32_t
+ kKeyHeight = 'heig', // int32_t
+ kKeyIFramesInterval = 'ifiv', // int32_t
+ kKeyStride = 'strd', // int32_t
+ kKeySliceHeight = 'slht', // int32_t
+ kKeyChannelCount = '#chn', // int32_t
+ kKeySampleRate = 'srte', // int32_t
kKeyBitRate = 'brte', // int32_t (bps)
kKeyESDS = 'esds', // raw data
kKeyAVCC = 'avcc', // raw data
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index f836c55..c95fc02 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -52,6 +52,8 @@
virtual status_t read(
MediaBuffer **buffer, const ReadOptions *options = NULL);
+ virtual status_t pause();
+
void on_message(const omx_message &msg);
// from MediaBufferObserver
@@ -98,6 +100,7 @@
kDecoderLiesAboutNumberOfChannels = 256,
kInputBufferSizesAreBogus = 512,
kSupportsMultipleFramesPerInputBuffer = 1024,
+ kAvoidMemcopyInputRecordingFrames = 2048,
};
struct BufferInfo {
@@ -143,6 +146,8 @@
Mutex mLock;
Condition mAsyncCompletion;
+ bool mPaused;
+
// A list of indices into mPortStatus[kPortIndexOutput] filled with data.
List<size_t> mFilledBuffers;
Condition mBufferFilled;
@@ -165,10 +170,10 @@
OMX_COLOR_FORMATTYPE colorFormat);
void setVideoInputFormat(
- const char *mime, OMX_U32 width, OMX_U32 height);
+ const char *mime, const sp<MetaData>& meta);
- status_t setupMPEG4EncoderParameters();
- status_t setupAVCEncoderParameters();
+ status_t setupMPEG4EncoderParameters(const sp<MetaData>& meta);
+ status_t setupAVCEncoderParameters(const sp<MetaData>& meta);
status_t setVideoOutputFormat(
const char *mime, OMX_U32 width, OMX_U32 height);
diff --git a/include/media/stagefright/foundation/AHandlerReflector.h b/include/media/stagefright/foundation/AHandlerReflector.h
new file mode 100644
index 0000000..857866a
--- /dev/null
+++ b/include/media/stagefright/foundation/AHandlerReflector.h
@@ -0,0 +1,32 @@
+#ifndef A_HANDLER_REFLECTOR_H_
+
+#define A_HANDLER_REFLECTOR_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+template<class T>
+struct AHandlerReflector : public AHandler {
+ AHandlerReflector(T *target)
+ : mTarget(target) {
+ }
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg) {
+ sp<T> target = mTarget.promote();
+ if (target != NULL) {
+ target->onMessageReceived(msg);
+ }
+ }
+
+private:
+ wp<T> mTarget;
+
+ AHandlerReflector(const AHandlerReflector<T> &);
+ AHandlerReflector<T> &operator=(const AHandlerReflector<T> &);
+};
+
+} // namespace android
+
+#endif // A_HANDLER_REFLECTOR_H_
diff --git a/include/private/README b/include/private/README
new file mode 100644
index 0000000..ee41492
--- /dev/null
+++ b/include/private/README
@@ -0,0 +1,4 @@
+This folder contains private include files.
+
+These include files are part of the private implementation details of
+various framework components. Use at your peril.
diff --git a/include/private/surfaceflinger/SharedBufferStack.h b/include/private/surfaceflinger/SharedBufferStack.h
index c11c855..633b543 100644
--- a/include/private/surfaceflinger/SharedBufferStack.h
+++ b/include/private/surfaceflinger/SharedBufferStack.h
@@ -43,15 +43,6 @@
* unless they are in use by the server, which is only the case for the last
* dequeue-able buffer. When these various conditions are not met, the caller
* waits until the condition is met.
- *
- *
- * CAVEATS:
- *
- * In the current implementation there are several limitations:
- * - buffers must be locked in the same order they've been dequeued
- * - buffers must be enqueued in the same order they've been locked
- * - dequeue() is not reentrant
- * - no error checks are done on the condition above
*
*/
@@ -269,7 +260,9 @@
// ----------------------------------------------------------------------------
-class SharedBufferServer : public SharedBufferBase
+class SharedBufferServer
+ : public SharedBufferBase,
+ public LightRefBase<SharedBufferServer>
{
public:
SharedBufferServer(SharedClient* sharedClient, int surface, int num,
@@ -290,6 +283,9 @@
private:
+ friend class LightRefBase<SharedBufferServer>;
+ ~SharedBufferServer();
+
/*
* BufferList is basically a fixed-capacity sorted-vector of
* unsigned 5-bits ints using a 32-bits int as storage.
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 11f3016..d7ca635 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -771,9 +771,14 @@
}
}
- // Use PV_PLAYER for rtsp for now
if (!strncasecmp(url, "rtsp://", 7)) {
- return PV_PLAYER;
+ char value[PROPERTY_VALUE_MAX];
+ if (!property_get("media.stagefright.enable-rtsp", value, NULL)
+ || (strcmp(value, "1") && strcasecmp(value, "true"))) {
+ // For now, we're going to use PV for rtsp-based playback
+ // by default until we can clear up a few more issues.
+ return PV_PLAYER;
+ }
}
return getDefaultPlayerType();
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 572389f..a7ccce4 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -114,25 +114,27 @@
}
status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera) {
- LOGV("setCamera: pid %d pid %d", IPCThreadState::self()->getCallingPid(), getpid());
+ LOGV("setCamera");
if (camera == 0) {
LOGE("camera is NULL");
return UNKNOWN_ERROR;
}
- mFlags &= ~ FLAGS_SET_CAMERA | FLAGS_HOT_CAMERA;
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
+ mFlags &= ~FLAGS_HOT_CAMERA;
mCamera = Camera::create(camera);
if (mCamera == 0) {
LOGE("Unable to connect to camera");
+ IPCThreadState::self()->restoreCallingIdentity(token);
return UNKNOWN_ERROR;
}
LOGV("Connected to camera");
- mFlags |= FLAGS_SET_CAMERA;
if (mCamera->previewEnabled()) {
LOGV("camera is hot");
mFlags |= FLAGS_HOT_CAMERA;
}
+ IPCThreadState::self()->restoreCallingIdentity(token);
return OK;
}
@@ -287,14 +289,32 @@
status_t StagefrightRecorder::setParamInterleaveDuration(int32_t durationUs) {
LOGV("setParamInterleaveDuration: %d", durationUs);
- if (durationUs <= 20000) { // XXX: 20 ms
+ if (durationUs <= 500000) { // 500 ms
+ // If interleave duration is too small, it is very inefficient to do
+ // interleaving since the metadata overhead will count for a significant
+ // portion of the saved contents
LOGE("Audio/video interleave duration is too small: %d us", durationUs);
return BAD_VALUE;
+ } else if (durationUs >= 10000000) { // 10 seconds
+ // If interleaving duration is too large, it can cause the recording
+ // session to use too much memory since we have to save the output
+ // data before we write them out
+ LOGE("Audio/video interleave duration is too large: %d us", durationUs);
+ return BAD_VALUE;
}
mInterleaveDurationUs = durationUs;
return OK;
}
+// If interval < 0, only the first frame is I frame, and rest are all P frames
+// If interval == 0, all frames are encoded as I frames. No P frames
+// If interval > 0, it is the time spacing between 2 neighboring I frames
+status_t StagefrightRecorder::setParamIFramesInterval(int32_t interval) {
+ LOGV("setParamIFramesInterval: %d seconds", interval);
+ mIFramesInterval = interval;
+ return OK;
+}
+
status_t StagefrightRecorder::setParameter(
const String8 &key, const String8 &value) {
LOGV("setParameter: key (%s) => value (%s)", key.string(), value.string());
@@ -335,6 +355,11 @@
if (safe_strtoi32(value.string(), &durationUs)) {
return setParamInterleaveDuration(durationUs);
}
+ } else if (key == "param-i-frames-interval") {
+ int32_t interval;
+ if (safe_strtoi32(value.string(), &interval)) {
+ return setParamIFramesInterval(interval);
+ }
} else {
LOGE("setParameter: failed to find key %s", key.string());
}
@@ -561,7 +586,12 @@
}
if (mVideoSource == VIDEO_SOURCE_DEFAULT
|| mVideoSource == VIDEO_SOURCE_CAMERA) {
- CHECK(mCamera != NULL);
+
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
+ if (mCamera == 0) {
+ mCamera = Camera::connect(0);
+ mCamera->lock();
+ }
// Set the actual video recording frame size
CameraParameters params(mCamera->getParameters());
@@ -578,6 +608,7 @@
frameHeight < 0 || frameHeight != mVideoHeight) {
LOGE("Failed to set the video frame size to %dx%d",
mVideoWidth, mVideoHeight);
+ IPCThreadState::self()->restoreCallingIdentity(token);
return UNKNOWN_ERROR;
}
@@ -589,6 +620,7 @@
}
CHECK_EQ(OK, mCamera->setPreviewDisplay(mPreviewSurface));
+ IPCThreadState::self()->restoreCallingIdentity(token);
sp<CameraSource> cameraSource =
CameraSource::CreateFromCamera(mCamera);
@@ -619,12 +651,17 @@
sp<MetaData> meta = cameraSource->getFormat();
- int32_t width, height;
+ int32_t width, height, stride, sliceHeight;
CHECK(meta->findInt32(kKeyWidth, &width));
CHECK(meta->findInt32(kKeyHeight, &height));
+ CHECK(meta->findInt32(kKeyStride, &stride));
+ CHECK(meta->findInt32(kKeySliceHeight, &sliceHeight));
enc_meta->setInt32(kKeyWidth, width);
enc_meta->setInt32(kKeyHeight, height);
+ enc_meta->setInt32(kKeyIFramesInterval, mIFramesInterval);
+ enc_meta->setInt32(kKeyStride, stride);
+ enc_meta->setInt32(kKeySliceHeight, sliceHeight);
OMXClient client;
CHECK_EQ(client.connect(), OK);
@@ -655,6 +692,14 @@
return OK;
}
+status_t StagefrightRecorder::pause() {
+ if (mWriter == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ mWriter->pause();
+ return OK;
+}
+
status_t StagefrightRecorder::stop() {
if (mWriter == NULL) {
return UNKNOWN_ERROR;
@@ -670,14 +715,14 @@
stop();
if (mCamera != 0) {
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
if ((mFlags & FLAGS_HOT_CAMERA) == 0) {
LOGV("Camera was cold when we started, stopping preview");
mCamera->stopPreview();
}
- if (mFlags & FLAGS_SET_CAMERA) {
- LOGV("Unlocking camera");
- mCamera->unlock();
- }
+ mCamera->unlock();
+ mCamera = NULL;
+ IPCThreadState::self()->restoreCallingIdentity(token);
mFlags = 0;
}
return OK;
@@ -702,6 +747,7 @@
mAudioChannels = 1;
mAudioBitRate = 12200;
mInterleaveDurationUs = 0;
+ mIFramesInterval = 1;
mOutputFd = -1;
mFlags = 0;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index b7d554b..baf33cf 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -47,6 +47,7 @@
virtual status_t setListener(const sp<IMediaPlayerClient>& listener);
virtual status_t prepare();
virtual status_t start();
+ virtual status_t pause();
virtual status_t stop();
virtual status_t close();
virtual status_t reset();
@@ -75,6 +76,7 @@
int32_t mAudioChannels;
int32_t mSampleRate;
int32_t mInterleaveDurationUs;
+ int32_t mIFramesInterval;
int64_t mMaxFileSizeBytes;
int64_t mMaxFileDurationUs;
@@ -92,6 +94,7 @@
status_t setParamAudioNumberOfChannels(int32_t channles);
status_t setParamAudioSamplingRate(int32_t sampleRate);
status_t setParamInterleaveDuration(int32_t durationUs);
+ status_t setParamIFramesInterval(int32_t interval);
status_t setParamMaxDurationOrFileSize(int64_t limit, bool limit_is_duration);
StagefrightRecorder(const StagefrightRecorder &);
diff --git a/media/libstagefright/AMRWriter.cpp b/media/libstagefright/AMRWriter.cpp
index aec7394..8951f5b 100644
--- a/media/libstagefright/AMRWriter.cpp
+++ b/media/libstagefright/AMRWriter.cpp
@@ -29,13 +29,17 @@
AMRWriter::AMRWriter(const char *filename)
: mFile(fopen(filename, "wb")),
mInitCheck(mFile != NULL ? OK : NO_INIT),
- mStarted(false) {
+ mStarted(false),
+ mPaused(false),
+ mResumed(false) {
}
AMRWriter::AMRWriter(int fd)
: mFile(fdopen(fd, "wb")),
mInitCheck(mFile != NULL ? OK : NO_INIT),
- mStarted(false) {
+ mStarted(false),
+ mPaused(false),
+ mResumed(false) {
}
AMRWriter::~AMRWriter() {
@@ -98,10 +102,19 @@
return mInitCheck;
}
- if (mStarted || mSource == NULL) {
+ if (mSource == NULL) {
return UNKNOWN_ERROR;
}
+ if (mStarted && mPaused) {
+ mPaused = false;
+ mResumed = true;
+ return OK;
+ } else if (mStarted) {
+ // Already started, does nothing
+ return OK;
+ }
+
status_t err = mSource->start();
if (err != OK) {
@@ -123,6 +136,13 @@
return OK;
}
+void AMRWriter::pause() {
+ if (!mStarted) {
+ return;
+ }
+ mPaused = true;
+}
+
void AMRWriter::stop() {
if (!mStarted) {
return;
@@ -163,6 +183,9 @@
mEstimatedDurationUs = 0;
mEstimatedSizeBytes = 0;
bool stoppedPrematurely = true;
+ int64_t previousPausedDurationUs = 0;
+ int64_t maxTimestampUs = 0;
+
while (!mDone) {
MediaBuffer *buffer;
status_t err = mSource->read(&buffer);
@@ -171,6 +194,12 @@
break;
}
+ if (mPaused) {
+ buffer->release();
+ buffer = NULL;
+ continue;
+ }
+
mEstimatedSizeBytes += buffer->range_length();
if (exceedsFileSizeLimit()) {
buffer->release();
@@ -184,6 +213,17 @@
if (timestampUs > mEstimatedDurationUs) {
mEstimatedDurationUs = timestampUs;
}
+ if (mResumed) {
+ previousPausedDurationUs += (timestampUs - maxTimestampUs - 20000);
+ mResumed = false;
+ }
+ timestampUs -= previousPausedDurationUs;
+ LOGV("time stamp: %lld, previous paused duration: %lld",
+ timestampUs, previousPausedDurationUs);
+ if (timestampUs > maxTimestampUs) {
+ maxTimestampUs = timestampUs;
+ }
+
if (exceedsFileDurationLimit()) {
buffer->release();
buffer = NULL;
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index f67826e..00a6995 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -22,19 +22,18 @@
AudioPlayer.cpp \
AudioSource.cpp \
AwesomePlayer.cpp \
- CachingDataSource.cpp \
CameraSource.cpp \
DataSource.cpp \
FileSource.cpp \
- HTTPDataSource.cpp \
HTTPStream.cpp \
JPEGSource.cpp \
MP3Extractor.cpp \
MPEG4Extractor.cpp \
MPEG4Writer.cpp \
MediaExtractor.cpp \
+ NuCachedSource2.cpp \
+ NuHTTPDataSource.cpp \
OggExtractor.cpp \
- Prefetcher.cpp \
SampleIterator.cpp \
SampleTable.cpp \
ShoutcastSource.cpp \
@@ -82,6 +81,7 @@
libvpx \
libstagefright_mpeg2ts \
libstagefright_httplive \
+ libstagefright_rtsp \
LOCAL_SHARED_LIBRARIES += \
libstagefright_amrnb_common \
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
index abd8abc..9717aa6 100644
--- a/media/libstagefright/AudioSource.cpp
+++ b/media/libstagefright/AudioSource.cpp
@@ -25,6 +25,9 @@
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
+#include <cutils/properties.h>
+#include <sys/time.h>
+#include <time.h>
namespace android {
@@ -34,6 +37,10 @@
inputSource, sampleRate, AudioSystem::PCM_16_BIT, channels)),
mInitCheck(mRecord->initCheck()),
mStarted(false),
+ mCollectStats(false),
+ mTotalReadTimeUs(0),
+ mTotalReadBytes(0),
+ mTotalReads(0),
mGroup(NULL) {
}
@@ -55,6 +62,11 @@
return UNKNOWN_ERROR;
}
+ char value[PROPERTY_VALUE_MAX];
+ if (property_get("media.stagefright.record-stats", value, NULL)
+ && (!strcmp(value, "1") || !strcasecmp(value, "true"))) {
+ mCollectStats = true;
+ }
status_t err = mRecord->start();
if (err == OK) {
@@ -79,6 +91,13 @@
mStarted = false;
+ if (mCollectStats) {
+ LOGI("%lld reads: %.2f bps in %lld us",
+ mTotalReads,
+ (mTotalReadBytes * 8000000.0) / mTotalReadTimeUs,
+ mTotalReadTimeUs);
+ }
+
return OK;
}
@@ -95,19 +114,35 @@
status_t AudioSource::read(
MediaBuffer **out, const ReadOptions *options) {
*out = NULL;
+ ++mTotalReads;
MediaBuffer *buffer;
CHECK_EQ(mGroup->acquire_buffer(&buffer), OK);
uint32_t numFramesRecorded;
mRecord->getPosition(&numFramesRecorded);
+ int64_t latency = mRecord->latency() * 1000;
+ uint32_t sampleRate = mRecord->getSampleRate();
+ int64_t timestampUs = (1000000LL * numFramesRecorded) / sampleRate - latency;
+ LOGV("latency: %lld, sample rate: %d, timestamp: %lld",
+ latency, sampleRate, timestampUs);
- buffer->meta_data()->setInt64(
- kKeyTime,
- (1000000ll * numFramesRecorded) / mRecord->getSampleRate()
- - mRecord->latency() * 1000);
+ buffer->meta_data()->setInt64(kKeyTime, timestampUs);
- ssize_t n = mRecord->read(buffer->data(), buffer->size());
+ ssize_t n = 0;
+ if (mCollectStats) {
+ struct timeval tv_start, tv_end;
+ gettimeofday(&tv_start, NULL);
+ n = mRecord->read(buffer->data(), buffer->size());
+ gettimeofday(&tv_end, NULL);
+ mTotalReadTimeUs += ((1000000LL * (tv_end.tv_sec - tv_start.tv_sec))
+ + (tv_end.tv_usec - tv_start.tv_usec));
+ if (n >= 0) {
+ mTotalReadBytes += n;
+ }
+ } else {
+ n = mRecord->read(buffer->data(), buffer->size());
+ }
if (n < 0) {
buffer->release();
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 88c8ee4..4a1580f 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -20,13 +20,15 @@
#include <dlfcn.h>
+#include "include/ARTSPController.h"
#include "include/AwesomePlayer.h"
-#include "include/Prefetcher.h"
+#include "include/LiveSource.h"
#include "include/SoftwareRenderer.h"
+#include "include/NuCachedSource2.h"
+#include "include/ThrottledSource.h"
#include <binder/IPCThreadState.h>
#include <media/stagefright/AudioPlayer.h>
-#include <media/stagefright/CachingDataSource.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/FileSource.h>
#include <media/stagefright/MediaBuffer.h>
@@ -39,7 +41,7 @@
#include <surfaceflinger/ISurface.h>
-#include "include/LiveSource.h"
+#include <media/stagefright/foundation/ALooper.h>
namespace android {
@@ -352,11 +354,7 @@
cancelPlayerEvents();
- if (mPrefetcher != NULL) {
- CHECK_EQ(mPrefetcher->getStrongCount(), 1);
- }
- mPrefetcher.clear();
-
+ mCachedSource.clear();
mAudioTrack.clear();
mVideoTrack.clear();
@@ -393,6 +391,8 @@
mVideoBuffer = NULL;
}
+ mRTSPController.clear();
+
if (mVideoSource != NULL) {
mVideoSource->stop();
@@ -444,30 +444,45 @@
}
mBufferingEventPending = false;
- int64_t durationUs;
- {
- Mutex::Autolock autoLock(mMiscStateLock);
- durationUs = mDurationUs;
+ if (mCachedSource == NULL) {
+ return;
}
- int64_t cachedDurationUs = mPrefetcher->getCachedDurationUs();
+ size_t lowWatermark = 400000;
+ size_t highWatermark = 1000000;
- LOGI("cache holds %.2f secs worth of data.", cachedDurationUs / 1E6);
+ off_t size;
+ if (mDurationUs >= 0 && mCachedSource->getSize(&size) == OK) {
+ int64_t bitrate = size * 8000000ll / mDurationUs; // in bits/sec
- if (durationUs >= 0) {
- int64_t positionUs;
- getPosition(&positionUs);
+ size_t cachedSize = mCachedSource->cachedSize();
+ int64_t cachedDurationUs = cachedSize * 8000000ll / bitrate;
- cachedDurationUs += positionUs;
+ double percentage = (double)cachedDurationUs / mDurationUs;
- double percentage = (double)cachedDurationUs / durationUs;
notifyListener_l(MEDIA_BUFFERING_UPDATE, percentage * 100.0);
- postBufferingEvent_l();
- } else {
- // LOGE("Not sending buffering status because duration is unknown.");
- postBufferingEvent_l();
+ lowWatermark = 2 * bitrate / 8; // 2 secs
+ highWatermark = 10 * bitrate / 8; // 10 secs
}
+
+ bool eos;
+ size_t cachedDataRemaining = mCachedSource->approxDataRemaining(&eos);
+
+ if ((mFlags & PLAYING) && !eos && (cachedDataRemaining < lowWatermark)) {
+ LOGI("cache is running low (< %d) , pausing.", lowWatermark);
+ mFlags |= CACHE_UNDERRUN;
+ pause_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START);
+ } else if ((mFlags & CACHE_UNDERRUN)
+ && (eos || cachedDataRemaining > highWatermark)) {
+ LOGI("cache has filled up (> %d), resuming.", highWatermark);
+ mFlags &= ~CACHE_UNDERRUN;
+ play_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+ }
+
+ postBufferingEvent_l();
}
void AwesomePlayer::onStreamDone() {
@@ -504,6 +519,9 @@
status_t AwesomePlayer::play() {
Mutex::Autolock autoLock(mLock);
+
+ mFlags &= ~CACHE_UNDERRUN;
+
return play_l();
}
@@ -628,6 +646,9 @@
status_t AwesomePlayer::pause() {
Mutex::Autolock autoLock(mLock);
+
+ mFlags &= ~CACHE_UNDERRUN;
+
return pause_l();
}
@@ -648,7 +669,7 @@
}
bool AwesomePlayer::isPlaying() const {
- return mFlags & PLAYING;
+ return (mFlags & PLAYING) || (mFlags & CACHE_UNDERRUN);
}
void AwesomePlayer::setISurface(const sp<ISurface> &isurface) {
@@ -715,6 +736,11 @@
}
status_t AwesomePlayer::seekTo_l(int64_t timeUs) {
+ if (mFlags & CACHE_UNDERRUN) {
+ mFlags &= ~CACHE_UNDERRUN;
+ play_l();
+ }
+
mSeeking = true;
mSeekNotificationSent = false;
mSeekTimeUs = timeUs;
@@ -760,10 +786,6 @@
void AwesomePlayer::setAudioSource(sp<MediaSource> source) {
CHECK(source != NULL);
- if (mPrefetcher != NULL) {
- source = mPrefetcher->addSource(source);
- }
-
mAudioTrack = source;
}
@@ -810,10 +832,6 @@
void AwesomePlayer::setVideoSource(sp<MediaSource> source) {
CHECK(source != NULL);
- if (mPrefetcher != NULL) {
- source = mPrefetcher->addSource(source);
- }
-
mVideoTrack = source;
}
@@ -865,6 +883,21 @@
mVideoBuffer->release();
mVideoBuffer = NULL;
}
+
+ if (mCachedSource != NULL && mAudioSource != NULL) {
+ // We're going to seek the video source first, followed by
+ // the audio source.
+ // In order to avoid jumps in the DataSource offset caused by
+ // the audio codec prefetching data from the old locations
+ // while the video codec is already reading data from the new
+ // locations, we'll "pause" the audio source, causing it to
+ // stop reading input data until a subsequent seek.
+
+ if (mAudioPlayer != NULL) {
+ mAudioPlayer->pause();
+ }
+ mAudioSource->pause();
+ }
}
if (!mVideoBuffer) {
@@ -921,6 +954,7 @@
LOGV("seeking audio to %lld us (%.2f secs).", timeUs, timeUs / 1E6);
mAudioPlayer->seekTo(timeUs);
+ mAudioPlayer->resume();
mWatchForAudioSeekComplete = true;
mWatchForAudioEOS = true;
} else if (!mSeekNotificationSent) {
@@ -1008,10 +1042,6 @@
}
void AwesomePlayer::postBufferingEvent_l() {
- if (mPrefetcher == NULL) {
- return;
- }
-
if (mBufferingEventPending) {
return;
}
@@ -1119,10 +1149,10 @@
sp<DataSource> dataSource;
if (!strncasecmp("http://", mUri.string(), 7)) {
- mConnectingDataSource = new HTTPDataSource(mUri, &mUriHeaders);
+ mConnectingDataSource = new NuHTTPDataSource;
mLock.unlock();
- status_t err = mConnectingDataSource->connect();
+ status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders);
mLock.lock();
if (err != OK) {
@@ -1132,24 +1162,46 @@
return err;
}
- dataSource = new CachingDataSource(
- mConnectingDataSource, 64 * 1024, 10);
-
+#if 0
+ mCachedSource = new NuCachedSource2(
+ new ThrottledSource(
+ mConnectingDataSource, 50 * 1024 /* bytes/sec */));
+#else
+ mCachedSource = new NuCachedSource2(mConnectingDataSource);
+#endif
mConnectingDataSource.clear();
+
+ dataSource = mCachedSource;
} else if (!strncasecmp(mUri.string(), "httplive://", 11)) {
String8 uri("http://");
uri.append(mUri.string() + 11);
dataSource = new LiveSource(uri.string());
- if (dataSource->flags() & DataSource::kWantsPrefetching) {
- mPrefetcher = new Prefetcher;
- }
+ mCachedSource = new NuCachedSource2(dataSource);
+ dataSource = mCachedSource;
sp<MediaExtractor> extractor =
MediaExtractor::Create(dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS);
return setDataSource_l(extractor);
+ } else if (!strncasecmp("rtsp://", mUri.string(), 7)) {
+ if (mLooper == NULL) {
+ mLooper = new ALooper;
+ mLooper->start();
+ }
+ mRTSPController = new ARTSPController(mLooper);
+ status_t err = mRTSPController->connect(mUri.string());
+
+ LOGI("ARTSPController::connect returned %d", err);
+
+ if (err != OK) {
+ mRTSPController.clear();
+ return err;
+ }
+
+ sp<MediaExtractor> extractor = mRTSPController.get();
+ return setDataSource_l(extractor);
} else {
dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);
}
@@ -1164,10 +1216,6 @@
return UNKNOWN_ERROR;
}
- if (dataSource->flags() & DataSource::kWantsPrefetching) {
- mPrefetcher = new Prefetcher;
- }
-
return setDataSource_l(extractor);
}
@@ -1192,8 +1240,6 @@
}
void AwesomePlayer::onPrepareAsyncEvent() {
- sp<Prefetcher> prefetcher;
-
{
Mutex::Autolock autoLock(mLock);
@@ -1229,39 +1275,6 @@
return;
}
}
-
- prefetcher = mPrefetcher;
- }
-
- if (prefetcher != NULL) {
- {
- Mutex::Autolock autoLock(mLock);
- if (mFlags & PREPARE_CANCELLED) {
- LOGI("prepare was cancelled before preparing the prefetcher");
-
- prefetcher.clear();
- abortPrepare(UNKNOWN_ERROR);
- return;
- }
- }
-
- LOGI("calling prefetcher->prepare()");
- status_t result =
- prefetcher->prepare(&AwesomePlayer::ContinuePreparation, this);
-
- prefetcher.clear();
-
- if (result == OK) {
- LOGI("prefetcher is done preparing");
- } else {
- Mutex::Autolock autoLock(mLock);
-
- CHECK_EQ(result, -EINTR);
-
- LOGI("prefetcher->prepare() was cancelled early.");
- abortPrepare(UNKNOWN_ERROR);
- return;
- }
}
Mutex::Autolock autoLock(mLock);
diff --git a/media/libstagefright/CachingDataSource.cpp b/media/libstagefright/CachingDataSource.cpp
deleted file mode 100644
index 1ca463e..0000000
--- a/media/libstagefright/CachingDataSource.cpp
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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.
- */
-
-#include <stdlib.h>
-#include <string.h>
-
-#include <media/stagefright/CachingDataSource.h>
-#include <media/stagefright/MediaDebug.h>
-
-namespace android {
-
-CachingDataSource::CachingDataSource(
- const sp<DataSource> &source, size_t pageSize, int numPages)
- : mSource(source),
- mData(malloc(pageSize * numPages)),
- mPageSize(pageSize),
- mFirst(NULL),
- mLast(NULL) {
- for (int i = 0; i < numPages; ++i) {
- Page *page = new Page;
- page->mPrev = mLast;
- page->mNext = NULL;
-
- if (mLast == NULL) {
- mFirst = page;
- } else {
- mLast->mNext = page;
- }
-
- mLast = page;
-
- page->mOffset = -1;
- page->mLength = 0;
- page->mData = (char *)mData + mPageSize * i;
- }
-}
-
-CachingDataSource::~CachingDataSource() {
- Page *page = mFirst;
- while (page != NULL) {
- Page *next = page->mNext;
- delete page;
- page = next;
- }
- mFirst = mLast = NULL;
-
- free(mData);
- mData = NULL;
-}
-
-status_t CachingDataSource::initCheck() const {
- return mSource->initCheck();
-}
-
-status_t CachingDataSource::getSize(off_t *size) {
- return mSource->getSize(size);
-}
-
-uint32_t CachingDataSource::flags() {
- return mSource->flags();
-}
-
-ssize_t CachingDataSource::readAt(off_t offset, void *data, size_t size) {
- Mutex::Autolock autoLock(mLock);
-
- size_t total = 0;
- while (size > 0) {
- Page *page = mFirst;
- while (page != NULL) {
- if (page->mOffset >= 0 && offset >= page->mOffset
- && offset < page->mOffset + (off_t)page->mLength) {
- break;
- }
- page = page->mNext;
- }
-
- if (page == NULL) {
- page = allocate_page();
- page->mOffset = offset - offset % mPageSize;
- ssize_t n = mSource->readAt(page->mOffset, page->mData, mPageSize);
- if (n < 0) {
- page->mLength = 0;
- } else {
- page->mLength = (size_t)n;
- }
- mFirst->mPrev = page;
- page->mNext = mFirst;
- page->mPrev = NULL;
- mFirst = page;
-
- if (n < 0) {
- return n;
- }
-
- if (offset >= page->mOffset + (off_t)page->mLength) {
- break;
- }
- } else {
- // Move "page" to the front in LRU order.
- if (page->mNext != NULL) {
- page->mNext->mPrev = page->mPrev;
- } else {
- mLast = page->mPrev;
- }
-
- if (page->mPrev != NULL) {
- page->mPrev->mNext = page->mNext;
- } else {
- mFirst = page->mNext;
- }
-
- mFirst->mPrev = page;
- page->mNext = mFirst;
- page->mPrev = NULL;
- mFirst = page;
- }
-
- size_t copy = page->mLength - (offset - page->mOffset);
- if (copy > size) {
- copy = size;
- }
- memcpy(data,(const char *)page->mData + (offset - page->mOffset),
- copy);
-
- total += copy;
-
- if (page->mLength < mPageSize) {
- // This was the final page. There is no more data beyond it.
- break;
- }
-
- offset += copy;
- size -= copy;
- data = (char *)data + copy;
- }
-
- return total;
-}
-
-CachingDataSource::Page *CachingDataSource::allocate_page() {
- // The last page is the least recently used, i.e. oldest.
-
- Page *page = mLast;
-
- page->mPrev->mNext = NULL;
- mLast = page->mPrev;
- page->mPrev = NULL;
-
- return page;
-}
-
-} // namespace android
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 264ac5f..0ab76b3 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -19,7 +19,7 @@
#include <utils/Log.h>
#include <OMX_Component.h>
-
+#include <binder/IPCThreadState.h>
#include <media/stagefright/CameraSource.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
@@ -28,6 +28,7 @@
#include <camera/Camera.h>
#include <camera/CameraParameters.h>
#include <utils/String8.h>
+#include <cutils/properties.h>
namespace android {
@@ -122,11 +123,16 @@
mNumFramesReceived(0),
mNumFramesEncoded(0),
mNumFramesDropped(0),
+ mCollectStats(false),
mStarted(false) {
+
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
String8 s = mCamera->getParameters();
+ IPCThreadState::self()->restoreCallingIdentity(token);
+
printf("params: \"%s\"\n", s.string());
- int32_t width, height;
+ int32_t width, height, stride, sliceHeight;
CameraParameters params(s);
params.getPreviewSize(&width, &height);
@@ -134,11 +140,19 @@
CHECK(colorFormatStr != NULL);
int32_t colorFormat = getColorFormat(colorFormatStr);
+ // XXX: query camera for the stride and slice height
+ // when the capability becomes available.
+ stride = width;
+ sliceHeight = height;
+
mMeta = new MetaData;
mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW);
mMeta->setInt32(kKeyColorFormat, colorFormat);
mMeta->setInt32(kKeyWidth, width);
mMeta->setInt32(kKeyHeight, height);
+ mMeta->setInt32(kKeyStride, stride);
+ mMeta->setInt32(kKeySliceHeight, sliceHeight);
+
}
CameraSource::~CameraSource() {
@@ -151,8 +165,16 @@
LOGV("start");
CHECK(!mStarted);
+ char value[PROPERTY_VALUE_MAX];
+ if (property_get("media.stagefright.record-stats", value, NULL)
+ && (!strcmp(value, "1") || !strcasecmp(value, "true"))) {
+ mCollectStats = true;
+ }
+
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
mCamera->setListener(new CameraSourceListener(this));
CHECK_EQ(OK, mCamera->startRecording());
+ IPCThreadState::self()->restoreCallingIdentity(token);
mStarted = true;
return OK;
@@ -163,19 +185,24 @@
Mutex::Autolock autoLock(mLock);
mStarted = false;
mFrameAvailableCondition.signal();
+
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
mCamera->setListener(NULL);
mCamera->stopRecording();
-
releaseQueuedFrames();
-
while (!mFramesBeingEncoded.empty()) {
- LOGI("Number of outstanding frames is being encoded: %d", mFramesBeingEncoded.size());
+ LOGI("Waiting for outstanding frames being encoded: %d",
+ mFramesBeingEncoded.size());
mFrameCompleteCondition.wait(mLock);
}
+ mCamera = NULL;
+ IPCThreadState::self()->restoreCallingIdentity(token);
- LOGI("Frames received/encoded/dropped: %d/%d/%d, timestamp (us) last/first: %lld/%lld",
- mNumFramesReceived, mNumFramesEncoded, mNumFramesDropped,
- mLastFrameTimestampUs, mFirstFrameTimeUs);
+ if (mCollectStats) {
+ LOGI("Frames received/encoded/dropped: %d/%d/%d in %lld us",
+ mNumFramesReceived, mNumFramesEncoded, mNumFramesDropped,
+ mLastFrameTimestampUs - mFirstFrameTimeUs);
+ }
CHECK_EQ(mNumFramesReceived, mNumFramesEncoded + mNumFramesDropped);
return OK;
@@ -200,7 +227,11 @@
for (List<sp<IMemory> >::iterator it = mFramesBeingEncoded.begin();
it != mFramesBeingEncoded.end(); ++it) {
if ((*it)->pointer() == buffer->data()) {
+
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
mCamera->releaseRecordingFrame((*it));
+ IPCThreadState::self()->restoreCallingIdentity(token);
+
mFramesBeingEncoded.erase(it);
++mNumFramesEncoded;
buffer->setObserver(0);
@@ -252,15 +283,17 @@
void CameraSource::dataCallbackTimestamp(int64_t timestampUs,
int32_t msgType, const sp<IMemory> &data) {
LOGV("dataCallbackTimestamp: timestamp %lld us", timestampUs);
- mLastFrameTimestampUs = timestampUs;
Mutex::Autolock autoLock(mLock);
if (!mStarted) {
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
mCamera->releaseRecordingFrame(data);
+ IPCThreadState::self()->restoreCallingIdentity(token);
++mNumFramesReceived;
++mNumFramesDropped;
return;
}
+ mLastFrameTimestampUs = timestampUs;
if (mNumFramesReceived == 0) {
mFirstFrameTimeUs = timestampUs;
}
diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp
index b569a6b..90a596c 100644
--- a/media/libstagefright/DataSource.cpp
+++ b/media/libstagefright/DataSource.cpp
@@ -20,13 +20,13 @@
#include "include/WAVExtractor.h"
#include "include/OggExtractor.h"
#include "include/MPEG2TSExtractor.h"
+#include "include/NuCachedSource2.h"
+#include "include/NuHTTPDataSource.h"
#include "matroska/MatroskaExtractor.h"
-#include <media/stagefright/CachingDataSource.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/FileSource.h>
-#include <media/stagefright/HTTPDataSource.h>
#include <media/stagefright/MediaErrors.h>
#include <utils/String8.h>
@@ -108,11 +108,11 @@
if (!strncasecmp("file://", uri, 7)) {
source = new FileSource(uri + 7);
} else if (!strncasecmp("http://", uri, 7)) {
- sp<HTTPDataSource> httpSource = new HTTPDataSource(uri, headers);
- if (httpSource->connect() != OK) {
+ sp<NuHTTPDataSource> httpSource = new NuHTTPDataSource;
+ if (httpSource->connect(uri, headers) != OK) {
return NULL;
}
- source = new CachingDataSource(httpSource, 64 * 1024, 10);
+ source = new NuCachedSource2(httpSource);
} else {
// Assume it's a filename.
source = new FileSource(uri);
diff --git a/media/libstagefright/HTTPDataSource.cpp b/media/libstagefright/HTTPDataSource.cpp
deleted file mode 100644
index f72a6cc..0000000
--- a/media/libstagefright/HTTPDataSource.cpp
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * 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.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "HTTPDataSource"
-#include <utils/Log.h>
-
-#include "include/stagefright_string.h"
-#include "include/HTTPStream.h"
-
-#include <stdlib.h>
-
-#include <cutils/properties.h>
-#include <media/stagefright/HTTPDataSource.h>
-#include <media/stagefright/MediaDebug.h>
-
-namespace android {
-
-status_t HTTPDataSource::connectWithRedirectsAndRange(off_t rangeStart) {
- string host = mStartingHost.string();
- string path = mStartingPath.string();
- int port = mStartingPort;
-
- LOGV("Connecting to host '%s', port %d, path '%s'",
- host.c_str(), port, path.c_str());
-
- int numRedirectsRemaining = 5;
- while (numRedirectsRemaining-- > 0) {
- {
- Mutex::Autolock autoLock(mStateLock);
- if (mState == DISCONNECTED) {
- return UNKNOWN_ERROR;
- }
- }
-
- status_t err = mHttp->connect(host.c_str(), port);
-
- if (err != OK) {
- return err;
- }
-
- String8 request;
- request.append("GET ");
- request.append(path.c_str());
- request.append(" HTTP/1.1\r\n");
- request.append(mHeaders);
- request.append("Host: ");
- request.append(host.c_str());
- request.append("\r\n");
-
- if (rangeStart > 0) {
- char range[128];
- sprintf(range, "Range: bytes=%ld-\r\n", rangeStart);
-
- request.append(range);
- }
-
- request.append("\r\n");
-
- err = mHttp->send(request.string());
-
- if (err != OK) {
- return err;
- }
-
- int httpStatus;
- err = mHttp->receive_header(&httpStatus);
-
- if (err != OK) {
- return err;
- }
-
- if (httpStatus >= 200 && httpStatus < 300) {
- applyTimeoutResponse();
- return OK;
- }
-
- if (httpStatus != 301 && httpStatus != 302) {
- LOGE("HTTP request failed w/ http status %d", httpStatus);
- return ERROR_IO;
- }
-
- string location;
- CHECK(mHttp->find_header_value("Location", &location));
-
- CHECK(string(location, 0, 7) == "http://");
- location.erase(0, 7);
- string::size_type slashPos = location.find('/');
- if (slashPos == string::npos) {
- slashPos = location.size();
- location += '/';
- }
-
- mHttp->disconnect();
-
- LOGV("Redirecting to %s\n", location.c_str());
-
- host = string(location, 0, slashPos);
-
- string::size_type colonPos = host.find(':');
- if (colonPos != string::npos) {
- const char *start = host.c_str() + colonPos + 1;
- char *end;
- long tmp = strtol(start, &end, 10);
- CHECK(end > start && (*end == '\0'));
-
- port = (tmp >= 0 && tmp < 65536) ? (int)tmp : 80;
-
- host.erase(colonPos, host.size() - colonPos);
- } else {
- port = 80;
- }
-
- path = string(location, slashPos);
-
- mStartingHost = host.c_str();
- mStartingPath = path.c_str();
- mStartingPort = port;
- }
-
- return ERROR_IO;
-}
-
-void HTTPDataSource::applyTimeoutResponse() {
- string timeout;
- if (mHttp->find_header_value("X-SocketTimeout", &timeout)) {
- const char *s = timeout.c_str();
- char *end;
- long tmp = strtol(s, &end, 10);
- if (end == s || *end != '\0') {
- LOGW("Illegal X-SocketTimeout value given.");
- return;
- }
-
- LOGI("overriding default timeout, new timeout is %ld seconds", tmp);
- mHttp->setReceiveTimeout(tmp);
- }
-}
-
-HTTPDataSource::HTTPDataSource(
- const char *uri, const KeyedVector<String8, String8> *headers) {
- CHECK(!strncasecmp("http://", uri, 7));
-
- string host;
- string path;
- int port;
-
- char *slash = strchr(uri + 7, '/');
- if (slash == NULL) {
- host = uri + 7;
- path = "/";
- } else {
- host = string(uri + 7, slash - (uri + 7));
- path = slash;
- }
-
- char *colon = strchr(host.c_str(), ':');
- if (colon == NULL) {
- port = 80;
- } else {
- char *end;
- long tmp = strtol(colon + 1, &end, 10);
- CHECK(end > colon + 1);
- CHECK(tmp > 0 && tmp < 65536);
- port = tmp;
-
- host = string(host, 0, colon - host.c_str());
- }
-
- mStartingHost = host.c_str();
- mStartingPath = path.c_str();
- mStartingPort = port;
-
- init(headers);
-}
-
-HTTPDataSource::HTTPDataSource(
- const char *_host, int port, const char *_path,
- const KeyedVector<String8, String8> *headers) {
- mStartingHost = _host;
- mStartingPath = _path;
- mStartingPort = port;
-
- init(headers);
-}
-
-void HTTPDataSource::init(const KeyedVector<String8, String8> *headers) {
- mState = DISCONNECTED;
- mHttp = new HTTPStream;
-
- initHeaders(headers);
-
- mBuffer = malloc(kBufferSize);
-
- mNumRetriesLeft = kMaxNumRetries;
-}
-
-status_t HTTPDataSource::connect() {
- {
- Mutex::Autolock autoLock(mStateLock);
-
- if (mState != DISCONNECTED) {
- return ERROR_ALREADY_CONNECTED;
- }
-
- mState = CONNECTING;
- }
-
- mBufferLength = 0;
- mBufferOffset = 0;
- mContentLengthValid = false;
-
- status_t err = connectWithRedirectsAndRange(0);
-
- if (err != OK) {
- Mutex::Autolock autoLock(mStateLock);
-
- if (mState != CONNECTING) {
- LOGV("connect() cancelled");
- }
- mState = DISCONNECTED;
-
- return err;
- }
-
- string value;
- if (mHttp->find_header_value("Content-Length", &value)) {
- char *end;
- mContentLength = strtoull(value.c_str(), &end, 10);
- mContentLengthValid = true;
- }
-
- Mutex::Autolock autoLock(mStateLock);
-
- if (mState != CONNECTING) {
- // disconnect was called when we had just successfully connected.
- LOGV("connect() cancelled (we had just succeeded connecting)");
-
- mHttp->disconnect();
- return UNKNOWN_ERROR;
- }
-
- mState = CONNECTED;
-
- return OK;
-}
-
-void HTTPDataSource::disconnect() {
- Mutex::Autolock autoLock(mStateLock);
-
- if (mState == CONNECTING || mState == CONNECTED) {
- mHttp->disconnect();
- mState = DISCONNECTED;
- }
-}
-
-status_t HTTPDataSource::initCheck() const {
- Mutex::Autolock autoLock(mStateLock);
-
- return (mState == CONNECTED) ? (status_t)OK : ERROR_NOT_CONNECTED;
-}
-
-status_t HTTPDataSource::getSize(off_t *size) {
- *size = 0;
-
- {
- Mutex::Autolock autoLock(mStateLock);
- if (mState != CONNECTED) {
- return ERROR_NOT_CONNECTED;
- }
- }
-
- if (!mContentLengthValid) {
- return ERROR_UNSUPPORTED;
- }
-
- *size = mContentLength;
-
- return OK;
-}
-
-HTTPDataSource::~HTTPDataSource() {
- disconnect();
-
- delete mHttp;
- mHttp = NULL;
-
- free(mBuffer);
- mBuffer = NULL;
-}
-
-ssize_t HTTPDataSource::sendRangeRequest(size_t offset) {
- status_t err = connectWithRedirectsAndRange(offset);
-
- if (err != OK) {
- return err;
- }
-
- string value;
- if (!mHttp->find_header_value("Content-Length", &value)) {
- return kBufferSize;
- }
-
- char *end;
- unsigned long contentLength = strtoul(value.c_str(), &end, 10);
-
- return contentLength;
-}
-
-ssize_t HTTPDataSource::readAt(off_t offset, void *data, size_t size) {
- LOGV("readAt %ld, size %d", offset, size);
-
-rinse_repeat:
- {
- Mutex::Autolock autoLock(mStateLock);
- if (mState != CONNECTED) {
- return ERROR_NOT_CONNECTED;
- }
- }
-
- if (offset >= mBufferOffset
- && offset < (off_t)(mBufferOffset + mBufferLength)) {
- size_t num_bytes_available = mBufferLength - (offset - mBufferOffset);
-
- size_t copy = num_bytes_available;
- if (copy > size) {
- copy = size;
- }
-
- memcpy(data, (const char *)mBuffer + (offset - mBufferOffset), copy);
-
- if (copy < size) {
- LOGV("short read (1), returning %d vs. %d requested", copy, size);
- }
-
- return copy;
- }
-
- ssize_t contentLength = 0;
- if (offset != (off_t)(mBufferOffset + mBufferLength)) {
- LOGV("new range offset=%ld (old=%ld)",
- offset, mBufferOffset + mBufferLength);
-
- mHttp->disconnect();
-
- contentLength = sendRangeRequest(offset);
-
- if (contentLength > kBufferSize) {
- contentLength = kBufferSize;
- }
- } else {
- contentLength = kBufferSize;
- }
-
- mBufferOffset = offset;
-
- if (mContentLengthValid
- && mBufferOffset + contentLength >= (off_t)mContentLength) {
- // If we never triggered a range request but know the content length,
- // make sure to not read more data than there could be, otherwise
- // we'd block indefinitely if the server doesn't close the connection.
-
- contentLength = mContentLength - mBufferOffset;
- }
-
- if (contentLength <= 0) {
- return contentLength;
- }
-
- ssize_t num_bytes_received = mHttp->receive(mBuffer, contentLength);
-
- if (num_bytes_received < 0
- || (mContentLengthValid && num_bytes_received < contentLength)) {
- if (mNumRetriesLeft-- > 0) {
- mHttp->disconnect();
- mBufferLength = 0;
- num_bytes_received = connectWithRedirectsAndRange(mBufferOffset);
- if (num_bytes_received == OK) {
- LOGI("retrying connection succeeded.");
- goto rinse_repeat;
- }
- LOGE("retrying connection failed");
- }
-
- mBufferLength = 0;
-
- return num_bytes_received;
- }
-
- mBufferLength = (size_t)num_bytes_received;
-
- size_t copy = mBufferLength;
- if (copy > size) {
- copy = size;
- }
-
- memcpy(data, mBuffer, copy);
-
- return copy;
-}
-
-void HTTPDataSource::initHeaders(
- const KeyedVector<String8, String8> *overrides) {
- mHeaders = String8();
-
- mHeaders.append("User-Agent: stagefright/1.0 (Linux;Android ");
-
-#if (PROPERTY_VALUE_MAX < 8)
-#error "PROPERTY_VALUE_MAX must be at least 8"
-#endif
-
- char value[PROPERTY_VALUE_MAX];
- property_get("ro.build.version.release", value, "Unknown");
- mHeaders.append(value);
- mHeaders.append(")\r\n");
-
- if (overrides == NULL) {
- return;
- }
-
- for (size_t i = 0; i < overrides->size(); ++i) {
- String8 line;
- line.append(overrides->keyAt(i));
- line.append(": ");
- line.append(overrides->valueAt(i));
- line.append("\r\n");
-
- mHeaders.append(line);
- }
-}
-
-uint32_t HTTPDataSource::flags() {
- uint32_t f = kWantsPrefetching;
-
- if (!strcasecmp(mStartingHost.string(), "localhost")
- || !strcmp(mStartingHost.string(), "127.0.0.1")) {
- f |= kStreamedFromLocalHost;
- }
-
- return f;
-}
-
-} // namespace android
-
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index e0f8f9e..af11032 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -32,6 +32,7 @@
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/Utils.h>
#include <media/mediarecorder.h>
+#include <cutils/properties.h>
namespace android {
@@ -42,6 +43,7 @@
status_t start();
void stop();
+ void pause();
bool reachedEOS();
int64_t getDurationUs() const;
@@ -53,6 +55,8 @@
sp<MetaData> mMeta;
sp<MediaSource> mSource;
volatile bool mDone;
+ volatile bool mPaused;
+ volatile bool mResumed;
int64_t mMaxTimeStampUs;
int64_t mEstimatedTrackSizeBytes;
@@ -82,6 +86,7 @@
List<StscTableEntry> mStscTableEntries;
List<int32_t> mStssTableEntries;
+ List<int64_t> mChunkDurations;
struct SttsTableEntry {
@@ -106,6 +111,9 @@
status_t makeAVCCodecSpecificData(
const uint8_t *data, size_t size);
void writeOneChunk(bool isAvc);
+ void logStatisticalData(bool isAudio);
+ void findMinMaxFrameRates(float *minFps, float *maxFps);
+ void findMinMaxChunkDurations(int64_t *min, int64_t *max);
Track(const Track &);
Track &operator=(const Track &);
@@ -115,6 +123,8 @@
MPEG4Writer::MPEG4Writer(const char *filename)
: mFile(fopen(filename, "wb")),
+ mPaused(false),
+ mStarted(false),
mOffset(0),
mMdatOffset(0),
mEstimatedMoovBoxSize(0),
@@ -124,6 +134,8 @@
MPEG4Writer::MPEG4Writer(int fd)
: mFile(fdopen(fd, "wb")),
+ mPaused(false),
+ mStarted(false),
mOffset(0),
mMdatOffset(0),
mEstimatedMoovBoxSize(0),
@@ -148,11 +160,36 @@
return OK;
}
+status_t MPEG4Writer::startTracks() {
+ for (List<Track *>::iterator it = mTracks.begin();
+ it != mTracks.end(); ++it) {
+ status_t err = (*it)->start();
+
+ if (err != OK) {
+ for (List<Track *>::iterator it2 = mTracks.begin();
+ it2 != it; ++it2) {
+ (*it2)->stop();
+ }
+
+ return err;
+ }
+ }
+ return OK;
+}
+
status_t MPEG4Writer::start() {
if (mFile == NULL) {
return UNKNOWN_ERROR;
}
+ if (mStarted) {
+ if (mPaused) {
+ mPaused = false;
+ return startTracks();
+ }
+ return OK;
+ }
+
mStartTimestampUs = 0;
mStreamableFile = true;
mWriteMoovBoxToMemory = false;
@@ -181,21 +218,24 @@
mOffset = mMdatOffset;
fseeko(mFile, mMdatOffset, SEEK_SET);
write("\x00\x00\x00\x01mdat????????", 16);
+
+ status_t err = startTracks();
+ if (err != OK) {
+ return err;
+ }
+ mStarted = true;
+ return OK;
+}
+
+void MPEG4Writer::pause() {
+ if (mFile == NULL) {
+ return;
+ }
+ mPaused = true;
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
- status_t err = (*it)->start();
-
- if (err != OK) {
- for (List<Track *>::iterator it2 = mTracks.begin();
- it2 != it; ++it2) {
- (*it2)->stop();
- }
-
- return err;
- }
+ (*it)->pause();
}
-
- return OK;
}
void MPEG4Writer::stop() {
@@ -293,6 +333,7 @@
fflush(mFile);
fclose(mFile);
mFile = NULL;
+ mStarted = false;
}
status_t MPEG4Writer::setInterleaveDuration(uint32_t durationUs) {
@@ -523,6 +564,8 @@
mMeta(source->getFormat()),
mSource(source),
mDone(false),
+ mPaused(false),
+ mResumed(false),
mMaxTimeStampUs(0),
mEstimatedTrackSizeBytes(0),
mSamplesHaveSameSize(true),
@@ -542,6 +585,11 @@
}
status_t MPEG4Writer::Track::start() {
+ if (!mDone && mPaused) {
+ mPaused = false;
+ mResumed = true;
+ return OK;
+ }
status_t err = mSource->start();
if (err != OK) {
@@ -564,6 +612,10 @@
return OK;
}
+void MPEG4Writer::Track::pause() {
+ mPaused = true;
+}
+
void MPEG4Writer::Track::stop() {
if (mDone) {
return;
@@ -705,6 +757,8 @@
int64_t lastDuration = 0; // Time spacing between the previous two samples
int32_t sampleCount = 1; // Sample count in the current stts table entry
uint32_t previousSampleSize = 0; // Size of the previous sample
+ int64_t previousPausedDurationUs = 0;
+ sp<MetaData> meta_data;
MediaBuffer *buffer;
while (!mDone && mSource->read(&buffer) == OK) {
@@ -715,6 +769,15 @@
continue;
}
+ // If the codec specific data has not been received yet, delay pause.
+ // After the codec specific data is received, discard what we received
+ // when the track is to be paused.
+ if (mPaused && !mResumed) {
+ buffer->release();
+ buffer = NULL;
+ continue;
+ }
+
++count;
int32_t isCodecConfig;
@@ -825,40 +888,63 @@
continue;
}
- if (is_avc) StripStartcode(buffer);
+ if (!mGotAllCodecSpecificData) {
+ mGotAllCodecSpecificData = true;
+ }
+
+ // Make a deep copy of the MediaBuffer and Metadata and release
+ // the original as soon as we can
+ MediaBuffer *copy = new MediaBuffer(buffer->range_length());
+ memcpy(copy->data(), (uint8_t *)buffer->data() + buffer->range_offset(),
+ buffer->range_length());
+ copy->set_range(0, buffer->range_length());
+ meta_data = new MetaData(*buffer->meta_data().get());
+ buffer->release();
+ buffer = NULL;
+
+ if (is_avc) StripStartcode(copy);
SampleInfo info;
info.size = is_avc
#if USE_NALLEN_FOUR
- ? buffer->range_length() + 4
+ ? copy->range_length() + 4
#else
- ? buffer->range_length() + 2
+ ? copy->range_length() + 2
#endif
- : buffer->range_length();
+ : copy->range_length();
// Max file size or duration handling
mEstimatedTrackSizeBytes += info.size;
if (mOwner->exceedsFileSizeLimit()) {
- buffer->release();
- buffer = NULL;
mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0);
break;
}
if (mOwner->exceedsFileDurationLimit()) {
- buffer->release();
- buffer = NULL;
mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_DURATION_REACHED, 0);
break;
}
+ int32_t isSync = false;
+ meta_data->findInt32(kKeyIsSyncFrame, &isSync);
+
int64_t timestampUs;
- CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs));
+ CHECK(meta_data->findInt64(kKeyTime, ×tampUs));
+
+////////////////////////////////////////////////////////////////////////////////
if (mSampleInfos.empty()) {
mOwner->setStartTimestamp(timestampUs);
mStartTimestampUs = (timestampUs - mOwner->getStartTimestamp());
}
+ if (mResumed) {
+ previousPausedDurationUs += (timestampUs - mMaxTimeStampUs - 1000 * lastDuration);
+ mResumed = false;
+ }
+
+ timestampUs -= previousPausedDurationUs;
+ LOGV("time stamp: %lld and previous paused duration %lld",
+ timestampUs, previousPausedDurationUs);
if (timestampUs > mMaxTimeStampUs) {
mMaxTimeStampUs = timestampUs;
}
@@ -884,12 +970,10 @@
lastDuration = info.timestamp - lastTimestamp;
lastTimestamp = info.timestamp;
-////////////////////////////////////////////////////////////////////////////////
- // Make a deep copy of the MediaBuffer less Metadata
- MediaBuffer *copy = new MediaBuffer(buffer->range_length());
- memcpy(copy->data(), (uint8_t *)buffer->data() + buffer->range_offset(),
- buffer->range_length());
- copy->set_range(0, buffer->range_length());
+ if (isSync != 0) {
+ mStssTableEntries.push_back(mSampleInfos.size());
+ }
+
mChunkSamples.push_back(copy);
if (interleaveDurationUs == 0) {
@@ -902,6 +986,7 @@
} else {
if (timestampUs - chunkTimestampUs > interleaveDurationUs) {
++nChunks;
+ mChunkDurations.push_back(timestampUs - chunkTimestampUs);
if (nChunks == 1 || // First chunk
(--(mStscTableEntries.end()))->samplesPerChunk !=
mChunkSamples.size()) {
@@ -915,14 +1000,6 @@
}
}
- int32_t isSync = false;
- if (buffer->meta_data()->findInt32(kKeyIsSyncFrame, &isSync) &&
- isSync != 0) {
- mStssTableEntries.push_back(mSampleInfos.size());
- }
-
- buffer->release();
- buffer = NULL;
}
if (mSampleInfos.empty()) {
@@ -950,6 +1027,88 @@
mReachedEOS = true;
LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames - %s",
count, nZeroLengthFrames, mSampleInfos.size(), is_audio? "audio": "video");
+
+ logStatisticalData(is_audio);
+}
+
+void MPEG4Writer::Track::findMinMaxFrameRates(float *minFps, float *maxFps) {
+ int32_t minSampleDuration = 0x7FFFFFFF;
+ int32_t maxSampleDuration = 0;
+ for (List<SttsTableEntry>::iterator it = mSttsTableEntries.begin();
+ it != mSttsTableEntries.end(); ++it) {
+ int32_t sampleDuration = static_cast<int32_t>(it->sampleDuration);
+ if (sampleDuration > maxSampleDuration) {
+ maxSampleDuration = sampleDuration;
+ } else if (sampleDuration < minSampleDuration) {
+ minSampleDuration = sampleDuration;
+ }
+ }
+ CHECK(minSampleDuration != 0 && maxSampleDuration != 0);
+ *minFps = 1000.0 / maxSampleDuration;
+ *maxFps = 1000.0 / minSampleDuration;
+}
+
+// Don't count the last duration
+void MPEG4Writer::Track::findMinMaxChunkDurations(int64_t *min, int64_t *max) {
+ int64_t duration = mOwner->interleaveDuration();
+ int64_t minChunkDuration = duration;
+ int64_t maxChunkDuration = duration;
+ if (mChunkDurations.size() > 1) {
+ for (List<int64_t>::iterator it = mChunkDurations.begin();
+ it != --mChunkDurations.end(); ++it) {
+ if (minChunkDuration > (*it)) {
+ minChunkDuration = (*it);
+ } else if (maxChunkDuration < (*it)) {
+ maxChunkDuration = (*it);
+ }
+ }
+ }
+ *min = minChunkDuration;
+ *max = maxChunkDuration;
+}
+
+void MPEG4Writer::Track::logStatisticalData(bool isAudio) {
+ if (mMaxTimeStampUs <= 0 || mSampleInfos.empty()) {
+ LOGI("nothing is recorded");
+ return;
+ }
+
+ bool collectStats = false;
+ char value[PROPERTY_VALUE_MAX];
+ if (property_get("media.stagefright.record-stats", value, NULL)
+ && (!strcmp(value, "1") || !strcasecmp(value, "true"))) {
+ collectStats = true;
+ }
+
+ if (collectStats) {
+ if (isAudio) {
+ LOGI("audio track - duration %lld us", mMaxTimeStampUs);
+ } else {
+ float fps = (mSampleInfos.size() * 1000000.0) / mMaxTimeStampUs;
+ float minFps;
+ float maxFps;
+ findMinMaxFrameRates(&minFps, &maxFps);
+ LOGI("video track - duration %lld us", mMaxTimeStampUs);
+ LOGI("min/avg/max frame rate (fps): %.2f/%.2f/%.2f",
+ minFps, fps, maxFps);
+ }
+
+ int64_t totalBytes = 0;
+ for (List<SampleInfo>::iterator it = mSampleInfos.begin();
+ it != mSampleInfos.end(); ++it) {
+ totalBytes += it->size;
+ }
+ float bitRate = (totalBytes * 8000000.0) / mMaxTimeStampUs;
+ LOGI("avg bit rate (bps): %.2f", bitRate);
+
+ int64_t duration = mOwner->interleaveDuration();
+ if (duration != 0) { // If interleaving is enabled
+ int64_t minChunk, maxChunk;
+ findMinMaxChunkDurations(&minChunk, &maxChunk);
+ LOGI("min/avg/max chunk duration (ms): %lld/%lld/%lld",
+ minChunk, duration, maxChunk);
+ }
+ }
}
void MPEG4Writer::Track::writeOneChunk(bool isAvc) {
diff --git a/media/libstagefright/NuCachedSource2.cpp b/media/libstagefright/NuCachedSource2.cpp
new file mode 100644
index 0000000..6727c73
--- /dev/null
+++ b/media/libstagefright/NuCachedSource2.cpp
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NuCachedSource2"
+#include <utils/Log.h>
+
+#include "include/NuCachedSource2.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+struct PageCache {
+ PageCache(size_t pageSize);
+ ~PageCache();
+
+ struct Page {
+ void *mData;
+ size_t mSize;
+ };
+
+ Page *acquirePage();
+ void releasePage(Page *page);
+
+ void appendPage(Page *page);
+ size_t releaseFromStart(size_t maxBytes);
+
+ size_t totalSize() const {
+ return mTotalSize;
+ }
+
+ void copy(size_t from, void *data, size_t size);
+
+private:
+ size_t mPageSize;
+ size_t mTotalSize;
+
+ List<Page *> mActivePages;
+ List<Page *> mFreePages;
+
+ void freePages(List<Page *> *list);
+
+ DISALLOW_EVIL_CONSTRUCTORS(PageCache);
+};
+
+PageCache::PageCache(size_t pageSize)
+ : mPageSize(pageSize),
+ mTotalSize(0) {
+}
+
+PageCache::~PageCache() {
+ freePages(&mActivePages);
+ freePages(&mFreePages);
+}
+
+void PageCache::freePages(List<Page *> *list) {
+ List<Page *>::iterator it = list->begin();
+ while (it != list->end()) {
+ Page *page = *it;
+
+ free(page->mData);
+ delete page;
+ page = NULL;
+
+ ++it;
+ }
+}
+
+PageCache::Page *PageCache::acquirePage() {
+ if (!mFreePages.empty()) {
+ List<Page *>::iterator it = mFreePages.begin();
+ Page *page = *it;
+ mFreePages.erase(it);
+
+ return page;
+ }
+
+ Page *page = new Page;
+ page->mData = malloc(mPageSize);
+ page->mSize = 0;
+
+ return page;
+}
+
+void PageCache::releasePage(Page *page) {
+ page->mSize = 0;
+ mFreePages.push_back(page);
+}
+
+void PageCache::appendPage(Page *page) {
+ mTotalSize += page->mSize;
+ mActivePages.push_back(page);
+}
+
+size_t PageCache::releaseFromStart(size_t maxBytes) {
+ size_t bytesReleased = 0;
+
+ while (maxBytes > 0 && !mActivePages.empty()) {
+ List<Page *>::iterator it = mActivePages.begin();
+
+ Page *page = *it;
+
+ if (maxBytes < page->mSize) {
+ break;
+ }
+
+ mActivePages.erase(it);
+
+ maxBytes -= page->mSize;
+ bytesReleased += page->mSize;
+
+ releasePage(page);
+ }
+
+ mTotalSize -= bytesReleased;
+ return bytesReleased;
+}
+
+void PageCache::copy(size_t from, void *data, size_t size) {
+ LOG(VERBOSE) << "copy from " << from << " size " << size;
+
+ CHECK_LE(from + size, mTotalSize);
+
+ size_t offset = 0;
+ List<Page *>::iterator it = mActivePages.begin();
+ while (from >= offset + (*it)->mSize) {
+ offset += (*it)->mSize;
+ ++it;
+ }
+
+ size_t delta = from - offset;
+ size_t avail = (*it)->mSize - delta;
+
+ if (avail >= size) {
+ memcpy(data, (const uint8_t *)(*it)->mData + delta, size);
+ return;
+ }
+
+ memcpy(data, (const uint8_t *)(*it)->mData + delta, avail);
+ ++it;
+ data = (uint8_t *)data + avail;
+ size -= avail;
+
+ while (size > 0) {
+ size_t copy = (*it)->mSize;
+ if (copy > size) {
+ copy = size;
+ }
+ memcpy(data, (*it)->mData, copy);
+ data = (uint8_t *)data + copy;
+ size -= copy;
+ ++it;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NuCachedSource2::NuCachedSource2(const sp<DataSource> &source)
+ : mSource(source),
+ mReflector(new AHandlerReflector<NuCachedSource2>(this)),
+ mLooper(new ALooper),
+ mCache(new PageCache(kPageSize)),
+ mCacheOffset(0),
+ mFinalStatus(OK),
+ mLastAccessPos(0),
+ mFetching(true) {
+ mLooper->registerHandler(mReflector);
+ mLooper->start();
+
+ Mutex::Autolock autoLock(mLock);
+ (new AMessage(kWhatFetchMore, mReflector->id()))->post();
+}
+
+NuCachedSource2::~NuCachedSource2() {
+ mLooper->stop();
+ mLooper->unregisterHandler(mReflector->id());
+
+ delete mCache;
+ mCache = NULL;
+}
+
+status_t NuCachedSource2::initCheck() const {
+ return mSource->initCheck();
+}
+
+status_t NuCachedSource2::getSize(off_t *size) {
+ return mSource->getSize(size);
+}
+
+uint32_t NuCachedSource2::flags() {
+ return mSource->flags();
+}
+
+void NuCachedSource2::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatFetchMore:
+ {
+ onFetch();
+ break;
+ }
+
+ case kWhatRead:
+ {
+ onRead(msg);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void NuCachedSource2::fetchInternal() {
+ LOG(VERBOSE) << "fetchInternal";
+
+ CHECK_EQ(mFinalStatus, (status_t)OK);
+
+ PageCache::Page *page = mCache->acquirePage();
+
+ ssize_t n = mSource->readAt(
+ mCacheOffset + mCache->totalSize(), page->mData, kPageSize);
+
+ Mutex::Autolock autoLock(mLock);
+
+ if (n < 0) {
+ LOG(ERROR) << "source returned error " << n;
+ mFinalStatus = n;
+ mCache->releasePage(page);
+ } else if (n == 0) {
+ LOG(INFO) << "ERROR_END_OF_STREAM";
+ mFinalStatus = ERROR_END_OF_STREAM;
+ mCache->releasePage(page);
+ } else {
+ page->mSize = n;
+ mCache->appendPage(page);
+ }
+}
+
+void NuCachedSource2::onFetch() {
+ LOG(VERBOSE) << "onFetch";
+
+ if (mFinalStatus != OK) {
+ LOG(VERBOSE) << "EOS reached, done prefetching for now";
+ mFetching = false;
+ }
+
+ if (mFetching) {
+ fetchInternal();
+
+ if (mCache->totalSize() >= kHighWaterThreshold) {
+ LOG(INFO) << "Cache full, done prefetching for now";
+ mFetching = false;
+ }
+ } else {
+ restartPrefetcherIfNecessary_l();
+ }
+
+ (new AMessage(kWhatFetchMore, mReflector->id()))->post(
+ mFetching ? 0 : 100000ll);
+}
+
+void NuCachedSource2::onRead(const sp<AMessage> &msg) {
+ LOG(VERBOSE) << "onRead";
+
+ int64_t offset;
+ CHECK(msg->findInt64("offset", &offset));
+
+ void *data;
+ CHECK(msg->findPointer("data", &data));
+
+ size_t size;
+ CHECK(msg->findSize("size", &size));
+
+ ssize_t result = readInternal(offset, data, size);
+
+ if (result == -EAGAIN) {
+ msg->post(50000);
+ return;
+ }
+
+ Mutex::Autolock autoLock(mLock);
+
+ CHECK(mAsyncResult == NULL);
+
+ mAsyncResult = new AMessage;
+ mAsyncResult->setInt32("result", result);
+
+ mCondition.signal();
+}
+
+void NuCachedSource2::restartPrefetcherIfNecessary_l() {
+ static const size_t kGrayArea = 256 * 1024;
+
+ if (mFetching || mFinalStatus != OK) {
+ return;
+ }
+
+ if (mCacheOffset + mCache->totalSize() - mLastAccessPos
+ >= kLowWaterThreshold) {
+ return;
+ }
+
+ size_t maxBytes = mLastAccessPos - mCacheOffset;
+ if (maxBytes < kGrayArea) {
+ return;
+ }
+
+ maxBytes -= kGrayArea;
+
+ size_t actualBytes = mCache->releaseFromStart(maxBytes);
+ mCacheOffset += actualBytes;
+
+ LOG(INFO) << "restarting prefetcher, totalSize = " << mCache->totalSize();
+ mFetching = true;
+}
+
+ssize_t NuCachedSource2::readAt(off_t offset, void *data, size_t size) {
+ Mutex::Autolock autoSerializer(mSerializer);
+
+ LOG(VERBOSE) << "readAt offset " << offset << " size " << size;
+
+ Mutex::Autolock autoLock(mLock);
+
+ // If the request can be completely satisfied from the cache, do so.
+
+ if (offset >= mCacheOffset
+ && offset + size <= mCacheOffset + mCache->totalSize()) {
+ size_t delta = offset - mCacheOffset;
+ mCache->copy(delta, data, size);
+
+ mLastAccessPos = offset + size;
+
+ return size;
+ }
+
+ sp<AMessage> msg = new AMessage(kWhatRead, mReflector->id());
+ msg->setInt64("offset", offset);
+ msg->setPointer("data", data);
+ msg->setSize("size", size);
+
+ CHECK(mAsyncResult == NULL);
+ msg->post();
+
+ while (mAsyncResult == NULL) {
+ mCondition.wait(mLock);
+ }
+
+ int32_t result;
+ CHECK(mAsyncResult->findInt32("result", &result));
+
+ mAsyncResult.clear();
+
+ if (result > 0) {
+ mLastAccessPos = offset + result;
+ }
+
+ return (ssize_t)result;
+}
+
+size_t NuCachedSource2::cachedSize() {
+ Mutex::Autolock autoLock(mLock);
+ return mCacheOffset + mCache->totalSize();
+}
+
+size_t NuCachedSource2::approxDataRemaining(bool *eos) {
+ Mutex::Autolock autoLock(mLock);
+ return approxDataRemaining_l(eos);
+}
+
+size_t NuCachedSource2::approxDataRemaining_l(bool *eos) {
+ *eos = (mFinalStatus != OK);
+ off_t lastBytePosCached = mCacheOffset + mCache->totalSize();
+ if (mLastAccessPos < lastBytePosCached) {
+ return lastBytePosCached - mLastAccessPos;
+ }
+ return 0;
+}
+
+ssize_t NuCachedSource2::readInternal(off_t offset, void *data, size_t size) {
+ LOG(VERBOSE) << "readInternal offset " << offset << " size " << size;
+
+ Mutex::Autolock autoLock(mLock);
+
+ if (offset < mCacheOffset
+ || offset >= (off_t)(mCacheOffset + mCache->totalSize())) {
+ static const off_t kPadding = 32768;
+
+ // In the presence of multiple decoded streams, once of them will
+ // trigger this seek request, the other one will request data "nearby"
+ // soon, adjust the seek position so that that subsequent request
+ // does not trigger another seek.
+ off_t seekOffset = (offset > kPadding) ? offset - kPadding : 0;
+
+ seekInternal_l(seekOffset);
+ }
+
+ size_t delta = offset - mCacheOffset;
+
+ if (mFinalStatus != OK) {
+ if (delta >= mCache->totalSize()) {
+ return mFinalStatus;
+ }
+
+ size_t avail = mCache->totalSize() - delta;
+ mCache->copy(delta, data, avail);
+
+ return avail;
+ }
+
+ if (offset + size <= mCacheOffset + mCache->totalSize()) {
+ mCache->copy(delta, data, size);
+
+ return size;
+ }
+
+ LOG(VERBOSE) << "deferring read";
+
+ return -EAGAIN;
+}
+
+status_t NuCachedSource2::seekInternal_l(off_t offset) {
+ mLastAccessPos = offset;
+
+ if (offset >= mCacheOffset
+ && offset <= (off_t)(mCacheOffset + mCache->totalSize())) {
+ return OK;
+ }
+
+ LOG(INFO) << "new range: offset= " << offset;
+
+ mCacheOffset = offset;
+
+ size_t totalSize = mCache->totalSize();
+ CHECK_EQ(mCache->releaseFromStart(totalSize), totalSize);
+
+ mFinalStatus = OK;
+ mFetching = true;
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp
new file mode 100644
index 0000000..ab9285d
--- /dev/null
+++ b/media/libstagefright/NuHTTPDataSource.cpp
@@ -0,0 +1,338 @@
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NuHTTPDataSource"
+#include <utils/Log.h>
+
+#include "include/NuHTTPDataSource.h"
+
+#include <cutils/properties.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+static bool ParseSingleUnsignedLong(
+ const char *from, unsigned long *x) {
+ char *end;
+ *x = strtoul(from, &end, 10);
+
+ if (end == from || *end != '\0') {
+ return false;
+ }
+
+ return true;
+}
+
+static bool ParseURL(
+ const char *url, String8 *host, unsigned *port, String8 *path) {
+ host->setTo("");
+ *port = 0;
+ path->setTo("");
+
+ if (strncasecmp("http://", 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);
+ }
+
+ char *colonPos = strchr(host->string(), ':');
+
+ if (colonPos != NULL) {
+ unsigned long x;
+ if (!ParseSingleUnsignedLong(colonPos + 1, &x) || x >= 65536) {
+ return false;
+ }
+
+ *port = x;
+
+ size_t colonOffset = colonPos - host->string();
+ String8 tmp(host->string(), colonOffset);
+ *host = tmp;
+ } else {
+ *port = 80;
+ }
+
+ return true;
+}
+
+NuHTTPDataSource::NuHTTPDataSource()
+ : mState(DISCONNECTED),
+ mPort(0),
+ mOffset(0),
+ mContentLength(0),
+ mContentLengthValid(false) {
+}
+
+NuHTTPDataSource::~NuHTTPDataSource() {
+}
+
+status_t NuHTTPDataSource::connect(
+ const char *uri,
+ const KeyedVector<String8, String8> *overrides,
+ off_t offset) {
+ String8 headers;
+ MakeFullHeaders(overrides, &headers);
+
+ return connect(uri, headers, offset);
+}
+
+status_t NuHTTPDataSource::connect(
+ const char *uri,
+ const String8 &headers,
+ off_t offset) {
+ String8 host, path;
+ unsigned port;
+ if (!ParseURL(uri, &host, &port, &path)) {
+ return ERROR_MALFORMED;
+ }
+
+ return connect(host, port, path, headers, offset);
+}
+
+status_t NuHTTPDataSource::connect(
+ const char *host, unsigned port, const char *path,
+ const String8 &headers,
+ off_t offset) {
+ LOGI("connect to %s:%u%s @%ld", host, port, path, offset);
+
+ bool needsToReconnect = true;
+
+ if (mState == CONNECTED && host == mHost && port == mPort
+ && offset == mOffset) {
+ if (mContentLengthValid && mOffset == mContentLength) {
+ LOGI("Didn't have to reconnect, old one's still good.");
+ needsToReconnect = false;
+ }
+ }
+
+ mHost = host;
+ mPort = port;
+ mPath = path;
+ mHeaders = headers;
+
+ status_t err = OK;
+
+ mState = CONNECTING;
+
+ if (needsToReconnect) {
+ mHTTP.disconnect();
+ err = mHTTP.connect(host, port);
+ }
+
+ if (err != OK) {
+ mState = DISCONNECTED;
+ } else if (mState != CONNECTING) {
+ err = UNKNOWN_ERROR;
+ } else {
+ mState = CONNECTED;
+
+ mOffset = offset;
+ mContentLength = 0;
+ mContentLengthValid = false;
+
+ String8 request("GET ");
+ request.append(mPath);
+ request.append(" HTTP/1.1\r\n");
+ request.append("Host: ");
+ request.append(mHost);
+ request.append("\r\n");
+
+ if (offset != 0) {
+ char rangeHeader[128];
+ sprintf(rangeHeader, "Range: bytes=%ld-\r\n", offset);
+ request.append(rangeHeader);
+ }
+
+ request.append(mHeaders);
+ request.append("\r\n");
+
+ int httpStatus;
+ if ((err = mHTTP.send(request.string(), request.size())) != OK
+ || (err = mHTTP.receive_header(&httpStatus)) != OK) {
+ mHTTP.disconnect();
+ mState = DISCONNECTED;
+ return err;
+ }
+
+ if (httpStatus == 302) {
+ string value;
+ CHECK(mHTTP.find_header_value("Location", &value));
+
+ mState = DISCONNECTED;
+
+ mHTTP.disconnect();
+
+ return connect(value.c_str(), headers, offset);
+ }
+
+ if (httpStatus < 200 || httpStatus >= 300) {
+ mState = DISCONNECTED;
+ mHTTP.disconnect();
+
+ return ERROR_IO;
+ }
+
+ applyTimeoutResponse();
+
+ if (offset == 0) {
+ string value;
+ unsigned long x;
+ if (mHTTP.find_header_value(string("Content-Length"), &value)
+ && ParseSingleUnsignedLong(value.c_str(), &x)) {
+ mContentLength = (off_t)x;
+ mContentLengthValid = true;
+ }
+ } else {
+ string value;
+ unsigned long x;
+ if (mHTTP.find_header_value(string("Content-Range"), &value)) {
+ const char *slashPos = strchr(value.c_str(), '/');
+ if (slashPos != NULL
+ && ParseSingleUnsignedLong(slashPos + 1, &x)) {
+ mContentLength = x;
+ mContentLengthValid = true;
+ }
+ }
+ }
+ }
+
+ return err;
+}
+
+void NuHTTPDataSource::disconnect() {
+ if (mState == CONNECTING || mState == CONNECTED) {
+ mHTTP.disconnect();
+ }
+ mState = DISCONNECTED;
+}
+
+status_t NuHTTPDataSource::initCheck() const {
+ return mState == CONNECTED ? OK : NO_INIT;
+}
+
+ssize_t NuHTTPDataSource::readAt(off_t offset, void *data, size_t size) {
+ LOGV("readAt offset %ld, size %d", offset, size);
+
+ Mutex::Autolock autoLock(mLock);
+
+ if (offset != mOffset) {
+ String8 host = mHost;
+ String8 path = mPath;
+ String8 headers = mHeaders;
+ status_t err = connect(host, mPort, path, headers, offset);
+
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ if (mContentLengthValid) {
+ size_t avail =
+ (offset >= mContentLength) ? 0 : mContentLength - offset;
+
+ if (size > avail) {
+ size = avail;
+ }
+ }
+
+ size_t numBytesRead = 0;
+ while (numBytesRead < size) {
+ ssize_t n =
+ mHTTP.receive((uint8_t *)data + numBytesRead, size - numBytesRead);
+
+ if (n < 0) {
+ return n;
+ }
+
+ numBytesRead += (size_t)n;
+
+ if (n == 0) {
+ if (mContentLengthValid) {
+ // We know the content length and made sure not to read beyond
+ // it and yet the server closed the connection on us.
+ return ERROR_IO;
+ }
+
+ break;
+ }
+ }
+
+ mOffset += numBytesRead;
+
+ return numBytesRead;
+}
+
+status_t NuHTTPDataSource::getSize(off_t *size) {
+ *size = 0;
+
+ if (mState != CONNECTED) {
+ return ERROR_IO;
+ }
+
+ if (mContentLengthValid) {
+ *size = mContentLength;
+ return OK;
+ }
+
+ return ERROR_UNSUPPORTED;
+}
+
+uint32_t NuHTTPDataSource::flags() {
+ return kWantsPrefetching;
+}
+
+// static
+void NuHTTPDataSource::MakeFullHeaders(
+ const KeyedVector<String8, String8> *overrides, String8 *headers) {
+ headers->setTo("");
+
+ headers->append("User-Agent: stagefright/1.1 (Linux;Android ");
+
+#if (PROPERTY_VALUE_MAX < 8)
+#error "PROPERTY_VALUE_MAX must be at least 8"
+#endif
+
+ char value[PROPERTY_VALUE_MAX];
+ property_get("ro.build.version.release", value, "Unknown");
+ headers->append(value);
+ headers->append(")\r\n");
+
+ if (overrides == NULL) {
+ return;
+ }
+
+ for (size_t i = 0; i < overrides->size(); ++i) {
+ String8 line;
+ line.append(overrides->keyAt(i));
+ line.append(": ");
+ line.append(overrides->valueAt(i));
+ line.append("\r\n");
+
+ headers->append(line);
+ }
+}
+
+void NuHTTPDataSource::applyTimeoutResponse() {
+ string timeout;
+ if (mHTTP.find_header_value("X-SocketTimeout", &timeout)) {
+ const char *s = timeout.c_str();
+ char *end;
+ long tmp = strtol(s, &end, 10);
+ if (end == s || *end != '\0') {
+ LOGW("Illegal X-SocketTimeout value given.");
+ return;
+ }
+
+ LOGI("overriding default timeout, new timeout is %ld seconds", tmp);
+ mHTTP.setReceiveTimeout(tmp);
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 66011ca..6be41b4 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -347,6 +347,9 @@
quirks |= kRequiresAllocateBufferOnInputPorts;
quirks |= kRequiresAllocateBufferOnOutputPorts;
+ if (!strncmp(componentName, "OMX.TI.video.encoder", 20)) {
+ quirks |= kAvoidMemcopyInputRecordingFrames;
+ }
}
if (!strcmp(componentName, "OMX.TI.Video.Decoder")) {
@@ -535,11 +538,12 @@
size -= length;
}
- LOGV("AVC profile = %d (%s), level = %d",
- (int)profile, AVCProfileToString(profile), (int)level / 10);
+ CODEC_LOGV(
+ "AVC profile = %d (%s), level = %d",
+ (int)profile, AVCProfileToString(profile), level);
if (!strcmp(mComponentName, "OMX.TI.Video.Decoder")
- && (profile != kAVCProfileBaseline || level > 39)) {
+ && (profile != kAVCProfileBaseline || level > 30)) {
// This stream exceeds the decoder's capabilities. The decoder
// does not handle this gracefully and would clobber the heap
// and wreak havoc instead...
@@ -568,14 +572,14 @@
}
if (!strncasecmp(mMIME, "video/", 6)) {
- int32_t width, height;
- bool success = meta->findInt32(kKeyWidth, &width);
- success = success && meta->findInt32(kKeyHeight, &height);
- CHECK(success);
if (mIsEncoder) {
- setVideoInputFormat(mMIME, width, height);
+ setVideoInputFormat(mMIME, meta);
} else {
+ int32_t width, height;
+ bool success = meta->findInt32(kKeyWidth, &width);
+ success = success && meta->findInt32(kKeyHeight, &height);
+ CHECK(success);
status_t err = setVideoOutputFormat(
mMIME, width, height);
@@ -739,8 +743,17 @@
}
void OMXCodec::setVideoInputFormat(
- const char *mime, OMX_U32 width, OMX_U32 height) {
- CODEC_LOGV("setVideoInputFormat width=%ld, height=%ld", width, height);
+ const char *mime, const sp<MetaData>& meta) {
+
+ int32_t width, height, frameRate, bitRate, stride, sliceHeight;
+ bool success = meta->findInt32(kKeyWidth, &width);
+ success = success && meta->findInt32(kKeyHeight, &height);
+ success = success && meta->findInt32(kKeySampleRate, &frameRate);
+ success = success && meta->findInt32(kKeyBitRate, &bitRate);
+ success = success && meta->findInt32(kKeyStride, &stride);
+ success = success && meta->findInt32(kKeySliceHeight, &sliceHeight);
+ CHECK(success);
+ CHECK(stride != 0);
OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused;
if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
@@ -759,8 +772,6 @@
colorFormat = OMX_COLOR_FormatYCbYCr;
}
-
-
status_t err;
OMX_PARAM_PORTDEFINITIONTYPE def;
OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
@@ -769,6 +780,7 @@
CHECK_EQ(setVideoPortFormatType(
kPortIndexInput, OMX_VIDEO_CodingUnused,
colorFormat), OK);
+
InitOMXParams(&def);
def.nPortIndex = kPortIndexInput;
@@ -776,17 +788,19 @@
mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
CHECK_EQ(err, OK);
- def.nBufferSize = getFrameSize(colorFormat, width, height);
+ def.nBufferSize = getFrameSize(colorFormat,
+ stride > 0? stride: -stride, sliceHeight);
CHECK_EQ(def.eDomain, OMX_PortDomainVideo);
video_def->nFrameWidth = width;
video_def->nFrameHeight = height;
+ video_def->nStride = stride;
+ video_def->nSliceHeight = sliceHeight;
+ video_def->xFramerate = (frameRate << 16); // Q16 format
video_def->eCompressionFormat = OMX_VIDEO_CodingUnused;
video_def->eColorFormat = colorFormat;
- video_def->xFramerate = (24 << 16); // Q16 format
-
err = mOMX->setParameter(
mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
CHECK_EQ(err, OK);
@@ -806,7 +820,8 @@
video_def->nFrameWidth = width;
video_def->nFrameHeight = height;
-
+ video_def->xFramerate = (frameRate << 16); // Q16 format
+ video_def->nBitrate = bitRate; // Q16 format
video_def->eCompressionFormat = compressionFormat;
video_def->eColorFormat = OMX_COLOR_FormatUnused;
@@ -818,7 +833,7 @@
switch (compressionFormat) {
case OMX_VIDEO_CodingMPEG4:
{
- CHECK_EQ(setupMPEG4EncoderParameters(), OK);
+ CHECK_EQ(setupMPEG4EncoderParameters(meta), OK);
break;
}
@@ -827,7 +842,7 @@
case OMX_VIDEO_CodingAVC:
{
- CHECK_EQ(setupAVCEncoderParameters(), OK);
+ CHECK_EQ(setupAVCEncoderParameters(meta), OK);
break;
}
@@ -837,7 +852,23 @@
}
}
-status_t OMXCodec::setupMPEG4EncoderParameters() {
+static OMX_U32 setPFramesSpacing(int32_t iFramesInterval, int32_t frameRate) {
+ if (iFramesInterval < 0) {
+ return 0xFFFFFFFF;
+ } else if (iFramesInterval == 0) {
+ return 0;
+ }
+ OMX_U32 ret = frameRate * iFramesInterval;
+ CHECK(ret > 1);
+ return ret;
+}
+
+status_t OMXCodec::setupMPEG4EncoderParameters(const sp<MetaData>& meta) {
+ int32_t iFramesInterval, frameRate, bitRate;
+ bool success = meta->findInt32(kKeyBitRate, &bitRate);
+ success = success && meta->findInt32(kKeySampleRate, &frameRate);
+ success = success && meta->findInt32(kKeyIFramesInterval, &iFramesInterval);
+ CHECK(success);
OMX_VIDEO_PARAM_MPEG4TYPE mpeg4type;
InitOMXParams(&mpeg4type);
mpeg4type.nPortIndex = kPortIndexOutput;
@@ -853,9 +884,11 @@
mpeg4type.nAllowedPictureTypes =
OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
- mpeg4type.nPFrames = 23;
+ mpeg4type.nPFrames = setPFramesSpacing(iFramesInterval, frameRate);
+ if (mpeg4type.nPFrames == 0) {
+ mpeg4type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI;
+ }
mpeg4type.nBFrames = 0;
-
mpeg4type.nIDCVLCThreshold = 0;
mpeg4type.bACPred = OMX_TRUE;
mpeg4type.nMaxPacketSize = 256;
@@ -882,7 +915,7 @@
CHECK_EQ(err, OK);
bitrateType.eControlRate = OMX_Video_ControlRateVariable;
- bitrateType.nTargetBitrate = 1000000;
+ bitrateType.nTargetBitrate = bitRate;
err = mOMX->setParameter(
mNode, OMX_IndexParamVideoBitrate,
@@ -914,7 +947,13 @@
return OK;
}
-status_t OMXCodec::setupAVCEncoderParameters() {
+status_t OMXCodec::setupAVCEncoderParameters(const sp<MetaData>& meta) {
+ int32_t iFramesInterval, frameRate, bitRate;
+ bool success = meta->findInt32(kKeyBitRate, &bitRate);
+ success = success && meta->findInt32(kKeySampleRate, &frameRate);
+ success = success && meta->findInt32(kKeyIFramesInterval, &iFramesInterval);
+ CHECK(success);
+
OMX_VIDEO_PARAM_AVCTYPE h264type;
InitOMXParams(&h264type);
h264type.nPortIndex = kPortIndexOutput;
@@ -927,7 +966,11 @@
OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
h264type.nSliceHeaderSpacing = 0;
- h264type.nBFrames = 0;
+ h264type.nBFrames = 0; // No B frames support yet
+ h264type.nPFrames = setPFramesSpacing(iFramesInterval, frameRate);
+ if (h264type.nPFrames == 0) {
+ h264type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI;
+ }
h264type.bUseHadamard = OMX_TRUE;
h264type.nRefFrames = 1;
h264type.nRefIdx10ActiveMinus1 = 0;
@@ -960,7 +1003,7 @@
CHECK_EQ(err, OK);
bitrateType.eControlRate = OMX_Video_ControlRateVariable;
- bitrateType.nTargetBitrate = 3000000;
+ bitrateType.nTargetBitrate = bitRate;
err = mOMX->setParameter(
mNode, OMX_IndexParamVideoBitrate,
@@ -1102,7 +1145,8 @@
mNoMoreOutputData(false),
mOutputPortSettingsHaveChanged(false),
mSeekTimeUs(-1),
- mLeftOverBuffer(NULL) {
+ mLeftOverBuffer(NULL),
+ mPaused(false) {
mPortStatus[kPortIndexInput] = ENABLED;
mPortStatus[kPortIndexOutput] = ENABLED;
@@ -1692,6 +1736,9 @@
CODEC_LOGV("Finished flushing both ports, now continuing from"
" seek-time.");
+ // We implicitly resume pulling on our upstream source.
+ mPaused = false;
+
drainInputBuffers();
fillOutputBuffers();
}
@@ -1991,6 +2038,10 @@
return;
}
+ if (mPaused) {
+ return;
+ }
+
status_t err;
bool signalEOS = false;
@@ -2049,9 +2100,15 @@
break;
}
- memcpy((uint8_t *)info->mData + offset,
- (const uint8_t *)srcBuffer->data() + srcBuffer->range_offset(),
- srcBuffer->range_length());
+ if (mIsEncoder && (mQuirks & kAvoidMemcopyInputRecordingFrames)) {
+ CHECK(mOMXLivesLocally && offset == 0);
+ OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *) info->mBuffer;
+ header->pBuffer = (OMX_U8 *) srcBuffer->data() + srcBuffer->range_offset();
+ } else {
+ memcpy((uint8_t *)info->mData + offset,
+ (const uint8_t *)srcBuffer->data() + srcBuffer->range_offset(),
+ srcBuffer->range_length());
+ }
int64_t lastBufferTimeUs;
CHECK(srcBuffer->meta_data()->findInt64(kKeyTime, &lastBufferTimeUs));
@@ -2505,6 +2562,7 @@
mOutputPortSettingsHaveChanged = false;
mSeekTimeUs = -1;
mFilledBuffers.clear();
+ mPaused = false;
return init();
}
@@ -2611,6 +2669,7 @@
// There's no reason to trigger the code below, there's
// nothing to flush yet.
seeking = false;
+ mPaused = false;
}
drainInputBuffers();
@@ -3171,6 +3230,14 @@
}
}
+status_t OMXCodec::pause() {
+ Mutex::Autolock autoLock(mLock);
+
+ mPaused = true;
+
+ return OK;
+}
+
////////////////////////////////////////////////////////////////////////////////
status_t QueryCodecs(
diff --git a/media/libstagefright/Prefetcher.cpp b/media/libstagefright/Prefetcher.cpp
deleted file mode 100644
index b6ed56b..0000000
--- a/media/libstagefright/Prefetcher.cpp
+++ /dev/null
@@ -1,474 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "Prefetcher"
-//#define LOG_NDEBUG 0
-#include <utils/Log.h>
-
-#include "include/Prefetcher.h"
-
-#include <media/stagefright/MediaBuffer.h>
-#include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MediaSource.h>
-#include <media/stagefright/MetaData.h>
-#include <utils/List.h>
-
-namespace android {
-
-struct PrefetchedSource : public MediaSource {
- PrefetchedSource(
- size_t index,
- const sp<MediaSource> &source);
-
- virtual status_t start(MetaData *params);
- virtual status_t stop();
-
- virtual status_t read(
- MediaBuffer **buffer, const ReadOptions *options);
-
- virtual sp<MetaData> getFormat();
-
-protected:
- virtual ~PrefetchedSource();
-
-private:
- friend struct Prefetcher;
-
- Mutex mLock;
- Condition mCondition;
-
- sp<MediaSource> mSource;
- size_t mIndex;
- bool mStarted;
- bool mReachedEOS;
- status_t mFinalStatus;
- int64_t mSeekTimeUs;
- int64_t mCacheDurationUs;
- size_t mCacheSizeBytes;
- bool mPrefetcherStopped;
- bool mCurrentlyPrefetching;
-
- List<MediaBuffer *> mCachedBuffers;
-
- // Returns true iff source is currently caching.
- bool getCacheDurationUs(int64_t *durationUs, size_t *totalSize = NULL);
-
- void updateCacheDuration_l();
- void clearCache_l();
-
- void cacheMore();
- void onPrefetcherStopped();
-
- PrefetchedSource(const PrefetchedSource &);
- PrefetchedSource &operator=(const PrefetchedSource &);
-};
-
-Prefetcher::Prefetcher()
- : mDone(false),
- mThreadExited(false) {
- startThread();
-}
-
-Prefetcher::~Prefetcher() {
- stopThread();
-}
-
-sp<MediaSource> Prefetcher::addSource(const sp<MediaSource> &source) {
- Mutex::Autolock autoLock(mLock);
-
- sp<PrefetchedSource> psource =
- new PrefetchedSource(mSources.size(), source);
-
- mSources.add(psource);
-
- return psource;
-}
-
-void Prefetcher::startThread() {
- mThreadExited = false;
- mDone = false;
-
- int res = androidCreateThreadEtc(
- ThreadWrapper, this, "Prefetcher",
- ANDROID_PRIORITY_DEFAULT, 0, &mThread);
-
- CHECK_EQ(res, 1);
-}
-
-void Prefetcher::stopThread() {
- Mutex::Autolock autoLock(mLock);
-
- while (!mThreadExited) {
- mDone = true;
- mCondition.signal();
- mCondition.wait(mLock);
- }
-}
-
-// static
-int Prefetcher::ThreadWrapper(void *me) {
- static_cast<Prefetcher *>(me)->threadFunc();
-
- return 0;
-}
-
-// Cache at most 1 min for each source.
-static int64_t kMaxCacheDurationUs = 60 * 1000000ll;
-
-// At the same time cache at most 5MB per source.
-static size_t kMaxCacheSizeBytes = 5 * 1024 * 1024;
-
-// If the amount of cached data drops below this,
-// fill the cache up to the max duration again.
-static int64_t kLowWaterDurationUs = 5000000ll;
-
-void Prefetcher::threadFunc() {
- bool fillingCache = false;
-
- for (;;) {
- sp<PrefetchedSource> minSource;
- int64_t minCacheDurationUs = -1;
-
- {
- Mutex::Autolock autoLock(mLock);
- if (mDone) {
- break;
- }
- mCondition.waitRelative(
- mLock, fillingCache ? 10000000ll : 1000000000ll);
-
- ssize_t minIndex = -1;
- for (size_t i = 0; i < mSources.size(); ++i) {
- sp<PrefetchedSource> source = mSources[i].promote();
-
- if (source == NULL) {
- continue;
- }
-
- int64_t cacheDurationUs;
- size_t cacheSizeBytes;
- if (!source->getCacheDurationUs(&cacheDurationUs, &cacheSizeBytes)) {
- continue;
- }
-
- if (cacheSizeBytes > kMaxCacheSizeBytes) {
- LOGI("max cache size reached");
- continue;
- }
-
- if (mSources.size() > 1 && cacheDurationUs >= kMaxCacheDurationUs) {
- LOGI("max duration reached, size = %d bytes", cacheSizeBytes);
- continue;
- }
-
- if (minIndex < 0 || cacheDurationUs < minCacheDurationUs) {
- minCacheDurationUs = cacheDurationUs;
- minIndex = i;
- minSource = source;
- }
- }
-
- if (minIndex < 0) {
- if (fillingCache) {
- LOGV("[%p] done filling the cache, above high water mark.",
- this);
- fillingCache = false;
- }
- continue;
- }
- }
-
- if (!fillingCache && minCacheDurationUs < kLowWaterDurationUs) {
- LOGI("[%p] cache below low water mark, filling cache.", this);
- fillingCache = true;
- }
-
- if (fillingCache) {
- // Make sure not to hold the lock while calling into the source.
- // The lock guards the list of sources, not the individual sources
- // themselves.
- minSource->cacheMore();
- }
- }
-
- Mutex::Autolock autoLock(mLock);
- for (size_t i = 0; i < mSources.size(); ++i) {
- sp<PrefetchedSource> source = mSources[i].promote();
-
- if (source == NULL) {
- continue;
- }
-
- source->onPrefetcherStopped();
- }
-
- mThreadExited = true;
- mCondition.signal();
-}
-
-int64_t Prefetcher::getCachedDurationUs(bool *noMoreData) {
- Mutex::Autolock autoLock(mLock);
-
- int64_t minCacheDurationUs = -1;
- ssize_t minIndex = -1;
- bool anySourceActive = false;
- for (size_t i = 0; i < mSources.size(); ++i) {
- int64_t cacheDurationUs;
- sp<PrefetchedSource> source = mSources[i].promote();
- if (source == NULL) {
- continue;
- }
-
- if (source->getCacheDurationUs(&cacheDurationUs)) {
- anySourceActive = true;
- }
-
- if (minIndex < 0 || cacheDurationUs < minCacheDurationUs) {
- minCacheDurationUs = cacheDurationUs;
- minIndex = i;
- }
- }
-
- if (noMoreData) {
- *noMoreData = !anySourceActive;
- }
-
- return minCacheDurationUs < 0 ? 0 : minCacheDurationUs;
-}
-
-status_t Prefetcher::prepare(
- bool (*continueFunc)(void *cookie), void *cookie) {
- // Fill the cache.
-
- int64_t duration;
- bool noMoreData;
- do {
- usleep(100000);
-
- if (continueFunc && !(*continueFunc)(cookie)) {
- return -EINTR;
- }
-
- duration = getCachedDurationUs(&noMoreData);
- } while (!noMoreData && duration < 2000000ll);
-
- return OK;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-PrefetchedSource::PrefetchedSource(
- size_t index,
- const sp<MediaSource> &source)
- : mSource(source),
- mIndex(index),
- mStarted(false),
- mReachedEOS(false),
- mSeekTimeUs(0),
- mCacheDurationUs(0),
- mCacheSizeBytes(0),
- mPrefetcherStopped(false),
- mCurrentlyPrefetching(false) {
-}
-
-PrefetchedSource::~PrefetchedSource() {
- if (mStarted) {
- stop();
- }
-}
-
-status_t PrefetchedSource::start(MetaData *params) {
- CHECK(!mStarted);
-
- Mutex::Autolock autoLock(mLock);
-
- status_t err = mSource->start(params);
-
- if (err != OK) {
- return err;
- }
-
- mStarted = true;
-
- return OK;
-}
-
-status_t PrefetchedSource::stop() {
- CHECK(mStarted);
-
- Mutex::Autolock autoLock(mLock);
-
- while (mCurrentlyPrefetching) {
- mCondition.wait(mLock);
- }
-
- clearCache_l();
-
- status_t err = mSource->stop();
-
- mStarted = false;
-
- return err;
-}
-
-status_t PrefetchedSource::read(
- MediaBuffer **out, const ReadOptions *options) {
- *out = NULL;
-
- Mutex::Autolock autoLock(mLock);
-
- CHECK(mStarted);
-
- int64_t seekTimeUs;
- if (options && options->getSeekTo(&seekTimeUs)) {
- CHECK(seekTimeUs >= 0);
-
- clearCache_l();
-
- mReachedEOS = false;
- mSeekTimeUs = seekTimeUs;
- }
-
- while (!mPrefetcherStopped && !mReachedEOS && mCachedBuffers.empty()) {
- mCondition.wait(mLock);
- }
-
- if (mCachedBuffers.empty()) {
- return mReachedEOS ? mFinalStatus : ERROR_END_OF_STREAM;
- }
-
- *out = *mCachedBuffers.begin();
- mCachedBuffers.erase(mCachedBuffers.begin());
- updateCacheDuration_l();
- mCacheSizeBytes -= (*out)->size();
-
- return OK;
-}
-
-sp<MetaData> PrefetchedSource::getFormat() {
- return mSource->getFormat();
-}
-
-bool PrefetchedSource::getCacheDurationUs(
- int64_t *durationUs, size_t *totalSize) {
- Mutex::Autolock autoLock(mLock);
-
- *durationUs = mCacheDurationUs;
- if (totalSize != NULL) {
- *totalSize = mCacheSizeBytes;
- }
-
- if (!mStarted || mReachedEOS) {
- return false;
- }
-
- return true;
-}
-
-void PrefetchedSource::cacheMore() {
- MediaSource::ReadOptions options;
-
- Mutex::Autolock autoLock(mLock);
-
- if (!mStarted) {
- return;
- }
-
- mCurrentlyPrefetching = true;
-
- if (mSeekTimeUs >= 0) {
- options.setSeekTo(mSeekTimeUs);
- mSeekTimeUs = -1;
- }
-
- // Ensure our object does not go away while we're not holding
- // the lock.
- sp<PrefetchedSource> me = this;
-
- mLock.unlock();
- MediaBuffer *buffer;
- status_t err = mSource->read(&buffer, &options);
- mLock.lock();
-
- if (err != OK) {
- mCurrentlyPrefetching = false;
- mReachedEOS = true;
- mFinalStatus = err;
- mCondition.signal();
-
- return;
- }
-
- CHECK(buffer != NULL);
-
- MediaBuffer *copy = new MediaBuffer(buffer->range_length());
- memcpy(copy->data(),
- (const uint8_t *)buffer->data() + buffer->range_offset(),
- buffer->range_length());
-
- sp<MetaData> from = buffer->meta_data();
- sp<MetaData> to = copy->meta_data();
-
- int64_t timeUs;
- if (from->findInt64(kKeyTime, &timeUs)) {
- to->setInt64(kKeyTime, timeUs);
- }
-
- buffer->release();
- buffer = NULL;
-
- mCachedBuffers.push_back(copy);
- updateCacheDuration_l();
- mCacheSizeBytes += copy->size();
-
- mCurrentlyPrefetching = false;
- mCondition.signal();
-}
-
-void PrefetchedSource::updateCacheDuration_l() {
- if (mCachedBuffers.size() < 2) {
- mCacheDurationUs = 0;
- } else {
- int64_t firstTimeUs, lastTimeUs;
- CHECK((*mCachedBuffers.begin())->meta_data()->findInt64(
- kKeyTime, &firstTimeUs));
- CHECK((*--mCachedBuffers.end())->meta_data()->findInt64(
- kKeyTime, &lastTimeUs));
-
- mCacheDurationUs = lastTimeUs - firstTimeUs;
- }
-}
-
-void PrefetchedSource::clearCache_l() {
- List<MediaBuffer *>::iterator it = mCachedBuffers.begin();
- while (it != mCachedBuffers.end()) {
- (*it)->release();
-
- it = mCachedBuffers.erase(it);
- }
-
- updateCacheDuration_l();
- mCacheSizeBytes = 0;
-}
-
-void PrefetchedSource::onPrefetcherStopped() {
- Mutex::Autolock autoLock(mLock);
- mPrefetcherStopped = true;
- mCondition.signal();
-}
-
-} // namespace android
diff --git a/media/libstagefright/codecs/aacenc/AACEncoder.cpp b/media/libstagefright/codecs/aacenc/AACEncoder.cpp
index d222cd9..282a10b 100644
--- a/media/libstagefright/codecs/aacenc/AACEncoder.cpp
+++ b/media/libstagefright/codecs/aacenc/AACEncoder.cpp
@@ -205,13 +205,15 @@
buffer->set_range(0, 2);
buffer->meta_data()->setInt32(kKeyIsCodecConfig, true);
*out = buffer;
- ++mFrameCount;
mInputBuffer = NULL;
+ ++mFrameCount;
return OK;
- } else {
+ } else if (mFrameCount == 1) {
buffer->meta_data()->setInt32(kKeyIsCodecConfig, false);
}
+ // XXX: We assume that the input buffer contains at least
+ // (actually, exactly) 1024 PCM samples. This needs to be fixed.
if (mInputBuffer == NULL) {
if (mSource->read(&mInputBuffer, options) != OK) {
LOGE("failed to read from input audio source");
@@ -252,9 +254,10 @@
}
buffer->set_range(0, outputLength);
- ++mFrameCount;
- int64_t timestampUs = (mFrameCount * 1000000LL * 1024) / mSampleRate;
+ // Each output frame compresses 1024 input PCM samples.
+ int64_t timestampUs = ((mFrameCount - 1) * 1000000LL * 1024) / mSampleRate;
+ ++mFrameCount;
buffer->meta_data()->setInt64(kKeyTime, timestampUs);
*out = buffer;
diff --git a/media/libstagefright/httplive/LiveSource.cpp b/media/libstagefright/httplive/LiveSource.cpp
index 9e3aa7b..001afc4 100644
--- a/media/libstagefright/httplive/LiveSource.cpp
+++ b/media/libstagefright/httplive/LiveSource.cpp
@@ -18,12 +18,10 @@
#include <utils/Log.h>
#include "include/LiveSource.h"
-
-#include "include/HTTPStream.h"
#include "include/M3UParser.h"
+#include "include/NuHTTPDataSource.h"
#include <media/stagefright/foundation/ABuffer.h>
-#include <media/stagefright/HTTPDataSource.h>
#include <media/stagefright/MediaDebug.h>
namespace android {
@@ -33,6 +31,7 @@
mInitCheck(NO_INIT),
mPlaylistIndex(0),
mLastFetchTimeUs(-1),
+ mSource(new NuHTTPDataSource),
mSourceSize(0),
mOffsetBias(0) {
if (switchToNext()) {
@@ -115,8 +114,7 @@
CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri));
LOGI("switching to %s", uri.c_str());
- mSource = new HTTPDataSource(uri.c_str());
- if (mSource->connect() != OK
+ if (mSource->connect(uri.c_str()) != OK
|| mSource->getSize(&mSourceSize) != OK) {
return false;
}
@@ -156,8 +154,7 @@
status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
*out = NULL;
- mSource = new HTTPDataSource(url);
- status_t err = mSource->connect();
+ status_t err = mSource->connect(url);
if (err != OK) {
return err;
diff --git a/media/libstagefright/include/ARTSPController.h b/media/libstagefright/include/ARTSPController.h
new file mode 100644
index 0000000..55efd41
--- /dev/null
+++ b/media/libstagefright/include/ARTSPController.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_RTSP_CONTROLLER_H_
+
+#define A_RTSP_CONTROLLER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/MediaExtractor.h>
+
+namespace android {
+
+struct ALooper;
+struct MyHandler;
+
+struct ARTSPController : public MediaExtractor {
+ ARTSPController(const sp<ALooper> &looper);
+
+ status_t connect(const char *url);
+ void disconnect();
+
+ virtual size_t countTracks();
+ virtual sp<MediaSource> getTrack(size_t index);
+
+ virtual sp<MetaData> getTrackMetaData(
+ size_t index, uint32_t flags);
+
+protected:
+ virtual ~ARTSPController();
+
+private:
+ sp<ALooper> mLooper;
+ sp<MyHandler> mHandler;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTSPController);
+};
+
+} // namespace android
+
+#endif // A_RTSP_CONTROLLER_H_
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 9455743..2a9f21b 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -18,11 +18,11 @@
#define AWESOME_PLAYER_H_
+#include "NuHTTPDataSource.h"
#include "TimedEventQueue.h"
#include <media/MediaPlayerInterface.h>
#include <media/stagefright/DataSource.h>
-#include <media/stagefright/HTTPDataSource.h>
#include <media/stagefright/OMXClient.h>
#include <utils/threads.h>
@@ -33,8 +33,11 @@
struct MediaBuffer;
struct MediaExtractor;
struct MediaSource;
-struct Prefetcher;
struct TimeSource;
+struct NuCachedSource2;
+
+struct ALooper;
+struct ARTSPController;
struct AwesomeRenderer : public RefBase {
AwesomeRenderer() {}
@@ -98,6 +101,7 @@
PREPARED = 16,
AT_EOS = 32,
PREPARE_CANCELLED = 64,
+ CACHE_UNDERRUN = 128,
};
mutable Mutex mLock;
@@ -166,8 +170,11 @@
MediaBuffer *mLastVideoBuffer;
MediaBuffer *mVideoBuffer;
- sp<Prefetcher> mPrefetcher;
- sp<HTTPDataSource> mConnectingDataSource;
+ sp<NuHTTPDataSource> mConnectingDataSource;
+ sp<NuCachedSource2> mCachedSource;
+
+ sp<ALooper> mLooper;
+ sp<ARTSPController> mRTSPController;
struct SuspensionState {
String8 mUri;
diff --git a/media/libstagefright/include/LiveSource.h b/media/libstagefright/include/LiveSource.h
index 3218633..c55508c 100644
--- a/media/libstagefright/include/LiveSource.h
+++ b/media/libstagefright/include/LiveSource.h
@@ -26,7 +26,7 @@
namespace android {
struct ABuffer;
-struct HTTPDataSource;
+struct NuHTTPDataSource;
struct M3UParser;
struct LiveSource : public DataSource {
@@ -52,7 +52,7 @@
size_t mPlaylistIndex;
int64_t mLastFetchTimeUs;
- sp<HTTPDataSource> mSource;
+ sp<NuHTTPDataSource> mSource;
off_t mSourceSize;
off_t mOffsetBias;
diff --git a/media/libstagefright/include/NuCachedSource2.h b/media/libstagefright/include/NuCachedSource2.h
new file mode 100644
index 0000000..c30f029
--- /dev/null
+++ b/media/libstagefright/include/NuCachedSource2.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 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 NU_CACHED_SOURCE_2_H_
+
+#define NU_CACHED_SOURCE_2_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <media/stagefright/DataSource.h>
+
+namespace android {
+
+struct ALooper;
+struct PageCache;
+
+struct NuCachedSource2 : public DataSource {
+ NuCachedSource2(const sp<DataSource> &source);
+
+ virtual status_t initCheck() const;
+
+ virtual ssize_t readAt(off_t offset, void *data, size_t size);
+
+ virtual status_t getSize(off_t *size);
+ virtual uint32_t flags();
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ size_t cachedSize();
+ size_t approxDataRemaining(bool *eos);
+
+protected:
+ virtual ~NuCachedSource2();
+
+private:
+ friend struct AHandlerReflector<NuCachedSource2>;
+
+ enum {
+ kPageSize = 16384,
+ kHighWaterThreshold = 3 * 1024 * 1024,
+ kLowWaterThreshold = 512 * 1024,
+ };
+
+ enum {
+ kWhatFetchMore = 'fetc',
+ kWhatRead = 'read',
+ };
+
+ sp<DataSource> mSource;
+ sp<AHandlerReflector<NuCachedSource2> > mReflector;
+ sp<ALooper> mLooper;
+
+ Mutex mSerializer;
+ Mutex mLock;
+ Condition mCondition;
+
+ PageCache *mCache;
+ off_t mCacheOffset;
+ status_t mFinalStatus;
+ off_t mLastAccessPos;
+ sp<AMessage> mAsyncResult;
+ bool mFetching;
+
+ void onMessageReceived(const sp<AMessage> &msg);
+ void onFetch();
+ void onRead(const sp<AMessage> &msg);
+
+ void fetchInternal();
+ ssize_t readInternal(off_t offset, void *data, size_t size);
+ status_t seekInternal_l(off_t offset);
+
+ size_t approxDataRemaining_l(bool *eos);
+ void restartPrefetcherIfNecessary_l();
+
+ DISALLOW_EVIL_CONSTRUCTORS(NuCachedSource2);
+};
+
+} // namespace android
+
+#endif // NU_CACHED_SOURCE_2_H_
diff --git a/media/libstagefright/include/NuHTTPDataSource.h b/media/libstagefright/include/NuHTTPDataSource.h
new file mode 100644
index 0000000..8593a91
--- /dev/null
+++ b/media/libstagefright/include/NuHTTPDataSource.h
@@ -0,0 +1,73 @@
+#ifndef NU_HTTP_DATA_SOURCE_H_
+
+#define NU_HTTP_DATA_SOURCE_H_
+
+#include <media/stagefright/DataSource.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+
+#include "HTTPStream.h"
+
+namespace android {
+
+struct NuHTTPDataSource : public DataSource {
+ NuHTTPDataSource();
+
+ status_t connect(
+ const char *uri,
+ const KeyedVector<String8, String8> *headers = NULL,
+ off_t offset = 0);
+
+ void disconnect();
+
+ virtual status_t initCheck() const;
+
+ virtual ssize_t readAt(off_t offset, void *data, size_t size);
+ virtual status_t getSize(off_t *size);
+ virtual uint32_t flags();
+
+protected:
+ virtual ~NuHTTPDataSource();
+
+private:
+ enum State {
+ DISCONNECTED,
+ CONNECTING,
+ CONNECTED
+ };
+
+ Mutex mLock;
+
+ State mState;
+
+ String8 mHost;
+ unsigned mPort;
+ String8 mPath;
+ String8 mHeaders;
+
+ HTTPStream mHTTP;
+ off_t mOffset;
+ off_t mContentLength;
+ bool mContentLengthValid;
+
+ status_t connect(
+ const char *uri, const String8 &headers, off_t offset);
+
+ status_t connect(
+ const char *host, unsigned port, const char *path,
+ const String8 &headers,
+ off_t offset);
+
+ void applyTimeoutResponse();
+
+ static void MakeFullHeaders(
+ const KeyedVector<String8, String8> *overrides,
+ String8 *headers);
+
+ NuHTTPDataSource(const NuHTTPDataSource &);
+ NuHTTPDataSource &operator=(const NuHTTPDataSource &);
+};
+
+} // namespace android
+
+#endif // NU_HTTP_DATA_SOURCE_H_
diff --git a/media/libstagefright/include/Prefetcher.h b/media/libstagefright/include/Prefetcher.h
deleted file mode 100644
index b411d1b..0000000
--- a/media/libstagefright/include/Prefetcher.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2010 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 PREFETCHER_H_
-
-#define PREFETCHER_H_
-
-#include <utils/RefBase.h>
-#include <utils/Vector.h>
-#include <utils/threads.h>
-
-namespace android {
-
-struct MediaSource;
-struct PrefetchedSource;
-
-struct Prefetcher : public RefBase {
- Prefetcher();
-
- // Given an existing MediaSource returns a new MediaSource
- // that will benefit from prefetching/caching the original one.
- sp<MediaSource> addSource(const sp<MediaSource> &source);
-
- int64_t getCachedDurationUs(bool *noMoreData = NULL);
-
- // If provided (non-NULL), "continueFunc" will be called repeatedly
- // while preparing and preparation will finish early if it returns
- // false. In this case "-EINTR" is returned as a result.
- status_t prepare(
- bool (*continueFunc)(void *cookie) = NULL,
- void *cookie = NULL);
-
-protected:
- virtual ~Prefetcher();
-
-private:
- Mutex mLock;
- Condition mCondition;
-
- Vector<wp<PrefetchedSource> > mSources;
- android_thread_id_t mThread;
- bool mDone;
- bool mThreadExited;
-
- void startThread();
- void stopThread();
-
- static int ThreadWrapper(void *me);
- void threadFunc();
-
- Prefetcher(const Prefetcher &);
- Prefetcher &operator=(const Prefetcher &);
-};
-
-} // namespace android
-
-#endif // PREFETCHER_H_
diff --git a/media/libstagefright/rtsp/AAVCAssembler.cpp b/media/libstagefright/rtsp/AAVCAssembler.cpp
new file mode 100644
index 0000000..3dfb200
--- /dev/null
+++ b/media/libstagefright/rtsp/AAVCAssembler.cpp
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2010 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 "AAVCAssembler.h"
+
+#include "ARTPSource.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 <stdint.h>
+
+#define BE_VERBOSE 0
+
+namespace android {
+
+// static
+AAVCAssembler::AAVCAssembler(const sp<AMessage> ¬ify)
+ : mNotifyMsg(notify),
+ mAccessUnitRTPTime(0),
+ mNextExpectedSeqNoValid(false),
+ mNextExpectedSeqNo(0),
+ mAccessUnitDamaged(false) {
+}
+
+AAVCAssembler::~AAVCAssembler() {
+}
+
+ARTPAssembler::AssemblyStatus AAVCAssembler::addNALUnit(
+ const sp<ARTPSource> &source) {
+ List<sp<ABuffer> > *queue = source->queue();
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ if (mNextExpectedSeqNoValid) {
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ while (it != queue->end()) {
+ if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
+ break;
+ }
+
+ it = queue->erase(it);
+ }
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+ }
+
+ sp<ABuffer> buffer = *queue->begin();
+
+ if (!mNextExpectedSeqNoValid) {
+ mNextExpectedSeqNoValid = true;
+ mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
+ } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
+#if BE_VERBOSE
+ LOG(VERBOSE) << "Not the sequence number I expected";
+#endif
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if (size < 1 || (data[0] & 0x80)) {
+ // Corrupt.
+
+ LOG(ERROR) << "Ignoring corrupt buffer.";
+ queue->erase(queue->begin());
+
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ unsigned nalType = data[0] & 0x1f;
+ if (nalType >= 1 && nalType <= 23) {
+ addSingleNALUnit(buffer);
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return OK;
+ } else if (nalType == 28) {
+ // FU-A
+ return addFragmentedNALUnit(queue);
+ } else if (nalType == 24) {
+ // STAP-A
+ bool success = addSingleTimeAggregationPacket(buffer);
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return success ? OK : MALFORMED_PACKET;
+ } else {
+ LOG(ERROR) << "Ignoring unsupported buffer (nalType=" << nalType << ")";
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return MALFORMED_PACKET;
+ }
+}
+
+void AAVCAssembler::addSingleNALUnit(const sp<ABuffer> &buffer) {
+#if BE_VERBOSE
+ LOG(VERBOSE) << "addSingleNALUnit of size " << buffer->size();
+ hexdump(buffer->data(), buffer->size());
+#endif
+
+ uint32_t rtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ if (!mNALUnits.empty() && rtpTime != mAccessUnitRTPTime) {
+ submitAccessUnit();
+ }
+ mAccessUnitRTPTime = rtpTime;
+
+ mNALUnits.push_back(buffer);
+}
+
+bool AAVCAssembler::addSingleTimeAggregationPacket(const sp<ABuffer> &buffer) {
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if (size < 3) {
+ LOG(ERROR) << "Discarding too small STAP-A packet.";
+ return false;
+ }
+
+ ++data;
+ --size;
+ while (size >= 2) {
+ size_t nalSize = (data[0] << 8) | data[1];
+
+ if (size < nalSize + 2) {
+ LOG(ERROR) << "Discarding malformed STAP-A packet.";
+ return false;
+ }
+
+ sp<ABuffer> unit = new ABuffer(nalSize);
+ memcpy(unit->data(), &data[2], nalSize);
+
+ PropagateTimes(buffer, unit);
+
+ addSingleNALUnit(unit);
+
+ data += 2 + nalSize;
+ size -= 2 + nalSize;
+ }
+
+ if (size != 0) {
+ LOG(WARNING) << "Unexpected padding at end of STAP-A packet.";
+ }
+
+ return true;
+}
+
+ARTPAssembler::AssemblyStatus AAVCAssembler::addFragmentedNALUnit(
+ List<sp<ABuffer> > *queue) {
+ CHECK(!queue->empty());
+
+ sp<ABuffer> buffer = *queue->begin();
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ CHECK(size > 0);
+ unsigned indicator = data[0];
+
+ CHECK((indicator & 0x1f) == 28);
+
+ if (size < 2) {
+ LOG(ERROR) << "Ignoring malformed FU buffer (size = " << size << ")";
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ if (!(data[1] & 0x80)) {
+ // Start bit not set on the first buffer.
+
+#if BE_VERBOSE
+ LOG(ERROR) << "Start bit not set on first buffer";
+#endif
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ uint32_t nalType = data[1] & 0x1f;
+ uint32_t nri = (data[0] >> 5) & 3;
+
+ uint32_t expectedSeqNo = (uint32_t)buffer->int32Data() + 1;
+ size_t totalSize = size - 2;
+ size_t totalCount = 1;
+ bool complete = false;
+
+ if (data[1] & 0x40) {
+ // Huh? End bit also set on the first buffer.
+
+#if BE_VERBOSE
+ LOG(WARNING) << "Grrr. This isn't fragmented at all.";
+#endif
+
+ complete = true;
+ } else {
+ List<sp<ABuffer> >::iterator it = ++queue->begin();
+ while (it != queue->end()) {
+#if BE_VERBOSE
+ LOG(VERBOSE) << "sequence length " << totalCount;
+#endif
+
+ const sp<ABuffer> &buffer = *it;
+
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if ((uint32_t)buffer->int32Data() != expectedSeqNo) {
+#if BE_VERBOSE
+ LOG(VERBOSE) << "sequence not complete, expected seqNo "
+ << expectedSeqNo << ", got "
+ << (uint32_t)buffer->int32Data();
+#endif
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ if (size < 2
+ || data[0] != indicator
+ || (data[1] & 0x1f) != nalType
+ || (data[1] & 0x80)) {
+ LOG(ERROR) << "Ignoring malformed FU buffer.\n";
+
+ // Delete the whole start of the FU.
+
+ it = queue->begin();
+ for (size_t i = 0; i <= totalCount; ++i) {
+ it = queue->erase(it);
+ }
+
+ mNextExpectedSeqNo = expectedSeqNo + 1;
+
+ return MALFORMED_PACKET;
+ }
+
+ totalSize += size - 2;
+ ++totalCount;
+
+ expectedSeqNo = expectedSeqNo + 1;
+
+ if (data[1] & 0x40) {
+ // This is the last fragment.
+ complete = true;
+ break;
+ }
+
+ ++it;
+ }
+ }
+
+ if (!complete) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ mNextExpectedSeqNo = expectedSeqNo;
+
+ // We found all the fragments that make up the complete NAL unit.
+
+ // Leave room for the header. So far totalSize did not include the
+ // header byte.
+ ++totalSize;
+
+ sp<ABuffer> unit = new ABuffer(totalSize);
+ PropagateTimes(buffer, unit);
+
+ unit->data()[0] = (nri << 5) | nalType;
+
+ size_t offset = 1;
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ for (size_t i = 0; i < totalCount; ++i) {
+ const sp<ABuffer> &buffer = *it;
+
+#if BE_VERBOSE
+ LOG(VERBOSE) << "piece #" << (i + 1) << "/" << totalCount;
+ hexdump(buffer->data(), buffer->size());
+#endif
+
+ memcpy(unit->data() + offset, buffer->data() + 2, buffer->size() - 2);
+ offset += buffer->size() - 2;
+
+ it = queue->erase(it);
+ }
+
+ unit->setRange(0, totalSize);
+
+ addSingleNALUnit(unit);
+
+#if BE_VERBOSE
+ LOG(VERBOSE) << "successfully assembled a NAL unit from fragments.";
+#endif
+
+ return OK;
+}
+
+void AAVCAssembler::submitAccessUnit() {
+ CHECK(!mNALUnits.empty());
+
+#if BE_VERBOSE
+ LOG(VERBOSE) << "Access unit complete (" << mNALUnits.size() << " nal units)";
+#endif
+
+ uint64_t ntpTime;
+ CHECK((*mNALUnits.begin())->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+ 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) {
+ memcpy(accessUnit->data() + offset, "\x00\x00\x00\x01", 4);
+ offset += 4;
+
+ sp<ABuffer> nal = *it;
+ memcpy(accessUnit->data() + offset, nal->data(), nal->size());
+ offset += nal->size();
+ }
+
+ accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+#if 0
+ printf(mAccessUnitDamaged ? "X" : ".");
+ fflush(stdout);
+#endif
+
+ if (mAccessUnitDamaged) {
+ accessUnit->meta()->setInt32("damaged", true);
+ }
+
+ mNALUnits.clear();
+ mAccessUnitDamaged = false;
+
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setObject("access-unit", accessUnit);
+ msg->post();
+}
+
+ARTPAssembler::AssemblyStatus AAVCAssembler::assembleMore(
+ const sp<ARTPSource> &source) {
+ AssemblyStatus status = addNALUnit(source);
+ if (status == MALFORMED_PACKET) {
+ mAccessUnitDamaged = true;
+ }
+ return status;
+}
+
+void AAVCAssembler::packetLost() {
+ CHECK(mNextExpectedSeqNoValid);
+ ++mNextExpectedSeqNo;
+
+ mAccessUnitDamaged = true;
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/AAVCAssembler.h b/media/libstagefright/rtsp/AAVCAssembler.h
new file mode 100644
index 0000000..1e97520
--- /dev/null
+++ b/media/libstagefright/rtsp/AAVCAssembler.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_AVC_ASSEMBLER_H_
+
+#define A_AVC_ASSEMBLER_H_
+
+#include "ARTPAssembler.h"
+
+#include <utils/List.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+
+struct AAVCAssembler : public ARTPAssembler {
+ AAVCAssembler(const sp<AMessage> ¬ify);
+
+protected:
+ virtual ~AAVCAssembler();
+
+ virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+ virtual void packetLost();
+
+private:
+ sp<AMessage> mNotifyMsg;
+
+ uint32_t mAccessUnitRTPTime;
+ bool mNextExpectedSeqNoValid;
+ uint32_t mNextExpectedSeqNo;
+ bool mAccessUnitDamaged;
+ List<sp<ABuffer> > mNALUnits;
+
+ AssemblyStatus addNALUnit(const sp<ARTPSource> &source);
+ void addSingleNALUnit(const sp<ABuffer> &buffer);
+ AssemblyStatus addFragmentedNALUnit(List<sp<ABuffer> > *queue);
+ bool addSingleTimeAggregationPacket(const sp<ABuffer> &buffer);
+
+ void submitAccessUnit();
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAVCAssembler);
+};
+
+} // namespace android
+
+#endif // A_AVC_ASSEMBLER_H_
diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
new file mode 100644
index 0000000..0549d84
--- /dev/null
+++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2010 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 "AMPEG4AudioAssembler.h"
+
+#include "ARTPSource.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+namespace android {
+
+AMPEG4AudioAssembler::AMPEG4AudioAssembler(const sp<AMessage> ¬ify)
+ : mNotifyMsg(notify),
+ mAccessUnitRTPTime(0),
+ mNextExpectedSeqNoValid(false),
+ mNextExpectedSeqNo(0),
+ mAccessUnitDamaged(false) {
+}
+
+AMPEG4AudioAssembler::~AMPEG4AudioAssembler() {
+}
+
+ARTPAssembler::AssemblyStatus AMPEG4AudioAssembler::assembleMore(
+ const sp<ARTPSource> &source) {
+ AssemblyStatus status = addPacket(source);
+ if (status == MALFORMED_PACKET) {
+ mAccessUnitDamaged = true;
+ }
+ return status;
+}
+
+ARTPAssembler::AssemblyStatus AMPEG4AudioAssembler::addPacket(
+ const sp<ARTPSource> &source) {
+ List<sp<ABuffer> > *queue = source->queue();
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ if (mNextExpectedSeqNoValid) {
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ while (it != queue->end()) {
+ if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
+ break;
+ }
+
+ it = queue->erase(it);
+ }
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+ }
+
+ sp<ABuffer> buffer = *queue->begin();
+
+ if (!mNextExpectedSeqNoValid) {
+ mNextExpectedSeqNoValid = true;
+ mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
+ } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
+#if VERBOSE
+ LOG(VERBOSE) << "Not the sequence number I expected";
+#endif
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ uint32_t rtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ if (mPackets.size() > 0 && rtpTime != mAccessUnitRTPTime) {
+ submitAccessUnit();
+ }
+ mAccessUnitRTPTime = rtpTime;
+
+ mPackets.push_back(buffer);
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return OK;
+}
+
+void AMPEG4AudioAssembler::submitAccessUnit() {
+ CHECK(!mPackets.empty());
+
+#if VERBOSE
+ LOG(VERBOSE) << "Access unit complete (" << mPackets.size() << " packets)";
+#endif
+
+ uint64_t ntpTime;
+ CHECK((*mPackets.begin())->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+ size_t totalSize = 0;
+ List<sp<ABuffer> >::iterator it = mPackets.begin();
+ while (it != mPackets.end()) {
+ const sp<ABuffer> &unit = *it;
+
+ size_t n = 0;
+ while (unit->data()[n] == 0xff) {
+ ++n;
+ }
+ ++n;
+
+ totalSize += unit->size() - n;
+ ++it;
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(totalSize);
+ size_t offset = 0;
+ it = mPackets.begin();
+ while (it != mPackets.end()) {
+ const sp<ABuffer> &unit = *it;
+
+ size_t n = 0;
+ while (unit->data()[n] == 0xff) {
+ ++n;
+ }
+ ++n;
+
+ memcpy((uint8_t *)accessUnit->data() + offset,
+ unit->data() + n, unit->size() - n);
+
+ offset += unit->size() - n;
+
+ ++it;
+ }
+
+ accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+ if (mAccessUnitDamaged) {
+ accessUnit->meta()->setInt32("damaged", true);
+ }
+
+ mPackets.clear();
+ mAccessUnitDamaged = false;
+
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setObject("access-unit", accessUnit);
+ msg->post();
+}
+
+void AMPEG4AudioAssembler::packetLost() {
+ CHECK(mNextExpectedSeqNoValid);
+ ++mNextExpectedSeqNo;
+
+ mAccessUnitDamaged = true;
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.h b/media/libstagefright/rtsp/AMPEG4AudioAssembler.h
new file mode 100644
index 0000000..5c2a2dd
--- /dev/null
+++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_MPEG4_AUDIO_ASSEMBLER_H_
+
+#define A_MPEG4_AUDIO_ASSEMBLER_H_
+
+#include "ARTPAssembler.h"
+
+#include <utils/List.h>
+
+#include <stdint.h>
+
+namespace android {
+
+struct AMessage;
+
+struct AMPEG4AudioAssembler : public ARTPAssembler {
+ AMPEG4AudioAssembler(const sp<AMessage> ¬ify);
+
+protected:
+ virtual ~AMPEG4AudioAssembler();
+
+ virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+ virtual void packetLost();
+
+private:
+ sp<AMessage> mNotifyMsg;
+ uint32_t mAccessUnitRTPTime;
+ bool mNextExpectedSeqNoValid;
+ uint32_t mNextExpectedSeqNo;
+ bool mAccessUnitDamaged;
+ List<sp<ABuffer> > mPackets;
+
+ AssemblyStatus addPacket(const sp<ARTPSource> &source);
+ void submitAccessUnit();
+
+ DISALLOW_EVIL_CONSTRUCTORS(AMPEG4AudioAssembler);
+};
+
+} // namespace android
+
+#endif // A_MPEG4_AUDIO_ASSEMBLER_H_
diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp
new file mode 100644
index 0000000..2869d54
--- /dev/null
+++ b/media/libstagefright/rtsp/APacketSource.cpp
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2010 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 "APacketSource.h"
+
+#include "ASessionDescription.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/base64.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+static bool GetAttribute(const char *s, const char *key, AString *value) {
+ value->clear();
+
+ size_t keyLen = strlen(key);
+
+ for (;;) {
+ const char *colonPos = strchr(s, ';');
+
+ size_t len =
+ (colonPos == NULL) ? strlen(s) : colonPos - s;
+
+ if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) {
+ value->setTo(&s[keyLen + 1], len - keyLen - 1);
+ return true;
+ }
+
+ if (colonPos == NULL) {
+ return false;
+ }
+
+ s = colonPos + 1;
+ }
+}
+
+static sp<ABuffer> decodeHex(const AString &s) {
+ if ((s.size() % 2) != 0) {
+ return NULL;
+ }
+
+ size_t outLen = s.size() / 2;
+ sp<ABuffer> buffer = new ABuffer(outLen);
+ uint8_t *out = buffer->data();
+
+ uint8_t accum = 0;
+ for (size_t i = 0; i < s.size(); ++i) {
+ char c = s.c_str()[i];
+ unsigned value;
+ if (c >= '0' && c <= '9') {
+ value = c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ value = c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ value = c - 'A' + 10;
+ } else {
+ return NULL;
+ }
+
+ accum = (accum << 4) | value;
+
+ if (i & 1) {
+ *out++ = accum;
+
+ accum = 0;
+ }
+ }
+
+ return buffer;
+}
+
+static sp<ABuffer> MakeAVCCodecSpecificData(const char *params) {
+ AString val;
+ CHECK(GetAttribute(params, "profile-level-id", &val));
+
+ sp<ABuffer> profileLevelID = decodeHex(val);
+ CHECK(profileLevelID != NULL);
+ CHECK_EQ(profileLevelID->size(), 3u);
+
+ Vector<sp<ABuffer> > paramSets;
+
+ size_t numSeqParameterSets = 0;
+ size_t totalSeqParameterSetSize = 0;
+ size_t numPicParameterSets = 0;
+ size_t totalPicParameterSetSize = 0;
+
+ CHECK(GetAttribute(params, "sprop-parameter-sets", &val));
+ size_t start = 0;
+ for (;;) {
+ ssize_t commaPos = val.find(",", start);
+ size_t end = (commaPos < 0) ? val.size() : commaPos;
+
+ AString nalString(val, start, end - start);
+ sp<ABuffer> nal = decodeBase64(nalString);
+ CHECK(nal != NULL);
+ CHECK_GT(nal->size(), 0u);
+ CHECK_LE(nal->size(), 65535u);
+
+ uint8_t nalType = nal->data()[0] & 0x1f;
+ if (numSeqParameterSets == 0) {
+ CHECK_EQ((unsigned)nalType, 7u);
+ } else if (numPicParameterSets > 0) {
+ CHECK_EQ((unsigned)nalType, 8u);
+ }
+ if (nalType == 7) {
+ ++numSeqParameterSets;
+ totalSeqParameterSetSize += nal->size();
+ } else {
+ CHECK_EQ((unsigned)nalType, 8u);
+ ++numPicParameterSets;
+ totalPicParameterSetSize += nal->size();
+ }
+
+ paramSets.push(nal);
+
+ if (commaPos < 0) {
+ break;
+ }
+
+ start = commaPos + 1;
+ }
+
+ CHECK_LT(numSeqParameterSets, 32u);
+ CHECK_LE(numPicParameterSets, 255u);
+
+ size_t csdSize =
+ 1 + 3 + 1 + 1
+ + 2 * numSeqParameterSets + totalSeqParameterSetSize
+ + 1 + 2 * numPicParameterSets + totalPicParameterSetSize;
+
+ sp<ABuffer> csd = new ABuffer(csdSize);
+ uint8_t *out = csd->data();
+
+ *out++ = 0x01; // configurationVersion
+ memcpy(out, profileLevelID->data(), 3);
+ out += 3;
+ *out++ = (0x3f << 2) | 1; // lengthSize == 2 bytes
+ *out++ = 0xe0 | numSeqParameterSets;
+
+ for (size_t i = 0; i < numSeqParameterSets; ++i) {
+ sp<ABuffer> nal = paramSets.editItemAt(i);
+
+ *out++ = nal->size() >> 8;
+ *out++ = nal->size() & 0xff;
+
+ memcpy(out, nal->data(), nal->size());
+
+ out += nal->size();
+ }
+
+ *out++ = numPicParameterSets;
+
+ for (size_t i = 0; i < numPicParameterSets; ++i) {
+ sp<ABuffer> nal = paramSets.editItemAt(i + numSeqParameterSets);
+
+ *out++ = nal->size() >> 8;
+ *out++ = nal->size() & 0xff;
+
+ memcpy(out, nal->data(), nal->size());
+
+ out += nal->size();
+ }
+
+ hexdump(csd->data(), csd->size());
+
+ return csd;
+}
+
+sp<ABuffer> MakeAACCodecSpecificData(const char *params) {
+ AString val;
+ CHECK(GetAttribute(params, "config", &val));
+
+ sp<ABuffer> config = decodeHex(val);
+ CHECK(config != NULL);
+ CHECK_GE(config->size(), 4u);
+
+ const uint8_t *data = config->data();
+ uint32_t x = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+ x = (x >> 1) & 0xffff;
+
+ static const uint8_t kStaticESDS[] = {
+ 0x03, 22,
+ 0x00, 0x00, // ES_ID
+ 0x00, // streamDependenceFlag, URL_Flag, OCRstreamFlag
+
+ 0x04, 17,
+ 0x40, // Audio ISO/IEC 14496-3
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x05, 2,
+ // AudioSpecificInfo follows
+ };
+
+ sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + 2);
+ memcpy(csd->data(), kStaticESDS, sizeof(kStaticESDS));
+ csd->data()[sizeof(kStaticESDS)] = (x >> 8) & 0xff;
+ csd->data()[sizeof(kStaticESDS) + 1] = x & 0xff;
+
+ hexdump(csd->data(), csd->size());
+
+ return csd;
+}
+
+APacketSource::APacketSource(
+ const sp<ASessionDescription> &sessionDesc, size_t index)
+ : mFormat(new MetaData),
+ mEOSResult(OK) {
+ unsigned long PT;
+ AString desc;
+ AString params;
+ sessionDesc->getFormatType(index, &PT, &desc, ¶ms);
+
+ int64_t durationUs;
+ if (sessionDesc->getDurationUs(&durationUs)) {
+ mFormat->setInt64(kKeyDuration, durationUs);
+ } else {
+ mFormat->setInt64(kKeyDuration, 60 * 60 * 1000000ll);
+ }
+
+ if (!strncmp(desc.c_str(), "H264/", 5)) {
+ mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+
+ int32_t width, height;
+ sessionDesc->getDimensions(index, PT, &width, &height);
+
+ mFormat->setInt32(kKeyWidth, width);
+ mFormat->setInt32(kKeyHeight, height);
+
+ sp<ABuffer> codecSpecificData =
+ MakeAVCCodecSpecificData(params.c_str());
+
+ mFormat->setData(
+ kKeyAVCC, 0,
+ codecSpecificData->data(), codecSpecificData->size());
+
+ } else if (!strncmp(desc.c_str(), "MP4A-LATM", 9)) {
+ mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
+
+ int32_t sampleRate, numChannels;
+ ASessionDescription::ParseFormatDesc(
+ desc.c_str(), &sampleRate, &numChannels);
+
+ mFormat->setInt32(kKeySampleRate, sampleRate);
+ mFormat->setInt32(kKeyChannelCount, numChannels);
+
+ sp<ABuffer> codecSpecificData =
+ MakeAACCodecSpecificData(params.c_str());
+
+ mFormat->setData(
+ kKeyESDS, 0,
+ codecSpecificData->data(), codecSpecificData->size());
+ } else {
+ TRESPASS();
+ }
+}
+
+APacketSource::~APacketSource() {
+}
+
+status_t APacketSource::start(MetaData *params) {
+ return OK;
+}
+
+status_t APacketSource::stop() {
+ return OK;
+}
+
+sp<MetaData> APacketSource::getFormat() {
+ return mFormat;
+}
+
+status_t APacketSource::read(
+ MediaBuffer **out, const ReadOptions *) {
+ *out = NULL;
+
+ Mutex::Autolock autoLock(mLock);
+ while (mEOSResult == OK && mBuffers.empty()) {
+ mCondition.wait(mLock);
+ }
+
+ if (!mBuffers.empty()) {
+ const sp<ABuffer> buffer = *mBuffers.begin();
+
+ uint64_t ntpTime;
+ CHECK(buffer->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+ int64_t timeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
+
+ MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
+ mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
+ memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
+ *out = mediaBuffer;
+
+ mBuffers.erase(mBuffers.begin());
+ return OK;
+ }
+
+ return mEOSResult;
+}
+
+void APacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
+ int32_t damaged;
+ if (buffer->meta()->findInt32("damaged", &damaged) && damaged) {
+ // LOG(VERBOSE) << "discarding damaged AU";
+ return;
+ }
+
+ Mutex::Autolock autoLock(mLock);
+ mBuffers.push_back(buffer);
+ mCondition.signal();
+}
+
+void APacketSource::signalEOS(status_t result) {
+ CHECK(result != OK);
+
+ Mutex::Autolock autoLock(mLock);
+ mEOSResult = result;
+ mCondition.signal();
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/APacketSource.h b/media/libstagefright/rtsp/APacketSource.h
new file mode 100644
index 0000000..4040eee
--- /dev/null
+++ b/media/libstagefright/rtsp/APacketSource.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_PACKET_SOURCE_H_
+
+#define A_PACKET_SOURCE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+
+namespace android {
+
+struct ABuffer;
+struct ASessionDescription;
+
+struct APacketSource : public MediaSource {
+ APacketSource(const sp<ASessionDescription> &sessionDesc, size_t index);
+
+ virtual status_t start(MetaData *params = NULL);
+ virtual status_t stop();
+ virtual sp<MetaData> getFormat();
+
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options = NULL);
+
+ void queueAccessUnit(const sp<ABuffer> &buffer);
+ void signalEOS(status_t result);
+
+protected:
+ virtual ~APacketSource();
+
+private:
+ Mutex mLock;
+ Condition mCondition;
+
+ sp<MetaData> mFormat;
+ List<sp<ABuffer> > mBuffers;
+ status_t mEOSResult;
+
+ DISALLOW_EVIL_CONSTRUCTORS(APacketSource);
+};
+
+
+} // namespace android
+
+#endif // A_PACKET_SOURCE_H_
diff --git a/media/libstagefright/rtsp/ARTPAssembler.cpp b/media/libstagefright/rtsp/ARTPAssembler.cpp
new file mode 100644
index 0000000..24225b8
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPAssembler.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 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 "ARTPAssembler.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include <stdint.h>
+
+namespace android {
+
+static int64_t getNowUs() {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+
+ return (int64_t)tv.tv_usec + tv.tv_sec * 1000000ll;
+}
+
+ARTPAssembler::ARTPAssembler()
+ : mFirstFailureTimeUs(-1) {
+}
+
+void ARTPAssembler::PropagateTimes(
+ const sp<ABuffer> &from, const sp<ABuffer> &to) {
+ uint32_t rtpTime;
+ CHECK(from->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ uint64_t ntpTime = 0;
+ CHECK(from->meta()->findInt64("ntp-time", (int64_t *)&ntpTime));
+
+ to->meta()->setInt32("rtp-time", rtpTime);
+ to->meta()->setInt64("ntp-time", ntpTime);
+}
+
+void ARTPAssembler::onPacketReceived(const sp<ARTPSource> &source) {
+ AssemblyStatus status;
+ for (;;) {
+ status = assembleMore(source);
+
+ if (status == WRONG_SEQUENCE_NUMBER) {
+ if (mFirstFailureTimeUs >= 0) {
+ if (getNowUs() - mFirstFailureTimeUs > 10000ll) {
+ mFirstFailureTimeUs = -1;
+
+ // LOG(VERBOSE) << "waited too long for packet.";
+ packetLost();
+ continue;
+ }
+ } else {
+ mFirstFailureTimeUs = getNowUs();
+ }
+ break;
+ } else {
+ mFirstFailureTimeUs = -1;
+
+ if (status == NOT_ENOUGH_DATA) {
+ break;
+ }
+ }
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/ARTPAssembler.h b/media/libstagefright/rtsp/ARTPAssembler.h
new file mode 100644
index 0000000..892bd65
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPAssembler.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_RTP_ASSEMBLER_H_
+
+#define A_RTP_ASSEMBLER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct ARTPSource;
+
+struct ARTPAssembler : public RefBase {
+ enum AssemblyStatus {
+ MALFORMED_PACKET,
+ WRONG_SEQUENCE_NUMBER,
+ NOT_ENOUGH_DATA,
+ OK
+ };
+
+ ARTPAssembler();
+
+ void onPacketReceived(const sp<ARTPSource> &source);
+
+protected:
+ static void PropagateTimes(
+ const sp<ABuffer> &from, const sp<ABuffer> &to);
+
+ virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source) = 0;
+ virtual void packetLost() = 0;
+
+private:
+ int64_t mFirstFailureTimeUs;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTPAssembler);
+};
+
+} // namespace android
+
+#endif // A_RTP_ASSEMBLER_H_
diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp
new file mode 100644
index 0000000..a4413f0
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPConnection.cpp
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2010 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 "ARTPConnection.h"
+
+#include "ARTPSource.h"
+#include "ASessionDescription.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/AString.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#define VERBOSE 0
+
+#if VERBOSE
+#include "hexdump.h"
+#endif
+
+namespace android {
+
+static uint16_t u16at(const uint8_t *data) {
+ return data[0] << 8 | data[1];
+}
+
+static uint32_t u32at(const uint8_t *data) {
+ return u16at(data) << 16 | u16at(&data[2]);
+}
+
+static uint64_t u64at(const uint8_t *data) {
+ return (uint64_t)(u32at(data)) << 32 | u32at(&data[4]);
+}
+
+// static
+const int64_t ARTPConnection::kSelectTimeoutUs = 1000ll;
+
+struct ARTPConnection::StreamInfo {
+ int mRTPSocket;
+ int mRTCPSocket;
+ sp<ASessionDescription> mSessionDesc;
+ size_t mIndex;
+ sp<AMessage> mNotifyMsg;
+};
+
+ARTPConnection::ARTPConnection()
+ : mPollEventPending(false) {
+}
+
+ARTPConnection::~ARTPConnection() {
+}
+
+void ARTPConnection::addStream(
+ int rtpSocket, int rtcpSocket,
+ const sp<ASessionDescription> &sessionDesc,
+ size_t index,
+ const sp<AMessage> ¬ify) {
+ sp<AMessage> msg = new AMessage(kWhatAddStream, id());
+ msg->setInt32("rtp-socket", rtpSocket);
+ msg->setInt32("rtcp-socket", rtcpSocket);
+ msg->setObject("session-desc", sessionDesc);
+ msg->setSize("index", index);
+ msg->setMessage("notify", notify);
+ msg->post();
+}
+
+void ARTPConnection::removeStream(int rtpSocket, int rtcpSocket) {
+ sp<AMessage> msg = new AMessage(kWhatRemoveStream, id());
+ msg->setInt32("rtp-socket", rtpSocket);
+ msg->setInt32("rtcp-socket", rtcpSocket);
+ msg->post();
+}
+
+static void bumpSocketBufferSize(int s) {
+ int size = 256 * 1024;
+ CHECK_EQ(setsockopt(s, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)), 0);
+}
+
+// static
+void ARTPConnection::MakePortPair(
+ int *rtpSocket, int *rtcpSocket, unsigned *rtpPort) {
+ *rtpSocket = socket(AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(*rtpSocket, 0);
+
+ bumpSocketBufferSize(*rtpSocket);
+
+ *rtcpSocket = socket(AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(*rtcpSocket, 0);
+
+ bumpSocketBufferSize(*rtcpSocket);
+
+ unsigned start = (rand() * 1000)/ RAND_MAX + 15550;
+ start &= ~1;
+
+ for (unsigned port = start; port < 65536; port += 2) {
+ struct sockaddr_in addr;
+ memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = htons(port);
+
+ if (bind(*rtpSocket,
+ (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ continue;
+ }
+
+ addr.sin_port = htons(port + 1);
+
+ if (bind(*rtcpSocket,
+ (const struct sockaddr *)&addr, sizeof(addr)) == 0) {
+ *rtpPort = port;
+ return;
+ }
+ }
+
+ TRESPASS();
+}
+
+void ARTPConnection::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatAddStream:
+ {
+ onAddStream(msg);
+ break;
+ }
+
+ case kWhatRemoveStream:
+ {
+ onRemoveStream(msg);
+ break;
+ }
+
+ case kWhatPollStreams:
+ {
+ onPollStreams();
+ break;
+ }
+
+ default:
+ {
+ TRESPASS();
+ break;
+ }
+ }
+}
+
+void ARTPConnection::onAddStream(const sp<AMessage> &msg) {
+ mStreams.push_back(StreamInfo());
+ StreamInfo *info = &*--mStreams.end();
+
+ int32_t s;
+ CHECK(msg->findInt32("rtp-socket", &s));
+ info->mRTPSocket = s;
+ CHECK(msg->findInt32("rtcp-socket", &s));
+ info->mRTCPSocket = s;
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("session-desc", &obj));
+ info->mSessionDesc = static_cast<ASessionDescription *>(obj.get());
+
+ CHECK(msg->findSize("index", &info->mIndex));
+ CHECK(msg->findMessage("notify", &info->mNotifyMsg));
+
+ postPollEvent();
+}
+
+void ARTPConnection::onRemoveStream(const sp<AMessage> &msg) {
+ int32_t rtpSocket, rtcpSocket;
+ CHECK(msg->findInt32("rtp-socket", &rtpSocket));
+ CHECK(msg->findInt32("rtcp-socket", &rtcpSocket));
+
+ List<StreamInfo>::iterator it = mStreams.begin();
+ while (it != mStreams.end()
+ && (it->mRTPSocket != rtpSocket || it->mRTCPSocket != rtcpSocket)) {
+ ++it;
+ }
+
+ if (it == mStreams.end()) {
+ TRESPASS();
+ }
+
+ mStreams.erase(it);
+}
+
+void ARTPConnection::postPollEvent() {
+ if (mPollEventPending) {
+ return;
+ }
+
+ sp<AMessage> msg = new AMessage(kWhatPollStreams, id());
+ msg->post();
+
+ mPollEventPending = true;
+}
+
+void ARTPConnection::onPollStreams() {
+ mPollEventPending = false;
+
+ if (mStreams.empty()) {
+ return;
+ }
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = kSelectTimeoutUs;
+
+ fd_set rs;
+ FD_ZERO(&rs);
+
+ int maxSocket = -1;
+ for (List<StreamInfo>::iterator it = mStreams.begin();
+ it != mStreams.end(); ++it) {
+ FD_SET(it->mRTPSocket, &rs);
+ FD_SET(it->mRTCPSocket, &rs);
+
+ if (it->mRTPSocket > maxSocket) {
+ maxSocket = it->mRTPSocket;
+ }
+ if (it->mRTCPSocket > maxSocket) {
+ maxSocket = it->mRTCPSocket;
+ }
+ }
+
+ int res = select(maxSocket + 1, &rs, NULL, NULL, &tv);
+ CHECK_GE(res, 0);
+
+ if (res > 0) {
+ for (List<StreamInfo>::iterator it = mStreams.begin();
+ it != mStreams.end(); ++it) {
+ if (FD_ISSET(it->mRTPSocket, &rs)) {
+ receive(&*it, true);
+ }
+ if (FD_ISSET(it->mRTCPSocket, &rs)) {
+ receive(&*it, false);
+ }
+ }
+ }
+
+ postPollEvent();
+}
+
+status_t ARTPConnection::receive(StreamInfo *s, bool receiveRTP) {
+ sp<ABuffer> buffer = new ABuffer(65536);
+
+ struct sockaddr_in from;
+ socklen_t fromSize = sizeof(from);
+
+ ssize_t nbytes = recvfrom(
+ receiveRTP ? s->mRTPSocket : s->mRTCPSocket,
+ buffer->data(),
+ buffer->capacity(),
+ 0,
+ (struct sockaddr *)&from,
+ &fromSize);
+
+ if (nbytes < 0) {
+ return -1;
+ }
+
+ buffer->setRange(0, nbytes);
+
+ status_t err;
+ if (receiveRTP) {
+ err = parseRTP(s, buffer);
+ } else {
+ err = parseRTCP(s, buffer);
+ }
+
+ return err;
+}
+
+status_t ARTPConnection::parseRTP(StreamInfo *s, const sp<ABuffer> &buffer) {
+ size_t size = buffer->size();
+
+ if (size < 12) {
+ // Too short to be a valid RTP header.
+ return -1;
+ }
+
+ const uint8_t *data = buffer->data();
+
+ if ((data[0] >> 6) != 2) {
+ // Unsupported version.
+ return -1;
+ }
+
+ 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 -1;
+ }
+
+ 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 -1;
+ }
+
+ 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 -1;
+ }
+
+ const uint8_t *extensionData = &data[payloadOffset];
+
+ size_t extensionLength =
+ 4 * (extensionData[2] << 8 | extensionData[3]);
+
+ if (size < payloadOffset + 4 + extensionLength) {
+ return -1;
+ }
+
+ payloadOffset += 4 + extensionLength;
+ }
+
+ uint32_t srcId = u32at(&data[8]);
+
+ sp<ARTPSource> source;
+ ssize_t index = mSources.indexOfKey(srcId);
+ if (index < 0) {
+ index = mSources.size();
+
+ source = new ARTPSource(
+ srcId, s->mSessionDesc, s->mIndex, s->mNotifyMsg);
+
+ mSources.add(srcId, source);
+ } else {
+ source = mSources.valueAt(index);
+ }
+
+ uint32_t rtpTime = u32at(&data[4]);
+
+ 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->setInt32Data(u16at(&data[2]));
+
+#if VERBOSE
+ printf("RTP = {\n"
+ " PT: %d\n"
+ " sequence number: %d\n"
+ " RTP-time: 0x%08x\n"
+ " M: %d\n"
+ " SSRC: 0x%08x\n"
+ "}\n",
+ data[1] & 0x7f,
+ u16at(&data[2]),
+ rtpTime,
+ data[1] >> 7,
+ srcId);
+
+ // hexdump(&data[payloadOffset], size - payloadOffset);
+#endif
+
+ buffer->setRange(payloadOffset, size - payloadOffset);
+
+ source->processRTPPacket(buffer);
+
+ return OK;
+}
+
+status_t ARTPConnection::parseRTCP(StreamInfo *s, const sp<ABuffer> &buffer) {
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ while (size > 0) {
+ if (size < 8) {
+ // Too short to be a valid RTCP header
+ return -1;
+ }
+
+ if ((data[0] >> 6) != 2) {
+ // Unsupported version.
+ return -1;
+ }
+
+ 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 -1;
+ }
+
+ size -= paddingLength;
+ }
+
+ size_t headerLength = 4 * (data[2] << 8 | data[3]) + 4;
+
+ if (size < headerLength) {
+ // Only received a partial packet?
+ return -1;
+ }
+
+ switch (data[1]) {
+ case 200:
+ {
+ parseSR(s, data, headerLength);
+ break;
+ }
+
+ default:
+ {
+#if VERBOSE
+ printf("Unknown RTCP packet type %d of size %ld\n",
+ data[1], headerLength);
+
+ hexdump(data, headerLength);
+#endif
+ break;
+ }
+ }
+
+ data += headerLength;
+ size -= headerLength;
+ }
+
+ return OK;
+}
+
+status_t ARTPConnection::parseSR(
+ StreamInfo *s, const uint8_t *data, size_t size) {
+ size_t RC = data[0] & 0x1f;
+
+ if (size < (7 + RC * 6) * 4) {
+ // Packet too short for the minimal SR header.
+ return -1;
+ }
+
+ uint32_t id = u32at(&data[4]);
+ uint64_t ntpTime = u64at(&data[8]);
+ uint32_t rtpTime = u32at(&data[16]);
+
+#if VERBOSE
+ printf("SR = {\n"
+ " SSRC: 0x%08x\n"
+ " NTP-time: 0x%016llx\n"
+ " RTP-time: 0x%08x\n"
+ "}\n",
+ id, ntpTime, rtpTime);
+#endif
+
+ sp<ARTPSource> source;
+ ssize_t index = mSources.indexOfKey(id);
+ if (index < 0) {
+ index = mSources.size();
+
+ source = new ARTPSource(
+ id, s->mSessionDesc, s->mIndex, s->mNotifyMsg);
+
+ mSources.add(id, source);
+ } else {
+ source = mSources.valueAt(index);
+ }
+
+ source->timeUpdate(rtpTime, ntpTime);
+
+ return 0;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/rtsp/ARTPConnection.h b/media/libstagefright/rtsp/ARTPConnection.h
new file mode 100644
index 0000000..c77e3a4
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPConnection.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_RTP_CONNECTION_H_
+
+#define A_RTP_CONNECTION_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+#include <utils/List.h>
+
+namespace android {
+
+struct ABuffer;
+struct ARTPSource;
+struct ASessionDescription;
+
+struct ARTPConnection : public AHandler {
+ ARTPConnection();
+
+ void addStream(
+ int rtpSocket, int rtcpSocket,
+ const sp<ASessionDescription> &sessionDesc, size_t index,
+ const sp<AMessage> ¬ify);
+
+ void removeStream(int rtpSocket, int rtcpSocket);
+
+ // Creates a pair of UDP datagram sockets bound to adjacent ports
+ // (the rtpSocket is bound to an even port, the rtcpSocket to the
+ // next higher port).
+ static void MakePortPair(
+ int *rtpSocket, int *rtcpSocket, unsigned *rtpPort);
+
+protected:
+ virtual ~ARTPConnection();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatAddStream,
+ kWhatRemoveStream,
+ kWhatPollStreams,
+ };
+
+ static const int64_t kSelectTimeoutUs;
+
+ struct StreamInfo;
+ List<StreamInfo> mStreams;
+
+ KeyedVector<uint32_t, sp<ARTPSource> > mSources;
+
+ bool mPollEventPending;
+
+ void onAddStream(const sp<AMessage> &msg);
+ void onRemoveStream(const sp<AMessage> &msg);
+ void onPollStreams();
+
+ status_t receive(StreamInfo *info, bool receiveRTP);
+
+ status_t parseRTP(StreamInfo *info, const sp<ABuffer> &buffer);
+ status_t parseRTCP(StreamInfo *info, const sp<ABuffer> &buffer);
+ status_t parseSR(StreamInfo *info, const uint8_t *data, size_t size);
+
+ void postPollEvent();
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTPConnection);
+};
+
+} // namespace android
+
+#endif // A_RTP_CONNECTION_H_
diff --git a/media/libstagefright/rtsp/ARTPSource.cpp b/media/libstagefright/rtsp/ARTPSource.cpp
new file mode 100644
index 0000000..f05daa8
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPSource.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2010 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 "ARTPSource.h"
+
+#include "AAVCAssembler.h"
+#include "AMPEG4AudioAssembler.h"
+#include "ASessionDescription.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#define VERBOSE 0
+
+namespace android {
+
+ARTPSource::ARTPSource(
+ uint32_t id,
+ const sp<ASessionDescription> &sessionDesc, size_t index,
+ const sp<AMessage> ¬ify)
+ : mID(id),
+ mHighestSeqNumber(0),
+ mNumBuffersReceived(0),
+ mNumTimes(0) {
+ unsigned long PT;
+ AString desc;
+ AString params;
+ sessionDesc->getFormatType(index, &PT, &desc, ¶ms);
+
+ if (!strncmp(desc.c_str(), "H264/", 5)) {
+ mAssembler = new AAVCAssembler(notify);
+ } else if (!strncmp(desc.c_str(), "MP4A-LATM", 9)) {
+ mAssembler = new AMPEG4AudioAssembler(notify);
+ } else {
+ TRESPASS();
+ }
+}
+
+static uint32_t AbsDiff(uint32_t seq1, uint32_t seq2) {
+ return seq1 > seq2 ? seq1 - seq2 : seq2 - seq1;
+}
+
+void ARTPSource::processRTPPacket(const sp<ABuffer> &buffer) {
+ if (queuePacket(buffer) && mNumTimes == 2 && mAssembler != NULL) {
+ mAssembler->onPacketReceived(this);
+ }
+
+ dump();
+}
+
+void ARTPSource::timeUpdate(uint32_t rtpTime, uint64_t ntpTime) {
+#if VERBOSE
+ LOG(VERBOSE) << "timeUpdate";
+#endif
+
+ if (mNumTimes == 2) {
+ mNTPTime[0] = mNTPTime[1];
+ mRTPTime[0] = mRTPTime[1];
+ mNumTimes = 1;
+ }
+ mNTPTime[mNumTimes] = ntpTime;
+ mRTPTime[mNumTimes++] = rtpTime;
+
+ if (mNumTimes == 2) {
+ for (List<sp<ABuffer> >::iterator it = mQueue.begin();
+ it != mQueue.end(); ++it) {
+ sp<AMessage> meta = (*it)->meta();
+
+ uint32_t rtpTime;
+ CHECK(meta->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ meta->setInt64("ntp-time", RTP2NTP(rtpTime));
+ }
+ }
+}
+
+bool ARTPSource::queuePacket(const sp<ABuffer> &buffer) {
+ uint32_t seqNum = (uint32_t)buffer->int32Data();
+
+ if (mNumTimes == 2) {
+ sp<AMessage> meta = buffer->meta();
+
+ uint32_t rtpTime;
+ CHECK(meta->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ meta->setInt64("ntp-time", RTP2NTP(rtpTime));
+ }
+
+ if (mNumBuffersReceived++ == 0) {
+ mHighestSeqNumber = seqNum;
+ mQueue.push_back(buffer);
+ return true;
+ }
+
+ // Only the lower 16-bit of the sequence numbers are transmitted,
+ // derive the high-order bits by choosing the candidate closest
+ // to the highest sequence number (extended to 32 bits) received so far.
+
+ uint32_t seq1 = seqNum | (mHighestSeqNumber & 0xffff0000);
+ uint32_t seq2 = seqNum | ((mHighestSeqNumber & 0xffff0000) + 0x10000);
+ uint32_t seq3 = seqNum | ((mHighestSeqNumber & 0xffff0000) - 0x10000);
+ uint32_t diff1 = AbsDiff(seq1, mHighestSeqNumber);
+ uint32_t diff2 = AbsDiff(seq2, mHighestSeqNumber);
+ uint32_t diff3 = AbsDiff(seq3, mHighestSeqNumber);
+
+ if (diff1 < diff2) {
+ if (diff1 < diff3) {
+ // diff1 < diff2 ^ diff1 < diff3
+ seqNum = seq1;
+ } else {
+ // diff3 <= diff1 < diff2
+ seqNum = seq3;
+ }
+ } else if (diff2 < diff3) {
+ // diff2 <= diff1 ^ diff2 < diff3
+ seqNum = seq2;
+ } else {
+ // diff3 <= diff2 <= diff1
+ seqNum = seq3;
+ }
+
+ if (seqNum > mHighestSeqNumber) {
+ mHighestSeqNumber = seqNum;
+ }
+
+ buffer->setInt32Data(seqNum);
+
+ List<sp<ABuffer> >::iterator it = mQueue.begin();
+ while (it != mQueue.end() && (uint32_t)(*it)->int32Data() < seqNum) {
+ ++it;
+ }
+
+ if (it != mQueue.end() && (uint32_t)(*it)->int32Data() == seqNum) {
+ LOG(WARNING) << "Discarding duplicate buffer";
+ return false;
+ }
+
+ mQueue.insert(it, buffer);
+
+ return true;
+}
+
+void ARTPSource::dump() const {
+ if ((mNumBuffersReceived % 128) != 0) {
+ return;
+ }
+
+#if 0
+ if (mAssembler == NULL) {
+ char tmp[20];
+ sprintf(tmp, "0x%08x", mID);
+
+ int32_t numMissing = 0;
+
+ if (!mQueue.empty()) {
+ List<sp<ABuffer> >::const_iterator it = mQueue.begin();
+ uint32_t expectedSeqNum = (uint32_t)(*it)->int32Data();
+ ++expectedSeqNum;
+ ++it;
+
+ for (; it != mQueue.end(); ++it) {
+ uint32_t seqNum = (uint32_t)(*it)->int32Data();
+ CHECK_GE(seqNum, expectedSeqNum);
+
+ if (seqNum != expectedSeqNum) {
+ numMissing += seqNum - expectedSeqNum;
+ expectedSeqNum = seqNum;
+ }
+
+ ++expectedSeqNum;
+ }
+ }
+
+ LOG(VERBOSE) << "[" << tmp << "] Missing " << numMissing
+ << " / " << (mNumBuffersReceived + numMissing) << " packets. ("
+ << (100.0 * numMissing / (mNumBuffersReceived + numMissing))
+ << " %%)";
+ }
+#endif
+
+#if 0
+ AString out;
+
+ out.append(tmp);
+ out.append(" [");
+
+ List<sp<ABuffer> >::const_iterator it = mQueue.begin();
+ while (it != mQueue.end()) {
+ uint32_t start = (uint32_t)(*it)->int32Data();
+
+ out.append(start);
+
+ ++it;
+ uint32_t expected = start + 1;
+
+ while (it != mQueue.end()) {
+ uint32_t seqNum = (uint32_t)(*it)->int32Data();
+
+ if (seqNum != expected) {
+ if (expected > start + 1) {
+ out.append("-");
+ out.append(expected - 1);
+ }
+ out.append(", ");
+ break;
+ }
+
+ ++it;
+ ++expected;
+ }
+
+ if (it == mQueue.end()) {
+ if (expected > start + 1) {
+ out.append("-");
+ out.append(expected - 1);
+ }
+ }
+ }
+
+ out.append("]");
+
+ LOG(VERBOSE) << out;
+#endif
+}
+
+uint64_t ARTPSource::RTP2NTP(uint32_t rtpTime) const {
+ CHECK_EQ(mNumTimes, 2u);
+
+ return mNTPTime[0] + (double)(mNTPTime[1] - mNTPTime[0])
+ * ((double)rtpTime - (double)mRTPTime[0])
+ / (double)(mRTPTime[1] - mRTPTime[0]);
+}
+
+} // namespace android
+
+
diff --git a/media/libstagefright/rtsp/ARTPSource.h b/media/libstagefright/rtsp/ARTPSource.h
new file mode 100644
index 0000000..b93cd56
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPSource.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_RTP_SOURCE_H_
+
+#define A_RTP_SOURCE_H_
+
+#include <stdint.h>
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/List.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+struct ARTPAssembler;
+struct ASessionDescription;
+
+struct ARTPSource : public RefBase {
+ ARTPSource(
+ uint32_t id,
+ const sp<ASessionDescription> &sessionDesc, size_t index,
+ const sp<AMessage> ¬ify);
+
+ void processRTPPacket(const sp<ABuffer> &buffer);
+ void timeUpdate(uint32_t rtpTime, uint64_t ntpTime);
+
+ List<sp<ABuffer> > *queue() { return &mQueue; }
+
+private:
+ uint32_t mID;
+ uint32_t mHighestSeqNumber;
+ int32_t mNumBuffersReceived;
+
+ List<sp<ABuffer> > mQueue;
+ sp<ARTPAssembler> mAssembler;
+
+ size_t mNumTimes;
+ uint64_t mNTPTime[2];
+ uint32_t mRTPTime[2];
+
+ uint64_t RTP2NTP(uint32_t rtpTime) const;
+
+ bool queuePacket(const sp<ABuffer> &buffer);
+ void dump() const;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTPSource);
+};
+
+} // namespace android
+
+#endif // A_RTP_SOURCE_H_
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
new file mode 100644
index 0000000..e9162c0
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -0,0 +1,549 @@
+/*
+ * Copyright (C) 2010 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 "ARTSPConnection.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+namespace android {
+
+// static
+const int64_t ARTSPConnection::kSelectTimeoutUs = 1000ll;
+
+ARTSPConnection::ARTSPConnection()
+ : mState(DISCONNECTED),
+ mSocket(-1),
+ mConnectionID(0),
+ mNextCSeq(0),
+ mReceiveResponseEventPending(false) {
+}
+
+ARTSPConnection::~ARTSPConnection() {
+ if (mSocket >= 0) {
+ LOG(ERROR) << "Connection is still open, closing the socket.";
+ close(mSocket);
+ mSocket = -1;
+ }
+}
+
+void ARTSPConnection::connect(const char *url, const sp<AMessage> &reply) {
+ sp<AMessage> msg = new AMessage(kWhatConnect, id());
+ msg->setString("url", url);
+ msg->setMessage("reply", reply);
+ msg->post();
+}
+
+void ARTSPConnection::disconnect(const sp<AMessage> &reply) {
+ sp<AMessage> msg = new AMessage(kWhatDisconnect, id());
+ msg->setMessage("reply", reply);
+ msg->post();
+}
+
+void ARTSPConnection::sendRequest(
+ const char *request, const sp<AMessage> &reply) {
+ sp<AMessage> msg = new AMessage(kWhatSendRequest, id());
+ msg->setString("request", request);
+ msg->setMessage("reply", reply);
+ msg->post();
+}
+
+void ARTSPConnection::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatConnect:
+ onConnect(msg);
+ break;
+
+ case kWhatDisconnect:
+ onDisconnect(msg);
+ break;
+
+ case kWhatCompleteConnection:
+ onCompleteConnection(msg);
+ break;
+
+ case kWhatSendRequest:
+ onSendRequest(msg);
+ break;
+
+ case kWhatReceiveResponse:
+ onReceiveResponse();
+ break;
+
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
+// static
+bool ARTSPConnection::ParseURL(
+ const char *url, AString *host, unsigned *port, AString *path) {
+ host->clear();
+ *port = 0;
+ path->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);
+ }
+
+ char *colonPos = strchr(host->c_str(), ':');
+
+ if (colonPos != NULL) {
+ unsigned long x;
+ if (!ParseSingleUnsignedLong(colonPos + 1, &x) || 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 ARTSPConnection::onConnect(const sp<AMessage> &msg) {
+ ++mConnectionID;
+
+ if (mState != DISCONNECTED) {
+ close(mSocket);
+ mSocket = -1;
+
+ flushPendingRequests();
+ }
+
+ mState = CONNECTING;
+
+ mSocket = socket(AF_INET, SOCK_STREAM, 0);
+
+ // Make socket non-blocking.
+ int flags = fcntl(mSocket, F_GETFL, 0);
+ CHECK_NE(flags, -1);
+ CHECK_NE(fcntl(mSocket, F_SETFL, flags | O_NONBLOCK), -1);
+
+ AString url;
+ CHECK(msg->findString("url", &url));
+
+ AString host, path;
+ unsigned port;
+ CHECK(ParseURL(url.c_str(), &host, &port, &path));
+
+ struct hostent *ent = gethostbyname(host.c_str());
+ CHECK(ent != NULL);
+
+ struct sockaddr_in remote;
+ memset(remote.sin_zero, 0, sizeof(remote.sin_zero));
+ remote.sin_family = AF_INET;
+ remote.sin_addr.s_addr = *(in_addr_t *)ent->h_addr;
+ remote.sin_port = htons(port);
+
+ int err = ::connect(
+ mSocket, (const struct sockaddr *)&remote, sizeof(remote));
+
+ sp<AMessage> reply;
+ CHECK(msg->findMessage("reply", &reply));
+
+ reply->setInt32("server-ip", ntohl(remote.sin_addr.s_addr));
+
+ if (err < 0) {
+ if (errno == EINPROGRESS) {
+ sp<AMessage> msg = new AMessage(kWhatCompleteConnection, id());
+ msg->setMessage("reply", reply);
+ msg->setInt32("connection-id", mConnectionID);
+ msg->post();
+ return;
+ }
+
+ reply->setInt32("result", -errno);
+ mState = DISCONNECTED;
+
+ close(mSocket);
+ mSocket = -1;
+ } else {
+ reply->setInt32("result", OK);
+ mState = CONNECTED;
+ mNextCSeq = 1;
+
+ postReceiveReponseEvent();
+ }
+
+ reply->post();
+}
+
+void ARTSPConnection::onDisconnect(const sp<AMessage> &msg) {
+ if (mState == CONNECTED || mState == CONNECTING) {
+ close(mSocket);
+ mSocket = -1;
+
+ flushPendingRequests();
+ }
+
+ sp<AMessage> reply;
+ CHECK(msg->findMessage("reply", &reply));
+
+ reply->setInt32("result", OK);
+ mState = DISCONNECTED;
+
+ reply->post();
+}
+
+void ARTSPConnection::onCompleteConnection(const sp<AMessage> &msg) {
+ sp<AMessage> reply;
+ CHECK(msg->findMessage("reply", &reply));
+
+ int32_t connectionID;
+ CHECK(msg->findInt32("connection-id", &connectionID));
+
+ if ((connectionID != mConnectionID) || mState != CONNECTING) {
+ // While we were attempting to connect, the attempt was
+ // cancelled.
+ reply->setInt32("result", -ECONNABORTED);
+ reply->post();
+ return;
+ }
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = kSelectTimeoutUs;
+
+ fd_set ws;
+ FD_ZERO(&ws);
+ FD_SET(mSocket, &ws);
+
+ int res = select(mSocket + 1, NULL, &ws, NULL, &tv);
+ CHECK_GE(res, 0);
+
+ if (res == 0) {
+ // Timed out. Not yet connected.
+
+ msg->post();
+ return;
+ }
+
+ int err;
+ socklen_t optionLen = sizeof(err);
+ CHECK_EQ(getsockopt(mSocket, SOL_SOCKET, SO_ERROR, &err, &optionLen), 0);
+ CHECK_EQ(optionLen, (socklen_t)sizeof(err));
+
+ if (err != 0) {
+ LOG(ERROR) << "err = " << err << " (" << strerror(err) << ")";
+
+ reply->setInt32("result", -err);
+
+ mState = DISCONNECTED;
+ close(mSocket);
+ mSocket = -1;
+ } else {
+ reply->setInt32("result", OK);
+ mState = CONNECTED;
+ mNextCSeq = 1;
+
+ postReceiveReponseEvent();
+ }
+
+ reply->post();
+}
+
+void ARTSPConnection::onSendRequest(const sp<AMessage> &msg) {
+ sp<AMessage> reply;
+ CHECK(msg->findMessage("reply", &reply));
+
+ if (mState != CONNECTED) {
+ reply->setInt32("result", -ENOTCONN);
+ reply->post();
+ return;
+ }
+
+ AString request;
+ CHECK(msg->findString("request", &request));
+
+ // Find the boundary between headers and the body.
+ ssize_t i = request.find("\r\n\r\n");
+ CHECK_GE(i, 0);
+
+ int32_t cseq = mNextCSeq++;
+
+ AString cseqHeader = "CSeq: ";
+ cseqHeader.append(cseq);
+ cseqHeader.append("\r\n");
+
+ request.insert(cseqHeader, i + 2);
+
+ LOG(VERBOSE) << request;
+
+ size_t numBytesSent = 0;
+ while (numBytesSent < request.size()) {
+ ssize_t n =
+ send(mSocket, request.c_str() + numBytesSent,
+ request.size() - numBytesSent, 0);
+
+ if (n == 0) {
+ // Server closed the connection.
+ TRESPASS();
+ } else if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ TRESPASS();
+ }
+
+ numBytesSent += (size_t)n;
+ }
+
+ mPendingRequests.add(cseq, reply);
+}
+
+void ARTSPConnection::onReceiveResponse() {
+ mReceiveResponseEventPending = false;
+
+ if (mState != CONNECTED) {
+ return;
+ }
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = kSelectTimeoutUs;
+
+ fd_set rs;
+ FD_ZERO(&rs);
+ FD_SET(mSocket, &rs);
+
+ int res = select(mSocket + 1, &rs, NULL, NULL, &tv);
+ CHECK_GE(res, 0);
+
+ if (res == 1) {
+ if (!receiveRTSPReponse()) {
+ // Something horrible, irreparable has happened.
+ flushPendingRequests();
+ return;
+ }
+ }
+
+ postReceiveReponseEvent();
+}
+
+void ARTSPConnection::flushPendingRequests() {
+ for (size_t i = 0; i < mPendingRequests.size(); ++i) {
+ sp<AMessage> reply = mPendingRequests.valueAt(i);
+
+ reply->setInt32("result", -ECONNABORTED);
+ reply->post();
+ }
+
+ mPendingRequests.clear();
+}
+
+void ARTSPConnection::postReceiveReponseEvent() {
+ if (mReceiveResponseEventPending) {
+ return;
+ }
+
+ sp<AMessage> msg = new AMessage(kWhatReceiveResponse, id());
+ msg->post();
+
+ mReceiveResponseEventPending = true;
+}
+
+bool ARTSPConnection::receiveLine(AString *line) {
+ line->clear();
+
+ bool sawCR = false;
+ for (;;) {
+ char c;
+ ssize_t n = recv(mSocket, &c, 1, 0);
+ if (n == 0) {
+ // Server closed the connection.
+ return false;
+ } else if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ TRESPASS();
+ }
+
+ if (sawCR && c == '\n') {
+ line->erase(line->size() - 1, 1);
+ return true;
+ }
+
+ line->append(&c, 1);
+
+ sawCR = (c == '\r');
+ }
+}
+
+bool ARTSPConnection::receiveRTSPReponse() {
+ sp<ARTSPResponse> response = new ARTSPResponse;
+
+ if (!receiveLine(&response->mStatusLine)) {
+ return false;
+ }
+
+ LOG(INFO) << "status: " << response->mStatusLine;
+
+ ssize_t space1 = response->mStatusLine.find(" ");
+ if (space1 < 0) {
+ return false;
+ }
+ ssize_t space2 = response->mStatusLine.find(" ", space1 + 1);
+ if (space2 < 0) {
+ return false;
+ }
+
+ AString statusCodeStr(
+ response->mStatusLine, space1 + 1, space2 - space1 - 1);
+
+ if (!ParseSingleUnsignedLong(
+ statusCodeStr.c_str(), &response->mStatusCode)
+ || response->mStatusCode < 100 || response->mStatusCode > 999) {
+ return false;
+ }
+
+ AString line;
+ for (;;) {
+ if (!receiveLine(&line)) {
+ break;
+ }
+
+ if (line.empty()) {
+ break;
+ }
+
+ LOG(VERBOSE) << "line: " << line;
+
+ ssize_t colonPos = line.find(":");
+ if (colonPos < 0) {
+ // Malformed header line.
+ return false;
+ }
+
+ AString key(line, 0, colonPos);
+ key.trim();
+ key.tolower();
+
+ line.erase(0, colonPos + 1);
+ line.trim();
+
+ response->mHeaders.add(key, line);
+ }
+
+ unsigned long contentLength = 0;
+
+ ssize_t i = response->mHeaders.indexOfKey("content-length");
+
+ if (i >= 0) {
+ AString value = response->mHeaders.valueAt(i);
+ if (!ParseSingleUnsignedLong(value.c_str(), &contentLength)) {
+ return false;
+ }
+ }
+
+ if (contentLength > 0) {
+ response->mContent = new ABuffer(contentLength);
+
+ size_t numBytesRead = 0;
+ while (numBytesRead < contentLength) {
+ ssize_t n = recv(
+ mSocket, response->mContent->data() + numBytesRead,
+ contentLength - numBytesRead, 0);
+
+ if (n == 0) {
+ // Server closed the connection.
+ TRESPASS();
+ } else if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ TRESPASS();
+ }
+
+ numBytesRead += (size_t)n;
+ }
+ }
+
+ return notifyResponseListener(response);
+}
+
+// static
+bool ARTSPConnection::ParseSingleUnsignedLong(
+ const char *from, unsigned long *x) {
+ char *end;
+ *x = strtoul(from, &end, 10);
+
+ if (end == from || *end != '\0') {
+ return false;
+ }
+
+ return true;
+}
+
+bool ARTSPConnection::notifyResponseListener(
+ const sp<ARTSPResponse> &response) {
+ ssize_t i = response->mHeaders.indexOfKey("cseq");
+
+ if (i < 0) {
+ return true;
+ }
+
+ AString value = response->mHeaders.valueAt(i);
+
+ unsigned long cseq;
+ if (!ParseSingleUnsignedLong(value.c_str(), &cseq)) {
+ return false;
+ }
+
+ i = mPendingRequests.indexOfKey(cseq);
+
+ if (i < 0) {
+ // Unsolicited response?
+ TRESPASS();
+ }
+
+ sp<AMessage> reply = mPendingRequests.valueAt(i);
+ mPendingRequests.removeItemsAt(i);
+
+ reply->setInt32("result", OK);
+ reply->setObject("response", response);
+ reply->post();
+
+ return true;
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/ARTSPConnection.h b/media/libstagefright/rtsp/ARTSPConnection.h
new file mode 100644
index 0000000..3577a2f
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTSPConnection.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_RTSP_CONNECTION_H_
+
+#define A_RTSP_CONNECTION_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/AString.h>
+
+namespace android {
+
+struct ABuffer;
+
+struct ARTSPResponse : public RefBase {
+ unsigned long mStatusCode;
+ AString mStatusLine;
+ KeyedVector<AString,AString> mHeaders;
+ sp<ABuffer> mContent;
+};
+
+struct ARTSPConnection : public AHandler {
+ ARTSPConnection();
+
+ void connect(const char *url, const sp<AMessage> &reply);
+ void disconnect(const sp<AMessage> &reply);
+
+ void sendRequest(const char *request, const sp<AMessage> &reply);
+
+protected:
+ virtual ~ARTSPConnection();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum State {
+ DISCONNECTED,
+ CONNECTING,
+ CONNECTED,
+ };
+
+ enum {
+ kWhatConnect = 'conn',
+ kWhatDisconnect = 'disc',
+ kWhatCompleteConnection = 'comc',
+ kWhatSendRequest = 'sreq',
+ kWhatReceiveResponse = 'rres',
+ };
+
+ static const int64_t kSelectTimeoutUs;
+
+ State mState;
+ int mSocket;
+ int32_t mConnectionID;
+ int32_t mNextCSeq;
+ bool mReceiveResponseEventPending;
+
+ KeyedVector<int32_t, sp<AMessage> > mPendingRequests;
+
+ void onConnect(const sp<AMessage> &msg);
+ void onDisconnect(const sp<AMessage> &msg);
+ void onCompleteConnection(const sp<AMessage> &msg);
+ void onSendRequest(const sp<AMessage> &msg);
+ void onReceiveResponse();
+
+ void flushPendingRequests();
+ void postReceiveReponseEvent();
+
+ // Return false iff something went unrecoverably wrong.
+ bool receiveRTSPReponse();
+ bool receiveLine(AString *line);
+ bool notifyResponseListener(const sp<ARTSPResponse> &response);
+
+ static bool ParseURL(
+ const char *url, AString *host, unsigned *port, AString *path);
+
+ static bool ParseSingleUnsignedLong(
+ const char *from, unsigned long *x);
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTSPConnection);
+};
+
+} // namespace android
+
+#endif // A_RTSP_CONNECTION_H_
diff --git a/media/libstagefright/rtsp/ARTSPController.cpp b/media/libstagefright/rtsp/ARTSPController.cpp
new file mode 100644
index 0000000..7b87d42
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTSPController.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 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 "ARTSPController.h"
+
+#include "MyHandler.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+ARTSPController::ARTSPController(const sp<ALooper> &looper)
+ : mLooper(looper) {
+}
+
+ARTSPController::~ARTSPController() {
+}
+
+status_t ARTSPController::connect(const char *url) {
+ if (mHandler != NULL) {
+ return ERROR_ALREADY_CONNECTED;
+ }
+
+ mHandler = new MyHandler(url, mLooper);
+ sleep(10);
+
+ return OK;
+}
+
+void ARTSPController::disconnect() {
+ if (mHandler == NULL) {
+ return;
+ }
+
+ mHandler.clear();
+}
+
+size_t ARTSPController::countTracks() {
+ if (mHandler == NULL) {
+ return 0;
+ }
+
+ return mHandler->countTracks();
+}
+
+sp<MediaSource> ARTSPController::getTrack(size_t index) {
+ CHECK(mHandler != NULL);
+
+ return mHandler->getPacketSource(index);
+}
+
+sp<MetaData> ARTSPController::getTrackMetaData(
+ size_t index, uint32_t flags) {
+ CHECK(mHandler != NULL);
+
+ return mHandler->getPacketSource(index)->getFormat();
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp
new file mode 100644
index 0000000..ca4c55e
--- /dev/null
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2010 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 "ASessionDescription.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AString.h>
+
+#include <stdlib.h>
+
+namespace android {
+
+ASessionDescription::ASessionDescription()
+ : mIsValid(false) {
+}
+
+ASessionDescription::~ASessionDescription() {
+}
+
+bool ASessionDescription::setTo(const void *data, size_t size) {
+ mIsValid = parse(data, size);
+
+ if (!mIsValid) {
+ mTracks.clear();
+ mFormats.clear();
+ }
+
+ return mIsValid;
+}
+
+bool ASessionDescription::parse(const void *data, size_t size) {
+ mTracks.clear();
+ mFormats.clear();
+
+ mTracks.push(Attribs());
+ mFormats.push(AString("[root]"));
+
+ AString desc((const char *)data, size);
+ LOG(VERBOSE) << desc;
+
+ size_t i = 0;
+ for (;;) {
+ ssize_t eolPos = desc.find("\r\n", i);
+ if (eolPos < 0) {
+ break;
+ }
+
+ AString line(desc, i, eolPos - i);
+
+ if (line.size() < 2 || line.c_str()[1] != '=') {
+ return false;
+ }
+
+ switch (line.c_str()[0]) {
+ case 'v':
+ {
+ if (strcmp(line.c_str(), "v=0")) {
+ return false;
+ }
+ break;
+ }
+
+ case 'a':
+ case 'b':
+ {
+ AString key, value;
+
+ ssize_t colonPos = line.find(":", 2);
+ if (colonPos < 0) {
+ key = line;
+ } else {
+ key.setTo(line, 0, colonPos);
+
+ if (key == "a=fmtp" || key == "a=rtpmap"
+ || key == "a=framesize") {
+ ssize_t spacePos = line.find(" ", colonPos + 1);
+ if (spacePos < 0) {
+ return false;
+ }
+
+ key.setTo(line, 0, spacePos);
+
+ colonPos = spacePos;
+ }
+
+ value.setTo(line, colonPos + 1, line.size() - colonPos - 1);
+ }
+
+ key.trim();
+ value.trim();
+
+ LOG(VERBOSE) << "adding '" << key << "' => '" << value << "'";
+
+ mTracks.editItemAt(mTracks.size() - 1).add(key, value);
+ break;
+ }
+
+ case 'm':
+ {
+ LOG(VERBOSE) << "new section '" << AString(line, 2, line.size() - 2) << "'";
+
+ mTracks.push(Attribs());
+ mFormats.push(AString(line, 2, line.size() - 2));
+ break;
+ }
+ }
+
+ i = eolPos + 2;
+ }
+
+ return true;
+}
+
+bool ASessionDescription::isValid() const {
+ return mIsValid;
+}
+
+size_t ASessionDescription::countTracks() const {
+ return mTracks.size();
+}
+
+void ASessionDescription::getFormat(size_t index, AString *value) const {
+ CHECK_GE(index, 0u);
+ CHECK_LT(index, mTracks.size());
+
+ *value = mFormats.itemAt(index);
+}
+
+bool ASessionDescription::findAttribute(
+ size_t index, const char *key, AString *value) const {
+ CHECK_GE(index, 0u);
+ CHECK_LT(index, mTracks.size());
+
+ value->clear();
+
+ const Attribs &track = mTracks.itemAt(index);
+ ssize_t i = track.indexOfKey(AString(key));
+
+ if (i < 0) {
+ return false;
+ }
+
+ *value = track.valueAt(i);
+
+ return true;
+}
+
+void ASessionDescription::getFormatType(
+ size_t index, unsigned long *PT,
+ AString *desc, AString *params) const {
+ AString format;
+ getFormat(index, &format);
+
+ char *lastSpacePos = strrchr(format.c_str(), ' ');
+ CHECK(lastSpacePos != NULL);
+
+ char *end;
+ unsigned long x = strtoul(lastSpacePos + 1, &end, 10);
+ CHECK_GT(end, lastSpacePos + 1);
+ CHECK_EQ(*end, '\0');
+
+ *PT = x;
+
+ char key[20];
+ sprintf(key, "a=rtpmap:%lu", x);
+
+ CHECK(findAttribute(index, key, desc));
+
+ sprintf(key, "a=fmtp:%lu", x);
+ if (!findAttribute(index, key, params)) {
+ params->clear();
+ }
+}
+
+void ASessionDescription::getDimensions(
+ size_t index, unsigned long PT,
+ int32_t *width, int32_t *height) const {
+ char key[20];
+ sprintf(key, "a=framesize:%lu", PT);
+ AString value;
+ CHECK(findAttribute(index, key, &value));
+
+ const char *s = value.c_str();
+ char *end;
+ *width = strtoul(s, &end, 10);
+ CHECK_GT(end, s);
+ CHECK_EQ(*end, '-');
+
+ s = end + 1;
+ *height = strtoul(s, &end, 10);
+ CHECK_GT(end, s);
+ CHECK_EQ(*end, '\0');
+}
+
+bool ASessionDescription::getDurationUs(int64_t *durationUs) const {
+ *durationUs = 0;
+
+ CHECK(mIsValid);
+
+ AString value;
+ if (!findAttribute(0, "a=range", &value)) {
+ return false;
+ }
+
+ if (value == "npt=now-") {
+ return false;
+ }
+
+ if (strncmp(value.c_str(), "npt=", 4)) {
+ return false;
+ }
+
+ const char *s = value.c_str() + 4;
+ char *end;
+ double from = strtod(s, &end);
+ CHECK_GT(end, s);
+ CHECK_EQ(*end, '-');
+
+ s = end + 1;
+ double to = strtod(s, &end);
+ CHECK_GT(end, s);
+ CHECK_EQ(*end, '\0');
+
+ CHECK_GE(to, from);
+
+ *durationUs = (int64_t)((to - from) * 1E6);
+
+ return true;
+}
+
+// static
+void ASessionDescription::ParseFormatDesc(
+ const char *desc, int32_t *timescale, int32_t *numChannels) {
+ const char *slash1 = strchr(desc, '/');
+ CHECK(slash1 != NULL);
+
+ const char *s = slash1 + 1;
+ char *end;
+ unsigned long x = strtoul(s, &end, 10);
+ CHECK_GT(end, s);
+ CHECK(*end == '\0' || *end == '/');
+
+ *timescale = x;
+ *numChannels = 1;
+
+ if (*end == '/') {
+ s = end + 1;
+ unsigned long x = strtoul(s, &end, 10);
+ CHECK_GT(end, s);
+ CHECK_EQ(*end, '\0');
+
+ *numChannels = x;
+ }
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/rtsp/ASessionDescription.h b/media/libstagefright/rtsp/ASessionDescription.h
new file mode 100644
index 0000000..b26980f
--- /dev/null
+++ b/media/libstagefright/rtsp/ASessionDescription.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_SESSION_DESCRIPTION_H_
+
+#define A_SESSION_DESCRIPTION_H_
+
+#include <sys/types.h>
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct AString;
+
+struct ASessionDescription : public RefBase {
+ ASessionDescription();
+
+ bool setTo(const void *data, size_t size);
+ bool isValid() const;
+
+ // Actually, 1 + number of tracks, as index 0 is reserved for the
+ // session description root-level attributes.
+ size_t countTracks() const;
+ void getFormat(size_t index, AString *value) const;
+
+ void getFormatType(
+ size_t index, unsigned long *PT,
+ AString *desc, AString *params) const;
+
+ void getDimensions(
+ size_t index, unsigned long PT,
+ int32_t *width, int32_t *height) const;
+
+ bool getDurationUs(int64_t *durationUs) const;
+
+ static void ParseFormatDesc(
+ const char *desc, int32_t *timescale, int32_t *numChannels);
+
+ bool findAttribute(size_t index, const char *key, AString *value) const;
+
+protected:
+ virtual ~ASessionDescription();
+
+private:
+ typedef KeyedVector<AString,AString> Attribs;
+
+ bool mIsValid;
+ Vector<Attribs> mTracks;
+ Vector<AString> mFormats;
+
+ bool parse(const void *data, size_t size);
+
+ DISALLOW_EVIL_CONSTRUCTORS(ASessionDescription);
+};
+
+} // namespace android
+
+#endif // A_SESSION_DESCRIPTION_H_
diff --git a/media/libstagefright/rtsp/Android.mk b/media/libstagefright/rtsp/Android.mk
new file mode 100644
index 0000000..4608fa0
--- /dev/null
+++ b/media/libstagefright/rtsp/Android.mk
@@ -0,0 +1,28 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ ARTSPController.cpp \
+ AAVCAssembler.cpp \
+ AMPEG4AudioAssembler.cpp \
+ APacketSource.cpp \
+ ARTPAssembler.cpp \
+ ARTPConnection.cpp \
+ ARTPSource.cpp \
+ ARTSPConnection.cpp \
+ ASessionDescription.cpp \
+
+LOCAL_C_INCLUDES:= \
+ $(JNI_H_INCLUDE) \
+ $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
+ $(TOP)/frameworks/base/media/libstagefright/include \
+
+LOCAL_MODULE:= libstagefright_rtsp
+
+ifeq ($(TARGET_ARCH),arm)
+ LOCAL_CFLAGS += -Wno-psabi
+endif
+
+include $(BUILD_STATIC_LIBRARY)
+
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
new file mode 100644
index 0000000..74bb798
--- /dev/null
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2010 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 MY_HANDLER_H_
+
+#define MY_HANDLER_H_
+
+#include "APacketSource.h"
+#include "ARTPConnection.h"
+#include "ARTSPConnection.h"
+#include "ASessionDescription.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+struct MyHandler : public AHandler {
+ MyHandler(const char *url, const sp<ALooper> &looper)
+ : mLooper(looper),
+ mConn(new ARTSPConnection),
+ mRTPConn(new ARTPConnection),
+ mSessionURL(url),
+ mSetupTracksSuccessful(false),
+ mFirstAccessUnit(true),
+ mFirstAccessUnitNTP(-1) {
+ mLooper->registerHandler(this);
+ mLooper->registerHandler(mConn);
+ mLooper->registerHandler(mRTPConn);
+ sp<AMessage> reply = new AMessage('conn', id());
+ mConn->connect(mSessionURL.c_str(), reply);
+ }
+
+ virtual void onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case 'conn':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "connection request completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ if (result == OK) {
+ AString request;
+ request = "DESCRIBE ";
+ request.append(mSessionURL);
+ request.append(" RTSP/1.0\r\n");
+ request.append("Accept: application/sdp\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('desc', id());
+ mConn->sendRequest(request.c_str(), reply);
+ }
+ break;
+ }
+
+ case 'disc':
+ {
+ LOG(INFO) << "disconnect completed";
+
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ case 'desc':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "DESCRIBE completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ if (result == OK) {
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response =
+ static_cast<ARTSPResponse *>(obj.get());
+
+ if (response->mStatusCode == 302) {
+ ssize_t i = response->mHeaders.indexOfKey("location");
+ CHECK_GE(i, 0);
+
+ mSessionURL = response->mHeaders.valueAt(i);
+
+ AString request;
+ request = "DESCRIBE ";
+ request.append(mSessionURL);
+ request.append(" RTSP/1.0\r\n");
+ request.append("Accept: application/sdp\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('desc', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ CHECK_EQ(response->mStatusCode, 200u);
+
+ mSessionDesc = new ASessionDescription;
+
+ mSessionDesc->setTo(
+ response->mContent->data(),
+ response->mContent->size());
+
+ CHECK(mSessionDesc->isValid());
+
+ ssize_t i = response->mHeaders.indexOfKey("content-base");
+ if (i >= 0) {
+ mBaseURL = response->mHeaders.valueAt(i);
+ } else {
+ i = response->mHeaders.indexOfKey("content-location");
+ if (i >= 0) {
+ mBaseURL = response->mHeaders.valueAt(i);
+ } else {
+ mBaseURL = mSessionURL;
+ }
+ }
+
+ CHECK_GT(mSessionDesc->countTracks(), 1u);
+ setupTrack(1);
+ } else {
+ sp<AMessage> reply = new AMessage('disc', id());
+ mConn->disconnect(reply);
+ }
+ break;
+ }
+
+ case 'setu':
+ {
+ size_t index;
+ CHECK(msg->findSize("index", &index));
+
+ size_t trackIndex;
+ CHECK(msg->findSize("track-index", &trackIndex));
+
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "SETUP(" << index << ") completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ TrackInfo *track = &mTracks.editItemAt(trackIndex);
+
+ if (result == OK) {
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response =
+ static_cast<ARTSPResponse *>(obj.get());
+
+ CHECK_EQ(response->mStatusCode, 200u);
+
+ ssize_t i = response->mHeaders.indexOfKey("session");
+ CHECK_GE(i, 0);
+
+ if (index == 1) {
+ mSessionID = response->mHeaders.valueAt(i);
+ i = mSessionID.find(";");
+ if (i >= 0) {
+ // Remove options, i.e. ";timeout=90"
+ mSessionID.erase(i, mSessionID.size() - i);
+ }
+ }
+
+ sp<AMessage> notify = new AMessage('accu', id());
+ notify->setSize("track-index", trackIndex);
+
+ mRTPConn->addStream(
+ track->mRTPSocket, track->mRTCPSocket,
+ mSessionDesc, index,
+ notify);
+
+ track->mPacketSource =
+ new APacketSource(mSessionDesc, index);
+
+ mSetupTracksSuccessful = true;
+
+ ++index;
+ if (index < mSessionDesc->countTracks()) {
+ setupTrack(index);
+ break;
+ }
+ } else {
+ close(track->mRTPSocket);
+ close(track->mRTCPSocket);
+
+ mTracks.removeItemsAt(mTracks.size() - 1);
+ }
+
+ if (mSetupTracksSuccessful) {
+ AString request = "PLAY ";
+ request.append(mSessionURL);
+ request.append(" RTSP/1.0\r\n");
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('play', id());
+ mConn->sendRequest(request.c_str(), reply);
+ } else {
+ sp<AMessage> reply = new AMessage('disc', id());
+ mConn->disconnect(reply);
+ }
+ break;
+ }
+
+ case 'play':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "PLAY completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ if (result == OK) {
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response =
+ static_cast<ARTSPResponse *>(obj.get());
+
+ CHECK_EQ(response->mStatusCode, 200u);
+
+ sp<AMessage> msg = new AMessage('abor', id());
+ msg->post(60000000ll);
+ } else {
+ sp<AMessage> reply = new AMessage('disc', id());
+ mConn->disconnect(reply);
+ }
+
+ break;
+ }
+
+ case 'abor':
+ {
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ mTracks.editItemAt(i).mPacketSource->signalEOS(
+ ERROR_END_OF_STREAM);
+ }
+
+ sp<AMessage> reply = new AMessage('tear', id());
+
+ AString request;
+ request = "TEARDOWN ";
+
+ // XXX should use aggregate url from SDP here...
+ request.append(mSessionURL);
+ request.append(" RTSP/1.0\r\n");
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+
+ request.append("\r\n");
+
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ case 'tear':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "TEARDOWN completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<AMessage> reply = new AMessage('disc', id());
+ mConn->disconnect(reply);
+ break;
+ }
+
+ case 'quit':
+ {
+ mLooper->stop();
+ break;
+ }
+
+ case 'accu':
+ {
+ size_t trackIndex;
+ CHECK(msg->findSize("track-index", &trackIndex));
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("access-unit", &obj));
+
+ sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get());
+
+ uint64_t ntpTime;
+ CHECK(accessUnit->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+ if (mFirstAccessUnit) {
+ mFirstAccessUnit = false;
+ mFirstAccessUnitNTP = ntpTime;
+ }
+ if (ntpTime > mFirstAccessUnitNTP) {
+ ntpTime -= mFirstAccessUnitNTP;
+ } else {
+ ntpTime = 0;
+ }
+
+ accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+ TrackInfo *track = &mTracks.editItemAt(trackIndex);
+ track->mPacketSource->queueAccessUnit(accessUnit);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ break;
+ }
+ }
+
+ sp<APacketSource> getPacketSource(size_t index) {
+ CHECK_GE(index, 0u);
+ CHECK_LT(index, mTracks.size());
+
+ return mTracks.editItemAt(index).mPacketSource;
+ }
+
+ size_t countTracks() const {
+ return mTracks.size();
+ }
+
+private:
+ sp<ALooper> mLooper;
+ sp<ARTSPConnection> mConn;
+ sp<ARTPConnection> mRTPConn;
+ sp<ASessionDescription> mSessionDesc;
+ AString mSessionURL;
+ AString mBaseURL;
+ AString mSessionID;
+ bool mSetupTracksSuccessful;
+ bool mFirstAccessUnit;
+ uint64_t mFirstAccessUnitNTP;
+
+ struct TrackInfo {
+ int mRTPSocket;
+ int mRTCPSocket;
+
+ sp<APacketSource> mPacketSource;
+ };
+ Vector<TrackInfo> mTracks;
+
+ void setupTrack(size_t index) {
+ AString url;
+ CHECK(mSessionDesc->findAttribute(index, "a=control", &url));
+
+ AString trackURL;
+ CHECK(MakeURL(mBaseURL.c_str(), url.c_str(), &trackURL));
+
+ mTracks.push(TrackInfo());
+ TrackInfo *info = &mTracks.editItemAt(mTracks.size() - 1);
+
+ unsigned rtpPort;
+ ARTPConnection::MakePortPair(
+ &info->mRTPSocket, &info->mRTCPSocket, &rtpPort);
+
+ AString request = "SETUP ";
+ request.append(trackURL);
+ request.append(" RTSP/1.0\r\n");
+
+ request.append("Transport: RTP/AVP/UDP;unicast;client_port=");
+ request.append(rtpPort);
+ request.append("-");
+ request.append(rtpPort + 1);
+ request.append("\r\n");
+
+ if (index > 1) {
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+ }
+
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('setu', id());
+ reply->setSize("index", index);
+ reply->setSize("track-index", mTracks.size() - 1);
+ mConn->sendRequest(request.c_str(), reply);
+ }
+
+ static bool MakeURL(const char *baseURL, const char *url, AString *out) {
+ out->clear();
+
+ if (strncasecmp("rtsp://", baseURL, 7)) {
+ // Base URL must be absolute
+ return false;
+ }
+
+ if (!strncasecmp("rtsp://", url, 7)) {
+ // "url" is already an absolute URL, ignore base URL.
+ out->setTo(url);
+ return true;
+ }
+
+ size_t n = strlen(baseURL);
+ if (baseURL[n - 1] == '/') {
+ out->setTo(baseURL);
+ out->append(url);
+ } else {
+ char *slashPos = strrchr(baseURL, '/');
+
+ if (slashPos > &baseURL[6]) {
+ out->setTo(baseURL, slashPos - baseURL);
+ } else {
+ out->setTo(baseURL);
+ }
+
+ out->append("/");
+ out->append(url);
+ }
+
+ return true;
+ }
+
+ DISALLOW_EVIL_CONSTRUCTORS(MyHandler);
+};
+
+} // namespace android
+
+#endif // MY_HANDLER_H_
diff --git a/media/libstagefright/rtsp/MyTransmitter.h b/media/libstagefright/rtsp/MyTransmitter.h
new file mode 100644
index 0000000..009a3b1
--- /dev/null
+++ b/media/libstagefright/rtsp/MyTransmitter.h
@@ -0,0 +1,981 @@
+/*
+ * Copyright (C) 2010 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 MY_TRANSMITTER_H_
+
+#define MY_TRANSMITTER_H_
+
+#include "ARTPConnection.h"
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <openssl/md5.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/base64.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+#ifdef ANDROID
+#include "VideoSource.h"
+
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#endif
+
+namespace android {
+
+#define TRACK_SUFFIX "trackid=1"
+#define PT 96
+#define PT_STR "96"
+
+#define USERNAME "bcast"
+#define PASSWORD "test"
+
+static int uniformRand(int limit) {
+ return ((double)rand() * limit) / RAND_MAX;
+}
+
+static bool GetAttribute(const char *s, const char *key, AString *value) {
+ value->clear();
+
+ size_t keyLen = strlen(key);
+
+ for (;;) {
+ const char *colonPos = strchr(s, ';');
+
+ size_t len =
+ (colonPos == NULL) ? strlen(s) : colonPos - s;
+
+ if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) {
+ value->setTo(&s[keyLen + 1], len - keyLen - 1);
+ return true;
+ }
+
+ if (colonPos == NULL) {
+ return false;
+ }
+
+ s = colonPos + 1;
+ }
+}
+
+struct MyTransmitter : public AHandler {
+ MyTransmitter(const char *url, const sp<ALooper> &looper)
+ : mServerURL(url),
+ mLooper(looper),
+ mConn(new ARTSPConnection),
+ mConnected(false),
+ mAuthType(NONE),
+ mRTPSocket(-1),
+ mRTCPSocket(-1),
+ mSourceID(rand()),
+ mSeqNo(uniformRand(65536)),
+ mRTPTimeBase(rand()),
+ mNumSamplesSent(0),
+ mNumRTPSent(0),
+ mNumRTPOctetsSent(0),
+ mLastRTPTime(0),
+ mLastNTPTime(0) {
+ mStreamURL = mServerURL;
+ mStreamURL.append("/bazong.sdp");
+
+ mTrackURL = mStreamURL;
+ mTrackURL.append("/");
+ mTrackURL.append(TRACK_SUFFIX);
+
+ mLooper->registerHandler(this);
+ mLooper->registerHandler(mConn);
+
+ sp<AMessage> reply = new AMessage('conn', id());
+ mConn->connect(mServerURL.c_str(), reply);
+
+#ifdef ANDROID
+ int width = 640;
+ int height = 480;
+
+ sp<MediaSource> source = new VideoSource(width, height);
+
+ sp<MetaData> encMeta = new MetaData;
+ encMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+ encMeta->setInt32(kKeyWidth, width);
+ encMeta->setInt32(kKeyHeight, height);
+
+ OMXClient client;
+ client.connect();
+
+ mEncoder = OMXCodec::Create(
+ client.interface(), encMeta,
+ true /* createEncoder */, source);
+
+ mEncoder->start();
+
+ MediaBuffer *buffer;
+ CHECK_EQ(mEncoder->read(&buffer), (status_t)OK);
+ CHECK(buffer != NULL);
+
+ makeH264SPropParamSets(buffer);
+
+ buffer->release();
+ buffer = NULL;
+#endif
+ }
+
+ uint64_t ntpTime() {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+
+ uint64_t nowUs = tv.tv_sec * 1000000ll + tv.tv_usec;
+
+ nowUs += ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll;
+
+ uint64_t hi = nowUs / 1000000ll;
+ uint64_t lo = ((1ll << 32) * (nowUs % 1000000ll)) / 1000000ll;
+
+ return (hi << 32) | lo;
+ }
+
+ void issueAnnounce() {
+ AString sdp;
+ sdp = "v=0\r\n";
+
+ sdp.append("o=- ");
+
+ uint64_t ntp = ntpTime();
+ sdp.append(ntp);
+ sdp.append(" ");
+ sdp.append(ntp);
+ sdp.append(" IN IP4 127.0.0.0\r\n");
+
+ sdp.append(
+ "s=Sample\r\n"
+ "i=Playing around with ANNOUNCE\r\n"
+ "c=IN IP4 ");
+
+ struct in_addr addr;
+ addr.s_addr = htonl(mServerIP);
+
+ sdp.append(inet_ntoa(addr));
+
+ sdp.append(
+ "\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=now-\r\n");
+
+#ifdef ANDROID
+ sp<MetaData> meta = mEncoder->getFormat();
+ int32_t width, height;
+ CHECK(meta->findInt32(kKeyWidth, &width));
+ CHECK(meta->findInt32(kKeyHeight, &height));
+
+ sdp.append(
+ "m=video 0 RTP/AVP " PT_STR "\r\n"
+ "b=AS 320000\r\n"
+ "a=rtpmap:" PT_STR " H264/90000\r\n");
+
+ sdp.append("a=cliprect 0,0,");
+ sdp.append(height);
+ sdp.append(",");
+ sdp.append(width);
+ sdp.append("\r\n");
+
+ sdp.append(
+ "a=framesize:" PT_STR " ");
+ sdp.append(width);
+ sdp.append("-");
+ sdp.append(height);
+ sdp.append("\r\n");
+
+ sdp.append(
+ "a=fmtp:" PT_STR " profile-level-id=42C015;sprop-parameter-sets=");
+
+ sdp.append(mSeqParamSet);
+ sdp.append(",");
+ sdp.append(mPicParamSet);
+ sdp.append(";packetization-mode=1\r\n");
+#else
+ sdp.append(
+ "m=audio 0 RTP/AVP " PT_STR "\r\n"
+ "a=rtpmap:" PT_STR " L8/8000/1\r\n");
+#endif
+
+ sdp.append("a=control:" TRACK_SUFFIX "\r\n");
+
+ AString request;
+ request.append("ANNOUNCE ");
+ request.append(mStreamURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "ANNOUNCE", mStreamURL.c_str());
+
+ request.append("Content-Type: application/sdp\r\n");
+ request.append("Content-Length: ");
+ request.append(sdp.size());
+ request.append("\r\n");
+
+ request.append("\r\n");
+ request.append(sdp);
+
+ sp<AMessage> reply = new AMessage('anno', id());
+ mConn->sendRequest(request.c_str(), reply);
+ }
+
+ void H(const AString &s, AString *out) {
+ out->clear();
+
+ MD5_CTX m;
+ MD5_Init(&m);
+ MD5_Update(&m, s.c_str(), s.size());
+
+ uint8_t key[16];
+ MD5_Final(key, &m);
+
+ for (size_t i = 0; i < 16; ++i) {
+ char nibble = key[i] >> 4;
+ if (nibble <= 9) {
+ nibble += '0';
+ } else {
+ nibble += 'a' - 10;
+ }
+ out->append(&nibble, 1);
+
+ nibble = key[i] & 0x0f;
+ if (nibble <= 9) {
+ nibble += '0';
+ } else {
+ nibble += 'a' - 10;
+ }
+ out->append(&nibble, 1);
+ }
+ }
+
+ void authenticate(const sp<ARTSPResponse> &response) {
+ ssize_t i = response->mHeaders.indexOfKey("www-authenticate");
+ CHECK_GE(i, 0);
+
+ AString value = response->mHeaders.valueAt(i);
+
+ if (!strncmp(value.c_str(), "Basic", 5)) {
+ mAuthType = BASIC;
+ } else {
+ CHECK(!strncmp(value.c_str(), "Digest", 6));
+ mAuthType = DIGEST;
+
+ i = value.find("nonce=");
+ CHECK_GE(i, 0);
+ CHECK_EQ(value.c_str()[i + 6], '\"');
+ ssize_t j = value.find("\"", i + 7);
+ CHECK_GE(j, 0);
+
+ mNonce.setTo(value, i + 7, j - i - 7);
+ }
+
+ issueAnnounce();
+ }
+
+ void addAuthentication(
+ AString *request, const char *method, const char *url) {
+ if (mAuthType == NONE) {
+ return;
+ }
+
+ if (mAuthType == BASIC) {
+ request->append("Authorization: Basic YmNhc3Q6dGVzdAo=\r\n");
+ return;
+ }
+
+ CHECK_EQ((int)mAuthType, (int)DIGEST);
+
+ AString A1;
+ A1.append(USERNAME);
+ A1.append(":");
+ A1.append("Streaming Server");
+ A1.append(":");
+ A1.append(PASSWORD);
+
+ AString A2;
+ A2.append(method);
+ A2.append(":");
+ A2.append(url);
+
+ AString HA1, HA2;
+ H(A1, &HA1);
+ H(A2, &HA2);
+
+ AString tmp;
+ tmp.append(HA1);
+ tmp.append(":");
+ tmp.append(mNonce);
+ tmp.append(":");
+ tmp.append(HA2);
+
+ AString digest;
+ H(tmp, &digest);
+
+ request->append("Authorization: Digest ");
+ request->append("nonce=\"");
+ request->append(mNonce);
+ request->append("\", ");
+ request->append("username=\"" USERNAME "\", ");
+ request->append("uri=\"");
+ request->append(url);
+ request->append("\", ");
+ request->append("response=\"");
+ request->append(digest);
+ request->append("\"");
+ request->append("\r\n");
+ }
+
+ virtual void onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case 'conn':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "connection request completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ if (result != OK) {
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ mConnected = true;
+
+ CHECK(msg->findInt32("server-ip", (int32_t *)&mServerIP));
+
+ issueAnnounce();
+ break;
+ }
+
+ case 'anno':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "ANNOUNCE completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response;
+
+ if (result == OK) {
+ response = static_cast<ARTSPResponse *>(obj.get());
+ CHECK(response != NULL);
+
+ if (response->mStatusCode == 401) {
+ if (mAuthType != NONE) {
+ LOG(INFO) << "FAILED to authenticate";
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ authenticate(response);
+ break;
+ }
+ }
+
+ if (result != OK || response->mStatusCode != 200) {
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ unsigned rtpPort;
+ ARTPConnection::MakePortPair(&mRTPSocket, &mRTCPSocket, &rtpPort);
+
+ // (new AMessage('poll', id()))->post();
+
+ AString request;
+ request.append("SETUP ");
+ request.append(mTrackURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "SETUP", mTrackURL.c_str());
+
+ request.append("Transport: RTP/AVP;unicast;client_port=");
+ request.append(rtpPort);
+ request.append("-");
+ request.append(rtpPort + 1);
+ request.append(";mode=record\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('setu', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+#if 0
+ case 'poll':
+ {
+ fd_set rs;
+ FD_ZERO(&rs);
+ FD_SET(mRTCPSocket, &rs);
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ int res = select(mRTCPSocket + 1, &rs, NULL, NULL, &tv);
+
+ if (res == 1) {
+ sp<ABuffer> buffer = new ABuffer(65536);
+ ssize_t n = recv(mRTCPSocket, buffer->data(), buffer->size(), 0);
+
+ if (n <= 0) {
+ LOG(ERROR) << "recv returned " << n;
+ } else {
+ LOG(INFO) << "recv returned " << n << " bytes of data.";
+
+ hexdump(buffer->data(), n);
+ }
+ }
+
+ msg->post(50000);
+ break;
+ }
+#endif
+
+ case 'setu':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "SETUP completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response;
+
+ if (result == OK) {
+ response = static_cast<ARTSPResponse *>(obj.get());
+ CHECK(response != NULL);
+ }
+
+ if (result != OK || response->mStatusCode != 200) {
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ ssize_t i = response->mHeaders.indexOfKey("session");
+ CHECK_GE(i, 0);
+ mSessionID = response->mHeaders.valueAt(i);
+ i = mSessionID.find(";");
+ if (i >= 0) {
+ // Remove options, i.e. ";timeout=90"
+ mSessionID.erase(i, mSessionID.size() - i);
+ }
+
+ i = response->mHeaders.indexOfKey("transport");
+ CHECK_GE(i, 0);
+ AString transport = response->mHeaders.valueAt(i);
+
+ LOG(INFO) << "transport = '" << transport << "'";
+
+ AString value;
+ CHECK(GetAttribute(transport.c_str(), "server_port", &value));
+
+ unsigned rtpPort, rtcpPort;
+ CHECK_EQ(sscanf(value.c_str(), "%u-%u", &rtpPort, &rtcpPort), 2);
+
+ CHECK(GetAttribute(transport.c_str(), "source", &value));
+
+ memset(mRemoteAddr.sin_zero, 0, sizeof(mRemoteAddr.sin_zero));
+ mRemoteAddr.sin_family = AF_INET;
+ mRemoteAddr.sin_addr.s_addr = inet_addr(value.c_str());
+ mRemoteAddr.sin_port = htons(rtpPort);
+
+ mRemoteRTCPAddr = mRemoteAddr;
+ mRemoteRTCPAddr.sin_port = htons(rtpPort + 1);
+
+ CHECK_EQ(0, connect(mRTPSocket,
+ (const struct sockaddr *)&mRemoteAddr,
+ sizeof(mRemoteAddr)));
+
+ CHECK_EQ(0, connect(mRTCPSocket,
+ (const struct sockaddr *)&mRemoteRTCPAddr,
+ sizeof(mRemoteRTCPAddr)));
+
+ uint32_t x = ntohl(mRemoteAddr.sin_addr.s_addr);
+ LOG(INFO) << "sending data to "
+ << (x >> 24)
+ << "."
+ << ((x >> 16) & 0xff)
+ << "."
+ << ((x >> 8) & 0xff)
+ << "."
+ << (x & 0xff)
+ << ":"
+ << rtpPort;
+
+ AString request;
+ request.append("RECORD ");
+ request.append(mStreamURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "RECORD", mStreamURL.c_str());
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('reco', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ case 'reco':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "RECORD completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response;
+
+ if (result == OK) {
+ response = static_cast<ARTSPResponse *>(obj.get());
+ CHECK(response != NULL);
+ }
+
+ if (result != OK) {
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ (new AMessage('more', id()))->post();
+ (new AMessage('sr ', id()))->post();
+ (new AMessage('aliv', id()))->post(30000000ll);
+ break;
+ }
+
+ case 'aliv':
+ {
+ if (!mConnected) {
+ break;
+ }
+
+ AString request;
+ request.append("OPTIONS ");
+ request.append(mStreamURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "RECORD", mStreamURL.c_str());
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('opts', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ case 'opts':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "OPTIONS completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ if (!mConnected) {
+ break;
+ }
+
+ (new AMessage('aliv', id()))->post(30000000ll);
+ break;
+ }
+
+ case 'more':
+ {
+ if (!mConnected) {
+ break;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(65536);
+ uint8_t *data = buffer->data();
+ data[0] = 0x80;
+ data[1] = (1 << 7) | PT; // M-bit
+ data[2] = (mSeqNo >> 8) & 0xff;
+ data[3] = mSeqNo & 0xff;
+ data[8] = mSourceID >> 24;
+ data[9] = (mSourceID >> 16) & 0xff;
+ data[10] = (mSourceID >> 8) & 0xff;
+ data[11] = mSourceID & 0xff;
+
+#ifdef ANDROID
+ MediaBuffer *mediaBuf = NULL;
+ for (;;) {
+ CHECK_EQ(mEncoder->read(&mediaBuf), (status_t)OK);
+ if (mediaBuf->range_length() > 0) {
+ break;
+ }
+ mediaBuf->release();
+ mediaBuf = NULL;
+ }
+
+ int64_t timeUs;
+ CHECK(mediaBuf->meta_data()->findInt64(kKeyTime, &timeUs));
+
+ uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100ll);
+
+ const uint8_t *mediaData =
+ (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
+
+ CHECK(!memcmp("\x00\x00\x00\x01", mediaData, 4));
+
+ CHECK_LE(mediaBuf->range_length() - 4 + 12, buffer->size());
+
+ memcpy(&data[12],
+ mediaData + 4, mediaBuf->range_length() - 4);
+
+ buffer->setRange(0, mediaBuf->range_length() - 4 + 12);
+
+ mediaBuf->release();
+ mediaBuf = NULL;
+#else
+ uint32_t rtpTime = mRTPTimeBase + mNumRTPSent * 128;
+ memset(&data[12], 0, 128);
+ buffer->setRange(0, 12 + 128);
+#endif
+
+ data[4] = rtpTime >> 24;
+ data[5] = (rtpTime >> 16) & 0xff;
+ data[6] = (rtpTime >> 8) & 0xff;
+ data[7] = rtpTime & 0xff;
+
+ ssize_t n = send(
+ mRTPSocket, data, buffer->size(), 0);
+ if (n < 0) {
+ LOG(ERROR) << "send failed (" << strerror(errno) << ")";
+ }
+ CHECK_EQ(n, (ssize_t)buffer->size());
+
+ ++mSeqNo;
+
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += buffer->size() - 12;
+
+ mLastRTPTime = rtpTime;
+ mLastNTPTime = ntpTime();
+
+#ifdef ANDROID
+ if (mNumRTPSent < 60 * 25) { // 60 secs worth
+ msg->post(40000);
+#else
+ if (mNumRTPOctetsSent < 8000 * 60) {
+ msg->post(1000000ll * 128 / 8000);
+#endif
+ } else {
+ LOG(INFO) << "That's enough, pausing.";
+
+ AString request;
+ request.append("PAUSE ");
+ request.append(mStreamURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "PAUSE", mStreamURL.c_str());
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('paus', id());
+ mConn->sendRequest(request.c_str(), reply);
+ }
+ break;
+ }
+
+ case 'sr ':
+ {
+ if (!mConnected) {
+ break;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(65536);
+ buffer->setRange(0, 0);
+
+ addSR(buffer);
+ addSDES(buffer);
+
+ uint8_t *data = buffer->data();
+ ssize_t n = send(
+ mRTCPSocket, data, buffer->size(), 0);
+ CHECK_EQ(n, (ssize_t)buffer->size());
+
+ msg->post(3000000);
+ break;
+ }
+
+ case 'paus':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "PAUSE completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response;
+
+ AString request;
+ request.append("TEARDOWN ");
+ request.append(mStreamURL);
+ request.append(" RTSP/1.0\r\n");
+
+ addAuthentication(&request, "TEARDOWN", mStreamURL.c_str());
+
+ request.append("Session: ");
+ request.append(mSessionID);
+ request.append("\r\n");
+ request.append("\r\n");
+
+ sp<AMessage> reply = new AMessage('tear', id());
+ mConn->sendRequest(request.c_str(), reply);
+ break;
+ }
+
+ case 'tear':
+ {
+ int32_t result;
+ CHECK(msg->findInt32("result", &result));
+
+ LOG(INFO) << "TEARDOWN completed with result "
+ << result << " (" << strerror(-result) << ")";
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("response", &obj));
+ sp<ARTSPResponse> response;
+
+ if (result == OK) {
+ response = static_cast<ARTSPResponse *>(obj.get());
+ CHECK(response != NULL);
+ }
+
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ case 'disc':
+ {
+ LOG(INFO) << "disconnect completed";
+
+ mConnected = false;
+ (new AMessage('quit', id()))->post();
+ break;
+ }
+
+ case 'quit':
+ {
+ if (mConnected) {
+ mConn->disconnect(new AMessage('disc', id()));
+ break;
+ }
+
+ if (mRTPSocket >= 0) {
+ close(mRTPSocket);
+ mRTPSocket = -1;
+ }
+
+ if (mRTCPSocket >= 0) {
+ close(mRTCPSocket);
+ mRTCPSocket = -1;
+ }
+
+#ifdef ANDROID
+ mEncoder->stop();
+ mEncoder.clear();
+#endif
+
+ mLooper->stop();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ }
+
+protected:
+ virtual ~MyTransmitter() {
+ }
+
+private:
+ enum AuthType {
+ NONE,
+ BASIC,
+ DIGEST
+ };
+
+ AString mServerURL;
+ AString mTrackURL;
+ AString mStreamURL;
+
+ sp<ALooper> mLooper;
+ sp<ARTSPConnection> mConn;
+ bool mConnected;
+ uint32_t mServerIP;
+ AuthType mAuthType;
+ AString mNonce;
+ AString mSessionID;
+ int mRTPSocket, mRTCPSocket;
+ uint32_t mSourceID;
+ uint32_t mSeqNo;
+ uint32_t mRTPTimeBase;
+ struct sockaddr_in mRemoteAddr;
+ struct sockaddr_in mRemoteRTCPAddr;
+ size_t mNumSamplesSent;
+ uint32_t mNumRTPSent;
+ uint32_t mNumRTPOctetsSent;
+ uint32_t mLastRTPTime;
+ uint64_t mLastNTPTime;
+
+#ifdef ANDROID
+ sp<MediaSource> mEncoder;
+ AString mSeqParamSet;
+ AString mPicParamSet;
+
+ void makeH264SPropParamSets(MediaBuffer *buffer) {
+ static const char kStartCode[] = "\x00\x00\x00\x01";
+
+ const uint8_t *data =
+ (const uint8_t *)buffer->data() + buffer->range_offset();
+ size_t size = buffer->range_length();
+
+ CHECK_GE(size, 0u);
+ CHECK(!memcmp(kStartCode, data, 4));
+
+ data += 4;
+ size -= 4;
+
+ size_t startCodePos = 0;
+ while (startCodePos + 3 < size
+ && memcmp(kStartCode, &data[startCodePos], 4)) {
+ ++startCodePos;
+ }
+
+ CHECK_LT(startCodePos + 3, size);
+
+ encodeBase64(data, startCodePos, &mSeqParamSet);
+
+ encodeBase64(&data[startCodePos + 4], size - startCodePos - 4,
+ &mPicParamSet);
+ }
+#endif
+
+ void addSR(const sp<ABuffer> &buffer) {
+ uint8_t *data = buffer->data() + buffer->size();
+
+ data[0] = 0x80 | 0;
+ data[1] = 200; // SR
+ data[2] = 0;
+ data[3] = 6;
+ data[4] = mSourceID >> 24;
+ data[5] = (mSourceID >> 16) & 0xff;
+ data[6] = (mSourceID >> 8) & 0xff;
+ data[7] = mSourceID & 0xff;
+
+ data[8] = mLastNTPTime >> (64 - 8);
+ data[9] = (mLastNTPTime >> (64 - 16)) & 0xff;
+ data[10] = (mLastNTPTime >> (64 - 24)) & 0xff;
+ data[11] = (mLastNTPTime >> 32) & 0xff;
+ data[12] = (mLastNTPTime >> 24) & 0xff;
+ data[13] = (mLastNTPTime >> 16) & 0xff;
+ data[14] = (mLastNTPTime >> 8) & 0xff;
+ data[15] = mLastNTPTime & 0xff;
+
+ data[16] = (mLastRTPTime >> 24) & 0xff;
+ data[17] = (mLastRTPTime >> 16) & 0xff;
+ data[18] = (mLastRTPTime >> 8) & 0xff;
+ data[19] = mLastRTPTime & 0xff;
+
+ data[20] = mNumRTPSent >> 24;
+ data[21] = (mNumRTPSent >> 16) & 0xff;
+ data[22] = (mNumRTPSent >> 8) & 0xff;
+ data[23] = mNumRTPSent & 0xff;
+
+ data[24] = mNumRTPOctetsSent >> 24;
+ data[25] = (mNumRTPOctetsSent >> 16) & 0xff;
+ data[26] = (mNumRTPOctetsSent >> 8) & 0xff;
+ data[27] = mNumRTPOctetsSent & 0xff;
+
+ buffer->setRange(buffer->offset(), buffer->size() + 28);
+ }
+
+ void addSDES(const sp<ABuffer> &buffer) {
+ uint8_t *data = buffer->data() + buffer->size();
+ data[0] = 0x80 | 1;
+ data[1] = 202; // SDES
+ data[4] = mSourceID >> 24;
+ data[5] = (mSourceID >> 16) & 0xff;
+ data[6] = (mSourceID >> 8) & 0xff;
+ data[7] = mSourceID & 0xff;
+
+ size_t offset = 8;
+
+ data[offset++] = 1; // CNAME
+
+ static const char *kCNAME = "andih@laptop";
+ data[offset++] = strlen(kCNAME);
+
+ memcpy(&data[offset], kCNAME, strlen(kCNAME));
+ offset += strlen(kCNAME);
+
+ data[offset++] = 7; // NOTE
+
+ static const char *kNOTE = "Hell's frozen over.";
+ data[offset++] = strlen(kNOTE);
+
+ memcpy(&data[offset], kNOTE, strlen(kNOTE));
+ offset += strlen(kNOTE);
+
+ 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);
+ }
+
+ DISALLOW_EVIL_CONSTRUCTORS(MyTransmitter);
+};
+
+} // namespace android
+
+#endif // MY_TRANSMITTER_H_
diff --git a/media/libstagefright/rtsp/VideoSource.h b/media/libstagefright/rtsp/VideoSource.h
new file mode 100644
index 0000000..ae0c85b
--- /dev/null
+++ b/media/libstagefright/rtsp/VideoSource.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef VIDEO_SOURCE_H_
+
+#define VIDEO_SOURCE_H_
+
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+class VideoSource : public MediaSource {
+ static const int32_t kFramerate = 24; // fps
+
+public:
+ VideoSource(int width, int height)
+ : mWidth(width),
+ mHeight(height),
+ mSize((width * height * 3) / 2) {
+ mGroup.add_buffer(new MediaBuffer(mSize));
+ }
+
+ virtual sp<MetaData> getFormat() {
+ sp<MetaData> meta = new MetaData;
+ meta->setInt32(kKeyWidth, mWidth);
+ meta->setInt32(kKeyHeight, mHeight);
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW);
+
+ return meta;
+ }
+
+ virtual status_t start(MetaData *params) {
+ mNumFramesOutput = 0;
+ return OK;
+ }
+
+ virtual status_t stop() {
+ return OK;
+ }
+
+ virtual status_t read(
+ MediaBuffer **buffer, const MediaSource::ReadOptions *options) {
+ if (mNumFramesOutput == kFramerate * 100) {
+ // Stop returning data after 10 secs.
+ return ERROR_END_OF_STREAM;
+ }
+
+ // printf("VideoSource::read\n");
+ status_t err = mGroup.acquire_buffer(buffer);
+ if (err != OK) {
+ return err;
+ }
+
+ char x = (char)((double)rand() / RAND_MAX * 255);
+ memset((*buffer)->data(), x, mSize);
+ (*buffer)->set_range(0, mSize);
+ (*buffer)->meta_data()->clear();
+ (*buffer)->meta_data()->setInt64(
+ kKeyTime, (mNumFramesOutput * 1000000) / kFramerate);
+ ++mNumFramesOutput;
+
+ // printf("VideoSource::read - returning buffer\n");
+ // LOG(INFO)("VideoSource::read - returning buffer");
+ return OK;
+ }
+
+protected:
+ virtual ~VideoSource() {}
+
+private:
+ MediaBufferGroup mGroup;
+ int mWidth, mHeight;
+ size_t mSize;
+ int64_t mNumFramesOutput;;
+
+ VideoSource(const VideoSource &);
+ VideoSource &operator=(const VideoSource &);
+};
+
+} // namespace android
+
+#endif // VIDEO_SOURCE_H_