summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Huber <andih@google.com>2010-08-04 10:14:30 -0700
committerAndreas Huber <andih@google.com>2010-08-04 11:49:24 -0700
commit39ddf8e0f18766f7ba1e3246b774aa6ebd93eea8 (patch)
tree87f5d0d68c1779f113843e939c41440ff6b00389
parent610959a52fe22a88e50d158f5f5f492fee4f1921 (diff)
downloadframeworks_av-39ddf8e0f18766f7ba1e3246b774aa6ebd93eea8.zip
frameworks_av-39ddf8e0f18766f7ba1e3246b774aa6ebd93eea8.tar.gz
frameworks_av-39ddf8e0f18766f7ba1e3246b774aa6ebd93eea8.tar.bz2
Support for Gtalk video, includes AMR/H.263 assembler and packetization support, extensions to MediaRecorder to stream via RTP over a pair of UDP sockets as well as various fixes to the RTP implementation.
Change-Id: I95b8dd487061add9bade15749e563b01cd99d9a6
-rw-r--r--include/media/mediarecorder.h3
-rw-r--r--include/media/stagefright/MetaData.h1
-rw-r--r--media/libmedia/mediarecorder.cpp2
-rw-r--r--media/libmediaplayerservice/Android.mk7
-rw-r--r--media/libmediaplayerservice/StagefrightRecorder.cpp49
-rw-r--r--media/libmediaplayerservice/StagefrightRecorder.h3
-rw-r--r--media/libstagefright/Android.mk3
-rw-r--r--media/libstagefright/AwesomePlayer.cpp176
-rw-r--r--media/libstagefright/OMXCodec.cpp4
-rw-r--r--media/libstagefright/include/AwesomePlayer.h4
-rw-r--r--media/libstagefright/rtsp/AAMRAssembler.cpp232
-rw-r--r--media/libstagefright/rtsp/AAMRAssembler.h59
-rw-r--r--media/libstagefright/rtsp/AAVCAssembler.cpp8
-rw-r--r--media/libstagefright/rtsp/AAVCAssembler.h1
-rw-r--r--media/libstagefright/rtsp/AH263Assembler.cpp191
-rw-r--r--media/libstagefright/rtsp/AH263Assembler.h57
-rw-r--r--media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp6
-rw-r--r--media/libstagefright/rtsp/AMPEG4AudioAssembler.h1
-rw-r--r--media/libstagefright/rtsp/APacketSource.cpp95
-rw-r--r--media/libstagefright/rtsp/APacketSource.h9
-rw-r--r--media/libstagefright/rtsp/ARTPAssembler.h1
-rw-r--r--media/libstagefright/rtsp/ARTPConnection.cpp182
-rw-r--r--media/libstagefright/rtsp/ARTPConnection.h7
-rw-r--r--media/libstagefright/rtsp/ARTPSession.cpp231
-rw-r--r--media/libstagefright/rtsp/ARTPSession.h69
-rw-r--r--media/libstagefright/rtsp/ARTPSource.cpp155
-rw-r--r--media/libstagefright/rtsp/ARTPSource.h11
-rw-r--r--media/libstagefright/rtsp/ARTPWriter.cpp813
-rw-r--r--media/libstagefright/rtsp/ARTPWriter.h128
-rw-r--r--media/libstagefright/rtsp/ASessionDescription.cpp20
-rw-r--r--media/libstagefright/rtsp/Android.mk32
-rw-r--r--media/libstagefright/rtsp/MyHandler.h65
-rw-r--r--media/libstagefright/rtsp/UDPPusher.cpp146
-rw-r--r--media/libstagefright/rtsp/UDPPusher.h56
-rw-r--r--media/libstagefright/rtsp/rtp_test.cpp227
35 files changed, 2937 insertions, 117 deletions
diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h
index b21bc4d..291b18a 100644
--- a/include/media/mediarecorder.h
+++ b/include/media/mediarecorder.h
@@ -73,6 +73,9 @@ enum output_format {
OUTPUT_FORMAT_AAC_ADIF = 5,
OUTPUT_FORMAT_AAC_ADTS = 6,
+ /* Stream over a socket, limited to a single stream */
+ OUTPUT_FORMAT_RTP_AVP = 7,
+
OUTPUT_FORMAT_LIST_END // must be last - used to validate format type
};
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index e631c7f..ab1fa4f 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -46,6 +46,7 @@ enum {
kKeyIsSyncFrame = 'sync', // int32_t (bool)
kKeyIsCodecConfig = 'conf', // int32_t (bool)
kKeyTime = 'time', // int64_t (usecs)
+ kKeyNTPTime = 'ntpT', // uint64_t (ntp-timestamp)
kKeyTargetTime = 'tarT', // int64_t (usecs)
kKeyDuration = 'dura', // int64_t (usecs)
kKeyColorFormat = 'colf',
diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp
index 5adc116..9d53c25 100644
--- a/media/libmedia/mediarecorder.cpp
+++ b/media/libmedia/mediarecorder.cpp
@@ -181,7 +181,7 @@ status_t MediaRecorder::setOutputFormat(int of)
LOGE("setOutputFormat called in an invalid state: %d", mCurrentState);
return INVALID_OPERATION;
}
- if (mIsVideoSourceSet && of >= OUTPUT_FORMAT_AUDIO_ONLY_START) { //first non-video output format
+ if (mIsVideoSourceSet && of >= OUTPUT_FORMAT_AUDIO_ONLY_START && of != OUTPUT_FORMAT_RTP_AVP) { //first non-video output format
LOGE("output format (%d) is meant for audio recording only and incompatible with video recording", of);
return INVALID_OPERATION;
}
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index 8f010c9..e1edd16 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -31,9 +31,13 @@ LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
libstagefright \
libstagefright_omx \
- libstagefright_color_conversion \
+ libstagefright_color_conversion \
+ libstagefright_foundation \
libsurfaceflinger_client
+LOCAL_STATIC_LIBRARIES := \
+ libstagefright_rtsp
+
ifneq ($(BUILD_WITHOUT_PV),true)
LOCAL_SHARED_LIBRARIES += \
libopencore_player \
@@ -51,6 +55,7 @@ LOCAL_C_INCLUDES := \
$(call include-path-for, graphics corecg) \
$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
$(TOP)/frameworks/base/media/libstagefright/include \
+ $(TOP)/frameworks/base/media/libstagefright/rtsp \
$(TOP)/external/tremolo/Tremolo
LOCAL_MODULE:= libmediaplayerservice
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index dfddae0..f6f89c7 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -40,6 +40,8 @@
#include <unistd.h>
#include <ctype.h>
+#include "ARTPWriter.h"
+
namespace android {
StagefrightRecorder::StagefrightRecorder()
@@ -628,6 +630,9 @@ status_t StagefrightRecorder::start() {
case OUTPUT_FORMAT_AAC_ADTS:
return startAACRecording();
+ case OUTPUT_FORMAT_RTP_AVP:
+ return startRTPRecording();
+
default:
LOGE("Unsupported output file format: %d", mOutputFormat);
return UNKNOWN_ERROR;
@@ -760,6 +765,39 @@ status_t StagefrightRecorder::startAMRRecording() {
return OK;
}
+status_t StagefrightRecorder::startRTPRecording() {
+ CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_RTP_AVP);
+
+ if ((mAudioSource != AUDIO_SOURCE_LIST_END
+ && mVideoSource != VIDEO_SOURCE_LIST_END)
+ || (mAudioSource == AUDIO_SOURCE_LIST_END
+ && mVideoSource == VIDEO_SOURCE_LIST_END)) {
+ // Must have exactly one source.
+ return BAD_VALUE;
+ }
+
+ if (mOutputFd < 0) {
+ return BAD_VALUE;
+ }
+
+ sp<MediaSource> source;
+
+ if (mAudioSource != AUDIO_SOURCE_LIST_END) {
+ source = createAudioSource();
+ } else {
+ status_t err = setupVideoEncoder(&source);
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ mWriter = new ARTPWriter(dup(mOutputFd));
+ mWriter->addSource(source);
+ mWriter->setListener(mListener);
+
+ return mWriter->start();
+}
+
void StagefrightRecorder::clipVideoFrameRate() {
LOGV("clipVideoFrameRate: encoder %d", mVideoEncoder);
int minFrameRate = mEncoderProfiles->getVideoEncoderParamByName(
@@ -882,7 +920,9 @@ void StagefrightRecorder::clipVideoFrameHeight() {
}
}
-status_t StagefrightRecorder::setupVideoEncoder(const sp<MediaWriter>& writer) {
+status_t StagefrightRecorder::setupVideoEncoder(sp<MediaSource> *source) {
+ source->clear();
+
status_t err = setupCameraSource();
if (err != OK) return err;
@@ -944,7 +984,8 @@ status_t StagefrightRecorder::setupVideoEncoder(const sp<MediaWriter>& writer) {
return UNKNOWN_ERROR;
}
- writer->addSource(encoder);
+ *source = encoder;
+
return OK;
}
@@ -982,8 +1023,10 @@ status_t StagefrightRecorder::startMPEG4Recording() {
}
if (mVideoSource == VIDEO_SOURCE_DEFAULT
|| mVideoSource == VIDEO_SOURCE_CAMERA) {
- err = setupVideoEncoder(writer);
+ sp<MediaSource> encoder;
+ err = setupVideoEncoder(&encoder);
if (err != OK) return err;
+ writer->addSource(encoder);
totalBitRate += mVideoBitRate;
}
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index b4c5900..216f6bc 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -101,10 +101,11 @@ private:
status_t startMPEG4Recording();
status_t startAMRRecording();
status_t startAACRecording();
+ status_t startRTPRecording();
sp<MediaSource> createAudioSource();
status_t setupCameraSource();
status_t setupAudioEncoder(const sp<MediaWriter>& writer);
- status_t setupVideoEncoder(const sp<MediaWriter>& writer);
+ status_t setupVideoEncoder(sp<MediaSource> *source);
// Encoding parameter handling utilities
status_t setParameter(const String8 &key, const String8 &value);
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 89bfc1f..fb85287 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -45,7 +45,8 @@ LOCAL_C_INCLUDES:= \
$(JNI_H_INCLUDE) \
$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
$(TOP)/external/opencore/android \
- $(TOP)/external/tremolo
+ $(TOP)/external/tremolo \
+ $(TOP)/frameworks/base/media/libstagefright/rtsp
LOCAL_SHARED_LIBRARIES := \
libbinder \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 236a62b..436f098 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -27,6 +27,11 @@
#include "include/NuCachedSource2.h"
#include "include/ThrottledSource.h"
+#include "ARTPSession.h"
+#include "APacketSource.h"
+#include "ASessionDescription.h"
+#include "UDPPusher.h"
+
#include <binder/IPCThreadState.h>
#include <media/stagefright/AudioPlayer.h>
#include <media/stagefright/DataSource.h>
@@ -389,6 +394,9 @@ void AwesomePlayer::reset_l() {
}
mRTSPController.clear();
+ mRTPPusher.clear();
+ mRTCPPusher.clear();
+ mRTPSession.clear();
if (mVideoSource != NULL) {
mVideoSource->stop();
@@ -845,10 +853,24 @@ void AwesomePlayer::setVideoSource(sp<MediaSource> source) {
}
status_t AwesomePlayer::initVideoDecoder() {
+ uint32_t flags = 0;
+#if 1
+ if (mRTPSession != NULL) {
+ // XXX hack.
+
+ const char *mime;
+ CHECK(mVideoTrack->getFormat()->findCString(kKeyMIMEType, &mime));
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
+ flags |= OMXCodec::kPreferSoftwareCodecs;
+ }
+ }
+#endif
+
mVideoSource = OMXCodec::Create(
mClient.interface(), mVideoTrack->getFormat(),
false, // createEncoder
- mVideoTrack);
+ mVideoTrack,
+ NULL, flags);
if (mVideoSource != NULL) {
int64_t durationUs;
@@ -1200,6 +1222,158 @@ status_t AwesomePlayer::finishSetDataSource_l() {
MediaExtractor::Create(dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS);
return setDataSource_l(extractor);
+ } else if (!strcmp("rtsp://gtalk", mUri.string())) {
+ if (mLooper == NULL) {
+ mLooper = new ALooper;
+ mLooper->start();
+ }
+
+#if 0
+ mRTPPusher = new UDPPusher("/data/misc/rtpout.bin", 5434);
+ mLooper->registerHandler(mRTPPusher);
+
+ mRTCPPusher = new UDPPusher("/data/misc/rtcpout.bin", 5435);
+ mLooper->registerHandler(mRTCPPusher);
+#endif
+
+ mRTPSession = new ARTPSession;
+ mLooper->registerHandler(mRTPSession);
+
+#if 0
+ // My H264 SDP
+ static const char *raw =
+ "v=0\r\n"
+ "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+ "s=QuickTime\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=0-315\r\n"
+ "a=isma-compliance:2,2.0,2\r\n"
+ "m=video 5434 RTP/AVP 97\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "b=AS:30\r\n"
+ "a=rtpmap:97 H264/90000\r\n"
+ "a=fmtp:97 packetization-mode=1;profile-level-id=42000C;"
+ "sprop-parameter-sets=Z0IADJZUCg+I,aM44gA==\r\n"
+ "a=mpeg4-esid:201\r\n"
+ "a=cliprect:0,0,240,320\r\n"
+ "a=framesize:97 320-240\r\n";
+#elif 0
+ // My H263 SDP
+ static const char *raw =
+ "v=0\r\n"
+ "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+ "s=QuickTime\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=0-315\r\n"
+ "a=isma-compliance:2,2.0,2\r\n"
+ "m=video 5434 RTP/AVP 97\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "b=AS:30\r\n"
+ "a=rtpmap:97 H263-1998/90000\r\n"
+ "a=cliprect:0,0,240,320\r\n"
+ "a=framesize:97 320-240\r\n";
+#elif 0
+ // My AMR SDP
+ static const char *raw =
+ "v=0\r\n"
+ "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+ "s=QuickTime\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=0-315\r\n"
+ "a=isma-compliance:2,2.0,2\r\n"
+ "m=audio 5434 RTP/AVP 97\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "b=AS:30\r\n"
+ "a=rtpmap:97 AMR/8000/1\r\n"
+ "a=fmtp:97 octet-align\r\n";
+#elif 1
+ // My GTalk H.264 SDP
+ static const char *raw =
+ "v=0\r\n"
+ "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+ "s=QuickTime\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=0-315\r\n"
+ "a=isma-compliance:2,2.0,2\r\n"
+ "m=video 5434 RTP/AVP 97\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "b=AS:30\r\n"
+ "a=rtpmap:97 H264/90000\r\n"
+ "a=fmtp:97 packetization-mode=1;profile-level-id=42E00D;"
+ "sprop-parameter-sets=J0LgDZWgUG/lQA==,KM4DnoA=\r\n"
+ "a=mpeg4-esid:201\r\n"
+ "a=cliprect:0,0,200,320\r\n"
+ "a=framesize:97 320-200\r\n";
+#elif 0
+ // GTalk H263 SDP
+ static const char *raw =
+ "v=0\r\n"
+ "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+ "s=QuickTime\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=0-315\r\n"
+ "a=isma-compliance:2,2.0,2\r\n"
+ "m=video 5434 RTP/AVP 98\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "b=AS:30\r\n"
+ "a=rtpmap:98 H263-1998/90000\r\n"
+ "a=cliprect:0,0,200,320\r\n"
+ "a=framesize:98 320-200\r\n";
+#else
+ // sholes H264 SDP
+ static const char *raw =
+ "v=0\r\n"
+ "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+ "s=QuickTime\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=now-\r\n"
+ "m=video 5434 RTP/AVP 96\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "b=AS:320000\r\n"
+ "a=rtpmap:96 H264/90000\r\n"
+ "a=fmtp:96 packetization-mode=1;profile-level-id=42001E;"
+ "sprop-parameter-sets=Z0KACukCg+QgAAB9AAAOpgCA,aM48gA==\r\n"
+ "a=cliprect:0,0,240,320\r\n"
+ "a=framesize:96 320-240\r\n";
+#endif
+
+ sp<ASessionDescription> desc = new ASessionDescription;
+ CHECK(desc->setTo(raw, strlen(raw)));
+
+ CHECK_EQ(mRTPSession->setup(desc), (status_t)OK);
+
+ if (mRTPPusher != NULL) {
+ mRTPPusher->start();
+ }
+
+ if (mRTCPPusher != NULL) {
+ mRTCPPusher->start();
+ }
+
+ CHECK_EQ(mRTPSession->countTracks(), 1u);
+ sp<MediaSource> source = mRTPSession->trackAt(0);
+
+#if 0
+ bool eos;
+ while (((APacketSource *)source.get())
+ ->getQueuedDuration(&eos) < 5000000ll && !eos) {
+ usleep(100000ll);
+ }
+#endif
+
+ const char *mime;
+ CHECK(source->getFormat()->findCString(kKeyMIMEType, &mime));
+
+ if (!strncasecmp("video/", mime, 6)) {
+ setVideoSource(source);
+ } else {
+ CHECK(!strncasecmp("audio/", mime, 6));
+ setAudioSource(source);
+ }
+
+ mExtractorFlags = MediaExtractor::CAN_PAUSE;
+
+ return OK;
} else if (!strncasecmp("rtsp://", mUri.string(), 7)) {
if (mLooper == NULL) {
mLooper = new ALooper;
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 3fba2d9..d19fbe5 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -1429,6 +1429,10 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) {
return err;
}
+ CODEC_LOGI("allocating %lu buffers of size %lu on %s port",
+ def.nBufferCountActual, def.nBufferSize,
+ portIndex == kPortIndexInput ? "input" : "output");
+
size_t totalSize = def.nBufferCountActual * def.nBufferSize;
mDealer[portIndex] = new MemoryDealer(totalSize, "OMXCodec");
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 8d0877c..55e2c36 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -38,6 +38,8 @@ struct NuCachedSource2;
struct ALooper;
struct ARTSPController;
+struct ARTPSession;
+struct UDPPusher;
struct AwesomeRenderer : public RefBase {
AwesomeRenderer() {}
@@ -178,6 +180,8 @@ private:
sp<ALooper> mLooper;
sp<ARTSPController> mRTSPController;
+ sp<ARTPSession> mRTPSession;
+ sp<UDPPusher> mRTPPusher, mRTCPPusher;
struct SuspensionState {
String8 mUri;
diff --git a/media/libstagefright/rtsp/AAMRAssembler.cpp b/media/libstagefright/rtsp/AAMRAssembler.cpp
new file mode 100644
index 0000000..c56578b
--- /dev/null
+++ b/media/libstagefright/rtsp/AAMRAssembler.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AAMRAssembler.h"
+
+#include "ARTPSource.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+static bool GetAttribute(const char *s, const char *key, AString *value) {
+ value->clear();
+
+ size_t keyLen = strlen(key);
+
+ for (;;) {
+ const char *colonPos = strchr(s, ';');
+
+ size_t len =
+ (colonPos == NULL) ? strlen(s) : colonPos - s;
+
+ if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) {
+ value->setTo(&s[keyLen + 1], len - keyLen - 1);
+ return true;
+ }
+ if (len == keyLen && !strncmp(s, key, keyLen)) {
+ value->setTo("1");
+ return true;
+ }
+
+ if (colonPos == NULL) {
+ return false;
+ }
+
+ s = colonPos + 1;
+ }
+}
+
+AAMRAssembler::AAMRAssembler(
+ const sp<AMessage> &notify, bool isWide, const AString &params)
+ : mIsWide(isWide),
+ mNotifyMsg(notify),
+ mNextExpectedSeqNoValid(false),
+ mNextExpectedSeqNo(0) {
+ AString value;
+ CHECK(GetAttribute(params.c_str(), "octet-align", &value) && value == "1");
+ CHECK(!GetAttribute(params.c_str(), "crc", &value) || value == "0");
+ CHECK(!GetAttribute(params.c_str(), "interleaving", &value));
+}
+
+AAMRAssembler::~AAMRAssembler() {
+}
+
+ARTPAssembler::AssemblyStatus AAMRAssembler::assembleMore(
+ const sp<ARTPSource> &source) {
+ return addPacket(source);
+}
+
+static size_t getFrameSize(bool isWide, unsigned FT) {
+ static const size_t kFrameSizeNB[8] = {
+ 95, 103, 118, 134, 148, 159, 204, 244
+ };
+ static const size_t kFrameSizeWB[9] = {
+ 132, 177, 253, 285, 317, 365, 397, 461, 477
+ };
+
+ size_t frameSize = isWide ? kFrameSizeWB[FT] : kFrameSizeNB[FT];
+
+ // Round up bits to bytes and add 1 for the header byte.
+ frameSize = (frameSize + 7) / 8 + 1;
+
+ return frameSize;
+}
+
+ARTPAssembler::AssemblyStatus AAMRAssembler::addPacket(
+ const sp<ARTPSource> &source) {
+ List<sp<ABuffer> > *queue = source->queue();
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ if (mNextExpectedSeqNoValid) {
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ while (it != queue->end()) {
+ if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
+ break;
+ }
+
+ it = queue->erase(it);
+ }
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+ }
+
+ sp<ABuffer> buffer = *queue->begin();
+
+ if (!mNextExpectedSeqNoValid) {
+ mNextExpectedSeqNoValid = true;
+ mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
+ } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
+#if VERBOSE
+ LOG(VERBOSE) << "Not the sequence number I expected";
+#endif
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ // hexdump(buffer->data(), buffer->size());
+
+ if (buffer->size() < 1) {
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ LOG(VERBOSE) << "AMR packet too short.";
+
+ return MALFORMED_PACKET;
+ }
+
+ unsigned payloadHeader = buffer->data()[0];
+ unsigned CMR = payloadHeader >> 4;
+ CHECK_EQ(payloadHeader & 0x0f, 0u); // RR
+
+ Vector<uint8_t> tableOfContents;
+
+ size_t offset = 1;
+ size_t totalSize = 0;
+ for (;;) {
+ if (offset >= buffer->size()) {
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ LOG(VERBOSE) << "Unable to parse TOC.";
+
+ return MALFORMED_PACKET;
+ }
+
+ uint8_t toc = buffer->data()[offset++];
+
+ unsigned FT = (toc >> 3) & 0x0f;
+ if ((toc & 3) != 0
+ || (mIsWide && FT > 8)
+ || (!mIsWide && FT > 7)) {
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ LOG(VERBOSE) << "Illegal TOC entry.";
+
+ return MALFORMED_PACKET;
+ }
+
+ totalSize += getFrameSize(mIsWide, (toc >> 3) & 0x0f);
+
+ tableOfContents.push(toc);
+
+ if (0 == (toc & 0x80)) {
+ break;
+ }
+ }
+
+ uint64_t ntpTime;
+ CHECK(buffer->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+ sp<ABuffer> accessUnit = new ABuffer(totalSize);
+ accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+ size_t dstOffset = 0;
+ for (size_t i = 0; i < tableOfContents.size(); ++i) {
+ uint8_t toc = tableOfContents[i];
+
+ size_t frameSize = getFrameSize(mIsWide, (toc >> 3) & 0x0f);
+
+ if (offset + frameSize - 1 > buffer->size()) {
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ LOG(VERBOSE) << "AMR packet too short.";
+
+ return MALFORMED_PACKET;
+ }
+
+ accessUnit->data()[dstOffset++] = toc;
+ memcpy(accessUnit->data() + dstOffset,
+ buffer->data() + offset, frameSize - 1);
+
+ offset += frameSize - 1;
+ dstOffset += frameSize - 1;
+ }
+
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setObject("access-unit", accessUnit);
+ msg->post();
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return OK;
+}
+
+void AAMRAssembler::packetLost() {
+ CHECK(mNextExpectedSeqNoValid);
+ ++mNextExpectedSeqNo;
+}
+
+void AAMRAssembler::onByeReceived() {
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setInt32("eos", true);
+ msg->post();
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/AAMRAssembler.h b/media/libstagefright/rtsp/AAMRAssembler.h
new file mode 100644
index 0000000..d55e109
--- /dev/null
+++ b/media/libstagefright/rtsp/AAMRAssembler.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_AMR_ASSEMBLER_H_
+
+#define A_AMR_ASSEMBLER_H_
+
+#include "ARTPAssembler.h"
+
+#include <utils/List.h>
+
+#include <stdint.h>
+
+namespace android {
+
+struct AMessage;
+struct AString;
+
+struct AAMRAssembler : public ARTPAssembler {
+ AAMRAssembler(
+ const sp<AMessage> &notify, bool isWide,
+ const AString &params);
+
+protected:
+ virtual ~AAMRAssembler();
+
+ virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+ virtual void onByeReceived();
+ virtual void packetLost();
+
+private:
+ bool mIsWide;
+
+ sp<AMessage> mNotifyMsg;
+ bool mNextExpectedSeqNoValid;
+ uint32_t mNextExpectedSeqNo;
+
+ AssemblyStatus addPacket(const sp<ARTPSource> &source);
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAMRAssembler);
+};
+
+} // namespace android
+
+#endif // A_AMR_ASSEMBLER_H_
+
diff --git a/media/libstagefright/rtsp/AAVCAssembler.cpp b/media/libstagefright/rtsp/AAVCAssembler.cpp
index 3dfb200..b22de2c 100644
--- a/media/libstagefright/rtsp/AAVCAssembler.cpp
+++ b/media/libstagefright/rtsp/AAVCAssembler.cpp
@@ -377,9 +377,17 @@ ARTPAssembler::AssemblyStatus AAVCAssembler::assembleMore(
void AAVCAssembler::packetLost() {
CHECK(mNextExpectedSeqNoValid);
+ LOG(VERBOSE) << "packetLost (expected " << mNextExpectedSeqNo << ")";
+
++mNextExpectedSeqNo;
mAccessUnitDamaged = true;
}
+void AAVCAssembler::onByeReceived() {
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setInt32("eos", true);
+ msg->post();
+}
+
} // namespace android
diff --git a/media/libstagefright/rtsp/AAVCAssembler.h b/media/libstagefright/rtsp/AAVCAssembler.h
index 1e97520..bf389ec 100644
--- a/media/libstagefright/rtsp/AAVCAssembler.h
+++ b/media/libstagefright/rtsp/AAVCAssembler.h
@@ -35,6 +35,7 @@ protected:
virtual ~AAVCAssembler();
virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+ virtual void onByeReceived();
virtual void packetLost();
private:
diff --git a/media/libstagefright/rtsp/AH263Assembler.cpp b/media/libstagefright/rtsp/AH263Assembler.cpp
new file mode 100644
index 0000000..8b59998
--- /dev/null
+++ b/media/libstagefright/rtsp/AH263Assembler.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AH263Assembler.h"
+
+#include "ARTPSource.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+AH263Assembler::AH263Assembler(const sp<AMessage> &notify)
+ : mNotifyMsg(notify),
+ mAccessUnitRTPTime(0),
+ mNextExpectedSeqNoValid(false),
+ mNextExpectedSeqNo(0),
+ mAccessUnitDamaged(false) {
+}
+
+AH263Assembler::~AH263Assembler() {
+}
+
+ARTPAssembler::AssemblyStatus AH263Assembler::assembleMore(
+ const sp<ARTPSource> &source) {
+ AssemblyStatus status = addPacket(source);
+ if (status == MALFORMED_PACKET) {
+ mAccessUnitDamaged = true;
+ }
+ return status;
+}
+
+ARTPAssembler::AssemblyStatus AH263Assembler::addPacket(
+ const sp<ARTPSource> &source) {
+ List<sp<ABuffer> > *queue = source->queue();
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ if (mNextExpectedSeqNoValid) {
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ while (it != queue->end()) {
+ if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
+ break;
+ }
+
+ it = queue->erase(it);
+ }
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+ }
+
+ sp<ABuffer> buffer = *queue->begin();
+
+ if (!mNextExpectedSeqNoValid) {
+ mNextExpectedSeqNoValid = true;
+ mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
+ } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
+#if VERBOSE
+ LOG(VERBOSE) << "Not the sequence number I expected";
+#endif
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ uint32_t rtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ if (mPackets.size() > 0 && rtpTime != mAccessUnitRTPTime) {
+ submitAccessUnit();
+ }
+ mAccessUnitRTPTime = rtpTime;
+
+ // hexdump(buffer->data(), buffer->size());
+
+ if (buffer->size() < 2) {
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return MALFORMED_PACKET;
+ }
+
+ unsigned payloadHeader = U16_AT(buffer->data());
+ CHECK_EQ(payloadHeader >> 11, 0u); // RR=0
+ unsigned P = (payloadHeader >> 10) & 1;
+ CHECK_EQ((payloadHeader >> 9) & 1, 0u); // V=0
+ CHECK_EQ((payloadHeader >> 3) & 0x3f, 0u); // PLEN=0
+ CHECK_EQ(payloadHeader & 7, 0u); // PEBIT=0
+
+ if (P) {
+ buffer->data()[0] = 0x00;
+ buffer->data()[1] = 0x00;
+ } else {
+ buffer->setRange(2, buffer->size() - 2);
+ }
+
+ mPackets.push_back(buffer);
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return OK;
+}
+
+void AH263Assembler::submitAccessUnit() {
+ CHECK(!mPackets.empty());
+
+#if VERBOSE
+ LOG(VERBOSE) << "Access unit complete (" << mPackets.size() << " packets)";
+#endif
+
+ uint64_t ntpTime;
+ CHECK((*mPackets.begin())->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+ size_t totalSize = 0;
+ List<sp<ABuffer> >::iterator it = mPackets.begin();
+ while (it != mPackets.end()) {
+ const sp<ABuffer> &unit = *it;
+
+ totalSize += unit->size();
+ ++it;
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(totalSize);
+ size_t offset = 0;
+ it = mPackets.begin();
+ while (it != mPackets.end()) {
+ const sp<ABuffer> &unit = *it;
+
+ memcpy((uint8_t *)accessUnit->data() + offset,
+ unit->data(), unit->size());
+
+ offset += unit->size();
+
+ ++it;
+ }
+
+ accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+#if 0
+ printf(mAccessUnitDamaged ? "X" : ".");
+ fflush(stdout);
+#endif
+
+ if (mAccessUnitDamaged) {
+ accessUnit->meta()->setInt32("damaged", true);
+ }
+
+ mPackets.clear();
+ mAccessUnitDamaged = false;
+
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setObject("access-unit", accessUnit);
+ msg->post();
+}
+
+void AH263Assembler::packetLost() {
+ CHECK(mNextExpectedSeqNoValid);
+ ++mNextExpectedSeqNo;
+
+ mAccessUnitDamaged = true;
+}
+
+void AH263Assembler::onByeReceived() {
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setInt32("eos", true);
+ msg->post();
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/rtsp/AH263Assembler.h b/media/libstagefright/rtsp/AH263Assembler.h
new file mode 100644
index 0000000..2b6c625
--- /dev/null
+++ b/media/libstagefright/rtsp/AH263Assembler.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_H263_ASSEMBLER_H_
+
+#define A_H263_ASSEMBLER_H_
+
+#include "ARTPAssembler.h"
+
+#include <utils/List.h>
+
+#include <stdint.h>
+
+namespace android {
+
+struct AMessage;
+
+struct AH263Assembler : public ARTPAssembler {
+ AH263Assembler(const sp<AMessage> &notify);
+
+protected:
+ virtual ~AH263Assembler();
+
+ virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+ virtual void onByeReceived();
+ virtual void packetLost();
+
+private:
+ sp<AMessage> mNotifyMsg;
+ uint32_t mAccessUnitRTPTime;
+ bool mNextExpectedSeqNoValid;
+ uint32_t mNextExpectedSeqNo;
+ bool mAccessUnitDamaged;
+ List<sp<ABuffer> > mPackets;
+
+ AssemblyStatus addPacket(const sp<ARTPSource> &source);
+ void submitAccessUnit();
+
+ DISALLOW_EVIL_CONSTRUCTORS(AH263Assembler);
+};
+
+} // namespace android
+
+#endif // A_H263_ASSEMBLER_H_
diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
index 7e55106..6e46361 100644
--- a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
+++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp
@@ -168,4 +168,10 @@ void AMPEG4AudioAssembler::packetLost() {
mAccessUnitDamaged = true;
}
+void AMPEG4AudioAssembler::onByeReceived() {
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setInt32("eos", true);
+ msg->post();
+}
+
} // namespace android
diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.h b/media/libstagefright/rtsp/AMPEG4AudioAssembler.h
index 5c2a2dd..bf9f204 100644
--- a/media/libstagefright/rtsp/AMPEG4AudioAssembler.h
+++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.h
@@ -35,6 +35,7 @@ protected:
virtual ~AMPEG4AudioAssembler();
virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+ virtual void onByeReceived();
virtual void packetLost();
private:
diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp
index 2869d54..88336ba 100644
--- a/media/libstagefright/rtsp/APacketSource.cpp
+++ b/media/libstagefright/rtsp/APacketSource.cpp
@@ -226,8 +226,11 @@ sp<ABuffer> MakeAACCodecSpecificData(const char *params) {
APacketSource::APacketSource(
const sp<ASessionDescription> &sessionDesc, size_t index)
- : mFormat(new MetaData),
- mEOSResult(OK) {
+ : mInitCheck(NO_INIT),
+ mFormat(new MetaData),
+ mEOSResult(OK),
+ mFirstAccessUnit(true),
+ mFirstAccessUnitNTP(0) {
unsigned long PT;
AString desc;
AString params;
@@ -240,6 +243,7 @@ APacketSource::APacketSource(
mFormat->setInt64(kKeyDuration, 60 * 60 * 1000000ll);
}
+ mInitCheck = OK;
if (!strncmp(desc.c_str(), "H264/", 5)) {
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
@@ -255,8 +259,16 @@ APacketSource::APacketSource(
mFormat->setData(
kKeyAVCC, 0,
codecSpecificData->data(), codecSpecificData->size());
+ } else if (!strncmp(desc.c_str(), "H263-2000/", 10)
+ || !strncmp(desc.c_str(), "H263-1998/", 10)) {
+ mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
- } else if (!strncmp(desc.c_str(), "MP4A-LATM", 9)) {
+ int32_t width, height;
+ sessionDesc->getDimensions(index, PT, &width, &height);
+
+ mFormat->setInt32(kKeyWidth, width);
+ mFormat->setInt32(kKeyHeight, height);
+ } else if (!strncmp(desc.c_str(), "MP4A-LATM/", 10)) {
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
int32_t sampleRate, numChannels;
@@ -272,15 +284,48 @@ APacketSource::APacketSource(
mFormat->setData(
kKeyESDS, 0,
codecSpecificData->data(), codecSpecificData->size());
+ } else if (!strncmp(desc.c_str(), "AMR/", 4)) {
+ mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AMR_NB);
+
+ int32_t sampleRate, numChannels;
+ ASessionDescription::ParseFormatDesc(
+ desc.c_str(), &sampleRate, &numChannels);
+
+ mFormat->setInt32(kKeySampleRate, sampleRate);
+ mFormat->setInt32(kKeyChannelCount, numChannels);
+
+ if (sampleRate != 8000 || numChannels != 1) {
+ mInitCheck = ERROR_UNSUPPORTED;
+ }
+ } else if (!strncmp(desc.c_str(), "AMR-WB/", 7)) {
+ mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AMR_WB);
+
+ int32_t sampleRate, numChannels;
+ ASessionDescription::ParseFormatDesc(
+ desc.c_str(), &sampleRate, &numChannels);
+
+ mFormat->setInt32(kKeySampleRate, sampleRate);
+ mFormat->setInt32(kKeyChannelCount, numChannels);
+
+ if (sampleRate != 16000 || numChannels != 1) {
+ mInitCheck = ERROR_UNSUPPORTED;
+ }
} else {
- TRESPASS();
+ mInitCheck = ERROR_UNSUPPORTED;
}
}
APacketSource::~APacketSource() {
}
+status_t APacketSource::initCheck() const {
+ return mInitCheck;
+}
+
status_t APacketSource::start(MetaData *params) {
+ mFirstAccessUnit = true;
+ mFirstAccessUnitNTP = 0;
+
return OK;
}
@@ -308,10 +353,23 @@ status_t APacketSource::read(
CHECK(buffer->meta()->findInt64(
"ntp-time", (int64_t *)&ntpTime));
+ MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
+ mediaBuffer->meta_data()->setInt64(kKeyNTPTime, ntpTime);
+
+ if (mFirstAccessUnit) {
+ mFirstAccessUnit = false;
+ mFirstAccessUnitNTP = ntpTime;
+ }
+ if (ntpTime > mFirstAccessUnitNTP) {
+ ntpTime -= mFirstAccessUnitNTP;
+ } else {
+ ntpTime = 0;
+ }
+
int64_t timeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
- MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
+
memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
*out = mediaBuffer;
@@ -342,4 +400,31 @@ void APacketSource::signalEOS(status_t result) {
mCondition.signal();
}
+int64_t APacketSource::getQueuedDuration(bool *eos) {
+ Mutex::Autolock autoLock(mLock);
+
+ *eos = (mEOSResult != OK);
+
+ if (mBuffers.empty()) {
+ return 0;
+ }
+
+ sp<ABuffer> buffer = *mBuffers.begin();
+
+ uint64_t ntpTime;
+ CHECK(buffer->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+ int64_t firstTimeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
+
+ buffer = *--mBuffers.end();
+
+ CHECK(buffer->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+ int64_t lastTimeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
+
+ return lastTimeUs - firstTimeUs;
+}
+
} // namespace android
diff --git a/media/libstagefright/rtsp/APacketSource.h b/media/libstagefright/rtsp/APacketSource.h
index 4040eee..647da6e 100644
--- a/media/libstagefright/rtsp/APacketSource.h
+++ b/media/libstagefright/rtsp/APacketSource.h
@@ -31,6 +31,8 @@ struct ASessionDescription;
struct APacketSource : public MediaSource {
APacketSource(const sp<ASessionDescription> &sessionDesc, size_t index);
+ status_t initCheck() const;
+
virtual status_t start(MetaData *params = NULL);
virtual status_t stop();
virtual sp<MetaData> getFormat();
@@ -41,10 +43,14 @@ struct APacketSource : public MediaSource {
void queueAccessUnit(const sp<ABuffer> &buffer);
void signalEOS(status_t result);
+ int64_t getQueuedDuration(bool *eos);
+
protected:
virtual ~APacketSource();
private:
+ status_t mInitCheck;
+
Mutex mLock;
Condition mCondition;
@@ -52,6 +58,9 @@ private:
List<sp<ABuffer> > mBuffers;
status_t mEOSResult;
+ bool mFirstAccessUnit;
+ uint64_t mFirstAccessUnitNTP;
+
DISALLOW_EVIL_CONSTRUCTORS(APacketSource);
};
diff --git a/media/libstagefright/rtsp/ARTPAssembler.h b/media/libstagefright/rtsp/ARTPAssembler.h
index 892bd65..e598088 100644
--- a/media/libstagefright/rtsp/ARTPAssembler.h
+++ b/media/libstagefright/rtsp/ARTPAssembler.h
@@ -37,6 +37,7 @@ struct ARTPAssembler : public RefBase {
ARTPAssembler();
void onPacketReceived(const sp<ARTPSource> &source);
+ virtual void onByeReceived() = 0;
protected:
static void PropagateTimes(
diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp
index a4413f0..9abdab4 100644
--- a/media/libstagefright/rtsp/ARTPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTPConnection.cpp
@@ -23,18 +23,17 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/hexdump.h>
#include <arpa/inet.h>
#include <sys/socket.h>
-#define VERBOSE 0
-
-#if VERBOSE
-#include "hexdump.h"
-#endif
+#define IGNORE_RTCP_TIME 0
namespace android {
+static const size_t kMaxUDPSize = 1500;
+
static uint16_t u16at(const uint8_t *data) {
return data[0] << 8 | data[1];
}
@@ -56,10 +55,15 @@ struct ARTPConnection::StreamInfo {
sp<ASessionDescription> mSessionDesc;
size_t mIndex;
sp<AMessage> mNotifyMsg;
+ KeyedVector<uint32_t, sp<ARTPSource> > mSources;
+
+ int32_t mNumRTCPPacketsReceived;
+ struct sockaddr_in mRemoteRTCPAddr;
};
ARTPConnection::ARTPConnection()
- : mPollEventPending(false) {
+ : mPollEventPending(false),
+ mLastReceiverReportTimeUs(-1) {
}
ARTPConnection::~ARTPConnection() {
@@ -176,6 +180,9 @@ void ARTPConnection::onAddStream(const sp<AMessage> &msg) {
CHECK(msg->findSize("index", &info->mIndex));
CHECK(msg->findMessage("notify", &info->mNotifyMsg));
+ info->mNumRTCPPacketsReceived = 0;
+ memset(&info->mRemoteRTCPAddr, 0, sizeof(info->mRemoteRTCPAddr));
+
postPollEvent();
}
@@ -252,21 +259,59 @@ void ARTPConnection::onPollStreams() {
}
postPollEvent();
+
+ int64_t nowUs = ALooper::GetNowUs();
+ if (mLastReceiverReportTimeUs <= 0
+ || mLastReceiverReportTimeUs + 5000000ll <= nowUs) {
+ sp<ABuffer> buffer = new ABuffer(kMaxUDPSize);
+ for (List<StreamInfo>::iterator it = mStreams.begin();
+ it != mStreams.end(); ++it) {
+ StreamInfo *s = &*it;
+
+ if (s->mNumRTCPPacketsReceived == 0) {
+ // We have never received any RTCP packets on this stream,
+ // we don't even know where to send a report.
+ continue;
+ }
+
+ buffer->setRange(0, 0);
+
+ for (size_t i = 0; i < s->mSources.size(); ++i) {
+ sp<ARTPSource> source = s->mSources.valueAt(i);
+
+ source->addReceiverReport(buffer);
+ source->addFIR(buffer);
+ }
+
+ if (buffer->size() > 0) {
+ LOG(VERBOSE) << "Sending RR...";
+
+ ssize_t n = sendto(
+ s->mRTCPSocket, buffer->data(), buffer->size(), 0,
+ (const struct sockaddr *)&s->mRemoteRTCPAddr,
+ sizeof(s->mRemoteRTCPAddr));
+ CHECK_EQ(n, (ssize_t)buffer->size());
+
+ mLastReceiverReportTimeUs = nowUs;
+ }
+ }
+ }
}
status_t ARTPConnection::receive(StreamInfo *s, bool receiveRTP) {
sp<ABuffer> buffer = new ABuffer(65536);
- struct sockaddr_in from;
- socklen_t fromSize = sizeof(from);
+ socklen_t remoteAddrLen =
+ (!receiveRTP && s->mNumRTCPPacketsReceived == 0)
+ ? sizeof(s->mRemoteRTCPAddr) : 0;
ssize_t nbytes = recvfrom(
receiveRTP ? s->mRTPSocket : s->mRTCPSocket,
buffer->data(),
buffer->capacity(),
0,
- (struct sockaddr *)&from,
- &fromSize);
+ remoteAddrLen > 0 ? (struct sockaddr *)&s->mRemoteRTCPAddr : NULL,
+ remoteAddrLen > 0 ? &remoteAddrLen : NULL);
if (nbytes < 0) {
return -1;
@@ -278,6 +323,7 @@ status_t ARTPConnection::receive(StreamInfo *s, bool receiveRTP) {
if (receiveRTP) {
err = parseRTP(s, buffer);
} else {
+ ++s->mNumRTCPPacketsReceived;
err = parseRTCP(s, buffer);
}
@@ -346,18 +392,7 @@ status_t ARTPConnection::parseRTP(StreamInfo *s, const sp<ABuffer> &buffer) {
uint32_t srcId = u32at(&data[8]);
- sp<ARTPSource> source;
- ssize_t index = mSources.indexOfKey(srcId);
- if (index < 0) {
- index = mSources.size();
-
- source = new ARTPSource(
- srcId, s->mSessionDesc, s->mIndex, s->mNotifyMsg);
-
- mSources.add(srcId, source);
- } else {
- source = mSources.valueAt(index);
- }
+ sp<ARTPSource> source = findSource(s, srcId);
uint32_t rtpTime = u32at(&data[4]);
@@ -368,24 +403,6 @@ status_t ARTPConnection::parseRTP(StreamInfo *s, const sp<ABuffer> &buffer) {
meta->setInt32("M", data[1] >> 7);
buffer->setInt32Data(u16at(&data[2]));
-
-#if VERBOSE
- printf("RTP = {\n"
- " PT: %d\n"
- " sequence number: %d\n"
- " RTP-time: 0x%08x\n"
- " M: %d\n"
- " SSRC: 0x%08x\n"
- "}\n",
- data[1] & 0x7f,
- u16at(&data[2]),
- rtpTime,
- data[1] >> 7,
- srcId);
-
- // hexdump(&data[payloadOffset], size - payloadOffset);
-#endif
-
buffer->setRange(payloadOffset, size - payloadOffset);
source->processRTPPacket(buffer);
@@ -436,14 +453,27 @@ status_t ARTPConnection::parseRTCP(StreamInfo *s, const sp<ABuffer> &buffer) {
break;
}
- default:
+ case 201: // RR
+ case 202: // SDES
+ case 204: // APP
+ break;
+
+ case 205: // TSFB (transport layer specific feedback)
+ case 206: // PSFB (payload specific feedback)
+ // hexdump(data, headerLength);
+ break;
+
+ case 203:
{
-#if VERBOSE
- printf("Unknown RTCP packet type %d of size %ld\n",
- data[1], headerLength);
+ parseBYE(s, data, headerLength);
+ break;
+ }
- hexdump(data, headerLength);
-#endif
+ default:
+ {
+ LOG(WARNING) << "Unknown RTCP packet type "
+ << (unsigned)data[1]
+ << " of size " << headerLength;
break;
}
}
@@ -455,6 +485,24 @@ status_t ARTPConnection::parseRTCP(StreamInfo *s, const sp<ABuffer> &buffer) {
return OK;
}
+status_t ARTPConnection::parseBYE(
+ StreamInfo *s, const uint8_t *data, size_t size) {
+ size_t SC = data[0] & 0x3f;
+
+ if (SC == 0 || size < (4 + SC * 4)) {
+ // Packet too short for the minimal BYE header.
+ return -1;
+ }
+
+ uint32_t id = u32at(&data[4]);
+
+ sp<ARTPSource> source = findSource(s, id);
+
+ source->byeReceived();
+
+ return OK;
+}
+
status_t ARTPConnection::parseSR(
StreamInfo *s, const uint8_t *data, size_t size) {
size_t RC = data[0] & 0x1f;
@@ -468,31 +516,43 @@ status_t ARTPConnection::parseSR(
uint64_t ntpTime = u64at(&data[8]);
uint32_t rtpTime = u32at(&data[16]);
-#if VERBOSE
- printf("SR = {\n"
- " SSRC: 0x%08x\n"
- " NTP-time: 0x%016llx\n"
- " RTP-time: 0x%08x\n"
- "}\n",
- id, ntpTime, rtpTime);
+#if 0
+ LOG(INFO) << StringPrintf(
+ "XXX timeUpdate: ssrc=0x%08x, rtpTime %u == ntpTime %.3f",
+ id,
+ rtpTime, (ntpTime >> 32) + (double)(ntpTime & 0xffffffff) / (1ll << 32));
+#endif
+
+ sp<ARTPSource> source = findSource(s, id);
+
+#if !IGNORE_RTCP_TIME
+ source->timeUpdate(rtpTime, ntpTime);
#endif
+ return 0;
+}
+
+sp<ARTPSource> ARTPConnection::findSource(StreamInfo *info, uint32_t srcId) {
sp<ARTPSource> source;
- ssize_t index = mSources.indexOfKey(id);
+ ssize_t index = info->mSources.indexOfKey(srcId);
if (index < 0) {
- index = mSources.size();
+ index = info->mSources.size();
source = new ARTPSource(
- id, s->mSessionDesc, s->mIndex, s->mNotifyMsg);
+ srcId, info->mSessionDesc, info->mIndex, info->mNotifyMsg);
+
+#if IGNORE_RTCP_TIME
+ // For H.263 gtalk to work...
+ source->timeUpdate(0, 0);
+ source->timeUpdate(30, 0x100000000ll);
+#endif
- mSources.add(id, source);
+ info->mSources.add(srcId, source);
} else {
- source = mSources.valueAt(index);
+ source = info->mSources.valueAt(index);
}
- source->timeUpdate(rtpTime, ntpTime);
-
- return 0;
+ return source;
}
} // namespace android
diff --git a/media/libstagefright/rtsp/ARTPConnection.h b/media/libstagefright/rtsp/ARTPConnection.h
index c77e3a4..49839ad 100644
--- a/media/libstagefright/rtsp/ARTPConnection.h
+++ b/media/libstagefright/rtsp/ARTPConnection.h
@@ -59,19 +59,22 @@ private:
struct StreamInfo;
List<StreamInfo> mStreams;
- KeyedVector<uint32_t, sp<ARTPSource> > mSources;
-
bool mPollEventPending;
+ int64_t mLastReceiverReportTimeUs;
void onAddStream(const sp<AMessage> &msg);
void onRemoveStream(const sp<AMessage> &msg);
void onPollStreams();
+ void onSendReceiverReports();
status_t receive(StreamInfo *info, bool receiveRTP);
status_t parseRTP(StreamInfo *info, const sp<ABuffer> &buffer);
status_t parseRTCP(StreamInfo *info, const sp<ABuffer> &buffer);
status_t parseSR(StreamInfo *info, const uint8_t *data, size_t size);
+ status_t parseBYE(StreamInfo *info, const uint8_t *data, size_t size);
+
+ sp<ARTPSource> findSource(StreamInfo *info, uint32_t id);
void postPollEvent();
diff --git a/media/libstagefright/rtsp/ARTPSession.cpp b/media/libstagefright/rtsp/ARTPSession.cpp
new file mode 100644
index 0000000..0e0f45a
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPSession.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ARTPSession.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+#include <ctype.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include "APacketSource.h"
+#include "ARTPConnection.h"
+#include "ASessionDescription.h"
+
+namespace android {
+
+ARTPSession::ARTPSession()
+ : mInitCheck(NO_INIT) {
+}
+
+status_t ARTPSession::setup(const sp<ASessionDescription> &desc) {
+ CHECK_EQ(mInitCheck, (status_t)NO_INIT);
+
+ mDesc = desc;
+
+ mRTPConn = new ARTPConnection;
+ looper()->registerHandler(mRTPConn);
+
+ for (size_t i = 1; i < mDesc->countTracks(); ++i) {
+ AString connection;
+ if (!mDesc->findAttribute(i, "c=", &connection)) {
+ // No per-stream connection information, try global fallback.
+ if (!mDesc->findAttribute(0, "c=", &connection)) {
+ LOG(ERROR) << "Unable to find connection attribtue.";
+ return mInitCheck;
+ }
+ }
+ if (!(connection == "IN IP4 127.0.0.1")) {
+ LOG(ERROR) << "We only support localhost connections for now.";
+ return mInitCheck;
+ }
+
+ unsigned port;
+ if (!validateMediaFormat(i, &port) || (port & 1) != 0) {
+ LOG(ERROR) << "Invalid media format.";
+ return mInitCheck;
+ }
+
+ sp<APacketSource> source = new APacketSource(mDesc, i);
+ if (source->initCheck() != OK) {
+ LOG(ERROR) << "Unsupported format.";
+ return mInitCheck;
+ }
+
+ int rtpSocket = MakeUDPSocket(port);
+ int rtcpSocket = MakeUDPSocket(port + 1);
+
+ mTracks.push(TrackInfo());
+ TrackInfo *info = &mTracks.editItemAt(mTracks.size() - 1);
+ info->mRTPSocket = rtpSocket;
+ info->mRTCPSocket = rtcpSocket;
+
+ sp<AMessage> notify = new AMessage(kWhatAccessUnitComplete, id());
+ notify->setSize("track-index", mTracks.size() - 1);
+
+ mRTPConn->addStream(rtpSocket, rtcpSocket, mDesc, i, notify);
+
+ info->mPacketSource = source;
+ }
+
+ mInitCheck = OK;
+
+ return OK;
+}
+
+// static
+int ARTPSession::MakeUDPSocket(unsigned port) {
+ int s = socket(AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(s, 0);
+
+ struct sockaddr_in addr;
+ memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = htons(port);
+
+ CHECK_EQ(0, bind(s, (const struct sockaddr *)&addr, sizeof(addr)));
+
+ return s;
+}
+
+ARTPSession::~ARTPSession() {
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ TrackInfo *info = &mTracks.editItemAt(i);
+
+ info->mPacketSource->signalEOS(UNKNOWN_ERROR);
+
+ close(info->mRTPSocket);
+ close(info->mRTCPSocket);
+ }
+}
+
+void ARTPSession::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatAccessUnitComplete:
+ {
+ size_t trackIndex;
+ CHECK(msg->findSize("track-index", &trackIndex));
+
+ int32_t eos;
+ if (msg->findInt32("eos", &eos) && eos) {
+ TrackInfo *info = &mTracks.editItemAt(trackIndex);
+ info->mPacketSource->signalEOS(ERROR_END_OF_STREAM);
+ break;
+ }
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject("access-unit", &obj));
+
+ sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get());
+
+ uint64_t ntpTime;
+ CHECK(accessUnit->meta()->findInt64(
+ "ntp-time", (int64_t *)&ntpTime));
+
+#if 0
+#if 0
+ printf("access unit complete size=%d\tntp-time=0x%016llx\n",
+ accessUnit->size(), ntpTime);
+#else
+ LOG(INFO) << "access unit complete, "
+ << "size=" << accessUnit->size() << ", "
+ << "ntp-time=" << ntpTime;
+ hexdump(accessUnit->data(), accessUnit->size());
+#endif
+#endif
+
+#if 0
+ CHECK_GE(accessUnit->size(), 5u);
+ CHECK(!memcmp("\x00\x00\x00\x01", accessUnit->data(), 4));
+ unsigned x = accessUnit->data()[4];
+
+ LOG(INFO) << "access unit complete: "
+ << StringPrintf("nalType=0x%02x, nalRefIdc=0x%02x",
+ x & 0x1f, (x & 0x60) >> 5);
+#endif
+
+ accessUnit->meta()->setInt64("ntp-time", ntpTime);
+
+#if 0
+ int32_t damaged;
+ if (accessUnit->meta()->findInt32("damaged", &damaged)
+ && damaged != 0) {
+ LOG(INFO) << "ignoring damaged AU";
+ } else
+#endif
+ {
+ TrackInfo *info = &mTracks.editItemAt(trackIndex);
+ info->mPacketSource->queueAccessUnit(accessUnit);
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
+bool ARTPSession::validateMediaFormat(size_t index, unsigned *port) const {
+ AString format;
+ mDesc->getFormat(index, &format);
+
+ ssize_t i = format.find(" ");
+ if (i < 0) {
+ return false;
+ }
+
+ ++i;
+ size_t j = i;
+ while (isdigit(format.c_str()[j])) {
+ ++j;
+ }
+ if (format.c_str()[j] != ' ') {
+ return false;
+ }
+
+ AString portString(format, i, j - i);
+
+ char *end;
+ unsigned long x = strtoul(portString.c_str(), &end, 10);
+ if (end == portString.c_str() || *end != '\0') {
+ return false;
+ }
+
+ if (x == 0 || x > 65535) {
+ return false;
+ }
+
+ *port = x;
+
+ return true;
+}
+
+size_t ARTPSession::countTracks() {
+ return mTracks.size();
+}
+
+sp<MediaSource> ARTPSession::trackAt(size_t index) {
+ CHECK_LT(index, mTracks.size());
+ return mTracks.editItemAt(index).mPacketSource;
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/ARTPSession.h b/media/libstagefright/rtsp/ARTPSession.h
new file mode 100644
index 0000000..9bff74c
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPSession.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_RTP_SESSION_H_
+
+#define A_RTP_SESSION_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+namespace android {
+
+struct APacketSource;
+struct ARTPConnection;
+struct ASessionDescription;
+struct MediaSource;
+
+struct ARTPSession : public AHandler {
+ ARTPSession();
+
+ status_t setup(const sp<ASessionDescription> &desc);
+
+ size_t countTracks();
+ sp<MediaSource> trackAt(size_t index);
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+ virtual ~ARTPSession();
+
+private:
+ enum {
+ kWhatAccessUnitComplete = 'accu'
+ };
+
+ struct TrackInfo {
+ int mRTPSocket;
+ int mRTCPSocket;
+
+ sp<APacketSource> mPacketSource;
+ };
+
+ status_t mInitCheck;
+ sp<ASessionDescription> mDesc;
+ sp<ARTPConnection> mRTPConn;
+
+ Vector<TrackInfo> mTracks;
+
+ bool validateMediaFormat(size_t index, unsigned *port) const;
+ static int MakeUDPSocket(unsigned port);
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTPSession);
+};
+
+} // namespace android
+
+#endif // A_RTP_SESSION_H_
diff --git a/media/libstagefright/rtsp/ARTPSource.cpp b/media/libstagefright/rtsp/ARTPSource.cpp
index f05daa8..2aa0c1f 100644
--- a/media/libstagefright/rtsp/ARTPSource.cpp
+++ b/media/libstagefright/rtsp/ARTPSource.cpp
@@ -16,7 +16,9 @@
#include "ARTPSource.h"
+#include "AAMRAssembler.h"
#include "AAVCAssembler.h"
+#include "AH263Assembler.h"
#include "AMPEG4AudioAssembler.h"
#include "ASessionDescription.h"
@@ -24,10 +26,12 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
-#define VERBOSE 0
+#define BE_VERBOSE 0
namespace android {
+static const uint32_t kSourceID = 0xdeadbeef;
+
ARTPSource::ARTPSource(
uint32_t id,
const sp<ASessionDescription> &sessionDesc, size_t index,
@@ -35,7 +39,12 @@ ARTPSource::ARTPSource(
: mID(id),
mHighestSeqNumber(0),
mNumBuffersReceived(0),
- mNumTimes(0) {
+ mNumTimes(0),
+ mLastNTPTime(0),
+ mLastNTPTimeUpdateUs(0),
+ mIssueFIRRequests(false),
+ mLastFIRRequestUs(-1),
+ mNextFIRSeqNo((rand() * 256.0) / RAND_MAX) {
unsigned long PT;
AString desc;
AString params;
@@ -43,8 +52,16 @@ ARTPSource::ARTPSource(
if (!strncmp(desc.c_str(), "H264/", 5)) {
mAssembler = new AAVCAssembler(notify);
- } else if (!strncmp(desc.c_str(), "MP4A-LATM", 9)) {
+ mIssueFIRRequests = true;
+ } else if (!strncmp(desc.c_str(), "MP4A-LATM/", 10)) {
mAssembler = new AMPEG4AudioAssembler(notify);
+ } else if (!strncmp(desc.c_str(), "H263-1998/", 10)
+ || !strncmp(desc.c_str(), "H263-2000/", 10)) {
+ mAssembler = new AH263Assembler(notify);
+ } else if (!strncmp(desc.c_str(), "AMR/", 4)) {
+ mAssembler = new AAMRAssembler(notify, false /* isWide */, params);
+ } else if (!strncmp(desc.c_str(), "AMR-WB/", 7)) {
+ mAssembler = new AAMRAssembler(notify, true /* isWide */, params);
} else {
TRESPASS();
}
@@ -55,7 +72,9 @@ static uint32_t AbsDiff(uint32_t seq1, uint32_t seq2) {
}
void ARTPSource::processRTPPacket(const sp<ABuffer> &buffer) {
- if (queuePacket(buffer) && mNumTimes == 2 && mAssembler != NULL) {
+ if (queuePacket(buffer)
+ && mNumTimes == 2
+ && mAssembler != NULL) {
mAssembler->onPacketReceived(this);
}
@@ -63,10 +82,13 @@ void ARTPSource::processRTPPacket(const sp<ABuffer> &buffer) {
}
void ARTPSource::timeUpdate(uint32_t rtpTime, uint64_t ntpTime) {
-#if VERBOSE
+#if BE_VERBOSE
LOG(VERBOSE) << "timeUpdate";
#endif
+ mLastNTPTime = ntpTime;
+ mLastNTPTimeUpdateUs = ALooper::GetNowUs();
+
if (mNumTimes == 2) {
mNTPTime[0] = mNTPTime[1];
mRTPTime[0] = mRTPTime[1];
@@ -89,6 +111,13 @@ void ARTPSource::timeUpdate(uint32_t rtpTime, uint64_t ntpTime) {
}
bool ARTPSource::queuePacket(const sp<ABuffer> &buffer) {
+#if 1
+ if (mNumTimes != 2) {
+ // Drop incoming packets until we've established a time base.
+ return false;
+ }
+#endif
+
uint32_t seqNum = (uint32_t)buffer->int32Data();
if (mNumTimes == 2) {
@@ -194,7 +223,7 @@ void ARTPSource::dump() const {
#if 0
AString out;
-
+
out.append(tmp);
out.append(" [");
@@ -245,6 +274,120 @@ uint64_t ARTPSource::RTP2NTP(uint32_t rtpTime) const {
/ (double)(mRTPTime[1] - mRTPTime[0]);
}
+void ARTPSource::byeReceived() {
+ mAssembler->onByeReceived();
+}
+
+void ARTPSource::addFIR(const sp<ABuffer> &buffer) {
+ if (!mIssueFIRRequests) {
+ return;
+ }
+
+ int64_t nowUs = ALooper::GetNowUs();
+ if (mLastFIRRequestUs >= 0 && mLastFIRRequestUs + 5000000ll > nowUs) {
+ // Send FIR requests at most every 5 secs.
+ return;
+ }
+
+ mLastFIRRequestUs = nowUs;
+
+ if (buffer->size() + 20 > buffer->capacity()) {
+ LOG(WARNING) << "RTCP buffer too small to accomodate FIR.";
+ return;
+ }
+
+ uint8_t *data = buffer->data() + buffer->size();
+
+ data[0] = 0x80 | 4;
+ data[1] = 206; // PSFB
+ data[2] = 0;
+ data[3] = 4;
+ data[4] = kSourceID >> 24;
+ data[5] = (kSourceID >> 16) & 0xff;
+ data[6] = (kSourceID >> 8) & 0xff;
+ data[7] = kSourceID & 0xff;
+
+ data[8] = 0x00; // SSRC of media source (unused)
+ data[9] = 0x00;
+ data[10] = 0x00;
+ data[11] = 0x00;
+
+ data[12] = mID >> 24;
+ data[13] = (mID >> 16) & 0xff;
+ data[14] = (mID >> 8) & 0xff;
+ data[15] = mID & 0xff;
+
+ data[16] = mNextFIRSeqNo++; // Seq Nr.
+
+ data[17] = 0x00; // Reserved
+ data[18] = 0x00;
+ data[19] = 0x00;
+
+ buffer->setRange(buffer->offset(), buffer->size() + 20);
+
+ LOG(VERBOSE) << "Added FIR request.";
+}
+
+void ARTPSource::addReceiverReport(const sp<ABuffer> &buffer) {
+ if (buffer->size() + 32 > buffer->capacity()) {
+ LOG(WARNING) << "RTCP buffer too small to accomodate RR.";
+ return;
+ }
+
+ uint8_t *data = buffer->data() + buffer->size();
+
+ data[0] = 0x80 | 1;
+ data[1] = 201; // RR
+ data[2] = 0;
+ data[3] = 7;
+ data[4] = kSourceID >> 24;
+ data[5] = (kSourceID >> 16) & 0xff;
+ data[6] = (kSourceID >> 8) & 0xff;
+ data[7] = kSourceID & 0xff;
+
+ data[8] = mID >> 24;
+ data[9] = (mID >> 16) & 0xff;
+ data[10] = (mID >> 8) & 0xff;
+ data[11] = mID & 0xff;
+
+ data[12] = 0x00; // fraction lost
+
+ data[13] = 0x00; // cumulative lost
+ data[14] = 0x00;
+ data[15] = 0x00;
+
+ data[16] = mHighestSeqNumber >> 24;
+ data[17] = (mHighestSeqNumber >> 16) & 0xff;
+ data[18] = (mHighestSeqNumber >> 8) & 0xff;
+ data[19] = mHighestSeqNumber & 0xff;
+
+ data[20] = 0x00; // Interarrival jitter
+ data[21] = 0x00;
+ data[22] = 0x00;
+ data[23] = 0x00;
+
+ uint32_t LSR = 0;
+ uint32_t DLSR = 0;
+ if (mLastNTPTime != 0) {
+ LSR = (mLastNTPTime >> 16) & 0xffffffff;
+
+ DLSR = (uint32_t)
+ ((ALooper::GetNowUs() - mLastNTPTimeUpdateUs) * 65536.0 / 1E6);
+ }
+
+ data[24] = LSR >> 24;
+ data[25] = (LSR >> 16) & 0xff;
+ data[26] = (LSR >> 8) & 0xff;
+ data[27] = LSR & 0xff;
+
+ data[28] = DLSR >> 24;
+ data[29] = (DLSR >> 16) & 0xff;
+ data[30] = (DLSR >> 8) & 0xff;
+ data[31] = DLSR & 0xff;
+
+ buffer->setRange(buffer->offset(), buffer->size() + 32);
+}
+
} // namespace android
diff --git a/media/libstagefright/rtsp/ARTPSource.h b/media/libstagefright/rtsp/ARTPSource.h
index b93cd56..8e483a8 100644
--- a/media/libstagefright/rtsp/ARTPSource.h
+++ b/media/libstagefright/rtsp/ARTPSource.h
@@ -39,9 +39,13 @@ struct ARTPSource : public RefBase {
void processRTPPacket(const sp<ABuffer> &buffer);
void timeUpdate(uint32_t rtpTime, uint64_t ntpTime);
+ void byeReceived();
List<sp<ABuffer> > *queue() { return &mQueue; }
+ void addReceiverReport(const sp<ABuffer> &buffer);
+ void addFIR(const sp<ABuffer> &buffer);
+
private:
uint32_t mID;
uint32_t mHighestSeqNumber;
@@ -54,6 +58,13 @@ private:
uint64_t mNTPTime[2];
uint32_t mRTPTime[2];
+ uint64_t mLastNTPTime;
+ int64_t mLastNTPTimeUpdateUs;
+
+ bool mIssueFIRRequests;
+ int64_t mLastFIRRequestUs;
+ uint8_t mNextFIRSeqNo;
+
uint64_t RTP2NTP(uint32_t rtpTime) const;
bool queuePacket(const sp<ABuffer> &buffer);
diff --git a/media/libstagefright/rtsp/ARTPWriter.cpp b/media/libstagefright/rtsp/ARTPWriter.cpp
new file mode 100644
index 0000000..cc23856
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPWriter.cpp
@@ -0,0 +1,813 @@
+#include "ARTPWriter.h"
+
+#include <fcntl.h>
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/ByteOrder.h>
+
+#define PT 97
+#define PT_STR "97"
+
+namespace android {
+
+// static const size_t kMaxPacketSize = 65507; // maximum payload in UDP over IP
+static const size_t kMaxPacketSize = 1500;
+
+static int UniformRand(int limit) {
+ return ((double)rand() * limit) / RAND_MAX;
+}
+
+ARTPWriter::ARTPWriter(int fd)
+ : mFlags(0),
+ mFd(fd),
+ mLooper(new ALooper),
+ mReflector(new AHandlerReflector<ARTPWriter>(this)) {
+ CHECK_GE(fd, 0);
+
+ mLooper->registerHandler(mReflector);
+ mLooper->start();
+
+ mSocket = socket(AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(mSocket, 0);
+
+ memset(mRTPAddr.sin_zero, 0, sizeof(mRTPAddr.sin_zero));
+ mRTPAddr.sin_family = AF_INET;
+
+#if 1
+ mRTPAddr.sin_addr.s_addr = INADDR_ANY;
+#else
+ mRTPAddr.sin_addr.s_addr = inet_addr("172.19.19.74");
+#endif
+
+ mRTPAddr.sin_port = htons(5634);
+ CHECK_EQ(0, ntohs(mRTPAddr.sin_port) & 1);
+
+ mRTCPAddr = mRTPAddr;
+ mRTCPAddr.sin_port = htons(ntohs(mRTPAddr.sin_port) | 1);
+
+#if LOG_TO_FILES
+ mRTPFd = open(
+ "/data/misc/rtpout.bin",
+ O_WRONLY | O_CREAT | O_TRUNC,
+ 0644);
+ CHECK_GE(mRTPFd, 0);
+
+ mRTCPFd = open(
+ "/data/misc/rtcpout.bin",
+ O_WRONLY | O_CREAT | O_TRUNC,
+ 0644);
+ CHECK_GE(mRTCPFd, 0);
+#endif
+}
+
+ARTPWriter::~ARTPWriter() {
+#if LOG_TO_FILES
+ close(mRTCPFd);
+ mRTCPFd = -1;
+
+ close(mRTPFd);
+ mRTPFd = -1;
+#endif
+
+ close(mSocket);
+ mSocket = -1;
+
+ close(mFd);
+ mFd = -1;
+}
+
+status_t ARTPWriter::addSource(const sp<MediaSource> &source) {
+ mSource = source;
+ return OK;
+}
+
+bool ARTPWriter::reachedEOS() {
+ Mutex::Autolock autoLock(mLock);
+ return (mFlags & kFlagEOS) != 0;
+}
+
+status_t ARTPWriter::start(MetaData *params) {
+ Mutex::Autolock autoLock(mLock);
+ if (mFlags & kFlagStarted) {
+ return INVALID_OPERATION;
+ }
+
+ mFlags &= ~kFlagEOS;
+ mSourceID = rand();
+ mSeqNo = UniformRand(65536);
+ mRTPTimeBase = rand();
+ mNumRTPSent = 0;
+ mNumRTPOctetsSent = 0;
+ mLastRTPTime = 0;
+ mLastNTPTime = 0;
+ mNumSRsSent = 0;
+
+ const char *mime;
+ CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+
+ mMode = INVALID;
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
+ mMode = H264;
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_H263)) {
+ mMode = H263;
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) {
+ mMode = AMR_NB;
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
+ mMode = AMR_WB;
+ } else {
+ TRESPASS();
+ }
+
+ (new AMessage(kWhatStart, mReflector->id()))->post();
+
+ while (!(mFlags & kFlagStarted)) {
+ mCondition.wait(mLock);
+ }
+
+ return OK;
+}
+
+void ARTPWriter::stop() {
+ Mutex::Autolock autoLock(mLock);
+ if (!(mFlags & kFlagStarted)) {
+ return;
+ }
+
+ (new AMessage(kWhatStop, mReflector->id()))->post();
+
+ while (mFlags & kFlagStarted) {
+ mCondition.wait(mLock);
+ }
+}
+
+void ARTPWriter::pause() {
+}
+
+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);
+ }
+}
+
+void ARTPWriter::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatStart:
+ {
+ CHECK_EQ(mSource->start(), (status_t)OK);
+
+#if 0
+ if (mMode == H264) {
+ MediaBuffer *buffer;
+ CHECK_EQ(mSource->read(&buffer), (status_t)OK);
+
+ StripStartcode(buffer);
+ makeH264SPropParamSets(buffer);
+ buffer->release();
+ buffer = NULL;
+ }
+
+ dumpSessionDesc();
+#endif
+
+ {
+ Mutex::Autolock autoLock(mLock);
+ mFlags |= kFlagStarted;
+ mCondition.signal();
+ }
+
+ (new AMessage(kWhatRead, mReflector->id()))->post();
+ (new AMessage(kWhatSendSR, mReflector->id()))->post();
+ break;
+ }
+
+ case kWhatStop:
+ {
+ CHECK_EQ(mSource->stop(), (status_t)OK);
+
+ sendBye();
+
+ {
+ Mutex::Autolock autoLock(mLock);
+ mFlags &= ~kFlagStarted;
+ mCondition.signal();
+ }
+ break;
+ }
+
+ case kWhatRead:
+ {
+ {
+ Mutex::Autolock autoLock(mLock);
+ if (!(mFlags & kFlagStarted)) {
+ break;
+ }
+ }
+
+ onRead(msg);
+ break;
+ }
+
+ case kWhatSendSR:
+ {
+ {
+ Mutex::Autolock autoLock(mLock);
+ if (!(mFlags & kFlagStarted)) {
+ break;
+ }
+ }
+
+ onSendSR(msg);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
+void ARTPWriter::onRead(const sp<AMessage> &msg) {
+ MediaBuffer *mediaBuf;
+ status_t err = mSource->read(&mediaBuf);
+
+ if (err != OK) {
+ LOG(INFO) << "reached EOS.";
+
+ Mutex::Autolock autoLock(mLock);
+ mFlags |= kFlagEOS;
+ return;
+ }
+
+ if (mediaBuf->range_length() > 0) {
+ LOG(VERBOSE) << "read buffer of size " << mediaBuf->range_length();
+
+ if (mMode == H264) {
+ StripStartcode(mediaBuf);
+ sendAVCData(mediaBuf);
+ } else if (mMode == H263) {
+ sendH263Data(mediaBuf);
+ } else if (mMode == AMR_NB || mMode == AMR_WB) {
+ sendAMRData(mediaBuf);
+ }
+ }
+
+ mediaBuf->release();
+ mediaBuf = NULL;
+
+ msg->post();
+}
+
+void ARTPWriter::onSendSR(const sp<AMessage> &msg) {
+ sp<ABuffer> buffer = new ABuffer(65536);
+ buffer->setRange(0, 0);
+
+ addSR(buffer);
+ addSDES(buffer);
+
+ send(buffer, true /* isRTCP */);
+
+ ++mNumSRsSent;
+ msg->post(3000000);
+}
+
+void ARTPWriter::send(const sp<ABuffer> &buffer, bool isRTCP) {
+ ssize_t n = sendto(
+ mSocket, buffer->data(), buffer->size(), 0,
+ (const struct sockaddr *)(isRTCP ? &mRTCPAddr : &mRTPAddr),
+ sizeof(mRTCPAddr));
+
+ CHECK_EQ(n, (ssize_t)buffer->size());
+
+#if LOG_TO_FILES
+ int fd = isRTCP ? mRTCPFd : mRTPFd;
+
+ uint32_t ms = tolel(ALooper::GetNowUs() / 1000ll);
+ uint32_t length = tolel(buffer->size());
+ write(fd, &ms, sizeof(ms));
+ write(fd, &length, sizeof(length));
+ write(fd, buffer->data(), buffer->size());
+#endif
+}
+
+void ARTPWriter::addSR(const sp<ABuffer> &buffer) {
+ uint8_t *data = buffer->data() + buffer->size();
+
+ data[0] = 0x80 | 0;
+ data[1] = 200; // SR
+ data[2] = 0;
+ data[3] = 6;
+ data[4] = mSourceID >> 24;
+ data[5] = (mSourceID >> 16) & 0xff;
+ data[6] = (mSourceID >> 8) & 0xff;
+ data[7] = mSourceID & 0xff;
+
+ data[8] = mLastNTPTime >> (64 - 8);
+ data[9] = (mLastNTPTime >> (64 - 16)) & 0xff;
+ data[10] = (mLastNTPTime >> (64 - 24)) & 0xff;
+ data[11] = (mLastNTPTime >> 32) & 0xff;
+ data[12] = (mLastNTPTime >> 24) & 0xff;
+ data[13] = (mLastNTPTime >> 16) & 0xff;
+ data[14] = (mLastNTPTime >> 8) & 0xff;
+ data[15] = mLastNTPTime & 0xff;
+
+ data[16] = (mLastRTPTime >> 24) & 0xff;
+ data[17] = (mLastRTPTime >> 16) & 0xff;
+ data[18] = (mLastRTPTime >> 8) & 0xff;
+ data[19] = mLastRTPTime & 0xff;
+
+ data[20] = mNumRTPSent >> 24;
+ data[21] = (mNumRTPSent >> 16) & 0xff;
+ data[22] = (mNumRTPSent >> 8) & 0xff;
+ data[23] = mNumRTPSent & 0xff;
+
+ data[24] = mNumRTPOctetsSent >> 24;
+ data[25] = (mNumRTPOctetsSent >> 16) & 0xff;
+ data[26] = (mNumRTPOctetsSent >> 8) & 0xff;
+ data[27] = mNumRTPOctetsSent & 0xff;
+
+ buffer->setRange(buffer->offset(), buffer->size() + 28);
+}
+
+void ARTPWriter::addSDES(const sp<ABuffer> &buffer) {
+ uint8_t *data = buffer->data() + buffer->size();
+ data[0] = 0x80 | 1;
+ data[1] = 202; // SDES
+ data[4] = mSourceID >> 24;
+ data[5] = (mSourceID >> 16) & 0xff;
+ data[6] = (mSourceID >> 8) & 0xff;
+ data[7] = mSourceID & 0xff;
+
+ size_t offset = 8;
+
+ data[offset++] = 1; // CNAME
+
+ static const char *kCNAME = "someone@somewhere";
+ data[offset++] = strlen(kCNAME);
+
+ memcpy(&data[offset], kCNAME, strlen(kCNAME));
+ offset += strlen(kCNAME);
+
+ data[offset++] = 7; // NOTE
+
+ static const char *kNOTE = "Hell's frozen over.";
+ data[offset++] = strlen(kNOTE);
+
+ memcpy(&data[offset], kNOTE, strlen(kNOTE));
+ offset += strlen(kNOTE);
+
+ data[offset++] = 0;
+
+ if ((offset % 4) > 0) {
+ size_t count = 4 - (offset % 4);
+ switch (count) {
+ case 3:
+ data[offset++] = 0;
+ case 2:
+ data[offset++] = 0;
+ case 1:
+ data[offset++] = 0;
+ }
+ }
+
+ size_t numWords = (offset / 4) - 1;
+ data[2] = numWords >> 8;
+ data[3] = numWords & 0xff;
+
+ buffer->setRange(buffer->offset(), buffer->size() + offset);
+}
+
+// static
+uint64_t ARTPWriter::GetNowNTP() {
+ uint64_t nowUs = ALooper::GetNowUs();
+
+ nowUs += ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll;
+
+ uint64_t hi = nowUs / 1000000ll;
+ uint64_t lo = ((1ll << 32) * (nowUs % 1000000ll)) / 1000000ll;
+
+ return (hi << 32) | lo;
+}
+
+void ARTPWriter::dumpSessionDesc() {
+ AString sdp;
+ sdp = "v=0\r\n";
+
+ sdp.append("o=- ");
+
+ uint64_t ntp = GetNowNTP();
+ sdp.append(ntp);
+ sdp.append(" ");
+ sdp.append(ntp);
+ sdp.append(" IN IP4 127.0.0.0\r\n");
+
+ sdp.append(
+ "s=Sample\r\n"
+ "i=Playing around\r\n"
+ "c=IN IP4 ");
+
+ struct in_addr addr;
+ addr.s_addr = ntohl(INADDR_LOOPBACK);
+
+ sdp.append(inet_ntoa(addr));
+
+ sdp.append(
+ "\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=now-\r\n");
+
+ sp<MetaData> meta = mSource->getFormat();
+
+ if (mMode == H264 || mMode == H263) {
+ sdp.append("m=video ");
+ } else {
+ sdp.append("m=audio ");
+ }
+
+ sdp.append(StringPrintf("%d", ntohs(mRTPAddr.sin_port)));
+ sdp.append(
+ " RTP/AVP " PT_STR "\r\n"
+ "b=AS 320000\r\n"
+ "a=rtpmap:" PT_STR " ");
+
+ if (mMode == H264) {
+ sdp.append("H264/90000");
+ } else if (mMode == H263) {
+ sdp.append("H263-1998/90000");
+ } else if (mMode == AMR_NB || mMode == AMR_WB) {
+ int32_t sampleRate, numChannels;
+ CHECK(mSource->getFormat()->findInt32(kKeySampleRate, &sampleRate));
+ CHECK(mSource->getFormat()->findInt32(kKeyChannelCount, &numChannels));
+
+ CHECK_EQ(numChannels, 1);
+ CHECK_EQ(sampleRate, (mMode == AMR_NB) ? 8000 : 16000);
+
+ sdp.append(mMode == AMR_NB ? "AMR" : "AMR-WB");
+ sdp.append(StringPrintf("/%d/%d", sampleRate, numChannels));
+ } else {
+ TRESPASS();
+ }
+
+ sdp.append("\r\n");
+
+ if (mMode == H264 || mMode == H263) {
+ int32_t width, height;
+ CHECK(meta->findInt32(kKeyWidth, &width));
+ CHECK(meta->findInt32(kKeyHeight, &height));
+
+ sdp.append("a=cliprect 0,0,");
+ sdp.append(height);
+ sdp.append(",");
+ sdp.append(width);
+ sdp.append("\r\n");
+
+ sdp.append(
+ "a=framesize:" PT_STR " ");
+ sdp.append(width);
+ sdp.append("-");
+ sdp.append(height);
+ sdp.append("\r\n");
+ }
+
+ if (mMode == H264) {
+ sdp.append(
+ "a=fmtp:" PT_STR " profile-level-id=");
+ sdp.append(mProfileLevel);
+ sdp.append(";sprop-parameter-sets=");
+
+ sdp.append(mSeqParamSet);
+ sdp.append(",");
+ sdp.append(mPicParamSet);
+ sdp.append(";packetization-mode=1\r\n");
+ } else if (mMode == AMR_NB || mMode == AMR_WB) {
+ sdp.append("a=fmtp:" PT_STR " octed-align\r\n");
+ }
+
+ LOG(INFO) << sdp;
+}
+
+void ARTPWriter::makeH264SPropParamSets(MediaBuffer *buffer) {
+ static const char kStartCode[] = "\x00\x00\x00\x01";
+
+ const uint8_t *data =
+ (const uint8_t *)buffer->data() + buffer->range_offset();
+ size_t size = buffer->range_length();
+
+ CHECK_GE(size, 0u);
+
+ size_t startCodePos = 0;
+ while (startCodePos + 3 < size
+ && memcmp(kStartCode, &data[startCodePos], 4)) {
+ ++startCodePos;
+ }
+
+ CHECK_LT(startCodePos + 3, size);
+
+ CHECK_EQ((unsigned)data[0], 0x67u);
+
+ mProfileLevel =
+ StringPrintf("%02X%02X%02X", data[1], data[2], data[3]);
+
+ encodeBase64(data, startCodePos, &mSeqParamSet);
+
+ encodeBase64(&data[startCodePos + 4], size - startCodePos - 4,
+ &mPicParamSet);
+}
+
+void ARTPWriter::sendBye() {
+ sp<ABuffer> buffer = new ABuffer(8);
+ uint8_t *data = buffer->data();
+ *data++ = (2 << 6) | 1;
+ *data++ = 203;
+ *data++ = 0;
+ *data++ = 1;
+ *data++ = mSourceID >> 24;
+ *data++ = (mSourceID >> 16) & 0xff;
+ *data++ = (mSourceID >> 8) & 0xff;
+ *data++ = mSourceID & 0xff;
+ buffer->setRange(0, 8);
+
+ send(buffer, true /* isRTCP */);
+}
+
+void ARTPWriter::sendAVCData(MediaBuffer *mediaBuf) {
+ // 12 bytes RTP header + 2 bytes for the FU-indicator and FU-header.
+ CHECK_GE(kMaxPacketSize, 12u + 2u);
+
+ int64_t timeUs;
+ CHECK(mediaBuf->meta_data()->findInt64(kKeyTime, &timeUs));
+
+ uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100ll);
+
+ const uint8_t *mediaData =
+ (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
+
+ sp<ABuffer> buffer = new ABuffer(kMaxPacketSize);
+ if (mediaBuf->range_length() + 12 <= buffer->capacity()) {
+ // The data fits into a single packet
+ uint8_t *data = buffer->data();
+ data[0] = 0x80;
+ data[1] = (1 << 7) | PT; // M-bit
+ data[2] = (mSeqNo >> 8) & 0xff;
+ data[3] = mSeqNo & 0xff;
+ data[4] = rtpTime >> 24;
+ data[5] = (rtpTime >> 16) & 0xff;
+ data[6] = (rtpTime >> 8) & 0xff;
+ data[7] = rtpTime & 0xff;
+ data[8] = mSourceID >> 24;
+ data[9] = (mSourceID >> 16) & 0xff;
+ data[10] = (mSourceID >> 8) & 0xff;
+ data[11] = mSourceID & 0xff;
+
+ memcpy(&data[12],
+ mediaData, mediaBuf->range_length());
+
+ buffer->setRange(0, mediaBuf->range_length() + 12);
+
+ send(buffer, false /* isRTCP */);
+
+ ++mSeqNo;
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += buffer->size() - 12;
+ } else {
+ // FU-A
+
+ unsigned nalType = mediaData[0];
+ size_t offset = 1;
+
+ bool firstPacket = true;
+ while (offset < mediaBuf->range_length()) {
+ size_t size = mediaBuf->range_length() - offset;
+ bool lastPacket = true;
+ if (size + 12 + 2 > buffer->capacity()) {
+ lastPacket = false;
+ size = buffer->capacity() - 12 - 2;
+ }
+
+ uint8_t *data = buffer->data();
+ data[0] = 0x80;
+ data[1] = (lastPacket ? (1 << 7) : 0x00) | PT; // M-bit
+ data[2] = (mSeqNo >> 8) & 0xff;
+ data[3] = mSeqNo & 0xff;
+ data[4] = rtpTime >> 24;
+ data[5] = (rtpTime >> 16) & 0xff;
+ data[6] = (rtpTime >> 8) & 0xff;
+ data[7] = rtpTime & 0xff;
+ data[8] = mSourceID >> 24;
+ data[9] = (mSourceID >> 16) & 0xff;
+ data[10] = (mSourceID >> 8) & 0xff;
+ data[11] = mSourceID & 0xff;
+
+ data[12] = 28 | (nalType & 0xe0);
+
+ CHECK(!firstPacket || !lastPacket);
+
+ data[13] =
+ (firstPacket ? 0x80 : 0x00)
+ | (lastPacket ? 0x40 : 0x00)
+ | (nalType & 0x1f);
+
+ memcpy(&data[14], &mediaData[offset], size);
+
+ buffer->setRange(0, 14 + size);
+
+ send(buffer, false /* isRTCP */);
+
+ ++mSeqNo;
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += buffer->size() - 12;
+
+ firstPacket = false;
+ offset += size;
+ }
+ }
+
+ mLastRTPTime = rtpTime;
+ mLastNTPTime = GetNowNTP();
+}
+
+void ARTPWriter::sendH263Data(MediaBuffer *mediaBuf) {
+ CHECK_GE(kMaxPacketSize, 12u + 2u);
+
+ int64_t timeUs;
+ CHECK(mediaBuf->meta_data()->findInt64(kKeyTime, &timeUs));
+
+ uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100ll);
+
+ const uint8_t *mediaData =
+ (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
+
+ // hexdump(mediaData, mediaBuf->range_length());
+
+ CHECK_EQ((unsigned)mediaData[0], 0u);
+ CHECK_EQ((unsigned)mediaData[1], 0u);
+
+ size_t offset = 2;
+ size_t size = mediaBuf->range_length();
+
+ while (offset < size) {
+ sp<ABuffer> buffer = new ABuffer(kMaxPacketSize);
+ // CHECK_LE(mediaBuf->range_length() -2 + 14, buffer->capacity());
+
+ size_t remaining = size - offset;
+ bool lastPacket = (remaining + 14 <= buffer->capacity());
+ if (!lastPacket) {
+ remaining = buffer->capacity() - 14;
+ }
+
+ uint8_t *data = buffer->data();
+ data[0] = 0x80;
+ data[1] = (lastPacket ? 0x80 : 0x00) | PT; // M-bit
+ data[2] = (mSeqNo >> 8) & 0xff;
+ data[3] = mSeqNo & 0xff;
+ data[4] = rtpTime >> 24;
+ data[5] = (rtpTime >> 16) & 0xff;
+ data[6] = (rtpTime >> 8) & 0xff;
+ data[7] = rtpTime & 0xff;
+ data[8] = mSourceID >> 24;
+ data[9] = (mSourceID >> 16) & 0xff;
+ data[10] = (mSourceID >> 8) & 0xff;
+ data[11] = mSourceID & 0xff;
+
+ data[12] = (offset == 2) ? 0x04 : 0x00; // P=?, V=0
+ data[13] = 0x00; // PLEN = PEBIT = 0
+
+ memcpy(&data[14], &mediaData[offset], remaining);
+ offset += remaining;
+
+ buffer->setRange(0, remaining + 14);
+
+ send(buffer, false /* isRTCP */);
+
+ ++mSeqNo;
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += buffer->size() - 12;
+ }
+
+ mLastRTPTime = rtpTime;
+ mLastNTPTime = GetNowNTP();
+}
+
+static size_t getFrameSize(bool isWide, unsigned FT) {
+ static const size_t kFrameSizeNB[8] = {
+ 95, 103, 118, 134, 148, 159, 204, 244
+ };
+ static const size_t kFrameSizeWB[9] = {
+ 132, 177, 253, 285, 317, 365, 397, 461, 477
+ };
+
+ size_t frameSize = isWide ? kFrameSizeWB[FT] : kFrameSizeNB[FT];
+
+ // Round up bits to bytes and add 1 for the header byte.
+ frameSize = (frameSize + 7) / 8 + 1;
+
+ return frameSize;
+}
+
+void ARTPWriter::sendAMRData(MediaBuffer *mediaBuf) {
+ const uint8_t *mediaData =
+ (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
+
+ size_t mediaLength = mediaBuf->range_length();
+
+ CHECK_GE(kMaxPacketSize, 12u + 1u + mediaLength);
+
+ const bool isWide = (mMode == AMR_WB);
+
+ int64_t timeUs;
+ CHECK(mediaBuf->meta_data()->findInt64(kKeyTime, &timeUs));
+ uint32_t rtpTime = mRTPTimeBase + (timeUs / (isWide ? 250 : 125));
+
+ // hexdump(mediaData, mediaLength);
+
+ Vector<uint8_t> tableOfContents;
+ size_t srcOffset = 0;
+ while (srcOffset < mediaLength) {
+ uint8_t toc = mediaData[srcOffset];
+
+ unsigned FT = (toc >> 3) & 0x0f;
+ CHECK((isWide && FT <= 8) || (!isWide && FT <= 7));
+
+ tableOfContents.push(toc);
+ srcOffset += getFrameSize(isWide, FT);
+ }
+ CHECK_EQ(srcOffset, mediaLength);
+
+ sp<ABuffer> buffer = new ABuffer(kMaxPacketSize);
+ CHECK_LE(mediaLength + 12 + 1, buffer->capacity());
+
+ // The data fits into a single packet
+ uint8_t *data = buffer->data();
+ data[0] = 0x80;
+ data[1] = PT;
+ if (mNumRTPSent == 0) {
+ // Signal start of talk-spurt.
+ data[1] |= 0x80; // M-bit
+ }
+ data[2] = (mSeqNo >> 8) & 0xff;
+ data[3] = mSeqNo & 0xff;
+ data[4] = rtpTime >> 24;
+ data[5] = (rtpTime >> 16) & 0xff;
+ data[6] = (rtpTime >> 8) & 0xff;
+ data[7] = rtpTime & 0xff;
+ data[8] = mSourceID >> 24;
+ data[9] = (mSourceID >> 16) & 0xff;
+ data[10] = (mSourceID >> 8) & 0xff;
+ data[11] = mSourceID & 0xff;
+
+ data[12] = 0xf0; // CMR=15, RR=0
+
+ size_t dstOffset = 13;
+
+ for (size_t i = 0; i < tableOfContents.size(); ++i) {
+ uint8_t toc = tableOfContents[i];
+
+ if (i + 1 < tableOfContents.size()) {
+ toc |= 0x80;
+ } else {
+ toc &= ~0x80;
+ }
+
+ data[dstOffset++] = toc;
+ }
+
+ srcOffset = 0;
+ for (size_t i = 0; i < tableOfContents.size(); ++i) {
+ uint8_t toc = tableOfContents[i];
+ unsigned FT = (toc >> 3) & 0x0f;
+ size_t frameSize = getFrameSize(isWide, FT);
+
+ ++srcOffset; // skip toc
+ memcpy(&data[dstOffset], &mediaData[srcOffset], frameSize - 1);
+ srcOffset += frameSize - 1;
+ dstOffset += frameSize - 1;
+ }
+
+ buffer->setRange(0, dstOffset);
+
+ send(buffer, false /* isRTCP */);
+
+ ++mSeqNo;
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += buffer->size() - 12;
+
+ mLastRTPTime = rtpTime;
+ mLastNTPTime = GetNowNTP();
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/rtsp/ARTPWriter.h b/media/libstagefright/rtsp/ARTPWriter.h
new file mode 100644
index 0000000..b1b8b45
--- /dev/null
+++ b/media/libstagefright/rtsp/ARTPWriter.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_RTP_WRITER_H_
+
+#define A_RTP_WRITER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/base64.h>
+#include <media/stagefright/MediaWriter.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#define LOG_TO_FILES 0
+
+namespace android {
+
+struct ABuffer;
+struct MediaBuffer;
+
+struct ARTPWriter : public MediaWriter {
+ ARTPWriter(int fd);
+
+ virtual status_t addSource(const sp<MediaSource> &source);
+ virtual bool reachedEOS();
+ virtual status_t start(MetaData *params);
+ virtual void stop();
+ virtual void pause();
+
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+protected:
+ virtual ~ARTPWriter();
+
+private:
+ enum {
+ kWhatStart = 'strt',
+ kWhatStop = 'stop',
+ kWhatRead = 'read',
+ kWhatSendSR = 'sr ',
+ };
+
+ enum {
+ kFlagStarted = 1,
+ kFlagEOS = 2,
+ };
+
+ Mutex mLock;
+ Condition mCondition;
+ uint32_t mFlags;
+
+ int mFd;
+
+#if LOG_TO_FILES
+ int mRTPFd;
+ int mRTCPFd;
+#endif
+
+ sp<MediaSource> mSource;
+ sp<ALooper> mLooper;
+ sp<AHandlerReflector<ARTPWriter> > mReflector;
+
+ int mSocket;
+ struct sockaddr_in mRTPAddr;
+ struct sockaddr_in mRTCPAddr;
+
+ AString mProfileLevel;
+ AString mSeqParamSet;
+ AString mPicParamSet;
+
+ uint32_t mSourceID;
+ uint32_t mSeqNo;
+ uint32_t mRTPTimeBase;
+ uint32_t mNumRTPSent;
+ uint32_t mNumRTPOctetsSent;
+ uint32_t mLastRTPTime;
+ uint64_t mLastNTPTime;
+
+ int32_t mNumSRsSent;
+
+ enum {
+ INVALID,
+ H264,
+ H263,
+ AMR_NB,
+ AMR_WB,
+ } mMode;
+
+ static uint64_t GetNowNTP();
+
+ void onRead(const sp<AMessage> &msg);
+ void onSendSR(const sp<AMessage> &msg);
+
+ void addSR(const sp<ABuffer> &buffer);
+ void addSDES(const sp<ABuffer> &buffer);
+
+ void makeH264SPropParamSets(MediaBuffer *buffer);
+ void dumpSessionDesc();
+
+ void sendBye();
+ void sendAVCData(MediaBuffer *mediaBuf);
+ void sendH263Data(MediaBuffer *mediaBuf);
+ void sendAMRData(MediaBuffer *mediaBuf);
+
+ void send(const sp<ABuffer> &buffer, bool isRTCP);
+
+ DISALLOW_EVIL_CONSTRUCTORS(ARTPWriter);
+};
+
+} // namespace android
+
+#endif // A_RTP_WRITER_H_
diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp
index ca4c55e..ad813cd 100644
--- a/media/libstagefright/rtsp/ASessionDescription.cpp
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -49,7 +49,7 @@ bool ASessionDescription::parse(const void *data, size_t size) {
mFormats.push(AString("[root]"));
AString desc((const char *)data, size);
- LOG(VERBOSE) << desc;
+ LOG(INFO) << desc;
size_t i = 0;
for (;;) {
@@ -116,6 +116,24 @@ bool ASessionDescription::parse(const void *data, size_t size) {
mFormats.push(AString(line, 2, line.size() - 2));
break;
}
+
+ default:
+ {
+ AString key, value;
+
+ ssize_t equalPos = line.find("=");
+
+ key = AString(line, 0, equalPos + 1);
+ value = AString(line, equalPos + 1, line.size() - equalPos - 1);
+
+ key.trim();
+ value.trim();
+
+ LOG(VERBOSE) << "adding '" << key << "' => '" << value << "'";
+
+ mTracks.editItemAt(mTracks.size() - 1).add(key, value);
+ break;
+ }
}
i = eolPos + 2;
diff --git a/media/libstagefright/rtsp/Android.mk b/media/libstagefright/rtsp/Android.mk
index 4608fa0..7f3659f 100644
--- a/media/libstagefright/rtsp/Android.mk
+++ b/media/libstagefright/rtsp/Android.mk
@@ -3,15 +3,20 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- ARTSPController.cpp \
+ AAMRAssembler.cpp \
AAVCAssembler.cpp \
+ AH263Assembler.cpp \
AMPEG4AudioAssembler.cpp \
APacketSource.cpp \
ARTPAssembler.cpp \
ARTPConnection.cpp \
+ ARTPSession.cpp \
ARTPSource.cpp \
+ ARTPWriter.cpp \
ARTSPConnection.cpp \
+ ARTSPController.cpp \
ASessionDescription.cpp \
+ UDPPusher.cpp \
LOCAL_C_INCLUDES:= \
$(JNI_H_INCLUDE) \
@@ -26,3 +31,28 @@ endif
include $(BUILD_STATIC_LIBRARY)
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ rtp_test.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libstagefright liblog libutils libbinder libstagefright_foundation
+
+LOCAL_STATIC_LIBRARIES := \
+ libstagefright_rtsp
+
+LOCAL_C_INCLUDES:= \
+ $(JNI_H_INCLUDE) \
+ frameworks/base/media/libstagefright \
+ $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include
+
+LOCAL_CFLAGS += -Wno-multichar
+
+LOCAL_MODULE_TAGS := debug
+
+LOCAL_MODULE:= rtp_test
+
+include $(BUILD_EXECUTABLE)
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index 044393f..f21c8dc 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -38,9 +38,7 @@ struct MyHandler : public AHandler {
mConn(new ARTSPConnection),
mRTPConn(new ARTPConnection),
mSessionURL(url),
- mSetupTracksSuccessful(false),
- mFirstAccessUnit(true),
- mFirstAccessUnitNTP(-1) {
+ mSetupTracksSuccessful(false) {
mNetLooper->start(false /* runOnCallingThread */,
false /* canCallJava */,
@@ -161,8 +159,11 @@ struct MyHandler : public AHandler {
size_t index;
CHECK(msg->findSize("index", &index));
+ TrackInfo *track = NULL;
size_t trackIndex;
- CHECK(msg->findSize("track-index", &trackIndex));
+ if (msg->findSize("track-index", &trackIndex)) {
+ track = &mTracks.editItemAt(trackIndex);
+ }
int32_t result;
CHECK(msg->findInt32("result", &result));
@@ -170,9 +171,16 @@ struct MyHandler : public AHandler {
LOG(INFO) << "SETUP(" << index << ") completed with result "
<< result << " (" << strerror(-result) << ")";
- TrackInfo *track = &mTracks.editItemAt(trackIndex);
+ if (result != OK) {
+ if (track) {
+ close(track->mRTPSocket);
+ close(track->mRTCPSocket);
+
+ mTracks.removeItemsAt(trackIndex);
+ }
+ } else {
+ CHECK(track != NULL);
- if (result == OK) {
sp<RefBase> obj;
CHECK(msg->findObject("response", &obj));
sp<ARTSPResponse> response =
@@ -200,24 +208,13 @@ struct MyHandler : public AHandler {
mSessionDesc, index,
notify);
- track->mPacketSource =
- new APacketSource(mSessionDesc, index);
-
mSetupTracksSuccessful = true;
-
- ++index;
- if (index < mSessionDesc->countTracks()) {
- setupTrack(index);
- break;
- }
- } else {
- close(track->mRTPSocket);
- close(track->mRTCPSocket);
-
- mTracks.removeItemsAt(mTracks.size() - 1);
}
- if (mSetupTracksSuccessful) {
+ ++index;
+ if (index < mSessionDesc->countTracks()) {
+ setupTrack(index);
+ } else if (mSetupTracksSuccessful) {
AString request = "PLAY ";
request.append(mSessionURL);
request.append(" RTSP/1.0\r\n");
@@ -321,16 +318,6 @@ struct MyHandler : public AHandler {
CHECK(accessUnit->meta()->findInt64(
"ntp-time", (int64_t *)&ntpTime));
- if (mFirstAccessUnit) {
- mFirstAccessUnit = false;
- mFirstAccessUnitNTP = ntpTime;
- }
- if (ntpTime > mFirstAccessUnitNTP) {
- ntpTime -= mFirstAccessUnitNTP;
- } else {
- ntpTime = 0;
- }
-
accessUnit->meta()->setInt64("ntp-time", ntpTime);
#if 0
@@ -374,8 +361,6 @@ private:
AString mBaseURL;
AString mSessionID;
bool mSetupTracksSuccessful;
- bool mFirstAccessUnit;
- uint64_t mFirstAccessUnitNTP;
struct TrackInfo {
int mRTPSocket;
@@ -386,6 +371,19 @@ private:
Vector<TrackInfo> mTracks;
void setupTrack(size_t index) {
+ sp<APacketSource> source =
+ new APacketSource(mSessionDesc, index);
+ if (source->initCheck() != OK) {
+ LOG(WARNING) << "Unsupported format. Ignoring track #"
+ << index << ".";
+
+ sp<AMessage> reply = new AMessage('setu', id());
+ reply->setSize("index", index);
+ reply->setInt32("result", ERROR_UNSUPPORTED);
+ reply->post();
+ return;
+ }
+
AString url;
CHECK(mSessionDesc->findAttribute(index, "a=control", &url));
@@ -394,6 +392,7 @@ private:
mTracks.push(TrackInfo());
TrackInfo *info = &mTracks.editItemAt(mTracks.size() - 1);
+ info->mPacketSource = source;
unsigned rtpPort;
ARTPConnection::MakePortPair(
diff --git a/media/libstagefright/rtsp/UDPPusher.cpp b/media/libstagefright/rtsp/UDPPusher.cpp
new file mode 100644
index 0000000..28a343f
--- /dev/null
+++ b/media/libstagefright/rtsp/UDPPusher.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "UDPPusher.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <utils/ByteOrder.h>
+
+#include <sys/socket.h>
+
+namespace android {
+
+UDPPusher::UDPPusher(const char *filename, unsigned port)
+ : mFile(fopen(filename, "rb")),
+ mFirstTimeMs(0),
+ mFirstTimeUs(0) {
+ CHECK(mFile != NULL);
+
+ mSocket = socket(AF_INET, SOCK_DGRAM, 0);
+
+ struct sockaddr_in addr;
+ memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = 0;
+
+ CHECK_EQ(0, bind(mSocket, (const struct sockaddr *)&addr, sizeof(addr)));
+
+ memset(mRemoteAddr.sin_zero, 0, sizeof(mRemoteAddr.sin_zero));
+ mRemoteAddr.sin_family = AF_INET;
+ mRemoteAddr.sin_addr.s_addr = INADDR_ANY;
+ mRemoteAddr.sin_port = htons(port);
+}
+
+UDPPusher::~UDPPusher() {
+ close(mSocket);
+ mSocket = -1;
+
+ fclose(mFile);
+ mFile = NULL;
+}
+
+void UDPPusher::start() {
+ uint32_t timeMs;
+ CHECK_EQ(fread(&timeMs, 1, sizeof(timeMs), mFile), sizeof(timeMs));
+ mFirstTimeMs = fromlel(timeMs);
+ mFirstTimeUs = ALooper::GetNowUs();
+
+ (new AMessage(kWhatPush, id()))->post();
+}
+
+bool UDPPusher::onPush() {
+ uint32_t length;
+ if (fread(&length, 1, sizeof(length), mFile) < sizeof(length)) {
+ LOG(INFO) << "No more data to push.";
+ return false;
+ }
+
+ length = fromlel(length);
+
+ CHECK_GT(length, 0u);
+
+ sp<ABuffer> buffer = new ABuffer(length);
+ if (fread(buffer->data(), 1, length, mFile) < length) {
+ LOG(ERROR) << "File truncated?.";
+ return false;
+ }
+
+ ssize_t n = sendto(
+ mSocket, buffer->data(), buffer->size(), 0,
+ (const struct sockaddr *)&mRemoteAddr, sizeof(mRemoteAddr));
+
+ CHECK_EQ(n, (ssize_t)buffer->size());
+
+ uint32_t timeMs;
+ if (fread(&timeMs, 1, sizeof(timeMs), mFile) < sizeof(timeMs)) {
+ LOG(INFO) << "No more data to push.";
+ return false;
+ }
+
+ timeMs = fromlel(timeMs);
+ CHECK_GE(timeMs, mFirstTimeMs);
+
+ timeMs -= mFirstTimeMs;
+ int64_t whenUs = mFirstTimeUs + timeMs * 1000ll;
+ int64_t nowUs = ALooper::GetNowUs();
+ (new AMessage(kWhatPush, id()))->post(whenUs - nowUs);
+
+ return true;
+}
+
+void UDPPusher::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatPush:
+ {
+ if (!onPush() && !(ntohs(mRemoteAddr.sin_port) & 1)) {
+ LOG(INFO) << "emulating BYE packet";
+
+ sp<ABuffer> buffer = new ABuffer(8);
+ uint8_t *data = buffer->data();
+ *data++ = (2 << 6) | 1;
+ *data++ = 203;
+ *data++ = 0;
+ *data++ = 1;
+ *data++ = 0x8f;
+ *data++ = 0x49;
+ *data++ = 0xc0;
+ *data++ = 0xd0;
+ buffer->setRange(0, 8);
+
+ struct sockaddr_in tmp = mRemoteAddr;
+ tmp.sin_port = htons(ntohs(mRemoteAddr.sin_port) | 1);
+
+ ssize_t n = sendto(
+ mSocket, buffer->data(), buffer->size(), 0,
+ (const struct sockaddr *)&tmp,
+ sizeof(tmp));
+
+ CHECK_EQ(n, (ssize_t)buffer->size());
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/rtsp/UDPPusher.h b/media/libstagefright/rtsp/UDPPusher.h
new file mode 100644
index 0000000..2bde533
--- /dev/null
+++ b/media/libstagefright/rtsp/UDPPusher.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UDP_PUSHER_H_
+
+#define UDP_PUSHER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+#include <stdio.h>
+#include <arpa/inet.h>
+
+namespace android {
+
+struct UDPPusher : public AHandler {
+ UDPPusher(const char *filename, unsigned port);
+
+ void start();
+
+protected:
+ virtual ~UDPPusher();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatPush = 'push'
+ };
+
+ FILE *mFile;
+ int mSocket;
+ struct sockaddr_in mRemoteAddr;
+
+ uint32_t mFirstTimeMs;
+ int64_t mFirstTimeUs;
+
+ bool onPush();
+
+ DISALLOW_EVIL_CONSTRUCTORS(UDPPusher);
+};
+
+} // namespace android
+
+#endif // UDP_PUSHER_H_
diff --git a/media/libstagefright/rtsp/rtp_test.cpp b/media/libstagefright/rtsp/rtp_test.cpp
new file mode 100644
index 0000000..cec6c0c
--- /dev/null
+++ b/media/libstagefright/rtsp/rtp_test.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/ProcessState.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/foundation/base64.h>
+
+#include "ARTPSession.h"
+#include "ASessionDescription.h"
+#include "UDPPusher.h"
+
+using namespace android;
+
+int main(int argc, char **argv) {
+ android::ProcessState::self()->startThreadPool();
+
+ DataSource::RegisterDefaultSniffers();
+
+ const char *rtpFilename = NULL;
+ const char *rtcpFilename = NULL;
+
+ if (argc == 3) {
+ rtpFilename = argv[1];
+ rtcpFilename = argv[2];
+ } else if (argc != 1) {
+ fprintf(stderr, "usage: %s [ rtpFilename rtcpFilename ]\n", argv[0]);
+ return 1;
+ }
+
+#if 0
+ static const uint8_t kSPS[] = {
+ 0x67, 0x42, 0x80, 0x0a, 0xe9, 0x02, 0x83, 0xe4, 0x20, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x0e, 0xa6, 0x00, 0x80
+ };
+ static const uint8_t kPPS[] = {
+ 0x68, 0xce, 0x3c, 0x80
+ };
+ AString out1, out2;
+ encodeBase64(kSPS, sizeof(kSPS), &out1);
+ encodeBase64(kPPS, sizeof(kPPS), &out2);
+ printf("params=%s,%s\n", out1.c_str(), out2.c_str());
+#endif
+
+ sp<ALooper> looper = new ALooper;
+
+ sp<UDPPusher> rtp_pusher;
+ sp<UDPPusher> rtcp_pusher;
+
+ if (rtpFilename != NULL) {
+ rtp_pusher = new UDPPusher(rtpFilename, 5434);
+ looper->registerHandler(rtp_pusher);
+
+ rtcp_pusher = new UDPPusher(rtcpFilename, 5435);
+ looper->registerHandler(rtcp_pusher);
+ }
+
+ sp<ARTPSession> session = new ARTPSession;
+ looper->registerHandler(session);
+
+#if 0
+ // My H264 SDP
+ static const char *raw =
+ "v=0\r\n"
+ "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+ "s=QuickTime\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=0-315\r\n"
+ "a=isma-compliance:2,2.0,2\r\n"
+ "m=video 5434 RTP/AVP 97\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "b=AS:30\r\n"
+ "a=rtpmap:97 H264/90000\r\n"
+ "a=fmtp:97 packetization-mode=1;profile-level-id=42000C;"
+ "sprop-parameter-sets=Z0IADJZUCg+I,aM44gA==\r\n"
+ "a=mpeg4-esid:201\r\n"
+ "a=cliprect:0,0,240,320\r\n"
+ "a=framesize:97 320-240\r\n";
+#elif 0
+ // My H263 SDP
+ static const char *raw =
+ "v=0\r\n"
+ "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+ "s=QuickTime\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=0-315\r\n"
+ "a=isma-compliance:2,2.0,2\r\n"
+ "m=video 5434 RTP/AVP 97\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "b=AS:30\r\n"
+ "a=rtpmap:97 H263-1998/90000\r\n"
+ "a=cliprect:0,0,240,320\r\n"
+ "a=framesize:97 320-240\r\n";
+#elif 0
+ // My AMR SDP
+ static const char *raw =
+ "v=0\r\n"
+ "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+ "s=QuickTime\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=0-315\r\n"
+ "a=isma-compliance:2,2.0,2\r\n"
+ "m=audio 5434 RTP/AVP 97\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "b=AS:30\r\n"
+ "a=rtpmap:97 AMR/8000/1\r\n"
+ "a=fmtp:97 octet-align\r\n";
+#elif 1
+ // GTalk's H264 SDP
+ static const char *raw =
+ "v=0\r\n"
+ "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+ "s=QuickTime\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=now-\r\n"
+ "m=video 5434 RTP/AVP 96\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "b=AS:320000\r\n"
+ "a=rtpmap:96 H264/90000\r\n"
+ "a=fmtp:96 packetization-mode=1;profile-level-id=42001E;"
+ "sprop-parameter-sets=Z0IAHpZUBaHogA==,aM44gA==\r\n"
+ "a=cliprect:0,0,480,270\r\n"
+ "a=framesize:96 720-480\r\n";
+#else
+ // sholes H264 SDP
+ static const char *raw =
+ "v=0\r\n"
+ "o=- 64 233572944 IN IP4 127.0.0.0\r\n"
+ "s=QuickTime\r\n"
+ "t=0 0\r\n"
+ "a=range:npt=now-\r\n"
+ "m=video 5434 RTP/AVP 96\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "b=AS:320000\r\n"
+ "a=rtpmap:96 H264/90000\r\n"
+ "a=fmtp:96 packetization-mode=1;profile-level-id=42001E;"
+ "sprop-parameter-sets=Z0KACukCg+QgAAB9AAAOpgCA,aM48gA==\r\n"
+ "a=cliprect:0,0,240,320\r\n"
+ "a=framesize:96 320-240\r\n";
+#endif
+
+ sp<ASessionDescription> desc = new ASessionDescription;
+ CHECK(desc->setTo(raw, strlen(raw)));
+
+ CHECK_EQ(session->setup(desc), (status_t)OK);
+
+ if (rtp_pusher != NULL) {
+ rtp_pusher->start();
+ }
+
+ if (rtcp_pusher != NULL) {
+ rtcp_pusher->start();
+ }
+
+ looper->start(false /* runOnCallingThread */);
+
+ CHECK_EQ(session->countTracks(), 1u);
+ sp<MediaSource> source = session->trackAt(0);
+
+ OMXClient client;
+ CHECK_EQ(client.connect(), (status_t)OK);
+
+ sp<MediaSource> decoder = OMXCodec::Create(
+ client.interface(),
+ source->getFormat(), false /* createEncoder */,
+ source,
+ NULL,
+ 0); // OMXCodec::kPreferSoftwareCodecs);
+ CHECK(decoder != NULL);
+
+ CHECK_EQ(decoder->start(), (status_t)OK);
+
+ for (;;) {
+ MediaBuffer *buffer;
+ status_t err = decoder->read(&buffer);
+
+ if (err != OK) {
+ if (err == INFO_FORMAT_CHANGED) {
+ int32_t width, height;
+ CHECK(decoder->getFormat()->findInt32(kKeyWidth, &width));
+ CHECK(decoder->getFormat()->findInt32(kKeyHeight, &height));
+ printf("INFO_FORMAT_CHANGED %d x %d\n", width, height);
+ continue;
+ }
+
+ LOG(ERROR) << "decoder returned error "
+ << StringPrintf("0x%08x", err);
+ break;
+ }
+
+#if 1
+ if (buffer->range_length() != 0) {
+ int64_t timeUs;
+ CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs));
+
+ printf("decoder returned frame of size %d at time %.2f secs\n",
+ buffer->range_length(), timeUs / 1E6);
+ }
+#endif
+
+ buffer->release();
+ buffer = NULL;
+ }
+
+ CHECK_EQ(decoder->stop(), (status_t)OK);
+
+ looper->stop();
+
+ return 0;
+}