Merge "Support for Gtalk video, includes AMR/H.263 assembler and packetization support, extensions to MediaRecorder to stream via RTP over a pair of UDP sockets as well as various fixes to the RTP implementation." into gingerbread

This commit is contained in:
Andreas Huber
2010-08-04 14:06:12 -07:00
committed by Android (Google) Code Review
36 changed files with 2944 additions and 121 deletions

View File

@@ -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
};

View File

@@ -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',

View File

@@ -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;
};
/**

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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 \

View File

@@ -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;

View File

@@ -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");

View File

@@ -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;

View File

@@ -0,0 +1,232 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AAMRAssembler.h"
#include "ARTPSource.h"
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/Utils.h>
namespace android {
static bool GetAttribute(const char *s, const char *key, AString *value) {
value->clear();
size_t keyLen = strlen(key);
for (;;) {
const char *colonPos = strchr(s, ';');
size_t len =
(colonPos == NULL) ? strlen(s) : colonPos - s;
if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) {
value->setTo(&s[keyLen + 1], len - keyLen - 1);
return true;
}
if (len == keyLen && !strncmp(s, key, keyLen)) {
value->setTo("1");
return true;
}
if (colonPos == NULL) {
return false;
}
s = colonPos + 1;
}
}
AAMRAssembler::AAMRAssembler(
const sp<AMessage> &notify, bool isWide, const AString &params)
: mIsWide(isWide),
mNotifyMsg(notify),
mNextExpectedSeqNoValid(false),
mNextExpectedSeqNo(0) {
AString value;
CHECK(GetAttribute(params.c_str(), "octet-align", &value) && value == "1");
CHECK(!GetAttribute(params.c_str(), "crc", &value) || value == "0");
CHECK(!GetAttribute(params.c_str(), "interleaving", &value));
}
AAMRAssembler::~AAMRAssembler() {
}
ARTPAssembler::AssemblyStatus AAMRAssembler::assembleMore(
const sp<ARTPSource> &source) {
return addPacket(source);
}
static size_t getFrameSize(bool isWide, unsigned FT) {
static const size_t kFrameSizeNB[8] = {
95, 103, 118, 134, 148, 159, 204, 244
};
static const size_t kFrameSizeWB[9] = {
132, 177, 253, 285, 317, 365, 397, 461, 477
};
size_t frameSize = isWide ? kFrameSizeWB[FT] : kFrameSizeNB[FT];
// Round up bits to bytes and add 1 for the header byte.
frameSize = (frameSize + 7) / 8 + 1;
return frameSize;
}
ARTPAssembler::AssemblyStatus AAMRAssembler::addPacket(
const sp<ARTPSource> &source) {
List<sp<ABuffer> > *queue = source->queue();
if (queue->empty()) {
return NOT_ENOUGH_DATA;
}
if (mNextExpectedSeqNoValid) {
List<sp<ABuffer> >::iterator it = queue->begin();
while (it != queue->end()) {
if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
break;
}
it = queue->erase(it);
}
if (queue->empty()) {
return NOT_ENOUGH_DATA;
}
}
sp<ABuffer> buffer = *queue->begin();
if (!mNextExpectedSeqNoValid) {
mNextExpectedSeqNoValid = true;
mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
} else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
#if VERBOSE
LOG(VERBOSE) << "Not the sequence number I expected";
#endif
return WRONG_SEQUENCE_NUMBER;
}
// hexdump(buffer->data(), buffer->size());
if (buffer->size() < 1) {
queue->erase(queue->begin());
++mNextExpectedSeqNo;
LOG(VERBOSE) << "AMR packet too short.";
return MALFORMED_PACKET;
}
unsigned payloadHeader = buffer->data()[0];
unsigned CMR = payloadHeader >> 4;
CHECK_EQ(payloadHeader & 0x0f, 0u); // RR
Vector<uint8_t> tableOfContents;
size_t offset = 1;
size_t totalSize = 0;
for (;;) {
if (offset >= buffer->size()) {
queue->erase(queue->begin());
++mNextExpectedSeqNo;
LOG(VERBOSE) << "Unable to parse TOC.";
return MALFORMED_PACKET;
}
uint8_t toc = buffer->data()[offset++];
unsigned FT = (toc >> 3) & 0x0f;
if ((toc & 3) != 0
|| (mIsWide && FT > 8)
|| (!mIsWide && FT > 7)) {
queue->erase(queue->begin());
++mNextExpectedSeqNo;
LOG(VERBOSE) << "Illegal TOC entry.";
return MALFORMED_PACKET;
}
totalSize += getFrameSize(mIsWide, (toc >> 3) & 0x0f);
tableOfContents.push(toc);
if (0 == (toc & 0x80)) {
break;
}
}
uint64_t ntpTime;
CHECK(buffer->meta()->findInt64(
"ntp-time", (int64_t *)&ntpTime));
sp<ABuffer> accessUnit = new ABuffer(totalSize);
accessUnit->meta()->setInt64("ntp-time", ntpTime);
size_t dstOffset = 0;
for (size_t i = 0; i < tableOfContents.size(); ++i) {
uint8_t toc = tableOfContents[i];
size_t frameSize = getFrameSize(mIsWide, (toc >> 3) & 0x0f);
if (offset + frameSize - 1 > buffer->size()) {
queue->erase(queue->begin());
++mNextExpectedSeqNo;
LOG(VERBOSE) << "AMR packet too short.";
return MALFORMED_PACKET;
}
accessUnit->data()[dstOffset++] = toc;
memcpy(accessUnit->data() + dstOffset,
buffer->data() + offset, frameSize - 1);
offset += frameSize - 1;
dstOffset += frameSize - 1;
}
sp<AMessage> msg = mNotifyMsg->dup();
msg->setObject("access-unit", accessUnit);
msg->post();
queue->erase(queue->begin());
++mNextExpectedSeqNo;
return OK;
}
void AAMRAssembler::packetLost() {
CHECK(mNextExpectedSeqNoValid);
++mNextExpectedSeqNo;
}
void AAMRAssembler::onByeReceived() {
sp<AMessage> msg = mNotifyMsg->dup();
msg->setInt32("eos", true);
msg->post();
}
} // namespace android

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef A_AMR_ASSEMBLER_H_
#define A_AMR_ASSEMBLER_H_
#include "ARTPAssembler.h"
#include <utils/List.h>
#include <stdint.h>
namespace android {
struct AMessage;
struct AString;
struct AAMRAssembler : public ARTPAssembler {
AAMRAssembler(
const sp<AMessage> &notify, bool isWide,
const AString &params);
protected:
virtual ~AAMRAssembler();
virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
virtual void onByeReceived();
virtual void packetLost();
private:
bool mIsWide;
sp<AMessage> mNotifyMsg;
bool mNextExpectedSeqNoValid;
uint32_t mNextExpectedSeqNo;
AssemblyStatus addPacket(const sp<ARTPSource> &source);
DISALLOW_EVIL_CONSTRUCTORS(AAMRAssembler);
};
} // namespace android
#endif // A_AMR_ASSEMBLER_H_

View File

@@ -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

View File

@@ -35,6 +35,7 @@ protected:
virtual ~AAVCAssembler();
virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
virtual void onByeReceived();
virtual void packetLost();
private:

View File

@@ -0,0 +1,191 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AH263Assembler.h"
#include "ARTPSource.h"
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/Utils.h>
namespace android {
AH263Assembler::AH263Assembler(const sp<AMessage> &notify)
: mNotifyMsg(notify),
mAccessUnitRTPTime(0),
mNextExpectedSeqNoValid(false),
mNextExpectedSeqNo(0),
mAccessUnitDamaged(false) {
}
AH263Assembler::~AH263Assembler() {
}
ARTPAssembler::AssemblyStatus AH263Assembler::assembleMore(
const sp<ARTPSource> &source) {
AssemblyStatus status = addPacket(source);
if (status == MALFORMED_PACKET) {
mAccessUnitDamaged = true;
}
return status;
}
ARTPAssembler::AssemblyStatus AH263Assembler::addPacket(
const sp<ARTPSource> &source) {
List<sp<ABuffer> > *queue = source->queue();
if (queue->empty()) {
return NOT_ENOUGH_DATA;
}
if (mNextExpectedSeqNoValid) {
List<sp<ABuffer> >::iterator it = queue->begin();
while (it != queue->end()) {
if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
break;
}
it = queue->erase(it);
}
if (queue->empty()) {
return NOT_ENOUGH_DATA;
}
}
sp<ABuffer> buffer = *queue->begin();
if (!mNextExpectedSeqNoValid) {
mNextExpectedSeqNoValid = true;
mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
} else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
#if VERBOSE
LOG(VERBOSE) << "Not the sequence number I expected";
#endif
return WRONG_SEQUENCE_NUMBER;
}
uint32_t rtpTime;
CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
if (mPackets.size() > 0 && rtpTime != mAccessUnitRTPTime) {
submitAccessUnit();
}
mAccessUnitRTPTime = rtpTime;
// hexdump(buffer->data(), buffer->size());
if (buffer->size() < 2) {
queue->erase(queue->begin());
++mNextExpectedSeqNo;
return MALFORMED_PACKET;
}
unsigned payloadHeader = U16_AT(buffer->data());
CHECK_EQ(payloadHeader >> 11, 0u); // RR=0
unsigned P = (payloadHeader >> 10) & 1;
CHECK_EQ((payloadHeader >> 9) & 1, 0u); // V=0
CHECK_EQ((payloadHeader >> 3) & 0x3f, 0u); // PLEN=0
CHECK_EQ(payloadHeader & 7, 0u); // PEBIT=0
if (P) {
buffer->data()[0] = 0x00;
buffer->data()[1] = 0x00;
} else {
buffer->setRange(2, buffer->size() - 2);
}
mPackets.push_back(buffer);
queue->erase(queue->begin());
++mNextExpectedSeqNo;
return OK;
}
void AH263Assembler::submitAccessUnit() {
CHECK(!mPackets.empty());
#if VERBOSE
LOG(VERBOSE) << "Access unit complete (" << mPackets.size() << " packets)";
#endif
uint64_t ntpTime;
CHECK((*mPackets.begin())->meta()->findInt64(
"ntp-time", (int64_t *)&ntpTime));
size_t totalSize = 0;
List<sp<ABuffer> >::iterator it = mPackets.begin();
while (it != mPackets.end()) {
const sp<ABuffer> &unit = *it;
totalSize += unit->size();
++it;
}
sp<ABuffer> accessUnit = new ABuffer(totalSize);
size_t offset = 0;
it = mPackets.begin();
while (it != mPackets.end()) {
const sp<ABuffer> &unit = *it;
memcpy((uint8_t *)accessUnit->data() + offset,
unit->data(), unit->size());
offset += unit->size();
++it;
}
accessUnit->meta()->setInt64("ntp-time", ntpTime);
#if 0
printf(mAccessUnitDamaged ? "X" : ".");
fflush(stdout);
#endif
if (mAccessUnitDamaged) {
accessUnit->meta()->setInt32("damaged", true);
}
mPackets.clear();
mAccessUnitDamaged = false;
sp<AMessage> msg = mNotifyMsg->dup();
msg->setObject("access-unit", accessUnit);
msg->post();
}
void AH263Assembler::packetLost() {
CHECK(mNextExpectedSeqNoValid);
++mNextExpectedSeqNo;
mAccessUnitDamaged = true;
}
void AH263Assembler::onByeReceived() {
sp<AMessage> msg = mNotifyMsg->dup();
msg->setInt32("eos", true);
msg->post();
}
} // namespace android

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef A_H263_ASSEMBLER_H_
#define A_H263_ASSEMBLER_H_
#include "ARTPAssembler.h"
#include <utils/List.h>
#include <stdint.h>
namespace android {
struct AMessage;
struct AH263Assembler : public ARTPAssembler {
AH263Assembler(const sp<AMessage> &notify);
protected:
virtual ~AH263Assembler();
virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
virtual void onByeReceived();
virtual void packetLost();
private:
sp<AMessage> mNotifyMsg;
uint32_t mAccessUnitRTPTime;
bool mNextExpectedSeqNoValid;
uint32_t mNextExpectedSeqNo;
bool mAccessUnitDamaged;
List<sp<ABuffer> > mPackets;
AssemblyStatus addPacket(const sp<ARTPSource> &source);
void submitAccessUnit();
DISALLOW_EVIL_CONSTRUCTORS(AH263Assembler);
};
} // namespace android
#endif // A_H263_ASSEMBLER_H_

View File

@@ -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

View File

@@ -35,6 +35,7 @@ protected:
virtual ~AMPEG4AudioAssembler();
virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
virtual void onByeReceived();
virtual void packetLost();
private:

View File

@@ -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

View File

@@ -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);
};

View File

@@ -37,6 +37,7 @@ struct ARTPAssembler : public RefBase {
ARTPAssembler();
void onPacketReceived(const sp<ARTPSource> &source);
virtual void onByeReceived() = 0;
protected:
static void PropagateTimes(

View File

@@ -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;
}
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:
{
parseBYE(s, data, headerLength);
break;
}
default:
{
#if VERBOSE
printf("Unknown RTCP packet type %d of size %ld\n",
data[1], headerLength);
hexdump(data, headerLength);
#endif
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,32 +516,44 @@ 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;
ssize_t index = mSources.indexOfKey(id);
if (index < 0) {
index = mSources.size();
source = new ARTPSource(
id, s->mSessionDesc, s->mIndex, s->mNotifyMsg);
mSources.add(id, source);
} else {
source = mSources.valueAt(index);
}
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 = info->mSources.indexOfKey(srcId);
if (index < 0) {
index = info->mSources.size();
source = new ARTPSource(
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
info->mSources.add(srcId, source);
} else {
source = info->mSources.valueAt(index);
}
return source;
}
} // namespace android

View File

@@ -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();

View File

@@ -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

View File

@@ -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_

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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_

View File

@@ -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;

View File

@@ -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)

View File

@@ -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(

View File

@@ -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

View File

@@ -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_

View File

@@ -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;
}