am a5cd816c: am eb9128f9: Fix sampleTable instantiation, this makes sure that the sample table refers to the custom datasource that caches the metadata to prevent needless seeking.
Merge commit 'a5cd816c720ed87b91a33aa5d000a0d308c74453' into kraken
* commit 'a5cd816c720ed87b91a33aa5d000a0d308c74453':
Fix sampleTable instantiation, this makes sure that the sample table refers to the custom datasource that caches the metadata to prevent needless seeking.
diff --git a/camera/Camera.cpp b/camera/Camera.cpp
index f19c502..2fbddd5 100644
--- a/camera/Camera.cpp
+++ b/camera/Camera.cpp
@@ -92,16 +92,28 @@
Camera::~Camera()
{
- disconnect();
+ // We don't need to call disconnect() here because if the CameraService
+ // thinks we are the owner of the hardware, it will hold a (strong)
+ // reference to us, and we can't possibly be here. We also don't want to
+ // call disconnect() here if we are in the same process as mediaserver,
+ // because we may be invoked by CameraService::Client::connect() and will
+ // deadlock if we call any method of ICamera here.
}
-sp<Camera> Camera::connect()
+int32_t Camera::getNumberOfCameras()
+{
+ const sp<ICameraService>& cs = getCameraService();
+ if (cs == 0) return 0;
+ return cs->getNumberOfCameras();
+}
+
+sp<Camera> Camera::connect(int cameraId)
{
LOGV("connect");
sp<Camera> c = new Camera();
const sp<ICameraService>& cs = getCameraService();
if (cs != 0) {
- c->mCamera = cs->connect(c);
+ c->mCamera = cs->connect(c, cameraId);
}
if (c->mCamera != 0) {
c->mCamera->asBinder()->linkToDeath(c);
diff --git a/camera/CameraParameters.cpp b/camera/CameraParameters.cpp
index 65fd7ac..cfb7ba1 100644
--- a/camera/CameraParameters.cpp
+++ b/camera/CameraParameters.cpp
@@ -137,6 +137,7 @@
const char CameraParameters::FOCUS_MODE_MACRO[] = "macro";
const char CameraParameters::FOCUS_MODE_FIXED[] = "fixed";
const char CameraParameters::FOCUS_MODE_EDOF[] = "edof";
+const char CameraParameters::FOCUS_MODE_CONTINUOUS[] = "continuous";
CameraParameters::CameraParameters()
: mMap()
diff --git a/camera/ICameraService.cpp b/camera/ICameraService.cpp
index 46b5478..db1dca6 100644
--- a/camera/ICameraService.cpp
+++ b/camera/ICameraService.cpp
@@ -34,12 +34,22 @@
{
}
+ // get number of cameras available
+ virtual int32_t getNumberOfCameras()
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICameraService::getInterfaceDescriptor());
+ remote()->transact(BnCameraService::GET_NUMBER_OF_CAMERAS, data, &reply);
+ return reply.readInt32();
+ }
+
// connect to camera service
- virtual sp<ICamera> connect(const sp<ICameraClient>& cameraClient)
+ virtual sp<ICamera> connect(const sp<ICameraClient>& cameraClient, int cameraId)
{
Parcel data, reply;
data.writeInterfaceToken(ICameraService::getInterfaceDescriptor());
data.writeStrongBinder(cameraClient->asBinder());
+ data.writeInt32(cameraId);
remote()->transact(BnCameraService::CONNECT, data, &reply);
return interface_cast<ICamera>(reply.readStrongBinder());
}
@@ -53,10 +63,15 @@
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch(code) {
+ case GET_NUMBER_OF_CAMERAS: {
+ CHECK_INTERFACE(ICameraService, data, reply);
+ reply->writeInt32(getNumberOfCameras());
+ return NO_ERROR;
+ } break;
case CONNECT: {
CHECK_INTERFACE(ICameraService, data, reply);
sp<ICameraClient> cameraClient = interface_cast<ICameraClient>(data.readStrongBinder());
- sp<ICamera> camera = connect(cameraClient);
+ sp<ICamera> camera = connect(cameraClient, data.readInt32());
reply->writeStrongBinder(camera->asBinder());
return NO_ERROR;
} break;
diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp
index 845c854..5a87f4c 100644
--- a/cmds/stagefright/record.cpp
+++ b/cmds/stagefright/record.cpp
@@ -147,7 +147,7 @@
OMXClient client;
CHECK_EQ(client.connect(), OK);
-#if 1
+#if 0
sp<MediaSource> source = createSource(argv[1]);
if (source == NULL) {
@@ -165,14 +165,15 @@
success = success && meta->findInt32(kKeyHeight, &height);
CHECK(success);
#else
- int width = 800;
+ int width = 720;
int height = 480;
sp<MediaSource> decoder = new DummySource(width, height);
#endif
sp<MetaData> enc_meta = new MetaData;
// enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
- enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4);
+ // enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4);
+ enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
enc_meta->setInt32(kKeyWidth, width);
enc_meta->setInt32(kKeyHeight, height);
@@ -213,6 +214,8 @@
#if 0
CameraSource *source = CameraSource::Create();
+ source->start();
+
printf("source = %p\n", source);
for (int i = 0; i < 100; ++i) {
@@ -227,6 +230,8 @@
buffer = NULL;
}
+ source->stop();
+
delete source;
source = NULL;
#endif
diff --git a/include/camera/Camera.h b/include/camera/Camera.h
index ee2b30c..1beac27 100644
--- a/include/camera/Camera.h
+++ b/include/camera/Camera.h
@@ -113,7 +113,8 @@
public:
// construct a camera client from an existing remote
static sp<Camera> create(const sp<ICamera>& camera);
- static sp<Camera> connect();
+ static int32_t getNumberOfCameras();
+ static sp<Camera> connect(int cameraId);
~Camera();
void init();
diff --git a/include/camera/CameraParameters.h b/include/camera/CameraParameters.h
index 5ea83a5..979df9f 100644
--- a/include/camera/CameraParameters.h
+++ b/include/camera/CameraParameters.h
@@ -309,6 +309,12 @@
// continuously. Applications should not call
// CameraHardwareInterface.autoFocus in this mode.
static const char FOCUS_MODE_EDOF[];
+ // Continuous focus mode. The camera continuously tries to focus. This is
+ // ideal for shooting video or shooting photo of moving object. Continuous
+ // focus starts when CameraHardwareInterface.autoFocus is called. Focus
+ // callback will be only called once as soon as the picture is in focus.
+ static const char FOCUS_MODE_CONTINUOUS[];
+
private:
DefaultKeyedVector<String8,String8> mMap;
diff --git a/include/camera/ICameraService.h b/include/camera/ICameraService.h
index 82b1283..dcd434f 100644
--- a/include/camera/ICameraService.h
+++ b/include/camera/ICameraService.h
@@ -30,13 +30,16 @@
{
public:
enum {
- CONNECT = IBinder::FIRST_CALL_TRANSACTION,
+ GET_NUMBER_OF_CAMERAS = IBinder::FIRST_CALL_TRANSACTION,
+ CONNECT
};
public:
DECLARE_META_INTERFACE(CameraService);
- virtual sp<ICamera> connect(const sp<ICameraClient>& cameraClient) = 0;
+ virtual int32_t getNumberOfCameras() = 0;
+ virtual sp<ICamera> connect(const sp<ICameraClient>& cameraClient,
+ int cameraId) = 0;
};
// ----------------------------------------------------------------------------
diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h
index 9ea6c7b..eead166 100644
--- a/include/media/mediarecorder.h
+++ b/include/media/mediarecorder.h
@@ -135,7 +135,8 @@
enum media_recorder_info_type {
MEDIA_RECORDER_INFO_UNKNOWN = 1,
MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800,
- MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED = 801
+ MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED = 801,
+ MEDIA_RECORDER_INFO_STOP_PREMATURELY = 802
};
// ----------------------------------------------------------------------------
diff --git a/include/media/stagefright/AMRWriter.h b/include/media/stagefright/AMRWriter.h
index 372909a..dd11809 100644
--- a/include/media/stagefright/AMRWriter.h
+++ b/include/media/stagefright/AMRWriter.h
@@ -42,18 +42,20 @@
virtual ~AMRWriter();
private:
- Mutex mLock;
-
FILE *mFile;
status_t mInitCheck;
sp<MediaSource> mSource;
bool mStarted;
volatile bool mDone;
- bool mReachedEOS;
+ volatile bool mReachedEOS;
pthread_t mThread;
+ int64_t mEstimatedSizeBytes;
+ int64_t mEstimatedDurationUs;
static void *ThreadWrapper(void *);
void threadFunc();
+ bool exceedsFileSizeLimit();
+ bool exceedsFileDurationLimit();
AMRWriter(const AMRWriter &);
AMRWriter &operator=(const AMRWriter &);
diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h
index ea435de..42d6634 100644
--- a/include/media/stagefright/CameraSource.h
+++ b/include/media/stagefright/CameraSource.h
@@ -28,18 +28,15 @@
class ICamera;
class IMemory;
-class ISurface;
class Camera;
class CameraSource : public MediaSource {
public:
static CameraSource *Create();
- static CameraSource *CreateFromICamera(const sp<ICamera> &icamera);
+ static CameraSource *CreateFromCamera(const sp<Camera> &camera);
virtual ~CameraSource();
- void setPreviewSurface(const sp<ISurface> &surface);
-
virtual status_t start(MetaData *params = NULL);
virtual status_t stop();
@@ -52,7 +49,6 @@
friend class CameraSourceListener;
sp<Camera> mCamera;
- sp<ISurface> mPreviewSurface;
Mutex mLock;
Condition mFrameAvailableCondition;
@@ -61,12 +57,18 @@
int mWidth, mHeight;
int64_t mFirstFrameTimeUs;
- int32_t mNumFrames;
+ int64_t mLastFrameTimestampUs;
+ int32_t mNumFramesReceived;
+ int32_t mNumFramesEncoded;
+ int32_t mNumFramesDropped;
bool mStarted;
CameraSource(const sp<Camera> &camera);
- void dataCallback(int32_t msgType, const sp<IMemory> &data);
+ void dataCallbackTimestamp(
+ int64_t timestampUs, int32_t msgType, const sp<IMemory> &data);
+
+ void releaseQueuedFrames();
CameraSource(const CameraSource &);
CameraSource &operator=(const CameraSource &);
diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index 6b93f19..3c85eca 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -49,6 +49,8 @@
void writeFourcc(const char *fourcc);
void write(const void *data, size_t size);
void endBox();
+ uint32_t interleaveDuration() const { return mInterleaveDurationUs; }
+ status_t setInterleaveDuration(uint32_t duration);
protected:
virtual ~MPEG4Writer();
@@ -59,14 +61,33 @@
FILE *mFile;
off_t mOffset;
off_t mMdatOffset;
+ uint8_t *mMoovBoxBuffer;
+ off_t mMoovBoxBufferOffset;
+ bool mWriteMoovBoxToMemory;
+ off_t mFreeBoxOffset;
+ bool mStreamableFile;
+ off_t mEstimatedMoovBoxSize;
+ uint32_t mInterleaveDurationUs;
+ int64_t mStartTimestampUs;
Mutex mLock;
List<Track *> mTracks;
List<off_t> mBoxes;
- off_t addSample(MediaBuffer *buffer);
- off_t addLengthPrefixedSample(MediaBuffer *buffer);
+ void setStartTimestamp(int64_t timeUs);
+ int64_t getStartTimestamp(); // Not const
+
+ void lock();
+ void unlock();
+
+ // Acquire lock before calling these methods
+ off_t addSample_l(MediaBuffer *buffer);
+ off_t addLengthPrefixedSample_l(MediaBuffer *buffer);
+
+ inline size_t write(const void *ptr, size_t size, size_t nmemb, FILE* stream);
+ bool exceedsFileSizeLimit();
+ bool exceedsFileDurationLimit();
MPEG4Writer(const MPEG4Writer &);
MPEG4Writer &operator=(const MPEG4Writer &);
diff --git a/include/media/stagefright/MediaWriter.h b/include/media/stagefright/MediaWriter.h
index b8232c6..b15f69c 100644
--- a/include/media/stagefright/MediaWriter.h
+++ b/include/media/stagefright/MediaWriter.h
@@ -19,6 +19,7 @@
#define MEDIA_WRITER_H_
#include <utils/RefBase.h>
+#include <media/IMediaPlayerClient.h>
namespace android {
@@ -31,10 +32,23 @@
virtual bool reachedEOS() = 0;
virtual status_t start() = 0;
virtual void stop() = 0;
+ virtual void setMaxFileSize(int64_t bytes) { mMaxFileSizeLimitBytes = bytes; }
+ virtual void setMaxFileDuration(int64_t durationUs) { mMaxFileDurationLimitUs = durationUs; }
+ virtual void setListener(const sp<IMediaPlayerClient>& listener) {
+ mListener = listener;
+ }
protected:
virtual ~MediaWriter() {}
+ int64_t mMaxFileSizeLimitBytes;
+ int64_t mMaxFileDurationLimitUs;
+ sp<IMediaPlayerClient> mListener;
+ void notify(int msg, int ext1, int ext2) {
+ if (mListener != NULL) {
+ mListener->notify(msg, ext1, ext2);
+ }
+ }
private:
MediaWriter(const MediaWriter &);
MediaWriter &operator=(const MediaWriter &);
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h
index 8e2db20..ab5ac64 100644
--- a/include/private/media/AudioTrackShared.h
+++ b/include/private/media/AudioTrackShared.h
@@ -59,7 +59,7 @@
// 8 bit PCM data: in this case, mCblk->frameSize is based on a sample size of
// 16 bit because data is converted to 16 bit before being stored in buffer
uint32_t frameSize;
- uint8_t channels;
+ uint8_t channelCount;
uint8_t flowControlFlag; // underrun (out) or overrrun (in) indication
uint8_t out; // out equals 1 for AudioTrack and 0 for AudioRecord
uint8_t forceReady;
diff --git a/include/private/surfaceflinger/SharedBufferStack.h b/include/private/surfaceflinger/SharedBufferStack.h
index 9b5a1e0..c23832d 100644
--- a/include/private/surfaceflinger/SharedBufferStack.h
+++ b/include/private/surfaceflinger/SharedBufferStack.h
@@ -58,7 +58,7 @@
// When changing these values, the COMPILE_TIME_ASSERT at the end of this
// file need to be updated.
const unsigned int NUM_LAYERS_MAX = 31;
-const unsigned int NUM_BUFFER_MAX = 4;
+const unsigned int NUM_BUFFER_MAX = 16;
const unsigned int NUM_DISPLAY_MAX = 4;
// ----------------------------------------------------------------------------
@@ -69,7 +69,11 @@
// ----------------------------------------------------------------------------
-// should be 128 bytes (32 longs)
+// 4 * (11 + 7 + (1 + 2*NUM_RECT_MAX) * NUM_BUFFER_MAX) * NUM_LAYERS_MAX
+// 4 * (11 + 7 + (1 + 2*7)*16) * 31
+// 1032 * 31
+// = ~27 KiB (31992)
+
class SharedBufferStack
{
friend class SharedClient;
@@ -78,21 +82,31 @@
friend class SharedBufferServer;
public:
- struct FlatRegion { // 12 bytes
- static const unsigned int NUM_RECT_MAX = 1;
- uint32_t count;
- uint16_t rects[4*NUM_RECT_MAX];
- };
-
struct Statistics { // 4 longs
typedef int32_t usecs_t;
usecs_t totalTime;
usecs_t reserved[3];
};
+
+ struct SmallRect {
+ uint16_t l, t, r, b;
+ };
+
+ struct FlatRegion { // 52 bytes = 4 * (1 + 2*N)
+ static const unsigned int NUM_RECT_MAX = 6;
+ uint32_t count;
+ SmallRect rects[NUM_RECT_MAX];
+ };
+
+ struct BufferData {
+ FlatRegion dirtyRegion;
+ SmallRect crop;
+ };
SharedBufferStack();
void init(int32_t identity);
status_t setDirtyRegion(int buffer, const Region& reg);
+ status_t setCrop(int buffer, const Rect& reg);
Region getDirtyRegion(int buffer) const;
// these attributes are part of the conditions/updates
@@ -104,16 +118,18 @@
// not part of the conditions
volatile int32_t reallocMask;
+ volatile int8_t index[NUM_BUFFER_MAX];
int32_t identity; // surface's identity (const)
- int32_t reserved32[9];
+ int32_t reserved32[2];
Statistics stats;
- FlatRegion dirtyRegion[NUM_BUFFER_MAX]; // 12*4=48 bytes
+ int32_t reserved;
+ BufferData buffers[NUM_BUFFER_MAX]; // 960 bytes
};
// ----------------------------------------------------------------------------
-// 4 KB max
+// 32 KB max
class SharedClient
{
public:
@@ -150,8 +166,9 @@
protected:
SharedClient* const mSharedClient;
SharedBufferStack* const mSharedStack;
- const int mNumBuffers;
+ int mNumBuffers;
const int mIdentity;
+ int32_t computeTail() const;
friend struct Update;
friend struct QueueUpdate;
@@ -160,61 +177,22 @@
SharedBufferStack& stack;
inline ConditionBase(SharedBufferBase* sbc)
: stack(*sbc->mSharedStack) { }
+ virtual ~ConditionBase() { };
+ virtual bool operator()() const = 0;
+ virtual const char* name() const = 0;
};
+ status_t waitForCondition(const ConditionBase& condition);
struct UpdateBase {
SharedBufferStack& stack;
inline UpdateBase(SharedBufferBase* sbb)
: stack(*sbb->mSharedStack) { }
};
-
- template <typename T>
- status_t waitForCondition(T condition);
-
template <typename T>
status_t updateCondition(T update);
};
template <typename T>
-status_t SharedBufferBase::waitForCondition(T condition)
-{
- const SharedBufferStack& stack( *mSharedStack );
- SharedClient& client( *mSharedClient );
- const nsecs_t TIMEOUT = s2ns(1);
- Mutex::Autolock _l(client.lock);
- while ((condition()==false) &&
- (stack.identity == mIdentity) &&
- (stack.status == NO_ERROR))
- {
- status_t err = client.cv.waitRelative(client.lock, TIMEOUT);
-
- // handle errors and timeouts
- if (CC_UNLIKELY(err != NO_ERROR)) {
- if (err == TIMED_OUT) {
- if (condition()) {
- LOGE("waitForCondition(%s) timed out (identity=%d), "
- "but condition is true! We recovered but it "
- "shouldn't happen." , T::name(),
- stack.identity);
- break;
- } else {
- LOGW("waitForCondition(%s) timed out "
- "(identity=%d, status=%d). "
- "CPU may be pegged. trying again.", T::name(),
- stack.identity, stack.status);
- }
- } else {
- LOGE("waitForCondition(%s) error (%s) ",
- T::name(), strerror(-err));
- return err;
- }
- }
- }
- return (stack.identity != mIdentity) ? status_t(BAD_INDEX) : stack.status;
-}
-
-
-template <typename T>
status_t SharedBufferBase::updateCondition(T update) {
SharedClient& client( *mSharedClient );
Mutex::Autolock _l(client.lock);
@@ -238,13 +216,13 @@
status_t queue(int buf);
bool needNewBuffer(int buffer) const;
status_t setDirtyRegion(int buffer, const Region& reg);
+ status_t setCrop(int buffer, const Rect& reg);
+ status_t setBufferCount(int bufferCount);
private:
friend struct Condition;
friend struct DequeueCondition;
friend struct LockCondition;
-
- int32_t computeTail() const;
struct QueueUpdate : public UpdateBase {
inline QueueUpdate(SharedBufferBase* sbb);
@@ -260,18 +238,20 @@
struct DequeueCondition : public ConditionBase {
inline DequeueCondition(SharedBufferClient* sbc);
- inline bool operator()();
- static inline const char* name() { return "DequeueCondition"; }
+ inline bool operator()() const;
+ inline const char* name() const { return "DequeueCondition"; }
};
struct LockCondition : public ConditionBase {
int buf;
inline LockCondition(SharedBufferClient* sbc, int buf);
- inline bool operator()();
- static inline const char* name() { return "LockCondition"; }
+ inline bool operator()() const;
+ inline const char* name() const { return "LockCondition"; }
};
int32_t tail;
+ int32_t undoDequeueTail;
+ int32_t queued_head;
// statistics...
nsecs_t mDequeueTime[NUM_BUFFER_MAX];
};
@@ -290,13 +270,61 @@
status_t reallocate();
status_t assertReallocate(int buffer);
int32_t getQueuedCount() const;
-
Region getDirtyRegion(int buffer) const;
+ status_t resize(int newNumBuffers);
+
SharedBufferStack::Statistics getStats() const;
private:
+ /*
+ * BufferList is basically a fixed-capacity sorted-vector of
+ * unsigned 5-bits ints using a 32-bits int as storage.
+ * it has efficient iterators to find items in the list and not in the list.
+ */
+ class BufferList {
+ size_t mCapacity;
+ uint32_t mList;
+ public:
+ BufferList(size_t c = NUM_BUFFER_MAX) : mCapacity(c), mList(0) { }
+ status_t add(int value);
+ status_t remove(int value);
+
+ class const_iterator {
+ friend class BufferList;
+ uint32_t mask, curr;
+ const_iterator(uint32_t mask) :
+ mask(mask), curr(31 - __builtin_clz(mask)) { }
+ public:
+ inline bool operator == (const const_iterator& rhs) const {
+ return mask == rhs.mask;
+ }
+ inline bool operator != (const const_iterator& rhs) const {
+ return mask != rhs.mask;
+ }
+ inline int operator *() const { return curr; }
+ inline const const_iterator& operator ++(int) {
+ mask &= ~curr;
+ curr = 31 - __builtin_clz(mask);
+ return *this;
+ }
+ };
+
+ inline const_iterator begin() const {
+ return const_iterator(mList);
+ }
+ inline const_iterator end() const {
+ return const_iterator(0);
+ }
+ inline const_iterator free_begin() const {
+ uint32_t mask = (1 << (32-mCapacity)) - 1;
+ return const_iterator( ~(mList | mask) );
+ }
+ };
+
+ BufferList mBufferList;
+
struct UnlockUpdate : public UpdateBase {
const int lockedBuffer;
inline UnlockUpdate(SharedBufferBase* sbb, int lockedBuffer);
@@ -318,8 +346,8 @@
struct ReallocateCondition : public ConditionBase {
int buf;
inline ReallocateCondition(SharedBufferBase* sbb, int buf);
- inline bool operator()();
- static inline const char* name() { return "ReallocateCondition"; }
+ inline bool operator()() const;
+ inline const char* name() const { return "ReallocateCondition"; }
};
};
@@ -349,8 +377,7 @@
// ---------------------------------------------------------------------------
-COMPILE_TIME_ASSERT(sizeof(SharedClient) <= 4096)
-COMPILE_TIME_ASSERT(sizeof(SharedBufferStack) == 128)
+COMPILE_TIME_ASSERT(sizeof(SharedClient) <= 32768)
COMPILE_TIME_ASSERT(sizeof(surface_flinger_cblk_t) <= 4096)
// ---------------------------------------------------------------------------
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp
index 01b6737..f3804b8 100644
--- a/media/libmedia/IOMX.cpp
+++ b/media/libmedia/IOMX.cpp
@@ -57,7 +57,7 @@
return NULL;
}
- jfieldID surfaceID = env->GetFieldID(surfaceClass, "mSurface", "I");
+ jfieldID surfaceID = env->GetFieldID(surfaceClass, ANDROID_VIEW_SURFACE_JNI_ID, "I");
if (surfaceID == NULL) {
LOGE("Can't find Surface.mSurface");
return NULL;
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index cf97b23..3b678cb 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -37,6 +37,7 @@
libvorbisidec \
libsonivox \
libmedia \
+ libcamera_client \
libandroid_runtime \
libstagefright \
libstagefright_omx \
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 6a6afa1..11f3016 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -228,14 +228,10 @@
sp<IMediaRecorder> MediaPlayerService::createMediaRecorder(pid_t pid)
{
-#ifndef NO_OPENCORE
sp<MediaRecorderClient> recorder = new MediaRecorderClient(this, pid);
wp<MediaRecorderClient> w = recorder;
Mutex::Autolock lock(mLock);
mMediaRecorderClients.add(w);
-#else
- sp<MediaRecorderClient> recorder = NULL;
-#endif
LOGV("Create new media recorder client from pid %d", pid);
return recorder;
}
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 531fd11..8404779 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -20,6 +20,7 @@
#include "StagefrightRecorder.h"
+#include <binder/IPCThreadState.h>
#include <media/stagefright/AudioSource.h>
#include <media/stagefright/AMRWriter.h>
#include <media/stagefright/CameraSource.h>
@@ -30,8 +31,13 @@
#include <media/stagefright/OMXClient.h>
#include <media/stagefright/OMXCodec.h>
#include <camera/ICamera.h>
+#include <camera/Camera.h>
+#include <camera/CameraParameters.h>
#include <surfaceflinger/ISurface.h>
#include <utils/Errors.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <ctype.h>
namespace android {
@@ -83,6 +89,12 @@
}
status_t StagefrightRecorder::setVideoSize(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ LOGE("Invalid video size: %dx%d", width, height);
+ return BAD_VALUE;
+ }
+
+ // Additional check on the dimension will be performed later
mVideoWidth = width;
mVideoHeight = height;
@@ -90,13 +102,37 @@
}
status_t StagefrightRecorder::setVideoFrameRate(int frames_per_second) {
+ if (frames_per_second <= 0 || frames_per_second > 30) {
+ LOGE("Invalid video frame rate: %d", frames_per_second);
+ return BAD_VALUE;
+ }
+
+ // Additional check on the frame rate will be performed later
mFrameRate = frames_per_second;
return OK;
}
status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera) {
- mCamera = camera;
+ LOGV("setCamera: pid %d pid %d", IPCThreadState::self()->getCallingPid(), getpid());
+ if (camera == 0) {
+ LOGE("camera is NULL");
+ return UNKNOWN_ERROR;
+ }
+
+ mFlags &= ~ FLAGS_SET_CAMERA | FLAGS_HOT_CAMERA;
+ mCamera = Camera::create(camera);
+ if (mCamera == 0) {
+ LOGE("Unable to connect to camera");
+ return UNKNOWN_ERROR;
+ }
+
+ LOGV("Connected to camera");
+ mFlags |= FLAGS_SET_CAMERA;
+ if (mCamera->previewEnabled()) {
+ LOGV("camera is hot");
+ mFlags |= FLAGS_HOT_CAMERA;
+ }
return OK;
}
@@ -127,9 +163,216 @@
return OK;
}
-status_t StagefrightRecorder::setParameters(const String8 ¶ms) {
- mParams = params;
+// Attempt to parse an int64 literal optionally surrounded by whitespace,
+// returns true on success, false otherwise.
+static bool safe_strtoi64(const char *s, int64_t *val) {
+ char *end;
+ *val = strtoll(s, &end, 10);
+ if (end == s || errno == ERANGE) {
+ return false;
+ }
+
+ // Skip trailing whitespace
+ while (isspace(*end)) {
+ ++end;
+ }
+
+ // For a successful return, the string must contain nothing but a valid
+ // int64 literal optionally surrounded by whitespace.
+
+ return *end == '\0';
+}
+
+// Return true if the value is in [0, 0x007FFFFFFF]
+static bool safe_strtoi32(const char *s, int32_t *val) {
+ int64_t temp;
+ if (safe_strtoi64(s, &temp)) {
+ if (temp >= 0 && temp <= 0x007FFFFFFF) {
+ *val = static_cast<int32_t>(temp);
+ return true;
+ }
+ }
+ return false;
+}
+
+// Trim both leading and trailing whitespace from the given string.
+static void TrimString(String8 *s) {
+ size_t num_bytes = s->bytes();
+ const char *data = s->string();
+
+ size_t leading_space = 0;
+ while (leading_space < num_bytes && isspace(data[leading_space])) {
+ ++leading_space;
+ }
+
+ size_t i = num_bytes;
+ while (i > leading_space && isspace(data[i - 1])) {
+ --i;
+ }
+
+ s->setTo(String8(&data[leading_space], i - leading_space));
+}
+
+status_t StagefrightRecorder::setParamAudioSamplingRate(int32_t sampleRate) {
+ LOGV("setParamAudioSamplingRate: %d", sampleRate);
+ if (sampleRate <= 0) {
+ LOGE("Invalid audio sampling rate: %d", sampleRate);
+ return BAD_VALUE;
+ }
+
+ // Additional check on the sample rate will be performed later.
+ mSampleRate = sampleRate;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamAudioNumberOfChannels(int32_t channels) {
+ LOGV("setParamAudioNumberOfChannels: %d", channels);
+ if (channels <= 0 || channels >= 3) {
+ LOGE("Invalid number of audio channels: %d", channels);
+ }
+
+ // Additional check on the number of channels will be performed later.
+ mAudioChannels = channels;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamAudioEncodingBitRate(int32_t bitRate) {
+ LOGV("setParamAudioEncodingBitRate: %d", bitRate);
+ if (bitRate <= 0) {
+ LOGE("Invalid audio encoding bit rate: %d", bitRate);
+ return BAD_VALUE;
+ }
+
+ // The target bit rate may not be exactly the same as the requested.
+ // It depends on many factors, such as rate control, and the bit rate
+ // range that a specific encoder supports. The mismatch between the
+ // the target and requested bit rate will NOT be treated as an error.
+ mAudioBitRate = bitRate;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamVideoEncodingBitRate(int32_t bitRate) {
+ LOGV("setParamVideoEncodingBitRate: %d", bitRate);
+ if (bitRate <= 0) {
+ LOGE("Invalid video encoding bit rate: %d", bitRate);
+ return BAD_VALUE;
+ }
+
+ // The target bit rate may not be exactly the same as the requested.
+ // It depends on many factors, such as rate control, and the bit rate
+ // range that a specific encoder supports. The mismatch between the
+ // the target and requested bit rate will NOT be treated as an error.
+ mVideoBitRate = bitRate;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamMaxDurationOrFileSize(int64_t limit,
+ bool limit_is_duration) {
+ LOGV("setParamMaxDurationOrFileSize: limit (%lld) for %s",
+ limit, limit_is_duration?"duration":"size");
+ if (limit_is_duration) { // limit is in ms
+ if (limit <= 1000) { // XXX: 1 second
+ LOGE("Max file duration is too short: %lld us", limit);
+ }
+ mMaxFileDurationUs = limit * 1000LL;
+ } else {
+ if (limit <= 1024) { // XXX: 1 kB
+ LOGE("Max file size is too small: %lld bytes", limit);
+ }
+ mMaxFileSizeBytes = limit;
+ }
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamInterleaveDuration(int32_t durationUs) {
+ LOGV("setParamInterleaveDuration: %d", durationUs);
+ if (durationUs <= 20000) { // XXX: 20 ms
+ LOGE("Audio/video interleave duration is too small: %d us", durationUs);
+ return BAD_VALUE;
+ }
+ mInterleaveDurationUs = durationUs;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParameter(
+ const String8 &key, const String8 &value) {
+ LOGV("setParameter: key (%s) => value (%s)", key.string(), value.string());
+ if (key == "max-duration") {
+ int64_t max_duration_ms;
+ if (safe_strtoi64(value.string(), &max_duration_ms)) {
+ return setParamMaxDurationOrFileSize(
+ max_duration_ms, true /* limit_is_duration */);
+ }
+ } else if (key == "max-filesize") {
+ int64_t max_filesize_bytes;
+ if (safe_strtoi64(value.string(), &max_filesize_bytes)) {
+ return setParamMaxDurationOrFileSize(
+ max_filesize_bytes, false /* limit is filesize */);
+ }
+ } else if (key == "audio-param-sampling-rate") {
+ int32_t sampling_rate;
+ if (safe_strtoi32(value.string(), &sampling_rate)) {
+ return setParamAudioSamplingRate(sampling_rate);
+ }
+ } else if (key == "audio-param-number-of-channels") {
+ int32_t number_of_channels;
+ if (safe_strtoi32(value.string(), &number_of_channels)) {
+ return setParamAudioNumberOfChannels(number_of_channels);
+ }
+ } else if (key == "audio-param-encoding-bitrate") {
+ int32_t audio_bitrate;
+ if (safe_strtoi32(value.string(), &audio_bitrate)) {
+ return setParamAudioEncodingBitRate(audio_bitrate);
+ }
+ } else if (key == "video-param-encoding-bitrate") {
+ int32_t video_bitrate;
+ if (safe_strtoi32(value.string(), &video_bitrate)) {
+ return setParamVideoEncodingBitRate(video_bitrate);
+ }
+ } else if (key == "param-interleave-duration-us") {
+ int32_t durationUs;
+ if (safe_strtoi32(value.string(), &durationUs)) {
+ return setParamInterleaveDuration(durationUs);
+ }
+ } else {
+ LOGE("setParameter: failed to find key %s", key.string());
+ }
+ return BAD_VALUE;
+}
+
+status_t StagefrightRecorder::setParameters(const String8 ¶ms) {
+ LOGV("setParameters: %s", params.string());
+ const char *cparams = params.string();
+ const char *key_start = cparams;
+ for (;;) {
+ const char *equal_pos = strchr(key_start, '=');
+ if (equal_pos == NULL) {
+ LOGE("Parameters %s miss a value", cparams);
+ return BAD_VALUE;
+ }
+ String8 key(key_start, equal_pos - key_start);
+ TrimString(&key);
+ if (key.length() == 0) {
+ LOGE("Parameters %s contains an empty key", cparams);
+ return BAD_VALUE;
+ }
+ const char *value_start = equal_pos + 1;
+ const char *semicolon_pos = strchr(value_start, ';');
+ String8 value;
+ if (semicolon_pos == NULL) {
+ value.setTo(value_start);
+ } else {
+ value.setTo(value_start, semicolon_pos - value_start);
+ }
+ if (setParameter(key, value) != OK) {
+ return BAD_VALUE;
+ }
+ if (semicolon_pos == NULL) {
+ break; // Reaches the end
+ }
+ key_start = semicolon_pos + 1;
+ }
return OK;
}
@@ -158,40 +401,56 @@
case OUTPUT_FORMAT_AMR_WB:
return startAMRRecording();
+ case OUTPUT_FORMAT_AAC_ADIF:
+ case OUTPUT_FORMAT_AAC_ADTS:
+ return startAACRecording();
+
default:
return UNKNOWN_ERROR;
}
}
-sp<MediaSource> StagefrightRecorder::createAMRAudioSource() {
- uint32_t sampleRate =
- mAudioEncoder == AUDIO_ENCODER_AMR_NB ? 8000 : 16000;
-
+sp<MediaSource> StagefrightRecorder::createAudioSource() {
sp<AudioSource> audioSource =
new AudioSource(
mAudioSource,
- sampleRate,
+ mSampleRate,
AudioSystem::CHANNEL_IN_MONO);
status_t err = audioSource->initCheck();
if (err != OK) {
+ LOGE("audio source is not initialized");
return NULL;
}
sp<MetaData> encMeta = new MetaData;
- encMeta->setCString(
- kKeyMIMEType,
- mAudioEncoder == AUDIO_ENCODER_AMR_NB
- ? MEDIA_MIMETYPE_AUDIO_AMR_NB : MEDIA_MIMETYPE_AUDIO_AMR_WB);
+ const char *mime;
+ switch (mAudioEncoder) {
+ case AUDIO_ENCODER_AMR_NB:
+ case AUDIO_ENCODER_DEFAULT:
+ mime = MEDIA_MIMETYPE_AUDIO_AMR_NB;
+ break;
+ case AUDIO_ENCODER_AMR_WB:
+ mime = MEDIA_MIMETYPE_AUDIO_AMR_WB;
+ break;
+ case AUDIO_ENCODER_AAC:
+ mime = MEDIA_MIMETYPE_AUDIO_AAC;
+ break;
+ default:
+ LOGE("Unknown audio encoder: %d", mAudioEncoder);
+ return NULL;
+ }
+ encMeta->setCString(kKeyMIMEType, mime);
int32_t maxInputSize;
CHECK(audioSource->getFormat()->findInt32(
kKeyMaxInputSize, &maxInputSize));
encMeta->setInt32(kKeyMaxInputSize, maxInputSize);
- encMeta->setInt32(kKeyChannelCount, 1);
- encMeta->setInt32(kKeySampleRate, sampleRate);
+ encMeta->setInt32(kKeyChannelCount, mAudioChannels);
+ encMeta->setInt32(kKeySampleRate, mSampleRate);
+ encMeta->setInt32(kKeyBitRate, mAudioBitRate);
OMXClient client;
CHECK_EQ(client.connect(), OK);
@@ -203,22 +462,59 @@
return audioEncoder;
}
+status_t StagefrightRecorder::startAACRecording() {
+ CHECK(mOutputFormat == OUTPUT_FORMAT_AAC_ADIF ||
+ mOutputFormat == OUTPUT_FORMAT_AAC_ADTS);
+
+ CHECK(mAudioEncoder == AUDIO_ENCODER_AAC);
+ CHECK(mAudioSource != AUDIO_SOURCE_LIST_END);
+ CHECK(mOutputFd >= 0);
+
+ CHECK(0 == "AACWriter is not implemented yet");
+
+ return OK;
+}
+
status_t StagefrightRecorder::startAMRRecording() {
- if (mAudioSource == AUDIO_SOURCE_LIST_END
- || mVideoSource != VIDEO_SOURCE_LIST_END) {
+ CHECK(mOutputFormat == OUTPUT_FORMAT_AMR_NB ||
+ mOutputFormat == OUTPUT_FORMAT_AMR_WB);
+
+ if (mOutputFormat == OUTPUT_FORMAT_AMR_NB) {
+ if (mAudioEncoder != AUDIO_ENCODER_DEFAULT &&
+ mAudioEncoder != AUDIO_ENCODER_AMR_NB) {
+ LOGE("Invalid encoder %d used for AMRNB recording",
+ mAudioEncoder);
+ return UNKNOWN_ERROR;
+ }
+ if (mSampleRate != 8000) {
+ LOGE("Invalid sampling rate %d used for AMRNB recording",
+ mSampleRate);
+ return UNKNOWN_ERROR;
+ }
+ } else { // mOutputFormat must be OUTPUT_FORMAT_AMR_WB
+ if (mAudioEncoder != AUDIO_ENCODER_AMR_WB) {
+ LOGE("Invlaid encoder %d used for AMRWB recording",
+ mAudioEncoder);
+ return UNKNOWN_ERROR;
+ }
+ if (mSampleRate != 16000) {
+ LOGE("Invalid sample rate %d used for AMRWB recording",
+ mSampleRate);
+ return UNKNOWN_ERROR;
+ }
+ }
+ if (mAudioChannels != 1) {
+ LOGE("Invalid number of audio channels %d used for amr recording",
+ mAudioChannels);
return UNKNOWN_ERROR;
}
- if (mOutputFormat == OUTPUT_FORMAT_AMR_NB
- && mAudioEncoder != AUDIO_ENCODER_DEFAULT
- && mAudioEncoder != AUDIO_ENCODER_AMR_NB) {
- return UNKNOWN_ERROR;
- } else if (mOutputFormat == OUTPUT_FORMAT_AMR_WB
- && mAudioEncoder != AUDIO_ENCODER_AMR_WB) {
+ if (mAudioSource >= AUDIO_SOURCE_LIST_END) {
+ LOGE("Invalid audio source: %d", mAudioSource);
return UNKNOWN_ERROR;
}
- sp<MediaSource> audioEncoder = createAMRAudioSource();
+ sp<MediaSource> audioEncoder = createAudioSource();
if (audioEncoder == NULL) {
return UNKNOWN_ERROR;
@@ -227,6 +523,14 @@
CHECK(mOutputFd >= 0);
mWriter = new AMRWriter(dup(mOutputFd));
mWriter->addSource(audioEncoder);
+
+ if (mMaxFileDurationUs != 0) {
+ mWriter->setMaxFileDuration(mMaxFileDurationUs);
+ }
+ if (mMaxFileSizeBytes != 0) {
+ mWriter->setMaxFileSize(mMaxFileSizeBytes);
+ }
+ mWriter->setListener(mListener);
mWriter->start();
return OK;
@@ -235,18 +539,66 @@
status_t StagefrightRecorder::startMPEG4Recording() {
mWriter = new MPEG4Writer(dup(mOutputFd));
+ // Add audio source first if it exists
+ if (mAudioSource != AUDIO_SOURCE_LIST_END) {
+ sp<MediaSource> audioEncoder;
+ switch(mAudioEncoder) {
+ case AUDIO_ENCODER_AMR_NB:
+ case AUDIO_ENCODER_AMR_WB:
+ case AUDIO_ENCODER_AAC:
+ audioEncoder = createAudioSource();
+ break;
+ default:
+ LOGE("Unsupported audio encoder: %d", mAudioEncoder);
+ return UNKNOWN_ERROR;
+ }
+
+ if (audioEncoder == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ mWriter->addSource(audioEncoder);
+ }
if (mVideoSource == VIDEO_SOURCE_DEFAULT
|| mVideoSource == VIDEO_SOURCE_CAMERA) {
CHECK(mCamera != NULL);
+ // Set the actual video recording frame size
+ CameraParameters params(mCamera->getParameters());
+ params.setPreviewSize(mVideoWidth, mVideoHeight);
+ params.setPreviewFrameRate(mFrameRate);
+ String8 s = params.flatten();
+ CHECK_EQ(OK, mCamera->setParameters(s));
+ CameraParameters newCameraParams(mCamera->getParameters());
+
+ // Check on video frame size
+ int frameWidth = 0, frameHeight = 0;
+ newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
+ if (frameWidth < 0 || frameWidth != mVideoWidth ||
+ frameHeight < 0 || frameHeight != mVideoHeight) {
+ LOGE("Failed to set the video frame size to %dx%d",
+ mVideoWidth, mVideoHeight);
+ return UNKNOWN_ERROR;
+ }
+
+ // Check on video frame rate
+ int frameRate = newCameraParams.getPreviewFrameRate();
+ if (frameRate < 0 || (frameRate - mFrameRate) != 0) {
+ LOGE("Failed to set frame rate to %d", mFrameRate);
+ return UNKNOWN_ERROR;
+ }
+
+ CHECK_EQ(OK, mCamera->setPreviewDisplay(mPreviewSurface));
+
sp<CameraSource> cameraSource =
- CameraSource::CreateFromICamera(mCamera);
+ CameraSource::CreateFromCamera(mCamera);
CHECK(cameraSource != NULL);
- cameraSource->setPreviewSurface(mPreviewSurface);
-
sp<MetaData> enc_meta = new MetaData;
+ enc_meta->setInt32(kKeyBitRate, mVideoBitRate);
+ enc_meta->setInt32(kKeySampleRate, mFrameRate); // XXX: kKeySampleRate?
+
switch (mVideoEncoder) {
case VIDEO_ENCODER_H263:
enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
@@ -286,16 +638,19 @@
mWriter->addSource(encoder);
}
- if (mAudioSource != AUDIO_SOURCE_LIST_END) {
- sp<MediaSource> audioEncoder = createAMRAudioSource();
-
- if (audioEncoder == NULL) {
- return UNKNOWN_ERROR;
- }
-
- mWriter->addSource(audioEncoder);
+ {
+ // MPEGWriter specific handling
+ MPEG4Writer *writer = ((MPEG4Writer *) mWriter.get()); // mWriter is an MPEGWriter
+ writer->setInterleaveDuration(mInterleaveDurationUs);
}
+ if (mMaxFileDurationUs != 0) {
+ mWriter->setMaxFileDuration(mMaxFileDurationUs);
+ }
+ if (mMaxFileSizeBytes != 0) {
+ mWriter->setMaxFileSize(mMaxFileSizeBytes);
+ }
+ mWriter->setListener(mListener);
mWriter->start();
return OK;
}
@@ -314,21 +669,41 @@
status_t StagefrightRecorder::close() {
stop();
+ if (mCamera != 0) {
+ 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();
+ }
+ mFlags = 0;
+ }
return OK;
}
status_t StagefrightRecorder::reset() {
stop();
+ // No audio or video source by default
mAudioSource = AUDIO_SOURCE_LIST_END;
mVideoSource = VIDEO_SOURCE_LIST_END;
- mOutputFormat = OUTPUT_FORMAT_LIST_END;
- mAudioEncoder = AUDIO_ENCODER_LIST_END;
- mVideoEncoder = VIDEO_ENCODER_LIST_END;
- mVideoWidth = -1;
- mVideoHeight = -1;
- mFrameRate = -1;
+
+ // Default parameters
+ mOutputFormat = OUTPUT_FORMAT_THREE_GPP;
+ mAudioEncoder = AUDIO_ENCODER_AMR_NB;
+ mVideoEncoder = VIDEO_ENCODER_H263;
+ mVideoWidth = 176;
+ mVideoHeight = 144;
+ mFrameRate = 20;
+ mVideoBitRate = 192000;
+ mSampleRate = 8000;
+ mAudioChannels = 1;
+ mAudioBitRate = 12200;
+
mOutputFd = -1;
+ mFlags = 0;
return OK;
}
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 7ec412d..b7d554b 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -23,6 +23,7 @@
namespace android {
+class Camera;
struct MediaSource;
struct MediaWriter;
@@ -52,7 +53,12 @@
virtual status_t getMaxAmplitude(int *max);
private:
- sp<ICamera> mCamera;
+ enum CameraFlags {
+ FLAGS_SET_CAMERA = 1L << 0,
+ FLAGS_HOT_CAMERA = 1L << 1,
+ };
+
+ sp<Camera> mCamera;
sp<ISurface> mPreviewSurface;
sp<IMediaPlayerClient> mListener;
sp<MediaWriter> mWriter;
@@ -62,14 +68,31 @@
output_format mOutputFormat;
audio_encoder mAudioEncoder;
video_encoder mVideoEncoder;
- int mVideoWidth, mVideoHeight;
- int mFrameRate;
+ int32_t mVideoWidth, mVideoHeight;
+ int32_t mFrameRate;
+ int32_t mVideoBitRate;
+ int32_t mAudioBitRate;
+ int32_t mAudioChannels;
+ int32_t mSampleRate;
+ int32_t mInterleaveDurationUs;
+ int64_t mMaxFileSizeBytes;
+ int64_t mMaxFileDurationUs;
+
String8 mParams;
int mOutputFd;
+ int32_t mFlags;
status_t startMPEG4Recording();
status_t startAMRRecording();
- sp<MediaSource> createAMRAudioSource();
+ status_t startAACRecording();
+ sp<MediaSource> createAudioSource();
+ status_t setParameter(const String8 &key, const String8 &value);
+ status_t setParamVideoEncodingBitRate(int32_t bitRate);
+ status_t setParamAudioEncodingBitRate(int32_t bitRate);
+ status_t setParamAudioNumberOfChannels(int32_t channles);
+ status_t setParamAudioSamplingRate(int32_t sampleRate);
+ status_t setParamInterleaveDuration(int32_t durationUs);
+ status_t setParamMaxDurationOrFileSize(int64_t limit, bool limit_is_duration);
StagefrightRecorder(const StagefrightRecorder &);
StagefrightRecorder &operator=(const StagefrightRecorder &);
diff --git a/media/libstagefright/AMRWriter.cpp b/media/libstagefright/AMRWriter.cpp
index bf4424b..aec7394 100644
--- a/media/libstagefright/AMRWriter.cpp
+++ b/media/libstagefright/AMRWriter.cpp
@@ -22,6 +22,7 @@
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MetaData.h>
+#include <media/mediarecorder.h>
namespace android {
@@ -53,8 +54,6 @@
}
status_t AMRWriter::addSource(const sp<MediaSource> &source) {
- Mutex::Autolock autoLock(mLock);
-
if (mInitCheck != OK) {
return mInitCheck;
}
@@ -95,8 +94,6 @@
}
status_t AMRWriter::start() {
- Mutex::Autolock autoLock(mLock);
-
if (mInitCheck != OK) {
return mInitCheck;
}
@@ -127,16 +124,12 @@
}
void AMRWriter::stop() {
- {
- Mutex::Autolock autoLock(mLock);
-
- if (!mStarted) {
- return;
- }
-
- mDone = true;
+ if (!mStarted) {
+ return;
}
+ mDone = true;
+
void *dummy;
pthread_join(mThread, &dummy);
@@ -145,6 +138,20 @@
mStarted = false;
}
+bool AMRWriter::exceedsFileSizeLimit() {
+ if (mMaxFileSizeLimitBytes == 0) {
+ return false;
+ }
+ return mEstimatedSizeBytes >= mMaxFileSizeLimitBytes;
+}
+
+bool AMRWriter::exceedsFileDurationLimit() {
+ if (mMaxFileDurationLimitUs == 0) {
+ return false;
+ }
+ return mEstimatedDurationUs >= mMaxFileDurationLimitUs;
+}
+
// static
void *AMRWriter::ThreadWrapper(void *me) {
static_cast<AMRWriter *>(me)->threadFunc();
@@ -153,13 +160,10 @@
}
void AMRWriter::threadFunc() {
- for (;;) {
- Mutex::Autolock autoLock(mLock);
-
- if (mDone) {
- break;
- }
-
+ mEstimatedDurationUs = 0;
+ mEstimatedSizeBytes = 0;
+ bool stoppedPrematurely = true;
+ while (!mDone) {
MediaBuffer *buffer;
status_t err = mSource->read(&buffer);
@@ -167,6 +171,25 @@
break;
}
+ mEstimatedSizeBytes += buffer->range_length();
+ if (exceedsFileSizeLimit()) {
+ buffer->release();
+ buffer = NULL;
+ notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0);
+ break;
+ }
+
+ int64_t timestampUs;
+ CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs));
+ if (timestampUs > mEstimatedDurationUs) {
+ mEstimatedDurationUs = timestampUs;
+ }
+ if (exceedsFileDurationLimit()) {
+ buffer->release();
+ buffer = NULL;
+ notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_DURATION_REACHED, 0);
+ break;
+ }
ssize_t n = fwrite(
(const uint8_t *)buffer->data() + buffer->range_offset(),
1,
@@ -180,16 +203,26 @@
break;
}
+ // XXX: How to tell it is stopped prematurely?
+ if (stoppedPrematurely) {
+ stoppedPrematurely = false;
+ }
+
buffer->release();
buffer = NULL;
}
- Mutex::Autolock autoLock(mLock);
+ if (stoppedPrematurely) {
+ notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_STOP_PREMATURELY, 0);
+ }
+
+ fflush(mFile);
+ fclose(mFile);
+ mFile = NULL;
mReachedEOS = true;
}
bool AMRWriter::reachedEOS() {
- Mutex::Autolock autoLock(mLock);
return mReachedEOS;
}
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
index edabaf9..abd8abc 100644
--- a/media/libstagefright/AudioSource.cpp
+++ b/media/libstagefright/AudioSource.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+//#define LOG_NDEBUG 0
+#define LOG_TAG "AudioSource"
+#include <utils/Log.h>
+
#include <media/stagefright/AudioSource.h>
#include <media/AudioRecord.h>
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 075b1e3..cd26e6b 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-#include <sys/time.h>
+//#define LOG_NDEBUG 0
+#define LOG_TAG "CameraSource"
+#include <utils/Log.h>
#include <OMX_Component.h>
-#include <binder/IServiceManager.h>
-#include <cutils/properties.h> // for property_get
#include <media/stagefright/CameraSource.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
@@ -27,46 +27,10 @@
#include <media/stagefright/MetaData.h>
#include <camera/Camera.h>
#include <camera/CameraParameters.h>
-#include <ui/GraphicBuffer.h>
-#include <ui/Overlay.h>
-#include <surfaceflinger/ISurface.h>
#include <utils/String8.h>
namespace android {
-static int64_t getNowUs() {
- struct timeval tv;
- gettimeofday(&tv, NULL);
-
- return (int64_t)tv.tv_usec + tv.tv_sec * 1000000;
-}
-
-struct DummySurface : public BnSurface {
- DummySurface() {}
-
- virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage) {
- return NULL;
- }
-
- virtual status_t registerBuffers(const BufferHeap &buffers) {
- return OK;
- }
-
- virtual void postBuffer(ssize_t offset) {}
- virtual void unregisterBuffers() {}
-
- virtual sp<OverlayRef> createOverlay(
- uint32_t w, uint32_t h, int32_t format, int32_t orientation) {
- return NULL;
- }
-
-protected:
- virtual ~DummySurface() {}
-
- DummySurface(const DummySurface &);
- DummySurface &operator=(const DummySurface &);
-};
-
struct CameraSourceListener : public CameraListener {
CameraSourceListener(const sp<CameraSource> &source);
@@ -100,22 +64,20 @@
void CameraSourceListener::postData(int32_t msgType, const sp<IMemory> &dataPtr) {
LOGV("postData(%d, ptr:%p, size:%d)",
msgType, dataPtr->pointer(), dataPtr->size());
-
- sp<CameraSource> source = mSource.promote();
- if (source.get() != NULL) {
- source->dataCallback(msgType, dataPtr);
- }
}
void CameraSourceListener::postDataTimestamp(
nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) {
- LOGV("postDataTimestamp(%lld, %d, ptr:%p, size:%d)",
- timestamp, msgType, dataPtr->pointer(), dataPtr->size());
+
+ sp<CameraSource> source = mSource.promote();
+ if (source.get() != NULL) {
+ source->dataCallbackTimestamp(timestamp/1000, msgType, dataPtr);
+ }
}
// static
CameraSource *CameraSource::Create() {
- sp<Camera> camera = Camera::connect();
+ sp<Camera> camera = Camera::connect(0);
if (camera.get() == NULL) {
return NULL;
@@ -125,9 +87,7 @@
}
// static
-CameraSource *CameraSource::CreateFromICamera(const sp<ICamera> &icamera) {
- sp<Camera> camera = Camera::create(icamera);
-
+CameraSource *CameraSource::CreateFromCamera(const sp<Camera> &camera) {
if (camera.get() == NULL) {
return NULL;
}
@@ -140,15 +100,11 @@
mWidth(0),
mHeight(0),
mFirstFrameTimeUs(0),
- mNumFrames(0),
+ mLastFrameTimestampUs(0),
+ mNumFramesReceived(0),
+ mNumFramesEncoded(0),
+ mNumFramesDropped(0),
mStarted(false) {
- char value[PROPERTY_VALUE_MAX];
- if (property_get("ro.hardware", value, NULL) && !strcmp(value, "sholes")) {
- // The hardware encoder(s) do not support yuv420, but only YCbYCr,
- // fortunately the camera also supports this, so we needn't transcode.
- mCamera->setParameters(String8("preview-format=yuv422i-yuyv"));
- }
-
String8 s = mCamera->getParameters();
printf("params: \"%s\"\n", s.string());
@@ -162,26 +118,12 @@
}
}
-void CameraSource::setPreviewSurface(const sp<ISurface> &surface) {
- mPreviewSurface = surface;
-}
-
status_t CameraSource::start(MetaData *) {
+ LOGV("start");
CHECK(!mStarted);
mCamera->setListener(new CameraSourceListener(this));
-
- status_t err =
- mCamera->setPreviewDisplay(
- mPreviewSurface != NULL ? mPreviewSurface : new DummySurface);
- CHECK_EQ(err, OK);
-
- mCamera->setPreviewCallbackFlags(
- FRAME_CALLBACK_FLAG_ENABLE_MASK
- | FRAME_CALLBACK_FLAG_COPY_OUT_MASK);
-
- err = mCamera->startPreview();
- CHECK_EQ(err, OK);
+ CHECK_EQ(OK, mCamera->startRecording());
mStarted = true;
@@ -189,15 +131,32 @@
}
status_t CameraSource::stop() {
- CHECK(mStarted);
-
- mCamera->stopPreview();
-
+ LOGV("stop");
+ Mutex::Autolock autoLock(mLock);
mStarted = false;
+ mFrameAvailableCondition.signal();
+ mCamera->setListener(NULL);
+ mCamera->stopRecording();
+ releaseQueuedFrames();
+ LOGI("Frames received/encoded/dropped: %d/%d/%d, timestamp (us) last/first: %lld/%lld",
+ mNumFramesReceived, mNumFramesEncoded, mNumFramesDropped,
+ mLastFrameTimestampUs, mFirstFrameTimeUs);
+
+ CHECK_EQ(mNumFramesReceived, mNumFramesEncoded + mNumFramesDropped);
return OK;
}
+void CameraSource::releaseQueuedFrames() {
+ List<sp<IMemory> >::iterator it;
+ while (!mFrames.empty()) {
+ it = mFrames.begin();
+ mCamera->releaseRecordingFrame(*it);
+ mFrames.erase(it);
+ ++mNumFramesDropped;
+ }
+}
+
sp<MetaData> CameraSource::getFormat() {
sp<MetaData> meta = new MetaData;
meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW);
@@ -210,7 +169,7 @@
status_t CameraSource::read(
MediaBuffer **buffer, const ReadOptions *options) {
- CHECK(mStarted);
+ LOGV("read");
*buffer = NULL;
@@ -224,20 +183,24 @@
{
Mutex::Autolock autoLock(mLock);
- while (mFrames.empty()) {
+ while (mStarted && mFrames.empty()) {
mFrameAvailableCondition.wait(mLock);
}
-
+ if (!mStarted) {
+ return OK;
+ }
frame = *mFrames.begin();
mFrames.erase(mFrames.begin());
frameTime = *mFrameTimes.begin();
mFrameTimes.erase(mFrameTimes.begin());
+ ++mNumFramesEncoded;
}
*buffer = new MediaBuffer(frame->size());
memcpy((*buffer)->data(), frame->pointer(), frame->size());
(*buffer)->set_range(0, frame->size());
+ mCamera->releaseRecordingFrame(frame);
(*buffer)->meta_data()->clear();
(*buffer)->meta_data()->setInt64(kKeyTime, frameTime);
@@ -245,17 +208,25 @@
return OK;
}
-void CameraSource::dataCallback(int32_t msgType, const sp<IMemory> &data) {
+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);
-
- int64_t nowUs = getNowUs();
- if (mNumFrames == 0) {
- mFirstFrameTimeUs = nowUs;
+ if (!mStarted) {
+ mCamera->releaseRecordingFrame(data);
+ ++mNumFramesReceived;
+ ++mNumFramesDropped;
+ return;
}
- ++mNumFrames;
+
+ if (mNumFramesReceived == 0) {
+ mFirstFrameTimeUs = timestampUs;
+ }
+ ++mNumFramesReceived;
mFrames.push_back(data);
- mFrameTimes.push_back(nowUs - mFirstFrameTimeUs);
+ mFrameTimes.push_back(timestampUs - mFirstFrameTimeUs);
mFrameAvailableCondition.signal();
}
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 2cf0ddf..5361f92 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MPEG4Writer"
+#include <utils/Log.h>
+
#include <arpa/inet.h>
#include <ctype.h>
@@ -24,8 +28,10 @@
#include <media/stagefright/MetaData.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/Utils.h>
+#include <media/mediarecorder.h>
namespace android {
@@ -39,6 +45,7 @@
bool reachedEOS();
int64_t getDurationUs() const;
+ int64_t getEstimatedTrackSizeBytes() const;
void writeTrackHeader(int32_t trackID);
private:
@@ -47,39 +54,80 @@
sp<MediaSource> mSource;
volatile bool mDone;
int64_t mMaxTimeStampUs;
+ int64_t mEstimatedTrackSizeBytes;
pthread_t mThread;
struct SampleInfo {
size_t size;
- off_t offset;
int64_t timestamp;
};
- List<SampleInfo> mSampleInfos;
+ List<SampleInfo> mSampleInfos;
+ bool mSamplesHaveSameSize;
+
+ List<MediaBuffer *> mChunkSamples;
+ List<off_t> mChunkOffsets;
+
+ struct StscTableEntry {
+
+ StscTableEntry(uint32_t chunk, uint32_t samples, uint32_t id)
+ : firstChunk(chunk),
+ samplesPerChunk(samples),
+ sampleDescriptionId(id) {}
+
+ uint32_t firstChunk;
+ uint32_t samplesPerChunk;
+ uint32_t sampleDescriptionId;
+ };
+ List<StscTableEntry> mStscTableEntries;
+
+ List<int32_t> mStssTableEntries;
+
+ struct SttsTableEntry {
+
+ SttsTableEntry(uint32_t count, uint32_t duration)
+ : sampleCount(count), sampleDuration(duration) {}
+
+ uint32_t sampleCount;
+ uint32_t sampleDuration;
+ };
+ List<SttsTableEntry> mSttsTableEntries;
void *mCodecSpecificData;
size_t mCodecSpecificDataSize;
+ bool mGotAllCodecSpecificData;
bool mReachedEOS;
+ int64_t mStartTimestampUs;
static void *ThreadWrapper(void *me);
void threadEntry();
+ status_t makeAVCCodecSpecificData(
+ const uint8_t *data, size_t size);
+ void writeOneChunk(bool isAvc);
+
Track(const Track &);
Track &operator=(const Track &);
};
+#define USE_NALLEN_FOUR 1
+
MPEG4Writer::MPEG4Writer(const char *filename)
: mFile(fopen(filename, "wb")),
mOffset(0),
- mMdatOffset(0) {
+ mMdatOffset(0),
+ mEstimatedMoovBoxSize(0),
+ mInterleaveDurationUs(500000) {
CHECK(mFile != NULL);
}
MPEG4Writer::MPEG4Writer(int fd)
: mFile(fdopen(fd, "wb")),
mOffset(0),
- mMdatOffset(0) {
+ mMdatOffset(0),
+ mEstimatedMoovBoxSize(0),
+ mInterleaveDurationUs(500000) {
CHECK(mFile != NULL);
}
@@ -105,15 +153,34 @@
return UNKNOWN_ERROR;
}
+ mStartTimestampUs = 0;
+ mStreamableFile = true;
+ mWriteMoovBoxToMemory = false;
+ mMoovBoxBuffer = NULL;
+ mMoovBoxBufferOffset = 0;
+
beginBox("ftyp");
writeFourcc("isom");
writeInt32(0);
writeFourcc("isom");
endBox();
- mMdatOffset = mOffset;
- write("\x00\x00\x00\x01mdat????????", 16);
+ mFreeBoxOffset = mOffset;
+ if (mEstimatedMoovBoxSize == 0) {
+ // XXX: Estimate the moov box size
+ // based on max file size or duration limit
+ mEstimatedMoovBoxSize = 0x0F00;
+ }
+ CHECK(mEstimatedMoovBoxSize >= 8);
+ fseeko(mFile, mFreeBoxOffset, SEEK_SET);
+ writeInt32(mEstimatedMoovBoxSize);
+ write("free", 4);
+
+ mMdatOffset = mFreeBoxOffset + mEstimatedMoovBoxSize;
+ mOffset = mMdatOffset;
+ fseeko(mFile, mMdatOffset, SEEK_SET);
+ write("\x00\x00\x00\x01mdat????????", 16);
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
status_t err = (*it)->start();
@@ -147,14 +214,20 @@
}
}
+
// Fix up the size of the 'mdat' chunk.
- fseek(mFile, mMdatOffset + 8, SEEK_SET);
+ fseeko(mFile, mMdatOffset + 8, SEEK_SET);
int64_t size = mOffset - mMdatOffset;
size = hton64(size);
fwrite(&size, 1, 8, mFile);
- fseek(mFile, mOffset, SEEK_SET);
+ fseeko(mFile, mOffset, SEEK_SET);
time_t now = time(NULL);
+ const off_t moovOffset = mOffset;
+ mWriteMoovBoxToMemory = true;
+ mMoovBoxBuffer = (uint8_t *) malloc(mEstimatedMoovBoxSize);
+ mMoovBoxBufferOffset = 0;
+ CHECK(mMoovBoxBuffer != NULL);
beginBox("moov");
@@ -194,15 +267,48 @@
}
endBox(); // moov
+ mWriteMoovBoxToMemory = false;
+ if (mStreamableFile) {
+ CHECK(mMoovBoxBufferOffset + 8 <= mEstimatedMoovBoxSize);
+
+ // Moov box
+ fseeko(mFile, mFreeBoxOffset, SEEK_SET);
+ mOffset = mFreeBoxOffset;
+ write(mMoovBoxBuffer, 1, mMoovBoxBufferOffset, mFile);
+
+ // Free box
+ mFreeBoxOffset = mStreamableFile? mOffset: mFreeBoxOffset;
+ fseeko(mFile, mFreeBoxOffset, SEEK_SET);
+ writeInt32(mEstimatedMoovBoxSize - mMoovBoxBufferOffset);
+ write("free", 4);
+
+ // Free temp memory
+ free(mMoovBoxBuffer);
+ mMoovBoxBuffer = NULL;
+ mMoovBoxBufferOffset = 0;
+ }
+
CHECK(mBoxes.empty());
+ fflush(mFile);
fclose(mFile);
mFile = NULL;
}
-off_t MPEG4Writer::addSample(MediaBuffer *buffer) {
- Mutex::Autolock autoLock(mLock);
+status_t MPEG4Writer::setInterleaveDuration(uint32_t durationUs) {
+ mInterleaveDurationUs = durationUs;
+ return OK;
+}
+void MPEG4Writer::lock() {
+ mLock.lock();
+}
+
+void MPEG4Writer::unlock() {
+ mLock.unlock();
+}
+
+off_t MPEG4Writer::addSample_l(MediaBuffer *buffer) {
off_t old_offset = mOffset;
fwrite((const uint8_t *)buffer->data() + buffer->range_offset(),
@@ -213,31 +319,92 @@
return old_offset;
}
-off_t MPEG4Writer::addLengthPrefixedSample(MediaBuffer *buffer) {
- Mutex::Autolock autoLock(mLock);
+static void StripStartcode(MediaBuffer *buffer) {
+ if (buffer->range_length() < 4) {
+ return;
+ }
+
+ const uint8_t *ptr =
+ (const uint8_t *)buffer->data() + buffer->range_offset();
+
+ if (!memcmp(ptr, "\x00\x00\x00\x01", 4)) {
+ buffer->set_range(
+ buffer->range_offset() + 4, buffer->range_length() - 4);
+ }
+}
+
+off_t MPEG4Writer::addLengthPrefixedSample_l(MediaBuffer *buffer) {
+ StripStartcode(buffer);
off_t old_offset = mOffset;
size_t length = buffer->range_length();
+
+#if USE_NALLEN_FOUR
+ uint8_t x = length >> 24;
+ fwrite(&x, 1, 1, mFile);
+ x = (length >> 16) & 0xff;
+ fwrite(&x, 1, 1, mFile);
+ x = (length >> 8) & 0xff;
+ fwrite(&x, 1, 1, mFile);
+ x = length & 0xff;
+ fwrite(&x, 1, 1, mFile);
+#else
CHECK(length < 65536);
uint8_t x = length >> 8;
fwrite(&x, 1, 1, mFile);
x = length & 0xff;
fwrite(&x, 1, 1, mFile);
+#endif
fwrite((const uint8_t *)buffer->data() + buffer->range_offset(),
1, length, mFile);
+#if USE_NALLEN_FOUR
+ mOffset += length + 4;
+#else
mOffset += length + 2;
+#endif
return old_offset;
}
+size_t MPEG4Writer::write(
+ const void *ptr, size_t size, size_t nmemb, FILE *stream) {
+
+ const size_t bytes = size * nmemb;
+ if (mWriteMoovBoxToMemory) {
+ if (8 + mMoovBoxBufferOffset + bytes > mEstimatedMoovBoxSize) {
+ for (List<off_t>::iterator it = mBoxes.begin();
+ it != mBoxes.end(); ++it) {
+ (*it) += mOffset;
+ }
+ fseeko(mFile, mOffset, SEEK_SET);
+ fwrite(mMoovBoxBuffer, 1, mMoovBoxBufferOffset, stream);
+ fwrite(ptr, size, nmemb, stream);
+ mOffset += (bytes + mMoovBoxBufferOffset);
+ free(mMoovBoxBuffer);
+ mMoovBoxBuffer = NULL;
+ mMoovBoxBufferOffset = 0;
+ mWriteMoovBoxToMemory = false;
+ mStreamableFile = false;
+ } else {
+ memcpy(mMoovBoxBuffer + mMoovBoxBufferOffset, ptr, bytes);
+ mMoovBoxBufferOffset += bytes;
+ }
+ } else {
+ fwrite(ptr, size, nmemb, stream);
+ mOffset += bytes;
+ }
+ return bytes;
+}
+
void MPEG4Writer::beginBox(const char *fourcc) {
CHECK_EQ(strlen(fourcc), 4);
- mBoxes.push_back(mOffset);
+ mBoxes.push_back(mWriteMoovBoxToMemory?
+ mMoovBoxBufferOffset: mOffset);
writeInt32(0);
writeFourcc(fourcc);
@@ -249,51 +416,77 @@
off_t offset = *--mBoxes.end();
mBoxes.erase(--mBoxes.end());
- fseek(mFile, offset, SEEK_SET);
- writeInt32(mOffset - offset);
- mOffset -= 4;
- fseek(mFile, mOffset, SEEK_SET);
+ if (mWriteMoovBoxToMemory) {
+ int32_t x = htonl(mMoovBoxBufferOffset - offset);
+ memcpy(mMoovBoxBuffer + offset, &x, 4);
+ } else {
+ fseeko(mFile, offset, SEEK_SET);
+ writeInt32(mOffset - offset);
+ mOffset -= 4;
+ fseeko(mFile, mOffset, SEEK_SET);
+ }
}
void MPEG4Writer::writeInt8(int8_t x) {
- fwrite(&x, 1, 1, mFile);
- ++mOffset;
+ write(&x, 1, 1, mFile);
}
void MPEG4Writer::writeInt16(int16_t x) {
x = htons(x);
- fwrite(&x, 1, 2, mFile);
- mOffset += 2;
+ write(&x, 1, 2, mFile);
}
void MPEG4Writer::writeInt32(int32_t x) {
x = htonl(x);
- fwrite(&x, 1, 4, mFile);
- mOffset += 4;
+ write(&x, 1, 4, mFile);
}
void MPEG4Writer::writeInt64(int64_t x) {
x = hton64(x);
- fwrite(&x, 1, 8, mFile);
- mOffset += 8;
+ write(&x, 1, 8, mFile);
}
void MPEG4Writer::writeCString(const char *s) {
size_t n = strlen(s);
-
- fwrite(s, 1, n + 1, mFile);
- mOffset += n + 1;
+ write(s, 1, n + 1, mFile);
}
void MPEG4Writer::writeFourcc(const char *s) {
CHECK_EQ(strlen(s), 4);
- fwrite(s, 1, 4, mFile);
- mOffset += 4;
+ write(s, 1, 4, mFile);
}
void MPEG4Writer::write(const void *data, size_t size) {
- fwrite(data, 1, size, mFile);
- mOffset += size;
+ write(data, 1, size, mFile);
+}
+
+bool MPEG4Writer::exceedsFileSizeLimit() {
+ // No limit
+ if (mMaxFileSizeLimitBytes == 0) {
+ return false;
+ }
+
+ int64_t nTotalBytesEstimate = mEstimatedMoovBoxSize;
+ for (List<Track *>::iterator it = mTracks.begin();
+ it != mTracks.end(); ++it) {
+ nTotalBytesEstimate += (*it)->getEstimatedTrackSizeBytes();
+ }
+ return (nTotalBytesEstimate >= mMaxFileSizeLimitBytes);
+}
+
+bool MPEG4Writer::exceedsFileDurationLimit() {
+ // No limit
+ if (mMaxFileDurationLimitUs == 0) {
+ return false;
+ }
+
+ for (List<Track *>::iterator it = mTracks.begin();
+ it != mTracks.end(); ++it) {
+ if ((*it)->getDurationUs() >= mMaxFileDurationLimitUs) {
+ return true;
+ }
+ }
+ return false;
}
bool MPEG4Writer::reachedEOS() {
@@ -309,6 +502,21 @@
return allDone;
}
+void MPEG4Writer::setStartTimestamp(int64_t timeUs) {
+ LOGI("setStartTimestamp: %lld", timeUs);
+ Mutex::Autolock autoLock(mLock);
+ if (mStartTimestampUs != 0) {
+ return; // Sorry, too late
+ }
+ mStartTimestampUs = timeUs;
+}
+
+int64_t MPEG4Writer::getStartTimestamp() {
+ LOGI("getStartTimestamp: %lld", mStartTimestampUs);
+ Mutex::Autolock autoLock(mLock);
+ return mStartTimestampUs;
+}
+
////////////////////////////////////////////////////////////////////////////////
MPEG4Writer::Track::Track(
@@ -318,8 +526,10 @@
mSource(source),
mDone(false),
mMaxTimeStampUs(0),
+ mSamplesHaveSameSize(true),
mCodecSpecificData(NULL),
mCodecSpecificDataSize(0),
+ mGotAllCodecSpecificData(false),
mReachedEOS(false) {
}
@@ -380,73 +590,163 @@
return NULL;
}
+#include <ctype.h>
+static void hexdump(const void *_data, size_t size) {
+ const uint8_t *data = (const uint8_t *)_data;
+ size_t offset = 0;
+ while (offset < size) {
+ printf("0x%04x ", offset);
+
+ size_t n = size - offset;
+ if (n > 16) {
+ n = 16;
+ }
+
+ for (size_t i = 0; i < 16; ++i) {
+ if (i == 8) {
+ printf(" ");
+ }
+
+ if (offset + i < size) {
+ printf("%02x ", data[offset + i]);
+ } else {
+ printf(" ");
+ }
+ }
+
+ printf(" ");
+
+ for (size_t i = 0; i < n; ++i) {
+ if (isprint(data[offset + i])) {
+ printf("%c", data[offset + i]);
+ } else {
+ printf(".");
+ }
+ }
+
+ printf("\n");
+
+ offset += 16;
+ }
+}
+
+
+status_t MPEG4Writer::Track::makeAVCCodecSpecificData(
+ const uint8_t *data, size_t size) {
+ // hexdump(data, size);
+
+ if (mCodecSpecificData != NULL) {
+ LOGE("Already have codec specific data");
+ return ERROR_MALFORMED;
+ }
+
+ if (size < 4 || memcmp("\x00\x00\x00\x01", data, 4)) {
+ LOGE("Must start with a start code");
+ return ERROR_MALFORMED;
+ }
+
+ size_t picParamOffset = 4;
+ while (picParamOffset + 3 < size
+ && memcmp("\x00\x00\x00\x01", &data[picParamOffset], 4)) {
+ ++picParamOffset;
+ }
+
+ if (picParamOffset + 3 >= size) {
+ LOGE("Could not find start-code for pictureParameterSet");
+ return ERROR_MALFORMED;
+ }
+
+ size_t seqParamSetLength = picParamOffset - 4;
+ size_t picParamSetLength = size - picParamOffset - 4;
+
+ mCodecSpecificDataSize =
+ 6 + 1 + seqParamSetLength + 2 + picParamSetLength + 2;
+
+ mCodecSpecificData = malloc(mCodecSpecificDataSize);
+ uint8_t *header = (uint8_t *)mCodecSpecificData;
+ header[0] = 1;
+ header[1] = 0x42; // profile
+ header[2] = 0x80;
+ header[3] = 0x1e; // level
+
+#if USE_NALLEN_FOUR
+ header[4] = 0xfc | 3; // length size == 4 bytes
+#else
+ header[4] = 0xfc | 1; // length size == 2 bytes
+#endif
+
+ header[5] = 0xe0 | 1;
+ header[6] = seqParamSetLength >> 8;
+ header[7] = seqParamSetLength & 0xff;
+ memcpy(&header[8], &data[4], seqParamSetLength);
+ header += 8 + seqParamSetLength;
+ header[0] = 1;
+ header[1] = picParamSetLength >> 8;
+ header[2] = picParamSetLength & 0xff;
+ memcpy(&header[3], &data[picParamOffset + 4], picParamSetLength);
+
+ return OK;
+}
+
void MPEG4Writer::Track::threadEntry() {
sp<MetaData> meta = mSource->getFormat();
const char *mime;
meta->findCString(kKeyMIMEType, &mime);
- bool is_mpeg4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4);
+ bool is_mpeg4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4) ||
+ !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC);
bool is_avc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
int32_t count = 0;
+ const int64_t interleaveDurationUs = mOwner->interleaveDuration();
+ int64_t chunkTimestampUs = 0;
+ int32_t nChunks = 0;
+ int32_t nZeroLengthFrames = 0;
+ int64_t lastTimestamp = 0; // Timestamp of the previous sample
+ 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
+ mEstimatedTrackSizeBytes = 0;
MediaBuffer *buffer;
while (!mDone && mSource->read(&buffer) == OK) {
if (buffer->range_length() == 0) {
buffer->release();
buffer = NULL;
-
+ ++nZeroLengthFrames;
continue;
}
++count;
- if (is_avc && count < 3) {
- size_t size = buffer->range_length();
+ int32_t isCodecConfig;
+ if (buffer->meta_data()->findInt32(kKeyIsCodecConfig, &isCodecConfig)
+ && isCodecConfig) {
+ CHECK(!mGotAllCodecSpecificData);
- switch (count) {
- case 1:
- {
- CHECK_EQ(mCodecSpecificData, NULL);
- mCodecSpecificData = malloc(size + 8);
- uint8_t *header = (uint8_t *)mCodecSpecificData;
- header[0] = 1;
- header[1] = 0x42; // profile
- header[2] = 0x80;
- header[3] = 0x1e; // level
- header[4] = 0xfc | 3;
- header[5] = 0xe0 | 1;
- header[6] = size >> 8;
- header[7] = size & 0xff;
- memcpy(&header[8],
- (const uint8_t *)buffer->data() + buffer->range_offset(),
- size);
-
- mCodecSpecificDataSize = size + 8;
- break;
- }
-
- case 2:
- {
- size_t offset = mCodecSpecificDataSize;
- mCodecSpecificDataSize += size + 3;
- mCodecSpecificData = realloc(mCodecSpecificData, mCodecSpecificDataSize);
- uint8_t *header = (uint8_t *)mCodecSpecificData;
- header[offset] = 1;
- header[offset + 1] = size >> 8;
- header[offset + 2] = size & 0xff;
- memcpy(&header[offset + 3],
- (const uint8_t *)buffer->data() + buffer->range_offset(),
- size);
- break;
- }
+ if (is_avc) {
+ status_t err = makeAVCCodecSpecificData(
+ (const uint8_t *)buffer->data()
+ + buffer->range_offset(),
+ buffer->range_length());
+ CHECK_EQ(OK, err);
+ } else if (is_mpeg4) {
+ mCodecSpecificDataSize = buffer->range_length();
+ mCodecSpecificData = malloc(mCodecSpecificDataSize);
+ memcpy(mCodecSpecificData,
+ (const uint8_t *)buffer->data()
+ + buffer->range_offset(),
+ buffer->range_length());
}
buffer->release();
buffer = NULL;
+ mGotAllCodecSpecificData = true;
continue;
- }
+ } else if (!mGotAllCodecSpecificData &&
+ count == 1 && is_mpeg4 && mCodecSpecificData == NULL) {
+ // The TI mpeg4 encoder does not properly set the
+ // codec-specific-data flag.
- if (mCodecSpecificData == NULL && is_mpeg4) {
const uint8_t *data =
(const uint8_t *)buffer->data() + buffer->range_offset();
@@ -474,17 +774,89 @@
memcpy(mCodecSpecificData, data, offset);
buffer->set_range(buffer->range_offset() + offset, size - offset);
+
+ if (size == offset) {
+ buffer->release();
+ buffer = NULL;
+
+ continue;
+ }
+
+ mGotAllCodecSpecificData = true;
+ } else if (!mGotAllCodecSpecificData && is_avc && count < 3) {
+ // The TI video encoder does not flag codec specific data
+ // as such and also splits up SPS and PPS across two buffers.
+
+ const uint8_t *data =
+ (const uint8_t *)buffer->data() + buffer->range_offset();
+
+ size_t size = buffer->range_length();
+
+ CHECK(count == 2 || mCodecSpecificData == NULL);
+
+ size_t offset = mCodecSpecificDataSize;
+ mCodecSpecificDataSize += size + 4;
+ mCodecSpecificData =
+ realloc(mCodecSpecificData, mCodecSpecificDataSize);
+
+ memcpy((uint8_t *)mCodecSpecificData + offset,
+ "\x00\x00\x00\x01", 4);
+
+ memcpy((uint8_t *)mCodecSpecificData + offset + 4, data, size);
+
+ buffer->release();
+ buffer = NULL;
+
+ if (count == 2) {
+ void *tmp = mCodecSpecificData;
+ size = mCodecSpecificDataSize;
+ mCodecSpecificData = NULL;
+ mCodecSpecificDataSize = 0;
+
+ status_t err = makeAVCCodecSpecificData(
+ (const uint8_t *)tmp, size);
+ free(tmp);
+ tmp = NULL;
+ CHECK_EQ(OK, err);
+
+ mGotAllCodecSpecificData = true;
+ }
+
+ continue;
}
- off_t offset = is_avc ? mOwner->addLengthPrefixedSample(buffer)
- : mOwner->addSample(buffer);
-
SampleInfo info;
- info.size = is_avc ? buffer->range_length() + 2 : buffer->range_length();
- info.offset = offset;
+ info.size = is_avc
+#if USE_NALLEN_FOUR
+ ? buffer->range_length() + 4
+#else
+ ? buffer->range_length() + 2
+#endif
+ : buffer->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;
+ }
+
+ bool is_audio = !strncasecmp(mime, "audio/", 6);
int64_t timestampUs;
CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs));
+ if (mSampleInfos.empty()) {
+ mOwner->setStartTimestamp(timestampUs);
+ mStartTimestampUs = (timestampUs - mOwner->getStartTimestamp());
+ }
if (timestampUs > mMaxTimeStampUs) {
mMaxTimeStampUs = timestampUs;
@@ -492,20 +864,121 @@
// Our timestamp is in ms.
info.timestamp = (timestampUs + 500) / 1000;
-
mSampleInfos.push_back(info);
+ if (mSampleInfos.size() > 2) {
+ if (lastDuration != info.timestamp - lastTimestamp) {
+ SttsTableEntry sttsEntry(sampleCount, lastDuration);
+ mSttsTableEntries.push_back(sttsEntry);
+ sampleCount = 1;
+ } else {
+ ++sampleCount;
+ }
+ }
+ if (mSamplesHaveSameSize) {
+ if (mSampleInfos.size() >= 2 && previousSampleSize != info.size) {
+ mSamplesHaveSameSize = false;
+ }
+ previousSampleSize = info.size;
+ }
+ 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());
+
+ mChunkSamples.push_back(copy);
+ if (interleaveDurationUs == 0) {
+ StscTableEntry stscEntry(++nChunks, 1, 1);
+ mStscTableEntries.push_back(stscEntry);
+ writeOneChunk(is_avc);
+ } else {
+ if (chunkTimestampUs == 0) {
+ chunkTimestampUs = timestampUs;
+ } else {
+ if (timestampUs - chunkTimestampUs > interleaveDurationUs) {
+ ++nChunks;
+ if (nChunks == 1 || // First chunk
+ (--(mStscTableEntries.end()))->samplesPerChunk !=
+ mChunkSamples.size()) {
+ StscTableEntry stscEntry(nChunks,
+ mChunkSamples.size(), 1);
+ mStscTableEntries.push_back(stscEntry);
+ }
+ writeOneChunk(is_avc);
+ chunkTimestampUs = timestampUs;
+ }
+ }
+ }
+
+ 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()) {
+ mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_STOP_PREMATURELY, 0);
+ }
+
+ // Last chunk
+ if (!mChunkSamples.empty()) {
+ ++nChunks;
+ StscTableEntry stscEntry(nChunks, mChunkSamples.size(), 1);
+ mStscTableEntries.push_back(stscEntry);
+ writeOneChunk(is_avc);
+ }
+
+ // We don't really know how long the last frame lasts, since
+ // there is no frame time after it, just repeat the previous
+ // frame's duration.
+ if (mSampleInfos.size() == 1) {
+ lastDuration = 0; // A single sample's duration
+ } else {
+ ++sampleCount; // Count for the last sample
+ }
+ SttsTableEntry sttsEntry(sampleCount, lastDuration);
+ mSttsTableEntries.push_back(sttsEntry);
mReachedEOS = true;
+ LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames",
+ count, nZeroLengthFrames, mSampleInfos.size());
+}
+
+void MPEG4Writer::Track::writeOneChunk(bool isAvc) {
+ mOwner->lock();
+ for (List<MediaBuffer *>::iterator it = mChunkSamples.begin();
+ it != mChunkSamples.end(); ++it) {
+ off_t offset = isAvc? mOwner->addLengthPrefixedSample_l(*it)
+ : mOwner->addSample_l(*it);
+ if (it == mChunkSamples.begin()) {
+ mChunkOffsets.push_back(offset);
+ }
+ }
+ mOwner->unlock();
+ while (!mChunkSamples.empty()) {
+ List<MediaBuffer *>::iterator it = mChunkSamples.begin();
+ (*it)->release();
+ (*it) = NULL;
+ mChunkSamples.erase(it);
+ }
+ mChunkSamples.clear();
}
int64_t MPEG4Writer::Track::getDurationUs() const {
return mMaxTimeStampUs;
}
+int64_t MPEG4Writer::Track::getEstimatedTrackSizeBytes() const {
+ return mEstimatedTrackSizeBytes;
+}
+
void MPEG4Writer::Track::writeTrackHeader(int32_t trackID) {
const char *mime;
bool success = mMeta->findCString(kKeyMIMEType, &mime);
@@ -550,11 +1023,24 @@
success = success && mMeta->findInt32(kKeyHeight, &height);
CHECK(success);
- mOwner->writeInt32(width);
- mOwner->writeInt32(height);
+ mOwner->writeInt32(width << 16); // 32-bit fixed-point value
+ mOwner->writeInt32(height << 16); // 32-bit fixed-point value
}
mOwner->endBox(); // tkhd
+ if (mStartTimestampUs != 0) {
+ mOwner->beginBox("edts");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->beginBox("elst");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(1); // a single entry
+ mOwner->writeInt32(mStartTimestampUs / 1000); // edit duration
+ mOwner->writeInt32(0); // edit media starting time
+ mOwner->writeInt32(1); // x1 rate
+ mOwner->endBox();
+ mOwner->endBox();
+ }
+
mOwner->beginBox("mdia");
mOwner->beginBox("mdhd");
@@ -569,26 +1055,15 @@
mOwner->beginBox("hdlr");
mOwner->writeInt32(0); // version=0, flags=0
- mOwner->writeInt32(0); // predefined
- mOwner->writeFourcc(is_audio ? "soun" : "vide");
+ mOwner->writeInt32(0); // component type: should be mhlr
+ mOwner->writeFourcc(is_audio ? "soun" : "vide"); // component subtype
mOwner->writeInt32(0); // reserved
mOwner->writeInt32(0); // reserved
mOwner->writeInt32(0); // reserved
- mOwner->writeCString(""); // name
+ mOwner->writeCString("SoundHandler"); // name
mOwner->endBox();
mOwner->beginBox("minf");
-
- mOwner->beginBox("dinf");
- mOwner->beginBox("dref");
- mOwner->writeInt32(0); // version=0, flags=0
- mOwner->writeInt32(1);
- mOwner->beginBox("url ");
- mOwner->writeInt32(1); // version=0, flags=1
- mOwner->endBox(); // url
- mOwner->endBox(); // dref
- mOwner->endBox(); // dinf
-
if (is_audio) {
mOwner->beginBox("smhd");
mOwner->writeInt32(0); // version=0, flags=0
@@ -604,7 +1079,18 @@
mOwner->writeInt16(0);
mOwner->endBox();
}
- mOwner->endBox(); // minf
+
+ mOwner->beginBox("dinf");
+ mOwner->beginBox("dref");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(1);
+ mOwner->beginBox("url ");
+ mOwner->writeInt32(1); // version=0, flags=1
+ mOwner->endBox(); // url
+ mOwner->endBox(); // dref
+ mOwner->endBox(); // dinf
+
+ mOwner->endBox(); // minf
mOwner->beginBox("stbl");
@@ -617,6 +1103,8 @@
fourcc = "samr";
} else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_WB, mime)) {
fourcc = "sawb";
+ } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AAC, mime)) {
+ fourcc = "mp4a";
} else {
LOGE("Unknown mime type '%s'.", mime);
CHECK(!"should not be here, unknown mime type.");
@@ -625,10 +1113,12 @@
mOwner->beginBox(fourcc); // audio format
mOwner->writeInt32(0); // reserved
mOwner->writeInt16(0); // reserved
- mOwner->writeInt16(0); // data ref index
+ mOwner->writeInt16(0x1); // data ref index
mOwner->writeInt32(0); // reserved
mOwner->writeInt32(0); // reserved
- mOwner->writeInt16(2); // channel count
+ int32_t nChannels;
+ CHECK_EQ(true, mMeta->findInt32(kKeyChannelCount, &nChannels));
+ mOwner->writeInt16(nChannels); // channel count
mOwner->writeInt16(16); // sample size
mOwner->writeInt16(0); // predefined
mOwner->writeInt16(0); // reserved
@@ -638,6 +1128,38 @@
CHECK(success);
mOwner->writeInt32(samplerate << 16);
+ if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AAC, mime)) {
+ mOwner->beginBox("esds");
+
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt8(0x03); // ES_DescrTag
+ mOwner->writeInt8(23 + mCodecSpecificDataSize);
+ mOwner->writeInt16(0x0000);// ES_ID
+ mOwner->writeInt8(0x00);
+
+ mOwner->writeInt8(0x04); // DecoderConfigDescrTag
+ mOwner->writeInt8(15 + mCodecSpecificDataSize);
+ mOwner->writeInt8(0x40); // objectTypeIndication ISO/IEC 14492-2
+ mOwner->writeInt8(0x15); // streamType AudioStream
+
+ mOwner->writeInt16(0x03); // XXX
+ mOwner->writeInt8(0x00); // buffer size 24-bit
+ mOwner->writeInt32(96000); // max bit rate
+ mOwner->writeInt32(96000); // avg bit rate
+
+ mOwner->writeInt8(0x05); // DecoderSpecificInfoTag
+ mOwner->writeInt8(mCodecSpecificDataSize);
+ mOwner->write(mCodecSpecificData, mCodecSpecificDataSize);
+
+ static const uint8_t kData2[] = {
+ 0x06, // SLConfigDescriptorTag
+ 0x01,
+ 0x02
+ };
+ mOwner->write(kData2, sizeof(kData2));
+
+ mOwner->endBox(); // esds
+ }
mOwner->endBox();
} else {
if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime)) {
@@ -698,7 +1220,7 @@
0x00, 0x03, 0xe8, 0x00
};
mOwner->write(kData, sizeof(kData));
-
+
mOwner->writeInt8(0x05); // DecoderSpecificInfoTag
mOwner->writeInt8(mCodecSpecificDataSize);
@@ -733,49 +1255,59 @@
mOwner->beginBox("stts");
mOwner->writeInt32(0); // version=0, flags=0
- mOwner->writeInt32(mSampleInfos.size() - 1);
-
- List<SampleInfo>::iterator it = mSampleInfos.begin();
- int64_t last = (*it).timestamp;
- ++it;
- while (it != mSampleInfos.end()) {
- mOwner->writeInt32(1);
- mOwner->writeInt32((*it).timestamp - last);
-
- last = (*it).timestamp;
-
- ++it;
+ mOwner->writeInt32(mSttsTableEntries.size());
+ for (List<SttsTableEntry>::iterator it = mSttsTableEntries.begin();
+ it != mSttsTableEntries.end(); ++it) {
+ mOwner->writeInt32(it->sampleCount);
+ mOwner->writeInt32(it->sampleDuration);
}
mOwner->endBox(); // stts
+ if (!is_audio) {
+ mOwner->beginBox("stss");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(mStssTableEntries.size()); // number of sync frames
+ for (List<int32_t>::iterator it = mStssTableEntries.begin();
+ it != mStssTableEntries.end(); ++it) {
+ mOwner->writeInt32(*it);
+ }
+ mOwner->endBox(); // stss
+ }
+
mOwner->beginBox("stsz");
mOwner->writeInt32(0); // version=0, flags=0
- mOwner->writeInt32(0); // default sample size
+ if (mSamplesHaveSameSize) {
+ List<SampleInfo>::iterator it = mSampleInfos.begin();
+ mOwner->writeInt32(it->size); // default sample size
+ } else {
+ mOwner->writeInt32(0);
+ }
mOwner->writeInt32(mSampleInfos.size());
- for (List<SampleInfo>::iterator it = mSampleInfos.begin();
- it != mSampleInfos.end(); ++it) {
- mOwner->writeInt32((*it).size);
+ if (!mSamplesHaveSameSize) {
+ for (List<SampleInfo>::iterator it = mSampleInfos.begin();
+ it != mSampleInfos.end(); ++it) {
+ mOwner->writeInt32((*it).size);
+ }
}
mOwner->endBox(); // stsz
mOwner->beginBox("stsc");
mOwner->writeInt32(0); // version=0, flags=0
- mOwner->writeInt32(mSampleInfos.size());
- int32_t n = 1;
- for (List<SampleInfo>::iterator it = mSampleInfos.begin();
- it != mSampleInfos.end(); ++it, ++n) {
- mOwner->writeInt32(n);
- mOwner->writeInt32(1);
- mOwner->writeInt32(1);
+ mOwner->writeInt32(mStscTableEntries.size());
+ for (List<StscTableEntry>::iterator it = mStscTableEntries.begin();
+ it != mStscTableEntries.end(); ++it) {
+ mOwner->writeInt32(it->firstChunk);
+ mOwner->writeInt32(it->samplesPerChunk);
+ mOwner->writeInt32(it->sampleDescriptionId);
}
mOwner->endBox(); // stsc
mOwner->beginBox("co64");
mOwner->writeInt32(0); // version=0, flags=0
- mOwner->writeInt32(mSampleInfos.size());
- for (List<SampleInfo>::iterator it = mSampleInfos.begin();
- it != mSampleInfos.end(); ++it, ++n) {
- mOwner->writeInt64((*it).offset);
+ mOwner->writeInt32(mChunkOffsets.size());
+ for (List<off_t>::iterator it = mChunkOffsets.begin();
+ it != mChunkOffsets.end(); ++it) {
+ mOwner->writeInt64((*it));
}
mOwner->endBox(); // co64
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 69da7ef..ce4dd32 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -141,6 +141,7 @@
{ MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.video.encoder.h263" },
{ MEDIA_MIMETYPE_VIDEO_H263, "OMX.TI.Video.encoder" },
{ MEDIA_MIMETYPE_VIDEO_H263, "OMX.PV.h263enc" },
+ { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.video.encoder.avc" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.Video.encoder" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.PV.avcenc" },
};
@@ -523,6 +524,7 @@
setAACFormat(numChannels, sampleRate);
}
+
if (!strncasecmp(mMIME, "video/", 6)) {
int32_t width, height;
bool success = meta->findInt32(kKeyWidth, &width);
@@ -570,7 +572,8 @@
}
if (!strcmp(mComponentName, "OMX.TI.AMR.encode")
- || !strcmp(mComponentName, "OMX.TI.WBAMR.encode")) {
+ || !strcmp(mComponentName, "OMX.TI.WBAMR.encode")
+ || !strcmp(mComponentName, "OMX.TI.AAC.encode")) {
setMinBufferSize(kPortIndexOutput, 8192); // XXX
}
@@ -683,6 +686,7 @@
case OMX_COLOR_FormatCbYCrY:
return width * height * 2;
+ case OMX_COLOR_FormatYUV420Planar:
case OMX_COLOR_FormatYUV420SemiPlanar:
return (width * height * 3) / 2;
@@ -713,21 +717,46 @@
colorFormat = OMX_COLOR_FormatYCbYCr;
}
+
+
+ status_t err;
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
+
+ //////////////////////// Input port /////////////////////////
CHECK_EQ(setVideoPortFormatType(
kPortIndexInput, OMX_VIDEO_CodingUnused,
colorFormat), OK);
+ InitOMXParams(&def);
+ def.nPortIndex = kPortIndexInput;
+ err = mOMX->getParameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ CHECK_EQ(err, OK);
+
+ def.nBufferSize = getFrameSize(colorFormat, width, height);
+
+ CHECK_EQ(def.eDomain, OMX_PortDomainVideo);
+
+ video_def->nFrameWidth = width;
+ video_def->nFrameHeight = height;
+ 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);
+
+ //////////////////////// Output port /////////////////////////
CHECK_EQ(setVideoPortFormatType(
kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused),
OK);
-
- OMX_PARAM_PORTDEFINITIONTYPE def;
InitOMXParams(&def);
def.nPortIndex = kPortIndexOutput;
- OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
-
- status_t err = mOMX->getParameter(
+ err = mOMX->getParameter(
mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
CHECK_EQ(err, OK);
@@ -743,31 +772,7 @@
mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
CHECK_EQ(err, OK);
- ////////////////////////////////////////////////////////////////////////////
-
- InitOMXParams(&def);
- def.nPortIndex = kPortIndexInput;
-
- err = mOMX->getParameter(
- mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
- CHECK_EQ(err, OK);
-
- def.nBufferSize = getFrameSize(colorFormat, width, height);
- CODEC_LOGV("Setting nBufferSize = %ld", def.nBufferSize);
-
- CHECK_EQ(def.eDomain, OMX_PortDomainVideo);
-
- video_def->nFrameWidth = width;
- video_def->nFrameHeight = height;
- video_def->eCompressionFormat = OMX_VIDEO_CodingUnused;
- video_def->eColorFormat = colorFormat;
-
- video_def->xFramerate = 24 << 16; // XXX crucial!
-
- err = mOMX->setParameter(
- mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
- CHECK_EQ(err, OK);
-
+ /////////////////// Codec-specific ////////////////////////
switch (compressionFormat) {
case OMX_VIDEO_CodingMPEG4:
{
@@ -915,7 +920,7 @@
CHECK_EQ(err, OK);
bitrateType.eControlRate = OMX_Video_ControlRateVariable;
- bitrateType.nTargetBitrate = 1000000;
+ bitrateType.nTargetBitrate = 3000000;
err = mOMX->setParameter(
mNode, OMX_IndexParamVideoBitrate,
@@ -2128,11 +2133,24 @@
void OMXCodec::setRawAudioFormat(
OMX_U32 portIndex, int32_t sampleRate, int32_t numChannels) {
+
+ // port definition
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ InitOMXParams(&def);
+ def.nPortIndex = portIndex;
+ status_t err = mOMX->getParameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ CHECK_EQ(err, OK);
+ def.format.audio.eEncoding = OMX_AUDIO_CodingPCM;
+ CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamPortDefinition,
+ &def, sizeof(def)), OK);
+
+ // pcm param
OMX_AUDIO_PARAM_PCMMODETYPE pcmParams;
InitOMXParams(&pcmParams);
pcmParams.nPortIndex = portIndex;
- status_t err = mOMX->getParameter(
+ err = mOMX->getParameter(
mNode, OMX_IndexParamAudioPcm, &pcmParams, sizeof(pcmParams));
CHECK_EQ(err, OK);
@@ -2172,6 +2190,8 @@
CHECK_EQ(err, OK);
def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF;
+
+ // XXX: Select bandmode based on bit rate
def.eAMRBandMode =
isWAMR ? OMX_AUDIO_AMRBandModeWB0 : OMX_AUDIO_AMRBandModeNB0;
@@ -2192,8 +2212,60 @@
}
void OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate) {
+ CHECK(numChannels == 1 || numChannels == 2);
if (mIsEncoder) {
+ //////////////// input port ////////////////////
setRawAudioFormat(kPortIndexInput, sampleRate, numChannels);
+
+ //////////////// output port ////////////////////
+ // format
+ OMX_AUDIO_PARAM_PORTFORMATTYPE format;
+ format.nPortIndex = kPortIndexOutput;
+ format.nIndex = 0;
+ status_t err = OMX_ErrorNone;
+ while (OMX_ErrorNone == err) {
+ CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamAudioPortFormat,
+ &format, sizeof(format)), OK);
+ if (format.eEncoding == OMX_AUDIO_CodingAAC) {
+ break;
+ }
+ format.nIndex++;
+ }
+ CHECK_EQ(OK, err);
+ CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamAudioPortFormat,
+ &format, sizeof(format)), OK);
+
+ // port definition
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ InitOMXParams(&def);
+ def.nPortIndex = kPortIndexOutput;
+ CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamPortDefinition,
+ &def, sizeof(def)), OK);
+ def.format.audio.bFlagErrorConcealment = OMX_TRUE;
+ def.format.audio.eEncoding = OMX_AUDIO_CodingAAC;
+ CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamPortDefinition,
+ &def, sizeof(def)), OK);
+
+ // profile
+ OMX_AUDIO_PARAM_AACPROFILETYPE profile;
+ InitOMXParams(&profile);
+ profile.nPortIndex = kPortIndexOutput;
+ CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamAudioAac,
+ &profile, sizeof(profile)), OK);
+ profile.nChannels = numChannels;
+ profile.eChannelMode = (numChannels == 1?
+ OMX_AUDIO_ChannelModeMono: OMX_AUDIO_ChannelModeStereo);
+ profile.nSampleRate = sampleRate;
+ profile.nBitRate = 96000; // XXX
+ profile.nAudioBandWidth = 0;
+ profile.nFrameLength = 0;
+ profile.nAACtools = OMX_AUDIO_AACToolAll;
+ profile.nAACERtools = OMX_AUDIO_AACERNone;
+ profile.eAACProfile = OMX_AUDIO_AACObjectLC;
+ profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4FF;
+ CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamAudioAac,
+ &profile, sizeof(profile)), OK);
+
} else {
OMX_AUDIO_PARAM_AACPROFILETYPE profile;
InitOMXParams(&profile);
@@ -2962,6 +3034,11 @@
} else if (audio_def->eEncoding == OMX_AUDIO_CodingAAC) {
mOutputFormat->setCString(
kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
+ int32_t numChannels, sampleRate;
+ inputFormat->findInt32(kKeyChannelCount, &numChannels);
+ inputFormat->findInt32(kKeySampleRate, &sampleRate);
+ mOutputFormat->setInt32(kKeyChannelCount, numChannels);
+ mOutputFormat->setInt32(kKeySampleRate, sampleRate);
} else {
CHECK(!"Should not be here. Unknown audio encoding.");
}
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index c1a010c..5db516e 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -264,6 +264,8 @@
return UNKNOWN_ERROR;
}
+ CHECK_EQ(header->pAppPrivate, buffer_meta);
+
*buffer = header;
addActiveBuffer(portIndex, *buffer);
@@ -294,6 +296,8 @@
return UNKNOWN_ERROR;
}
+ CHECK_EQ(header->pAppPrivate, buffer_meta);
+
*buffer = header;
*buffer_data = header->pBuffer;
@@ -325,6 +329,8 @@
return UNKNOWN_ERROR;
}
+ CHECK_EQ(header->pAppPrivate, buffer_meta);
+
*buffer = header;
addActiveBuffer(portIndex, *buffer);