1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17//#define LOG_NDEBUG 0 18#define LOG_TAG "TimedTextPlayer" 19#include <utils/Log.h> 20 21#include <inttypes.h> 22#include <limits.h> 23#include <media/stagefright/foundation/ADebug.h> 24#include <media/stagefright/foundation/AMessage.h> 25#include <media/stagefright/timedtext/TimedTextDriver.h> 26#include <media/stagefright/MediaErrors.h> 27#include <media/MediaPlayerInterface.h> 28 29#include "TimedTextPlayer.h" 30 31#include "TimedTextSource.h" 32 33namespace android { 34 35// Event should be fired a bit earlier considering the processing time till 36// application actually gets the notification message. 37static const int64_t kAdjustmentProcessingTimeUs = 100000ll; 38static const int64_t kMaxDelayUs = 5000000ll; 39static const int64_t kWaitTimeUsToRetryRead = 100000ll; 40static const int64_t kInvalidTimeUs = INT_MIN; 41 42TimedTextPlayer::TimedTextPlayer(const wp<MediaPlayerBase> &listener) 43 : mListener(listener), 44 mSource(NULL), 45 mPendingSeekTimeUs(kInvalidTimeUs), 46 mPaused(false), 47 mSendSubtitleGeneration(0) { 48} 49 50TimedTextPlayer::~TimedTextPlayer() { 51 if (mSource != NULL) { 52 mSource->stop(); 53 mSource.clear(); 54 mSource = NULL; 55 } 56} 57 58void TimedTextPlayer::start() { 59 (new AMessage(kWhatStart, id()))->post(); 60} 61 62void TimedTextPlayer::pause() { 63 (new AMessage(kWhatPause, id()))->post(); 64} 65 66void TimedTextPlayer::resume() { 67 (new AMessage(kWhatResume, id()))->post(); 68} 69 70void TimedTextPlayer::seekToAsync(int64_t timeUs) { 71 sp<AMessage> msg = new AMessage(kWhatSeek, id()); 72 msg->setInt64("seekTimeUs", timeUs); 73 msg->post(); 74} 75 76void TimedTextPlayer::setDataSource(sp<TimedTextSource> source) { 77 sp<AMessage> msg = new AMessage(kWhatSetSource, id()); 78 msg->setObject("source", source); 79 msg->post(); 80} 81 82void TimedTextPlayer::onMessageReceived(const sp<AMessage> &msg) { 83 switch (msg->what()) { 84 case kWhatPause: { 85 mPaused = true; 86 break; 87 } 88 case kWhatResume: { 89 mPaused = false; 90 if (mPendingSeekTimeUs != kInvalidTimeUs) { 91 seekToAsync(mPendingSeekTimeUs); 92 mPendingSeekTimeUs = kInvalidTimeUs; 93 } else { 94 doRead(); 95 } 96 break; 97 } 98 case kWhatStart: { 99 sp<MediaPlayerBase> listener = mListener.promote(); 100 if (listener == NULL) { 101 ALOGE("Listener is NULL when kWhatStart is received."); 102 break; 103 } 104 mPaused = false; 105 mPendingSeekTimeUs = kInvalidTimeUs; 106 int32_t positionMs = 0; 107 listener->getCurrentPosition(&positionMs); 108 int64_t seekTimeUs = positionMs * 1000ll; 109 110 notifyListener(); 111 mSendSubtitleGeneration++; 112 doSeekAndRead(seekTimeUs); 113 break; 114 } 115 case kWhatRetryRead: { 116 int32_t generation = -1; 117 CHECK(msg->findInt32("generation", &generation)); 118 if (generation != mSendSubtitleGeneration) { 119 // Drop obsolete msg. 120 break; 121 } 122 int64_t seekTimeUs; 123 int seekMode; 124 if (msg->findInt64("seekTimeUs", &seekTimeUs) && 125 msg->findInt32("seekMode", &seekMode)) { 126 MediaSource::ReadOptions options; 127 options.setSeekTo( 128 seekTimeUs, 129 static_cast<MediaSource::ReadOptions::SeekMode>(seekMode)); 130 doRead(&options); 131 } else { 132 doRead(); 133 } 134 break; 135 } 136 case kWhatSeek: { 137 int64_t seekTimeUs = kInvalidTimeUs; 138 // Clear a displayed timed text before seeking. 139 notifyListener(); 140 msg->findInt64("seekTimeUs", &seekTimeUs); 141 if (seekTimeUs == kInvalidTimeUs) { 142 sp<MediaPlayerBase> listener = mListener.promote(); 143 if (listener != NULL) { 144 int32_t positionMs = 0; 145 listener->getCurrentPosition(&positionMs); 146 seekTimeUs = positionMs * 1000ll; 147 } 148 } 149 if (mPaused) { 150 mPendingSeekTimeUs = seekTimeUs; 151 break; 152 } 153 mSendSubtitleGeneration++; 154 doSeekAndRead(seekTimeUs); 155 break; 156 } 157 case kWhatSendSubtitle: { 158 int32_t generation; 159 CHECK(msg->findInt32("generation", &generation)); 160 if (generation != mSendSubtitleGeneration) { 161 // Drop obsolete msg. 162 break; 163 } 164 // If current time doesn't reach to the fire time, 165 // re-post the message with the adjusted delay time. 166 int64_t fireTimeUs = kInvalidTimeUs; 167 if (msg->findInt64("fireTimeUs", &fireTimeUs)) { 168 // TODO: check if fireTimeUs is not kInvalidTimeUs. 169 int64_t delayUs = delayUsFromCurrentTime(fireTimeUs); 170 if (delayUs > 0) { 171 msg->post(delayUs); 172 break; 173 } 174 } 175 sp<RefBase> obj; 176 if (msg->findObject("subtitle", &obj)) { 177 sp<ParcelEvent> parcelEvent; 178 parcelEvent = static_cast<ParcelEvent*>(obj.get()); 179 notifyListener(&(parcelEvent->parcel)); 180 doRead(); 181 } else { 182 notifyListener(); 183 } 184 break; 185 } 186 case kWhatSetSource: { 187 mSendSubtitleGeneration++; 188 sp<RefBase> obj; 189 msg->findObject("source", &obj); 190 if (mSource != NULL) { 191 mSource->stop(); 192 mSource.clear(); 193 mSource = NULL; 194 } 195 // null source means deselect track. 196 if (obj == NULL) { 197 mPendingSeekTimeUs = kInvalidTimeUs; 198 mPaused = false; 199 notifyListener(); 200 break; 201 } 202 mSource = static_cast<TimedTextSource*>(obj.get()); 203 status_t err = mSource->start(); 204 if (err != OK) { 205 notifyError(err); 206 break; 207 } 208 Parcel parcel; 209 err = mSource->extractGlobalDescriptions(&parcel); 210 if (err != OK) { 211 notifyError(err); 212 break; 213 } 214 notifyListener(&parcel); 215 break; 216 } 217 } 218} 219 220void TimedTextPlayer::doSeekAndRead(int64_t seekTimeUs) { 221 MediaSource::ReadOptions options; 222 options.setSeekTo(seekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); 223 doRead(&options); 224} 225 226void TimedTextPlayer::doRead(MediaSource::ReadOptions* options) { 227 int64_t startTimeUs = 0; 228 int64_t endTimeUs = 0; 229 sp<ParcelEvent> parcelEvent = new ParcelEvent(); 230 CHECK(mSource != NULL); 231 status_t err = mSource->read(&startTimeUs, &endTimeUs, 232 &(parcelEvent->parcel), options); 233 if (err == WOULD_BLOCK) { 234 sp<AMessage> msg = new AMessage(kWhatRetryRead, id()); 235 if (options != NULL) { 236 int64_t seekTimeUs = kInvalidTimeUs; 237 MediaSource::ReadOptions::SeekMode seekMode = 238 MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC; 239 CHECK(options->getSeekTo(&seekTimeUs, &seekMode)); 240 msg->setInt64("seekTimeUs", seekTimeUs); 241 msg->setInt32("seekMode", seekMode); 242 } 243 msg->setInt32("generation", mSendSubtitleGeneration); 244 msg->post(kWaitTimeUsToRetryRead); 245 return; 246 } else if (err != OK) { 247 notifyError(err); 248 return; 249 } 250 251 postTextEvent(parcelEvent, startTimeUs); 252 if (endTimeUs > 0) { 253 CHECK_GE(endTimeUs, startTimeUs); 254 // send an empty timed text to clear the subtitle when it reaches to the 255 // end time. 256 postTextEvent(NULL, endTimeUs); 257 } 258} 259 260void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeUs) { 261 int64_t delayUs = delayUsFromCurrentTime(timeUs); 262 sp<AMessage> msg = new AMessage(kWhatSendSubtitle, id()); 263 msg->setInt32("generation", mSendSubtitleGeneration); 264 if (parcel != NULL) { 265 msg->setObject("subtitle", parcel); 266 } 267 msg->setInt64("fireTimeUs", timeUs); 268 msg->post(delayUs); 269} 270 271int64_t TimedTextPlayer::delayUsFromCurrentTime(int64_t fireTimeUs) { 272 sp<MediaPlayerBase> listener = mListener.promote(); 273 if (listener == NULL) { 274 // TODO: it may be better to return kInvalidTimeUs 275 ALOGE("%s: Listener is NULL. (fireTimeUs = %" PRId64" )", 276 __FUNCTION__, fireTimeUs); 277 return 0; 278 } 279 int32_t positionMs = 0; 280 listener->getCurrentPosition(&positionMs); 281 int64_t positionUs = positionMs * 1000ll; 282 283 if (fireTimeUs <= positionUs + kAdjustmentProcessingTimeUs) { 284 return 0; 285 } else { 286 int64_t delayUs = fireTimeUs - positionUs - kAdjustmentProcessingTimeUs; 287 if (delayUs > kMaxDelayUs) { 288 return kMaxDelayUs; 289 } 290 return delayUs; 291 } 292} 293 294void TimedTextPlayer::notifyError(int error) { 295 sp<MediaPlayerBase> listener = mListener.promote(); 296 if (listener == NULL) { 297 ALOGE("%s(error=%d): Listener is NULL.", __FUNCTION__, error); 298 return; 299 } 300 listener->sendEvent(MEDIA_INFO, MEDIA_INFO_TIMED_TEXT_ERROR, error); 301} 302 303void TimedTextPlayer::notifyListener(const Parcel *parcel) { 304 sp<MediaPlayerBase> listener = mListener.promote(); 305 if (listener == NULL) { 306 ALOGE("%s: Listener is NULL.", __FUNCTION__); 307 return; 308 } 309 if (parcel != NULL && (parcel->dataSize() > 0)) { 310 listener->sendEvent(MEDIA_TIMED_TEXT, 0, 0, parcel); 311 } else { // send an empty timed text to clear the screen 312 listener->sendEvent(MEDIA_TIMED_TEXT); 313 } 314} 315 316} // namespace android 317