diff options
36 files changed, 2940 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/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 47a8cfc..34a86ec 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -188,6 +188,9 @@ public class MediaRecorder public static final int AAC_ADIF = 5; /** @hide AAC ADTS file format */ public static final int AAC_ADTS = 6; + + /** @hide Stream over a socket, limited to a single stream */ + public static final int OUTPUT_FORMAT_RTP_AVP = 7; }; /** 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> ¬ify, bool isWide, const AString ¶ms) + : 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> ¬ify, bool isWide, + const AString ¶ms); + +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> ¬ify) + : 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> ¬ify); + +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; +} |