blob: 78754e664abbe867c65f9f978105d7b28c328db7 [file] [log] [blame]
/*
* 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.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "APacketSource"
#include <utils/Log.h>
#include "APacketSource.h"
#include "ASessionDescription.h"
#include "avc_utils.h"
#include <ctype.h>
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/AString.h>
#include <media/stagefright/foundation/base64.h>
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
#include <utils/Vector.h>
namespace android {
static bool GetAttribute(const char *s, const char *key, AString *value) {
value->clear();
size_t keyLen = strlen(key);
for (;;) {
while (isspace(*s)) {
++s;
}
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 (colonPos == NULL) {
return false;
}
s = colonPos + 1;
}
}
static sp<ABuffer> decodeHex(const AString &s) {
if ((s.size() % 2) != 0) {
return NULL;
}
size_t outLen = s.size() / 2;
sp<ABuffer> buffer = new ABuffer(outLen);
uint8_t *out = buffer->data();
uint8_t accum = 0;
for (size_t i = 0; i < s.size(); ++i) {
char c = s.c_str()[i];
unsigned value;
if (c >= '0' && c <= '9') {
value = c - '0';
} else if (c >= 'a' && c <= 'f') {
value = c - 'a' + 10;
} else if (c >= 'A' && c <= 'F') {
value = c - 'A' + 10;
} else {
return NULL;
}
accum = (accum << 4) | value;
if (i & 1) {
*out++ = accum;
accum = 0;
}
}
return buffer;
}
static sp<ABuffer> MakeAVCCodecSpecificData(
const char *params, int32_t *width, int32_t *height) {
*width = 0;
*height = 0;
AString val;
if (!GetAttribute(params, "profile-level-id", &val)) {
return NULL;
}
sp<ABuffer> profileLevelID = decodeHex(val);
CHECK(profileLevelID != NULL);
CHECK_EQ(profileLevelID->size(), 3u);
Vector<sp<ABuffer> > paramSets;
size_t numSeqParameterSets = 0;
size_t totalSeqParameterSetSize = 0;
size_t numPicParameterSets = 0;
size_t totalPicParameterSetSize = 0;
if (!GetAttribute(params, "sprop-parameter-sets", &val)) {
return NULL;
}
size_t start = 0;
for (;;) {
ssize_t commaPos = val.find(",", start);
size_t end = (commaPos < 0) ? val.size() : commaPos;
AString nalString(val, start, end - start);
sp<ABuffer> nal = decodeBase64(nalString);
CHECK(nal != NULL);
CHECK_GT(nal->size(), 0u);
CHECK_LE(nal->size(), 65535u);
uint8_t nalType = nal->data()[0] & 0x1f;
if (numSeqParameterSets == 0) {
CHECK_EQ((unsigned)nalType, 7u);
} else if (numPicParameterSets > 0) {
CHECK_EQ((unsigned)nalType, 8u);
}
if (nalType == 7) {
++numSeqParameterSets;
totalSeqParameterSetSize += nal->size();
} else {
CHECK_EQ((unsigned)nalType, 8u);
++numPicParameterSets;
totalPicParameterSetSize += nal->size();
}
paramSets.push(nal);
if (commaPos < 0) {
break;
}
start = commaPos + 1;
}
CHECK_LT(numSeqParameterSets, 32u);
CHECK_LE(numPicParameterSets, 255u);
size_t csdSize =
1 + 3 + 1 + 1
+ 2 * numSeqParameterSets + totalSeqParameterSetSize
+ 1 + 2 * numPicParameterSets + totalPicParameterSetSize;
sp<ABuffer> csd = new ABuffer(csdSize);
uint8_t *out = csd->data();
*out++ = 0x01; // configurationVersion
memcpy(out, profileLevelID->data(), 3);
out += 3;
*out++ = (0x3f << 2) | 1; // lengthSize == 2 bytes
*out++ = 0xe0 | numSeqParameterSets;
for (size_t i = 0; i < numSeqParameterSets; ++i) {
sp<ABuffer> nal = paramSets.editItemAt(i);
*out++ = nal->size() >> 8;
*out++ = nal->size() & 0xff;
memcpy(out, nal->data(), nal->size());
out += nal->size();
if (i == 0) {
FindAVCDimensions(nal, width, height);
LOGI("dimensions %dx%d", *width, *height);
}
}
*out++ = numPicParameterSets;
for (size_t i = 0; i < numPicParameterSets; ++i) {
sp<ABuffer> nal = paramSets.editItemAt(i + numSeqParameterSets);
*out++ = nal->size() >> 8;
*out++ = nal->size() & 0xff;
memcpy(out, nal->data(), nal->size());
out += nal->size();
}
// hexdump(csd->data(), csd->size());
return csd;
}
sp<ABuffer> MakeAACCodecSpecificData(const char *params) {
AString val;
CHECK(GetAttribute(params, "config", &val));
sp<ABuffer> config = decodeHex(val);
CHECK(config != NULL);
CHECK_GE(config->size(), 4u);
const uint8_t *data = config->data();
uint32_t x = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
x = (x >> 1) & 0xffff;
static const uint8_t kStaticESDS[] = {
0x03, 22,
0x00, 0x00, // ES_ID
0x00, // streamDependenceFlag, URL_Flag, OCRstreamFlag
0x04, 17,
0x40, // Audio ISO/IEC 14496-3
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x05, 2,
// AudioSpecificInfo follows
};
sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + 2);
memcpy(csd->data(), kStaticESDS, sizeof(kStaticESDS));
csd->data()[sizeof(kStaticESDS)] = (x >> 8) & 0xff;
csd->data()[sizeof(kStaticESDS) + 1] = x & 0xff;
// hexdump(csd->data(), csd->size());
return csd;
}
// From mpeg4-generic configuration data.
sp<ABuffer> MakeAACCodecSpecificData2(const char *params) {
AString val;
unsigned long objectType;
if (GetAttribute(params, "objectType", &val)) {
const char *s = val.c_str();
char *end;
objectType = strtoul(s, &end, 10);
CHECK(end > s && *end == '\0');
} else {
objectType = 0x40; // Audio ISO/IEC 14496-3
}
CHECK(GetAttribute(params, "config", &val));
sp<ABuffer> config = decodeHex(val);
CHECK(config != NULL);
// Make sure size fits into a single byte and doesn't have to
// be encoded.
CHECK_LT(20 + config->size(), 128u);
const uint8_t *data = config->data();
static const uint8_t kStaticESDS[] = {
0x03, 22,
0x00, 0x00, // ES_ID
0x00, // streamDependenceFlag, URL_Flag, OCRstreamFlag
0x04, 17,
0x40, // Audio ISO/IEC 14496-3
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x05, 2,
// AudioSpecificInfo follows
};
sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + config->size());
uint8_t *dst = csd->data();
*dst++ = 0x03;
*dst++ = 20 + config->size();
*dst++ = 0x00; // ES_ID
*dst++ = 0x00;
*dst++ = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag
*dst++ = 0x04;
*dst++ = 15 + config->size();
*dst++ = objectType;
for (int i = 0; i < 12; ++i) { *dst++ = 0x00; }
*dst++ = 0x05;
*dst++ = config->size();
memcpy(dst, config->data(), config->size());
// hexdump(csd->data(), csd->size());
return csd;
}
static size_t GetSizeWidth(size_t x) {
size_t n = 1;
while (x > 127) {
++n;
x >>= 7;
}
return n;
}
static uint8_t *EncodeSize(uint8_t *dst, size_t x) {
while (x > 127) {
*dst++ = (x & 0x7f) | 0x80;
x >>= 7;
}
*dst++ = x;
return dst;
}
static bool ExtractDimensionsFromVOLHeader(
const sp<ABuffer> &config, int32_t *width, int32_t *height) {
*width = 0;
*height = 0;
const uint8_t *ptr = config->data();
size_t offset = 0;
bool foundVOL = false;
while (offset + 3 < config->size()) {
if (memcmp("\x00\x00\x01", &ptr[offset], 3)
|| (ptr[offset + 3] & 0xf0) != 0x20) {
++offset;
continue;
}
foundVOL = true;
break;
}
if (!foundVOL) {
return false;
}
ABitReader br(&ptr[offset + 4], config->size() - offset - 4);
br.skipBits(1); // random_accessible_vol
unsigned video_object_type_indication = br.getBits(8);
CHECK_NE(video_object_type_indication,
0x21u /* Fine Granularity Scalable */);
unsigned video_object_layer_verid;
unsigned video_object_layer_priority;
if (br.getBits(1)) {
video_object_layer_verid = br.getBits(4);
video_object_layer_priority = br.getBits(3);
}
unsigned aspect_ratio_info = br.getBits(4);
if (aspect_ratio_info == 0x0f /* extended PAR */) {
br.skipBits(8); // par_width
br.skipBits(8); // par_height
}
if (br.getBits(1)) { // vol_control_parameters
br.skipBits(2); // chroma_format
br.skipBits(1); // low_delay
if (br.getBits(1)) { // vbv_parameters
TRESPASS();
}
}
unsigned video_object_layer_shape = br.getBits(2);
CHECK_EQ(video_object_layer_shape, 0x00u /* rectangular */);
CHECK(br.getBits(1)); // marker_bit
unsigned vop_time_increment_resolution = br.getBits(16);
CHECK(br.getBits(1)); // marker_bit
if (br.getBits(1)) { // fixed_vop_rate
// range [0..vop_time_increment_resolution)
// vop_time_increment_resolution
// 2 => 0..1, 1 bit
// 3 => 0..2, 2 bits
// 4 => 0..3, 2 bits
// 5 => 0..4, 3 bits
// ...
CHECK_GT(vop_time_increment_resolution, 0u);
--vop_time_increment_resolution;
unsigned numBits = 0;
while (vop_time_increment_resolution > 0) {
++numBits;
vop_time_increment_resolution >>= 1;
}
br.skipBits(numBits); // fixed_vop_time_increment
}
CHECK(br.getBits(1)); // marker_bit
unsigned video_object_layer_width = br.getBits(13);
CHECK(br.getBits(1)); // marker_bit
unsigned video_object_layer_height = br.getBits(13);
CHECK(br.getBits(1)); // marker_bit
unsigned interlaced = br.getBits(1);
*width = video_object_layer_width;
*height = video_object_layer_height;
LOGI("VOL dimensions = %dx%d", *width, *height);
return true;
}
sp<ABuffer> MakeMPEG4VideoCodecSpecificData(
const char *params, int32_t *width, int32_t *height) {
*width = 0;
*height = 0;
AString val;
CHECK(GetAttribute(params, "config", &val));
sp<ABuffer> config = decodeHex(val);
CHECK(config != NULL);
if (!ExtractDimensionsFromVOLHeader(config, width, height)) {
return NULL;
}
size_t len1 = config->size() + GetSizeWidth(config->size()) + 1;
size_t len2 = len1 + GetSizeWidth(len1) + 1 + 13;
size_t len3 = len2 + GetSizeWidth(len2) + 1 + 3;
sp<ABuffer> csd = new ABuffer(len3);
uint8_t *dst = csd->data();
*dst++ = 0x03;
dst = EncodeSize(dst, len2 + 3);
*dst++ = 0x00; // ES_ID
*dst++ = 0x00;
*dst++ = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag
*dst++ = 0x04;
dst = EncodeSize(dst, len1 + 13);
*dst++ = 0x01; // Video ISO/IEC 14496-2 Simple Profile
for (size_t i = 0; i < 12; ++i) {
*dst++ = 0x00;
}
*dst++ = 0x05;
dst = EncodeSize(dst, config->size());
memcpy(dst, config->data(), config->size());
dst += config->size();
// hexdump(csd->data(), csd->size());
return csd;
}
static bool GetClockRate(const AString &desc, uint32_t *clockRate) {
ssize_t slashPos = desc.find("/");
if (slashPos < 0) {
return false;
}
const char *s = desc.c_str() + slashPos + 1;
char *end;
unsigned long x = strtoul(s, &end, 10);
if (end == s || (*end != '\0' && *end != '/')) {
return false;
}
*clockRate = x;
return true;
}
APacketSource::APacketSource(
const sp<ASessionDescription> &sessionDesc, size_t index)
: mInitCheck(NO_INIT),
mFormat(new MetaData),
mEOSResult(OK),
mRTPTimeBase(0),
mNormalPlayTimeBaseUs(0),
mLastNormalPlayTimeUs(0) {
unsigned long PT;
AString desc;
AString params;
sessionDesc->getFormatType(index, &PT, &desc, &params);
CHECK(GetClockRate(desc, &mClockRate));
int64_t durationUs;
if (sessionDesc->getDurationUs(&durationUs)) {
mFormat->setInt64(kKeyDuration, durationUs);
} else {
mFormat->setInt64(kKeyDuration, 60 * 60 * 1000000ll);
}
mInitCheck = OK;
if (!strncmp(desc.c_str(), "H264/", 5)) {
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
int32_t width, height;
if (!sessionDesc->getDimensions(index, PT, &width, &height)) {
width = -1;
height = -1;
}
int32_t encWidth, encHeight;
sp<ABuffer> codecSpecificData =
MakeAVCCodecSpecificData(params.c_str(), &encWidth, &encHeight);
if (codecSpecificData != NULL) {
if (width < 0) {
// If no explicit width/height given in the sdp, use the dimensions
// extracted from the first sequence parameter set.
width = encWidth;
height = encHeight;
}
mFormat->setData(
kKeyAVCC, 0,
codecSpecificData->data(), codecSpecificData->size());
} else if (width < 0) {
mInitCheck = ERROR_UNSUPPORTED;
return;
}
mFormat->setInt32(kKeyWidth, width);
mFormat->setInt32(kKeyHeight, height);
} else if (!strncmp(desc.c_str(), "H263-2000/", 10)
|| !strncmp(desc.c_str(), "H263-1998/", 10)) {
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
int32_t width, height;
if (!sessionDesc->getDimensions(index, PT, &width, &height)) {
mInitCheck = ERROR_UNSUPPORTED;
return;
}
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;
ASessionDescription::ParseFormatDesc(
desc.c_str(), &sampleRate, &numChannels);
mFormat->setInt32(kKeySampleRate, sampleRate);
mFormat->setInt32(kKeyChannelCount, numChannels);
sp<ABuffer> codecSpecificData =
MakeAACCodecSpecificData(params.c_str());
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 if (!strncmp(desc.c_str(), "MP4V-ES/", 8)) {
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4);
int32_t width, height;
if (!sessionDesc->getDimensions(index, PT, &width, &height)) {
width = -1;
height = -1;
}
int32_t encWidth, encHeight;
sp<ABuffer> codecSpecificData =
MakeMPEG4VideoCodecSpecificData(
params.c_str(), &encWidth, &encHeight);
if (codecSpecificData != NULL) {
mFormat->setData(
kKeyESDS, 0,
codecSpecificData->data(), codecSpecificData->size());
if (width < 0) {
width = encWidth;
height = encHeight;
}
} else if (width < 0) {
mInitCheck = ERROR_UNSUPPORTED;
return;
}
mFormat->setInt32(kKeyWidth, width);
mFormat->setInt32(kKeyHeight, height);
} else if (!strncmp(desc.c_str(), "mpeg4-generic/", 14)) {
AString val;
if (!GetAttribute(params.c_str(), "mode", &val)
|| (strcasecmp(val.c_str(), "AAC-lbr")
&& strcasecmp(val.c_str(), "AAC-hbr"))) {
mInitCheck = ERROR_UNSUPPORTED;
return;
}
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
int32_t sampleRate, numChannels;
ASessionDescription::ParseFormatDesc(
desc.c_str(), &sampleRate, &numChannels);
mFormat->setInt32(kKeySampleRate, sampleRate);
mFormat->setInt32(kKeyChannelCount, numChannels);
sp<ABuffer> codecSpecificData =
MakeAACCodecSpecificData2(params.c_str());
mFormat->setData(
kKeyESDS, 0,
codecSpecificData->data(), codecSpecificData->size());
} else {
mInitCheck = ERROR_UNSUPPORTED;
}
}
APacketSource::~APacketSource() {
}
status_t APacketSource::initCheck() const {
return mInitCheck;
}
status_t APacketSource::start(MetaData *params) {
return OK;
}
status_t APacketSource::stop() {
return OK;
}
sp<MetaData> APacketSource::getFormat() {
return mFormat;
}
status_t APacketSource::read(
MediaBuffer **out, const ReadOptions *) {
*out = NULL;
Mutex::Autolock autoLock(mLock);
while (mEOSResult == OK && mBuffers.empty()) {
mCondition.wait(mLock);
}
if (!mBuffers.empty()) {
const sp<ABuffer> buffer = *mBuffers.begin();
updateNormalPlayTime_l(buffer);
MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
int64_t timeUs;
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
*out = mediaBuffer;
mBuffers.erase(mBuffers.begin());
return OK;
}
return mEOSResult;
}
void APacketSource::updateNormalPlayTime_l(const sp<ABuffer> &buffer) {
uint32_t rtpTime;
CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
mLastNormalPlayTimeUs =
(((double)rtpTime - (double)mRTPTimeBase) / mClockRate)
* 1000000ll
+ mNormalPlayTimeBaseUs;
}
void APacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
int32_t damaged;
if (buffer->meta()->findInt32("damaged", &damaged) && damaged) {
LOGV("discarding damaged AU");
return;
}
Mutex::Autolock autoLock(mLock);
mBuffers.push_back(buffer);
mCondition.signal();
}
void APacketSource::signalEOS(status_t result) {
CHECK(result != OK);
Mutex::Autolock autoLock(mLock);
mEOSResult = result;
mCondition.signal();
}
void APacketSource::flushQueue() {
Mutex::Autolock autoLock(mLock);
mBuffers.clear();
}
int64_t APacketSource::getNormalPlayTimeUs() {
Mutex::Autolock autoLock(mLock);
return mLastNormalPlayTimeUs;
}
void APacketSource::setNormalPlayTimeMapping(
uint32_t rtpTime, int64_t normalPlayTimeUs) {
Mutex::Autolock autoLock(mLock);
mRTPTimeBase = rtpTime;
mNormalPlayTimeBaseUs = normalPlayTimeUs;
}
int64_t APacketSource::getQueueDurationUs(bool *eos) {
Mutex::Autolock autoLock(mLock);
*eos = (mEOSResult != OK);
if (mBuffers.size() < 2) {
return 0;
}
const sp<ABuffer> first = *mBuffers.begin();
const sp<ABuffer> last = *--mBuffers.end();
int64_t firstTimeUs;
CHECK(first->meta()->findInt64("timeUs", &firstTimeUs));
int64_t lastTimeUs;
CHECK(last->meta()->findInt64("timeUs", &lastTimeUs));
if (lastTimeUs < firstTimeUs) {
LOGE("Huh? Time moving backwards? %lld > %lld",
firstTimeUs, lastTimeUs);
return 0;
}
return lastTimeUs - firstTimeUs;
}
} // namespace android