am c17f35dd: 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
Merge commit 'c17f35dd927570c5c8d0c00b25a5bfaa1d4f1444' into gingerbread-plus-aosp * commit 'c17f35dd927570c5c8d0c00b25a5bfaa1d4f1444': 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.
This commit is contained in:
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
232
media/libstagefright/rtsp/AAMRAssembler.cpp
Normal file
232
media/libstagefright/rtsp/AAMRAssembler.cpp
Normal 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> ¬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
|
||||
59
media/libstagefright/rtsp/AAMRAssembler.h
Normal file
59
media/libstagefright/rtsp/AAMRAssembler.h
Normal 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> ¬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_
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,6 +35,7 @@ protected:
|
||||
virtual ~AAVCAssembler();
|
||||
|
||||
virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
|
||||
virtual void onByeReceived();
|
||||
virtual void packetLost();
|
||||
|
||||
private:
|
||||
|
||||
191
media/libstagefright/rtsp/AH263Assembler.cpp
Normal file
191
media/libstagefright/rtsp/AH263Assembler.cpp
Normal 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> ¬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
|
||||
|
||||
57
media/libstagefright/rtsp/AH263Assembler.h
Normal file
57
media/libstagefright/rtsp/AH263Assembler.h
Normal 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> ¬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_
|
||||
@@ -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
|
||||
|
||||
@@ -35,6 +35,7 @@ protected:
|
||||
virtual ~AMPEG4AudioAssembler();
|
||||
|
||||
virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
|
||||
virtual void onByeReceived();
|
||||
virtual void packetLost();
|
||||
|
||||
private:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ struct ARTPAssembler : public RefBase {
|
||||
ARTPAssembler();
|
||||
|
||||
void onPacketReceived(const sp<ARTPSource> &source);
|
||||
virtual void onByeReceived() = 0;
|
||||
|
||||
protected:
|
||||
static void PropagateTimes(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
231
media/libstagefright/rtsp/ARTPSession.cpp
Normal file
231
media/libstagefright/rtsp/ARTPSession.cpp
Normal 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
|
||||
69
media/libstagefright/rtsp/ARTPSession.h
Normal file
69
media/libstagefright/rtsp/ARTPSession.h
Normal 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_
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
813
media/libstagefright/rtsp/ARTPWriter.cpp
Normal file
813
media/libstagefright/rtsp/ARTPWriter.cpp
Normal 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
|
||||
|
||||
128
media/libstagefright/rtsp/ARTPWriter.h
Normal file
128
media/libstagefright/rtsp/ARTPWriter.h
Normal 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_
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
146
media/libstagefright/rtsp/UDPPusher.cpp
Normal file
146
media/libstagefright/rtsp/UDPPusher.cpp
Normal 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
|
||||
|
||||
56
media/libstagefright/rtsp/UDPPusher.h
Normal file
56
media/libstagefright/rtsp/UDPPusher.h
Normal 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_
|
||||
227
media/libstagefright/rtsp/rtp_test.cpp
Normal file
227
media/libstagefright/rtsp/rtp_test.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user