blob: 18b0e2b290ebd4ea5a3cd7391b08cf85fcbef804 [file] [log] [blame]
/*
* Copyright (C) 2011 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_TAG "LibAAH_RTP"
//#define LOG_NDEBUG 0
#include <utils/Log.h>
#include <include/avc_utils.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/OMXCodec.h>
#include <media/stagefright/Utils.h>
#include "aah_rx_player.h"
#include "aah_tx_packet.h"
inline uint32_t min(uint32_t a, uint32_t b) {
return (a < b ? a : b);
}
namespace android {
int64_t AAH_RXPlayer::Substream::kAboutToUnderflowThreshold =
50ull * 1000;
AAH_RXPlayer::Substream::Substream(uint32_t ssrc, OMXClient& omx) {
ssrc_ = ssrc;
substream_details_known_ = false;
buffer_in_progress_ = NULL;
status_ = OK;
codec_mime_type_ = "";
decoder_ = new AAH_DecoderPump(omx);
if (decoder_ == NULL) {
ALOGE("%s failed to allocate decoder pump!", __PRETTY_FUNCTION__);
}
if (OK != decoder_->initCheck()) {
ALOGE("%s failed to initialize decoder pump!", __PRETTY_FUNCTION__);
}
// cleanupBufferInProgress will reset most of the internal state variables.
// Just need to make sure that buffer_in_progress_ is NULL before calling.
cleanupBufferInProgress();
}
AAH_RXPlayer::Substream::~Substream() {
shutdown();
}
void AAH_RXPlayer::Substream::shutdown() {
substream_meta_ = NULL;
status_ = OK;
cleanupBufferInProgress();
cleanupDecoder();
}
void AAH_RXPlayer::Substream::cleanupBufferInProgress() {
if (NULL != buffer_in_progress_) {
buffer_in_progress_->release();
buffer_in_progress_ = NULL;
}
expected_buffer_size_ = 0;
buffer_filled_ = 0;
waiting_for_rap_ = true;
aux_data_in_progress_.clear();
aux_data_expected_size_ = 0;
}
void AAH_RXPlayer::Substream::cleanupDecoder() {
if (decoder_ != NULL) {
decoder_->shutdown();
}
}
bool AAH_RXPlayer::Substream::shouldAbort(const char* log_tag) {
// If we have already encountered a fatal error, do nothing. We are just
// waiting for our owner to shut us down now.
if (OK != status_) {
ALOGV("Skipping %s, substream has encountered fatal error (%d).",
log_tag, status_);
return true;
}
return false;
}
void AAH_RXPlayer::Substream::processPayloadStart(uint8_t* buf,
uint32_t amt,
int32_t ts_lower) {
uint32_t min_length = 6;
if (shouldAbort(__PRETTY_FUNCTION__)) {
return;
}
// Do we have a buffer in progress already? If so, abort the buffer. In
// theory, this should never happen. If there were a discontinutity in the
// stream, the discon in the seq_nos at the RTP level should have already
// triggered a cleanup of the buffer in progress. To see a problem at this
// level is an indication either of a bug in the transmitter, or some form
// of terrible corruption/tampering on the wire.
if (NULL != buffer_in_progress_) {
ALOGE("processPayloadStart is aborting payload already in progress.");
cleanupBufferInProgress();
}
// Parse enough of the header to know where we stand. Since this is a
// payload start, it should begin with a TRTP header which has to be at
// least 6 bytes long.
if (amt < min_length) {
ALOGV("Discarding payload too short to contain TRTP header (len = %u)",
amt);
return;
}
// Check the TRTP version number.
if (0x01 != buf[0]) {
ALOGV("Unexpected TRTP version (%u) in header. Expected %u.",
buf[0], 1);
return;
}
// Extract the substream type field and make sure its one we understand (and
// one that does not conflict with any previously received substream type.
uint8_t header_type = (buf[1] >> 4) & 0xF;
switch (header_type) {
case TRTPPacket::kHeaderTypeAudio:
// Audio, yay! Just break. We understand audio payloads.
break;
case TRTPPacket::kHeaderTypeVideo:
ALOGV("RXed packet with unhandled TRTP header type (Video).");
return;
case TRTPPacket::kHeaderTypeSubpicture:
ALOGV("RXed packet with unhandled TRTP header type (Subpicture).");
return;
case TRTPPacket::kHeaderTypeControl:
ALOGV("RXed packet with unhandled TRTP header type (Control).");
return;
default:
ALOGV("RXed packet with unhandled TRTP header type (%u).",
header_type);
return;
}
if (substream_details_known_ && (header_type != substream_type_)) {
ALOGV("RXed TRTP Payload for SSRC=0x%08x where header type (%u) does"
" not match previously received header type (%u)",
ssrc_, header_type, substream_type_);
return;
}
// Check the flags to see if there is another 32 bits of timestamp present.
uint32_t trtp_header_len = 6;
bool ts_valid = buf[1] & TRTPPacket::kFlag_TSValid;
if (ts_valid) {
min_length += 4;
trtp_header_len += 4;
if (amt < min_length) {
ALOGV("Discarding payload too short to contain TRTP timestamp"
" (len = %u)", amt);
return;
}
}
// Extract the TRTP length field and sanity check it.
uint32_t trtp_len = U32_AT(buf + 2);
if (trtp_len < min_length) {
ALOGV("TRTP length (%u) is too short to be valid. Must be at least %u"
" bytes.", trtp_len, min_length);
return;
}
// Extract the rest of the timestamp field if valid.
int64_t ts = 0;
uint32_t parse_offset = 6;
if (ts_valid) {
uint32_t ts_upper = U32_AT(buf + parse_offset);
parse_offset += 4;
ts = (static_cast<int64_t>(ts_upper) << 32) | ts_lower;
}
// Check the flags to see if there is another 24 bytes of timestamp
// transformation present.
if (buf[1] & TRTPPacket::kFlag_TSTransformPresent) {
min_length += 24;
parse_offset += 24;
trtp_header_len += 24;
if (amt < min_length) {
ALOGV("Discarding payload too short to contain TRTP timestamp"
" transformation (len = %u)", amt);
return;
}
}
// TODO : break the parsing into individual parsers for the different
// payload types (audio, video, etc).
//
// At this point in time, we know that this is audio. Go ahead and parse
// the basic header, check the codec type, and find the payload portion of
// the packet.
min_length += 3;
if (trtp_len < min_length) {
ALOGV("TRTP length (%u) is too short to be a valid audio payload. Must"
" be at least %u bytes.", trtp_len, min_length);
return;
}
if (amt < min_length) {
ALOGV("TRTP porttion of RTP payload (%u bytes) too small to contain"
" entire TRTP header. TRTP does not currently support"
" fragmenting TRTP headers across RTP payloads", amt);
return;
}
uint8_t codec_type = buf[parse_offset ];
uint8_t flags = buf[parse_offset + 1];
uint8_t volume = buf[parse_offset + 2];
parse_offset += 3;
trtp_header_len += 3;
if (!setupSubstreamType(header_type, codec_type)) {
return;
}
if (decoder_ != NULL) {
decoder_->setRenderVolume(volume);
}
if (waiting_for_rap_ && !(flags & TRTPAudioPacket::kFlag_RandomAccessPoint)) {
ALOGV("Dropping non-RAP TRTP Audio Payload while waiting for RAP.");
return;
}
// Check for the presence of codec aux data.
if (flags & TRTPAudioPacket::kFlag_AuxLengthPresent) {
min_length += 4;
trtp_header_len += 4;
if (trtp_len < min_length) {
ALOGV("TRTP length (%u) is too short to be a valid audio payload. "
"Must be at least %u bytes.", trtp_len, min_length);
return;
}
if (amt < min_length) {
ALOGV("TRTP porttion of RTP payload (%u bytes) too small to contain"
" entire TRTP header. TRTP does not currently support"
" fragmenting TRTP headers across RTP payloads", amt);
return;
}
aux_data_expected_size_ = U32_AT(buf + parse_offset);
aux_data_in_progress_.clear();
if (aux_data_in_progress_.capacity() < aux_data_expected_size_) {
aux_data_in_progress_.setCapacity(aux_data_expected_size_);
}
} else {
aux_data_expected_size_ = 0;
}
if ((aux_data_expected_size_ + trtp_header_len) > trtp_len) {
ALOGV("Expected codec aux data length (%u) and TRTP header overhead"
" (%u) too large for total TRTP payload length (%u).",
aux_data_expected_size_, trtp_header_len, trtp_len);
return;
}
// OK - everything left is just payload. Compute the payload size, start
// the buffer in progress and pack as much payload as we can into it. If
// the payload is finished once we are done, go ahead and send the payload
// to the decoder.
expected_buffer_size_ = trtp_len
- trtp_header_len
- aux_data_expected_size_;
if (!expected_buffer_size_) {
ALOGV("Dropping TRTP Audio Payload with 0 Access Unit length");
return;
}
CHECK(amt >= trtp_header_len);
uint32_t todo = amt - trtp_header_len;
if ((expected_buffer_size_ + aux_data_expected_size_) < todo) {
ALOGV("Extra data (%u > %u) present in initial TRTP Audio Payload;"
" dropping payload.", todo,
expected_buffer_size_ + aux_data_expected_size_);
return;
}
buffer_filled_ = 0;
buffer_in_progress_ = new MediaBuffer(expected_buffer_size_);
if ((NULL == buffer_in_progress_) ||
(NULL == buffer_in_progress_->data())) {
ALOGV("Failed to allocate MediaBuffer of length %u",
expected_buffer_size_);
cleanupBufferInProgress();
return;
}
sp<MetaData> meta = buffer_in_progress_->meta_data();
if (meta == NULL) {
ALOGV("Missing metadata structure in allocated MediaBuffer; dropping"
" payload");
cleanupBufferInProgress();
return;
}
meta->setCString(kKeyMIMEType, codec_mime_type_);
if (ts_valid) {
meta->setInt64(kKeyTime, ts);
}
// Skip over the header we have already extracted.
amt -= trtp_header_len;
buf += trtp_header_len;
// Extract as much of the expected aux data as we can.
todo = min(aux_data_expected_size_, amt);
if (todo) {
aux_data_in_progress_.appendArray(buf, todo);
buf += todo;
amt -= todo;
}
// Extract as much of the expected payload as we can.
todo = min(expected_buffer_size_, amt);
if (todo > 0) {
uint8_t* tgt =
reinterpret_cast<uint8_t*>(buffer_in_progress_->data());
memcpy(tgt, buf, todo);
buffer_filled_ = amt;
buf += todo;
amt -= todo;
}
if (buffer_filled_ >= expected_buffer_size_) {
processCompletedBuffer();
}
}
void AAH_RXPlayer::Substream::processPayloadCont(uint8_t* buf,
uint32_t amt) {
if (shouldAbort(__PRETTY_FUNCTION__)) {
return;
}
if (NULL == buffer_in_progress_) {
ALOGV("TRTP Receiver skipping payload continuation; no buffer currently"
" in progress.");
return;
}
CHECK(aux_data_in_progress_.size() <= aux_data_expected_size_);
uint32_t aux_left = aux_data_expected_size_ - aux_data_in_progress_.size();
if (aux_left) {
uint32_t todo = min(aux_left, amt);
aux_data_in_progress_.appendArray(buf, todo);
amt -= todo;
buf += todo;
if (!amt)
return;
}
CHECK(buffer_filled_ < expected_buffer_size_);
uint32_t buffer_left = expected_buffer_size_ - buffer_filled_;
if (amt > buffer_left) {
ALOGV("Extra data (%u > %u) present in continued TRTP Audio Payload;"
" dropping payload.", amt, buffer_left);
cleanupBufferInProgress();
return;
}
if (amt > 0) {
uint8_t* tgt =
reinterpret_cast<uint8_t*>(buffer_in_progress_->data());
memcpy(tgt + buffer_filled_, buf, amt);
buffer_filled_ += amt;
}
if (buffer_filled_ >= expected_buffer_size_) {
processCompletedBuffer();
}
}
void AAH_RXPlayer::Substream::processCompletedBuffer() {
status_t res;
CHECK(NULL != buffer_in_progress_);
if (decoder_ == NULL) {
ALOGV("Dropping complete buffer, no decoder pump allocated");
goto bailout;
}
// Make sure our metadata used to initialize the decoder has been properly
// set up.
if (!setupSubstreamMeta())
goto bailout;
// If our decoder has not be set up, do so now.
res = decoder_->init(substream_meta_);
if (OK != res) {
ALOGE("Failed to init decoder (res = %d)", res);
cleanupDecoder();
substream_meta_ = NULL;
goto bailout;
}
// Queue the payload for decode.
res = decoder_->queueForDecode(buffer_in_progress_);
if (res != OK) {
ALOGD("Failed to queue payload for decode, resetting decoder pump!"
" (res = %d)", res);
status_ = res;
cleanupDecoder();
cleanupBufferInProgress();
}
// NULL out buffer_in_progress before calling the cleanup helper.
//
// MediaBuffers use something of a hybrid ref-counting pattern which prevent
// the AAH_DecoderPump's input queue from adding their own reference to the
// MediaBuffer. MediaBuffers start life with a reference count of 0, as
// well as an observer which starts as NULL. Before being given an
// observer, the ref count cannot be allowed to become non-zero as it will
// cause calls to release() to assert. Basically, before a MediaBuffer has
// an observer, they behave like non-ref counted obects where release()
// serves the roll of delete. After a MediaBuffer has an observer, they
// become more like ref counted objects where add ref and release can be
// used, and when the ref count hits zero, the MediaBuffer is handed off to
// the observer.
//
// Given all of this, when we give the buffer to the decoder pump to wait in
// the to-be-processed queue, the decoder cannot add a ref to the buffer as
// it would in a traditional ref counting system. Instead it needs to
// "steal" the non-existent ref. In the case of queue failure, we need to
// make certain to release this non-existent reference so that the buffer is
// cleaned up during the cleanupBufferInProgress helper. In the case of a
// successful queue operation, we need to make certain that the
// cleanupBufferInProgress helper does not release the buffer since it needs
// to remain alive in the queue. We acomplish this by NULLing out the
// buffer pointer before calling the cleanup helper.
buffer_in_progress_ = NULL;
bailout:
cleanupBufferInProgress();
}
bool AAH_RXPlayer::Substream::setupSubstreamMeta() {
switch (codec_type_) {
case TRTPAudioPacket::kCodecMPEG1Audio:
codec_mime_type_ = MEDIA_MIMETYPE_AUDIO_MPEG;
return setupMP3SubstreamMeta();
case TRTPAudioPacket::kCodecAACAudio:
codec_mime_type_ = MEDIA_MIMETYPE_AUDIO_AAC;
return setupAACSubstreamMeta();
default:
ALOGV("Failed to setup substream metadata for unsupported codec"
" type (%u)", codec_type_);
break;
}
return false;
}
bool AAH_RXPlayer::Substream::setupMP3SubstreamMeta() {
const uint8_t* buffer_data = NULL;
int sample_rate;
int channel_count;
size_t frame_size;
status_t res;
buffer_data = reinterpret_cast<const uint8_t*>(buffer_in_progress_->data());
if (buffer_in_progress_->size() < 4) {
ALOGV("MP3 payload too short to contain header, dropping payload.");
return false;
}
// Extract the channel count and the sample rate from the MP3 header. The
// stagefright MP3 requires that these be delivered before decoing can
// begin.
if (!GetMPEGAudioFrameSize(U32_AT(buffer_data),
&frame_size,
&sample_rate,
&channel_count,
NULL,
NULL)) {
ALOGV("Failed to parse MP3 header in payload, droping payload.");
return false;
}
// Make sure that our substream metadata is set up properly. If there has
// been a format change, be sure to reset the underlying decoder. In
// stagefright, it seems like the only way to do this is to destroy and
// recreate the decoder.
if (substream_meta_ == NULL) {
substream_meta_ = new MetaData();
if (substream_meta_ == NULL) {
ALOGE("Failed to allocate MetaData structure for MP3 substream");
return false;
}
substream_meta_->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
substream_meta_->setInt32 (kKeyChannelCount, channel_count);
substream_meta_->setInt32 (kKeySampleRate, sample_rate);
} else {
int32_t prev_sample_rate;
int32_t prev_channel_count;
substream_meta_->findInt32(kKeySampleRate, &prev_sample_rate);
substream_meta_->findInt32(kKeyChannelCount, &prev_channel_count);
if ((prev_channel_count != channel_count) ||
(prev_sample_rate != sample_rate)) {
ALOGW("MP3 format change detected, forcing decoder reset.");
cleanupDecoder();
substream_meta_->setInt32(kKeyChannelCount, channel_count);
substream_meta_->setInt32(kKeySampleRate, sample_rate);
}
}
return true;
}
bool AAH_RXPlayer::Substream::setupAACSubstreamMeta() {
int32_t sample_rate, channel_cnt;
static const size_t overhead = sizeof(sample_rate)
+ sizeof(channel_cnt);
if (aux_data_in_progress_.size() < overhead) {
ALOGE("Not enough aux data (%u) to initialize AAC substream decoder",
aux_data_in_progress_.size());
return false;
}
const uint8_t* aux_data = aux_data_in_progress_.array();
size_t aux_data_size = aux_data_in_progress_.size();
sample_rate = U32_AT(aux_data);
channel_cnt = U32_AT(aux_data + sizeof(sample_rate));
const uint8_t* esds_data = NULL;
size_t esds_data_size = 0;
if (aux_data_size > overhead) {
esds_data = aux_data + overhead;
esds_data_size = aux_data_size - overhead;
}
// Do we already have metadata? If so, has it changed at all? If not, then
// there should be nothing else to do. Otherwise, release our old stream
// metadata and make new metadata.
if (substream_meta_ != NULL) {
uint32_t type;
const void* data;
size_t size;
int32_t prev_sample_rate;
int32_t prev_channel_count;
bool res;
res = substream_meta_->findInt32(kKeySampleRate, &prev_sample_rate);
CHECK(res);
res = substream_meta_->findInt32(kKeyChannelCount, &prev_channel_count);
CHECK(res);
// If nothing has changed about the codec aux data (esds, sample rate,
// channel count), then we can just do nothing and get out. Otherwise,
// we will need to reset the decoder and make a new metadata object to
// deal with the format change.
bool hasData = (esds_data != NULL);
bool hadData = substream_meta_->findData(kKeyESDS, &type, &data, &size);
bool esds_change = (hadData != hasData);
if (!esds_change && hasData)
esds_change = ((size != esds_data_size) ||
memcmp(data, esds_data, size));
if (!esds_change &&
(prev_sample_rate == sample_rate) &&
(prev_channel_count == channel_cnt)) {
return true; // no change, just get out.
}
ALOGW("AAC format change detected, forcing decoder reset.");
cleanupDecoder();
substream_meta_ = NULL;
}
CHECK(substream_meta_ == NULL);
substream_meta_ = new MetaData();
if (substream_meta_ == NULL) {
ALOGE("Failed to allocate MetaData structure for AAC substream");
return false;
}
substream_meta_->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
substream_meta_->setInt32 (kKeySampleRate, sample_rate);
substream_meta_->setInt32 (kKeyChannelCount, channel_cnt);
if (esds_data) {
substream_meta_->setData(kKeyESDS, kTypeESDS,
esds_data, esds_data_size);
}
return true;
}
void AAH_RXPlayer::Substream::processTSTransform(const LinearTransform& trans) {
if (decoder_ != NULL) {
decoder_->setRenderTSTransform(trans);
}
}
bool AAH_RXPlayer::Substream::isAboutToUnderflow() {
if (decoder_ == NULL) {
return false;
}
return decoder_->isAboutToUnderflow(kAboutToUnderflowThreshold);
}
bool AAH_RXPlayer::Substream::setupSubstreamType(uint8_t substream_type,
uint8_t codec_type) {
// Sanity check the codec type. Right now we only support MP3 and AAC.
// Also check for conflicts with previously delivered codec types.
if (substream_details_known_) {
if (codec_type != codec_type_) {
ALOGV("RXed TRTP Payload for SSRC=0x%08x where codec type (%u) does"
" not match previously received codec type (%u)",
ssrc_, codec_type, codec_type_);
return false;
}
return true;
}
switch (codec_type) {
// MP3 and AAC are all we support right now.
case TRTPAudioPacket::kCodecMPEG1Audio:
case TRTPAudioPacket::kCodecAACAudio:
break;
default:
ALOGV("RXed TRTP Audio Payload for SSRC=0x%08x with unsupported"
" codec type (%u)", ssrc_, codec_type);
return false;
}
substream_type_ = substream_type;
codec_type_ = codec_type;
substream_details_known_ = true;
return true;
}
} // namespace android