merge in jb-mr2-release history after reset to jb-mr2-dev
diff --git a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
index c34690b..00f6de3 100644
--- a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
+++ b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
@@ -343,7 +343,31 @@
Mutex::Autolock lock(mLock);
ALOGD("MockDrmPlugin::setPropertyString(name=%s, value=%s)",
name.string(), value.string());
- mStringProperties.add(name, value);
+
+ if (name == "mock-send-event") {
+ unsigned code, extra;
+ sscanf(value.string(), "%d %d", &code, &extra);
+ DrmPlugin::EventType eventType = (DrmPlugin::EventType)code;
+
+ Vector<uint8_t> const *pSessionId = NULL;
+ ssize_t index = mByteArrayProperties.indexOfKey(String8("mock-event-session-id"));
+ if (index >= 0) {
+ pSessionId = &mByteArrayProperties[index];
+ }
+
+ Vector<uint8_t> const *pData = NULL;
+ index = mByteArrayProperties.indexOfKey(String8("mock-event-data"));
+ if (index >= 0) {
+ pData = &mByteArrayProperties[index];
+ }
+ ALOGD("sending event from mock drm plugin: %d %d %s %s",
+ (int)code, extra, pSessionId ? vectorToString(*pSessionId) : "{}",
+ pData ? vectorToString(*pData) : "{}");
+
+ sendEvent(eventType, extra, pSessionId, pData);
+ } else {
+ mStringProperties.add(name, value);
+ }
return OK;
}
diff --git a/include/media/IDrm.h b/include/media/IDrm.h
index 15d0a75..d630c40 100644
--- a/include/media/IDrm.h
+++ b/include/media/IDrm.h
@@ -17,6 +17,7 @@
#include <binder/IInterface.h>
#include <media/stagefright/foundation/ABase.h>
#include <media/drm/DrmAPI.h>
+#include <media/IDrmClient.h>
#ifndef ANDROID_IDRM_H_
@@ -106,6 +107,8 @@
Vector<uint8_t> const &signature,
bool &match) = 0;
+ virtual status_t setListener(const sp<IDrmClient>& listener) = 0;
+
private:
DISALLOW_EVIL_CONSTRUCTORS(IDrm);
};
diff --git a/include/media/IDrmClient.h b/include/media/IDrmClient.h
new file mode 100644
index 0000000..3b2fc7c
--- /dev/null
+++ b/include/media/IDrmClient.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_IDRMCLIENT_H
+#define ANDROID_IDRMCLIENT_H
+
+#include <utils/RefBase.h>
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+#include <media/drm/DrmAPI.h>
+
+namespace android {
+
+class IDrmClient: public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(DrmClient);
+
+ virtual void notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj) = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnDrmClient: public BnInterface<IDrmClient>
+{
+public:
+ virtual status_t onTransact(uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_IDRMCLIENT_H
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index 1ada9c3..fbe71ad 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -20,6 +20,7 @@
IAudioRecord.cpp \
ICrypto.cpp \
IDrm.cpp \
+ IDrmClient.cpp \
IHDCP.cpp \
AudioRecord.cpp \
AudioSystem.cpp \
diff --git a/media/libmedia/IDrm.cpp b/media/libmedia/IDrm.cpp
index 1641b56..1578846 100644
--- a/media/libmedia/IDrm.cpp
+++ b/media/libmedia/IDrm.cpp
@@ -51,7 +51,8 @@
ENCRYPT,
DECRYPT,
SIGN,
- VERIFY
+ VERIFY,
+ SET_LISTENER
};
struct BpDrm : public BpInterface<IDrm> {
@@ -384,6 +385,14 @@
return reply.readInt32();
}
+ virtual status_t setListener(const sp<IDrmClient>& listener) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+ data.writeStrongBinder(listener->asBinder());
+ remote()->transact(SET_LISTENER, data, &reply);
+ return reply.readInt32();
+ }
+
private:
void readVector(Parcel &reply, Vector<uint8_t> &vector) const {
uint32_t size = reply.readInt32();
@@ -712,6 +721,14 @@
return OK;
}
+ case SET_LISTENER: {
+ CHECK_INTERFACE(IDrm, data, reply);
+ sp<IDrmClient> listener =
+ interface_cast<IDrmClient>(data.readStrongBinder());
+ reply->writeInt32(setListener(listener));
+ return NO_ERROR;
+ } break;
+
default:
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/media/libmedia/IDrmClient.cpp b/media/libmedia/IDrmClient.cpp
new file mode 100644
index 0000000..f50715e
--- /dev/null
+++ b/media/libmedia/IDrmClient.cpp
@@ -0,0 +1,81 @@
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "IDrmClient"
+#include <utils/Log.h>
+
+#include <utils/RefBase.h>
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+
+#include <media/IMediaPlayerClient.h>
+#include <media/IDrmClient.h>
+
+namespace android {
+
+enum {
+ NOTIFY = IBinder::FIRST_CALL_TRANSACTION,
+};
+
+class BpDrmClient: public BpInterface<IDrmClient>
+{
+public:
+ BpDrmClient(const sp<IBinder>& impl)
+ : BpInterface<IDrmClient>(impl)
+ {
+ }
+
+ virtual void notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj)
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(IDrmClient::getInterfaceDescriptor());
+ data.writeInt32((int)eventType);
+ data.writeInt32(extra);
+ if (obj && obj->dataSize() > 0) {
+ data.appendFrom(const_cast<Parcel *>(obj), 0, obj->dataSize());
+ }
+ remote()->transact(NOTIFY, data, &reply, IBinder::FLAG_ONEWAY);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(DrmClient, "android.media.IDrmClient");
+
+// ----------------------------------------------------------------------
+
+status_t BnDrmClient::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+ switch (code) {
+ case NOTIFY: {
+ CHECK_INTERFACE(IDrmClient, data, reply);
+ int eventType = data.readInt32();
+ int extra = data.readInt32();
+ Parcel obj;
+ if (data.dataAvail() > 0) {
+ obj.appendFrom(const_cast<Parcel *>(&data), data.dataPosition(), data.dataAvail());
+ }
+
+ notify((DrmPlugin::EventType)eventType, extra, &obj);
+ return NO_ERROR;
+ } break;
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+}; // namespace android
diff --git a/media/libmediaplayerservice/Drm.cpp b/media/libmediaplayerservice/Drm.cpp
index 5fdb9f4..1e6cd94 100644
--- a/media/libmediaplayerservice/Drm.cpp
+++ b/media/libmediaplayerservice/Drm.cpp
@@ -47,6 +47,7 @@
Drm::Drm()
: mInitCheck(NO_INIT),
+ mListener(NULL),
mFactory(NULL),
mPlugin(NULL) {
}
@@ -67,6 +68,41 @@
return mInitCheck;
}
+status_t Drm::setListener(const sp<IDrmClient>& listener)
+{
+ Mutex::Autolock lock(mEventLock);
+ mListener = listener;
+ return NO_ERROR;
+}
+
+void Drm::sendEvent(DrmPlugin::EventType eventType, int extra,
+ Vector<uint8_t> const *sessionId,
+ Vector<uint8_t> const *data)
+{
+ mEventLock.lock();
+ sp<IDrmClient> listener = mListener;
+ mEventLock.unlock();
+
+ if (listener != NULL) {
+ Parcel obj;
+ if (sessionId && sessionId->size()) {
+ obj.writeInt32(sessionId->size());
+ obj.write(sessionId->array(), sessionId->size());
+ } else {
+ obj.writeInt32(0);
+ }
+
+ if (data && data->size()) {
+ obj.writeInt32(data->size());
+ obj.write(data->array(), data->size());
+ } else {
+ obj.writeInt32(0);
+ }
+
+ Mutex::Autolock lock(mNotifyLock);
+ listener->notify(eventType, extra, &obj);
+ }
+}
/*
* Search the plugins directory for a plugin that supports the scheme
@@ -195,7 +231,9 @@
return mInitCheck;
}
- return mFactory->createDrmPlugin(uuid, &mPlugin);
+ status_t result = mFactory->createDrmPlugin(uuid, &mPlugin);
+ mPlugin->setListener(this);
+ return result;
}
status_t Drm::destroyPlugin() {
diff --git a/media/libmediaplayerservice/Drm.h b/media/libmediaplayerservice/Drm.h
index f24921e..3da8ad4 100644
--- a/media/libmediaplayerservice/Drm.h
+++ b/media/libmediaplayerservice/Drm.h
@@ -21,6 +21,7 @@
#include "SharedLibrary.h"
#include <media/IDrm.h>
+#include <media/IDrmClient.h>
#include <utils/threads.h>
namespace android {
@@ -28,7 +29,7 @@
struct DrmFactory;
struct DrmPlugin;
-struct Drm : public BnDrm {
+struct Drm : public BnDrm, public DrmPluginListener {
Drm();
virtual ~Drm();
@@ -108,10 +109,21 @@
Vector<uint8_t> const &signature,
bool &match);
+ virtual status_t setListener(const sp<IDrmClient>& listener);
+
+ virtual void sendEvent(DrmPlugin::EventType eventType, int extra,
+ Vector<uint8_t> const *sessionId,
+ Vector<uint8_t> const *data);
+
private:
mutable Mutex mLock;
status_t mInitCheck;
+
+ sp<IDrmClient> mListener;
+ mutable Mutex mEventLock;
+ mutable Mutex mNotifyLock;
+
sp<SharedLibrary> mLibrary;
DrmFactory *mFactory;
DrmPlugin *mPlugin;
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index c9f8741..ff72b71 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -3416,6 +3416,21 @@
return true;
}
+ case kWhatResume:
+ {
+ // We'll be active soon enough.
+ return true;
+ }
+
+ case kWhatFlush:
+ {
+ // We haven't even started yet, so we're flushed alright...
+ sp<AMessage> notify = mCodec->mNotify->dup();
+ notify->setInt32("what", ACodec::kWhatFlushCompleted);
+ notify->post();
+ return true;
+ }
+
default:
return BaseState::onMessageReceived(msg);
}
diff --git a/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp b/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp
index 7719435..5749733 100644
--- a/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp
+++ b/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp
@@ -481,7 +481,7 @@
void* inBuffer[] = { (unsigned char *)mInputFrame };
INT inBufferIds[] = { IN_AUDIO_DATA };
- INT inBufferSize[] = { numBytesPerInputFrame };
+ INT inBufferSize[] = { (INT)numBytesPerInputFrame };
INT inBufferElSize[] = { sizeof(int16_t) };
AACENC_BufDesc inBufDesc;
diff --git a/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp
index 07f8b4f..50b739c 100644
--- a/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp
+++ b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp
@@ -257,7 +257,7 @@
}
if (pcmParams->nChannels != 1
- || pcmParams->nSamplingRate != kSampleRate) {
+ || pcmParams->nSamplingRate != (OMX_U32)kSampleRate) {
return OMX_ErrorUndefined;
}
diff --git a/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp b/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp
index cc38dc3..e25637a 100644
--- a/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp
+++ b/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp
@@ -655,7 +655,8 @@
vpx_codec_iter_t encoded_packet_iterator = NULL;
const vpx_codec_cx_pkt_t* encoded_packet;
- while (encoded_packet = vpx_codec_get_cx_data(mCodecContext, &encoded_packet_iterator)) {
+ while ((encoded_packet = vpx_codec_get_cx_data(
+ mCodecContext, &encoded_packet_iterator))) {
if (encoded_packet->kind == VPX_CODEC_CX_FRAME_PKT) {
outputBufferHeader->nTimeStamp = encoded_packet->data.frame.pts;
outputBufferHeader->nFlags = 0;
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 9499712..9f3b19c 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -147,9 +147,9 @@
}
if (startOffset > 0) {
- ALOGI("found something resembling an H.264/MPEG syncword at "
- "offset %ld",
- startOffset);
+ ALOGI("found something resembling an H.264/MPEG syncword "
+ "at offset %d",
+ startOffset);
}
data = &ptr[startOffset];
@@ -180,9 +180,9 @@
}
if (startOffset > 0) {
- ALOGI("found something resembling an H.264/MPEG syncword at "
- "offset %ld",
- startOffset);
+ ALOGI("found something resembling an H.264/MPEG syncword "
+ "at offset %d",
+ startOffset);
}
data = &ptr[startOffset];
@@ -213,8 +213,9 @@
}
if (startOffset > 0) {
- ALOGI("found something resembling an AAC syncword at offset %ld",
- startOffset);
+ ALOGI("found something resembling an AAC syncword at "
+ "offset %d",
+ startOffset);
}
data = &ptr[startOffset];
@@ -241,8 +242,8 @@
if (startOffset > 0) {
ALOGI("found something resembling an MPEG audio "
- "syncword at offset %ld",
- startOffset);
+ "syncword at offset %d",
+ startOffset);
}
data = &ptr[startOffset];
@@ -394,10 +395,30 @@
}
sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitAAC() {
- int64_t timeUs;
+ if (mBuffer->size() == 0) {
+ return NULL;
+ }
+ CHECK(!mRangeInfos.empty());
+
+ const RangeInfo &info = *mRangeInfos.begin();
+ if (mBuffer->size() < info.mLength) {
+ return NULL;
+ }
+
+ CHECK_GE(info.mTimestampUs, 0ll);
+
+ // The idea here is consume all AAC frames starting at offsets before
+ // info.mLength so we can assign a meaningful timestamp without
+ // having to interpolate.
+ // The final AAC frame may well extend into the next RangeInfo but
+ // that's ok.
size_t offset = 0;
- while (offset + 7 <= mBuffer->size()) {
+ while (offset < info.mLength) {
+ if (offset + 7 > mBuffer->size()) {
+ return NULL;
+ }
+
ABitReader bits(mBuffer->data() + offset, mBuffer->size() - offset);
// adts_fixed_header
@@ -450,24 +471,15 @@
}
if (offset + aac_frame_length > mBuffer->size()) {
- break;
+ return NULL;
}
size_t headerSize = protection_absent ? 7 : 9;
- int64_t tmpUs = fetchTimestamp(aac_frame_length);
- CHECK_GE(tmpUs, 0ll);
-
- if (offset == 0) {
- timeUs = tmpUs;
- }
-
offset += aac_frame_length;
}
- if (offset == 0) {
- return NULL;
- }
+ int64_t timeUs = fetchTimestamp(offset);
sp<ABuffer> accessUnit = new ABuffer(offset);
memcpy(accessUnit->data(), mBuffer->data(), offset);
@@ -492,7 +504,6 @@
if (first) {
timeUs = info->mTimestampUs;
- first = false;
}
if (info->mLength > size) {
@@ -509,6 +520,8 @@
mRangeInfos.erase(mRangeInfos.begin());
info = NULL;
}
+
+ first = false;
}
if (timeUs == 0ll) {
diff --git a/media/libstagefright/wifi-display/ANetworkSession.cpp b/media/libstagefright/wifi-display/ANetworkSession.cpp
index 88ca1cc..f074438 100644
--- a/media/libstagefright/wifi-display/ANetworkSession.cpp
+++ b/media/libstagefright/wifi-display/ANetworkSession.cpp
@@ -451,24 +451,6 @@
const Fragment &frag = *mOutFragments.begin();
const sp<ABuffer> &datagram = frag.mBuffer;
- uint8_t *data = datagram->data();
- if (data[0] == 0x80 && (data[1] & 0x7f) == 33) {
- int64_t nowUs = ALooper::GetNowUs();
-
- uint32_t prevRtpTime = U32_AT(&data[4]);
-
- // 90kHz time scale
- uint32_t rtpTime = (nowUs * 9ll) / 100ll;
- int32_t diffTime = (int32_t)rtpTime - (int32_t)prevRtpTime;
-
- ALOGV("correcting rtpTime by %.0f ms", diffTime / 90.0);
-
- data[4] = rtpTime >> 24;
- data[5] = (rtpTime >> 16) & 0xff;
- data[6] = (rtpTime >> 8) & 0xff;
- data[7] = rtpTime & 0xff;
- }
-
int n;
do {
n = send(mSocket, datagram->data(), datagram->size(), 0);
@@ -874,6 +856,14 @@
err = -errno;
goto bail2;
}
+
+ int tos = 224; // VOICE
+ res = setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+
+ if (res < 0) {
+ err = -errno;
+ goto bail2;
+ }
}
err = MakeSocketNonBlocking(s);
diff --git a/media/libstagefright/wifi-display/Android.mk b/media/libstagefright/wifi-display/Android.mk
index f1f9f45..1578c21 100644
--- a/media/libstagefright/wifi-display/Android.mk
+++ b/media/libstagefright/wifi-display/Android.mk
@@ -109,3 +109,25 @@
LOCAL_MODULE_TAGS := debug
include $(BUILD_EXECUTABLE)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ nettest.cpp \
+
+LOCAL_SHARED_LIBRARIES:= \
+ libbinder \
+ libgui \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libstagefright_wfd \
+ libutils \
+
+LOCAL_MODULE:= nettest
+
+LOCAL_MODULE_TAGS := debug
+
+include $(BUILD_EXECUTABLE)
diff --git a/media/libstagefright/wifi-display/TimeSyncer.cpp b/media/libstagefright/wifi-display/TimeSyncer.cpp
index 64e182e..cb429bc 100644
--- a/media/libstagefright/wifi-display/TimeSyncer.cpp
+++ b/media/libstagefright/wifi-display/TimeSyncer.cpp
@@ -102,6 +102,10 @@
case kWhatSendPacket:
{
+ if (mHistory.size() == 0) {
+ ALOGI("starting batch");
+ }
+
TimeInfo ti;
memset(&ti, 0, sizeof(ti));
@@ -229,6 +233,8 @@
} else {
notifyOffset();
+ ALOGI("batch done");
+
mHistory.clear();
postSendPacket(kBatchDelayUs);
}
diff --git a/media/libstagefright/wifi-display/TimeSyncer.h b/media/libstagefright/wifi-display/TimeSyncer.h
index 0e3aed7..4e7571f 100644
--- a/media/libstagefright/wifi-display/TimeSyncer.h
+++ b/media/libstagefright/wifi-display/TimeSyncer.h
@@ -75,7 +75,7 @@
kNumPacketsPerBatch = 30,
};
static const int64_t kTimeoutDelayUs = 500000ll;
- static const int64_t kBatchDelayUs = 10000000ll; // every 10 secs
+ static const int64_t kBatchDelayUs = 60000000ll; // every minute
sp<ANetworkSession> mNetSession;
sp<AMessage> mNotify;
diff --git a/media/libstagefright/wifi-display/nettest.cpp b/media/libstagefright/wifi-display/nettest.cpp
new file mode 100644
index 0000000..0779bf5
--- /dev/null
+++ b/media/libstagefright/wifi-display/nettest.cpp
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NEBUG 0
+#define LOG_TAG "nettest"
+#include <utils/Log.h>
+
+#include "ANetworkSession.h"
+#include "TimeSyncer.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/NuMediaExtractor.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+struct TestHandler : public AHandler {
+ TestHandler(const sp<ANetworkSession> &netSession);
+
+ void listen(int32_t port);
+ void connect(const char *host, int32_t port);
+
+protected:
+ virtual ~TestHandler();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kTimeSyncerPort = 8123,
+ };
+
+ enum {
+ kWhatListen,
+ kWhatConnect,
+ kWhatTimeSyncerNotify,
+ kWhatNetNotify,
+ kWhatSendMore,
+ kWhatStop,
+ };
+
+ sp<ANetworkSession> mNetSession;
+ sp<TimeSyncer> mTimeSyncer;
+
+ int32_t mServerSessionID;
+ int32_t mSessionID;
+
+ int64_t mTimeOffsetUs;
+ bool mTimeOffsetValid;
+
+ int32_t mCounter;
+
+ int64_t mMaxDelayMs;
+
+ void dumpDelay(int32_t counter, int64_t delayMs);
+
+ DISALLOW_EVIL_CONSTRUCTORS(TestHandler);
+};
+
+TestHandler::TestHandler(const sp<ANetworkSession> &netSession)
+ : mNetSession(netSession),
+ mServerSessionID(0),
+ mSessionID(0),
+ mTimeOffsetUs(-1ll),
+ mTimeOffsetValid(false),
+ mCounter(0),
+ mMaxDelayMs(-1ll) {
+}
+
+TestHandler::~TestHandler() {
+}
+
+void TestHandler::listen(int32_t port) {
+ sp<AMessage> msg = new AMessage(kWhatListen, id());
+ msg->setInt32("port", port);
+ msg->post();
+}
+
+void TestHandler::connect(const char *host, int32_t port) {
+ sp<AMessage> msg = new AMessage(kWhatConnect, id());
+ msg->setString("host", host);
+ msg->setInt32("port", port);
+ msg->post();
+}
+
+void TestHandler::dumpDelay(int32_t counter, int64_t delayMs) {
+ static const int64_t kMinDelayMs = 0;
+ static const int64_t kMaxDelayMs = 300;
+
+ const char *kPattern = "########################################";
+ size_t kPatternSize = strlen(kPattern);
+
+ int n = (kPatternSize * (delayMs - kMinDelayMs))
+ / (kMaxDelayMs - kMinDelayMs);
+
+ if (n < 0) {
+ n = 0;
+ } else if ((size_t)n > kPatternSize) {
+ n = kPatternSize;
+ }
+
+ if (delayMs > mMaxDelayMs) {
+ mMaxDelayMs = delayMs;
+ }
+
+ ALOGI("[%d] (%4lld ms / %4lld ms) %s",
+ counter,
+ delayMs,
+ mMaxDelayMs,
+ kPattern + kPatternSize - n);
+}
+
+void TestHandler::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatListen:
+ {
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+
+ notify = new AMessage(kWhatNetNotify, id());
+
+ int32_t port;
+ CHECK(msg->findInt32("port", &port));
+
+ struct in_addr ifaceAddr;
+ ifaceAddr.s_addr = INADDR_ANY;
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->createTCPDatagramSession(
+ ifaceAddr,
+ port,
+ notify,
+ &mServerSessionID));
+ break;
+ }
+
+ case kWhatConnect:
+ {
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+ mTimeSyncer->startServer(kTimeSyncerPort);
+
+ AString host;
+ CHECK(msg->findString("host", &host));
+
+ int32_t port;
+ CHECK(msg->findInt32("port", &port));
+
+ notify = new AMessage(kWhatNetNotify, id());
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->createTCPDatagramSession(
+ 0 /* localPort */,
+ host.c_str(),
+ port,
+ notify,
+ &mSessionID));
+ break;
+ }
+
+ case kWhatNetNotify:
+ {
+ int32_t reason;
+ CHECK(msg->findInt32("reason", &reason));
+
+ switch (reason) {
+ case ANetworkSession::kWhatConnected:
+ {
+ ALOGI("kWhatConnected");
+
+ (new AMessage(kWhatSendMore, id()))->post();
+ break;
+ }
+
+ case ANetworkSession::kWhatClientConnected:
+ {
+ ALOGI("kWhatClientConnected");
+
+ CHECK_EQ(mSessionID, 0);
+ CHECK(msg->findInt32("sessionID", &mSessionID));
+
+ AString clientIP;
+ CHECK(msg->findString("client-ip", &clientIP));
+
+ mTimeSyncer->startClient(clientIP.c_str(), kTimeSyncerPort);
+ break;
+ }
+
+ case ANetworkSession::kWhatDatagram:
+ {
+ sp<ABuffer> packet;
+ CHECK(msg->findBuffer("data", &packet));
+
+ CHECK_EQ(packet->size(), 12u);
+
+ int32_t counter = U32_AT(packet->data());
+ int64_t timeUs = U64_AT(packet->data() + 4);
+
+ if (mTimeOffsetValid) {
+ timeUs -= mTimeOffsetUs;
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayMs = (nowUs - timeUs) / 1000ll;
+
+ dumpDelay(counter, delayMs);
+ } else {
+ ALOGI("received %d", counter);
+ }
+ break;
+ }
+
+ case ANetworkSession::kWhatError:
+ {
+ ALOGE("kWhatError");
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+ break;
+ }
+
+ case kWhatTimeSyncerNotify:
+ {
+ CHECK(msg->findInt64("offset", &mTimeOffsetUs));
+ mTimeOffsetValid = true;
+ break;
+ }
+
+ case kWhatSendMore:
+ {
+ uint8_t buffer[4 + 8];
+ buffer[0] = mCounter >> 24;
+ buffer[1] = (mCounter >> 16) & 0xff;
+ buffer[2] = (mCounter >> 8) & 0xff;
+ buffer[3] = mCounter & 0xff;
+
+ int64_t nowUs = ALooper::GetNowUs();
+
+ buffer[4] = nowUs >> 56;
+ buffer[5] = (nowUs >> 48) & 0xff;
+ buffer[6] = (nowUs >> 40) & 0xff;
+ buffer[7] = (nowUs >> 32) & 0xff;
+ buffer[8] = (nowUs >> 24) & 0xff;
+ buffer[9] = (nowUs >> 16) & 0xff;
+ buffer[10] = (nowUs >> 8) & 0xff;
+ buffer[11] = nowUs & 0xff;
+
+ ++mCounter;
+
+ CHECK_EQ((status_t)OK,
+ mNetSession->sendRequest(
+ mSessionID,
+ buffer,
+ sizeof(buffer),
+ true /* timeValid */,
+ nowUs));
+
+ msg->post(100000ll);
+ break;
+ }
+
+ case kWhatStop:
+ {
+ if (mSessionID != 0) {
+ mNetSession->destroySession(mSessionID);
+ mSessionID = 0;
+ }
+
+ if (mServerSessionID != 0) {
+ mNetSession->destroySession(mServerSessionID);
+ mServerSessionID = 0;
+ }
+
+ looper()->stop();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+} // namespace android
+
+static void usage(const char *me) {
+ fprintf(stderr,
+ "usage: %s -c host:port\tconnect to remote host\n"
+ " -l port \tlisten\n",
+ me);
+}
+
+int main(int argc, char **argv) {
+ using namespace android;
+
+ // srand(time(NULL));
+
+ ProcessState::self()->startThreadPool();
+
+ DataSource::RegisterDefaultSniffers();
+
+ int32_t connectToPort = -1;
+ AString connectToHost;
+
+ int32_t listenOnPort = -1;
+
+ int res;
+ while ((res = getopt(argc, argv, "hc:l:")) >= 0) {
+ switch (res) {
+ case 'c':
+ {
+ const char *colonPos = strrchr(optarg, ':');
+
+ if (colonPos == NULL) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ connectToHost.setTo(optarg, colonPos - optarg);
+
+ char *end;
+ connectToPort = strtol(colonPos + 1, &end, 10);
+
+ if (*end != '\0' || end == colonPos + 1
+ || connectToPort < 0 || connectToPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ break;
+ }
+
+ case 'l':
+ {
+ char *end;
+ listenOnPort = strtol(optarg, &end, 10);
+
+ if (*end != '\0' || end == optarg
+ || listenOnPort < 0 || listenOnPort > 65535) {
+ fprintf(stderr, "Illegal port specified.\n");
+ exit(1);
+ }
+ break;
+ }
+
+ case '?':
+ case 'h':
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+ if ((listenOnPort < 0 && connectToPort < 0)
+ || (listenOnPort >= 0 && connectToPort >= 0)) {
+ fprintf(stderr,
+ "You need to select either client or server mode.\n");
+ exit(1);
+ }
+
+ sp<ANetworkSession> netSession = new ANetworkSession;
+ netSession->start();
+
+ sp<ALooper> looper = new ALooper;
+
+ sp<TestHandler> handler = new TestHandler(netSession);
+ looper->registerHandler(handler);
+
+ if (listenOnPort) {
+ handler->listen(listenOnPort);
+ }
+
+ if (connectToPort >= 0) {
+ handler->connect(connectToHost.c_str(), connectToPort);
+ }
+
+ looper->start(true /* runOnCallingThread */);
+
+ return 0;
+}
diff --git a/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp b/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp
index 5f189e7..7a96081 100644
--- a/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp
+++ b/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp
@@ -40,7 +40,6 @@
notify->setInt32("followsDiscontinuity", followsDiscontinuity);
notify->post();
}
-
////////////////////////////////////////////////////////////////////////////////
RTPReceiver::TSAssembler::TSAssembler(const sp<AMessage> ¬ify)
diff --git a/media/libstagefright/wifi-display/rtp/RTPBase.h b/media/libstagefright/wifi-display/rtp/RTPBase.h
index e3fa845..6178f00 100644
--- a/media/libstagefright/wifi-display/rtp/RTPBase.h
+++ b/media/libstagefright/wifi-display/rtp/RTPBase.h
@@ -25,6 +25,7 @@
PACKETIZATION_TRANSPORT_STREAM,
PACKETIZATION_H264,
PACKETIZATION_AAC,
+ PACKETIZATION_NONE,
};
enum TransportMode {
diff --git a/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp b/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp
index 238fb82..8fa1dae 100644
--- a/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp
+++ b/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp
@@ -30,11 +30,13 @@
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/Utils.h>
+#define TRACK_PACKET_LOSS 0
+
namespace android {
////////////////////////////////////////////////////////////////////////////////
-struct RTPReceiver::Source : public RefBase {
+struct RTPReceiver::Source : public AHandler {
Source(RTPReceiver *receiver, uint32_t ssrc);
void onPacketReceived(uint16_t seq, const sp<ABuffer> &buffer);
@@ -44,7 +46,14 @@
protected:
virtual ~Source();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
private:
+ enum {
+ kWhatRetransmit,
+ kWhatDeclareLost,
+ };
+
static const uint32_t kMinSequential = 2;
static const uint32_t kMaxDropout = 3000;
static const uint32_t kMaxMisorder = 100;
@@ -67,6 +76,17 @@
// Ordered by extended seq number.
List<sp<ABuffer> > mPackets;
+ enum StatusBits {
+ STATUS_DECLARED_LOST = 1,
+ STATUS_REQUESTED_RETRANSMISSION = 2,
+ STATUS_ARRIVED_LATE = 4,
+ };
+#if TRACK_PACKET_LOSS
+ KeyedVector<int32_t, uint32_t> mLostPackets;
+#endif
+
+ void modifyPacketStatus(int32_t extSeqNo, uint32_t mask);
+
int32_t mAwaitingExtSeqNo;
bool mRequestedRetransmission;
@@ -78,12 +98,20 @@
int32_t mNumDeclaredLost;
int32_t mNumDeclaredLostPrior;
+ int32_t mRetransmitGeneration;
+ int32_t mDeclareLostGeneration;
+ bool mDeclareLostTimerPending;
+
void queuePacket(const sp<ABuffer> &packet);
void dequeueMore();
sp<ABuffer> getNextPacket();
void resync();
+ void postRetransmitTimer(int64_t delayUs);
+ void postDeclareLostTimer(int64_t delayUs);
+ void cancelTimers();
+
DISALLOW_EVIL_CONSTRUCTORS(Source);
};
@@ -106,12 +134,71 @@
mActivePacketType(-1),
mNextReportTimeUs(-1ll),
mNumDeclaredLost(0),
- mNumDeclaredLostPrior(0) {
+ mNumDeclaredLostPrior(0),
+ mRetransmitGeneration(0),
+ mDeclareLostGeneration(0),
+ mDeclareLostTimerPending(false) {
}
RTPReceiver::Source::~Source() {
}
+void RTPReceiver::Source::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRetransmit:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mRetransmitGeneration) {
+ break;
+ }
+
+ mRequestedRetransmission = true;
+ mReceiver->requestRetransmission(mSSRC, mAwaitingExtSeqNo);
+
+ modifyPacketStatus(
+ mAwaitingExtSeqNo, STATUS_REQUESTED_RETRANSMISSION);
+ break;
+ }
+
+ case kWhatDeclareLost:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mDeclareLostGeneration) {
+ break;
+ }
+
+ cancelTimers();
+
+ ALOGV("Lost packet extSeqNo %d %s",
+ mAwaitingExtSeqNo,
+ mRequestedRetransmission ? "*" : "");
+
+ mRequestedRetransmission = false;
+ if (mActiveAssembler != NULL) {
+ mActiveAssembler->signalDiscontinuity();
+ }
+
+ modifyPacketStatus(mAwaitingExtSeqNo, STATUS_DECLARED_LOST);
+
+ // resync();
+ ++mAwaitingExtSeqNo;
+ ++mNumDeclaredLost;
+
+ mReceiver->notifyPacketLost();
+
+ dequeueMore();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
void RTPReceiver::Source::onPacketReceived(
uint16_t seq, const sp<ABuffer> &buffer) {
if (mFirst) {
@@ -164,6 +251,8 @@
if (mAwaitingExtSeqNo >= 0 && newExtendedSeqNo < mAwaitingExtSeqNo) {
// We're no longer interested in these. They're old.
ALOGV("dropping stale extSeqNo %d", newExtendedSeqNo);
+
+ modifyPacketStatus(newExtendedSeqNo, STATUS_ARRIVED_LATE);
return;
}
@@ -230,68 +319,34 @@
}
mNextReportTimeUs = nowUs + kReportIntervalUs;
+
+#if TRACK_PACKET_LOSS
+ for (size_t i = 0; i < mLostPackets.size(); ++i) {
+ int32_t key = mLostPackets.keyAt(i);
+ uint32_t value = mLostPackets.valueAt(i);
+
+ AString status;
+ if (value & STATUS_REQUESTED_RETRANSMISSION) {
+ status.append("retrans ");
+ }
+ if (value & STATUS_ARRIVED_LATE) {
+ status.append("arrived-late ");
+ }
+ ALOGI("Packet %d declared lost %s", key, status.c_str());
+ }
+#endif
}
- for (;;) {
- sp<ABuffer> packet = getNextPacket();
-
- if (packet == NULL) {
- if (mPackets.empty()) {
- break;
- }
-
- CHECK_GE(mAwaitingExtSeqNo, 0);
-
- const sp<ABuffer> &firstPacket = *mPackets.begin();
-
- uint32_t rtpTime;
- CHECK(firstPacket->meta()->findInt32(
- "rtp-time", (int32_t *)&rtpTime));
-
-
- int64_t rtpUs = (rtpTime * 100ll) / 9ll;
-
- int64_t maxArrivalTimeUs =
- mFirstArrivalTimeUs + rtpUs - mFirstRTPTimeUs;
-
- int64_t nowUs = ALooper::GetNowUs();
-
- CHECK_LT(mAwaitingExtSeqNo, firstPacket->int32Data());
-
- ALOGV("waiting for %d, comparing against %d, %lld us left",
- mAwaitingExtSeqNo,
- firstPacket->int32Data(),
- maxArrivalTimeUs - nowUs);
-
- if (maxArrivalTimeUs + kPacketLostAfterUs <= nowUs) {
- ALOGV("Lost packet extSeqNo %d %s",
- mAwaitingExtSeqNo,
- mRequestedRetransmission ? "*" : "");
-
- mRequestedRetransmission = false;
- if (mActiveAssembler != NULL) {
- mActiveAssembler->signalDiscontinuity();
- }
-
- // resync();
- ++mAwaitingExtSeqNo;
- ++mNumDeclaredLost;
-
- mReceiver->notifyPacketLost();
- continue;
- } else if (kRequestRetransmissionAfterUs > 0
- && maxArrivalTimeUs + kRequestRetransmissionAfterUs <= nowUs
- && !mRequestedRetransmission
- && mAwaitingExtSeqNo >= 0) {
- mRequestedRetransmission = true;
- mReceiver->requestRetransmission(mSSRC, mAwaitingExtSeqNo);
- break;
- } else {
- break;
- }
+ sp<ABuffer> packet;
+ while ((packet = getNextPacket()) != NULL) {
+ if (mDeclareLostTimerPending) {
+ cancelTimers();
}
- mRequestedRetransmission = false;
+ CHECK_GE(mAwaitingExtSeqNo, 0);
+#if TRACK_PACKET_LOSS
+ mLostPackets.removeItem(mAwaitingExtSeqNo);
+#endif
int32_t packetType;
CHECK(packet->meta()->findInt32("PT", &packetType));
@@ -301,14 +356,52 @@
mActivePacketType = packetType;
}
- if (mActiveAssembler == NULL) {
- continue;
+ if (mActiveAssembler != NULL) {
+ status_t err = mActiveAssembler->processPacket(packet);
+ if (err != OK) {
+ ALOGV("assembler returned error %d", err);
+ }
}
- status_t err = mActiveAssembler->processPacket(packet);
- if (err != OK) {
- ALOGV("assembler returned error %d", err);
- }
+ ++mAwaitingExtSeqNo;
+ }
+
+ if (mDeclareLostTimerPending) {
+ return;
+ }
+
+ if (mPackets.empty()) {
+ return;
+ }
+
+ CHECK_GE(mAwaitingExtSeqNo, 0);
+
+ const sp<ABuffer> &firstPacket = *mPackets.begin();
+
+ uint32_t rtpTime;
+ CHECK(firstPacket->meta()->findInt32(
+ "rtp-time", (int32_t *)&rtpTime));
+
+
+ int64_t rtpUs = (rtpTime * 100ll) / 9ll;
+
+ int64_t maxArrivalTimeUs =
+ mFirstArrivalTimeUs + rtpUs - mFirstRTPTimeUs;
+
+ nowUs = ALooper::GetNowUs();
+
+ CHECK_LT(mAwaitingExtSeqNo, firstPacket->int32Data());
+
+ ALOGV("waiting for %d, comparing against %d, %lld us left",
+ mAwaitingExtSeqNo,
+ firstPacket->int32Data(),
+ maxArrivalTimeUs - nowUs);
+
+ postDeclareLostTimer(maxArrivalTimeUs + kPacketLostAfterUs);
+
+ if (kRequestRetransmissionAfterUs > 0ll) {
+ postRetransmitTimer(
+ maxArrivalTimeUs + kRequestRetransmissionAfterUs);
}
}
@@ -328,8 +421,6 @@
sp<ABuffer> packet = *mPackets.begin();
mPackets.erase(mPackets.begin());
- ++mAwaitingExtSeqNo;
-
return packet;
}
@@ -404,9 +495,11 @@
RTPReceiver::RTPReceiver(
const sp<ANetworkSession> &netSession,
- const sp<AMessage> ¬ify)
+ const sp<AMessage> ¬ify,
+ uint32_t flags)
: mNetSession(netSession),
mNotify(notify),
+ mFlags(flags),
mRTPMode(TRANSPORT_UNDEFINED),
mRTCPMode(TRANSPORT_UNDEFINED),
mRTPSessionID(0),
@@ -693,6 +786,20 @@
CHECK(msg->findBuffer("data", &data));
if (isRTP) {
+ if (mFlags & FLAG_AUTO_CONNECT) {
+ AString fromAddr;
+ CHECK(msg->findString("fromAddr", &fromAddr));
+
+ int32_t fromPort;
+ CHECK(msg->findInt32("fromPort", &fromPort));
+
+ CHECK_EQ((status_t)OK,
+ connect(
+ fromAddr.c_str(), fromPort, fromPort + 1));
+
+ mFlags &= ~FLAG_AUTO_CONNECT;
+ }
+
onRTPData(data);
} else {
onRTCPData(data);
@@ -835,6 +942,8 @@
sp<Source> source;
if (index < 0) {
source = new Source(this, srcId);
+ looper()->registerHandler(source);
+
mSources.add(srcId, source);
} else {
source = mSources.valueAt(index);
@@ -965,6 +1074,7 @@
PacketizationMode mode = mPacketTypes.valueAt(index);
switch (mode) {
+ case PACKETIZATION_NONE:
case PACKETIZATION_TRANSPORT_STREAM:
return new TSAssembler(mNotify);
@@ -1005,5 +1115,39 @@
mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
}
+void RTPReceiver::Source::modifyPacketStatus(int32_t extSeqNo, uint32_t mask) {
+#if TRACK_PACKET_LOSS
+ ssize_t index = mLostPackets.indexOfKey(extSeqNo);
+ if (index < 0) {
+ mLostPackets.add(extSeqNo, mask);
+ } else {
+ mLostPackets.editValueAt(index) |= mask;
+ }
+#endif
+}
+
+void RTPReceiver::Source::postRetransmitTimer(int64_t timeUs) {
+ int64_t delayUs = timeUs - ALooper::GetNowUs();
+ sp<AMessage> msg = new AMessage(kWhatRetransmit, id());
+ msg->setInt32("generation", mRetransmitGeneration);
+ msg->post(delayUs);
+}
+
+void RTPReceiver::Source::postDeclareLostTimer(int64_t timeUs) {
+ CHECK(!mDeclareLostTimerPending);
+ mDeclareLostTimerPending = true;
+
+ int64_t delayUs = timeUs - ALooper::GetNowUs();
+ sp<AMessage> msg = new AMessage(kWhatDeclareLost, id());
+ msg->setInt32("generation", mDeclareLostGeneration);
+ msg->post(delayUs);
+}
+
+void RTPReceiver::Source::cancelTimers() {
+ ++mRetransmitGeneration;
+ ++mDeclareLostGeneration;
+ mDeclareLostTimerPending = false;
+}
+
} // namespace android
diff --git a/media/libstagefright/wifi-display/rtp/RTPReceiver.h b/media/libstagefright/wifi-display/rtp/RTPReceiver.h
index 630bce9..240ab2e 100644
--- a/media/libstagefright/wifi-display/rtp/RTPReceiver.h
+++ b/media/libstagefright/wifi-display/rtp/RTPReceiver.h
@@ -39,9 +39,14 @@
kWhatAccessUnit,
kWhatPacketLost,
};
+
+ enum Flags {
+ FLAG_AUTO_CONNECT = 1,
+ };
RTPReceiver(
const sp<ANetworkSession> &netSession,
- const sp<AMessage> ¬ify);
+ const sp<AMessage> ¬ify,
+ uint32_t flags = 0);
status_t registerPacketType(
uint8_t packetType, PacketizationMode mode);
@@ -82,6 +87,7 @@
sp<ANetworkSession> mNetSession;
sp<AMessage> mNotify;
+ uint32_t mFlags;
TransportMode mRTPMode;
TransportMode mRTCPMode;
int32_t mRTPSessionID;
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.cpp b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
index 9eeeabd..6bbe650 100644
--- a/media/libstagefright/wifi-display/rtp/RTPSender.cpp
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
@@ -187,6 +187,10 @@
status_t err;
switch (mode) {
+ case PACKETIZATION_NONE:
+ err = queueRawPacket(buffer, packetType);
+ break;
+
case PACKETIZATION_TRANSPORT_STREAM:
err = queueTSPackets(buffer, packetType);
break;
@@ -202,6 +206,46 @@
return err;
}
+status_t RTPSender::queueRawPacket(
+ const sp<ABuffer> &packet, uint8_t packetType) {
+ CHECK_LE(packet->size(), kMaxUDPPacketSize - 12);
+
+ int64_t timeUs;
+ CHECK(packet->meta()->findInt64("timeUs", &timeUs));
+
+ sp<ABuffer> udpPacket = new ABuffer(12 + packet->size());
+
+ udpPacket->setInt32Data(mRTPSeqNo);
+
+ uint8_t *rtp = udpPacket->data();
+ rtp[0] = 0x80;
+ rtp[1] = packetType;
+
+ rtp[2] = (mRTPSeqNo >> 8) & 0xff;
+ rtp[3] = mRTPSeqNo & 0xff;
+ ++mRTPSeqNo;
+
+ uint32_t rtpTime = (timeUs * 9) / 100ll;
+
+ rtp[4] = rtpTime >> 24;
+ rtp[5] = (rtpTime >> 16) & 0xff;
+ rtp[6] = (rtpTime >> 8) & 0xff;
+ rtp[7] = rtpTime & 0xff;
+
+ rtp[8] = kSourceID >> 24;
+ rtp[9] = (kSourceID >> 16) & 0xff;
+ rtp[10] = (kSourceID >> 8) & 0xff;
+ rtp[11] = kSourceID & 0xff;
+
+ memcpy(&rtp[12], packet->data(), packet->size());
+
+ return sendRTPPacket(
+ udpPacket,
+ true /* storeInHistory */,
+ true /* timeValid */,
+ ALooper::GetNowUs());
+}
+
status_t RTPSender::queueTSPackets(
const sp<ABuffer> &tsPackets, uint8_t packetType) {
CHECK_EQ(0, tsPackets->size() % 188);
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.h b/media/libstagefright/wifi-display/rtp/RTPSender.h
index 3a926ea..fefcab7 100644
--- a/media/libstagefright/wifi-display/rtp/RTPSender.h
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.h
@@ -94,6 +94,7 @@
static uint64_t GetNowNTP();
+ status_t queueRawPacket(const sp<ABuffer> &tsPackets, uint8_t packetType);
status_t queueTSPackets(const sp<ABuffer> &tsPackets, uint8_t packetType);
status_t queueAVCBuffer(const sp<ABuffer> &accessUnit, uint8_t packetType);
diff --git a/media/libstagefright/wifi-display/rtptest.cpp b/media/libstagefright/wifi-display/rtptest.cpp
index eade832..764a38b 100644
--- a/media/libstagefright/wifi-display/rtptest.cpp
+++ b/media/libstagefright/wifi-display/rtptest.cpp
@@ -21,6 +21,7 @@
#include "ANetworkSession.h"
#include "rtp/RTPSender.h"
#include "rtp/RTPReceiver.h"
+#include "TimeSyncer.h"
#include <binder/ProcessState.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -28,12 +29,116 @@
#include <media/stagefright/foundation/AHandler.h>
#include <media/stagefright/foundation/ALooper.h>
#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/NuMediaExtractor.h>
+#include <media/stagefright/Utils.h>
+
+#define MEDIA_FILENAME "/sdcard/Frame Counter HD 30FPS_1080p.mp4"
namespace android {
+struct PacketSource : public RefBase {
+ PacketSource() {}
+
+ virtual sp<ABuffer> getNextAccessUnit() = 0;
+
+protected:
+ virtual ~PacketSource() {}
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(PacketSource);
+};
+
+struct MediaPacketSource : public PacketSource {
+ MediaPacketSource()
+ : mMaxSampleSize(1024 * 1024) {
+ mExtractor = new NuMediaExtractor;
+ CHECK_EQ((status_t)OK,
+ mExtractor->setDataSource(MEDIA_FILENAME));
+
+ bool haveVideo = false;
+ for (size_t i = 0; i < mExtractor->countTracks(); ++i) {
+ sp<AMessage> format;
+ CHECK_EQ((status_t)OK, mExtractor->getTrackFormat(i, &format));
+
+ AString mime;
+ CHECK(format->findString("mime", &mime));
+
+ if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime.c_str())) {
+ mExtractor->selectTrack(i);
+ haveVideo = true;
+ break;
+ }
+ }
+
+ CHECK(haveVideo);
+ }
+
+ virtual sp<ABuffer> getNextAccessUnit() {
+ int64_t timeUs;
+ status_t err = mExtractor->getSampleTime(&timeUs);
+
+ if (err != OK) {
+ return NULL;
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(mMaxSampleSize);
+ CHECK_EQ((status_t)OK, mExtractor->readSampleData(accessUnit));
+
+ accessUnit->meta()->setInt64("timeUs", timeUs);
+
+ CHECK_EQ((status_t)OK, mExtractor->advance());
+
+ return accessUnit;
+ }
+
+protected:
+ virtual ~MediaPacketSource() {
+ }
+
+private:
+ sp<NuMediaExtractor> mExtractor;
+ size_t mMaxSampleSize;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MediaPacketSource);
+};
+
+struct SimplePacketSource : public PacketSource {
+ SimplePacketSource()
+ : mCounter(0) {
+ }
+
+ virtual sp<ABuffer> getNextAccessUnit() {
+ sp<ABuffer> buffer = new ABuffer(4);
+ uint8_t *dst = buffer->data();
+ dst[0] = mCounter >> 24;
+ dst[1] = (mCounter >> 16) & 0xff;
+ dst[2] = (mCounter >> 8) & 0xff;
+ dst[3] = mCounter & 0xff;
+
+ buffer->meta()->setInt64("timeUs", mCounter * 1000000ll / kFrameRate);
+
+ ++mCounter;
+
+ return buffer;
+ }
+
+protected:
+ virtual ~SimplePacketSource() {
+ }
+
+private:
+ enum {
+ kFrameRate = 30
+ };
+
+ uint32_t mCounter;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SimplePacketSource);
+};
+
struct TestHandler : public AHandler {
TestHandler(const sp<ANetworkSession> &netSession);
@@ -52,18 +157,39 @@
kWhatSenderNotify,
kWhatSendMore,
kWhatStop,
+ kWhatTimeSyncerNotify,
};
+#if 1
+ static const RTPBase::TransportMode kRTPMode = RTPBase::TRANSPORT_UDP;
+ static const RTPBase::TransportMode kRTCPMode = RTPBase::TRANSPORT_UDP;
+#else
+ static const RTPBase::TransportMode kRTPMode = RTPBase::TRANSPORT_TCP;
+ static const RTPBase::TransportMode kRTCPMode = RTPBase::TRANSPORT_NONE;
+#endif
+
+#if 1
+ static const RTPBase::PacketizationMode kPacketizationMode
+ = RTPBase::PACKETIZATION_H264;
+#else
+ static const RTPBase::PacketizationMode kPacketizationMode
+ = RTPBase::PACKETIZATION_NONE;
+#endif
+
sp<ANetworkSession> mNetSession;
- sp<NuMediaExtractor> mExtractor;
+ sp<PacketSource> mSource;
sp<RTPSender> mSender;
sp<RTPReceiver> mReceiver;
- size_t mMaxSampleSize;
+ sp<TimeSyncer> mTimeSyncer;
+ bool mTimeSyncerStarted;
int64_t mFirstTimeRealUs;
int64_t mFirstTimeMediaUs;
+ int64_t mTimeOffsetUs;
+ bool mTimeOffsetValid;
+
status_t readMore();
DISALLOW_EVIL_CONSTRUCTORS(TestHandler);
@@ -71,9 +197,11 @@
TestHandler::TestHandler(const sp<ANetworkSession> &netSession)
: mNetSession(netSession),
- mMaxSampleSize(1024 * 1024),
+ mTimeSyncerStarted(false),
mFirstTimeRealUs(-1ll),
- mFirstTimeMediaUs(-1ll) {
+ mFirstTimeMediaUs(-1ll),
+ mTimeOffsetUs(-1ll),
+ mTimeOffsetValid(false) {
}
TestHandler::~TestHandler() {
@@ -91,23 +219,48 @@
msg->post();
}
+static void dumpDelay(int64_t delayMs) {
+ static const int64_t kMinDelayMs = 0;
+ static const int64_t kMaxDelayMs = 300;
+
+ const char *kPattern = "########################################";
+ size_t kPatternSize = strlen(kPattern);
+
+ int n = (kPatternSize * (delayMs - kMinDelayMs))
+ / (kMaxDelayMs - kMinDelayMs);
+
+ if (n < 0) {
+ n = 0;
+ } else if ((size_t)n > kPatternSize) {
+ n = kPatternSize;
+ }
+
+ ALOGI("(%4lld ms) %s\n",
+ delayMs,
+ kPattern + kPatternSize - n);
+}
+
void TestHandler::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatListen:
{
- sp<AMessage> notify = new AMessage(kWhatReceiverNotify, id());
- mReceiver = new RTPReceiver(mNetSession, notify);
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+
+ notify = new AMessage(kWhatReceiverNotify, id());
+ mReceiver = new RTPReceiver(
+ mNetSession, notify, RTPReceiver::FLAG_AUTO_CONNECT);
looper()->registerHandler(mReceiver);
CHECK_EQ((status_t)OK,
- mReceiver->registerPacketType(
- 33, RTPReceiver::PACKETIZATION_H264));
+ mReceiver->registerPacketType(33, kPacketizationMode));
int32_t receiverRTPPort;
CHECK_EQ((status_t)OK,
mReceiver->initAsync(
- RTPReceiver::TRANSPORT_UDP, // rtpMode
- RTPReceiver::TRANSPORT_UDP, // rtcpMode
+ kRTPMode,
+ kRTCPMode,
&receiverRTPPort));
printf("picked receiverRTPPort %d\n", receiverRTPPort);
@@ -125,33 +278,23 @@
AString host;
CHECK(msg->findString("host", &host));
+ sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+ mTimeSyncer = new TimeSyncer(mNetSession, notify);
+ looper()->registerHandler(mTimeSyncer);
+ mTimeSyncer->startServer(8123);
+
int32_t receiverRTPPort;
CHECK(msg->findInt32("port", &receiverRTPPort));
- mExtractor = new NuMediaExtractor;
- CHECK_EQ((status_t)OK,
- mExtractor->setDataSource(
- "/sdcard/Frame Counter HD 30FPS_1080p.mp4"));
+#if 1
+ mSource = new MediaPacketSource;
+#else
+ mSource = new SimplePacketSource;
+#endif
- bool haveVideo = false;
- for (size_t i = 0; i < mExtractor->countTracks(); ++i) {
- sp<AMessage> format;
- CHECK_EQ((status_t)OK, mExtractor->getTrackFormat(i, &format));
-
- AString mime;
- CHECK(format->findString("mime", &mime));
-
- if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime.c_str())) {
- mExtractor->selectTrack(i);
- haveVideo = true;
- break;
- }
- }
-
- CHECK(haveVideo);
-
- sp<AMessage> notify = new AMessage(kWhatSenderNotify, id());
+ notify = new AMessage(kWhatSenderNotify, id());
mSender = new RTPSender(mNetSession, notify);
+
looper()->registerHandler(mSender);
int32_t senderRTPPort;
@@ -159,9 +302,10 @@
mSender->initAsync(
host.c_str(),
receiverRTPPort,
- RTPSender::TRANSPORT_UDP, // rtpMode
- receiverRTPPort + 1,
- RTPSender::TRANSPORT_UDP, // rtcpMode
+ kRTPMode,
+ kRTCPMode == RTPBase::TRANSPORT_NONE
+ ? -1 : receiverRTPPort + 1,
+ kRTCPMode,
&senderRTPPort));
printf("picked senderRTPPort %d\n", senderRTPPort);
@@ -201,7 +345,7 @@
case kWhatReceiverNotify:
{
- ALOGI("kWhatReceiverNotify");
+ ALOGV("kWhatReceiverNotify");
int32_t what;
CHECK(msg->findInt32("what", &what));
@@ -216,8 +360,40 @@
break;
}
- case RTPSender::kWhatError:
+ case RTPReceiver::kWhatError:
break;
+
+ case RTPReceiver::kWhatAccessUnit:
+ {
+#if 0
+ if (!mTimeSyncerStarted) {
+ mTimeSyncer->startClient("172.18.41.216", 8123);
+ mTimeSyncerStarted = true;
+ }
+
+ sp<ABuffer> accessUnit;
+ CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ if (mTimeOffsetValid) {
+ timeUs -= mTimeOffsetUs;
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t delayMs = (nowUs - timeUs) / 1000ll;
+
+ dumpDelay(delayMs);
+ }
+#endif
+ break;
+ }
+
+ case RTPReceiver::kWhatPacketLost:
+ ALOGV("kWhatPacketLost");
+ break;
+
+ default:
+ TRESPASS();
}
break;
}
@@ -231,7 +407,7 @@
mSender->queueBuffer(
accessUnit,
33,
- RTPSender::PACKETIZATION_H264));
+ kPacketizationMode));
status_t err = readMore();
@@ -253,31 +429,33 @@
mSender.clear();
}
- mExtractor.clear();
+ mSource.clear();
looper()->stop();
break;
}
+ case kWhatTimeSyncerNotify:
+ {
+ CHECK(msg->findInt64("offset", &mTimeOffsetUs));
+ mTimeOffsetValid = true;
+ break;
+ }
+
default:
TRESPASS();
}
}
status_t TestHandler::readMore() {
- int64_t timeUs;
- status_t err = mExtractor->getSampleTime(&timeUs);
+ sp<ABuffer> accessUnit = mSource->getNextAccessUnit();
- if (err != OK) {
- return err;
+ if (accessUnit == NULL) {
+ return ERROR_END_OF_STREAM;
}
- sp<ABuffer> accessUnit = new ABuffer(mMaxSampleSize);
- CHECK_EQ((status_t)OK, mExtractor->readSampleData(accessUnit));
-
- accessUnit->meta()->setInt64("timeUs", timeUs);
-
- CHECK_EQ((status_t)OK, mExtractor->advance());
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
int64_t nowUs = ALooper::GetNowUs();
int64_t whenUs;
@@ -289,6 +467,8 @@
whenUs = mFirstTimeRealUs + timeUs - mFirstTimeMediaUs;
}
+ accessUnit->meta()->setInt64("timeUs", whenUs);
+
sp<AMessage> msg = new AMessage(kWhatSendMore, id());
msg->setBuffer("accessUnit", accessUnit);
msg->post(whenUs - nowUs);
diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
index 1a08bf5..5db2099 100644
--- a/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
+++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
@@ -57,7 +57,8 @@
mSetupDeferred(false),
mLatencyCount(0),
mLatencySumUs(0ll),
- mLatencyMaxUs(0ll) {
+ mLatencyMaxUs(0ll),
+ mMaxDelayMs(-1ll) {
// We support any and all resolutions, but prefer 720p30
mSinkSupportedVideoFormats.setNativeResolution(
VideoFormats::RESOLUTION_CEA, 5); // 1280 x 720 p30
@@ -296,9 +297,13 @@
}
}
-static void dumpDelay(size_t trackIndex, int64_t timeUs) {
+void WifiDisplaySink::dumpDelay(size_t trackIndex, int64_t timeUs) {
int64_t delayMs = (ALooper::GetNowUs() - timeUs) / 1000ll;
+ if (delayMs > mMaxDelayMs) {
+ mMaxDelayMs = delayMs;
+ }
+
static const int64_t kMinDelayMs = 0;
static const int64_t kMaxDelayMs = 300;
@@ -314,9 +319,10 @@
n = kPatternSize;
}
- ALOGI("[%lld]: (%4lld ms) %s",
+ ALOGI("[%lld]: (%4lld ms / %4lld ms) %s",
timeUs / 1000,
delayMs,
+ mMaxDelayMs,
kPattern + kPatternSize - n);
}
@@ -350,14 +356,19 @@
looper()->registerHandler(mRenderer);
}
- CHECK(mTimeOffsetValid);
-
sp<ABuffer> accessUnit;
CHECK(msg->findBuffer("accessUnit", &accessUnit));
int64_t timeUs;
CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+ if (!mTimeOffsetValid && !(mFlags & FLAG_SPECIAL_MODE)) {
+ mTimeOffsetUs = timeUs - ALooper::GetNowUs();
+ mTimeOffsetValid = true;
+ }
+
+ CHECK(mTimeOffsetValid);
+
// We are the timesync _client_,
// client time = server time - time offset.
timeUs -= mTimeOffsetUs;
diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.h b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
index 7c62057..adb9d89 100644
--- a/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
+++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
@@ -132,6 +132,8 @@
int64_t mLatencySumUs;
int64_t mLatencyMaxUs;
+ int64_t mMaxDelayMs;
+
status_t sendM2(int32_t sessionID);
status_t sendSetup(int32_t sessionID, const char *uri);
status_t sendPlay(int32_t sessionID, const char *uri);
@@ -184,6 +186,8 @@
const char *url, AString *host, int32_t *port, AString *path,
AString *user, AString *pass);
+ void dumpDelay(size_t trackIndex, int64_t timeUs);
+
DISALLOW_EVIL_CONSTRUCTORS(WifiDisplaySink);
};
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
index 715d0b5..cacfcca 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.cpp
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
@@ -709,8 +709,11 @@
Converter::GetInt32Property(
"media.wfd.video-framerate", -1);
- if (rateHz < 0.0) {
- rateHz = repeaterSource->getFrameRate();
+ char val[PROPERTY_VALUE_MAX];
+ if (rateHz < 0.0
+ && property_get("media.wfd.video-framerate", val, NULL)
+ && !strcasecmp("adaptive", val)) {
+ rateHz = repeaterSource->getFrameRate();
if (avgLatencyUs > 300000ll) {
rateHz *= 0.9;
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 9d98f0b..47ca100 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -3122,16 +3122,15 @@
Vector< sp<Track> > *tracksToRemove
)
{
- sp<Track> trackToRemove;
-
+ size_t count = mActiveTracks.size();
mixer_state mixerStatus = MIXER_IDLE;
// find out which tracks need to be processed
- if (mActiveTracks.size() != 0) {
- sp<Track> t = mActiveTracks[0].promote();
+ for (size_t i = 0; i < count; i++) {
+ sp<Track> t = mActiveTracks[i].promote();
// The track died recently
if (t == 0) {
- return MIXER_IDLE;
+ continue;
}
Track* const track = t.get();
@@ -3180,35 +3179,40 @@
}
right = v_clamped/MAX_GAIN;
}
+ // Only consider last track started for volume and mixer state control.
+ // This is the last entry in mActiveTracks unless a track underruns.
+ // As we only care about the transition phase between two tracks on a
+ // direct output, it is not a problem to ignore the underrun case.
+ if (i == (count - 1)) {
+ if (left != mLeftVolFloat || right != mRightVolFloat) {
+ mLeftVolFloat = left;
+ mRightVolFloat = right;
- if (left != mLeftVolFloat || right != mRightVolFloat) {
- mLeftVolFloat = left;
- mRightVolFloat = right;
+ // Convert volumes from float to 8.24
+ uint32_t vl = (uint32_t)(left * (1 << 24));
+ uint32_t vr = (uint32_t)(right * (1 << 24));
- // Convert volumes from float to 8.24
- uint32_t vl = (uint32_t)(left * (1 << 24));
- uint32_t vr = (uint32_t)(right * (1 << 24));
-
- // Delegate volume control to effect in track effect chain if needed
- // only one effect chain can be present on DirectOutputThread, so if
- // there is one, the track is connected to it
- if (!mEffectChains.isEmpty()) {
- // Do not ramp volume if volume is controlled by effect
- mEffectChains[0]->setVolume_l(&vl, &vr);
- left = (float)vl / (1 << 24);
- right = (float)vr / (1 << 24);
+ // Delegate volume control to effect in track effect chain if needed
+ // only one effect chain can be present on DirectOutputThread, so if
+ // there is one, the track is connected to it
+ if (!mEffectChains.isEmpty()) {
+ // Do not ramp volume if volume is controlled by effect
+ mEffectChains[0]->setVolume_l(&vl, &vr);
+ left = (float)vl / (1 << 24);
+ right = (float)vr / (1 << 24);
+ }
+ mOutput->stream->set_volume(mOutput->stream, left, right);
}
- mOutput->stream->set_volume(mOutput->stream, left, right);
- }
- // reset retry count
- track->mRetryCount = kMaxTrackRetriesDirect;
- mActiveTrack = t;
- mixerStatus = MIXER_TRACKS_READY;
+ // reset retry count
+ track->mRetryCount = kMaxTrackRetriesDirect;
+ mActiveTrack = t;
+ mixerStatus = MIXER_TRACKS_READY;
+ }
} else {
- // clear effect chain input buffer if an active track underruns to avoid sending
- // previous audio buffer again to effects
- if (!mEffectChains.isEmpty()) {
+ // clear effect chain input buffer if the last active track started underruns
+ // to avoid sending previous audio buffer again to effects
+ if (!mEffectChains.isEmpty() && (i == (count -1))) {
mEffectChains[0]->clearInputBuffer();
}
@@ -3224,33 +3228,36 @@
if (track->isStopped()) {
track->reset();
}
- trackToRemove = track;
+ tracksToRemove->add(track);
}
} else {
// No buffers for this track. Give it a few chances to
// fill a buffer, then remove it from active list.
+ // Only consider last track started for mixer state control
if (--(track->mRetryCount) <= 0) {
ALOGV("BUFFER TIMEOUT: remove(%d) from active list", track->name());
- trackToRemove = track;
- } else {
+ tracksToRemove->add(track);
+ } else if (i == (count -1)){
mixerStatus = MIXER_TRACKS_ENABLED;
}
}
}
}
- // FIXME merge this with similar code for removing multiple tracks
// remove all the tracks that need to be...
- if (CC_UNLIKELY(trackToRemove != 0)) {
- tracksToRemove->add(trackToRemove);
- mActiveTracks.remove(trackToRemove);
- if (!mEffectChains.isEmpty()) {
- ALOGV("stopping track on chain %p for session Id: %d", mEffectChains[0].get(),
- trackToRemove->sessionId());
- mEffectChains[0]->decActiveTrackCnt();
- }
- if (trackToRemove->isTerminated()) {
- removeTrack_l(trackToRemove);
+ count = tracksToRemove->size();
+ if (CC_UNLIKELY(count)) {
+ for (size_t i = 0 ; i < count ; i++) {
+ const sp<Track>& track = tracksToRemove->itemAt(i);
+ mActiveTracks.remove(track);
+ if (!mEffectChains.isEmpty()) {
+ ALOGV("stopping track on chain %p for session Id: %d", mEffectChains[0].get(),
+ track->sessionId());
+ mEffectChains[0]->decActiveTrackCnt();
+ }
+ if (track->isTerminated()) {
+ removeTrack_l(track);
+ }
}
}
diff --git a/services/camera/libcameraservice/Camera3Device.cpp b/services/camera/libcameraservice/Camera3Device.cpp
index f2c8c04..e53dbb5 100644
--- a/services/camera/libcameraservice/Camera3Device.cpp
+++ b/services/camera/libcameraservice/Camera3Device.cpp
@@ -262,6 +262,8 @@
ATRACE_CALL();
Mutex::Autolock l(mLock);
+ // TODO: take ownership of the request
+
switch (mStatus) {
case STATUS_ERROR:
ALOGE("%s: Device has encountered a serious error", __FUNCTION__);
@@ -363,10 +365,8 @@
status_t Camera3Device::waitUntilRequestReceived(int32_t requestId, nsecs_t timeout) {
ATRACE_CALL();
- (void)requestId; (void)timeout;
- ALOGE("%s: Unimplemented", __FUNCTION__);
- return INVALID_OPERATION;
+ return mRequestThread->waitUntilRequestProcessed(requestId, timeout);
}
status_t Camera3Device::createStream(sp<ANativeWindow> consumer,
@@ -698,28 +698,62 @@
status_t Camera3Device::triggerAutofocus(uint32_t id) {
ATRACE_CALL();
- (void)id;
- ALOGE("%s: Unimplemented", __FUNCTION__);
- return INVALID_OPERATION;
+ ALOGV("%s: Triggering autofocus, id %d", __FUNCTION__, id);
+ // Mix-in this trigger into the next request and only the next request.
+ RequestTrigger trigger[] = {
+ {
+ ANDROID_CONTROL_AF_TRIGGER,
+ ANDROID_CONTROL_AF_TRIGGER_START
+ },
+ {
+ ANDROID_CONTROL_AF_TRIGGER_ID,
+ static_cast<int32_t>(id)
+ },
+ };
+
+ return mRequestThread->queueTrigger(trigger,
+ sizeof(trigger)/sizeof(trigger[0]));
}
status_t Camera3Device::triggerCancelAutofocus(uint32_t id) {
ATRACE_CALL();
- (void)id;
- ALOGE("%s: Unimplemented", __FUNCTION__);
- return INVALID_OPERATION;
+ ALOGV("%s: Triggering cancel autofocus, id %d", __FUNCTION__, id);
+ // Mix-in this trigger into the next request and only the next request.
+ RequestTrigger trigger[] = {
+ {
+ ANDROID_CONTROL_AF_TRIGGER,
+ ANDROID_CONTROL_AF_TRIGGER_CANCEL
+ },
+ {
+ ANDROID_CONTROL_AF_TRIGGER_ID,
+ static_cast<int32_t>(id)
+ },
+ };
+ return mRequestThread->queueTrigger(trigger,
+ sizeof(trigger)/sizeof(trigger[0]));
}
status_t Camera3Device::triggerPrecaptureMetering(uint32_t id) {
ATRACE_CALL();
- (void)id;
- ALOGE("%s: Unimplemented", __FUNCTION__);
- return INVALID_OPERATION;
+ ALOGV("%s: Triggering precapture metering, id %d", __FUNCTION__, id);
+ // Mix-in this trigger into the next request and only the next request.
+ RequestTrigger trigger[] = {
+ {
+ ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
+ ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_START
+ },
+ {
+ ANDROID_CONTROL_AE_PRECAPTURE_ID,
+ static_cast<int32_t>(id)
+ },
+ };
+ return mRequestThread->queueTrigger(trigger,
+ sizeof(trigger)/sizeof(trigger[0]));
}
status_t Camera3Device::pushReprocessBuffer(int reprocessStreamId,
@@ -997,9 +1031,13 @@
// Dispatch any 3A change events to listeners
if (listener != NULL) {
if (new3aState.aeState != cur3aState.aeState) {
+ ALOGVV("%s: AE state changed from 0x%x to 0x%x",
+ __FUNCTION__, cur3aState.aeState, new3aState.aeState);
listener->notifyAutoExposure(new3aState.aeState, aeTriggerId);
}
if (new3aState.afState != cur3aState.afState) {
+ ALOGVV("%s: AF state changed from 0x%x to 0x%x",
+ __FUNCTION__, cur3aState.afState, new3aState.afState);
listener->notifyAutoFocus(new3aState.afState, afTriggerId);
}
if (new3aState.awbState != cur3aState.awbState) {
@@ -1059,7 +1097,8 @@
mReconfigured(false),
mDoPause(false),
mPaused(true),
- mFrameNumber(0) {
+ mFrameNumber(0),
+ mLatestRequestId(NAME_NOT_FOUND) {
}
void Camera3Device::RequestThread::configurationComplete() {
@@ -1075,6 +1114,57 @@
return OK;
}
+
+status_t Camera3Device::RequestThread::queueTrigger(
+ RequestTrigger trigger[],
+ size_t count) {
+
+ Mutex::Autolock l(mTriggerMutex);
+ status_t ret;
+
+ for (size_t i = 0; i < count; ++i) {
+ ret = queueTriggerLocked(trigger[i]);
+
+ if (ret != OK) {
+ return ret;
+ }
+ }
+
+ return OK;
+}
+
+status_t Camera3Device::RequestThread::queueTriggerLocked(
+ RequestTrigger trigger) {
+
+ uint32_t tag = trigger.metadataTag;
+ ssize_t index = mTriggerMap.indexOfKey(tag);
+
+ switch (trigger.getTagType()) {
+ case TYPE_BYTE:
+ // fall-through
+ case TYPE_INT32:
+ break;
+ default:
+ ALOGE("%s: Type not supported: 0x%x",
+ __FUNCTION__,
+ trigger.getTagType());
+ return INVALID_OPERATION;
+ }
+
+ /**
+ * Collect only the latest trigger, since we only have 1 field
+ * in the request settings per trigger tag, and can't send more than 1
+ * trigger per request.
+ */
+ if (index != NAME_NOT_FOUND) {
+ mTriggerMap.editValueAt(index) = trigger;
+ } else {
+ mTriggerMap.add(tag, trigger);
+ }
+
+ return OK;
+}
+
status_t Camera3Device::RequestThread::setRepeatingRequests(
const RequestList &requests) {
Mutex::Autolock l(mRequestLock);
@@ -1108,6 +1198,24 @@
return OK;
}
+status_t Camera3Device::RequestThread::waitUntilRequestProcessed(
+ int32_t requestId, nsecs_t timeout) {
+ Mutex::Autolock l(mLatestRequestMutex);
+ status_t res;
+ while (mLatestRequestId != requestId) {
+ nsecs_t startTime = systemTime();
+
+ res = mLatestRequestSignal.waitRelative(mLatestRequestMutex, timeout);
+ if (res != OK) return res;
+
+ timeout -= (systemTime() - startTime);
+ }
+
+ return OK;
+}
+
+
+
bool Camera3Device::RequestThread::threadLoop() {
status_t res;
@@ -1125,16 +1233,55 @@
}
// Create request to HAL
-
camera3_capture_request_t request = camera3_capture_request_t();
+ Vector<camera3_stream_buffer_t> outputBuffers;
- if (mPrevRequest != nextRequest) {
+ // Insert any queued triggers (before metadata is locked)
+ int32_t triggerCount;
+ res = insertTriggers(nextRequest);
+ if (res < 0) {
+ ALOGE("RequestThread: Unable to insert triggers "
+ "(capture request %d, HAL device: %s (%d)",
+ (mFrameNumber+1), strerror(-res), res);
+ cleanUpFailedRequest(request, nextRequest, outputBuffers);
+ return false;
+ }
+ triggerCount = res;
+
+ bool triggersMixedIn = (triggerCount > 0 || mPrevTriggers > 0);
+
+ // If the request is the same as last, or we had triggers last time
+ if (mPrevRequest != nextRequest || triggersMixedIn) {
+ /**
+ * The request should be presorted so accesses in HAL
+ * are O(logn). Sidenote, sorting a sorted metadata is nop.
+ */
+ nextRequest->mSettings.sort();
request.settings = nextRequest->mSettings.getAndLock();
mPrevRequest = nextRequest;
- } // else leave request.settings NULL to indicate 'reuse latest given'
+ ALOGVV("%s: Request settings are NEW", __FUNCTION__);
+
+ IF_ALOGV() {
+ camera_metadata_ro_entry_t e = camera_metadata_ro_entry_t();
+ find_camera_metadata_ro_entry(
+ request.settings,
+ ANDROID_CONTROL_AF_TRIGGER,
+ &e
+ );
+ if (e.count > 0) {
+ ALOGV("%s: Request (frame num %d) had AF trigger 0x%x",
+ __FUNCTION__,
+ mFrameNumber+1,
+ e.data.u8[0]);
+ }
+ }
+ } else {
+ // leave request.settings NULL to indicate 'reuse latest given'
+ ALOGVV("%s: Request settings are REUSED",
+ __FUNCTION__);
+ }
camera3_stream_buffer_t inputBuffer;
- Vector<camera3_stream_buffer_t> outputBuffers;
// Fill in buffers
@@ -1168,6 +1315,7 @@
request.frame_number = mFrameNumber++;
+
// Submit request and block until ready for next one
res = mHal3Device->ops->process_capture_request(mHal3Device, &request);
@@ -1181,6 +1329,35 @@
if (request.settings != NULL) {
nextRequest->mSettings.unlock(request.settings);
}
+
+ // Remove any previously queued triggers (after unlock)
+ res = removeTriggers(mPrevRequest);
+ if (res != OK) {
+ ALOGE("RequestThread: Unable to remove triggers "
+ "(capture request %d, HAL device: %s (%d)",
+ request.frame_number, strerror(-res), res);
+ return false;
+ }
+ mPrevTriggers = triggerCount;
+
+ // Read android.request.id from the request settings metadata
+ // - inform waitUntilRequestProcessed thread of a new request ID
+ {
+ Mutex::Autolock al(mLatestRequestMutex);
+
+ camera_metadata_entry_t requestIdEntry =
+ nextRequest->mSettings.find(ANDROID_REQUEST_ID);
+ if (requestIdEntry.count > 0) {
+ mLatestRequestId = requestIdEntry.data.i32[0];
+ } else {
+ ALOGW("%s: Did not have android.request.id set in the request",
+ __FUNCTION__);
+ mLatestRequestId = NAME_NOT_FOUND;
+ }
+
+ mLatestRequestSignal.signal();
+ }
+
return true;
}
@@ -1285,6 +1462,141 @@
return false;
}
+status_t Camera3Device::RequestThread::insertTriggers(
+ const sp<CaptureRequest> &request) {
+
+ Mutex::Autolock al(mTriggerMutex);
+
+ CameraMetadata &metadata = request->mSettings;
+ size_t count = mTriggerMap.size();
+
+ for (size_t i = 0; i < count; ++i) {
+ RequestTrigger trigger = mTriggerMap.valueAt(i);
+
+ uint32_t tag = trigger.metadataTag;
+ camera_metadata_entry entry = metadata.find(tag);
+
+ if (entry.count > 0) {
+ /**
+ * Already has an entry for this trigger in the request.
+ * Rewrite it with our requested trigger value.
+ */
+ RequestTrigger oldTrigger = trigger;
+
+ oldTrigger.entryValue = entry.data.u8[0];
+
+ mTriggerReplacedMap.add(tag, oldTrigger);
+ } else {
+ /**
+ * More typical, no trigger entry, so we just add it
+ */
+ mTriggerRemovedMap.add(tag, trigger);
+ }
+
+ status_t res;
+
+ switch (trigger.getTagType()) {
+ case TYPE_BYTE: {
+ uint8_t entryValue = static_cast<uint8_t>(trigger.entryValue);
+ res = metadata.update(tag,
+ &entryValue,
+ /*count*/1);
+ break;
+ }
+ case TYPE_INT32:
+ res = metadata.update(tag,
+ &trigger.entryValue,
+ /*count*/1);
+ break;
+ default:
+ ALOGE("%s: Type not supported: 0x%x",
+ __FUNCTION__,
+ trigger.getTagType());
+ return INVALID_OPERATION;
+ }
+
+ if (res != OK) {
+ ALOGE("%s: Failed to update request metadata with trigger tag %s"
+ ", value %d", __FUNCTION__, trigger.getTagName(),
+ trigger.entryValue);
+ return res;
+ }
+
+ ALOGV("%s: Mixed in trigger %s, value %d", __FUNCTION__,
+ trigger.getTagName(),
+ trigger.entryValue);
+ }
+
+ mTriggerMap.clear();
+
+ return count;
+}
+
+status_t Camera3Device::RequestThread::removeTriggers(
+ const sp<CaptureRequest> &request) {
+ Mutex::Autolock al(mTriggerMutex);
+
+ CameraMetadata &metadata = request->mSettings;
+
+ /**
+ * Replace all old entries with their old values.
+ */
+ for (size_t i = 0; i < mTriggerReplacedMap.size(); ++i) {
+ RequestTrigger trigger = mTriggerReplacedMap.valueAt(i);
+
+ status_t res;
+
+ uint32_t tag = trigger.metadataTag;
+ switch (trigger.getTagType()) {
+ case TYPE_BYTE: {
+ uint8_t entryValue = static_cast<uint8_t>(trigger.entryValue);
+ res = metadata.update(tag,
+ &entryValue,
+ /*count*/1);
+ break;
+ }
+ case TYPE_INT32:
+ res = metadata.update(tag,
+ &trigger.entryValue,
+ /*count*/1);
+ break;
+ default:
+ ALOGE("%s: Type not supported: 0x%x",
+ __FUNCTION__,
+ trigger.getTagType());
+ return INVALID_OPERATION;
+ }
+
+ if (res != OK) {
+ ALOGE("%s: Failed to restore request metadata with trigger tag %s"
+ ", trigger value %d", __FUNCTION__,
+ trigger.getTagName(), trigger.entryValue);
+ return res;
+ }
+ }
+ mTriggerReplacedMap.clear();
+
+ /**
+ * Remove all new entries.
+ */
+ for (size_t i = 0; i < mTriggerRemovedMap.size(); ++i) {
+ RequestTrigger trigger = mTriggerRemovedMap.valueAt(i);
+ status_t res = metadata.erase(trigger.metadataTag);
+
+ if (res != OK) {
+ ALOGE("%s: Failed to erase metadata with trigger tag %s"
+ ", trigger value %d", __FUNCTION__,
+ trigger.getTagName(), trigger.entryValue);
+ return res;
+ }
+ }
+ mTriggerRemovedMap.clear();
+
+ return OK;
+}
+
+
+
/**
* Static callback forwarding methods from HAL to instance
*/
diff --git a/services/camera/libcameraservice/Camera3Device.h b/services/camera/libcameraservice/Camera3Device.h
index 8600c6c..7f294e6 100644
--- a/services/camera/libcameraservice/Camera3Device.h
+++ b/services/camera/libcameraservice/Camera3Device.h
@@ -109,7 +109,7 @@
private:
static const nsecs_t kShutdownTimeout = 5000000000; // 5 sec
-
+ struct RequestTrigger;
Mutex mLock;
@@ -172,6 +172,23 @@
*/
status_t configureStreamsLocked();
+ struct RequestTrigger {
+ // Metadata tag number, e.g. android.control.aePrecaptureTrigger
+ uint32_t metadataTag;
+ // Metadata value, e.g. 'START' or the trigger ID
+ int32_t entryValue;
+
+ // The last part of the fully qualified path, e.g. afTrigger
+ const char *getTagName() const {
+ return get_camera_metadata_tag_name(metadataTag) ?: "NULL";
+ }
+
+ // e.g. TYPE_BYTE, TYPE_INT32, etc.
+ int getTagType() const {
+ return get_camera_metadata_tag_type(metadataTag);
+ }
+ };
+
/**
* Thread for managing capture request submission to HAL device.
*/
@@ -198,6 +215,14 @@
status_t queueRequest(sp<CaptureRequest> request);
/**
+ * Queue a trigger to be dispatched with the next outgoing
+ * process_capture_request. The settings for that request only
+ * will be temporarily rewritten to add the trigger tag/value.
+ * Subsequent requests will not be rewritten (for this tag).
+ */
+ status_t queueTrigger(RequestTrigger trigger[], size_t count);
+
+ /**
* Pause/unpause the capture thread. Doesn't block, so use
* waitUntilPaused to wait until the thread is paused.
*/
@@ -210,11 +235,27 @@
*/
status_t waitUntilPaused(nsecs_t timeout);
+ /**
+ * Wait until thread processes the capture request with settings'
+ * android.request.id == requestId.
+ *
+ * Returns TIMED_OUT in case the thread does not process the request
+ * within the timeout.
+ */
+ status_t waitUntilRequestProcessed(int32_t requestId, nsecs_t timeout);
+
protected:
virtual bool threadLoop();
private:
+ status_t queueTriggerLocked(RequestTrigger trigger);
+ // Mix-in queued triggers into this request
+ int32_t insertTriggers(const sp<CaptureRequest> &request);
+ // Purge the queued triggers from this request,
+ // restoring the old field values for those tags.
+ status_t removeTriggers(const sp<CaptureRequest> &request);
+
static const nsecs_t kRequestTimeout = 50e6; // 50 ms
// Waits for a request, or returns NULL if times out.
@@ -249,8 +290,20 @@
Condition mPausedSignal;
sp<CaptureRequest> mPrevRequest;
+ int32_t mPrevTriggers;
int32_t mFrameNumber;
+
+ Mutex mLatestRequestMutex;
+ Condition mLatestRequestSignal;
+ // android.request.id for latest process_capture_request
+ int32_t mLatestRequestId;
+
+ typedef KeyedVector<uint32_t/*tag*/, RequestTrigger> TriggerMap;
+ Mutex mTriggerMutex;
+ TriggerMap mTriggerMap;
+ TriggerMap mTriggerRemovedMap;
+ TriggerMap mTriggerReplacedMap;
};
sp<RequestThread> mRequestThread;
diff --git a/services/camera/libcameraservice/camera2/CaptureSequencer.cpp b/services/camera/libcameraservice/camera2/CaptureSequencer.cpp
index 1880912..ee03329 100644
--- a/services/camera/libcameraservice/camera2/CaptureSequencer.cpp
+++ b/services/camera/libcameraservice/camera2/CaptureSequencer.cpp
@@ -270,6 +270,9 @@
processor->clearZslQueue();
}
+ /**
+ * Fire the jpegCallback in Camera#takePicture(..., jpegCallback)
+ */
if (mCaptureBuffer != 0 && res == OK) {
Camera2Client::SharedCameraCallbacks::Lock
l(client->mSharedCameraCallbacks);
@@ -367,6 +370,8 @@
CaptureSequencer::CaptureState CaptureSequencer::manageStandardStart(
sp<Camera2Client> &client) {
ATRACE_CALL();
+
+ // Get the onFrameAvailable callback when the requestID == mCaptureId
client->registerFrameListener(mCaptureId, mCaptureId + 1,
this);
{
@@ -426,6 +431,13 @@
SharedParameters::Lock l(client->getParameters());
Vector<uint8_t> outputStreams;
+ /**
+ * Set up output streams in the request
+ * - preview
+ * - capture/jpeg
+ * - callback (if preview callbacks enabled)
+ * - recording (if recording enabled)
+ */
outputStreams.push(client->getPreviewStreamId());
outputStreams.push(client->getCaptureStreamId());
@@ -454,6 +466,7 @@
return DONE;
}
+ // Create a capture copy since CameraDeviceBase#capture takes ownership
CameraMetadata captureCopy = mCaptureRequest;
if (captureCopy.entryCount() == 0) {
ALOGE("%s: Camera %d: Unable to copy capture request for HAL device",
@@ -461,7 +474,12 @@
return DONE;
}
+ /**
+ * Clear the streaming request for still-capture pictures
+ * (as opposed to i.e. video snapshots)
+ */
if (l.mParameters.state == Parameters::STILL_CAPTURE) {
+ // API definition of takePicture() - stop preview before taking pic
res = client->stopStream();
if (res != OK) {
ALOGE("%s: Camera %d: Unable to stop preview for still capture: "
@@ -488,6 +506,8 @@
status_t res;
ATRACE_CALL();
Mutex::Autolock l(mInputMutex);
+
+ // Wait for new metadata result (mNewFrame)
while (!mNewFrameReceived) {
res = mNewFrameSignal.waitRelative(mInputMutex, kWaitDuration);
if (res == TIMED_OUT) {
@@ -495,12 +515,17 @@
break;
}
}
+
+ // Approximation of the shutter being closed
+ // - TODO: use the hal3 exposure callback in Camera3Device instead
if (mNewFrameReceived && !mShutterNotified) {
SharedParameters::Lock l(client->getParameters());
/* warning: this also locks a SharedCameraCallbacks */
shutterNotifyLocked(l.mParameters, client, mMsgType);
mShutterNotified = true;
}
+
+ // Wait until jpeg was captured by JpegProcessor
while (mNewFrameReceived && !mNewCaptureReceived) {
res = mNewCaptureSignal.waitRelative(mInputMutex, kWaitDuration);
if (res == TIMED_OUT) {
@@ -524,7 +549,9 @@
}
if (entry.data.i64[0] != mCaptureTimestamp) {
ALOGW("Mismatched capture timestamps: Metadata frame %lld,"
- " captured buffer %lld", entry.data.i64[0], mCaptureTimestamp);
+ " captured buffer %lld",
+ entry.data.i64[0],
+ mCaptureTimestamp);
}
client->removeFrameListener(mCaptureId, mCaptureId + 1, this);
diff --git a/services/camera/libcameraservice/camera2/JpegProcessor.h b/services/camera/libcameraservice/camera2/JpegProcessor.h
index 2283f28..74f4738 100644
--- a/services/camera/libcameraservice/camera2/JpegProcessor.h
+++ b/services/camera/libcameraservice/camera2/JpegProcessor.h
@@ -44,6 +44,7 @@
JpegProcessor(wp<Camera2Client> client, wp<CaptureSequencer> sequencer);
~JpegProcessor();
+ // CpuConsumer listener implementation
void onFrameAvailable();
status_t updateStream(const Parameters ¶ms);