summaryrefslogtreecommitdiffstats
path: root/media/libaah_rtp/aah_tx_player.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/libaah_rtp/aah_tx_player.cpp')
-rw-r--r--media/libaah_rtp/aah_tx_player.cpp1177
1 files changed, 1177 insertions, 0 deletions
diff --git a/media/libaah_rtp/aah_tx_player.cpp b/media/libaah_rtp/aah_tx_player.cpp
new file mode 100644
index 0000000..974805b
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_player.cpp
@@ -0,0 +1,1177 @@
+/*
+ * 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"
+#include <utils/Log.h>
+
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+#include <netdb.h>
+#include <netinet/ip.h>
+
+#include <common_time/cc_helper.h>
+#include <media/IMediaPlayer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/Timers.h>
+
+#include "aah_tx_packet.h"
+#include "aah_tx_player.h"
+
+namespace android {
+
+static int64_t kLowWaterMarkUs = 2000000ll; // 2secs
+static int64_t kHighWaterMarkUs = 10000000ll; // 10secs
+static const size_t kLowWaterMarkBytes = 40000;
+static const size_t kHighWaterMarkBytes = 200000;
+
+// When we start up, how much lead time should we put on the first access unit?
+static const int64_t kAAHStartupLeadTimeUs = 300000LL;
+
+// How much time do we attempt to lead the clock by in steady state?
+static const int64_t kAAHBufferTimeUs = 1000000LL;
+
+// how long do we keep data in our retransmit buffer after sending it.
+const int64_t AAH_TXPlayer::kAAHRetryKeepAroundTimeNs =
+ kAAHBufferTimeUs * 1100;
+
+sp<MediaPlayerBase> createAAH_TXPlayer() {
+ sp<MediaPlayerBase> ret = new AAH_TXPlayer();
+ return ret;
+}
+
+template <typename T> static T clamp(T val, T min, T max) {
+ if (val < min) {
+ return min;
+ } else if (val > max) {
+ return max;
+ } else {
+ return val;
+ }
+}
+
+struct AAH_TXEvent : public TimedEventQueue::Event {
+ AAH_TXEvent(AAH_TXPlayer *player,
+ void (AAH_TXPlayer::*method)()) : mPlayer(player)
+ , mMethod(method) {}
+
+ protected:
+ virtual ~AAH_TXEvent() {}
+
+ virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
+ (mPlayer->*mMethod)();
+ }
+
+ private:
+ AAH_TXPlayer *mPlayer;
+ void (AAH_TXPlayer::*mMethod)();
+
+ AAH_TXEvent(const AAH_TXEvent &);
+ AAH_TXEvent& operator=(const AAH_TXEvent &);
+};
+
+AAH_TXPlayer::AAH_TXPlayer()
+ : mQueueStarted(false)
+ , mFlags(0)
+ , mExtractorFlags(0) {
+ DataSource::RegisterDefaultSniffers();
+
+ mBufferingEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onBufferingUpdate);
+ mBufferingEventPending = false;
+
+ mPumpAudioEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onPumpAudio);
+ mPumpAudioEventPending = false;
+
+ mAudioCodecData = NULL;
+
+ reset_l();
+}
+
+AAH_TXPlayer::~AAH_TXPlayer() {
+ if (mQueueStarted) {
+ mQueue.stop();
+ }
+
+ reset_l();
+}
+
+void AAH_TXPlayer::cancelPlayerEvents(bool keepBufferingGoing) {
+ if (!keepBufferingGoing) {
+ mQueue.cancelEvent(mBufferingEvent->eventID());
+ mBufferingEventPending = false;
+
+ mQueue.cancelEvent(mPumpAudioEvent->eventID());
+ mPumpAudioEventPending = false;
+ }
+}
+
+status_t AAH_TXPlayer::initCheck() {
+ // Check for the presense of the common time service by attempting to query
+ // for CommonTime's frequency. If we get an error back, we cannot talk to
+ // the service at all and should abort now.
+ status_t res;
+ uint64_t freq;
+ res = mCCHelper.getCommonFreq(&freq);
+ if (OK != res) {
+ ALOGE("Failed to connect to common time service! (res %d)", res);
+ return res;
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setDataSource(
+ const char *url,
+ const KeyedVector<String8, String8> *headers) {
+ Mutex::Autolock autoLock(mLock);
+ return setDataSource_l(url, headers);
+}
+
+status_t AAH_TXPlayer::setDataSource_l(
+ const char *url,
+ const KeyedVector<String8, String8> *headers) {
+ reset_l();
+
+ mUri.setTo(url);
+
+ if (headers) {
+ mUriHeaders = *headers;
+
+ ssize_t index = mUriHeaders.indexOfKey(String8("x-hide-urls-from-log"));
+ if (index >= 0) {
+ // Browser is in "incognito" mode, suppress logging URLs.
+
+ // This isn't something that should be passed to the server.
+ mUriHeaders.removeItemsAt(index);
+
+ mFlags |= INCOGNITO;
+ }
+ }
+
+ // The URL may optionally contain a "#" character followed by a Skyjam
+ // cookie. Ideally the cookie header should just be passed in the headers
+ // argument, but the Java API for supplying headers is apparently not yet
+ // exposed in the SDK used by application developers.
+ const char kSkyjamCookieDelimiter = '#';
+ char* skyjamCookie = strrchr(mUri.string(), kSkyjamCookieDelimiter);
+ if (skyjamCookie) {
+ skyjamCookie++;
+ mUriHeaders.add(String8("Cookie"), String8(skyjamCookie));
+ mUri = String8(mUri.string(), skyjamCookie - mUri.string());
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+ Mutex::Autolock autoLock(mLock);
+
+ reset_l();
+
+ sp<DataSource> dataSource = new FileSource(dup(fd), offset, length);
+
+ status_t err = dataSource->initCheck();
+
+ if (err != OK) {
+ return err;
+ }
+
+ mFileSource = dataSource;
+
+ sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+
+ if (extractor == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ return setDataSource_l(extractor);
+}
+
+status_t AAH_TXPlayer::setVideoSurface(const sp<Surface>& surface) {
+ return OK;
+}
+
+status_t AAH_TXPlayer::setVideoSurfaceTexture(
+ const sp<ISurfaceTexture>& surfaceTexture) {
+ return OK;
+}
+
+status_t AAH_TXPlayer::prepare() {
+ return INVALID_OPERATION;
+}
+
+status_t AAH_TXPlayer::prepareAsync() {
+ Mutex::Autolock autoLock(mLock);
+
+ return prepareAsync_l();
+}
+
+status_t AAH_TXPlayer::prepareAsync_l() {
+ if (mFlags & PREPARING) {
+ return UNKNOWN_ERROR; // async prepare already pending
+ }
+
+ mAAH_Sender = AAH_TXSender::GetInstance();
+ if (mAAH_Sender == NULL) {
+ return NO_MEMORY;
+ }
+
+ if (!mQueueStarted) {
+ mQueue.start();
+ mQueueStarted = true;
+ }
+
+ mFlags |= PREPARING;
+ mAsyncPrepareEvent = new AAH_TXEvent(
+ this, &AAH_TXPlayer::onPrepareAsyncEvent);
+
+ mQueue.postEvent(mAsyncPrepareEvent);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::finishSetDataSource_l() {
+ sp<DataSource> dataSource;
+
+ if (!strncasecmp("http://", mUri.string(), 7) ||
+ !strncasecmp("https://", mUri.string(), 8)) {
+
+ mConnectingDataSource = HTTPBase::Create(
+ (mFlags & INCOGNITO)
+ ? HTTPBase::kFlagIncognito
+ : 0);
+
+ mLock.unlock();
+ status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders);
+ mLock.lock();
+
+ if (err != OK) {
+ mConnectingDataSource.clear();
+
+ ALOGI("mConnectingDataSource->connect() returned %d", err);
+ return err;
+ }
+
+ mCachedSource = new NuCachedSource2(mConnectingDataSource);
+ mConnectingDataSource.clear();
+
+ dataSource = mCachedSource;
+
+ // We're going to prefill the cache before trying to instantiate
+ // the extractor below, as the latter is an operation that otherwise
+ // could block on the datasource for a significant amount of time.
+ // During that time we'd be unable to abort the preparation phase
+ // without this prefill.
+
+ mLock.unlock();
+
+ for (;;) {
+ status_t finalStatus;
+ size_t cachedDataRemaining =
+ mCachedSource->approxDataRemaining(&finalStatus);
+
+ if (finalStatus != OK ||
+ cachedDataRemaining >= kHighWaterMarkBytes ||
+ (mFlags & PREPARE_CANCELLED)) {
+ break;
+ }
+
+ usleep(200000);
+ }
+
+ mLock.lock();
+
+ if (mFlags & PREPARE_CANCELLED) {
+ ALOGI("Prepare cancelled while waiting for initial cache fill.");
+ return UNKNOWN_ERROR;
+ }
+ } else {
+ dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);
+ }
+
+ if (dataSource == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+
+ if (extractor == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ return setDataSource_l(extractor);
+}
+
+status_t AAH_TXPlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {
+ // Attempt to approximate overall stream bitrate by summing all
+ // tracks' individual bitrates, if not all of them advertise bitrate,
+ // we have to fail.
+
+ int64_t totalBitRate = 0;
+
+ for (size_t i = 0; i < extractor->countTracks(); ++i) {
+ sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+ int32_t bitrate;
+ if (!meta->findInt32(kKeyBitRate, &bitrate)) {
+ totalBitRate = -1;
+ break;
+ }
+
+ totalBitRate += bitrate;
+ }
+
+ mBitrate = totalBitRate;
+
+ ALOGV("mBitrate = %lld bits/sec", mBitrate);
+
+ bool haveAudio = false;
+ for (size_t i = 0; i < extractor->countTracks(); ++i) {
+ sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+ if (!strncasecmp(mime, "audio/", 6)) {
+ mAudioSource = extractor->getTrack(i);
+ CHECK(mAudioSource != NULL);
+ haveAudio = true;
+ break;
+ }
+ }
+
+ if (!haveAudio) {
+ return UNKNOWN_ERROR;
+ }
+
+ mExtractorFlags = extractor->flags();
+
+ return OK;
+}
+
+void AAH_TXPlayer::abortPrepare(status_t err) {
+ CHECK(err != OK);
+
+ notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
+
+ mPrepareResult = err;
+ mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
+ mPreparedCondition.broadcast();
+}
+
+void AAH_TXPlayer::onPrepareAsyncEvent() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mFlags & PREPARE_CANCELLED) {
+ ALOGI("prepare was cancelled before doing anything");
+ abortPrepare(UNKNOWN_ERROR);
+ return;
+ }
+
+ if (mUri.size() > 0) {
+ status_t err = finishSetDataSource_l();
+
+ if (err != OK) {
+ abortPrepare(err);
+ return;
+ }
+ }
+
+ mAudioFormat = mAudioSource->getFormat();
+ if (!mAudioFormat->findInt64(kKeyDuration, &mDurationUs))
+ mDurationUs = 1;
+
+ const char* mime_type = NULL;
+ if (!mAudioFormat->findCString(kKeyMIMEType, &mime_type)) {
+ ALOGE("Failed to find audio substream MIME type during prepare.");
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ if (!strcmp(mime_type, MEDIA_MIMETYPE_AUDIO_MPEG)) {
+ mAudioCodec = TRTPAudioPacket::kCodecMPEG1Audio;
+ } else
+ if (!strcmp(mime_type, MEDIA_MIMETYPE_AUDIO_AAC)) {
+ mAudioCodec = TRTPAudioPacket::kCodecAACAudio;
+
+ uint32_t type;
+ int32_t sample_rate;
+ int32_t channel_count;
+ const void* esds_data;
+ size_t esds_len;
+
+ if (!mAudioFormat->findInt32(kKeySampleRate, &sample_rate)) {
+ ALOGE("Failed to find sample rate for AAC substream.");
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ if (!mAudioFormat->findInt32(kKeyChannelCount, &channel_count)) {
+ ALOGE("Failed to find channel count for AAC substream.");
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ if (!mAudioFormat->findData(kKeyESDS, &type, &esds_data, &esds_len)) {
+ ALOGE("Failed to find codec init data for AAC substream.");
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ CHECK(NULL == mAudioCodecData);
+ mAudioCodecDataSize = esds_len
+ + sizeof(sample_rate)
+ + sizeof(channel_count);
+ mAudioCodecData = new uint8_t[mAudioCodecDataSize];
+ if (NULL == mAudioCodecData) {
+ ALOGE("Failed to allocate %u bytes for AAC substream codec aux"
+ " data.", mAudioCodecDataSize);
+ mAudioCodecDataSize = 0;
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ uint8_t* tmp = mAudioCodecData;
+ tmp[0] = static_cast<uint8_t>((sample_rate >> 24) & 0xFF);
+ tmp[1] = static_cast<uint8_t>((sample_rate >> 16) & 0xFF);
+ tmp[2] = static_cast<uint8_t>((sample_rate >> 8) & 0xFF);
+ tmp[3] = static_cast<uint8_t>((sample_rate ) & 0xFF);
+ tmp[4] = static_cast<uint8_t>((channel_count >> 24) & 0xFF);
+ tmp[5] = static_cast<uint8_t>((channel_count >> 16) & 0xFF);
+ tmp[6] = static_cast<uint8_t>((channel_count >> 8) & 0xFF);
+ tmp[7] = static_cast<uint8_t>((channel_count ) & 0xFF);
+
+ memcpy(tmp + 8, esds_data, esds_len);
+ } else {
+ ALOGE("Unsupported MIME type \"%s\" in audio substream", mime_type);
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ status_t err = mAudioSource->start();
+ if (err != OK) {
+ ALOGI("failed to start audio source, err=%d", err);
+ abortPrepare(err);
+ return;
+ }
+
+ mFlags |= PREPARING_CONNECTED;
+
+ if (mCachedSource != NULL) {
+ postBufferingEvent_l();
+ } else {
+ finishAsyncPrepare_l();
+ }
+}
+
+void AAH_TXPlayer::finishAsyncPrepare_l() {
+ notifyListener_l(MEDIA_PREPARED);
+
+ mPrepareResult = OK;
+ mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
+ mFlags |= PREPARED;
+ mPreparedCondition.broadcast();
+}
+
+status_t AAH_TXPlayer::start() {
+ Mutex::Autolock autoLock(mLock);
+
+ mFlags &= ~CACHE_UNDERRUN;
+
+ return play_l();
+}
+
+status_t AAH_TXPlayer::play_l() {
+ if (mFlags & PLAYING) {
+ return OK;
+ }
+
+ if (!(mFlags & PREPARED)) {
+ return INVALID_OPERATION;
+ }
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (!mEndpointValid) {
+ return INVALID_OPERATION;
+ }
+ if (!mEndpointRegistered) {
+ mProgramID = mAAH_Sender->registerEndpoint(mEndpoint);
+ mEndpointRegistered = true;
+ }
+ }
+
+ mFlags |= PLAYING;
+
+ updateClockTransform_l(false);
+
+ postPumpAudioEvent_l(-1);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::stop() {
+ status_t ret = pause();
+ sendEOS_l();
+ return ret;
+}
+
+status_t AAH_TXPlayer::pause() {
+ Mutex::Autolock autoLock(mLock);
+
+ mFlags &= ~CACHE_UNDERRUN;
+
+ return pause_l();
+}
+
+status_t AAH_TXPlayer::pause_l(bool doClockUpdate) {
+ if (!(mFlags & PLAYING)) {
+ return OK;
+ }
+
+ cancelPlayerEvents(true /* keepBufferingGoing */);
+
+ mFlags &= ~PLAYING;
+
+ if (doClockUpdate) {
+ updateClockTransform_l(true);
+ }
+
+ return OK;
+}
+
+void AAH_TXPlayer::updateClockTransform_l(bool pause) {
+ // record the new pause status so that onPumpAudio knows what rate to apply
+ // when it initializes the transform
+ mPlayRateIsPaused = pause;
+
+ // if we haven't yet established a valid clock transform, then we can't
+ // do anything here
+ if (!mCurrentClockTransformValid) {
+ return;
+ }
+
+ // sample the current common time
+ int64_t commonTimeNow;
+ if (OK != mCCHelper.getCommonTime(&commonTimeNow)) {
+ ALOGE("updateClockTransform_l get common time failed");
+ mCurrentClockTransformValid = false;
+ return;
+ }
+
+ // convert the current common time to media time using the old
+ // transform
+ int64_t mediaTimeNow;
+ if (!mCurrentClockTransform.doReverseTransform(
+ commonTimeNow, &mediaTimeNow)) {
+ ALOGE("updateClockTransform_l reverse transform failed");
+ mCurrentClockTransformValid = false;
+ return;
+ }
+
+ // calculate a new transform that preserves the old transform's
+ // result for the current time
+ mCurrentClockTransform.a_zero = mediaTimeNow;
+ mCurrentClockTransform.b_zero = commonTimeNow;
+ mCurrentClockTransform.a_to_b_numer = 1;
+ mCurrentClockTransform.a_to_b_denom = pause ? 0 : 1;
+
+ // send a packet announcing the new transform
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setClockTransform(mCurrentClockTransform);
+ packet->setCommandID(TRTPControlPacket::kCommandNop);
+ queuePacketToSender_l(packet);
+}
+
+void AAH_TXPlayer::sendEOS_l() {
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setCommandID(TRTPControlPacket::kCommandEOS);
+ queuePacketToSender_l(packet);
+}
+
+bool AAH_TXPlayer::isPlaying() {
+ return (mFlags & PLAYING) || (mFlags & CACHE_UNDERRUN);
+}
+
+status_t AAH_TXPlayer::seekTo(int msec) {
+ if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
+ Mutex::Autolock autoLock(mLock);
+ return seekTo_l(static_cast<int64_t>(msec) * 1000);
+ }
+
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
+ return OK;
+}
+
+status_t AAH_TXPlayer::seekTo_l(int64_t timeUs) {
+ mIsSeeking = true;
+ mSeekTimeUs = timeUs;
+
+ mCurrentClockTransformValid = false;
+ mLastQueuedMediaTimePTSValid = false;
+
+ // send a flush command packet
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setCommandID(TRTPControlPacket::kCommandFlush);
+ queuePacketToSender_l(packet);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::getCurrentPosition(int *msec) {
+ if (!msec) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mLock);
+
+ int position;
+
+ if (mIsSeeking) {
+ position = mSeekTimeUs / 1000;
+ } else if (mCurrentClockTransformValid) {
+ // sample the current common time
+ int64_t commonTimeNow;
+ if (OK != mCCHelper.getCommonTime(&commonTimeNow)) {
+ ALOGE("getCurrentPosition get common time failed");
+ return INVALID_OPERATION;
+ }
+
+ int64_t mediaTimeNow;
+ if (!mCurrentClockTransform.doReverseTransform(commonTimeNow,
+ &mediaTimeNow)) {
+ ALOGE("getCurrentPosition reverse transform failed");
+ return INVALID_OPERATION;
+ }
+
+ position = static_cast<int>(mediaTimeNow / 1000);
+ } else {
+ position = 0;
+ }
+
+ int duration;
+ if (getDuration_l(&duration) == OK) {
+ *msec = clamp(position, 0, duration);
+ } else {
+ *msec = (position >= 0) ? position : 0;
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::getDuration(int* msec) {
+ if (!msec) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mLock);
+
+ return getDuration_l(msec);
+}
+
+status_t AAH_TXPlayer::getDuration_l(int* msec) {
+ if (mDurationUs < 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ *msec = (mDurationUs + 500) / 1000;
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::reset() {
+ Mutex::Autolock autoLock(mLock);
+ reset_l();
+ return OK;
+}
+
+void AAH_TXPlayer::reset_l() {
+ if (mFlags & PREPARING) {
+ mFlags |= PREPARE_CANCELLED;
+ if (mConnectingDataSource != NULL) {
+ ALOGI("interrupting the connection process");
+ mConnectingDataSource->disconnect();
+ }
+
+ if (mFlags & PREPARING_CONNECTED) {
+ // We are basically done preparing, we're just buffering
+ // enough data to start playback, we can safely interrupt that.
+ finishAsyncPrepare_l();
+ }
+ }
+
+ while (mFlags & PREPARING) {
+ mPreparedCondition.wait(mLock);
+ }
+
+ cancelPlayerEvents();
+
+ sendEOS_l();
+
+ mCachedSource.clear();
+
+ if (mAudioSource != NULL) {
+ mAudioSource->stop();
+ }
+ mAudioSource.clear();
+ mAudioCodec = TRTPAudioPacket::kCodecInvalid;
+ mAudioFormat = NULL;
+ delete[] mAudioCodecData;
+ mAudioCodecData = NULL;
+ mAudioCodecDataSize = 0;
+
+ mFlags = 0;
+ mExtractorFlags = 0;
+
+ mDurationUs = -1;
+ mIsSeeking = false;
+ mSeekTimeUs = 0;
+
+ mUri.setTo("");
+ mUriHeaders.clear();
+
+ mFileSource.clear();
+
+ mBitrate = -1;
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (mAAH_Sender != NULL && mEndpointRegistered) {
+ mAAH_Sender->unregisterEndpoint(mEndpoint);
+ }
+ mEndpointRegistered = false;
+ mEndpointValid = false;
+ }
+
+ mProgramID = 0;
+
+ mAAH_Sender.clear();
+ mLastQueuedMediaTimePTSValid = false;
+ mCurrentClockTransformValid = false;
+ mPlayRateIsPaused = false;
+
+ mTRTPVolume = 255;
+}
+
+status_t AAH_TXPlayer::setLooping(int loop) {
+ return OK;
+}
+
+player_type AAH_TXPlayer::playerType() {
+ return AAH_TX_PLAYER;
+}
+
+status_t AAH_TXPlayer::setParameter(int key, const Parcel &request) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_TXPlayer::getParameter(int key, Parcel *reply) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_TXPlayer::invoke(const Parcel& request, Parcel *reply) {
+ return INVALID_OPERATION;
+}
+
+status_t AAH_TXPlayer::getMetadata(const media::Metadata::Filter& ids,
+ Parcel* records) {
+ using media::Metadata;
+
+ Metadata metadata(records);
+
+ metadata.appendBool(Metadata::kPauseAvailable, true);
+ metadata.appendBool(Metadata::kSeekBackwardAvailable, false);
+ metadata.appendBool(Metadata::kSeekForwardAvailable, false);
+ metadata.appendBool(Metadata::kSeekAvailable, false);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setVolume(float leftVolume, float rightVolume) {
+ if (leftVolume != rightVolume) {
+ ALOGE("%s does not support per channel volume: %f, %f",
+ __PRETTY_FUNCTION__, leftVolume, rightVolume);
+ }
+
+ float volume = clamp(leftVolume, 0.0f, 1.0f);
+
+ Mutex::Autolock lock(mLock);
+ mTRTPVolume = static_cast<uint8_t>((leftVolume * 255.0) + 0.5);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setAudioStreamType(audio_stream_type_t streamType) {
+ return OK;
+}
+
+status_t AAH_TXPlayer::setRetransmitEndpoint(
+ const struct sockaddr_in* endpoint) {
+ Mutex::Autolock lock(mLock);
+
+ if (NULL == endpoint)
+ return BAD_VALUE;
+
+ // Once the endpoint has been registered, it may not be changed.
+ if (mEndpointRegistered)
+ return INVALID_OPERATION;
+
+ mEndpoint.addr = endpoint->sin_addr.s_addr;
+ mEndpoint.port = endpoint->sin_port;
+ mEndpointValid = true;
+
+ return OK;
+}
+
+void AAH_TXPlayer::notifyListener_l(int msg, int ext1, int ext2) {
+ sendEvent(msg, ext1, ext2);
+}
+
+bool AAH_TXPlayer::getBitrate_l(int64_t *bitrate) {
+ off64_t size;
+ if (mDurationUs >= 0 &&
+ mCachedSource != NULL &&
+ mCachedSource->getSize(&size) == OK) {
+ *bitrate = size * 8000000ll / mDurationUs; // in bits/sec
+ return true;
+ }
+
+ if (mBitrate >= 0) {
+ *bitrate = mBitrate;
+ return true;
+ }
+
+ *bitrate = 0;
+
+ return false;
+}
+
+// Returns true iff cached duration is available/applicable.
+bool AAH_TXPlayer::getCachedDuration_l(int64_t *durationUs, bool *eos) {
+ int64_t bitrate;
+
+ if (mCachedSource != NULL && getBitrate_l(&bitrate)) {
+ status_t finalStatus;
+ size_t cachedDataRemaining = mCachedSource->approxDataRemaining(
+ &finalStatus);
+ *durationUs = cachedDataRemaining * 8000000ll / bitrate;
+ *eos = (finalStatus != OK);
+ return true;
+ }
+
+ return false;
+}
+
+void AAH_TXPlayer::ensureCacheIsFetching_l() {
+ if (mCachedSource != NULL) {
+ mCachedSource->resumeFetchingIfNecessary();
+ }
+}
+
+void AAH_TXPlayer::postBufferingEvent_l() {
+ if (mBufferingEventPending) {
+ return;
+ }
+ mBufferingEventPending = true;
+ mQueue.postEventWithDelay(mBufferingEvent, 1000000ll);
+}
+
+void AAH_TXPlayer::postPumpAudioEvent_l(int64_t delayUs) {
+ if (mPumpAudioEventPending) {
+ return;
+ }
+ mPumpAudioEventPending = true;
+ mQueue.postEventWithDelay(mPumpAudioEvent, delayUs < 0 ? 10000 : delayUs);
+}
+
+void AAH_TXPlayer::onBufferingUpdate() {
+ Mutex::Autolock autoLock(mLock);
+ if (!mBufferingEventPending) {
+ return;
+ }
+ mBufferingEventPending = false;
+
+ if (mCachedSource != NULL) {
+ status_t finalStatus;
+ size_t cachedDataRemaining = mCachedSource->approxDataRemaining(
+ &finalStatus);
+ bool eos = (finalStatus != OK);
+
+ if (eos) {
+ if (finalStatus == ERROR_END_OF_STREAM) {
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ }
+ if (mFlags & PREPARING) {
+ ALOGV("cache has reached EOS, prepare is done.");
+ finishAsyncPrepare_l();
+ }
+ } else {
+ int64_t bitrate;
+ if (getBitrate_l(&bitrate)) {
+ size_t cachedSize = mCachedSource->cachedSize();
+ int64_t cachedDurationUs = cachedSize * 8000000ll / bitrate;
+
+ int percentage = (100.0 * (double) cachedDurationUs)
+ / mDurationUs;
+ if (percentage > 100) {
+ percentage = 100;
+ }
+
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, percentage);
+ } else {
+ // We don't know the bitrate of the stream, use absolute size
+ // limits to maintain the cache.
+
+ if ((mFlags & PLAYING) &&
+ !eos &&
+ (cachedDataRemaining < kLowWaterMarkBytes)) {
+ ALOGI("cache is running low (< %d) , pausing.",
+ kLowWaterMarkBytes);
+ mFlags |= CACHE_UNDERRUN;
+ pause_l();
+ ensureCacheIsFetching_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START);
+ } else if (eos || cachedDataRemaining > kHighWaterMarkBytes) {
+ if (mFlags & CACHE_UNDERRUN) {
+ ALOGI("cache has filled up (> %d), resuming.",
+ kHighWaterMarkBytes);
+ mFlags &= ~CACHE_UNDERRUN;
+ play_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+ } else if (mFlags & PREPARING) {
+ ALOGV("cache has filled up (> %d), prepare is done",
+ kHighWaterMarkBytes);
+ finishAsyncPrepare_l();
+ }
+ }
+ }
+ }
+ }
+
+ int64_t cachedDurationUs;
+ bool eos;
+ if (getCachedDuration_l(&cachedDurationUs, &eos)) {
+ ALOGV("cachedDurationUs = %.2f secs, eos=%d",
+ cachedDurationUs / 1E6, eos);
+
+ if ((mFlags & PLAYING) &&
+ !eos &&
+ (cachedDurationUs < kLowWaterMarkUs)) {
+ ALOGI("cache is running low (%.2f secs) , pausing.",
+ cachedDurationUs / 1E6);
+ mFlags |= CACHE_UNDERRUN;
+ pause_l();
+ ensureCacheIsFetching_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START);
+ } else if (eos || cachedDurationUs > kHighWaterMarkUs) {
+ if (mFlags & CACHE_UNDERRUN) {
+ ALOGI("cache has filled up (%.2f secs), resuming.",
+ cachedDurationUs / 1E6);
+ mFlags &= ~CACHE_UNDERRUN;
+ play_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+ } else if (mFlags & PREPARING) {
+ ALOGV("cache has filled up (%.2f secs), prepare is done",
+ cachedDurationUs / 1E6);
+ finishAsyncPrepare_l();
+ }
+ }
+ }
+
+ postBufferingEvent_l();
+}
+
+void AAH_TXPlayer::onPumpAudio() {
+ while (true) {
+ Mutex::Autolock autoLock(mLock);
+ // If this flag is clear, its because someone has externally canceled
+ // this pump operation (probably because we a resetting/shutting down).
+ // Get out immediately, do not reschedule ourselves.
+ if (!mPumpAudioEventPending) {
+ return;
+ }
+
+ // Start by checking if there is still work to be doing. If we have
+ // never queued a payload (so we don't know what the last queued PTS is)
+ // or we have never established a MediaTime->CommonTime transformation,
+ // then we have work to do (one time through this loop should establish
+ // both). Otherwise, we want to keep a fixed amt of presentation time
+ // worth of data buffered. If we cannot get common time (service is
+ // unavailable, or common time is undefined)) then we don't have a lot
+ // of good options here. For now, signal an error up to the app level
+ // and shut down the transmission pump.
+ int64_t commonTimeNow;
+ if (OK != mCCHelper.getCommonTime(&commonTimeNow)) {
+ // Failed to get common time; either the service is down or common
+ // time is not synced. Raise an error and shutdown the player.
+ ALOGE("*** Cannot pump audio, unable to fetch common time."
+ " Shutting down.");
+ notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, UNKNOWN_ERROR);
+ mPumpAudioEventPending = false;
+ break;
+ }
+
+ if (mCurrentClockTransformValid && mLastQueuedMediaTimePTSValid) {
+ int64_t mediaTimeNow;
+ bool conversionResult = mCurrentClockTransform.doReverseTransform(
+ commonTimeNow,
+ &mediaTimeNow);
+ CHECK(conversionResult);
+
+ if ((mediaTimeNow +
+ kAAHBufferTimeUs -
+ mLastQueuedMediaTimePTS) <= 0) {
+ break;
+ }
+ }
+
+ MediaSource::ReadOptions options;
+ if (mIsSeeking) {
+ options.setSeekTo(mSeekTimeUs);
+ }
+
+ MediaBuffer* mediaBuffer;
+ status_t err = mAudioSource->read(&mediaBuffer, &options);
+ if (err != NO_ERROR) {
+ if (err == ERROR_END_OF_STREAM) {
+ ALOGI("*** %s reached end of stream", __PRETTY_FUNCTION__);
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ notifyListener_l(MEDIA_PLAYBACK_COMPLETE);
+ pause_l(false);
+ sendEOS_l();
+ } else {
+ ALOGE("*** %s read failed err=%d", __PRETTY_FUNCTION__, err);
+ }
+ return;
+ }
+
+ if (mIsSeeking) {
+ mIsSeeking = false;
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
+ }
+
+ uint8_t* data = (static_cast<uint8_t*>(mediaBuffer->data()) +
+ mediaBuffer->range_offset());
+ ALOGV("*** %s got media buffer data=[%02hhx %02hhx %02hhx %02hhx]"
+ " offset=%d length=%d", __PRETTY_FUNCTION__,
+ data[0], data[1], data[2], data[3],
+ mediaBuffer->range_offset(), mediaBuffer->range_length());
+
+ int64_t mediaTimeUs;
+ CHECK(mediaBuffer->meta_data()->findInt64(kKeyTime, &mediaTimeUs));
+ ALOGV("*** timeUs=%lld", mediaTimeUs);
+
+ if (!mCurrentClockTransformValid) {
+ if (OK == mCCHelper.getCommonTime(&commonTimeNow)) {
+ mCurrentClockTransform.a_zero = mediaTimeUs;
+ mCurrentClockTransform.b_zero = commonTimeNow +
+ kAAHStartupLeadTimeUs;
+ mCurrentClockTransform.a_to_b_numer = 1;
+ mCurrentClockTransform.a_to_b_denom = mPlayRateIsPaused ? 0 : 1;
+ mCurrentClockTransformValid = true;
+ } else {
+ // Failed to get common time; either the service is down or
+ // common time is not synced. Raise an error and shutdown the
+ // player.
+ ALOGE("*** Cannot begin transmission, unable to fetch common"
+ " time. Dropping sample with pts=%lld", mediaTimeUs);
+ notifyListener_l(MEDIA_ERROR,
+ MEDIA_ERROR_UNKNOWN,
+ UNKNOWN_ERROR);
+ mPumpAudioEventPending = false;
+ break;
+ }
+ }
+
+ ALOGV("*** transmitting packet with pts=%lld", mediaTimeUs);
+
+ sp<TRTPAudioPacket> packet = new TRTPAudioPacket();
+ packet->setPTS(mediaTimeUs);
+ packet->setSubstreamID(1);
+
+ packet->setCodecType(mAudioCodec);
+ packet->setVolume(mTRTPVolume);
+ // TODO : introduce a throttle for this so we can control the
+ // frequency with which transforms get sent.
+ packet->setClockTransform(mCurrentClockTransform);
+ packet->setAccessUnitData(data, mediaBuffer->range_length());
+
+ // TODO : while its pretty much universally true that audio ES payloads
+ // are all RAPs across all codecs, it might be a good idea to throttle
+ // the frequency with which we send codec out of band data to the RXers.
+ // If/when we do, we need to flag only those payloads which have
+ // required out of band data attached to them as RAPs.
+ packet->setRandomAccessPoint(true);
+
+ if (mAudioCodecData && mAudioCodecDataSize) {
+ packet->setAuxData(mAudioCodecData, mAudioCodecDataSize);
+ }
+
+ queuePacketToSender_l(packet);
+ mediaBuffer->release();
+
+ mLastQueuedMediaTimePTSValid = true;
+ mLastQueuedMediaTimePTS = mediaTimeUs;
+ }
+
+ { // Explicit scope for the autolock pattern.
+ Mutex::Autolock autoLock(mLock);
+
+ // If someone externally has cleared this flag, its because we should be
+ // shutting down. Do not reschedule ourselves.
+ if (!mPumpAudioEventPending) {
+ return;
+ }
+
+ // Looks like no one canceled us explicitly. Clear our flag and post a
+ // new event to ourselves.
+ mPumpAudioEventPending = false;
+ postPumpAudioEvent_l(10000);
+ }
+}
+
+void AAH_TXPlayer::queuePacketToSender_l(const sp<TRTPPacket>& packet) {
+ if (mAAH_Sender == NULL) {
+ return;
+ }
+
+ sp<AMessage> message = new AMessage(AAH_TXSender::kWhatSendPacket,
+ mAAH_Sender->handlerID());
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (!mEndpointValid) {
+ return;
+ }
+
+ message->setInt32(AAH_TXSender::kSendPacketIPAddr, mEndpoint.addr);
+ message->setInt32(AAH_TXSender::kSendPacketPort, mEndpoint.port);
+ }
+
+ packet->setProgramID(mProgramID);
+ packet->setExpireTime(systemTime() + kAAHRetryKeepAroundTimeNs);
+ packet->pack();
+
+ message->setObject(AAH_TXSender::kSendPacketTRTPPacket, packet);
+
+ message->post();
+}
+
+} // namespace android