summaryrefslogtreecommitdiffstats
path: root/media/libstagefright
diff options
context:
space:
mode:
Diffstat (limited to 'media/libstagefright')
-rw-r--r--media/libstagefright/ACodec.cpp410
-rw-r--r--media/libstagefright/Android.mk6
-rw-r--r--media/libstagefright/AudioPlayer.cpp389
-rw-r--r--media/libstagefright/AudioSource.cpp9
-rw-r--r--media/libstagefright/AwesomePlayer.cpp307
-rw-r--r--media/libstagefright/CameraSource.cpp2
-rw-r--r--media/libstagefright/HTTPBase.cpp12
-rw-r--r--media/libstagefright/MPEG4Extractor.cpp78
-rw-r--r--media/libstagefright/MediaCodec.cpp21
-rw-r--r--media/libstagefright/MediaCodecList.cpp5
-rw-r--r--media/libstagefright/MediaDefs.cpp3
-rw-r--r--media/libstagefright/MediaMuxer.cpp10
-rw-r--r--media/libstagefright/OMXClient.cpp38
-rw-r--r--media/libstagefright/OMXCodec.cpp25
-rw-r--r--media/libstagefright/SurfaceMediaSource.cpp41
-rw-r--r--media/libstagefright/TimedEventQueue.cpp82
-rw-r--r--media/libstagefright/Utils.cpp132
-rw-r--r--media/libstagefright/codecs/aacdec/SoftAAC2.cpp3
-rw-r--r--media/libstagefright/codecs/aacenc/SampleCode/Android.mk2
-rw-r--r--media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp4
-rw-r--r--media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp5
-rw-r--r--media/libstagefright/codecs/amrnb/dec/SoftAMR.h1
-rw-r--r--media/libstagefright/codecs/amrwbenc/SampleCode/Android.mk2
-rw-r--r--media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp338
-rw-r--r--media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.h33
-rw-r--r--media/libstagefright/codecs/mp3dec/SoftMP3.cpp2
-rw-r--r--media/libstagefright/codecs/on2/dec/SoftVPX.cpp256
-rw-r--r--media/libstagefright/codecs/on2/dec/SoftVPX.h31
-rw-r--r--media/libstagefright/codecs/on2/enc/Android.mk5
-rw-r--r--media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp232
-rw-r--r--media/libstagefright/codecs/on2/enc/SoftVPXEncoder.h27
-rw-r--r--media/libstagefright/codecs/on2/h264dec/Android.mk2
-rw-r--r--media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp313
-rw-r--r--media/libstagefright/codecs/on2/h264dec/SoftAVC.h31
-rw-r--r--media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp2
-rw-r--r--media/libstagefright/foundation/AHierarchicalStateMachine.cpp4
-rw-r--r--media/libstagefright/foundation/ALooper.cpp4
-rw-r--r--media/libstagefright/foundation/ALooperRoster.cpp14
-rw-r--r--media/libstagefright/foundation/ANetworkSession.cpp (renamed from media/libstagefright/wifi-display/ANetworkSession.cpp)183
-rw-r--r--media/libstagefright/foundation/Android.mk2
-rw-r--r--media/libstagefright/foundation/ParsedMessage.cpp (renamed from media/libstagefright/wifi-display/ParsedMessage.cpp)26
-rw-r--r--media/libstagefright/httplive/Android.mk12
-rw-r--r--media/libstagefright/httplive/LiveSession.cpp1249
-rw-r--r--media/libstagefright/httplive/LiveSession.h (renamed from media/libstagefright/include/LiveSession.h)130
-rw-r--r--media/libstagefright/httplive/M3UParser.cpp595
-rw-r--r--media/libstagefright/httplive/M3UParser.h (renamed from media/libstagefright/include/M3UParser.h)23
-rw-r--r--media/libstagefright/httplive/PlaylistFetcher.cpp976
-rw-r--r--media/libstagefright/httplive/PlaylistFetcher.h156
-rw-r--r--media/libstagefright/id3/Android.mk2
-rw-r--r--media/libstagefright/id3/ID3.cpp63
-rw-r--r--media/libstagefright/include/AwesomePlayer.h22
-rw-r--r--media/libstagefright/include/ESDS.h6
-rw-r--r--media/libstagefright/include/HTTPBase.h3
-rw-r--r--media/libstagefright/include/ID3.h7
-rw-r--r--media/libstagefright/include/MPEG2TSExtractor.h5
-rw-r--r--media/libstagefright/include/MPEG4Extractor.h1
-rw-r--r--media/libstagefright/include/OMX.h15
-rw-r--r--media/libstagefright/include/OMXNodeInstance.h14
-rw-r--r--media/libstagefright/include/SoftVideoDecoderOMXComponent.h93
-rw-r--r--media/libstagefright/include/TimedEventQueue.h25
-rw-r--r--media/libstagefright/matroska/MatroskaExtractor.cpp4
-rw-r--r--media/libstagefright/mpeg2ts/AnotherPacketSource.cpp29
-rw-r--r--media/libstagefright/mpeg2ts/AnotherPacketSource.h2
-rw-r--r--media/libstagefright/mpeg2ts/ESQueue.cpp7
-rw-r--r--media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp39
-rw-r--r--media/libstagefright/omx/Android.mk1
-rw-r--r--media/libstagefright/omx/GraphicBufferSource.cpp284
-rw-r--r--media/libstagefright/omx/GraphicBufferSource.h54
-rw-r--r--media/libstagefright/omx/OMX.cpp23
-rw-r--r--media/libstagefright/omx/OMXNodeInstance.cpp125
-rw-r--r--media/libstagefright/omx/SoftOMXPlugin.cpp5
-rw-r--r--media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp290
-rw-r--r--media/libstagefright/omx/tests/OMXHarness.cpp3
-rw-r--r--media/libstagefright/rtsp/ARTSPConnection.cpp6
-rw-r--r--media/libstagefright/rtsp/Android.mk2
-rw-r--r--media/libstagefright/rtsp/MyHandler.h46
-rw-r--r--media/libstagefright/tests/SurfaceMediaSource_test.cpp2
-rw-r--r--media/libstagefright/wifi-display/ANetworkSession.h132
-rw-r--r--media/libstagefright/wifi-display/Android.mk25
-rw-r--r--media/libstagefright/wifi-display/MediaSender.cpp55
-rw-r--r--media/libstagefright/wifi-display/MediaSender.h1
-rw-r--r--media/libstagefright/wifi-display/ParsedMessage.h60
-rw-r--r--media/libstagefright/wifi-display/VideoFormats.cpp144
-rw-r--r--media/libstagefright/wifi-display/VideoFormats.h23
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPSender.cpp14
-rw-r--r--media/libstagefright/wifi-display/rtp/RTPSender.h1
-rw-r--r--media/libstagefright/wifi-display/source/Converter.cpp235
-rw-r--r--media/libstagefright/wifi-display/source/Converter.h71
-rw-r--r--media/libstagefright/wifi-display/source/MediaPuller.cpp3
-rw-r--r--media/libstagefright/wifi-display/source/PlaybackSession.cpp149
-rw-r--r--media/libstagefright/wifi-display/source/PlaybackSession.h15
-rw-r--r--media/libstagefright/wifi-display/source/TSPacketizer.cpp24
-rw-r--r--media/libstagefright/wifi-display/source/WifiDisplaySource.cpp44
-rw-r--r--media/libstagefright/wifi-display/source/WifiDisplaySource.h4
-rw-r--r--media/libstagefright/wifi-display/wfd.cpp269
95 files changed, 6160 insertions, 2520 deletions
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index cf41cf2..1adab38 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -255,6 +255,8 @@ private:
struct ACodec::ExecutingState : public ACodec::BaseState {
ExecutingState(ACodec *codec);
+ void submitRegularOutputBuffers();
+ void submitOutputMetaBuffers();
void submitOutputBuffers();
// Submit output buffers to the decoder, submit input buffers to client
@@ -359,11 +361,16 @@ ACodec::ACodec()
mNode(NULL),
mSentFormat(false),
mIsEncoder(false),
+ mUseMetadataOnEncoderOutput(false),
mShutdownInProgress(false),
mEncoderDelay(0),
mEncoderPadding(0),
mChannelMaskPresent(false),
- mChannelMask(0) {
+ mChannelMask(0),
+ mDequeueCounter(0),
+ mStoreMetaDataInOutputBuffers(false),
+ mMetaDataBuffersToSubmit(0),
+ mRepeatFrameDelayUs(-1ll) {
mUninitializedState = new UninitializedState(this);
mLoadedState = new LoadedState(this);
mLoadedToIdleState = new LoadedToIdleState(this);
@@ -453,7 +460,11 @@ status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) {
status_t err;
if (mNativeWindow != NULL && portIndex == kPortIndexOutput) {
- err = allocateOutputBuffersFromNativeWindow();
+ if (mStoreMetaDataInOutputBuffers) {
+ err = allocateOutputMetaDataBuffers();
+ } else {
+ err = allocateOutputBuffersFromNativeWindow();
+ }
} else {
OMX_PARAM_PORTDEFINITIONTYPE def;
InitOMXParams(&def);
@@ -483,7 +494,8 @@ status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) {
? OMXCodec::kRequiresAllocateBufferOnInputPorts
: OMXCodec::kRequiresAllocateBufferOnOutputPorts;
- if (portIndex == kPortIndexInput && (mFlags & kFlagIsSecure)) {
+ if ((portIndex == kPortIndexInput && (mFlags & kFlagIsSecure))
+ || mUseMetadataOnEncoderOutput) {
mem.clear();
void *ptr;
@@ -491,7 +503,10 @@ status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) {
mNode, portIndex, def.nBufferSize, &info.mBufferID,
&ptr);
- info.mData = new ABuffer(ptr, def.nBufferSize);
+ int32_t bufSize = mUseMetadataOnEncoderOutput ?
+ (4 + sizeof(buffer_handle_t)) : def.nBufferSize;
+
+ info.mData = new ABuffer(ptr, bufSize);
} else if (mQuirks & requiresAllocateBufferBit) {
err = mOMX->allocateBufferWithBackup(
mNode, portIndex, mem, &info.mBufferID);
@@ -531,7 +546,9 @@ status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) {
return OK;
}
-status_t ACodec::allocateOutputBuffersFromNativeWindow() {
+status_t ACodec::configureOutputBuffersFromNativeWindow(
+ OMX_U32 *bufferCount, OMX_U32 *bufferSize,
+ OMX_U32 *minUndequeuedBuffers) {
OMX_PARAM_PORTDEFINITIONTYPE def;
InitOMXParams(&def);
def.nPortIndex = kPortIndexOutput;
@@ -596,10 +613,10 @@ status_t ACodec::allocateOutputBuffersFromNativeWindow() {
return err;
}
- int minUndequeuedBufs = 0;
+ *minUndequeuedBuffers = 0;
err = mNativeWindow->query(
mNativeWindow.get(), NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
- &minUndequeuedBufs);
+ (int *)minUndequeuedBuffers);
if (err != 0) {
ALOGE("NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS query failed: %s (%d)",
@@ -610,8 +627,8 @@ status_t ACodec::allocateOutputBuffersFromNativeWindow() {
// XXX: Is this the right logic to use? It's not clear to me what the OMX
// buffer counts refer to - how do they account for the renderer holding on
// to buffers?
- if (def.nBufferCountActual < def.nBufferCountMin + minUndequeuedBufs) {
- OMX_U32 newBufferCount = def.nBufferCountMin + minUndequeuedBufs;
+ if (def.nBufferCountActual < def.nBufferCountMin + *minUndequeuedBuffers) {
+ OMX_U32 newBufferCount = def.nBufferCountMin + *minUndequeuedBuffers;
def.nBufferCountActual = newBufferCount;
err = mOMX->setParameter(
mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
@@ -632,12 +649,24 @@ status_t ACodec::allocateOutputBuffersFromNativeWindow() {
return err;
}
+ *bufferCount = def.nBufferCountActual;
+ *bufferSize = def.nBufferSize;
+ return err;
+}
+
+status_t ACodec::allocateOutputBuffersFromNativeWindow() {
+ OMX_U32 bufferCount, bufferSize, minUndequeuedBuffers;
+ status_t err = configureOutputBuffersFromNativeWindow(
+ &bufferCount, &bufferSize, &minUndequeuedBuffers);
+ if (err != 0)
+ return err;
+
ALOGV("[%s] Allocating %lu buffers from a native window of size %lu on "
"output port",
- mComponentName.c_str(), def.nBufferCountActual, def.nBufferSize);
+ mComponentName.c_str(), bufferCount, bufferSize);
// Dequeue buffers and send them to OMX
- for (OMX_U32 i = 0; i < def.nBufferCountActual; i++) {
+ for (OMX_U32 i = 0; i < bufferCount; i++) {
ANativeWindowBuffer *buf;
err = native_window_dequeue_buffer_and_wait(mNativeWindow.get(), &buf);
if (err != 0) {
@@ -648,7 +677,7 @@ status_t ACodec::allocateOutputBuffersFromNativeWindow() {
sp<GraphicBuffer> graphicBuffer(new GraphicBuffer(buf, false));
BufferInfo info;
info.mStatus = BufferInfo::OWNED_BY_US;
- info.mData = new ABuffer(NULL /* data */, def.nBufferSize /* capacity */);
+ info.mData = new ABuffer(NULL /* data */, bufferSize /* capacity */);
info.mGraphicBuffer = graphicBuffer;
mBuffers[kPortIndexOutput].push(info);
@@ -677,9 +706,9 @@ status_t ACodec::allocateOutputBuffersFromNativeWindow() {
cancelStart = 0;
cancelEnd = mBuffers[kPortIndexOutput].size();
} else {
- // Return the last two buffers to the native window.
- cancelStart = def.nBufferCountActual - minUndequeuedBufs;
- cancelEnd = def.nBufferCountActual;
+ // Return the required minimum undequeued buffers to the native window.
+ cancelStart = bufferCount - minUndequeuedBuffers;
+ cancelEnd = bufferCount;
}
for (OMX_U32 i = cancelStart; i < cancelEnd; i++) {
@@ -690,6 +719,65 @@ status_t ACodec::allocateOutputBuffersFromNativeWindow() {
return err;
}
+status_t ACodec::allocateOutputMetaDataBuffers() {
+ OMX_U32 bufferCount, bufferSize, minUndequeuedBuffers;
+ status_t err = configureOutputBuffersFromNativeWindow(
+ &bufferCount, &bufferSize, &minUndequeuedBuffers);
+ if (err != 0)
+ return err;
+
+ ALOGV("[%s] Allocating %lu meta buffers on output port",
+ mComponentName.c_str(), bufferCount);
+
+ size_t totalSize = bufferCount * 8;
+ mDealer[kPortIndexOutput] = new MemoryDealer(totalSize, "ACodec");
+
+ // Dequeue buffers and send them to OMX
+ for (OMX_U32 i = 0; i < bufferCount; i++) {
+ BufferInfo info;
+ info.mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW;
+ info.mGraphicBuffer = NULL;
+ info.mDequeuedAt = mDequeueCounter;
+
+ sp<IMemory> mem = mDealer[kPortIndexOutput]->allocate(
+ sizeof(struct VideoDecoderOutputMetaData));
+ CHECK(mem.get() != NULL);
+ info.mData = new ABuffer(mem->pointer(), mem->size());
+
+ // we use useBuffer for metadata regardless of quirks
+ err = mOMX->useBuffer(
+ mNode, kPortIndexOutput, mem, &info.mBufferID);
+
+ mBuffers[kPortIndexOutput].push(info);
+
+ ALOGV("[%s] allocated meta buffer with ID %p (pointer = %p)",
+ mComponentName.c_str(), info.mBufferID, mem->pointer());
+ }
+
+ mMetaDataBuffersToSubmit = bufferCount - minUndequeuedBuffers;
+ return err;
+}
+
+status_t ACodec::submitOutputMetaDataBuffer() {
+ CHECK(mStoreMetaDataInOutputBuffers);
+ if (mMetaDataBuffersToSubmit == 0)
+ return OK;
+
+ BufferInfo *info = dequeueBufferFromNativeWindow();
+ if (info == NULL)
+ return ERROR_IO;
+
+ ALOGV("[%s] submitting output meta buffer ID %p for graphic buffer %p",
+ mComponentName.c_str(), info->mBufferID, info->mGraphicBuffer.get());
+
+ --mMetaDataBuffersToSubmit;
+ CHECK_EQ(mOMX->fillBuffer(mNode, info->mBufferID),
+ (status_t)OK);
+
+ info->mStatus = BufferInfo::OWNED_BY_COMPONENT;
+ return OK;
+}
+
status_t ACodec::cancelBufferToNativeWindow(BufferInfo *info) {
CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US);
@@ -709,16 +797,19 @@ status_t ACodec::cancelBufferToNativeWindow(BufferInfo *info) {
ACodec::BufferInfo *ACodec::dequeueBufferFromNativeWindow() {
ANativeWindowBuffer *buf;
int fenceFd = -1;
+ CHECK(mNativeWindow.get() != NULL);
if (native_window_dequeue_buffer_and_wait(mNativeWindow.get(), &buf) != 0) {
ALOGE("dequeueBuffer failed.");
return NULL;
}
+ BufferInfo *oldest = NULL;
for (size_t i = mBuffers[kPortIndexOutput].size(); i-- > 0;) {
BufferInfo *info =
&mBuffers[kPortIndexOutput].editItemAt(i);
- if (info->mGraphicBuffer->handle == buf->handle) {
+ if (info->mGraphicBuffer != NULL &&
+ info->mGraphicBuffer->handle == buf->handle) {
CHECK_EQ((int)info->mStatus,
(int)BufferInfo::OWNED_BY_NATIVE_WINDOW);
@@ -726,6 +817,39 @@ ACodec::BufferInfo *ACodec::dequeueBufferFromNativeWindow() {
return info;
}
+
+ if (info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW &&
+ (oldest == NULL ||
+ // avoid potential issues from counter rolling over
+ mDequeueCounter - info->mDequeuedAt >
+ mDequeueCounter - oldest->mDequeuedAt)) {
+ oldest = info;
+ }
+ }
+
+ if (oldest) {
+ CHECK(mStoreMetaDataInOutputBuffers);
+
+ // discard buffer in LRU info and replace with new buffer
+ oldest->mGraphicBuffer = new GraphicBuffer(buf, false);
+ oldest->mStatus = BufferInfo::OWNED_BY_US;
+
+ mOMX->updateGraphicBufferInMeta(
+ mNode, kPortIndexOutput, oldest->mGraphicBuffer,
+ oldest->mBufferID);
+
+ VideoDecoderOutputMetaData *metaData =
+ reinterpret_cast<VideoDecoderOutputMetaData *>(
+ oldest->mData->base());
+ CHECK_EQ(metaData->eType, kMetadataBufferTypeGrallocSource);
+
+ ALOGV("replaced oldest buffer #%u with age %u (%p/%p stored in %p)",
+ oldest - &mBuffers[kPortIndexOutput][0],
+ mDequeueCounter - oldest->mDequeuedAt,
+ metaData->pHandle,
+ oldest->mGraphicBuffer->handle, oldest->mData->base());
+
+ return oldest;
}
TRESPASS();
@@ -831,8 +955,10 @@ status_t ACodec::setComponentRole(
"video_decoder.mpeg4", "video_encoder.mpeg4" },
{ MEDIA_MIMETYPE_VIDEO_H263,
"video_decoder.h263", "video_encoder.h263" },
- { MEDIA_MIMETYPE_VIDEO_VPX,
- "video_decoder.vpx", "video_encoder.vpx" },
+ { MEDIA_MIMETYPE_VIDEO_VP8,
+ "video_decoder.vp8", "video_encoder.vp8" },
+ { MEDIA_MIMETYPE_VIDEO_VP9,
+ "video_decoder.vp9", "video_encoder.vp9" },
{ MEDIA_MIMETYPE_AUDIO_RAW,
"audio_decoder.raw", "audio_encoder.raw" },
{ MEDIA_MIMETYPE_AUDIO_FLAC,
@@ -912,14 +1038,14 @@ status_t ACodec::configureCodec(
err = mOMX->storeMetaDataInBuffers(mNode, kPortIndexInput, OMX_TRUE);
if (err != OK) {
- ALOGE("[%s] storeMetaDataInBuffers failed w/ err %d",
- mComponentName.c_str(), err);
+ ALOGE("[%s] storeMetaDataInBuffers (input) failed w/ err %d",
+ mComponentName.c_str(), err);
- return err;
- }
- }
+ return err;
+ }
+ }
- int32_t prependSPSPPS;
+ int32_t prependSPSPPS = 0;
if (encoder
&& msg->findInt32("prepend-sps-pps-to-idr-frames", &prependSPSPPS)
&& prependSPSPPS != 0) {
@@ -946,7 +1072,97 @@ status_t ACodec::configureCodec(
}
}
- if (!strncasecmp(mime, "video/", 6)) {
+ // Only enable metadata mode on encoder output if encoder can prepend
+ // sps/pps to idr frames, since in metadata mode the bitstream is in an
+ // opaque handle, to which we don't have access.
+ int32_t video = !strncasecmp(mime, "video/", 6);
+ if (encoder && video) {
+ OMX_BOOL enable = (OMX_BOOL) (prependSPSPPS
+ && msg->findInt32("store-metadata-in-buffers-output", &storeMeta)
+ && storeMeta != 0);
+
+ err = mOMX->storeMetaDataInBuffers(mNode, kPortIndexOutput, enable);
+
+ if (err != OK) {
+ ALOGE("[%s] storeMetaDataInBuffers (output) failed w/ err %d",
+ mComponentName.c_str(), err);
+ mUseMetadataOnEncoderOutput = 0;
+ } else {
+ mUseMetadataOnEncoderOutput = enable;
+ }
+
+ if (!msg->findInt64(
+ "repeat-previous-frame-after",
+ &mRepeatFrameDelayUs)) {
+ mRepeatFrameDelayUs = -1ll;
+ }
+ }
+
+ // Always try to enable dynamic output buffers on native surface
+ sp<RefBase> obj;
+ int32_t haveNativeWindow = msg->findObject("native-window", &obj) &&
+ obj != NULL;
+ mStoreMetaDataInOutputBuffers = false;
+ if (!encoder && video && haveNativeWindow) {
+ err = mOMX->storeMetaDataInBuffers(mNode, kPortIndexOutput, OMX_TRUE);
+ if (err != OK) {
+ ALOGE("[%s] storeMetaDataInBuffers failed w/ err %d",
+ mComponentName.c_str(), err);
+
+ // if adaptive playback has been requested, try JB fallback
+ // NOTE: THIS FALLBACK MECHANISM WILL BE REMOVED DUE TO ITS
+ // LARGE MEMORY REQUIREMENT
+
+ // we will not do adaptive playback on software accessed
+ // surfaces as they never had to respond to changes in the
+ // crop window, and we don't trust that they will be able to.
+ int usageBits = 0;
+ bool canDoAdaptivePlayback;
+
+ sp<NativeWindowWrapper> windowWrapper(
+ static_cast<NativeWindowWrapper *>(obj.get()));
+ sp<ANativeWindow> nativeWindow = windowWrapper->getNativeWindow();
+
+ if (nativeWindow->query(
+ nativeWindow.get(),
+ NATIVE_WINDOW_CONSUMER_USAGE_BITS,
+ &usageBits) != OK) {
+ canDoAdaptivePlayback = false;
+ } else {
+ canDoAdaptivePlayback =
+ (usageBits &
+ (GRALLOC_USAGE_SW_READ_MASK |
+ GRALLOC_USAGE_SW_WRITE_MASK)) == 0;
+ }
+
+ int32_t maxWidth = 0, maxHeight = 0;
+ if (canDoAdaptivePlayback &&
+ msg->findInt32("max-width", &maxWidth) &&
+ msg->findInt32("max-height", &maxHeight)) {
+ ALOGV("[%s] prepareForAdaptivePlayback(%ldx%ld)",
+ mComponentName.c_str(), maxWidth, maxHeight);
+
+ err = mOMX->prepareForAdaptivePlayback(
+ mNode, kPortIndexOutput, OMX_TRUE, maxWidth, maxHeight);
+ ALOGW_IF(err != OK,
+ "[%s] prepareForAdaptivePlayback failed w/ err %d",
+ mComponentName.c_str(), err);
+ }
+ // allow failure
+ err = OK;
+ } else {
+ ALOGV("[%s] storeMetaDataInBuffers succeeded", mComponentName.c_str());
+ mStoreMetaDataInOutputBuffers = true;
+ }
+
+ int32_t push;
+ if (msg->findInt32("push-blank-buffers-on-shutdown", &push)
+ && push != 0) {
+ mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown;
+ }
+ }
+
+ if (video) {
if (encoder) {
err = setupVideoEncoder(mime, msg);
} else {
@@ -1476,7 +1692,8 @@ static const struct VideoCodingMapEntry {
{ MEDIA_MIMETYPE_VIDEO_MPEG4, OMX_VIDEO_CodingMPEG4 },
{ MEDIA_MIMETYPE_VIDEO_H263, OMX_VIDEO_CodingH263 },
{ MEDIA_MIMETYPE_VIDEO_MPEG2, OMX_VIDEO_CodingMPEG2 },
- { MEDIA_MIMETYPE_VIDEO_VPX, OMX_VIDEO_CodingVPX },
+ { MEDIA_MIMETYPE_VIDEO_VP8, OMX_VIDEO_CodingVP8 },
+ { MEDIA_MIMETYPE_VIDEO_VP9, OMX_VIDEO_CodingVP9 },
};
static status_t GetVideoCodingTypeFromMime(
@@ -2189,6 +2406,10 @@ void ACodec::waitUntilAllPossibleNativeWindowBuffersAreReturnedToUs() {
while (countBuffersOwnedByNativeWindow() > (size_t)minUndequeuedBufs
&& dequeueBufferFromNativeWindow() != NULL) {
+ // these buffers will be submitted as regular buffers; account for this
+ if (mStoreMetaDataInOutputBuffers && mMetaDataBuffersToSubmit > 0) {
+ --mMetaDataBuffersToSubmit;
+ }
}
}
@@ -2321,10 +2542,15 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) {
&params, sizeof(params)),
(status_t)OK);
+ CHECK_GT(params.nChannels, 0);
CHECK(params.nChannels == 1 || params.bInterleaved);
CHECK_EQ(params.nBitPerSample, 16u);
- CHECK_EQ((int)params.eNumData, (int)OMX_NumericalDataSigned);
- CHECK_EQ((int)params.ePCMMode, (int)OMX_AUDIO_PCMModeLinear);
+
+ CHECK_EQ((int)params.eNumData,
+ (int)OMX_NumericalDataSigned);
+
+ CHECK_EQ((int)params.ePCMMode,
+ (int)OMX_AUDIO_PCMModeLinear);
notify->setString("mime", MEDIA_MIMETYPE_AUDIO_RAW);
notify->setInt32("channel-count", params.nChannels);
@@ -2334,11 +2560,14 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) {
if (mSkipCutBuffer != NULL) {
size_t prevbufsize = mSkipCutBuffer->size();
if (prevbufsize != 0) {
- ALOGW("Replacing SkipCutBuffer holding %d bytes", prevbufsize);
+ ALOGW("Replacing SkipCutBuffer holding %d "
+ "bytes",
+ prevbufsize);
}
}
- mSkipCutBuffer = new SkipCutBuffer(mEncoderDelay * frameSize,
- mEncoderPadding * frameSize);
+ mSkipCutBuffer = new SkipCutBuffer(
+ mEncoderDelay * frameSize,
+ mEncoderPadding * frameSize);
}
if (mChannelMaskPresent) {
@@ -2463,6 +2692,14 @@ status_t ACodec::pushBlankBuffersToNativeWindow() {
goto error;
}
+ err = native_window_set_scaling_mode(mNativeWindow.get(),
+ NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
+ if (err != NO_ERROR) {
+ ALOGE("error pushing blank_frames: set_scaling_mode failed: %s (%d)",
+ strerror(-err), -err);
+ goto error;
+ }
+
err = native_window_set_usage(mNativeWindow.get(),
GRALLOC_USAGE_SW_WRITE_OFTEN);
if (err != NO_ERROR) {
@@ -2829,16 +3066,17 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
sp<ABuffer> buffer;
int32_t err = OK;
bool eos = false;
+ PortMode mode = getPortMode(kPortIndexInput);
if (!msg->findBuffer("buffer", &buffer)) {
+ /* these are unfilled buffers returned by client */
CHECK(msg->findInt32("err", &err));
ALOGV("[%s] saw error %d instead of an input buffer",
mCodec->mComponentName.c_str(), err);
buffer.clear();
-
- eos = true;
+ mode = KEEP_BUFFERS;
}
int32_t tmp;
@@ -2852,8 +3090,6 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
info->mStatus = BufferInfo::OWNED_BY_US;
- PortMode mode = getPortMode(kPortIndexInput);
-
switch (mode) {
case KEEP_BUFFERS:
{
@@ -2916,6 +3152,20 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
mCodec->mBufferStats.add(timeUs, stats);
#endif
+ if (mCodec->mStoreMetaDataInOutputBuffers) {
+ // try to submit an output buffer for each input buffer
+ PortMode outputMode = getPortMode(kPortIndexOutput);
+
+ ALOGV("MetaDataBuffersToSubmit=%u portMode=%s",
+ mCodec->mMetaDataBuffersToSubmit,
+ (outputMode == FREE_BUFFERS ? "FREE" :
+ outputMode == KEEP_BUFFERS ? "KEEP" : "RESUBMIT"));
+ if (outputMode == RESUBMIT_BUFFERS) {
+ CHECK_EQ(mCodec->submitOutputMetaDataBuffer(),
+ (status_t)OK);
+ }
+ }
+
CHECK_EQ(mCodec->mOMX->emptyBuffer(
mCodec->mNode,
bufferID,
@@ -3033,6 +3283,7 @@ bool ACodec::BaseState::onOMXFillBufferDone(
CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_COMPONENT);
+ info->mDequeuedAt = ++mCodec->mDequeueCounter;
info->mStatus = BufferInfo::OWNED_BY_US;
PortMode mode = getPortMode(kPortIndexOutput);
@@ -3062,7 +3313,15 @@ bool ACodec::BaseState::onOMXFillBufferDone(
mCodec->sendFormatChange(reply);
}
- info->mData->setRange(rangeOffset, rangeLength);
+ if (mCodec->mUseMetadataOnEncoderOutput) {
+ native_handle_t* handle =
+ *(native_handle_t**)(info->mData->data() + 4);
+ info->mData->meta()->setPointer("handle", handle);
+ info->mData->meta()->setInt32("rangeOffset", rangeOffset);
+ info->mData->meta()->setInt32("rangeLength", rangeLength);
+ } else {
+ info->mData->setRange(rangeOffset, rangeLength);
+ }
#if 0
if (mCodec->mNativeWindow == NULL) {
if (IsIDR(info->mData)) {
@@ -3220,6 +3479,7 @@ void ACodec::UninitializedState::stateEntered() {
mCodec->mOMX.clear();
mCodec->mQuirks = 0;
mCodec->mFlags = 0;
+ mCodec->mUseMetadataOnEncoderOutput = 0;
mCodec->mComponentName.clear();
}
@@ -3373,6 +3633,7 @@ bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {
if (componentName.endsWith(".secure")) {
mCodec->mFlags |= kFlagIsSecure;
+ mCodec->mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown;
}
mCodec->mQuirks = quirks;
@@ -3405,6 +3666,10 @@ void ACodec::LoadedState::stateEntered() {
mCodec->mInputEOSResult = OK;
+ mCodec->mDequeueCounter = 0;
+ mCodec->mMetaDataBuffersToSubmit = 0;
+ mCodec->mRepeatFrameDelayUs = -1ll;
+
if (mCodec->mShutdownInProgress) {
bool keepComponentAllocated = mCodec->mKeepComponentAllocated;
@@ -3535,6 +3800,23 @@ void ACodec::LoadedState::onCreateInputSurface(
err = mCodec->mOMX->createInputSurface(mCodec->mNode, kPortIndexInput,
&bufferProducer);
+
+ if (err == OK && mCodec->mRepeatFrameDelayUs > 0ll) {
+ err = mCodec->mOMX->setInternalOption(
+ mCodec->mNode,
+ kPortIndexInput,
+ IOMX::INTERNAL_OPTION_REPEAT_PREVIOUS_FRAME_DELAY,
+ &mCodec->mRepeatFrameDelayUs,
+ sizeof(mCodec->mRepeatFrameDelayUs));
+
+ if (err != OK) {
+ ALOGE("[%s] Unable to configure option to repeat previous "
+ "frames (err %d)",
+ mCodec->mComponentName.c_str(),
+ err);
+ }
+ }
+
if (err == OK) {
notify->setObject("input-surface",
new BufferProducerWrapper(bufferProducer));
@@ -3722,7 +4004,20 @@ ACodec::BaseState::PortMode ACodec::ExecutingState::getPortMode(
return RESUBMIT_BUFFERS;
}
-void ACodec::ExecutingState::submitOutputBuffers() {
+void ACodec::ExecutingState::submitOutputMetaBuffers() {
+ // submit as many buffers as there are input buffers with the codec
+ // in case we are in port reconfiguring
+ for (size_t i = 0; i < mCodec->mBuffers[kPortIndexInput].size(); ++i) {
+ BufferInfo *info = &mCodec->mBuffers[kPortIndexInput].editItemAt(i);
+
+ if (info->mStatus == BufferInfo::OWNED_BY_COMPONENT) {
+ if (mCodec->submitOutputMetaDataBuffer() != OK)
+ break;
+ }
+ }
+}
+
+void ACodec::ExecutingState::submitRegularOutputBuffers() {
for (size_t i = 0; i < mCodec->mBuffers[kPortIndexOutput].size(); ++i) {
BufferInfo *info = &mCodec->mBuffers[kPortIndexOutput].editItemAt(i);
@@ -3747,6 +4042,13 @@ void ACodec::ExecutingState::submitOutputBuffers() {
}
}
+void ACodec::ExecutingState::submitOutputBuffers() {
+ submitRegularOutputBuffers();
+ if (mCodec->mStoreMetaDataInOutputBuffers) {
+ submitOutputMetaBuffers();
+ }
+}
+
void ACodec::ExecutingState::resume() {
if (mActive) {
ALOGV("[%s] We're already active, no need to resume.",
@@ -3871,7 +4173,7 @@ bool ACodec::ExecutingState::onMessageReceived(const sp<AMessage> &msg) {
status_t ACodec::setParameters(const sp<AMessage> &params) {
int32_t videoBitrate;
- if (params->findInt32("videoBitrate", &videoBitrate)) {
+ if (params->findInt32("video-bitrate", &videoBitrate)) {
OMX_VIDEO_CONFIG_BITRATETYPE configParams;
InitOMXParams(&configParams);
configParams.nPortIndex = kPortIndexOutput;
@@ -3891,6 +4193,34 @@ status_t ACodec::setParameters(const sp<AMessage> &params) {
}
}
+ int32_t dropInputFrames;
+ if (params->findInt32("drop-input-frames", &dropInputFrames)) {
+ bool suspend = dropInputFrames != 0;
+
+ status_t err =
+ mOMX->setInternalOption(
+ mNode,
+ kPortIndexInput,
+ IOMX::INTERNAL_OPTION_SUSPEND,
+ &suspend,
+ sizeof(suspend));
+
+ if (err != OK) {
+ ALOGE("Failed to set parameter 'drop-input-frames' (err %d)", err);
+ return err;
+ }
+ }
+
+ int32_t dummy;
+ if (params->findInt32("request-sync", &dummy)) {
+ status_t err = requestIDRFrame();
+
+ if (err != OK) {
+ ALOGE("Requesting a sync frame failed w/ err %d", err);
+ return err;
+ }
+ }
+
return OK;
}
@@ -3913,6 +4243,7 @@ bool ACodec::ExecutingState::onOMXEvent(
CHECK_EQ(data1, (OMX_U32)kPortIndexOutput);
if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) {
+ mCodec->mMetaDataBuffersToSubmit = 0;
CHECK_EQ(mCodec->mOMX->sendCommand(
mCodec->mNode,
OMX_CommandPortDisable, kPortIndexOutput),
@@ -4131,7 +4462,8 @@ void ACodec::ExecutingToIdleState::changeStateIfWeOwnAllBuffers() {
CHECK_EQ(mCodec->freeBuffersOnPort(kPortIndexInput), (status_t)OK);
CHECK_EQ(mCodec->freeBuffersOnPort(kPortIndexOutput), (status_t)OK);
- if (mCodec->mFlags & kFlagIsSecure && mCodec->mNativeWindow != NULL) {
+ if ((mCodec->mFlags & kFlagPushBlankBuffersToNativeWindowOnShutdown)
+ && mCodec->mNativeWindow != NULL) {
// We push enough 1x1 blank buffers to ensure that one of
// them has made it to the display. This allows the OMX
// component teardown to zero out any protected buffers
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index acc3abf..6a2a696 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -62,6 +62,7 @@ LOCAL_C_INCLUDES:= \
$(TOP)/frameworks/av/include/media/stagefright/timedtext \
$(TOP)/frameworks/native/include/media/hardware \
$(TOP)/frameworks/native/include/media/openmax \
+ $(TOP)/frameworks/native/services/connectivitymanager \
$(TOP)/external/flac/include \
$(TOP)/external/tremolo \
$(TOP)/external/openssl/include \
@@ -69,7 +70,7 @@ LOCAL_C_INCLUDES:= \
LOCAL_SHARED_LIBRARIES := \
libbinder \
libcamera_client \
- libcrypto \
+ libconnectivitymanager \
libcutils \
libdl \
libdrmframework \
@@ -88,6 +89,7 @@ LOCAL_SHARED_LIBRARIES := \
libutils \
libvorbisidec \
libz \
+ libpowermanager
LOCAL_STATIC_LIBRARIES := \
libstagefright_color_conversion \
@@ -97,9 +99,9 @@ LOCAL_STATIC_LIBRARIES := \
libvpx \
libwebm \
libstagefright_mpeg2ts \
- libstagefright_httplive \
libstagefright_id3 \
libFLAC \
+ libmedia_helper
LOCAL_SRC_FILES += \
chromium_http_stub.cpp
diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp
index 4208019..a8a8786 100644
--- a/media/libstagefright/AudioPlayer.cpp
+++ b/media/libstagefright/AudioPlayer.cpp
@@ -17,6 +17,7 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "AudioPlayer"
#include <utils/Log.h>
+#include <cutils/compiler.h>
#include <binder/IPCThreadState.h>
#include <media/AudioTrack.h>
@@ -27,6 +28,7 @@
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
#include "include/AwesomePlayer.h"
@@ -34,10 +36,9 @@ namespace android {
AudioPlayer::AudioPlayer(
const sp<MediaPlayerBase::AudioSink> &audioSink,
- bool allowDeepBuffering,
+ uint32_t flags,
AwesomePlayer *observer)
- : mAudioTrack(NULL),
- mInputBuffer(NULL),
+ : mInputBuffer(NULL),
mSampleRate(0),
mLatencyUs(0),
mFrameSize(0),
@@ -48,14 +49,17 @@ AudioPlayer::AudioPlayer(
mSeeking(false),
mReachedEOS(false),
mFinalStatus(OK),
+ mSeekTimeUs(0),
mStarted(false),
mIsFirstBuffer(false),
mFirstBufferResult(OK),
mFirstBuffer(NULL),
mAudioSink(audioSink),
- mAllowDeepBuffering(allowDeepBuffering),
mObserver(observer),
- mPinnedTimeUs(-1ll) {
+ mPinnedTimeUs(-1ll),
+ mPlaying(false),
+ mStartPosUs(0),
+ mCreateFlags(flags) {
}
AudioPlayer::~AudioPlayer() {
@@ -110,7 +114,7 @@ status_t AudioPlayer::start(bool sourceAlreadyStarted) {
const char *mime;
bool success = format->findCString(kKeyMIMEType, &mime);
CHECK(success);
- CHECK(!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW));
+ CHECK(useOffload() || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW));
success = format->findInt32(kKeySampleRate, &mSampleRate);
CHECK(success);
@@ -126,16 +130,74 @@ status_t AudioPlayer::start(bool sourceAlreadyStarted) {
channelMask = CHANNEL_MASK_USE_CHANNEL_ORDER;
}
+ audio_format_t audioFormat = AUDIO_FORMAT_PCM_16_BIT;
+
+ if (useOffload()) {
+ if (mapMimeToAudioFormat(audioFormat, mime) != OK) {
+ ALOGE("Couldn't map mime type \"%s\" to a valid AudioSystem::audio_format", mime);
+ audioFormat = AUDIO_FORMAT_INVALID;
+ } else {
+ ALOGV("Mime type \"%s\" mapped to audio_format 0x%x", mime, audioFormat);
+ }
+ }
+
+ int avgBitRate = -1;
+ format->findInt32(kKeyBitRate, &avgBitRate);
+
if (mAudioSink.get() != NULL) {
+ uint32_t flags = AUDIO_OUTPUT_FLAG_NONE;
+ audio_offload_info_t offloadInfo = AUDIO_INFO_INITIALIZER;
+
+ if (allowDeepBuffering()) {
+ flags |= AUDIO_OUTPUT_FLAG_DEEP_BUFFER;
+ }
+ if (useOffload()) {
+ flags |= AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD;
+
+ int64_t durationUs;
+ if (format->findInt64(kKeyDuration, &durationUs)) {
+ offloadInfo.duration_us = durationUs;
+ } else {
+ offloadInfo.duration_us = -1;
+ }
+
+ offloadInfo.sample_rate = mSampleRate;
+ offloadInfo.channel_mask = channelMask;
+ offloadInfo.format = audioFormat;
+ offloadInfo.stream_type = AUDIO_STREAM_MUSIC;
+ offloadInfo.bit_rate = avgBitRate;
+ offloadInfo.has_video = ((mCreateFlags & HAS_VIDEO) != 0);
+ offloadInfo.is_streaming = ((mCreateFlags & IS_STREAMING) != 0);
+ }
+
status_t err = mAudioSink->open(
- mSampleRate, numChannels, channelMask, AUDIO_FORMAT_PCM_16_BIT,
+ mSampleRate, numChannels, channelMask, audioFormat,
DEFAULT_AUDIOSINK_BUFFERCOUNT,
&AudioPlayer::AudioSinkCallback,
this,
- (mAllowDeepBuffering ?
- AUDIO_OUTPUT_FLAG_DEEP_BUFFER :
- AUDIO_OUTPUT_FLAG_NONE));
+ (audio_output_flags_t)flags,
+ useOffload() ? &offloadInfo : NULL);
+
+ if (err == OK) {
+ mLatencyUs = (int64_t)mAudioSink->latency() * 1000;
+ mFrameSize = mAudioSink->frameSize();
+
+ if (useOffload()) {
+ // If the playback is offloaded to h/w we pass the
+ // HAL some metadata information
+ // We don't want to do this for PCM because it will be going
+ // through the AudioFlinger mixer before reaching the hardware
+ sendMetaDataToHal(mAudioSink, format);
+ }
+
+ err = mAudioSink->start();
+ // do not alter behavior for non offloaded tracks: ignore start status.
+ if (!useOffload()) {
+ err = OK;
+ }
+ }
+
if (err != OK) {
if (mFirstBuffer != NULL) {
mFirstBuffer->release();
@@ -149,10 +211,6 @@ status_t AudioPlayer::start(bool sourceAlreadyStarted) {
return err;
}
- mLatencyUs = (int64_t)mAudioSink->latency() * 1000;
- mFrameSize = mAudioSink->frameSize();
-
- mAudioSink->start();
} else {
// playing to an AudioTrack, set up mask if necessary
audio_channel_mask_t audioMask = channelMask == CHANNEL_MASK_USE_CHANNEL_ORDER ?
@@ -166,8 +224,7 @@ status_t AudioPlayer::start(bool sourceAlreadyStarted) {
0, AUDIO_OUTPUT_FLAG_NONE, &AudioCallback, this, 0);
if ((err = mAudioTrack->initCheck()) != OK) {
- delete mAudioTrack;
- mAudioTrack = NULL;
+ mAudioTrack.clear();
if (mFirstBuffer != NULL) {
mFirstBuffer->release();
@@ -188,6 +245,7 @@ status_t AudioPlayer::start(bool sourceAlreadyStarted) {
}
mStarted = true;
+ mPlaying = true;
mPinnedTimeUs = -1ll;
return OK;
@@ -214,29 +272,57 @@ void AudioPlayer::pause(bool playPendingSamples) {
mPinnedTimeUs = ALooper::GetNowUs();
}
+
+ mPlaying = false;
}
-void AudioPlayer::resume() {
+status_t AudioPlayer::resume() {
CHECK(mStarted);
+ status_t err;
if (mAudioSink.get() != NULL) {
- mAudioSink->start();
+ err = mAudioSink->start();
} else {
- mAudioTrack->start();
+ err = mAudioTrack->start();
}
+
+ if (err == OK) {
+ mPlaying = true;
+ }
+
+ return err;
}
void AudioPlayer::reset() {
CHECK(mStarted);
+ ALOGV("reset: mPlaying=%d mReachedEOS=%d useOffload=%d",
+ mPlaying, mReachedEOS, useOffload() );
+
if (mAudioSink.get() != NULL) {
mAudioSink->stop();
+ // If we're closing and have reached EOS, we don't want to flush
+ // the track because if it is offloaded there could be a small
+ // amount of residual data in the hardware buffer which we must
+ // play to give gapless playback.
+ // But if we're resetting when paused or before we've reached EOS
+ // we can't be doing a gapless playback and there could be a large
+ // amount of data queued in the hardware if the track is offloaded,
+ // so we must flush to prevent a track switch being delayed playing
+ // the buffered data that we don't want now
+ if (!mPlaying || !mReachedEOS) {
+ mAudioSink->flush();
+ }
+
mAudioSink->close();
} else {
mAudioTrack->stop();
- delete mAudioTrack;
- mAudioTrack = NULL;
+ if (!mPlaying || !mReachedEOS) {
+ mAudioTrack->flush();
+ }
+
+ mAudioTrack.clear();
}
// Make sure to release any buffer we hold onto so that the
@@ -259,10 +345,16 @@ void AudioPlayer::reset() {
// The following hack is necessary to ensure that the OMX
// component is completely released by the time we may try
// to instantiate it again.
- wp<MediaSource> tmp = mSource;
- mSource.clear();
- while (tmp.promote() != NULL) {
- usleep(1000);
+ // When offloading, the OMX component is not used so this hack
+ // is not needed
+ if (!useOffload()) {
+ wp<MediaSource> tmp = mSource;
+ mSource.clear();
+ while (tmp.promote() != NULL) {
+ usleep(1000);
+ }
+ } else {
+ mSource.clear();
}
IPCThreadState::self()->flushCommands();
@@ -271,9 +363,12 @@ void AudioPlayer::reset() {
mPositionTimeMediaUs = -1;
mPositionTimeRealUs = -1;
mSeeking = false;
+ mSeekTimeUs = 0;
mReachedEOS = false;
mFinalStatus = OK;
mStarted = false;
+ mPlaying = false;
+ mStartPosUs = 0;
}
// static
@@ -294,10 +389,19 @@ bool AudioPlayer::reachedEOS(status_t *finalStatus) {
return mReachedEOS;
}
+void AudioPlayer::notifyAudioEOS() {
+ ALOGV("AudioPlayer@0x%p notifyAudioEOS", this);
+
+ if (mObserver != NULL) {
+ mObserver->postAudioEOS(0);
+ ALOGV("Notified observer of EOS!");
+ }
+}
+
status_t AudioPlayer::setPlaybackRatePermille(int32_t ratePermille) {
if (mAudioSink.get() != NULL) {
return mAudioSink->setPlaybackRatePermille(ratePermille);
- } else if (mAudioTrack != NULL){
+ } else if (mAudioTrack != 0){
return mAudioTrack->setSampleRate(ratePermille * mSampleRate / 1000);
} else {
return NO_INIT;
@@ -307,21 +411,44 @@ status_t AudioPlayer::setPlaybackRatePermille(int32_t ratePermille) {
// static
size_t AudioPlayer::AudioSinkCallback(
MediaPlayerBase::AudioSink *audioSink,
- void *buffer, size_t size, void *cookie) {
+ void *buffer, size_t size, void *cookie,
+ MediaPlayerBase::AudioSink::cb_event_t event) {
AudioPlayer *me = (AudioPlayer *)cookie;
- return me->fillBuffer(buffer, size);
-}
+ switch(event) {
+ case MediaPlayerBase::AudioSink::CB_EVENT_FILL_BUFFER:
+ return me->fillBuffer(buffer, size);
-void AudioPlayer::AudioCallback(int event, void *info) {
- if (event != AudioTrack::EVENT_MORE_DATA) {
- return;
+ case MediaPlayerBase::AudioSink::CB_EVENT_STREAM_END:
+ ALOGV("AudioSinkCallback: stream end");
+ me->mReachedEOS = true;
+ me->notifyAudioEOS();
+ break;
+
+ case MediaPlayerBase::AudioSink::CB_EVENT_TEAR_DOWN:
+ ALOGV("AudioSinkCallback: Tear down event");
+ me->mObserver->postAudioTearDown();
+ break;
}
- AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info;
- size_t numBytesWritten = fillBuffer(buffer->raw, buffer->size);
+ return 0;
+}
+
+void AudioPlayer::AudioCallback(int event, void *info) {
+ switch (event) {
+ case AudioTrack::EVENT_MORE_DATA:
+ {
+ AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info;
+ size_t numBytesWritten = fillBuffer(buffer->raw, buffer->size);
+ buffer->size = numBytesWritten;
+ }
+ break;
- buffer->size = numBytesWritten;
+ case AudioTrack::EVENT_STREAM_END:
+ mReachedEOS = true;
+ notifyAudioEOS();
+ break;
+ }
}
uint32_t AudioPlayer::getNumFramesPendingPlayout() const {
@@ -361,6 +488,7 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) {
size_t size_remaining = size;
while (size_remaining > 0) {
MediaSource::ReadOptions options;
+ bool refreshSeekTime = false;
{
Mutex::Autolock autoLock(mLock);
@@ -375,6 +503,7 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) {
}
options.setSeekTo(mSeekTimeUs);
+ refreshSeekTime = true;
if (mInputBuffer != NULL) {
mInputBuffer->release();
@@ -407,43 +536,56 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) {
Mutex::Autolock autoLock(mLock);
if (err != OK) {
- if (mObserver && !mReachedEOS) {
- // We don't want to post EOS right away but only
- // after all frames have actually been played out.
-
- // These are the number of frames submitted to the
- // AudioTrack that you haven't heard yet.
- uint32_t numFramesPendingPlayout =
- getNumFramesPendingPlayout();
-
- // These are the number of frames we're going to
- // submit to the AudioTrack by returning from this
- // callback.
- uint32_t numAdditionalFrames = size_done / mFrameSize;
-
- numFramesPendingPlayout += numAdditionalFrames;
-
- int64_t timeToCompletionUs =
- (1000000ll * numFramesPendingPlayout) / mSampleRate;
-
- ALOGV("total number of frames played: %lld (%lld us)",
- (mNumFramesPlayed + numAdditionalFrames),
- 1000000ll * (mNumFramesPlayed + numAdditionalFrames)
- / mSampleRate);
-
- ALOGV("%d frames left to play, %lld us (%.2f secs)",
- numFramesPendingPlayout,
- timeToCompletionUs, timeToCompletionUs / 1E6);
-
- postEOS = true;
- if (mAudioSink->needsTrailingPadding()) {
- postEOSDelayUs = timeToCompletionUs + mLatencyUs;
+ if (!mReachedEOS) {
+ if (useOffload()) {
+ // no more buffers to push - stop() and wait for STREAM_END
+ // don't set mReachedEOS until stream end received
+ if (mAudioSink != NULL) {
+ mAudioSink->stop();
+ } else {
+ mAudioTrack->stop();
+ }
} else {
- postEOSDelayUs = 0;
+ if (mObserver) {
+ // We don't want to post EOS right away but only
+ // after all frames have actually been played out.
+
+ // These are the number of frames submitted to the
+ // AudioTrack that you haven't heard yet.
+ uint32_t numFramesPendingPlayout =
+ getNumFramesPendingPlayout();
+
+ // These are the number of frames we're going to
+ // submit to the AudioTrack by returning from this
+ // callback.
+ uint32_t numAdditionalFrames = size_done / mFrameSize;
+
+ numFramesPendingPlayout += numAdditionalFrames;
+
+ int64_t timeToCompletionUs =
+ (1000000ll * numFramesPendingPlayout) / mSampleRate;
+
+ ALOGV("total number of frames played: %lld (%lld us)",
+ (mNumFramesPlayed + numAdditionalFrames),
+ 1000000ll * (mNumFramesPlayed + numAdditionalFrames)
+ / mSampleRate);
+
+ ALOGV("%d frames left to play, %lld us (%.2f secs)",
+ numFramesPendingPlayout,
+ timeToCompletionUs, timeToCompletionUs / 1E6);
+
+ postEOS = true;
+ if (mAudioSink->needsTrailingPadding()) {
+ postEOSDelayUs = timeToCompletionUs + mLatencyUs;
+ } else {
+ postEOSDelayUs = 0;
+ }
+ }
+
+ mReachedEOS = true;
}
}
- mReachedEOS = true;
mFinalStatus = err;
break;
}
@@ -454,17 +596,43 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) {
mLatencyUs = (int64_t)mAudioTrack->latency() * 1000;
}
- CHECK(mInputBuffer->meta_data()->findInt64(
+ if(mInputBuffer->range_length() != 0) {
+ CHECK(mInputBuffer->meta_data()->findInt64(
kKeyTime, &mPositionTimeMediaUs));
+ }
+
+ // need to adjust the mStartPosUs for offload decoding since parser
+ // might not be able to get the exact seek time requested.
+ if (refreshSeekTime) {
+ if (useOffload()) {
+ if (postSeekComplete) {
+ ALOGV("fillBuffer is going to post SEEK_COMPLETE");
+ mObserver->postAudioSeekComplete();
+ postSeekComplete = false;
+ }
- mPositionTimeRealUs =
- ((mNumFramesPlayed + size_done / mFrameSize) * 1000000)
- / mSampleRate;
+ mStartPosUs = mPositionTimeMediaUs;
+ ALOGV("adjust seek time to: %.2f", mStartPosUs/ 1E6);
+ }
+ // clear seek time with mLock locked and once we have valid mPositionTimeMediaUs
+ // and mPositionTimeRealUs
+ // before clearing mSeekTimeUs check if a new seek request has been received while
+ // we were reading from the source with mLock released.
+ if (!mSeeking) {
+ mSeekTimeUs = 0;
+ }
+ }
+
+ if (!useOffload()) {
+ mPositionTimeRealUs =
+ ((mNumFramesPlayed + size_done / mFrameSize) * 1000000)
+ / mSampleRate;
+ ALOGV("buffer->size() = %d, "
+ "mPositionTimeMediaUs=%.2f mPositionTimeRealUs=%.2f",
+ mInputBuffer->range_length(),
+ mPositionTimeMediaUs / 1E6, mPositionTimeRealUs / 1E6);
+ }
- ALOGV("buffer->size() = %d, "
- "mPositionTimeMediaUs=%.2f mPositionTimeRealUs=%.2f",
- mInputBuffer->range_length(),
- mPositionTimeMediaUs / 1E6, mPositionTimeRealUs / 1E6);
}
if (mInputBuffer->range_length() == 0) {
@@ -490,6 +658,13 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) {
size_remaining -= copy;
}
+ if (useOffload()) {
+ // We must ask the hardware what it has played
+ mPositionTimeRealUs = getOutputPlayPositionUs_l();
+ ALOGV("mPositionTimeMediaUs=%.2f mPositionTimeRealUs=%.2f",
+ mPositionTimeMediaUs / 1E6, mPositionTimeRealUs / 1E6);
+ }
+
{
Mutex::Autolock autoLock(mLock);
mNumFramesPlayed += size_done / mFrameSize;
@@ -515,6 +690,14 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) {
int64_t AudioPlayer::getRealTimeUs() {
Mutex::Autolock autoLock(mLock);
+ if (useOffload()) {
+ if (mSeeking) {
+ return mSeekTimeUs;
+ }
+ mPositionTimeRealUs = getOutputPlayPositionUs_l();
+ return mPositionTimeRealUs;
+ }
+
return getRealTimeUsLocked();
}
@@ -538,15 +721,40 @@ int64_t AudioPlayer::getRealTimeUsLocked() const {
return result + diffUs;
}
+int64_t AudioPlayer::getOutputPlayPositionUs_l() const
+{
+ uint32_t playedSamples = 0;
+ if (mAudioSink != NULL) {
+ mAudioSink->getPosition(&playedSamples);
+ } else {
+ mAudioTrack->getPosition(&playedSamples);
+ }
+
+ const int64_t playedUs = (static_cast<int64_t>(playedSamples) * 1000000 ) / mSampleRate;
+
+ // HAL position is relative to the first buffer we sent at mStartPosUs
+ const int64_t renderedDuration = mStartPosUs + playedUs;
+ ALOGV("getOutputPlayPositionUs_l %lld", renderedDuration);
+ return renderedDuration;
+}
+
int64_t AudioPlayer::getMediaTimeUs() {
Mutex::Autolock autoLock(mLock);
- if (mPositionTimeMediaUs < 0 || mPositionTimeRealUs < 0) {
+ if (useOffload()) {
if (mSeeking) {
return mSeekTimeUs;
}
+ mPositionTimeRealUs = getOutputPlayPositionUs_l();
+ ALOGV("getMediaTimeUs getOutputPlayPositionUs_l() mPositionTimeRealUs %lld",
+ mPositionTimeRealUs);
+ return mPositionTimeRealUs;
+ }
- return 0;
+
+ if (mPositionTimeMediaUs < 0 || mPositionTimeRealUs < 0) {
+ // mSeekTimeUs is either seek time while seeking or 0 if playback did not start.
+ return mSeekTimeUs;
}
int64_t realTimeOffset = getRealTimeUsLocked() - mPositionTimeRealUs;
@@ -561,8 +769,14 @@ bool AudioPlayer::getMediaTimeMapping(
int64_t *realtime_us, int64_t *mediatime_us) {
Mutex::Autolock autoLock(mLock);
- *realtime_us = mPositionTimeRealUs;
- *mediatime_us = mPositionTimeMediaUs;
+ if (useOffload()) {
+ mPositionTimeRealUs = getOutputPlayPositionUs_l();
+ *realtime_us = mPositionTimeRealUs;
+ *mediatime_us = mPositionTimeRealUs;
+ } else {
+ *realtime_us = mPositionTimeRealUs;
+ *mediatime_us = mPositionTimeMediaUs;
+ }
return mPositionTimeRealUs != -1 && mPositionTimeMediaUs != -1;
}
@@ -570,19 +784,34 @@ bool AudioPlayer::getMediaTimeMapping(
status_t AudioPlayer::seekTo(int64_t time_us) {
Mutex::Autolock autoLock(mLock);
+ ALOGV("seekTo( %lld )", time_us);
+
mSeeking = true;
mPositionTimeRealUs = mPositionTimeMediaUs = -1;
mReachedEOS = false;
mSeekTimeUs = time_us;
+ mStartPosUs = time_us;
// Flush resets the number of played frames
mNumFramesPlayed = 0;
mNumFramesPlayedSysTimeUs = ALooper::GetNowUs();
if (mAudioSink != NULL) {
+ if (mPlaying) {
+ mAudioSink->pause();
+ }
mAudioSink->flush();
+ if (mPlaying) {
+ mAudioSink->start();
+ }
} else {
+ if (mPlaying) {
+ mAudioTrack->pause();
+ }
mAudioTrack->flush();
+ if (mPlaying) {
+ mAudioTrack->start();
+ }
}
return OK;
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
index 3cf4d5c..bdd842f 100644
--- a/media/libstagefright/AudioSource.cpp
+++ b/media/libstagefright/AudioSource.cpp
@@ -49,8 +49,7 @@ static void AudioRecordCallbackFunction(int event, void *user, void *info) {
AudioSource::AudioSource(
audio_source_t inputSource, uint32_t sampleRate, uint32_t channelCount)
- : mRecord(NULL),
- mStarted(false),
+ : mStarted(false),
mSampleRate(sampleRate),
mPrevSampleTimeUs(0),
mNumFramesReceived(0),
@@ -91,9 +90,6 @@ AudioSource::~AudioSource() {
if (mStarted) {
reset();
}
-
- delete mRecord;
- mRecord = NULL;
}
status_t AudioSource::initCheck() const {
@@ -122,8 +118,7 @@ status_t AudioSource::start(MetaData *params) {
if (err == OK) {
mStarted = true;
} else {
- delete mRecord;
- mRecord = NULL;
+ mRecord.clear();
}
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index d53f442..c912f75 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -47,6 +47,7 @@
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/Utils.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/Surface.h>
@@ -65,6 +66,11 @@ static int64_t kHighWaterMarkUs = 5000000ll; // 5secs
static const size_t kLowWaterMarkBytes = 40000;
static const size_t kHighWaterMarkBytes = 200000;
+// maximum time in paused state when offloading audio decompression. When elapsed, the AudioPlayer
+// is destroyed to allow the audio DSP to power down.
+static int64_t kOffloadPauseMaxUs = 60000000ll;
+
+
struct AwesomeEvent : public TimedEventQueue::Event {
AwesomeEvent(
AwesomePlayer *player,
@@ -185,6 +191,8 @@ AwesomePlayer::AwesomePlayer()
mTimeSource(NULL),
mVideoRenderingStarted(false),
mVideoRendererIsPreview(false),
+ mMediaRenderingStartGeneration(0),
+ mStartGeneration(0),
mAudioPlayer(NULL),
mDisplayWidth(0),
mDisplayHeight(0),
@@ -194,7 +202,9 @@ AwesomePlayer::AwesomePlayer()
mVideoBuffer(NULL),
mDecryptHandle(NULL),
mLastVideoTimeUs(-1),
- mTextDriver(NULL) {
+ mTextDriver(NULL),
+ mOffloadAudio(false),
+ mAudioTearDown(false) {
CHECK_EQ(mClient.connect(), (status_t)OK);
DataSource::RegisterDefaultSniffers();
@@ -206,13 +216,17 @@ AwesomePlayer::AwesomePlayer()
mBufferingEvent = new AwesomeEvent(this, &AwesomePlayer::onBufferingUpdate);
mBufferingEventPending = false;
mVideoLagEvent = new AwesomeEvent(this, &AwesomePlayer::onVideoLagUpdate);
- mVideoEventPending = false;
+ mVideoLagEventPending = false;
mCheckAudioStatusEvent = new AwesomeEvent(
this, &AwesomePlayer::onCheckAudioStatus);
mAudioStatusEventPending = false;
+ mAudioTearDownEvent = new AwesomeEvent(this,
+ &AwesomePlayer::onAudioTearDownEvent);
+ mAudioTearDownEventPending = false;
+
reset();
}
@@ -232,6 +246,11 @@ void AwesomePlayer::cancelPlayerEvents(bool keepNotifications) {
mQueue.cancelEvent(mVideoLagEvent->eventID());
mVideoLagEventPending = false;
+ if (mOffloadAudio) {
+ mQueue.cancelEvent(mAudioTearDownEvent->eventID());
+ mAudioTearDownEventPending = false;
+ }
+
if (!keepNotifications) {
mQueue.cancelEvent(mStreamDoneEvent->eventID());
mStreamDoneEventPending = false;
@@ -240,6 +259,7 @@ void AwesomePlayer::cancelPlayerEvents(bool keepNotifications) {
mQueue.cancelEvent(mBufferingEvent->eventID());
mBufferingEventPending = false;
+ mAudioTearDown = false;
}
}
@@ -474,6 +494,8 @@ void AwesomePlayer::reset_l() {
mDisplayWidth = 0;
mDisplayHeight = 0;
+ notifyListener_l(MEDIA_STOPPED);
+
if (mDecryptHandle != NULL) {
mDrmManagerClient->setPlaybackStatus(mDecryptHandle,
Playback::STOP, 0);
@@ -518,7 +540,7 @@ void AwesomePlayer::reset_l() {
mVideoTrack.clear();
mExtractor.clear();
- // Shutdown audio first, so that the respone to the reset request
+ // Shutdown audio first, so that the response to the reset request
// appears to happen instantaneously as far as the user is concerned
// If we did this later, audio would continue playing while we
// shutdown the video-related resources and the player appear to
@@ -531,6 +553,7 @@ void AwesomePlayer::reset_l() {
mAudioSource->stop();
}
mAudioSource.clear();
+ mOmxSource.clear();
mTimeSource = NULL;
@@ -586,7 +609,7 @@ void AwesomePlayer::reset_l() {
}
void AwesomePlayer::notifyListener_l(int msg, int ext1, int ext2) {
- if (mListener != NULL) {
+ if ((mListener != NULL) && !mAudioTearDown) {
sp<MediaPlayerBase> listener = mListener.promote();
if (listener != NULL) {
@@ -597,7 +620,7 @@ void AwesomePlayer::notifyListener_l(int msg, int ext1, int ext2) {
bool AwesomePlayer::getBitrate(int64_t *bitrate) {
off64_t size;
- if (mDurationUs >= 0 && mCachedSource != NULL
+ if (mDurationUs > 0 && mCachedSource != NULL
&& mCachedSource->getSize(&size) == OK) {
*bitrate = size * 8000000ll / mDurationUs; // in bits/sec
return true;
@@ -775,7 +798,9 @@ void AwesomePlayer::onBufferingUpdate() {
}
}
- postBufferingEvent_l();
+ if (mFlags & (PLAYING | PREPARING)) {
+ postBufferingEvent_l();
+ }
}
void AwesomePlayer::sendCacheStats() {
@@ -842,6 +867,13 @@ void AwesomePlayer::onStreamDone() {
pause_l(true /* at eos */);
+ // If audio hasn't completed MEDIA_SEEK_COMPLETE yet,
+ // notify MEDIA_SEEK_COMPLETE to observer immediately for state persistence.
+ if (mWatchForAudioSeekComplete) {
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
+ mWatchForAudioSeekComplete = false;
+ }
+
modifyFlags(AT_EOS, SET);
}
}
@@ -883,41 +915,49 @@ status_t AwesomePlayer::play_l() {
if (mAudioSource != NULL) {
if (mAudioPlayer == NULL) {
- if (mAudioSink != NULL) {
- bool allowDeepBuffering;
- int64_t cachedDurationUs;
- bool eos;
- if (mVideoSource == NULL
- && (mDurationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US ||
- (getCachedDuration_l(&cachedDurationUs, &eos) &&
- cachedDurationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US))) {
- allowDeepBuffering = true;
- } else {
- allowDeepBuffering = false;
- }
-
- mAudioPlayer = new AudioPlayer(mAudioSink, allowDeepBuffering, this);
- mAudioPlayer->setSource(mAudioSource);
-
- mTimeSource = mAudioPlayer;
-
- // If there was a seek request before we ever started,
- // honor the request now.
- // Make sure to do this before starting the audio player
- // to avoid a race condition.
- seekAudioIfNecessary_l();
- }
+ createAudioPlayer_l();
}
CHECK(!(mFlags & AUDIO_RUNNING));
if (mVideoSource == NULL) {
+
// We don't want to post an error notification at this point,
// the error returned from MediaPlayer::start() will suffice.
status_t err = startAudioPlayer_l(
false /* sendErrorNotification */);
+ if ((err != OK) && mOffloadAudio) {
+ ALOGI("play_l() cannot create offload output, fallback to sw decode");
+ int64_t curTimeUs;
+ getPosition(&curTimeUs);
+
+ delete mAudioPlayer;
+ mAudioPlayer = NULL;
+ // if the player was started it will take care of stopping the source when destroyed
+ if (!(mFlags & AUDIOPLAYER_STARTED)) {
+ mAudioSource->stop();
+ }
+ modifyFlags((AUDIO_RUNNING | AUDIOPLAYER_STARTED), CLEAR);
+ mOffloadAudio = false;
+ mAudioSource = mOmxSource;
+ if (mAudioSource != NULL) {
+ err = mAudioSource->start();
+
+ if (err != OK) {
+ mAudioSource.clear();
+ } else {
+ mSeekNotificationSent = true;
+ if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
+ seekTo_l(curTimeUs);
+ }
+ createAudioPlayer_l();
+ err = startAudioPlayer_l(false);
+ }
+ }
+ }
+
if (err != OK) {
delete mAudioPlayer;
mAudioPlayer = NULL;
@@ -963,22 +1003,72 @@ status_t AwesomePlayer::play_l() {
}
addBatteryData(params);
+ if (isStreamingHTTP()) {
+ postBufferingEvent_l();
+ }
+
return OK;
}
+void AwesomePlayer::createAudioPlayer_l()
+{
+ uint32_t flags = 0;
+ int64_t cachedDurationUs;
+ bool eos;
+
+ if (mOffloadAudio) {
+ flags |= AudioPlayer::USE_OFFLOAD;
+ } else if (mVideoSource == NULL
+ && (mDurationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US ||
+ (getCachedDuration_l(&cachedDurationUs, &eos) &&
+ cachedDurationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US))) {
+ flags |= AudioPlayer::ALLOW_DEEP_BUFFERING;
+ }
+ if (isStreamingHTTP()) {
+ flags |= AudioPlayer::IS_STREAMING;
+ }
+ if (mVideoSource != NULL) {
+ flags |= AudioPlayer::HAS_VIDEO;
+ }
+
+ mAudioPlayer = new AudioPlayer(mAudioSink, flags, this);
+ mAudioPlayer->setSource(mAudioSource);
+
+ mTimeSource = mAudioPlayer;
+
+ // If there was a seek request before we ever started,
+ // honor the request now.
+ // Make sure to do this before starting the audio player
+ // to avoid a race condition.
+ seekAudioIfNecessary_l();
+}
+
+void AwesomePlayer::notifyIfMediaStarted_l() {
+ if (mMediaRenderingStartGeneration == mStartGeneration) {
+ mMediaRenderingStartGeneration = -1;
+ notifyListener_l(MEDIA_STARTED);
+ }
+}
+
status_t AwesomePlayer::startAudioPlayer_l(bool sendErrorNotification) {
CHECK(!(mFlags & AUDIO_RUNNING));
+ status_t err = OK;
if (mAudioSource == NULL || mAudioPlayer == NULL) {
return OK;
}
+ if (mOffloadAudio) {
+ mQueue.cancelEvent(mAudioTearDownEvent->eventID());
+ mAudioTearDownEventPending = false;
+ }
+
if (!(mFlags & AUDIOPLAYER_STARTED)) {
bool wasSeeking = mAudioPlayer->isSeeking();
// We've already started the MediaSource in order to enable
// the prefetcher to read its data.
- status_t err = mAudioPlayer->start(
+ err = mAudioPlayer->start(
true /* sourceAlreadyStarted */);
if (err != OK) {
@@ -996,16 +1086,20 @@ status_t AwesomePlayer::startAudioPlayer_l(bool sendErrorNotification) {
// We will have finished the seek while starting the audio player.
postAudioSeekComplete();
+ } else {
+ notifyIfMediaStarted_l();
}
} else {
- mAudioPlayer->resume();
+ err = mAudioPlayer->resume();
}
- modifyFlags(AUDIO_RUNNING, SET);
+ if (err == OK) {
+ modifyFlags(AUDIO_RUNNING, SET);
- mWatchForAudioEOS = true;
+ mWatchForAudioEOS = true;
+ }
- return OK;
+ return err;
}
void AwesomePlayer::notifyVideoSize_l() {
@@ -1131,21 +1225,29 @@ status_t AwesomePlayer::pause() {
status_t AwesomePlayer::pause_l(bool at_eos) {
if (!(mFlags & PLAYING)) {
+ if (mAudioTearDown && mAudioTearDownWasPlaying) {
+ ALOGV("pause_l() during teardown and finishSetDataSource_l() mFlags %x" , mFlags);
+ mAudioTearDownWasPlaying = false;
+ notifyListener_l(MEDIA_PAUSED);
+ mMediaRenderingStartGeneration = ++mStartGeneration;
+ }
return OK;
}
+ notifyListener_l(MEDIA_PAUSED);
+ mMediaRenderingStartGeneration = ++mStartGeneration;
+
cancelPlayerEvents(true /* keepNotifications */);
if (mAudioPlayer != NULL && (mFlags & AUDIO_RUNNING)) {
- if (at_eos) {
- // If we played the audio stream to completion we
- // want to make sure that all samples remaining in the audio
- // track's queue are played out.
- mAudioPlayer->pause(true /* playPendingSamples */);
- } else {
- mAudioPlayer->pause();
+ // If we played the audio stream to completion we
+ // want to make sure that all samples remaining in the audio
+ // track's queue are played out.
+ mAudioPlayer->pause(at_eos /* playPendingSamples */);
+ // send us a reminder to tear down the AudioPlayer if paused for too long.
+ if (mOffloadAudio) {
+ postAudioTearDownEvent(kOffloadPauseMaxUs);
}
-
modifyFlags(AUDIO_RUNNING, CLEAR);
}
@@ -1290,7 +1392,6 @@ status_t AwesomePlayer::getPosition(int64_t *positionUs) {
} else {
*positionUs = 0;
}
-
return OK;
}
@@ -1324,6 +1425,11 @@ status_t AwesomePlayer::seekTo_l(int64_t timeUs) {
mSeekTimeUs = timeUs;
modifyFlags((AT_EOS | AUDIO_AT_EOS | VIDEO_AT_EOS), CLEAR);
+ if (mFlags & PLAYING) {
+ notifyListener_l(MEDIA_PAUSED);
+ mMediaRenderingStartGeneration = ++mStartGeneration;
+ }
+
seekAudioIfNecessary_l();
if (mFlags & TEXTPLAYER_INITIALIZED) {
@@ -1385,14 +1491,29 @@ status_t AwesomePlayer::initAudioDecoder() {
const char *mime;
CHECK(meta->findCString(kKeyMIMEType, &mime));
+ // Check whether there is a hardware codec for this stream
+ // This doesn't guarantee that the hardware has a free stream
+ // but it avoids us attempting to open (and re-open) an offload
+ // stream to hardware that doesn't have the necessary codec
+ mOffloadAudio = canOffloadStream(meta, (mVideoSource != NULL), isStreamingHTTP());
if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW)) {
+ ALOGV("createAudioPlayer: bypass OMX (raw)");
mAudioSource = mAudioTrack;
} else {
- mAudioSource = OMXCodec::Create(
+ // If offloading we still create a OMX decoder as a fall-back
+ // but we don't start it
+ mOmxSource = OMXCodec::Create(
mClient.interface(), mAudioTrack->getFormat(),
false, // createEncoder
mAudioTrack);
+
+ if (mOffloadAudio) {
+ ALOGV("createAudioPlayer: bypass OMX (offload)");
+ mAudioSource = mAudioTrack;
+ } else {
+ mAudioSource = mOmxSource;
+ }
}
if (mAudioSource != NULL) {
@@ -1408,6 +1529,7 @@ status_t AwesomePlayer::initAudioDecoder() {
if (err != OK) {
mAudioSource.clear();
+ mOmxSource.clear();
return err;
}
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_QCELP)) {
@@ -1551,6 +1673,16 @@ void AwesomePlayer::finishSeekIfNecessary(int64_t videoTimeUs) {
return;
}
+ // If we paused, then seeked, then resumed, it is possible that we have
+ // signaled SEEK_COMPLETE at a copmletely different media time than where
+ // we are now resuming. Signal new position to media time provider.
+ // Cannot signal another SEEK_COMPLETE, as existing clients may not expect
+ // multiple SEEK_COMPLETE responses to a single seek() request.
+ if (mSeekNotificationSent && abs(mSeekTimeUs - videoTimeUs) > 10000) {
+ // notify if we are resuming more than 10ms away from desired seek time
+ notifyListener_l(MEDIA_SKIPPED);
+ }
+
if (mAudioPlayer != NULL) {
ALOGV("seeking audio to %lld us (%.2f secs).", videoTimeUs, videoTimeUs / 1E6);
@@ -1822,6 +1954,9 @@ void AwesomePlayer::onVideoEvent() {
notifyListener_l(MEDIA_INFO, MEDIA_INFO_RENDERING_START);
}
+ if (mFlags & PLAYING) {
+ notifyIfMediaStarted_l();
+ }
}
mVideoBuffer->release();
@@ -1885,6 +2020,15 @@ void AwesomePlayer::postCheckAudioStatusEvent(int64_t delayUs) {
mQueue.postEventWithDelay(mCheckAudioStatusEvent, delayUs);
}
+void AwesomePlayer::postAudioTearDownEvent(int64_t delayUs) {
+ Mutex::Autolock autoLock(mAudioLock);
+ if (mAudioTearDownEventPending) {
+ return;
+ }
+ mAudioTearDownEventPending = true;
+ mQueue.postEventWithDelay(mAudioTearDownEvent, delayUs);
+}
+
void AwesomePlayer::onCheckAudioStatus() {
{
Mutex::Autolock autoLock(mAudioLock);
@@ -1908,6 +2052,8 @@ void AwesomePlayer::onCheckAudioStatus() {
}
mSeeking = NO_SEEK;
+
+ notifyIfMediaStarted_l();
}
status_t finalStatus;
@@ -2189,6 +2335,7 @@ void AwesomePlayer::abortPrepare(status_t err) {
modifyFlags((PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED), CLEAR);
mAsyncPrepareEvent = NULL;
mPreparedCondition.broadcast();
+ mAudioTearDown = false;
}
// static
@@ -2200,7 +2347,10 @@ bool AwesomePlayer::ContinuePreparation(void *cookie) {
void AwesomePlayer::onPrepareAsyncEvent() {
Mutex::Autolock autoLock(mLock);
+ beginPrepareAsync_l();
+}
+void AwesomePlayer::beginPrepareAsync_l() {
if (mFlags & PREPARE_CANCELLED) {
ALOGI("prepare was cancelled before doing anything");
abortPrepare(UNKNOWN_ERROR);
@@ -2259,6 +2409,20 @@ void AwesomePlayer::finishAsyncPrepare_l() {
modifyFlags(PREPARED, SET);
mAsyncPrepareEvent = NULL;
mPreparedCondition.broadcast();
+
+ if (mAudioTearDown) {
+ if (mPrepareResult == OK) {
+ if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
+ seekTo_l(mAudioTearDownPosition);
+ }
+
+ if (mAudioTearDownWasPlaying) {
+ modifyFlags(CACHE_UNDERRUN, CLEAR);
+ play_l();
+ }
+ }
+ mAudioTearDown = false;
+ }
}
uint32_t AwesomePlayer::flags() const {
@@ -2273,6 +2437,10 @@ void AwesomePlayer::postAudioSeekComplete() {
postCheckAudioStatusEvent(0);
}
+void AwesomePlayer::postAudioTearDown() {
+ postAudioTearDownEvent(0);
+}
+
status_t AwesomePlayer::setParameter(int key, const Parcel &request) {
switch (key) {
case KEY_PARAMETER_CACHE_STAT_COLLECT_FREQ_MS:
@@ -2404,6 +2572,7 @@ status_t AwesomePlayer::selectAudioTrack_l(
mAudioSource->stop();
}
mAudioSource.clear();
+ mOmxSource.clear();
mTimeSource = NULL;
@@ -2660,4 +2829,52 @@ void AwesomePlayer::modifyFlags(unsigned value, FlagMode mode) {
}
}
+void AwesomePlayer::onAudioTearDownEvent() {
+
+ Mutex::Autolock autoLock(mLock);
+ if (!mAudioTearDownEventPending) {
+ return;
+ }
+ mAudioTearDownEventPending = false;
+
+ ALOGV("onAudioTearDownEvent");
+
+ // stream info is cleared by reset_l() so copy what we need
+ mAudioTearDownWasPlaying = (mFlags & PLAYING);
+ KeyedVector<String8, String8> uriHeaders(mUriHeaders);
+ sp<DataSource> fileSource(mFileSource);
+
+ mStatsLock.lock();
+ String8 uri(mStats.mURI);
+ mStatsLock.unlock();
+
+ // get current position so we can start recreated stream from here
+ getPosition(&mAudioTearDownPosition);
+
+ // Reset and recreate
+ reset_l();
+
+ status_t err;
+
+ if (fileSource != NULL) {
+ mFileSource = fileSource;
+ err = setDataSource_l(fileSource);
+ } else {
+ err = setDataSource_l(uri, &uriHeaders);
+ }
+
+ mFlags |= PREPARING;
+ if ( err != OK ) {
+ // This will force beingPrepareAsync_l() to notify
+ // a MEDIA_ERROR to the client and abort the prepare
+ mFlags |= PREPARE_CANCELLED;
+ }
+
+ mAudioTearDown = true;
+ mIsAsyncPrepare = true;
+
+ // Call prepare for the host decoding
+ beginPrepareAsync_l();
+}
+
} // namespace android
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 5a26b06..3017fe7 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -536,7 +536,7 @@ status_t CameraSource::initWithCameraAccess(
if (mSurface != NULL) {
// This CHECK is good, since we just passed the lock/unlock
// check earlier by calling mCamera->setParameters().
- CHECK_EQ((status_t)OK, mCamera->setPreviewTexture(mSurface));
+ CHECK_EQ((status_t)OK, mCamera->setPreviewTarget(mSurface));
}
// By default, do not store metadata in video buffers
diff --git a/media/libstagefright/HTTPBase.cpp b/media/libstagefright/HTTPBase.cpp
index d2cc6c2..5fa4b6f 100644
--- a/media/libstagefright/HTTPBase.cpp
+++ b/media/libstagefright/HTTPBase.cpp
@@ -30,6 +30,8 @@
#include <cutils/properties.h>
#include <cutils/qtaguid.h>
+#include <ConnectivityManager.h>
+
namespace android {
HTTPBase::HTTPBase()
@@ -164,4 +166,14 @@ void HTTPBase::UnRegisterSocketUserTag(int sockfd) {
}
}
+// static
+void HTTPBase::RegisterSocketUserMark(int sockfd, uid_t uid) {
+ ConnectivityManager::markSocketAsUser(sockfd, uid);
+}
+
+// static
+void HTTPBase::UnRegisterSocketUserMark(int sockfd) {
+ RegisterSocketUserMark(sockfd, geteuid());
+}
+
} // namespace android
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 25c080c..cbc169b 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -343,6 +343,7 @@ MPEG4Extractor::MPEG4Extractor(const sp<DataSource> &source)
mDataSource(source),
mInitCheck(NO_INIT),
mHasVideo(false),
+ mHeaderTimescale(0),
mFirstTrack(NULL),
mLastTrack(NULL),
mFileMetaData(new MetaData),
@@ -819,6 +820,7 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
case FOURCC('i', 'l', 's', 't'):
case FOURCC('s', 'i', 'n', 'f'):
case FOURCC('s', 'c', 'h', 'i'):
+ case FOURCC('e', 'd', 't', 's'):
{
if (chunk_type == FOURCC('s', 't', 'b', 'l')) {
ALOGV("sampleTable chunk is %d bytes long.", (size_t)chunk_size);
@@ -906,6 +908,68 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
break;
}
+ case FOURCC('e', 'l', 's', 't'):
+ {
+ // See 14496-12 8.6.6
+ uint8_t version;
+ if (mDataSource->readAt(data_offset, &version, 1) < 1) {
+ return ERROR_IO;
+ }
+
+ uint32_t entry_count;
+ if (!mDataSource->getUInt32(data_offset + 4, &entry_count)) {
+ return ERROR_IO;
+ }
+
+ if (entry_count != 1) {
+ // we only support a single entry at the moment, for gapless playback
+ ALOGW("ignoring edit list with %d entries", entry_count);
+ } else if (mHeaderTimescale == 0) {
+ ALOGW("ignoring edit list because timescale is 0");
+ } else {
+ off64_t entriesoffset = data_offset + 8;
+ uint64_t segment_duration;
+ int64_t media_time;
+
+ if (version == 1) {
+ if (!mDataSource->getUInt64(entriesoffset, &segment_duration) ||
+ !mDataSource->getUInt64(entriesoffset + 8, (uint64_t*)&media_time)) {
+ return ERROR_IO;
+ }
+ } else if (version == 0) {
+ uint32_t sd;
+ int32_t mt;
+ if (!mDataSource->getUInt32(entriesoffset, &sd) ||
+ !mDataSource->getUInt32(entriesoffset + 4, (uint32_t*)&mt)) {
+ return ERROR_IO;
+ }
+ segment_duration = sd;
+ media_time = mt;
+ } else {
+ return ERROR_IO;
+ }
+
+ uint64_t halfscale = mHeaderTimescale / 2;
+ segment_duration = (segment_duration * 1000000 + halfscale)/ mHeaderTimescale;
+ media_time = (media_time * 1000000 + halfscale) / mHeaderTimescale;
+
+ int64_t duration;
+ int32_t samplerate;
+ if (mLastTrack->meta->findInt64(kKeyDuration, &duration) &&
+ mLastTrack->meta->findInt32(kKeySampleRate, &samplerate)) {
+
+ int64_t delay = (media_time * samplerate + 500000) / 1000000;
+ mLastTrack->meta->setInt32(kKeyEncoderDelay, delay);
+
+ int64_t paddingus = duration - (segment_duration + media_time);
+ int64_t paddingsamples = (paddingus * samplerate + 500000) / 1000000;
+ mLastTrack->meta->setInt32(kKeyEncoderPadding, paddingsamples);
+ }
+ }
+ *offset += chunk_size;
+ break;
+ }
+
case FOURCC('f', 'r', 'm', 'a'):
{
uint32_t original_fourcc;
@@ -1566,24 +1630,26 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
case FOURCC('m', 'v', 'h', 'd'):
{
- if (chunk_data_size < 12) {
+ if (chunk_data_size < 24) {
return ERROR_MALFORMED;
}
- uint8_t header[12];
+ uint8_t header[24];
if (mDataSource->readAt(
data_offset, header, sizeof(header))
< (ssize_t)sizeof(header)) {
return ERROR_IO;
}
- int64_t creationTime;
+ uint64_t creationTime;
if (header[0] == 1) {
creationTime = U64_AT(&header[4]);
+ mHeaderTimescale = U32_AT(&header[20]);
} else if (header[0] != 0) {
return ERROR_MALFORMED;
} else {
creationTime = U32_AT(&header[4]);
+ mHeaderTimescale = U32_AT(&header[12]);
}
String8 s;
@@ -1877,13 +1943,13 @@ status_t MPEG4Extractor::parseTrackHeader(
mtime = U64_AT(&buffer[12]);
id = U32_AT(&buffer[20]);
duration = U64_AT(&buffer[28]);
- } else {
- CHECK_EQ((unsigned)version, 0u);
-
+ } else if (version == 0) {
ctime = U32_AT(&buffer[4]);
mtime = U32_AT(&buffer[8]);
id = U32_AT(&buffer[12]);
duration = U32_AT(&buffer[20]);
+ } else {
+ return ERROR_UNSUPPORTED;
}
mLastTrack->meta->setInt32(kKeyTrackID, id);
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 674e6e5..e299caf 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -31,6 +31,7 @@
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/ACodec.h>
#include <media/stagefright/BufferProducerWrapper.h>
+#include <media/stagefright/MediaCodecList.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MetaData.h>
@@ -104,8 +105,24 @@ status_t MediaCodec::init(const char *name, bool nameIsType, bool encoder) {
bool needDedicatedLooper = false;
if (nameIsType && !strncasecmp(name, "video/", 6)) {
needDedicatedLooper = true;
- } else if (!nameIsType && !strncmp(name, "OMX.TI.DUCATI1.VIDEO.", 21)) {
- needDedicatedLooper = true;
+ } else {
+ AString tmp = name;
+ if (tmp.endsWith(".secure")) {
+ tmp.erase(tmp.size() - 7, 7);
+ }
+ const MediaCodecList *mcl = MediaCodecList::getInstance();
+ ssize_t codecIdx = mcl->findCodecByName(tmp.c_str());
+ if (codecIdx >= 0) {
+ Vector<AString> types;
+ if (mcl->getSupportedTypes(codecIdx, &types) == OK) {
+ for (int i = 0; i < types.size(); i++) {
+ if (types[i].startsWith("video/")) {
+ needDedicatedLooper = true;
+ break;
+ }
+ }
+ }
+ }
}
if (needDedicatedLooper) {
diff --git a/media/libstagefright/MediaCodecList.cpp b/media/libstagefright/MediaCodecList.cpp
index d24337f..6248e90 100644
--- a/media/libstagefright/MediaCodecList.cpp
+++ b/media/libstagefright/MediaCodecList.cpp
@@ -509,7 +509,8 @@ status_t MediaCodecList::getSupportedTypes(
status_t MediaCodecList::getCodecCapabilities(
size_t index, const char *type,
Vector<ProfileLevel> *profileLevels,
- Vector<uint32_t> *colorFormats) const {
+ Vector<uint32_t> *colorFormats,
+ uint32_t *flags) const {
profileLevels->clear();
colorFormats->clear();
@@ -547,6 +548,8 @@ status_t MediaCodecList::getCodecCapabilities(
colorFormats->push(caps.mColorFormats.itemAt(i));
}
+ *flags = caps.mFlags;
+
return OK;
}
diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp
index 5d8029c..b5d4e44 100644
--- a/media/libstagefright/MediaDefs.cpp
+++ b/media/libstagefright/MediaDefs.cpp
@@ -20,7 +20,8 @@ namespace android {
const char *MEDIA_MIMETYPE_IMAGE_JPEG = "image/jpeg";
-const char *MEDIA_MIMETYPE_VIDEO_VPX = "video/x-vnd.on2.vp8";
+const char *MEDIA_MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
+const char *MEDIA_MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
const char *MEDIA_MIMETYPE_VIDEO_AVC = "video/avc";
const char *MEDIA_MIMETYPE_VIDEO_MPEG4 = "video/mp4v-es";
const char *MEDIA_MIMETYPE_VIDEO_H263 = "video/3gpp";
diff --git a/media/libstagefright/MediaMuxer.cpp b/media/libstagefright/MediaMuxer.cpp
index 94ce5de..d87e910 100644
--- a/media/libstagefright/MediaMuxer.cpp
+++ b/media/libstagefright/MediaMuxer.cpp
@@ -103,6 +103,16 @@ status_t MediaMuxer::setOrientationHint(int degrees) {
return OK;
}
+status_t MediaMuxer::setLocation(int latitude, int longitude) {
+ Mutex::Autolock autoLock(mMuxerLock);
+ if (mState != INITIALIZED) {
+ ALOGE("setLocation() must be called before start().");
+ return INVALID_OPERATION;
+ }
+ ALOGV("Setting location: latitude = %d, longitude = %d", latitude, longitude);
+ return mWriter->setGeoData(latitude, longitude);
+}
+
status_t MediaMuxer::start() {
Mutex::Autolock autoLock(mMuxerLock);
if (mState == INITIALIZED) {
diff --git a/media/libstagefright/OMXClient.cpp b/media/libstagefright/OMXClient.cpp
index 1822f07..9f9352d 100644
--- a/media/libstagefright/OMXClient.cpp
+++ b/media/libstagefright/OMXClient.cpp
@@ -69,6 +69,10 @@ struct MuxOMX : public IOMX {
virtual status_t storeMetaDataInBuffers(
node_id node, OMX_U32 port_index, OMX_BOOL enable);
+ virtual status_t prepareForAdaptivePlayback(
+ node_id node, OMX_U32 port_index, OMX_BOOL enable,
+ OMX_U32 maxFrameWidth, OMX_U32 maxFrameHeight);
+
virtual status_t enableGraphicBuffers(
node_id node, OMX_U32 port_index, OMX_BOOL enable);
@@ -83,6 +87,10 @@ struct MuxOMX : public IOMX {
node_id node, OMX_U32 port_index,
const sp<GraphicBuffer> &graphicBuffer, buffer_id *buffer);
+ virtual status_t updateGraphicBufferInMeta(
+ node_id node, OMX_U32 port_index,
+ const sp<GraphicBuffer> &graphicBuffer, buffer_id buffer);
+
virtual status_t createInputSurface(
node_id node, OMX_U32 port_index,
sp<IGraphicBufferProducer> *bufferProducer);
@@ -113,6 +121,13 @@ struct MuxOMX : public IOMX {
const char *parameter_name,
OMX_INDEXTYPE *index);
+ virtual status_t setInternalOption(
+ node_id node,
+ OMX_U32 port_index,
+ InternalOptionType type,
+ const void *data,
+ size_t size);
+
private:
mutable Mutex mLock;
@@ -257,6 +272,13 @@ status_t MuxOMX::storeMetaDataInBuffers(
return getOMX(node)->storeMetaDataInBuffers(node, port_index, enable);
}
+status_t MuxOMX::prepareForAdaptivePlayback(
+ node_id node, OMX_U32 port_index, OMX_BOOL enable,
+ OMX_U32 maxFrameWidth, OMX_U32 maxFrameHeight) {
+ return getOMX(node)->prepareForAdaptivePlayback(
+ node, port_index, enable, maxFrameWidth, maxFrameHeight);
+}
+
status_t MuxOMX::enableGraphicBuffers(
node_id node, OMX_U32 port_index, OMX_BOOL enable) {
return getOMX(node)->enableGraphicBuffers(node, port_index, enable);
@@ -280,6 +302,13 @@ status_t MuxOMX::useGraphicBuffer(
node, port_index, graphicBuffer, buffer);
}
+status_t MuxOMX::updateGraphicBufferInMeta(
+ node_id node, OMX_U32 port_index,
+ const sp<GraphicBuffer> &graphicBuffer, buffer_id buffer) {
+ return getOMX(node)->updateGraphicBufferInMeta(
+ node, port_index, graphicBuffer, buffer);
+}
+
status_t MuxOMX::createInputSurface(
node_id node, OMX_U32 port_index,
sp<IGraphicBufferProducer> *bufferProducer) {
@@ -331,6 +360,15 @@ status_t MuxOMX::getExtensionIndex(
return getOMX(node)->getExtensionIndex(node, parameter_name, index);
}
+status_t MuxOMX::setInternalOption(
+ node_id node,
+ OMX_U32 port_index,
+ InternalOptionType type,
+ const void *data,
+ size_t size) {
+ return getOMX(node)->setInternalOption(node, port_index, type, data, size);
+}
+
OMXClient::OMXClient() {
}
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 9d349a1..7f56af8 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -1195,8 +1195,10 @@ status_t OMXCodec::setVideoOutputFormat(
compressionFormat = OMX_VIDEO_CodingMPEG4;
} else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) {
compressionFormat = OMX_VIDEO_CodingH263;
- } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_VPX, mime)) {
- compressionFormat = OMX_VIDEO_CodingVPX;
+ } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_VP8, mime)) {
+ compressionFormat = OMX_VIDEO_CodingVP8;
+ } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_VP9, mime)) {
+ compressionFormat = OMX_VIDEO_CodingVP9;
} else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG2, mime)) {
compressionFormat = OMX_VIDEO_CodingMPEG2;
} else {
@@ -1388,8 +1390,10 @@ void OMXCodec::setComponentRole(
"video_decoder.mpeg4", "video_encoder.mpeg4" },
{ MEDIA_MIMETYPE_VIDEO_H263,
"video_decoder.h263", "video_encoder.h263" },
- { MEDIA_MIMETYPE_VIDEO_VPX,
- "video_decoder.vpx", "video_encoder.vpx" },
+ { MEDIA_MIMETYPE_VIDEO_VP8,
+ "video_decoder.vp8", "video_encoder.vp8" },
+ { MEDIA_MIMETYPE_VIDEO_VP9,
+ "video_decoder.vp9", "video_encoder.vp9" },
{ MEDIA_MIMETYPE_AUDIO_RAW,
"audio_decoder.raw", "audio_encoder.raw" },
{ MEDIA_MIMETYPE_AUDIO_FLAC,
@@ -4563,7 +4567,7 @@ status_t QueryCodec(
CodecCapabilities *caps) {
if (strncmp(componentName, "OMX.", 4)) {
// Not an OpenMax component but a software codec.
-
+ caps->mFlags = 0;
caps->mComponentName = componentName;
return OK;
}
@@ -4578,6 +4582,7 @@ status_t QueryCodec(
OMXCodec::setComponentRole(omx, node, isEncoder, mime);
+ caps->mFlags = 0;
caps->mComponentName = componentName;
OMX_VIDEO_PARAM_PROFILELEVELTYPE param;
@@ -4615,6 +4620,16 @@ status_t QueryCodec(
caps->mColorFormats.push(portFormat.eColorFormat);
}
+ if (!isEncoder && !strncmp(mime, "video/", 6)) {
+ if (omx->storeMetaDataInBuffers(
+ node, 1 /* port index */, OMX_TRUE) == OK ||
+ omx->prepareForAdaptivePlayback(
+ node, 1 /* port index */, OMX_TRUE,
+ 1280 /* width */, 720 /* height */) == OK) {
+ caps->mFlags |= CodecCapabilities::kFlagSupportsAdaptivePlayback;
+ }
+ }
+
CHECK_EQ(omx->freeNode(node), (status_t)OK);
return OK;
diff --git a/media/libstagefright/SurfaceMediaSource.cpp b/media/libstagefright/SurfaceMediaSource.cpp
index 409038a..6b934d4 100644
--- a/media/libstagefright/SurfaceMediaSource.cpp
+++ b/media/libstagefright/SurfaceMediaSource.cpp
@@ -21,7 +21,7 @@
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
#include <OMX_IVCommon.h>
-#include <MetadataBufferType.h>
+#include <media/hardware/MetadataBufferType.h>
#include <ui/GraphicBuffer.h>
#include <gui/ISurfaceComposer.h>
@@ -54,9 +54,8 @@ SurfaceMediaSource::SurfaceMediaSource(uint32_t bufferWidth, uint32_t bufferHeig
ALOGE("Invalid dimensions %dx%d", bufferWidth, bufferHeight);
}
- mBufferQueue = new BufferQueue(true);
+ mBufferQueue = new BufferQueue();
mBufferQueue->setDefaultBufferSize(bufferWidth, bufferHeight);
- mBufferQueue->setSynchronousMode(true);
mBufferQueue->setConsumerUsageBits(GRALLOC_USAGE_HW_VIDEO_ENCODER |
GRALLOC_USAGE_HW_TEXTURE);
@@ -66,12 +65,10 @@ SurfaceMediaSource::SurfaceMediaSource(uint32_t bufferWidth, uint32_t bufferHeig
// reference once the ctor ends, as that would cause the refcount of 'this'
// dropping to 0 at the end of the ctor. Since all we need is a wp<...>
// that's what we create.
- wp<BufferQueue::ConsumerListener> listener;
- sp<BufferQueue::ConsumerListener> proxy;
- listener = static_cast<BufferQueue::ConsumerListener*>(this);
- proxy = new BufferQueue::ProxyConsumerListener(listener);
+ wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this);
+ sp<BufferQueue::ProxyConsumerListener> proxy = new BufferQueue::ProxyConsumerListener(listener);
- status_t err = mBufferQueue->consumerConnect(proxy);
+ status_t err = mBufferQueue->consumerConnect(proxy, false);
if (err != NO_ERROR) {
ALOGE("SurfaceMediaSource: error connecting to BufferQueue: %s (%d)",
strerror(-err), err);
@@ -108,7 +105,7 @@ void SurfaceMediaSource::dump(String8& result, const char* prefix,
Mutex::Autolock lock(mMutex);
result.append(buffer);
- mBufferQueue->dump(result);
+ mBufferQueue->dump(result, "");
}
status_t SurfaceMediaSource::setFrameRate(int32_t fps)
@@ -293,7 +290,7 @@ status_t SurfaceMediaSource::read( MediaBuffer **buffer,
// wait here till the frames come in from the client side
while (mStarted) {
- status_t err = mBufferQueue->acquireBuffer(&item);
+ status_t err = mBufferQueue->acquireBuffer(&item, 0);
if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
// wait for a buffer to be queued
mFrameAvailableCondition.wait(mMutex);
@@ -305,8 +302,9 @@ status_t SurfaceMediaSource::read( MediaBuffer **buffer,
// First time seeing the buffer? Added it to the SMS slot
if (item.mGraphicBuffer != NULL) {
- mBufferSlot[item.mBuf] = item.mGraphicBuffer;
+ mSlots[item.mBuf].mGraphicBuffer = item.mGraphicBuffer;
}
+ mSlots[item.mBuf].mFrameNumber = item.mFrameNumber;
// check for the timing of this buffer
if (mNumFramesReceived == 0 && !mUseAbsoluteTimestamps) {
@@ -315,7 +313,8 @@ status_t SurfaceMediaSource::read( MediaBuffer **buffer,
if (mStartTimeNs > 0) {
if (item.mTimestamp < mStartTimeNs) {
// This frame predates start of record, discard
- mBufferQueue->releaseBuffer(item.mBuf, EGL_NO_DISPLAY,
+ mBufferQueue->releaseBuffer(
+ item.mBuf, item.mFrameNumber, EGL_NO_DISPLAY,
EGL_NO_SYNC_KHR, Fence::NO_FENCE);
continue;
}
@@ -345,17 +344,18 @@ status_t SurfaceMediaSource::read( MediaBuffer **buffer,
// First time seeing the buffer? Added it to the SMS slot
if (item.mGraphicBuffer != NULL) {
- mBufferSlot[mCurrentSlot] = item.mGraphicBuffer;
+ mSlots[item.mBuf].mGraphicBuffer = item.mGraphicBuffer;
}
+ mSlots[item.mBuf].mFrameNumber = item.mFrameNumber;
- mCurrentBuffers.push_back(mBufferSlot[mCurrentSlot]);
+ mCurrentBuffers.push_back(mSlots[mCurrentSlot].mGraphicBuffer);
int64_t prevTimeStamp = mCurrentTimestamp;
mCurrentTimestamp = item.mTimestamp;
mNumFramesEncoded++;
// Pass the data to the MediaBuffer. Pass in only the metadata
- passMetadataBuffer(buffer, mBufferSlot[mCurrentSlot]->handle);
+ passMetadataBuffer(buffer, mSlots[mCurrentSlot].mGraphicBuffer->handle);
(*buffer)->setObserver(this);
(*buffer)->add_ref();
@@ -405,15 +405,16 @@ void SurfaceMediaSource::signalBufferReturned(MediaBuffer *buffer) {
}
for (int id = 0; id < BufferQueue::NUM_BUFFER_SLOTS; id++) {
- if (mBufferSlot[id] == NULL) {
+ if (mSlots[id].mGraphicBuffer == NULL) {
continue;
}
- if (bufferHandle == mBufferSlot[id]->handle) {
+ if (bufferHandle == mSlots[id].mGraphicBuffer->handle) {
ALOGV("Slot %d returned, matches handle = %p", id,
- mBufferSlot[id]->handle);
+ mSlots[id].mGraphicBuffer->handle);
- mBufferQueue->releaseBuffer(id, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
+ mBufferQueue->releaseBuffer(id, mSlots[id].mFrameNumber,
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
Fence::NO_FENCE);
buffer->setObserver(0);
@@ -469,7 +470,7 @@ void SurfaceMediaSource::onBuffersReleased() {
mFrameAvailableCondition.signal();
for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
- mBufferSlot[i] = 0;
+ mSlots[i].mGraphicBuffer = 0;
}
}
diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp
index 7e9c4bf..6a16bb4 100644
--- a/media/libstagefright/TimedEventQueue.cpp
+++ b/media/libstagefright/TimedEventQueue.cpp
@@ -31,17 +31,26 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
+#include <binder/IServiceManager.h>
+#include <powermanager/PowerManager.h>
+#include <binder/IPCThreadState.h>
+#include <utils/CallStack.h>
namespace android {
TimedEventQueue::TimedEventQueue()
: mNextEventID(1),
mRunning(false),
- mStopped(false) {
+ mStopped(false),
+ mDeathRecipient(new PMDeathRecipient(this)) {
}
TimedEventQueue::~TimedEventQueue() {
stop();
+ if (mPowerManager != 0) {
+ sp<IBinder> binder = mPowerManager->asBinder();
+ binder->unlinkToDeath(mDeathRecipient);
+ }
}
void TimedEventQueue::start() {
@@ -76,6 +85,11 @@ void TimedEventQueue::stop(bool flush) {
void *dummy;
pthread_join(mThread, &dummy);
+ // some events may be left in the queue if we did not flush and the wake lock
+ // must be released.
+ if (!mQueue.empty()) {
+ releaseWakeLock_l();
+ }
mQueue.clear();
mRunning = false;
@@ -117,6 +131,9 @@ TimedEventQueue::event_id TimedEventQueue::postTimedEvent(
mQueueHeadChangedCondition.signal();
}
+ if (mQueue.empty()) {
+ acquireWakeLock_l();
+ }
mQueue.insert(it, item);
mQueueNotEmptyCondition.signal();
@@ -172,7 +189,9 @@ void TimedEventQueue::cancelEvents(
(*it).event->setEventID(0);
it = mQueue.erase(it);
-
+ if (mQueue.empty()) {
+ releaseWakeLock_l();
+ }
if (stopAfterFirstMatch) {
return;
}
@@ -280,7 +299,9 @@ sp<TimedEventQueue::Event> TimedEventQueue::removeEventFromQueue_l(
event->setEventID(0);
mQueue.erase(it);
-
+ if (mQueue.empty()) {
+ releaseWakeLock_l();
+ }
return event;
}
}
@@ -290,5 +311,60 @@ sp<TimedEventQueue::Event> TimedEventQueue::removeEventFromQueue_l(
return NULL;
}
+void TimedEventQueue::acquireWakeLock_l()
+{
+ if (mWakeLockToken != 0) {
+ return;
+ }
+ if (mPowerManager == 0) {
+ // use checkService() to avoid blocking if power service is not up yet
+ sp<IBinder> binder =
+ defaultServiceManager()->checkService(String16("power"));
+ if (binder == 0) {
+ ALOGW("cannot connect to the power manager service");
+ } else {
+ mPowerManager = interface_cast<IPowerManager>(binder);
+ binder->linkToDeath(mDeathRecipient);
+ }
+ }
+ if (mPowerManager != 0) {
+ sp<IBinder> binder = new BBinder();
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
+ status_t status = mPowerManager->acquireWakeLock(POWERMANAGER_PARTIAL_WAKE_LOCK,
+ binder,
+ String16("TimedEventQueue"),
+ String16("media"));
+ IPCThreadState::self()->restoreCallingIdentity(token);
+ if (status == NO_ERROR) {
+ mWakeLockToken = binder;
+ }
+ }
+}
+
+void TimedEventQueue::releaseWakeLock_l()
+{
+ if (mWakeLockToken == 0) {
+ return;
+ }
+ if (mPowerManager != 0) {
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
+ mPowerManager->releaseWakeLock(mWakeLockToken, 0);
+ IPCThreadState::self()->restoreCallingIdentity(token);
+ }
+ mWakeLockToken.clear();
+}
+
+void TimedEventQueue::clearPowerManager()
+{
+ Mutex::Autolock _l(mLock);
+ releaseWakeLock_l();
+ mPowerManager.clear();
+}
+
+void TimedEventQueue::PMDeathRecipient::binderDied(const wp<IBinder>& who)
+{
+ mQueue->clearPowerManager();
+}
+
} // namespace android
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index b0df379..4db8e80 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -26,7 +26,12 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/AudioSystem.h>
+#include <media/MediaPlayerInterface.h>
+#include <hardware/audio.h>
#include <media/stagefright/Utils.h>
+#include <media/AudioParameter.h>
namespace android {
@@ -471,5 +476,132 @@ AString MakeUserAgent() {
return ua;
}
+status_t sendMetaDataToHal(sp<MediaPlayerBase::AudioSink>& sink,
+ const sp<MetaData>& meta)
+{
+ int32_t sampleRate = 0;
+ int32_t bitRate = 0;
+ int32_t channelMask = 0;
+ int32_t delaySamples = 0;
+ int32_t paddingSamples = 0;
+
+ AudioParameter param = AudioParameter();
+
+ if (meta->findInt32(kKeySampleRate, &sampleRate)) {
+ param.addInt(String8(AUDIO_OFFLOAD_CODEC_SAMPLE_RATE), sampleRate);
+ }
+ if (meta->findInt32(kKeyChannelMask, &channelMask)) {
+ param.addInt(String8(AUDIO_OFFLOAD_CODEC_NUM_CHANNEL), channelMask);
+ }
+ if (meta->findInt32(kKeyBitRate, &bitRate)) {
+ param.addInt(String8(AUDIO_OFFLOAD_CODEC_AVG_BIT_RATE), bitRate);
+ }
+ if (meta->findInt32(kKeyEncoderDelay, &delaySamples)) {
+ param.addInt(String8(AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES), delaySamples);
+ }
+ if (meta->findInt32(kKeyEncoderPadding, &paddingSamples)) {
+ param.addInt(String8(AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES), paddingSamples);
+ }
+
+ ALOGV("sendMetaDataToHal: bitRate %d, sampleRate %d, chanMask %d,"
+ "delaySample %d, paddingSample %d", bitRate, sampleRate,
+ channelMask, delaySamples, paddingSamples);
+
+ sink->setParameters(param.toString());
+ return OK;
+}
+
+struct mime_conv_t {
+ const char* mime;
+ audio_format_t format;
+};
+
+static const struct mime_conv_t mimeLookup[] = {
+ { MEDIA_MIMETYPE_AUDIO_MPEG, AUDIO_FORMAT_MP3 },
+ { MEDIA_MIMETYPE_AUDIO_RAW, AUDIO_FORMAT_PCM_16_BIT },
+ { MEDIA_MIMETYPE_AUDIO_AMR_NB, AUDIO_FORMAT_AMR_NB },
+ { MEDIA_MIMETYPE_AUDIO_AMR_WB, AUDIO_FORMAT_AMR_WB },
+ { MEDIA_MIMETYPE_AUDIO_AAC, AUDIO_FORMAT_AAC },
+ { MEDIA_MIMETYPE_AUDIO_VORBIS, AUDIO_FORMAT_VORBIS },
+ { 0, AUDIO_FORMAT_INVALID }
+};
+
+status_t mapMimeToAudioFormat( audio_format_t& format, const char* mime )
+{
+const struct mime_conv_t* p = &mimeLookup[0];
+ while (p->mime != NULL) {
+ if (0 == strcasecmp(mime, p->mime)) {
+ format = p->format;
+ return OK;
+ }
+ ++p;
+ }
+
+ return BAD_VALUE;
+}
+
+bool canOffloadStream(const sp<MetaData>& meta, bool hasVideo, bool isStreaming)
+{
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+ audio_offload_info_t info = AUDIO_INFO_INITIALIZER;
+
+ info.format = AUDIO_FORMAT_INVALID;
+ if (mapMimeToAudioFormat(info.format, mime) != OK) {
+ ALOGE(" Couldn't map mime type \"%s\" to a valid AudioSystem::audio_format !", mime);
+ return false;
+ } else {
+ ALOGV("Mime type \"%s\" mapped to audio_format %d", mime, info.format);
+ }
+
+ if (AUDIO_FORMAT_INVALID == info.format) {
+ // can't offload if we don't know what the source format is
+ ALOGE("mime type \"%s\" not a known audio format", mime);
+ return false;
+ }
+
+ int32_t srate = -1;
+ if (!meta->findInt32(kKeySampleRate, &srate)) {
+ ALOGV("track of type '%s' does not publish sample rate", mime);
+ }
+ info.sample_rate = srate;
+
+ int32_t cmask = 0;
+ if (!meta->findInt32(kKeyChannelMask, &cmask)) {
+ ALOGV("track of type '%s' does not publish channel mask", mime);
+
+ // Try a channel count instead
+ int32_t channelCount;
+ if (!meta->findInt32(kKeyChannelCount, &channelCount)) {
+ ALOGV("track of type '%s' does not publish channel count", mime);
+ } else {
+ cmask = audio_channel_out_mask_from_count(channelCount);
+ }
+ }
+ info.channel_mask = cmask;
+
+ int64_t duration = 0;
+ if (!meta->findInt64(kKeyDuration, &duration)) {
+ ALOGV("track of type '%s' does not publish duration", mime);
+ }
+ info.duration_us = duration;
+
+ int32_t brate = -1;
+ if (!meta->findInt32(kKeyBitRate, &brate)) {
+ ALOGV("track of type '%s' does not publish bitrate", mime);
+ }
+ info.bit_rate = brate;
+
+
+ info.stream_type = AUDIO_STREAM_MUSIC;
+ info.has_video = hasVideo;
+ info.is_streaming = isStreaming;
+
+ // Check if offload is possible for given format, stream type, sample rate,
+ // bit rate, duration, video and streaming
+ return AudioSystem::isOffloadSupported(info);
+}
+
} // namespace android
diff --git a/media/libstagefright/codecs/aacdec/SoftAAC2.cpp b/media/libstagefright/codecs/aacdec/SoftAAC2.cpp
index cf50dc9..1b20cbb 100644
--- a/media/libstagefright/codecs/aacdec/SoftAAC2.cpp
+++ b/media/libstagefright/codecs/aacdec/SoftAAC2.cpp
@@ -604,6 +604,9 @@ void SoftAAC2::onReset() {
// To make the codec behave the same before and after a reset, we need to invalidate the
// streaminfo struct. This does that:
mStreamInfo->sampleRate = 0;
+
+ mSignalledError = false;
+ mOutputPortSettingsChange = NONE;
}
void SoftAAC2::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
diff --git a/media/libstagefright/codecs/aacenc/SampleCode/Android.mk b/media/libstagefright/codecs/aacenc/SampleCode/Android.mk
index 01016e7..d06dcf6 100644
--- a/media/libstagefright/codecs/aacenc/SampleCode/Android.mk
+++ b/media/libstagefright/codecs/aacenc/SampleCode/Android.mk
@@ -5,7 +5,7 @@ LOCAL_SRC_FILES := \
AAC_E_SAMPLES.c \
../../common/cmnMemory.c
-LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := AACEncTest
diff --git a/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp b/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp
index 5749733..ff2b503 100644
--- a/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp
+++ b/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp
@@ -292,6 +292,10 @@ static AUDIO_OBJECT_TYPE getAOTFromProfile(OMX_U32 profile) {
return AOT_AAC_LC;
} else if (profile == OMX_AUDIO_AACObjectHE) {
return AOT_SBR;
+ } else if (profile == OMX_AUDIO_AACObjectHE_PS) {
+ return AOT_PS;
+ } else if (profile == OMX_AUDIO_AACObjectLD) {
+ return AOT_ER_AAC_LD;
} else if (profile == OMX_AUDIO_AACObjectELD) {
return AOT_ER_AAC_ELD;
} else {
diff --git a/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp b/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp
index 4d4212f..3320688 100644
--- a/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp
+++ b/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp
@@ -457,6 +457,11 @@ void SoftAMR::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
}
}
+void SoftAMR::onReset() {
+ mSignalledError = false;
+ mOutputPortSettingsChange = NONE;
+}
+
} // namespace android
android::SoftOMXComponent *createSoftOMXComponent(
diff --git a/media/libstagefright/codecs/amrnb/dec/SoftAMR.h b/media/libstagefright/codecs/amrnb/dec/SoftAMR.h
index 9a596e5..758d6ac 100644
--- a/media/libstagefright/codecs/amrnb/dec/SoftAMR.h
+++ b/media/libstagefright/codecs/amrnb/dec/SoftAMR.h
@@ -40,6 +40,7 @@ protected:
virtual void onQueueFilled(OMX_U32 portIndex);
virtual void onPortFlushCompleted(OMX_U32 portIndex);
virtual void onPortEnableCompleted(OMX_U32 portIndex, bool enabled);
+ virtual void onReset();
private:
enum {
diff --git a/media/libstagefright/codecs/amrwbenc/SampleCode/Android.mk b/media/libstagefright/codecs/amrwbenc/SampleCode/Android.mk
index db34d08..c203f77 100644
--- a/media/libstagefright/codecs/amrwbenc/SampleCode/Android.mk
+++ b/media/libstagefright/codecs/amrwbenc/SampleCode/Android.mk
@@ -5,7 +5,7 @@ LOCAL_SRC_FILES := \
AMRWB_E_SAMPLE.c \
../../common/cmnMemory.c
-LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := AMRWBEncTest
LOCAL_ARM_MODE := arm
diff --git a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
index 020cc0a..fb2a430 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
@@ -48,42 +48,32 @@ static const CodecProfileLevel kH263ProfileLevels[] = {
{ OMX_VIDEO_H263ProfileISWV2, OMX_VIDEO_H263Level45 },
};
-template<class T>
-static void InitOMXParams(T *params) {
- params->nSize = sizeof(T);
- params->nVersion.s.nVersionMajor = 1;
- params->nVersion.s.nVersionMinor = 0;
- params->nVersion.s.nRevision = 0;
- params->nVersion.s.nStep = 0;
-}
-
SoftMPEG4::SoftMPEG4(
const char *name,
+ const char *componentRole,
+ OMX_VIDEO_CODINGTYPE codingType,
+ const CodecProfileLevel *profileLevels,
+ size_t numProfileLevels,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
OMX_COMPONENTTYPE **component)
- : SimpleSoftOMXComponent(name, callbacks, appData, component),
- mMode(MODE_MPEG4),
+ : SoftVideoDecoderOMXComponent(
+ name, componentRole, codingType, profileLevels, numProfileLevels,
+ 352 /* width */, 288 /* height */, callbacks, appData, component),
+ mMode(codingType == OMX_VIDEO_CodingH263 ? MODE_H263 : MODE_MPEG4),
mHandle(new tagvideoDecControls),
mInputBufferCount(0),
- mWidth(352),
- mHeight(288),
- mCropLeft(0),
- mCropTop(0),
- mCropRight(mWidth - 1),
- mCropBottom(mHeight - 1),
mSignalledError(false),
mInitialized(false),
mFramesConfigured(false),
mNumSamplesOutput(0),
- mOutputPortSettingsChange(NONE) {
- if (!strcmp(name, "OMX.google.h263.decoder")) {
- mMode = MODE_H263;
- } else {
- CHECK(!strcmp(name, "OMX.google.mpeg4.decoder"));
- }
-
- initPorts();
+ mPvTime(0) {
+ initPorts(
+ kNumInputBuffers,
+ 8192 /* inputBufferSize */,
+ kNumOutputBuffers,
+ (mMode == MODE_MPEG4)
+ ? MEDIA_MIMETYPE_VIDEO_MPEG4 : MEDIA_MIMETYPE_VIDEO_H263);
CHECK_EQ(initDecoder(), (status_t)OK);
}
@@ -96,219 +86,11 @@ SoftMPEG4::~SoftMPEG4() {
mHandle = NULL;
}
-void SoftMPEG4::initPorts() {
- OMX_PARAM_PORTDEFINITIONTYPE def;
- InitOMXParams(&def);
-
- def.nPortIndex = 0;
- def.eDir = OMX_DirInput;
- def.nBufferCountMin = kNumInputBuffers;
- def.nBufferCountActual = def.nBufferCountMin;
- def.nBufferSize = 8192;
- def.bEnabled = OMX_TRUE;
- def.bPopulated = OMX_FALSE;
- def.eDomain = OMX_PortDomainVideo;
- def.bBuffersContiguous = OMX_FALSE;
- def.nBufferAlignment = 1;
-
- def.format.video.cMIMEType =
- (mMode == MODE_MPEG4)
- ? const_cast<char *>(MEDIA_MIMETYPE_VIDEO_MPEG4)
- : const_cast<char *>(MEDIA_MIMETYPE_VIDEO_H263);
-
- def.format.video.pNativeRender = NULL;
- def.format.video.nFrameWidth = mWidth;
- def.format.video.nFrameHeight = mHeight;
- def.format.video.nStride = def.format.video.nFrameWidth;
- def.format.video.nSliceHeight = def.format.video.nFrameHeight;
- def.format.video.nBitrate = 0;
- def.format.video.xFramerate = 0;
- def.format.video.bFlagErrorConcealment = OMX_FALSE;
-
- def.format.video.eCompressionFormat =
- mMode == MODE_MPEG4 ? OMX_VIDEO_CodingMPEG4 : OMX_VIDEO_CodingH263;
-
- def.format.video.eColorFormat = OMX_COLOR_FormatUnused;
- def.format.video.pNativeWindow = NULL;
-
- addPort(def);
-
- def.nPortIndex = 1;
- def.eDir = OMX_DirOutput;
- def.nBufferCountMin = kNumOutputBuffers;
- def.nBufferCountActual = def.nBufferCountMin;
- def.bEnabled = OMX_TRUE;
- def.bPopulated = OMX_FALSE;
- def.eDomain = OMX_PortDomainVideo;
- def.bBuffersContiguous = OMX_FALSE;
- def.nBufferAlignment = 2;
-
- def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_RAW);
- def.format.video.pNativeRender = NULL;
- def.format.video.nFrameWidth = mWidth;
- def.format.video.nFrameHeight = mHeight;
- def.format.video.nStride = def.format.video.nFrameWidth;
- def.format.video.nSliceHeight = def.format.video.nFrameHeight;
- def.format.video.nBitrate = 0;
- def.format.video.xFramerate = 0;
- def.format.video.bFlagErrorConcealment = OMX_FALSE;
- def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused;
- def.format.video.eColorFormat = OMX_COLOR_FormatYUV420Planar;
- def.format.video.pNativeWindow = NULL;
-
- def.nBufferSize =
- (def.format.video.nFrameWidth * def.format.video.nFrameHeight * 3) / 2;
-
- addPort(def);
-}
-
status_t SoftMPEG4::initDecoder() {
memset(mHandle, 0, sizeof(tagvideoDecControls));
return OK;
}
-OMX_ERRORTYPE SoftMPEG4::internalGetParameter(
- OMX_INDEXTYPE index, OMX_PTR params) {
- switch (index) {
- case OMX_IndexParamVideoPortFormat:
- {
- OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams =
- (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params;
-
- if (formatParams->nPortIndex > 1) {
- return OMX_ErrorUndefined;
- }
-
- if (formatParams->nIndex != 0) {
- return OMX_ErrorNoMore;
- }
-
- if (formatParams->nPortIndex == 0) {
- formatParams->eCompressionFormat =
- (mMode == MODE_MPEG4)
- ? OMX_VIDEO_CodingMPEG4 : OMX_VIDEO_CodingH263;
-
- formatParams->eColorFormat = OMX_COLOR_FormatUnused;
- formatParams->xFramerate = 0;
- } else {
- CHECK_EQ(formatParams->nPortIndex, 1u);
-
- formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused;
- formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar;
- formatParams->xFramerate = 0;
- }
-
- return OMX_ErrorNone;
- }
-
- case OMX_IndexParamVideoProfileLevelQuerySupported:
- {
- OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileLevel =
- (OMX_VIDEO_PARAM_PROFILELEVELTYPE *) params;
-
- if (profileLevel->nPortIndex != 0) { // Input port only
- ALOGE("Invalid port index: %ld", profileLevel->nPortIndex);
- return OMX_ErrorUnsupportedIndex;
- }
-
- size_t index = profileLevel->nProfileIndex;
- if (mMode == MODE_H263) {
- size_t nProfileLevels =
- sizeof(kH263ProfileLevels) / sizeof(kH263ProfileLevels[0]);
- if (index >= nProfileLevels) {
- return OMX_ErrorNoMore;
- }
-
- profileLevel->eProfile = kH263ProfileLevels[index].mProfile;
- profileLevel->eLevel = kH263ProfileLevels[index].mLevel;
- } else {
- size_t nProfileLevels =
- sizeof(kM4VProfileLevels) / sizeof(kM4VProfileLevels[0]);
- if (index >= nProfileLevels) {
- return OMX_ErrorNoMore;
- }
-
- profileLevel->eProfile = kM4VProfileLevels[index].mProfile;
- profileLevel->eLevel = kM4VProfileLevels[index].mLevel;
- }
- return OMX_ErrorNone;
- }
-
- default:
- return SimpleSoftOMXComponent::internalGetParameter(index, params);
- }
-}
-
-OMX_ERRORTYPE SoftMPEG4::internalSetParameter(
- OMX_INDEXTYPE index, const OMX_PTR params) {
- switch (index) {
- case OMX_IndexParamStandardComponentRole:
- {
- const OMX_PARAM_COMPONENTROLETYPE *roleParams =
- (const OMX_PARAM_COMPONENTROLETYPE *)params;
-
- if (mMode == MODE_MPEG4) {
- if (strncmp((const char *)roleParams->cRole,
- "video_decoder.mpeg4",
- OMX_MAX_STRINGNAME_SIZE - 1)) {
- return OMX_ErrorUndefined;
- }
- } else {
- if (strncmp((const char *)roleParams->cRole,
- "video_decoder.h263",
- OMX_MAX_STRINGNAME_SIZE - 1)) {
- return OMX_ErrorUndefined;
- }
- }
-
- return OMX_ErrorNone;
- }
-
- case OMX_IndexParamVideoPortFormat:
- {
- OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams =
- (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params;
-
- if (formatParams->nPortIndex > 1) {
- return OMX_ErrorUndefined;
- }
-
- if (formatParams->nIndex != 0) {
- return OMX_ErrorNoMore;
- }
-
- return OMX_ErrorNone;
- }
-
- default:
- return SimpleSoftOMXComponent::internalSetParameter(index, params);
- }
-}
-
-OMX_ERRORTYPE SoftMPEG4::getConfig(
- OMX_INDEXTYPE index, OMX_PTR params) {
- switch (index) {
- case OMX_IndexConfigCommonOutputCrop:
- {
- OMX_CONFIG_RECTTYPE *rectParams = (OMX_CONFIG_RECTTYPE *)params;
-
- if (rectParams->nPortIndex != 1) {
- return OMX_ErrorUndefined;
- }
-
- rectParams->nLeft = mCropLeft;
- rectParams->nTop = mCropTop;
- rectParams->nWidth = mCropRight - mCropLeft + 1;
- rectParams->nHeight = mCropBottom - mCropTop + 1;
-
- return OMX_ErrorNone;
- }
-
- default:
- return OMX_ErrorUnsupportedIndex;
- }
-}
-
void SoftMPEG4::onQueueFilled(OMX_U32 portIndex) {
if (mSignalledError || mOutputPortSettingsChange != NONE) {
return;
@@ -415,9 +197,14 @@ void SoftMPEG4::onQueueFilled(OMX_U32 portIndex) {
uint32_t useExtTimestamp = (inHeader->nOffset == 0);
- // decoder deals in ms, OMX in us.
- uint32_t timestamp =
- useExtTimestamp ? (inHeader->nTimeStamp + 500) / 1000 : 0xFFFFFFFF;
+ // decoder deals in ms (int32_t), OMX in us (int64_t)
+ // so use fake timestamp instead
+ uint32_t timestamp = 0xFFFFFFFF;
+ if (useExtTimestamp) {
+ mPvToOmxTimeMap.add(mPvTime, inHeader->nTimeStamp);
+ timestamp = mPvTime;
+ mPvTime++;
+ }
int32_t bufferSize = inHeader->nFilledLen;
int32_t tmp = bufferSize;
@@ -441,7 +228,8 @@ void SoftMPEG4::onQueueFilled(OMX_U32 portIndex) {
}
// decoder deals in ms, OMX in us.
- outHeader->nTimeStamp = timestamp * 1000;
+ outHeader->nTimeStamp = mPvToOmxTimeMap.valueFor(timestamp);
+ mPvToOmxTimeMap.removeItem(timestamp);
inHeader->nOffset += bufferSize;
inHeader->nFilledLen = 0;
@@ -482,11 +270,11 @@ void SoftMPEG4::onQueueFilled(OMX_U32 portIndex) {
}
bool SoftMPEG4::portSettingsChanged() {
- int32_t disp_width, disp_height;
- PVGetVideoDimensions(mHandle, &disp_width, &disp_height);
+ uint32_t disp_width, disp_height;
+ PVGetVideoDimensions(mHandle, (int32 *)&disp_width, (int32 *)&disp_height);
- int32_t buf_width, buf_height;
- PVGetBufferDimensions(mHandle, &buf_width, &buf_height);
+ uint32_t buf_width, buf_height;
+ PVGetBufferDimensions(mHandle, (int32 *)&buf_width, (int32 *)&buf_height);
CHECK_LE(disp_width, buf_width);
CHECK_LE(disp_height, buf_height);
@@ -494,12 +282,12 @@ bool SoftMPEG4::portSettingsChanged() {
ALOGV("disp_width = %d, disp_height = %d, buf_width = %d, buf_height = %d",
disp_width, disp_height, buf_width, buf_height);
- if (mCropRight != disp_width - 1
- || mCropBottom != disp_height - 1) {
+ if (mCropWidth != disp_width
+ || mCropHeight != disp_height) {
mCropLeft = 0;
mCropTop = 0;
- mCropRight = disp_width - 1;
- mCropBottom = disp_height - 1;
+ mCropWidth = disp_width;
+ mCropHeight = disp_height;
notify(OMX_EventPortSettingsChanged,
1,
@@ -545,45 +333,22 @@ void SoftMPEG4::onPortFlushCompleted(OMX_U32 portIndex) {
}
}
-void SoftMPEG4::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
- if (portIndex != 1) {
- return;
- }
-
- switch (mOutputPortSettingsChange) {
- case NONE:
- break;
-
- case AWAITING_DISABLED:
- {
- CHECK(!enabled);
- mOutputPortSettingsChange = AWAITING_ENABLED;
- break;
- }
-
- default:
- {
- CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED);
- CHECK(enabled);
- mOutputPortSettingsChange = NONE;
- break;
- }
+void SoftMPEG4::onReset() {
+ SoftVideoDecoderOMXComponent::onReset();
+ mPvToOmxTimeMap.clear();
+ mSignalledError = false;
+ mFramesConfigured = false;
+ if (mInitialized) {
+ PVCleanUpVideoDecoder(mHandle);
+ mInitialized = false;
}
}
void SoftMPEG4::updatePortDefinitions() {
- OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(0)->mDef;
- def->format.video.nFrameWidth = mWidth;
- def->format.video.nFrameHeight = mHeight;
- def->format.video.nStride = def->format.video.nFrameWidth;
- def->format.video.nSliceHeight = def->format.video.nFrameHeight;
-
- def = &editPortInfo(1)->mDef;
- def->format.video.nFrameWidth = mWidth;
- def->format.video.nFrameHeight = mHeight;
- def->format.video.nStride = def->format.video.nFrameWidth;
- def->format.video.nSliceHeight = def->format.video.nFrameHeight;
+ SoftVideoDecoderOMXComponent::updatePortDefinitions();
+ /* We have to align our width and height - this should affect stride! */
+ OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kOutputPortIndex)->mDef;
def->nBufferSize =
(((def->format.video.nFrameWidth + 15) & -16)
* ((def->format.video.nFrameHeight + 15) & -16) * 3) / 2;
@@ -594,6 +359,19 @@ void SoftMPEG4::updatePortDefinitions() {
android::SoftOMXComponent *createSoftOMXComponent(
const char *name, const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData, OMX_COMPONENTTYPE **component) {
- return new android::SoftMPEG4(name, callbacks, appData, component);
+ using namespace android;
+ if (!strcmp(name, "OMX.google.h263.decoder")) {
+ return new android::SoftMPEG4(
+ name, "video_decoder.h263", OMX_VIDEO_CodingH263,
+ kH263ProfileLevels, ARRAY_SIZE(kH263ProfileLevels),
+ callbacks, appData, component);
+ } else if (!strcmp(name, "OMX.google.mpeg4.decoder")) {
+ return new android::SoftMPEG4(
+ name, "video_decoder.mpeg4", OMX_VIDEO_CodingMPEG4,
+ kM4VProfileLevels, ARRAY_SIZE(kM4VProfileLevels),
+ callbacks, appData, component);
+ } else {
+ CHECK(!"Unknown component");
+ }
}
diff --git a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.h b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.h
index dff08a7..de14aaf 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.h
+++ b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.h
@@ -18,14 +18,18 @@
#define SOFT_MPEG4_H_
-#include "SimpleSoftOMXComponent.h"
+#include "SoftVideoDecoderOMXComponent.h"
struct tagvideoDecControls;
namespace android {
-struct SoftMPEG4 : public SimpleSoftOMXComponent {
+struct SoftMPEG4 : public SoftVideoDecoderOMXComponent {
SoftMPEG4(const char *name,
+ const char *componentRole,
+ OMX_VIDEO_CODINGTYPE codingType,
+ const CodecProfileLevel *profileLevels,
+ size_t numProfileLevels,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
OMX_COMPONENTTYPE **component);
@@ -33,17 +37,9 @@ struct SoftMPEG4 : public SimpleSoftOMXComponent {
protected:
virtual ~SoftMPEG4();
- virtual OMX_ERRORTYPE internalGetParameter(
- OMX_INDEXTYPE index, OMX_PTR params);
-
- virtual OMX_ERRORTYPE internalSetParameter(
- OMX_INDEXTYPE index, const OMX_PTR params);
-
- virtual OMX_ERRORTYPE getConfig(OMX_INDEXTYPE index, OMX_PTR params);
-
virtual void onQueueFilled(OMX_U32 portIndex);
virtual void onPortFlushCompleted(OMX_U32 portIndex);
- virtual void onPortEnableCompleted(OMX_U32 portIndex, bool enabled);
+ virtual void onReset();
private:
enum {
@@ -54,32 +50,23 @@ private:
enum {
MODE_MPEG4,
MODE_H263,
-
} mMode;
tagvideoDecControls *mHandle;
size_t mInputBufferCount;
- int32_t mWidth, mHeight;
- int32_t mCropLeft, mCropTop, mCropRight, mCropBottom;
-
bool mSignalledError;
bool mInitialized;
bool mFramesConfigured;
int32_t mNumSamplesOutput;
+ int32_t mPvTime;
+ KeyedVector<int32_t, OMX_TICKS> mPvToOmxTimeMap;
- enum {
- NONE,
- AWAITING_DISABLED,
- AWAITING_ENABLED
- } mOutputPortSettingsChange;
-
- void initPorts();
status_t initDecoder();
- void updatePortDefinitions();
+ virtual void updatePortDefinitions();
bool portSettingsChanged();
DISALLOW_EVIL_CONSTRUCTORS(SoftMPEG4);
diff --git a/media/libstagefright/codecs/mp3dec/SoftMP3.cpp b/media/libstagefright/codecs/mp3dec/SoftMP3.cpp
index 9f25536..7c382fb 100644
--- a/media/libstagefright/codecs/mp3dec/SoftMP3.cpp
+++ b/media/libstagefright/codecs/mp3dec/SoftMP3.cpp
@@ -361,6 +361,8 @@ void SoftMP3::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
void SoftMP3::onReset() {
pvmp3_InitDecoder(mConfig, mDecoderBuf);
mIsFirst = true;
+ mSignalledError = false;
+ mOutputPortSettingsChange = NONE;
}
} // namespace android
diff --git a/media/libstagefright/codecs/on2/dec/SoftVPX.cpp b/media/libstagefright/codecs/on2/dec/SoftVPX.cpp
index a400b4c..476e986 100644
--- a/media/libstagefright/codecs/on2/dec/SoftVPX.cpp
+++ b/media/libstagefright/codecs/on2/dec/SoftVPX.cpp
@@ -29,26 +29,23 @@
namespace android {
-template<class T>
-static void InitOMXParams(T *params) {
- params->nSize = sizeof(T);
- params->nVersion.s.nVersionMajor = 1;
- params->nVersion.s.nVersionMinor = 0;
- params->nVersion.s.nRevision = 0;
- params->nVersion.s.nStep = 0;
-}
-
SoftVPX::SoftVPX(
const char *name,
+ const char *componentRole,
+ OMX_VIDEO_CODINGTYPE codingType,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
OMX_COMPONENTTYPE **component)
- : SimpleSoftOMXComponent(name, callbacks, appData, component),
- mCtx(NULL),
- mWidth(320),
- mHeight(240),
- mOutputPortSettingsChange(NONE) {
- initPorts();
+ : SoftVideoDecoderOMXComponent(
+ name, componentRole, codingType,
+ NULL /* profileLevels */, 0 /* numProfileLevels */,
+ 320 /* width */, 240 /* height */, callbacks, appData, component),
+ mMode(codingType == OMX_VIDEO_CodingVP8 ? MODE_VP8 : MODE_VP9),
+ mCtx(NULL) {
+ initPorts(kNumBuffers, 768 * 1024 /* inputBufferSize */,
+ kNumBuffers,
+ codingType == OMX_VIDEO_CodingVP8 ? MEDIA_MIMETYPE_VIDEO_VP8 : MEDIA_MIMETYPE_VIDEO_VP9);
+
CHECK_EQ(initDecoder(), (status_t)OK);
}
@@ -58,65 +55,6 @@ SoftVPX::~SoftVPX() {
mCtx = NULL;
}
-void SoftVPX::initPorts() {
- OMX_PARAM_PORTDEFINITIONTYPE def;
- InitOMXParams(&def);
-
- def.nPortIndex = 0;
- def.eDir = OMX_DirInput;
- def.nBufferCountMin = kNumBuffers;
- def.nBufferCountActual = def.nBufferCountMin;
- def.nBufferSize = 768 * 1024;
- def.bEnabled = OMX_TRUE;
- def.bPopulated = OMX_FALSE;
- def.eDomain = OMX_PortDomainVideo;
- def.bBuffersContiguous = OMX_FALSE;
- def.nBufferAlignment = 1;
-
- def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_VPX);
- def.format.video.pNativeRender = NULL;
- def.format.video.nFrameWidth = mWidth;
- def.format.video.nFrameHeight = mHeight;
- def.format.video.nStride = def.format.video.nFrameWidth;
- def.format.video.nSliceHeight = def.format.video.nFrameHeight;
- def.format.video.nBitrate = 0;
- def.format.video.xFramerate = 0;
- def.format.video.bFlagErrorConcealment = OMX_FALSE;
- def.format.video.eCompressionFormat = OMX_VIDEO_CodingVPX;
- def.format.video.eColorFormat = OMX_COLOR_FormatUnused;
- def.format.video.pNativeWindow = NULL;
-
- addPort(def);
-
- def.nPortIndex = 1;
- def.eDir = OMX_DirOutput;
- def.nBufferCountMin = kNumBuffers;
- def.nBufferCountActual = def.nBufferCountMin;
- def.bEnabled = OMX_TRUE;
- def.bPopulated = OMX_FALSE;
- def.eDomain = OMX_PortDomainVideo;
- def.bBuffersContiguous = OMX_FALSE;
- def.nBufferAlignment = 2;
-
- def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_RAW);
- def.format.video.pNativeRender = NULL;
- def.format.video.nFrameWidth = mWidth;
- def.format.video.nFrameHeight = mHeight;
- def.format.video.nStride = def.format.video.nFrameWidth;
- def.format.video.nSliceHeight = def.format.video.nFrameHeight;
- def.format.video.nBitrate = 0;
- def.format.video.xFramerate = 0;
- def.format.video.bFlagErrorConcealment = OMX_FALSE;
- def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused;
- def.format.video.eColorFormat = OMX_COLOR_FormatYUV420Planar;
- def.format.video.pNativeWindow = NULL;
-
- def.nBufferSize =
- (def.format.video.nFrameWidth * def.format.video.nFrameHeight * 3) / 2;
-
- addPort(def);
-}
-
static int GetCPUCoreCount() {
int cpuCoreCount = 1;
#if defined(_SC_NPROCESSORS_ONLN)
@@ -137,7 +75,9 @@ status_t SoftVPX::initDecoder() {
memset(&cfg, 0, sizeof(vpx_codec_dec_cfg_t));
cfg.threads = GetCPUCoreCount();
if ((vpx_err = vpx_codec_dec_init(
- (vpx_codec_ctx_t *)mCtx, &vpx_codec_vp8_dx_algo, &cfg, 0))) {
+ (vpx_codec_ctx_t *)mCtx,
+ mMode == MODE_VP8 ? &vpx_codec_vp8_dx_algo : &vpx_codec_vp9_dx_algo,
+ &cfg, 0))) {
ALOGE("on2 decoder failed to initialize. (%d)", vpx_err);
return UNKNOWN_ERROR;
}
@@ -145,80 +85,6 @@ status_t SoftVPX::initDecoder() {
return OK;
}
-OMX_ERRORTYPE SoftVPX::internalGetParameter(
- OMX_INDEXTYPE index, OMX_PTR params) {
- switch (index) {
- case OMX_IndexParamVideoPortFormat:
- {
- OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams =
- (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params;
-
- if (formatParams->nPortIndex > 1) {
- return OMX_ErrorUndefined;
- }
-
- if (formatParams->nIndex != 0) {
- return OMX_ErrorNoMore;
- }
-
- if (formatParams->nPortIndex == 0) {
- formatParams->eCompressionFormat = OMX_VIDEO_CodingVPX;
- formatParams->eColorFormat = OMX_COLOR_FormatUnused;
- formatParams->xFramerate = 0;
- } else {
- CHECK_EQ(formatParams->nPortIndex, 1u);
-
- formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused;
- formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar;
- formatParams->xFramerate = 0;
- }
-
- return OMX_ErrorNone;
- }
-
- default:
- return SimpleSoftOMXComponent::internalGetParameter(index, params);
- }
-}
-
-OMX_ERRORTYPE SoftVPX::internalSetParameter(
- OMX_INDEXTYPE index, const OMX_PTR params) {
- switch (index) {
- case OMX_IndexParamStandardComponentRole:
- {
- const OMX_PARAM_COMPONENTROLETYPE *roleParams =
- (const OMX_PARAM_COMPONENTROLETYPE *)params;
-
- if (strncmp((const char *)roleParams->cRole,
- "video_decoder.vpx",
- OMX_MAX_STRINGNAME_SIZE - 1)) {
- return OMX_ErrorUndefined;
- }
-
- return OMX_ErrorNone;
- }
-
- case OMX_IndexParamVideoPortFormat:
- {
- OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams =
- (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params;
-
- if (formatParams->nPortIndex > 1) {
- return OMX_ErrorUndefined;
- }
-
- if (formatParams->nIndex != 0) {
- return OMX_ErrorNoMore;
- }
-
- return OMX_ErrorNone;
- }
-
- default:
- return SimpleSoftOMXComponent::internalSetParameter(index, params);
- }
-}
-
void SoftVPX::onQueueFilled(OMX_U32 portIndex) {
if (mOutputPortSettingsChange != NONE) {
return;
@@ -226,6 +92,7 @@ void SoftVPX::onQueueFilled(OMX_U32 portIndex) {
List<BufferInfo *> &inQueue = getPortQueue(0);
List<BufferInfo *> &outQueue = getPortQueue(1);
+ bool EOSseen = false;
while (!inQueue.empty() && !outQueue.empty()) {
BufferInfo *inInfo = *inQueue.begin();
@@ -235,17 +102,20 @@ void SoftVPX::onQueueFilled(OMX_U32 portIndex) {
OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader;
if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) {
- inQueue.erase(inQueue.begin());
- inInfo->mOwnedByUs = false;
- notifyEmptyBufferDone(inHeader);
-
- outHeader->nFilledLen = 0;
- outHeader->nFlags = OMX_BUFFERFLAG_EOS;
-
- outQueue.erase(outQueue.begin());
- outInfo->mOwnedByUs = false;
- notifyFillBufferDone(outHeader);
- return;
+ EOSseen = true;
+ if (inHeader->nFilledLen == 0) {
+ inQueue.erase(inQueue.begin());
+ inInfo->mOwnedByUs = false;
+ notifyEmptyBufferDone(inHeader);
+
+ outHeader->nFilledLen = 0;
+ outHeader->nFlags = OMX_BUFFERFLAG_EOS;
+
+ outQueue.erase(outQueue.begin());
+ outInfo->mOwnedByUs = false;
+ notifyFillBufferDone(outHeader);
+ return;
+ }
}
if (vpx_codec_decode(
@@ -266,8 +136,8 @@ void SoftVPX::onQueueFilled(OMX_U32 portIndex) {
if (img != NULL) {
CHECK_EQ(img->fmt, IMG_FMT_I420);
- int32_t width = img->d_w;
- int32_t height = img->d_h;
+ uint32_t width = img->d_w;
+ uint32_t height = img->d_h;
if (width != mWidth || height != mHeight) {
mWidth = width;
@@ -282,7 +152,7 @@ void SoftVPX::onQueueFilled(OMX_U32 portIndex) {
outHeader->nOffset = 0;
outHeader->nFilledLen = (width * height * 3) / 2;
- outHeader->nFlags = 0;
+ outHeader->nFlags = EOSseen ? OMX_BUFFERFLAG_EOS : 0;
outHeader->nTimeStamp = inHeader->nTimeStamp;
const uint8_t *srcLine = (const uint8_t *)img->planes[PLANE_Y];
@@ -325,58 +195,20 @@ void SoftVPX::onQueueFilled(OMX_U32 portIndex) {
}
}
-void SoftVPX::onPortFlushCompleted(OMX_U32 portIndex) {
-}
-
-void SoftVPX::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
- if (portIndex != 1) {
- return;
- }
-
- switch (mOutputPortSettingsChange) {
- case NONE:
- break;
-
- case AWAITING_DISABLED:
- {
- CHECK(!enabled);
- mOutputPortSettingsChange = AWAITING_ENABLED;
- break;
- }
-
- default:
- {
- CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED);
- CHECK(enabled);
- mOutputPortSettingsChange = NONE;
- break;
- }
- }
-}
-
-void SoftVPX::updatePortDefinitions() {
- OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(0)->mDef;
- def->format.video.nFrameWidth = mWidth;
- def->format.video.nFrameHeight = mHeight;
- def->format.video.nStride = def->format.video.nFrameWidth;
- def->format.video.nSliceHeight = def->format.video.nFrameHeight;
-
- def = &editPortInfo(1)->mDef;
- def->format.video.nFrameWidth = mWidth;
- def->format.video.nFrameHeight = mHeight;
- def->format.video.nStride = def->format.video.nFrameWidth;
- def->format.video.nSliceHeight = def->format.video.nFrameHeight;
-
- def->nBufferSize =
- (def->format.video.nFrameWidth
- * def->format.video.nFrameHeight * 3) / 2;
-}
-
} // namespace android
android::SoftOMXComponent *createSoftOMXComponent(
const char *name, const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData, OMX_COMPONENTTYPE **component) {
- return new android::SoftVPX(name, callbacks, appData, component);
+ if (!strcmp(name, "OMX.google.vp8.decoder")) {
+ return new android::SoftVPX(
+ name, "video_decoder.vp8", OMX_VIDEO_CodingVP8,
+ callbacks, appData, component);
+ } else if (!strcmp(name, "OMX.google.vp9.decoder")) {
+ return new android::SoftVPX(
+ name, "video_decoder.vp9", OMX_VIDEO_CodingVP9,
+ callbacks, appData, component);
+ } else {
+ CHECK(!"Unknown component");
+ }
}
-
diff --git a/media/libstagefright/codecs/on2/dec/SoftVPX.h b/media/libstagefright/codecs/on2/dec/SoftVPX.h
index 3e814a2..cd5eb28 100644
--- a/media/libstagefright/codecs/on2/dec/SoftVPX.h
+++ b/media/libstagefright/codecs/on2/dec/SoftVPX.h
@@ -18,12 +18,14 @@
#define SOFT_VPX_H_
-#include "SimpleSoftOMXComponent.h"
+#include "SoftVideoDecoderOMXComponent.h"
namespace android {
-struct SoftVPX : public SimpleSoftOMXComponent {
+struct SoftVPX : public SoftVideoDecoderOMXComponent {
SoftVPX(const char *name,
+ const char *componentRole,
+ OMX_VIDEO_CODINGTYPE codingType,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
OMX_COMPONENTTYPE **component);
@@ -31,36 +33,21 @@ struct SoftVPX : public SimpleSoftOMXComponent {
protected:
virtual ~SoftVPX();
- virtual OMX_ERRORTYPE internalGetParameter(
- OMX_INDEXTYPE index, OMX_PTR params);
-
- virtual OMX_ERRORTYPE internalSetParameter(
- OMX_INDEXTYPE index, const OMX_PTR params);
-
virtual void onQueueFilled(OMX_U32 portIndex);
- virtual void onPortFlushCompleted(OMX_U32 portIndex);
- virtual void onPortEnableCompleted(OMX_U32 portIndex, bool enabled);
private:
enum {
kNumBuffers = 4
};
- void *mCtx;
-
- int32_t mWidth;
- int32_t mHeight;
-
enum {
- NONE,
- AWAITING_DISABLED,
- AWAITING_ENABLED
- } mOutputPortSettingsChange;
+ MODE_VP8,
+ MODE_VP9
+ } mMode;
- void initPorts();
- status_t initDecoder();
+ void *mCtx;
- void updatePortDefinitions();
+ status_t initDecoder();
DISALLOW_EVIL_CONSTRUCTORS(SoftVPX);
};
diff --git a/media/libstagefright/codecs/on2/enc/Android.mk b/media/libstagefright/codecs/on2/enc/Android.mk
index a92d376..4060a0a 100644
--- a/media/libstagefright/codecs/on2/enc/Android.mk
+++ b/media/libstagefright/codecs/on2/enc/Android.mk
@@ -12,11 +12,16 @@ LOCAL_C_INCLUDES := \
frameworks/av/media/libstagefright/include \
frameworks/native/include/media/openmax \
+ifeq ($(TARGET_DEVICE), manta)
+ LOCAL_CFLAGS += -DSURFACE_IS_BGR32
+endif
+
LOCAL_STATIC_LIBRARIES := \
libvpx
LOCAL_SHARED_LIBRARIES := \
libstagefright libstagefright_omx libstagefright_foundation libutils liblog \
+ libhardware \
LOCAL_MODULE := libstagefright_soft_vpxenc
LOCAL_MODULE_TAGS := optional
diff --git a/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp b/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp
index e25637a..8375cac 100644
--- a/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp
+++ b/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp
@@ -20,6 +20,8 @@
#include <utils/Log.h>
+#include <media/hardware/HardwareAPI.h>
+#include <media/hardware/MetadataBufferType.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/MediaDefs.h>
@@ -81,6 +83,52 @@ inline static void ConvertSemiPlanarToPlanar(uint8_t *inyuv,
}
}
+static void ConvertRGB32ToPlanar(
+ const uint8_t *src, uint8_t *dstY, int32_t width, int32_t height) {
+ CHECK((width & 1) == 0);
+ CHECK((height & 1) == 0);
+
+ uint8_t *dstU = dstY + width * height;
+ uint8_t *dstV = dstU + (width / 2) * (height / 2);
+
+ for (int32_t y = 0; y < height; ++y) {
+ for (int32_t x = 0; x < width; ++x) {
+#ifdef SURFACE_IS_BGR32
+ unsigned blue = src[4 * x];
+ unsigned green = src[4 * x + 1];
+ unsigned red= src[4 * x + 2];
+#else
+ unsigned red= src[4 * x];
+ unsigned green = src[4 * x + 1];
+ unsigned blue = src[4 * x + 2];
+#endif
+
+ unsigned luma =
+ ((red * 66 + green * 129 + blue * 25) >> 8) + 16;
+
+ dstY[x] = luma;
+
+ if ((x & 1) == 0 && (y & 1) == 0) {
+ unsigned U =
+ ((-red * 38 - green * 74 + blue * 112) >> 8) + 128;
+
+ unsigned V =
+ ((red * 112 - green * 94 - blue * 18) >> 8) + 128;
+
+ dstU[x / 2] = U;
+ dstV[x / 2] = V;
+ }
+ }
+
+ if ((y & 1) == 0) {
+ dstU += width / 2;
+ dstV += width / 2;
+ }
+
+ src += 4 * width;
+ dstY += width;
+ }
+}
SoftVPXEncoder::SoftVPXEncoder(const char *name,
const OMX_CALLBACKTYPE *callbacks,
@@ -93,14 +141,17 @@ SoftVPXEncoder::SoftVPXEncoder(const char *name,
mWidth(176),
mHeight(144),
mBitrate(192000), // in bps
+ mBitrateUpdated(false),
mBitrateControlMode(VPX_VBR), // variable bitrate
mFrameDurationUs(33333), // Defaults to 30 fps
mDCTPartitions(0),
mErrorResilience(OMX_FALSE),
mColorFormat(OMX_COLOR_FormatYUV420Planar),
mLevel(OMX_VIDEO_VP8Level_Version0),
- mConversionBuffer(NULL) {
-
+ mConversionBuffer(NULL),
+ mInputDataIsMeta(false),
+ mGrallocModule(NULL),
+ mKeyFrameRequested(false) {
initPorts();
}
@@ -165,8 +216,8 @@ void SoftVPXEncoder::initPorts() {
outputPort.eDir = OMX_DirOutput;
outputPort.nBufferAlignment = kOutputBufferAlignment;
outputPort.format.video.cMIMEType =
- const_cast<char *>(MEDIA_MIMETYPE_VIDEO_VPX);
- outputPort.format.video.eCompressionFormat = OMX_VIDEO_CodingVPX;
+ const_cast<char *>(MEDIA_MIMETYPE_VIDEO_VP8);
+ outputPort.format.video.eCompressionFormat = OMX_VIDEO_CodingVP8;
outputPort.format.video.eColorFormat = OMX_COLOR_FormatUnused;
outputPort.format.video.pNativeWindow = NULL;
outputPort.nBufferSize = 256 * 1024; // arbitrary
@@ -247,7 +298,7 @@ status_t SoftVPXEncoder::initEncoder() {
return UNKNOWN_ERROR;
}
- if (mColorFormat == OMX_COLOR_FormatYUV420SemiPlanar) {
+ if (mColorFormat == OMX_COLOR_FormatYUV420SemiPlanar || mInputDataIsMeta) {
if (mConversionBuffer == NULL) {
mConversionBuffer = (uint8_t *)malloc(mWidth * mHeight * 3 / 2);
if (mConversionBuffer == NULL) {
@@ -315,7 +366,7 @@ OMX_ERRORTYPE SoftVPXEncoder::internalGetParameter(OMX_INDEXTYPE index,
formatParams->xFramerate = (1000000/mFrameDurationUs) << 16;
return OMX_ErrorNone;
} else if (formatParams->nPortIndex == kOutputPortIndex) {
- formatParams->eCompressionFormat = OMX_VIDEO_CodingVPX;
+ formatParams->eCompressionFormat = OMX_VIDEO_CodingVP8;
formatParams->eColorFormat = OMX_COLOR_FormatUnused;
formatParams->xFramerate = 0;
return OMX_ErrorNone;
@@ -427,9 +478,17 @@ OMX_ERRORTYPE SoftVPXEncoder::internalSetParameter(OMX_INDEXTYPE index,
(const OMX_VIDEO_PARAM_BITRATETYPE *)param);
case OMX_IndexParamPortDefinition:
- return internalSetPortParams(
+ {
+ OMX_ERRORTYPE err = internalSetPortParams(
(const OMX_PARAM_PORTDEFINITIONTYPE *)param);
+ if (err != OMX_ErrorNone) {
+ return err;
+ }
+
+ return SimpleSoftOMXComponent::internalSetParameter(index, param);
+ }
+
case OMX_IndexParamVideoPortFormat:
return internalSetFormatParams(
(const OMX_VIDEO_PARAM_PORTFORMATTYPE *)param);
@@ -442,11 +501,63 @@ OMX_ERRORTYPE SoftVPXEncoder::internalSetParameter(OMX_INDEXTYPE index,
return internalSetProfileLevel(
(const OMX_VIDEO_PARAM_PROFILELEVELTYPE *)param);
+ case OMX_IndexVendorStartUnused:
+ {
+ // storeMetaDataInBuffers
+ const StoreMetaDataInBuffersParams *storeParam =
+ (const StoreMetaDataInBuffersParams *)param;
+
+ if (storeParam->nPortIndex != kInputPortIndex) {
+ return OMX_ErrorBadPortIndex;
+ }
+
+ mInputDataIsMeta = (storeParam->bStoreMetaData == OMX_TRUE);
+
+ return OMX_ErrorNone;
+ }
+
default:
return SimpleSoftOMXComponent::internalSetParameter(index, param);
}
}
+OMX_ERRORTYPE SoftVPXEncoder::setConfig(
+ OMX_INDEXTYPE index, const OMX_PTR _params) {
+ switch (index) {
+ case OMX_IndexConfigVideoIntraVOPRefresh:
+ {
+ OMX_CONFIG_INTRAREFRESHVOPTYPE *params =
+ (OMX_CONFIG_INTRAREFRESHVOPTYPE *)_params;
+
+ if (params->nPortIndex != kOutputPortIndex) {
+ return OMX_ErrorBadPortIndex;
+ }
+
+ mKeyFrameRequested = params->IntraRefreshVOP;
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexConfigVideoBitrate:
+ {
+ OMX_VIDEO_CONFIG_BITRATETYPE *params =
+ (OMX_VIDEO_CONFIG_BITRATETYPE *)_params;
+
+ if (params->nPortIndex != kOutputPortIndex) {
+ return OMX_ErrorBadPortIndex;
+ }
+
+ if (mBitrate != params->nEncodeBitrate) {
+ mBitrate = params->nEncodeBitrate;
+ mBitrateUpdated = true;
+ }
+ return OMX_ErrorNone;
+ }
+
+ default:
+ return SimpleSoftOMXComponent::setConfig(index, _params);
+ }
+}
+
OMX_ERRORTYPE SoftVPXEncoder::internalSetProfileLevel(
const OMX_VIDEO_PARAM_PROFILELEVELTYPE* profileAndLevel) {
if (profileAndLevel->nPortIndex != kOutputPortIndex) {
@@ -507,13 +618,17 @@ OMX_ERRORTYPE SoftVPXEncoder::internalSetFormatParams(
format->eColorFormat == OMX_COLOR_FormatYUV420SemiPlanar ||
format->eColorFormat == OMX_COLOR_FormatAndroidOpaque) {
mColorFormat = format->eColorFormat;
+
+ OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kInputPortIndex)->mDef;
+ def->format.video.eColorFormat = mColorFormat;
+
return OMX_ErrorNone;
} else {
ALOGE("Unsupported color format %i", format->eColorFormat);
return OMX_ErrorUnsupportedSetting;
}
} else if (format->nPortIndex == kOutputPortIndex) {
- if (format->eCompressionFormat == OMX_VIDEO_CodingVPX) {
+ if (format->eCompressionFormat == OMX_VIDEO_CodingVP8) {
return OMX_ErrorNone;
} else {
return OMX_ErrorUnsupportedSetting;
@@ -529,7 +644,7 @@ OMX_ERRORTYPE SoftVPXEncoder::internalSetRoleParams(
const char* roleText = (const char*)role->cRole;
const size_t roleTextMaxSize = OMX_MAX_STRINGNAME_SIZE - 1;
- if (strncmp(roleText, "video_encoder.vpx", roleTextMaxSize)) {
+ if (strncmp(roleText, "video_encoder.vp8", roleTextMaxSize)) {
ALOGE("Unsupported component role");
return OMX_ErrorBadParameter;
}
@@ -552,11 +667,17 @@ OMX_ERRORTYPE SoftVPXEncoder::internalSetPortParams(
if (port->format.video.eColorFormat == OMX_COLOR_FormatYUV420Planar ||
port->format.video.eColorFormat == OMX_COLOR_FormatYUV420SemiPlanar ||
port->format.video.eColorFormat == OMX_COLOR_FormatAndroidOpaque) {
- mColorFormat = port->format.video.eColorFormat;
+ mColorFormat = port->format.video.eColorFormat;
} else {
return OMX_ErrorUnsupportedSetting;
}
+ OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kInputPortIndex)->mDef;
+ def->format.video.nFrameWidth = mWidth;
+ def->format.video.nFrameHeight = mHeight;
+ def->format.video.xFramerate = port->format.video.xFramerate;
+ def->format.video.eColorFormat = mColorFormat;
+
return OMX_ErrorNone;
} else if (port->nPortIndex == kOutputPortIndex) {
mBitrate = port->format.video.nBitrate;
@@ -625,24 +746,78 @@ void SoftVPXEncoder::onQueueFilled(OMX_U32 portIndex) {
return;
}
- uint8_t* source = inputBufferHeader->pBuffer + inputBufferHeader->nOffset;
+ uint8_t *source =
+ inputBufferHeader->pBuffer + inputBufferHeader->nOffset;
+
+ if (mInputDataIsMeta) {
+ CHECK_GE(inputBufferHeader->nFilledLen,
+ 4 + sizeof(buffer_handle_t));
+
+ uint32_t bufferType = *(uint32_t *)source;
+ CHECK_EQ(bufferType, kMetadataBufferTypeGrallocSource);
+
+ if (mGrallocModule == NULL) {
+ CHECK_EQ(0, hw_get_module(
+ GRALLOC_HARDWARE_MODULE_ID, &mGrallocModule));
+ }
+
+ const gralloc_module_t *grmodule =
+ (const gralloc_module_t *)mGrallocModule;
+
+ buffer_handle_t handle = *(buffer_handle_t *)(source + 4);
+
+ void *bits;
+ CHECK_EQ(0,
+ grmodule->lock(
+ grmodule, handle,
+ GRALLOC_USAGE_SW_READ_OFTEN
+ | GRALLOC_USAGE_SW_WRITE_NEVER,
+ 0, 0, mWidth, mHeight, &bits));
+
+ ConvertRGB32ToPlanar(
+ (const uint8_t *)bits, mConversionBuffer, mWidth, mHeight);
+
+ source = mConversionBuffer;
+
+ CHECK_EQ(0, grmodule->unlock(grmodule, handle));
+ } else if (mColorFormat == OMX_COLOR_FormatYUV420SemiPlanar) {
+ ConvertSemiPlanarToPlanar(
+ source, mConversionBuffer, mWidth, mHeight);
- // NOTE: As much as nothing is known about color format
- // when it is denoted as AndroidOpaque, it is at least
- // assumed to be planar.
- if (mColorFormat == OMX_COLOR_FormatYUV420SemiPlanar) {
- ConvertSemiPlanarToPlanar(source, mConversionBuffer, mWidth, mHeight);
source = mConversionBuffer;
}
vpx_image_t raw_frame;
vpx_img_wrap(&raw_frame, VPX_IMG_FMT_I420, mWidth, mHeight,
kInputBufferAlignment, source);
- codec_return = vpx_codec_encode(mCodecContext,
- &raw_frame,
- inputBufferHeader->nTimeStamp, // in timebase units
- mFrameDurationUs, // frame duration in timebase units
- 0, // frame flags
- VPX_DL_REALTIME); // encoding deadline
+
+ vpx_enc_frame_flags_t flags = 0;
+ if (mKeyFrameRequested) {
+ flags |= VPX_EFLAG_FORCE_KF;
+ mKeyFrameRequested = false;
+ }
+
+ if (mBitrateUpdated) {
+ mCodecConfiguration->rc_target_bitrate = mBitrate/1000;
+ vpx_codec_err_t res = vpx_codec_enc_config_set(mCodecContext,
+ mCodecConfiguration);
+ if (res != VPX_CODEC_OK) {
+ ALOGE("vp8 encoder failed to update bitrate: %s",
+ vpx_codec_err_to_string(res));
+ notify(OMX_EventError,
+ OMX_ErrorUndefined,
+ 0, // Extra notification data
+ NULL); // Notification data pointer
+ }
+ mBitrateUpdated = false;
+ }
+
+ codec_return = vpx_codec_encode(
+ mCodecContext,
+ &raw_frame,
+ inputBufferHeader->nTimeStamp, // in timebase units
+ mFrameDurationUs, // frame duration in timebase units
+ flags, // frame flags
+ VPX_DL_REALTIME); // encoding deadline
if (codec_return != VPX_CODEC_OK) {
ALOGE("vpx encoder failed to encode frame");
notify(OMX_EventError,
@@ -660,6 +835,8 @@ void SoftVPXEncoder::onQueueFilled(OMX_U32 portIndex) {
if (encoded_packet->kind == VPX_CODEC_CX_FRAME_PKT) {
outputBufferHeader->nTimeStamp = encoded_packet->data.frame.pts;
outputBufferHeader->nFlags = 0;
+ if (encoded_packet->data.frame.flags & VPX_FRAME_IS_KEY)
+ outputBufferHeader->nFlags |= OMX_BUFFERFLAG_SYNCFRAME;
outputBufferHeader->nOffset = 0;
outputBufferHeader->nFilledLen = encoded_packet->data.frame.sz;
memcpy(outputBufferHeader->pBuffer,
@@ -676,6 +853,17 @@ void SoftVPXEncoder::onQueueFilled(OMX_U32 portIndex) {
notifyEmptyBufferDone(inputBufferHeader);
}
}
+
+OMX_ERRORTYPE SoftVPXEncoder::getExtensionIndex(
+ const char *name, OMX_INDEXTYPE *index) {
+ if (!strcmp(name, "OMX.google.android.index.storeMetaDataInBuffers")) {
+ *index = OMX_IndexVendorStartUnused;
+ return OMX_ErrorNone;
+ }
+
+ return SimpleSoftOMXComponent::getExtensionIndex(name, index);
+}
+
} // namespace android
diff --git a/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.h b/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.h
index 3bc05c0..076830f 100644
--- a/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.h
+++ b/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.h
@@ -23,6 +23,8 @@
#include <OMX_VideoExt.h>
#include <OMX_IndexExt.h>
+#include <hardware/gralloc.h>
+
#include "vpx/vpx_encoder.h"
#include "vpx/vpx_codec.h"
#include "vpx/vp8cx.h"
@@ -57,14 +59,13 @@ namespace android {
// - OMX timestamps are in microseconds, therefore
// encoder timebase is fixed to 1/1000000
-class SoftVPXEncoder : public SimpleSoftOMXComponent {
- public:
+struct SoftVPXEncoder : public SimpleSoftOMXComponent {
SoftVPXEncoder(const char *name,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
OMX_COMPONENTTYPE **component);
- protected:
+protected:
virtual ~SoftVPXEncoder();
// Returns current values for requested OMX
@@ -77,13 +78,19 @@ class SoftVPXEncoder : public SimpleSoftOMXComponent {
virtual OMX_ERRORTYPE internalSetParameter(
OMX_INDEXTYPE index, const OMX_PTR param);
+ virtual OMX_ERRORTYPE setConfig(
+ OMX_INDEXTYPE index, const OMX_PTR params);
+
// OMX callback when buffers available
// Note that both an input and output buffer
// is expected to be available to carry out
// encoding of the frame
virtual void onQueueFilled(OMX_U32 portIndex);
- private:
+ virtual OMX_ERRORTYPE getExtensionIndex(
+ const char *name, OMX_INDEXTYPE *index);
+
+private:
// number of buffers allocated per port
static const uint32_t kNumBuffers = 4;
@@ -121,7 +128,10 @@ class SoftVPXEncoder : public SimpleSoftOMXComponent {
int32_t mHeight;
// Target bitrate set for the encoder, in bits per second.
- int32_t mBitrate;
+ uint32_t mBitrate;
+
+ // If a request for a change it bitrate has been received.
+ bool mBitrateUpdated;
// Bitrate control mode, either constant or variable
vpx_rc_mode mBitrateControlMode;
@@ -156,6 +166,11 @@ class SoftVPXEncoder : public SimpleSoftOMXComponent {
// indeed YUV420SemiPlanar.
uint8_t* mConversionBuffer;
+ bool mInputDataIsMeta;
+ const hw_module_t *mGrallocModule;
+
+ bool mKeyFrameRequested;
+
// Initializes input and output OMX ports with sensible
// default values.
void initPorts();
@@ -175,7 +190,7 @@ class SoftVPXEncoder : public SimpleSoftOMXComponent {
const OMX_VIDEO_PARAM_PORTFORMATTYPE* format);
// Verifies the component role tried to be set to this OMX component is
- // strictly video_encoder.vpx
+ // strictly video_encoder.vp8
OMX_ERRORTYPE internalSetRoleParams(
const OMX_PARAM_COMPONENTROLETYPE* role);
diff --git a/media/libstagefright/codecs/on2/h264dec/Android.mk b/media/libstagefright/codecs/on2/h264dec/Android.mk
index 2539f98..655b2ab 100644
--- a/media/libstagefright/codecs/on2/h264dec/Android.mk
+++ b/media/libstagefright/codecs/on2/h264dec/Android.mk
@@ -119,7 +119,7 @@ LOCAL_C_INCLUDES := $(LOCAL_PATH)/inc
LOCAL_SHARED_LIBRARIES := libstagefright_soft_h264dec
-LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := decoder
diff --git a/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
index 6e36651..7ddb13c 100644
--- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
+++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
@@ -47,38 +47,28 @@ static const CodecProfileLevel kProfileLevels[] = {
{ OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel51 },
};
-template<class T>
-static void InitOMXParams(T *params) {
- params->nSize = sizeof(T);
- params->nVersion.s.nVersionMajor = 1;
- params->nVersion.s.nVersionMinor = 0;
- params->nVersion.s.nRevision = 0;
- params->nVersion.s.nStep = 0;
-}
-
SoftAVC::SoftAVC(
const char *name,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
OMX_COMPONENTTYPE **component)
- : SimpleSoftOMXComponent(name, callbacks, appData, component),
+ : SoftVideoDecoderOMXComponent(
+ name, "video_decoder.avc", OMX_VIDEO_CodingAVC,
+ kProfileLevels, ARRAY_SIZE(kProfileLevels),
+ 320 /* width */, 240 /* height */, callbacks, appData, component),
mHandle(NULL),
mInputBufferCount(0),
- mWidth(320),
- mHeight(240),
mPictureSize(mWidth * mHeight * 3 / 2),
- mCropLeft(0),
- mCropTop(0),
- mCropWidth(mWidth),
- mCropHeight(mHeight),
mFirstPicture(NULL),
mFirstPictureId(-1),
mPicId(0),
mHeadersDecoded(false),
mEOSStatus(INPUT_DATA_AVAILABLE),
- mOutputPortSettingsChange(NONE),
mSignalledError(false) {
- initPorts();
+ initPorts(
+ kNumInputBuffers, 8192 /* inputBufferSize */,
+ kNumOutputBuffers, MEDIA_MIMETYPE_VIDEO_AVC);
+
CHECK_EQ(initDecoder(), (status_t)OK);
}
@@ -100,65 +90,6 @@ SoftAVC::~SoftAVC() {
delete[] mFirstPicture;
}
-void SoftAVC::initPorts() {
- OMX_PARAM_PORTDEFINITIONTYPE def;
- InitOMXParams(&def);
-
- def.nPortIndex = kInputPortIndex;
- def.eDir = OMX_DirInput;
- def.nBufferCountMin = kNumInputBuffers;
- def.nBufferCountActual = def.nBufferCountMin;
- def.nBufferSize = 8192;
- def.bEnabled = OMX_TRUE;
- def.bPopulated = OMX_FALSE;
- def.eDomain = OMX_PortDomainVideo;
- def.bBuffersContiguous = OMX_FALSE;
- def.nBufferAlignment = 1;
-
- def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_AVC);
- def.format.video.pNativeRender = NULL;
- def.format.video.nFrameWidth = mWidth;
- def.format.video.nFrameHeight = mHeight;
- def.format.video.nStride = def.format.video.nFrameWidth;
- def.format.video.nSliceHeight = def.format.video.nFrameHeight;
- def.format.video.nBitrate = 0;
- def.format.video.xFramerate = 0;
- def.format.video.bFlagErrorConcealment = OMX_FALSE;
- def.format.video.eCompressionFormat = OMX_VIDEO_CodingAVC;
- def.format.video.eColorFormat = OMX_COLOR_FormatUnused;
- def.format.video.pNativeWindow = NULL;
-
- addPort(def);
-
- def.nPortIndex = kOutputPortIndex;
- def.eDir = OMX_DirOutput;
- def.nBufferCountMin = kNumOutputBuffers;
- def.nBufferCountActual = def.nBufferCountMin;
- def.bEnabled = OMX_TRUE;
- def.bPopulated = OMX_FALSE;
- def.eDomain = OMX_PortDomainVideo;
- def.bBuffersContiguous = OMX_FALSE;
- def.nBufferAlignment = 2;
-
- def.format.video.cMIMEType = const_cast<char *>(MEDIA_MIMETYPE_VIDEO_RAW);
- def.format.video.pNativeRender = NULL;
- def.format.video.nFrameWidth = mWidth;
- def.format.video.nFrameHeight = mHeight;
- def.format.video.nStride = def.format.video.nFrameWidth;
- def.format.video.nSliceHeight = def.format.video.nFrameHeight;
- def.format.video.nBitrate = 0;
- def.format.video.xFramerate = 0;
- def.format.video.bFlagErrorConcealment = OMX_FALSE;
- def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused;
- def.format.video.eColorFormat = OMX_COLOR_FormatYUV420Planar;
- def.format.video.pNativeWindow = NULL;
-
- def.nBufferSize =
- (def.format.video.nFrameWidth * def.format.video.nFrameHeight * 3) / 2;
-
- addPort(def);
-}
-
status_t SoftAVC::initDecoder() {
// Force decoder to output buffers in display order.
if (H264SwDecInit(&mHandle, 0) == H264SWDEC_OK) {
@@ -167,126 +98,6 @@ status_t SoftAVC::initDecoder() {
return UNKNOWN_ERROR;
}
-OMX_ERRORTYPE SoftAVC::internalGetParameter(
- OMX_INDEXTYPE index, OMX_PTR params) {
- switch (index) {
- case OMX_IndexParamVideoPortFormat:
- {
- OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams =
- (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params;
-
- if (formatParams->nPortIndex > kOutputPortIndex) {
- return OMX_ErrorUndefined;
- }
-
- if (formatParams->nIndex != 0) {
- return OMX_ErrorNoMore;
- }
-
- if (formatParams->nPortIndex == kInputPortIndex) {
- formatParams->eCompressionFormat = OMX_VIDEO_CodingAVC;
- formatParams->eColorFormat = OMX_COLOR_FormatUnused;
- formatParams->xFramerate = 0;
- } else {
- CHECK(formatParams->nPortIndex == kOutputPortIndex);
-
- formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused;
- formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar;
- formatParams->xFramerate = 0;
- }
-
- return OMX_ErrorNone;
- }
-
- case OMX_IndexParamVideoProfileLevelQuerySupported:
- {
- OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileLevel =
- (OMX_VIDEO_PARAM_PROFILELEVELTYPE *) params;
-
- if (profileLevel->nPortIndex != kInputPortIndex) {
- ALOGE("Invalid port index: %ld", profileLevel->nPortIndex);
- return OMX_ErrorUnsupportedIndex;
- }
-
- size_t index = profileLevel->nProfileIndex;
- size_t nProfileLevels =
- sizeof(kProfileLevels) / sizeof(kProfileLevels[0]);
- if (index >= nProfileLevels) {
- return OMX_ErrorNoMore;
- }
-
- profileLevel->eProfile = kProfileLevels[index].mProfile;
- profileLevel->eLevel = kProfileLevels[index].mLevel;
- return OMX_ErrorNone;
- }
-
- default:
- return SimpleSoftOMXComponent::internalGetParameter(index, params);
- }
-}
-
-OMX_ERRORTYPE SoftAVC::internalSetParameter(
- OMX_INDEXTYPE index, const OMX_PTR params) {
- switch (index) {
- case OMX_IndexParamStandardComponentRole:
- {
- const OMX_PARAM_COMPONENTROLETYPE *roleParams =
- (const OMX_PARAM_COMPONENTROLETYPE *)params;
-
- if (strncmp((const char *)roleParams->cRole,
- "video_decoder.avc",
- OMX_MAX_STRINGNAME_SIZE - 1)) {
- return OMX_ErrorUndefined;
- }
-
- return OMX_ErrorNone;
- }
-
- case OMX_IndexParamVideoPortFormat:
- {
- OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams =
- (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params;
-
- if (formatParams->nPortIndex > kOutputPortIndex) {
- return OMX_ErrorUndefined;
- }
-
- if (formatParams->nIndex != 0) {
- return OMX_ErrorNoMore;
- }
-
- return OMX_ErrorNone;
- }
-
- default:
- return SimpleSoftOMXComponent::internalSetParameter(index, params);
- }
-}
-
-OMX_ERRORTYPE SoftAVC::getConfig(
- OMX_INDEXTYPE index, OMX_PTR params) {
- switch (index) {
- case OMX_IndexConfigCommonOutputCrop:
- {
- OMX_CONFIG_RECTTYPE *rectParams = (OMX_CONFIG_RECTTYPE *)params;
-
- if (rectParams->nPortIndex != 1) {
- return OMX_ErrorUndefined;
- }
-
- rectParams->nLeft = mCropLeft;
- rectParams->nTop = mCropTop;
- rectParams->nWidth = mCropWidth;
- rectParams->nHeight = mCropHeight;
-
- return OMX_ErrorNone;
- }
-
- default:
- return OMX_ErrorUnsupportedIndex;
- }
-}
-
void SoftAVC::onQueueFilled(OMX_U32 portIndex) {
if (mSignalledError || mOutputPortSettingsChange != NONE) {
return;
@@ -298,13 +109,21 @@ void SoftAVC::onQueueFilled(OMX_U32 portIndex) {
List<BufferInfo *> &inQueue = getPortQueue(kInputPortIndex);
List<BufferInfo *> &outQueue = getPortQueue(kOutputPortIndex);
+
+ if (mHeadersDecoded) {
+ // Dequeue any already decoded output frames to free up space
+ // in the output queue.
+
+ drainAllOutputBuffers(false /* eos */);
+ }
+
H264SwDecRet ret = H264SWDEC_PIC_RDY;
bool portSettingsChanged = false;
while ((mEOSStatus != INPUT_DATA_AVAILABLE || !inQueue.empty())
&& outQueue.size() == kNumOutputBuffers) {
if (mEOSStatus == INPUT_EOS_SEEN) {
- drainAllOutputBuffers();
+ drainAllOutputBuffers(true /* eos */);
return;
}
@@ -392,15 +211,7 @@ void SoftAVC::onQueueFilled(OMX_U32 portIndex) {
mFirstPictureId = -1;
}
- while (!outQueue.empty() &&
- mHeadersDecoded &&
- H264SwDecNextPicture(mHandle, &decodedPicture, 0)
- == H264SWDEC_PIC_RDY) {
-
- int32_t picId = decodedPicture.picId;
- uint8_t *data = (uint8_t *) decodedPicture.pOutputPicture;
- drainOneOutputBuffer(picId, data);
- }
+ drainAllOutputBuffers(false /* eos */);
}
}
@@ -409,8 +220,6 @@ bool SoftAVC::handlePortSettingChangeEvent(const H264SwDecInfo *info) {
mWidth = info->picWidth;
mHeight = info->picHeight;
mPictureSize = mWidth * mHeight * 3 / 2;
- mCropWidth = mWidth;
- mCropHeight = mHeight;
updatePortDefinitions();
notify(OMX_EventPortSettingsChanged, 1, 0, NULL);
mOutputPortSettingsChange = AWAITING_DISABLED;
@@ -463,43 +272,38 @@ void SoftAVC::drainOneOutputBuffer(int32_t picId, uint8_t* data) {
notifyFillBufferDone(outHeader);
}
-bool SoftAVC::drainAllOutputBuffers() {
+void SoftAVC::drainAllOutputBuffers(bool eos) {
List<BufferInfo *> &outQueue = getPortQueue(kOutputPortIndex);
H264SwDecPicture decodedPicture;
+ if (mHeadersDecoded) {
+ while (!outQueue.empty()
+ && H264SWDEC_PIC_RDY == H264SwDecNextPicture(
+ mHandle, &decodedPicture, eos /* flush */)) {
+ int32_t picId = decodedPicture.picId;
+ uint8_t *data = (uint8_t *) decodedPicture.pOutputPicture;
+ drainOneOutputBuffer(picId, data);
+ }
+ }
+
+ if (!eos) {
+ return;
+ }
+
while (!outQueue.empty()) {
BufferInfo *outInfo = *outQueue.begin();
outQueue.erase(outQueue.begin());
OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader;
- if (mHeadersDecoded &&
- H264SWDEC_PIC_RDY ==
- H264SwDecNextPicture(mHandle, &decodedPicture, 1 /* flush */)) {
- int32_t picId = decodedPicture.picId;
- CHECK(mPicToHeaderMap.indexOfKey(picId) >= 0);
-
- memcpy(outHeader->pBuffer + outHeader->nOffset,
- decodedPicture.pOutputPicture,
- mPictureSize);
-
- OMX_BUFFERHEADERTYPE *header = mPicToHeaderMap.valueFor(picId);
- outHeader->nTimeStamp = header->nTimeStamp;
- outHeader->nFlags = header->nFlags;
- outHeader->nFilledLen = mPictureSize;
- mPicToHeaderMap.removeItem(picId);
- delete header;
- } else {
- outHeader->nTimeStamp = 0;
- outHeader->nFilledLen = 0;
- outHeader->nFlags = OMX_BUFFERFLAG_EOS;
- mEOSStatus = OUTPUT_FRAMES_FLUSHED;
- }
+ outHeader->nTimeStamp = 0;
+ outHeader->nFilledLen = 0;
+ outHeader->nFlags = OMX_BUFFERFLAG_EOS;
outInfo->mOwnedByUs = false;
notifyFillBufferDone(outHeader);
- }
- return true;
+ mEOSStatus = OUTPUT_FRAMES_FLUSHED;
+ }
}
void SoftAVC::onPortFlushCompleted(OMX_U32 portIndex) {
@@ -508,44 +312,9 @@ void SoftAVC::onPortFlushCompleted(OMX_U32 portIndex) {
}
}
-void SoftAVC::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
- switch (mOutputPortSettingsChange) {
- case NONE:
- break;
-
- case AWAITING_DISABLED:
- {
- CHECK(!enabled);
- mOutputPortSettingsChange = AWAITING_ENABLED;
- break;
- }
-
- default:
- {
- CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED);
- CHECK(enabled);
- mOutputPortSettingsChange = NONE;
- break;
- }
- }
-}
-
-void SoftAVC::updatePortDefinitions() {
- OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(0)->mDef;
- def->format.video.nFrameWidth = mWidth;
- def->format.video.nFrameHeight = mHeight;
- def->format.video.nStride = def->format.video.nFrameWidth;
- def->format.video.nSliceHeight = def->format.video.nFrameHeight;
-
- def = &editPortInfo(1)->mDef;
- def->format.video.nFrameWidth = mWidth;
- def->format.video.nFrameHeight = mHeight;
- def->format.video.nStride = def->format.video.nFrameWidth;
- def->format.video.nSliceHeight = def->format.video.nFrameHeight;
-
- def->nBufferSize =
- (def->format.video.nFrameWidth
- * def->format.video.nFrameHeight * 3) / 2;
+void SoftAVC::onReset() {
+ SoftVideoDecoderOMXComponent::onReset();
+ mSignalledError = false;
}
} // namespace android
diff --git a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
index 879b014..ee69926 100644
--- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
+++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
@@ -18,7 +18,7 @@
#define SOFT_AVC_H_
-#include "SimpleSoftOMXComponent.h"
+#include "SoftVideoDecoderOMXComponent.h"
#include <utils/KeyedVector.h>
#include "H264SwDecApi.h"
@@ -26,7 +26,7 @@
namespace android {
-struct SoftAVC : public SimpleSoftOMXComponent {
+struct SoftAVC : public SoftVideoDecoderOMXComponent {
SoftAVC(const char *name,
const OMX_CALLBACKTYPE *callbacks,
OMX_PTR appData,
@@ -35,22 +35,12 @@ struct SoftAVC : public SimpleSoftOMXComponent {
protected:
virtual ~SoftAVC();
- virtual OMX_ERRORTYPE internalGetParameter(
- OMX_INDEXTYPE index, OMX_PTR params);
-
- virtual OMX_ERRORTYPE internalSetParameter(
- OMX_INDEXTYPE index, const OMX_PTR params);
-
- virtual OMX_ERRORTYPE getConfig(OMX_INDEXTYPE index, OMX_PTR params);
-
virtual void onQueueFilled(OMX_U32 portIndex);
virtual void onPortFlushCompleted(OMX_U32 portIndex);
- virtual void onPortEnableCompleted(OMX_U32 portIndex, bool enabled);
+ virtual void onReset();
private:
enum {
- kInputPortIndex = 0,
- kOutputPortIndex = 1,
kNumInputBuffers = 8,
kNumOutputBuffers = 2,
};
@@ -65,9 +55,7 @@ private:
size_t mInputBufferCount;
- uint32_t mWidth, mHeight, mPictureSize;
- uint32_t mCropLeft, mCropTop;
- uint32_t mCropWidth, mCropHeight;
+ uint32_t mPictureSize;
uint8_t *mFirstPicture;
int32_t mFirstPictureId;
@@ -81,19 +69,10 @@ private:
EOSStatus mEOSStatus;
- enum OutputPortSettingChange {
- NONE,
- AWAITING_DISABLED,
- AWAITING_ENABLED
- };
- OutputPortSettingChange mOutputPortSettingsChange;
-
bool mSignalledError;
- void initPorts();
status_t initDecoder();
- void updatePortDefinitions();
- bool drainAllOutputBuffers();
+ void drainAllOutputBuffers(bool eos);
void drainOneOutputBuffer(int32_t picId, uint8_t *data);
void saveFirstOutputBuffer(int32_t pidId, uint8_t *data);
bool handleCropRectEvent(const CropParams* crop);
diff --git a/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp b/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp
index 4115324..51bb958 100644
--- a/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp
+++ b/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp
@@ -424,6 +424,8 @@ void SoftVorbis::onReset() {
delete mVi;
mVi = NULL;
}
+
+ mOutputPortSettingsChange = NONE;
}
void SoftVorbis::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
diff --git a/media/libstagefright/foundation/AHierarchicalStateMachine.cpp b/media/libstagefright/foundation/AHierarchicalStateMachine.cpp
index 40c5a3c..f7a00d8 100644
--- a/media/libstagefright/foundation/AHierarchicalStateMachine.cpp
+++ b/media/libstagefright/foundation/AHierarchicalStateMachine.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+//#define LOG_NDEBUG 0
+#define LOG_TAG "AHierarchicalStateMachine"
+#include <utils/Log.h>
+
#include <media/stagefright/foundation/AHierarchicalStateMachine.h>
#include <media/stagefright/foundation/ADebug.h>
diff --git a/media/libstagefright/foundation/ALooper.cpp b/media/libstagefright/foundation/ALooper.cpp
index 22777a2..ebf9d8d 100644
--- a/media/libstagefright/foundation/ALooper.cpp
+++ b/media/libstagefright/foundation/ALooper.cpp
@@ -72,6 +72,10 @@ ALooper::ALooper()
ALooper::~ALooper() {
stop();
+
+ // Since this looper is "dead" (or as good as dead by now),
+ // have ALooperRoster unregister any handlers still registered for it.
+ gLooperRoster.unregisterStaleHandlers();
}
void ALooper::setName(const char *name) {
diff --git a/media/libstagefright/foundation/ALooperRoster.cpp b/media/libstagefright/foundation/ALooperRoster.cpp
index ad10d2b..0c181ff 100644
--- a/media/libstagefright/foundation/ALooperRoster.cpp
+++ b/media/libstagefright/foundation/ALooperRoster.cpp
@@ -71,6 +71,20 @@ void ALooperRoster::unregisterHandler(ALooper::handler_id handlerID) {
mHandlers.removeItemsAt(index);
}
+void ALooperRoster::unregisterStaleHandlers() {
+ Mutex::Autolock autoLock(mLock);
+
+ for (size_t i = mHandlers.size(); i-- > 0;) {
+ const HandlerInfo &info = mHandlers.valueAt(i);
+
+ sp<ALooper> looper = info.mLooper.promote();
+ if (looper == NULL) {
+ ALOGV("Unregistering stale handler %d", mHandlers.keyAt(i));
+ mHandlers.removeItemsAt(i);
+ }
+ }
+}
+
status_t ALooperRoster::postMessage(
const sp<AMessage> &msg, int64_t delayUs) {
Mutex::Autolock autoLock(mLock);
diff --git a/media/libstagefright/wifi-display/ANetworkSession.cpp b/media/libstagefright/foundation/ANetworkSession.cpp
index 938d601..e629588 100644
--- a/media/libstagefright/wifi-display/ANetworkSession.cpp
+++ b/media/libstagefright/foundation/ANetworkSession.cpp
@@ -34,10 +34,21 @@
#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 uint16_t U16_AT(const uint8_t *ptr) {
+ return ptr[0] << 8 | ptr[1];
+}
+
+static uint32_t U32_AT(const uint8_t *ptr) {
+ return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
+}
+
+static uint64_t U64_AT(const uint8_t *ptr) {
+ return ((uint64_t)U32_AT(ptr)) << 32 | U32_AT(ptr + 4);
+}
+
static const size_t kMaxUDPSize = 1500;
static const int32_t kMaxUDPRetries = 200;
@@ -56,6 +67,12 @@ private:
};
struct ANetworkSession::Session : public RefBase {
+ enum Mode {
+ MODE_RTSP,
+ MODE_DATAGRAM,
+ MODE_WEBSOCKET,
+ };
+
enum State {
CONNECTING,
CONNECTED,
@@ -85,7 +102,9 @@ struct ANetworkSession::Session : public RefBase {
status_t sendRequest(
const void *data, ssize_t size, bool timeValid, int64_t timeUs);
- void setIsRTSPConnection(bool yesno);
+ void setMode(Mode mode);
+
+ status_t switchToWebSocketMode();
protected:
virtual ~Session();
@@ -102,7 +121,7 @@ private:
int32_t mSessionID;
State mState;
- bool mIsRTSPConnection;
+ Mode mMode;
int mSocket;
sp<AMessage> mNotify;
bool mSawReceiveFailure, mSawSendFailure;
@@ -145,7 +164,7 @@ ANetworkSession::Session::Session(
const sp<AMessage> &notify)
: mSessionID(sessionID),
mState(state),
- mIsRTSPConnection(false),
+ mMode(MODE_DATAGRAM),
mSocket(s),
mNotify(notify),
mSawReceiveFailure(false),
@@ -209,8 +228,18 @@ int ANetworkSession::Session::socket() const {
return mSocket;
}
-void ANetworkSession::Session::setIsRTSPConnection(bool yesno) {
- mIsRTSPConnection = yesno;
+void ANetworkSession::Session::setMode(Mode mode) {
+ mMode = mode;
+}
+
+status_t ANetworkSession::Session::switchToWebSocketMode() {
+ if (mState != CONNECTED || mMode != MODE_RTSP) {
+ return INVALID_OPERATION;
+ }
+
+ mMode = MODE_WEBSOCKET;
+
+ return OK;
}
sp<AMessage> ANetworkSession::Session::getNotificationMessage() const {
@@ -238,6 +267,8 @@ bool ANetworkSession::Session::wantsToWrite() {
status_t ANetworkSession::Session::readMore() {
if (mState == DATAGRAM) {
+ CHECK_EQ(mMode, MODE_DATAGRAM);
+
status_t err;
do {
sp<ABuffer> buf = new ABuffer(kMaxUDPSize);
@@ -326,7 +357,7 @@ status_t ANetworkSession::Session::readMore() {
err = -ECONNRESET;
}
- if (!mIsRTSPConnection) {
+ if (mMode == MODE_DATAGRAM) {
// TCP stream carrying 16-bit length-prefixed datagrams.
while (mInBuffer.size() >= 2) {
@@ -350,7 +381,7 @@ status_t ANetworkSession::Session::readMore() {
mInBuffer.erase(0, packetSize + 2);
}
- } else {
+ } else if (mMode == MODE_RTSP) {
for (;;) {
size_t length;
@@ -417,6 +448,69 @@ status_t ANetworkSession::Session::readMore() {
break;
}
}
+ } else {
+ CHECK_EQ(mMode, MODE_WEBSOCKET);
+
+ const uint8_t *data = (const uint8_t *)mInBuffer.c_str();
+ // hexdump(data, mInBuffer.size());
+
+ while (mInBuffer.size() >= 2) {
+ size_t offset = 2;
+
+ unsigned payloadLen = data[1] & 0x7f;
+ if (payloadLen == 126) {
+ if (offset + 2 > mInBuffer.size()) {
+ break;
+ }
+
+ payloadLen = U16_AT(&data[offset]);
+ offset += 2;
+ } else if (payloadLen == 127) {
+ if (offset + 8 > mInBuffer.size()) {
+ break;
+ }
+
+ payloadLen = U64_AT(&data[offset]);
+ offset += 8;
+ }
+
+ uint32_t mask = 0;
+ if (data[1] & 0x80) {
+ // MASK==1
+ if (offset + 4 > mInBuffer.size()) {
+ break;
+ }
+
+ mask = U32_AT(&data[offset]);
+ offset += 4;
+ }
+
+ if (offset + payloadLen > mInBuffer.size()) {
+ break;
+ }
+
+ // We have the full message.
+
+ sp<ABuffer> packet = new ABuffer(payloadLen);
+ memcpy(packet->data(), &data[offset], payloadLen);
+
+ if (mask != 0) {
+ for (size_t i = 0; i < payloadLen; ++i) {
+ packet->data()[i] =
+ data[offset + i]
+ ^ ((mask >> (8 * (3 - (i % 4)))) & 0xff);
+ }
+ }
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("sessionID", mSessionID);
+ notify->setInt32("reason", kWhatWebSocketMessage);
+ notify->setBuffer("data", packet);
+ notify->setInt32("headerByte", data[0]);
+ notify->post();
+
+ mInBuffer.erase(0, offset + payloadLen);
+ }
}
if (err != OK) {
@@ -608,13 +702,61 @@ status_t ANetworkSession::Session::sendRequest(
sp<ABuffer> buffer;
- if (mState == CONNECTED && !mIsRTSPConnection) {
+ if (mState == CONNECTED && mMode == MODE_DATAGRAM) {
CHECK_LE(size, 65535);
buffer = new ABuffer(size + 2);
buffer->data()[0] = size >> 8;
buffer->data()[1] = size & 0xff;
memcpy(buffer->data() + 2, data, size);
+ } else if (mState == CONNECTED && mMode == MODE_WEBSOCKET) {
+ static const bool kUseMask = false; // Chromium doesn't like it.
+
+ size_t numHeaderBytes = 2 + (kUseMask ? 4 : 0);
+ if (size > 65535) {
+ numHeaderBytes += 8;
+ } else if (size > 125) {
+ numHeaderBytes += 2;
+ }
+
+ buffer = new ABuffer(numHeaderBytes + size);
+ buffer->data()[0] = 0x81; // FIN==1 | opcode=1 (text)
+ buffer->data()[1] = kUseMask ? 0x80 : 0x00;
+
+ if (size > 65535) {
+ buffer->data()[1] |= 127;
+ buffer->data()[2] = 0x00;
+ buffer->data()[3] = 0x00;
+ buffer->data()[4] = 0x00;
+ buffer->data()[5] = 0x00;
+ buffer->data()[6] = (size >> 24) & 0xff;
+ buffer->data()[7] = (size >> 16) & 0xff;
+ buffer->data()[8] = (size >> 8) & 0xff;
+ buffer->data()[9] = size & 0xff;
+ } else if (size > 125) {
+ buffer->data()[1] |= 126;
+ buffer->data()[2] = (size >> 8) & 0xff;
+ buffer->data()[3] = size & 0xff;
+ } else {
+ buffer->data()[1] |= size;
+ }
+
+ if (kUseMask) {
+ uint32_t mask = rand();
+
+ buffer->data()[numHeaderBytes - 4] = (mask >> 24) & 0xff;
+ buffer->data()[numHeaderBytes - 3] = (mask >> 16) & 0xff;
+ buffer->data()[numHeaderBytes - 2] = (mask >> 8) & 0xff;
+ buffer->data()[numHeaderBytes - 1] = mask & 0xff;
+
+ for (size_t i = 0; i < (size_t)size; ++i) {
+ buffer->data()[numHeaderBytes + i] =
+ ((const uint8_t *)data)[i]
+ ^ ((mask >> (8 * (3 - (i % 4)))) & 0xff);
+ }
+ } else {
+ memcpy(buffer->data() + numHeaderBytes, data, size);
+ }
} else {
buffer = new ABuffer(size);
memcpy(buffer->data(), data, size);
@@ -1001,9 +1143,9 @@ status_t ANetworkSession::createClientOrServer(
notify);
if (mode == kModeCreateTCPDatagramSessionActive) {
- session->setIsRTSPConnection(false);
+ session->setMode(Session::MODE_DATAGRAM);
} else if (mode == kModeCreateRTSPClient) {
- session->setIsRTSPConnection(true);
+ session->setMode(Session::MODE_RTSP);
}
mSessions.add(session->sessionID(), session);
@@ -1080,6 +1222,19 @@ status_t ANetworkSession::sendRequest(
return err;
}
+status_t ANetworkSession::switchToWebSocketMode(int32_t sessionID) {
+ Mutex::Autolock autoLock(mLock);
+
+ ssize_t index = mSessions.indexOfKey(sessionID);
+
+ if (index < 0) {
+ return -ENOENT;
+ }
+
+ const sp<Session> session = mSessions.valueAt(index);
+ return session->switchToWebSocketMode();
+}
+
void ANetworkSession::interrupt() {
static const char dummy = 0;
@@ -1213,8 +1368,10 @@ void ANetworkSession::threadLoop() {
clientSocket,
session->getNotificationMessage());
- clientSession->setIsRTSPConnection(
- session->isRTSPServer());
+ clientSession->setMode(
+ session->isRTSPServer()
+ ? Session::MODE_RTSP
+ : Session::MODE_DATAGRAM);
sessionsToAdd.push_back(clientSession);
}
diff --git a/media/libstagefright/foundation/Android.mk b/media/libstagefright/foundation/Android.mk
index d65e213..ad2dab5 100644
--- a/media/libstagefright/foundation/Android.mk
+++ b/media/libstagefright/foundation/Android.mk
@@ -10,7 +10,9 @@ LOCAL_SRC_FILES:= \
ALooper.cpp \
ALooperRoster.cpp \
AMessage.cpp \
+ ANetworkSession.cpp \
AString.cpp \
+ ParsedMessage.cpp \
base64.cpp \
hexdump.cpp
diff --git a/media/libstagefright/wifi-display/ParsedMessage.cpp b/media/libstagefright/foundation/ParsedMessage.cpp
index c0e60c3..049c9ad 100644
--- a/media/libstagefright/wifi-display/ParsedMessage.cpp
+++ b/media/libstagefright/foundation/ParsedMessage.cpp
@@ -19,6 +19,7 @@
#include <ctype.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/hexdump.h>
namespace android {
@@ -89,6 +90,7 @@ ssize_t ParsedMessage::parse(const char *data, size_t size, bool noMoreData) {
ssize_t lastDictIndex = -1;
size_t offset = 0;
+ bool headersComplete = false;
while (offset < size) {
size_t lineEndOffset = offset;
while (lineEndOffset + 1 < size
@@ -113,6 +115,8 @@ ssize_t ParsedMessage::parse(const char *data, size_t size, bool noMoreData) {
}
if (lineEndOffset == offset) {
+ // An empty line separates headers from body.
+ headersComplete = true;
offset += 2;
break;
}
@@ -146,12 +150,17 @@ ssize_t ParsedMessage::parse(const char *data, size_t size, bool noMoreData) {
offset = lineEndOffset + 2;
}
+ if (!headersComplete && (!noMoreData || offset == 0)) {
+ // We either saw the empty line separating headers from body
+ // or we saw at least the status line and know that no more data
+ // is going to follow.
+ return -1;
+ }
+
for (size_t i = 0; i < mDict.size(); ++i) {
mDict.editValueAt(i).trim();
}
- // Found the end of headers.
-
int32_t contentLength;
if (!findInt32("content-length", &contentLength) || contentLength < 0) {
contentLength = 0;
@@ -168,13 +177,17 @@ ssize_t ParsedMessage::parse(const char *data, size_t size, bool noMoreData) {
return totalLength;
}
-void ParsedMessage::getRequestField(size_t index, AString *field) const {
+bool ParsedMessage::getRequestField(size_t index, AString *field) const {
AString line;
CHECK(findString("_", &line));
size_t prevOffset = 0;
size_t offset = 0;
for (size_t i = 0; i <= index; ++i) {
+ if (offset >= line.size()) {
+ return false;
+ }
+
ssize_t spacePos = line.find(" ", offset);
if (spacePos < 0) {
@@ -186,11 +199,16 @@ void ParsedMessage::getRequestField(size_t index, AString *field) const {
}
field->setTo(line, prevOffset, offset - prevOffset - 1);
+
+ return true;
}
bool ParsedMessage::getStatusCode(int32_t *statusCode) const {
AString statusCodeString;
- getRequestField(1, &statusCodeString);
+ if (!getRequestField(1, &statusCodeString)) {
+ *statusCode = 0;
+ return false;
+ }
char *end;
*statusCode = strtol(statusCodeString.c_str(), &end, 10);
diff --git a/media/libstagefright/httplive/Android.mk b/media/libstagefright/httplive/Android.mk
index a3fa7a3..f3529f9 100644
--- a/media/libstagefright/httplive/Android.mk
+++ b/media/libstagefright/httplive/Android.mk
@@ -6,16 +6,26 @@ LOCAL_SRC_FILES:= \
LiveDataSource.cpp \
LiveSession.cpp \
M3UParser.cpp \
+ PlaylistFetcher.cpp \
LOCAL_C_INCLUDES:= \
$(TOP)/frameworks/av/media/libstagefright \
$(TOP)/frameworks/native/include/media/openmax \
$(TOP)/external/openssl/include
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libcrypto \
+ libcutils \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libutils \
+
LOCAL_MODULE:= libstagefright_httplive
ifeq ($(TARGET_ARCH),arm)
LOCAL_CFLAGS += -Wno-psabi
endif
-include $(BUILD_STATIC_LIBRARY)
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index 505bdb3..bd12ddc 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -18,12 +18,13 @@
#define LOG_TAG "LiveSession"
#include <utils/Log.h>
-#include "include/LiveSession.h"
+#include "LiveSession.h"
-#include "LiveDataSource.h"
+#include "M3UParser.h"
+#include "PlaylistFetcher.h"
-#include "include/M3UParser.h"
#include "include/HTTPBase.h"
+#include "mpeg2ts/AnotherPacketSource.h"
#include <cutils/properties.h>
#include <media/stagefright/foundation/hexdump.h>
@@ -33,6 +34,8 @@
#include <media/stagefright/DataSource.h>
#include <media/stagefright/FileSource.h>
#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
#include <ctype.h>
#include <openssl/aes.h>
@@ -47,37 +50,115 @@ LiveSession::LiveSession(
mUIDValid(uidValid),
mUID(uid),
mInPreparationPhase(true),
- mDataSource(new LiveDataSource),
mHTTPDataSource(
HTTPBase::Create(
(mFlags & kFlagIncognito)
? HTTPBase::kFlagIncognito
: 0)),
mPrevBandwidthIndex(-1),
- mLastPlaylistFetchTimeUs(-1),
- mSeqNumber(-1),
- mSeekTimeUs(-1),
- mNumRetries(0),
- mStartOfPlayback(true),
- mDurationUs(-1),
- mDurationFixed(false),
- mSeekDone(false),
- mDisconnectPending(false),
- mMonitorQueueGeneration(0),
- mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY) {
+ mStreamMask(0),
+ mCheckBandwidthGeneration(0),
+ mLastDequeuedTimeUs(0ll),
+ mRealTimeBaseUs(0ll),
+ mReconfigurationInProgress(false),
+ mDisconnectReplyID(0) {
if (mUIDValid) {
mHTTPDataSource->setUID(mUID);
}
+
+ mPacketSources.add(
+ STREAMTYPE_AUDIO, new AnotherPacketSource(NULL /* meta */));
+
+ mPacketSources.add(
+ STREAMTYPE_VIDEO, new AnotherPacketSource(NULL /* meta */));
+
+ mPacketSources.add(
+ STREAMTYPE_SUBTITLES, new AnotherPacketSource(NULL /* meta */));
}
LiveSession::~LiveSession() {
}
-sp<DataSource> LiveSession::getDataSource() {
- return mDataSource;
+status_t LiveSession::dequeueAccessUnit(
+ StreamType stream, sp<ABuffer> *accessUnit) {
+ if (!(mStreamMask & stream)) {
+ return UNKNOWN_ERROR;
+ }
+
+ sp<AnotherPacketSource> packetSource = mPacketSources.valueFor(stream);
+
+ status_t finalResult;
+ if (!packetSource->hasBufferAvailable(&finalResult)) {
+ return finalResult == OK ? -EAGAIN : finalResult;
+ }
+
+ status_t err = packetSource->dequeueAccessUnit(accessUnit);
+
+ const char *streamStr;
+ switch (stream) {
+ case STREAMTYPE_AUDIO:
+ streamStr = "audio";
+ break;
+ case STREAMTYPE_VIDEO:
+ streamStr = "video";
+ break;
+ case STREAMTYPE_SUBTITLES:
+ streamStr = "subs";
+ break;
+ default:
+ TRESPASS();
+ }
+
+ if (err == INFO_DISCONTINUITY) {
+ int32_t type;
+ CHECK((*accessUnit)->meta()->findInt32("discontinuity", &type));
+
+ sp<AMessage> extra;
+ if (!(*accessUnit)->meta()->findMessage("extra", &extra)) {
+ extra.clear();
+ }
+
+ ALOGI("[%s] read discontinuity of type %d, extra = %s",
+ streamStr,
+ type,
+ extra == NULL ? "NULL" : extra->debugString().c_str());
+ } else if (err == OK) {
+ if (stream == STREAMTYPE_AUDIO || stream == STREAMTYPE_VIDEO) {
+ int64_t timeUs;
+ CHECK((*accessUnit)->meta()->findInt64("timeUs", &timeUs));
+ ALOGV("[%s] read buffer at time %lld us", streamStr, timeUs);
+
+ mLastDequeuedTimeUs = timeUs;
+ mRealTimeBaseUs = ALooper::GetNowUs() - timeUs;
+ } else if (stream == STREAMTYPE_SUBTITLES) {
+ (*accessUnit)->meta()->setInt32(
+ "trackIndex", mPlaylist->getSelectedIndex());
+ (*accessUnit)->meta()->setInt64("baseUs", mRealTimeBaseUs);
+ }
+ } else {
+ ALOGI("[%s] encountered error %d", streamStr, err);
+ }
+
+ return err;
+}
+
+status_t LiveSession::getStreamFormat(StreamType stream, sp<AMessage> *format) {
+ if (!(mStreamMask & stream)) {
+ return UNKNOWN_ERROR;
+ }
+
+ sp<AnotherPacketSource> packetSource = mPacketSources.valueFor(stream);
+
+ sp<MetaData> meta = packetSource->getFormat();
+
+ if (meta == NULL) {
+ return -EAGAIN;
+ }
+
+ return convertMetaDataToMessage(meta, format);
}
-void LiveSession::connect(
+void LiveSession::connectAsync(
const char *url, const KeyedVector<String8, String8> *headers) {
sp<AMessage> msg = new AMessage(kWhatConnect, id());
msg->setString("url", url);
@@ -91,55 +172,190 @@ void LiveSession::connect(
msg->post();
}
-void LiveSession::disconnect() {
- Mutex::Autolock autoLock(mLock);
- mDisconnectPending = true;
+status_t LiveSession::disconnect() {
+ sp<AMessage> msg = new AMessage(kWhatDisconnect, id());
- mHTTPDataSource->disconnect();
+ sp<AMessage> response;
+ status_t err = msg->postAndAwaitResponse(&response);
- (new AMessage(kWhatDisconnect, id()))->post();
+ return err;
}
-void LiveSession::seekTo(int64_t timeUs) {
- Mutex::Autolock autoLock(mLock);
- mSeekDone = false;
-
+status_t LiveSession::seekTo(int64_t timeUs) {
sp<AMessage> msg = new AMessage(kWhatSeek, id());
msg->setInt64("timeUs", timeUs);
- msg->post();
- while (!mSeekDone) {
- mCondition.wait(mLock);
- }
+ sp<AMessage> response;
+ status_t err = msg->postAndAwaitResponse(&response);
+
+ return err;
}
void LiveSession::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatConnect:
+ {
onConnect(msg);
break;
+ }
case kWhatDisconnect:
- onDisconnect();
+ {
+ CHECK(msg->senderAwaitsResponse(&mDisconnectReplyID));
+
+ if (mReconfigurationInProgress) {
+ break;
+ }
+
+ finishDisconnect();
break;
+ }
+
+ case kWhatSeek:
+ {
+ uint32_t replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ status_t err = onSeek(msg);
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+
+ response->postReply(replyID);
+ break;
+ }
+
+ case kWhatFetcherNotify:
+ {
+ int32_t what;
+ CHECK(msg->findInt32("what", &what));
+
+ switch (what) {
+ case PlaylistFetcher::kWhatStarted:
+ break;
+ case PlaylistFetcher::kWhatPaused:
+ case PlaylistFetcher::kWhatStopped:
+ {
+ if (what == PlaylistFetcher::kWhatStopped) {
+ AString uri;
+ CHECK(msg->findString("uri", &uri));
+ mFetcherInfos.removeItem(uri);
+ }
+
+ if (mContinuation != NULL) {
+ CHECK_GT(mContinuationCounter, 0);
+ if (--mContinuationCounter == 0) {
+ mContinuation->post();
+ }
+ }
+ break;
+ }
+
+ case PlaylistFetcher::kWhatDurationUpdate:
+ {
+ AString uri;
+ CHECK(msg->findString("uri", &uri));
+
+ int64_t durationUs;
+ CHECK(msg->findInt64("durationUs", &durationUs));
+
+ FetcherInfo *info = &mFetcherInfos.editValueFor(uri);
+ info->mDurationUs = durationUs;
+ break;
+ }
+
+ case PlaylistFetcher::kWhatError:
+ {
+ status_t err;
+ CHECK(msg->findInt32("err", &err));
+
+ ALOGE("XXX Received error %d from PlaylistFetcher.", err);
- case kWhatMonitorQueue:
+ if (mInPreparationPhase) {
+ postPrepared(err);
+ }
+
+ mPacketSources.valueFor(STREAMTYPE_AUDIO)->signalEOS(err);
+
+ mPacketSources.valueFor(STREAMTYPE_VIDEO)->signalEOS(err);
+
+ mPacketSources.valueFor(
+ STREAMTYPE_SUBTITLES)->signalEOS(err);
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+ break;
+ }
+
+ case PlaylistFetcher::kWhatTemporarilyDoneFetching:
+ {
+ AString uri;
+ CHECK(msg->findString("uri", &uri));
+
+ FetcherInfo *info = &mFetcherInfos.editValueFor(uri);
+ info->mIsPrepared = true;
+
+ if (mInPreparationPhase) {
+ bool allFetchersPrepared = true;
+ for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+ if (!mFetcherInfos.valueAt(i).mIsPrepared) {
+ allFetchersPrepared = false;
+ break;
+ }
+ }
+
+ if (allFetchersPrepared) {
+ postPrepared(OK);
+ }
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+
+ break;
+ }
+
+ case kWhatCheckBandwidth:
{
int32_t generation;
CHECK(msg->findInt32("generation", &generation));
- if (generation != mMonitorQueueGeneration) {
- // Stale event
+ if (generation != mCheckBandwidthGeneration) {
break;
}
- onMonitorQueue();
+ onCheckBandwidth();
break;
}
- case kWhatSeek:
- onSeek(msg);
+ case kWhatChangeConfiguration:
+ {
+ onChangeConfiguration(msg);
+ break;
+ }
+
+ case kWhatChangeConfiguration2:
+ {
+ onChangeConfiguration2(msg);
+ break;
+ }
+
+ case kWhatChangeConfiguration3:
+ {
+ onChangeConfiguration3(msg);
break;
+ }
+
+ case kWhatFinishDisconnect2:
+ {
+ onFinishDisconnect2();
+ break;
+ }
default:
TRESPASS();
@@ -172,48 +388,128 @@ void LiveSession::onConnect(const sp<AMessage> &msg) {
headers = NULL;
}
+#if 1
ALOGI("onConnect <URL suppressed>");
+#else
+ ALOGI("onConnect %s", url.c_str());
+#endif
mMasterURL = url;
bool dummy;
- sp<M3UParser> playlist = fetchPlaylist(url.c_str(), &dummy);
+ mPlaylist = fetchPlaylist(url.c_str(), NULL /* curPlaylistHash */, &dummy);
- if (playlist == NULL) {
+ if (mPlaylist == NULL) {
ALOGE("unable to fetch master playlist '%s'.", url.c_str());
- signalEOS(ERROR_IO);
+ postPrepared(ERROR_IO);
return;
}
- if (playlist->isVariantPlaylist()) {
- for (size_t i = 0; i < playlist->size(); ++i) {
+ // We trust the content provider to make a reasonable choice of preferred
+ // initial bandwidth by listing it first in the variant playlist.
+ // At startup we really don't have a good estimate on the available
+ // network bandwidth since we haven't tranferred any data yet. Once
+ // we have we can make a better informed choice.
+ size_t initialBandwidth = 0;
+ size_t initialBandwidthIndex = 0;
+
+ if (mPlaylist->isVariantPlaylist()) {
+ for (size_t i = 0; i < mPlaylist->size(); ++i) {
BandwidthItem item;
+ item.mPlaylistIndex = i;
+
sp<AMessage> meta;
- playlist->itemAt(i, &item.mURI, &meta);
+ AString uri;
+ mPlaylist->itemAt(i, &uri, &meta);
unsigned long bandwidth;
CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth));
+ if (initialBandwidth == 0) {
+ initialBandwidth = item.mBandwidth;
+ }
+
mBandwidthItems.push(item);
}
CHECK_GT(mBandwidthItems.size(), 0u);
mBandwidthItems.sort(SortByBandwidth);
+
+ for (size_t i = 0; i < mBandwidthItems.size(); ++i) {
+ if (mBandwidthItems.itemAt(i).mBandwidth == initialBandwidth) {
+ initialBandwidthIndex = i;
+ break;
+ }
+ }
+ } else {
+ // dummy item.
+ BandwidthItem item;
+ item.mPlaylistIndex = 0;
+ item.mBandwidth = 0;
+ mBandwidthItems.push(item);
+ }
+
+ changeConfiguration(
+ 0ll /* timeUs */, initialBandwidthIndex, true /* pickTrack */);
+}
+
+void LiveSession::finishDisconnect() {
+ // No reconfiguration is currently pending, make sure none will trigger
+ // during disconnection either.
+ cancelCheckBandwidthEvent();
+
+ for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+ mFetcherInfos.valueAt(i).mFetcher->stopAsync();
}
- postMonitorQueue();
+ sp<AMessage> msg = new AMessage(kWhatFinishDisconnect2, id());
+
+ mContinuationCounter = mFetcherInfos.size();
+ mContinuation = msg;
+
+ if (mContinuationCounter == 0) {
+ msg->post();
+ }
+}
+
+void LiveSession::onFinishDisconnect2() {
+ mContinuation.clear();
+
+ mPacketSources.valueFor(STREAMTYPE_AUDIO)->signalEOS(ERROR_END_OF_STREAM);
+ mPacketSources.valueFor(STREAMTYPE_VIDEO)->signalEOS(ERROR_END_OF_STREAM);
+
+ mPacketSources.valueFor(
+ STREAMTYPE_SUBTITLES)->signalEOS(ERROR_END_OF_STREAM);
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", OK);
+
+ response->postReply(mDisconnectReplyID);
+ mDisconnectReplyID = 0;
}
-void LiveSession::onDisconnect() {
- ALOGI("onDisconnect");
+sp<PlaylistFetcher> LiveSession::addFetcher(const char *uri) {
+ ssize_t index = mFetcherInfos.indexOfKey(uri);
+
+ if (index >= 0) {
+ return NULL;
+ }
- signalEOS(ERROR_END_OF_STREAM);
+ sp<AMessage> notify = new AMessage(kWhatFetcherNotify, id());
+ notify->setString("uri", uri);
- Mutex::Autolock autoLock(mLock);
- mDisconnectPending = false;
+ FetcherInfo info;
+ info.mFetcher = new PlaylistFetcher(notify, this, uri);
+ info.mDurationUs = -1ll;
+ info.mIsPrepared = false;
+ looper()->registerHandler(info.mFetcher);
+
+ mFetcherInfos.add(uri, info);
+
+ return info.mFetcher;
}
status_t LiveSession::fetchFile(
@@ -229,14 +525,6 @@ status_t LiveSession::fetchFile(
&& strncasecmp(url, "https://", 8)) {
return ERROR_UNSUPPORTED;
} else {
- {
- Mutex::Autolock autoLock(mLock);
-
- if (mDisconnectPending) {
- return ERROR_IO;
- }
- }
-
KeyedVector<String8, String8> headers = mExtraHeaders;
if (range_offset > 0 || range_length >= 0) {
headers.add(
@@ -315,7 +603,8 @@ status_t LiveSession::fetchFile(
return OK;
}
-sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) {
+sp<M3UParser> LiveSession::fetchPlaylist(
+ const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
ALOGV("fetchPlaylist '%s'", url);
*unchanged = false;
@@ -339,13 +628,8 @@ sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) {
MD5_Final(hash, &m);
- if (mPlaylist != NULL && !memcmp(hash, mPlaylistHash, 16)) {
+ if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
// playlist unchanged
-
- if (mRefreshState != THIRD_UNCHANGED_RELOAD_ATTEMPT) {
- mRefreshState = (RefreshState)(mRefreshState + 1);
- }
-
*unchanged = true;
ALOGV("Playlist unchanged, refresh state is now %d",
@@ -354,9 +638,9 @@ sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) {
return NULL;
}
- memcpy(mPlaylistHash, hash, sizeof(hash));
-
- mRefreshState = INITIAL_MINIMUM_RELOAD_DELAY;
+ if (curPlaylistHash != NULL) {
+ memcpy(curPlaylistHash, hash, sizeof(hash));
+ }
#endif
sp<M3UParser> playlist =
@@ -371,37 +655,6 @@ sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) {
return playlist;
}
-int64_t LiveSession::getSegmentStartTimeUs(int32_t seqNumber) const {
- CHECK(mPlaylist != NULL);
-
- int32_t firstSeqNumberInPlaylist;
- if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
- "media-sequence", &firstSeqNumberInPlaylist)) {
- firstSeqNumberInPlaylist = 0;
- }
-
- int32_t lastSeqNumberInPlaylist =
- firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
-
- CHECK_GE(seqNumber, firstSeqNumberInPlaylist);
- CHECK_LE(seqNumber, lastSeqNumberInPlaylist);
-
- int64_t segmentStartUs = 0ll;
- for (int32_t index = 0;
- index < seqNumber - firstSeqNumberInPlaylist; ++index) {
- sp<AMessage> itemMeta;
- CHECK(mPlaylist->itemAt(
- index, NULL /* uri */, &itemMeta));
-
- int64_t itemDurationUs;
- CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
-
- segmentStartUs += itemDurationUs;
- }
-
- return segmentStartUs;
-}
-
static double uniformRand() {
return (double)rand() / RAND_MAX;
}
@@ -412,36 +665,50 @@ size_t LiveSession::getBandwidthIndex() {
}
#if 1
- int32_t bandwidthBps;
- if (mHTTPDataSource != NULL
- && mHTTPDataSource->estimateBandwidth(&bandwidthBps)) {
- ALOGV("bandwidth estimated at %.2f kbps", bandwidthBps / 1024.0f);
- } else {
- ALOGV("no bandwidth estimate.");
- return 0; // Pick the lowest bandwidth stream by default.
- }
-
char value[PROPERTY_VALUE_MAX];
- if (property_get("media.httplive.max-bw", value, NULL)) {
+ ssize_t index = -1;
+ if (property_get("media.httplive.bw-index", value, NULL)) {
char *end;
- long maxBw = strtoul(value, &end, 10);
- if (end > value && *end == '\0') {
- if (maxBw > 0 && bandwidthBps > maxBw) {
- ALOGV("bandwidth capped to %ld bps", maxBw);
- bandwidthBps = maxBw;
- }
+ index = strtol(value, &end, 10);
+ CHECK(end > value && *end == '\0');
+
+ if (index >= 0 && (size_t)index >= mBandwidthItems.size()) {
+ index = mBandwidthItems.size() - 1;
}
}
- // Consider only 80% of the available bandwidth usable.
- bandwidthBps = (bandwidthBps * 8) / 10;
+ if (index < 0) {
+ int32_t bandwidthBps;
+ if (mHTTPDataSource != NULL
+ && mHTTPDataSource->estimateBandwidth(&bandwidthBps)) {
+ ALOGV("bandwidth estimated at %.2f kbps", bandwidthBps / 1024.0f);
+ } else {
+ ALOGV("no bandwidth estimate.");
+ return 0; // Pick the lowest bandwidth stream by default.
+ }
- // Pick the highest bandwidth stream below or equal to estimated bandwidth.
+ char value[PROPERTY_VALUE_MAX];
+ if (property_get("media.httplive.max-bw", value, NULL)) {
+ char *end;
+ long maxBw = strtoul(value, &end, 10);
+ if (end > value && *end == '\0') {
+ if (maxBw > 0 && bandwidthBps > maxBw) {
+ ALOGV("bandwidth capped to %ld bps", maxBw);
+ bandwidthBps = maxBw;
+ }
+ }
+ }
- size_t index = mBandwidthItems.size() - 1;
- while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth
- > (size_t)bandwidthBps) {
- --index;
+ // Consider only 80% of the available bandwidth usable.
+ bandwidthBps = (bandwidthBps * 8) / 10;
+
+ // Pick the highest bandwidth stream below or equal to estimated bandwidth.
+
+ index = mBandwidthItems.size() - 1;
+ while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth
+ > (size_t)bandwidthBps) {
+ --index;
+ }
}
#elif 0
// Change bandwidth at random()
@@ -452,6 +719,8 @@ size_t LiveSession::getBandwidthIndex() {
// to lowest)
const size_t kMinIndex = 0;
+ static ssize_t mPrevBandwidthIndex = -1;
+
size_t index;
if (mPrevBandwidthIndex < 0) {
index = kMinIndex;
@@ -463,6 +732,7 @@ size_t LiveSession::getBandwidthIndex() {
index = kMinIndex;
}
}
+ mPrevBandwidthIndex = index;
#elif 0
// Pick the highest bandwidth stream below or equal to 1.2 Mbit/sec
@@ -470,570 +740,405 @@ size_t LiveSession::getBandwidthIndex() {
while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth > 1200000) {
--index;
}
+#elif 1
+ char value[PROPERTY_VALUE_MAX];
+ size_t index;
+ if (property_get("media.httplive.bw-index", value, NULL)) {
+ char *end;
+ index = strtoul(value, &end, 10);
+ CHECK(end > value && *end == '\0');
+
+ if (index >= mBandwidthItems.size()) {
+ index = mBandwidthItems.size() - 1;
+ }
+ } else {
+ index = 0;
+ }
#else
size_t index = mBandwidthItems.size() - 1; // Highest bandwidth stream
#endif
+ CHECK_GE(index, 0);
+
return index;
}
-bool LiveSession::timeToRefreshPlaylist(int64_t nowUs) const {
- if (mPlaylist == NULL) {
- CHECK_EQ((int)mRefreshState, (int)INITIAL_MINIMUM_RELOAD_DELAY);
- return true;
- }
-
- int32_t targetDurationSecs;
- CHECK(mPlaylist->meta()->findInt32("target-duration", &targetDurationSecs));
-
- int64_t targetDurationUs = targetDurationSecs * 1000000ll;
-
- int64_t minPlaylistAgeUs;
-
- switch (mRefreshState) {
- case INITIAL_MINIMUM_RELOAD_DELAY:
- {
- size_t n = mPlaylist->size();
- if (n > 0) {
- sp<AMessage> itemMeta;
- CHECK(mPlaylist->itemAt(n - 1, NULL /* uri */, &itemMeta));
-
- int64_t itemDurationUs;
- CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
-
- minPlaylistAgeUs = itemDurationUs;
- break;
- }
-
- // fall through
- }
-
- case FIRST_UNCHANGED_RELOAD_ATTEMPT:
- {
- minPlaylistAgeUs = targetDurationUs / 2;
- break;
- }
-
- case SECOND_UNCHANGED_RELOAD_ATTEMPT:
- {
- minPlaylistAgeUs = (targetDurationUs * 3) / 2;
- break;
- }
-
- case THIRD_UNCHANGED_RELOAD_ATTEMPT:
- {
- minPlaylistAgeUs = targetDurationUs * 3;
- break;
- }
+status_t LiveSession::onSeek(const sp<AMessage> &msg) {
+ int64_t timeUs;
+ CHECK(msg->findInt64("timeUs", &timeUs));
- default:
- TRESPASS();
- break;
+ if (!mReconfigurationInProgress) {
+ changeConfiguration(timeUs, getBandwidthIndex());
}
- return mLastPlaylistFetchTimeUs + minPlaylistAgeUs <= nowUs;
+ return OK;
}
-void LiveSession::onDownloadNext() {
- size_t bandwidthIndex = getBandwidthIndex();
-
-rinse_repeat:
- int64_t nowUs = ALooper::GetNowUs();
-
- if (mLastPlaylistFetchTimeUs < 0
- || (ssize_t)bandwidthIndex != mPrevBandwidthIndex
- || (!mPlaylist->isComplete() && timeToRefreshPlaylist(nowUs))) {
- AString url;
- if (mBandwidthItems.size() > 0) {
- url = mBandwidthItems.editItemAt(bandwidthIndex).mURI;
- } else {
- url = mMasterURL;
- }
-
- if ((ssize_t)bandwidthIndex != mPrevBandwidthIndex) {
- // If we switch bandwidths, do not pay any heed to whether
- // playlists changed since the last time...
- mPlaylist.clear();
- }
-
- bool unchanged;
- sp<M3UParser> playlist = fetchPlaylist(url.c_str(), &unchanged);
- if (playlist == NULL) {
- if (unchanged) {
- // We succeeded in fetching the playlist, but it was
- // unchanged from the last time we tried.
- } else {
- ALOGE("failed to load playlist at url '%s'", url.c_str());
- signalEOS(ERROR_IO);
-
- return;
- }
- } else {
- mPlaylist = playlist;
- }
-
- if (!mDurationFixed) {
- Mutex::Autolock autoLock(mLock);
-
- if (!mPlaylist->isComplete() && !mPlaylist->isEvent()) {
- mDurationUs = -1;
- mDurationFixed = true;
- } else {
- mDurationUs = 0;
- for (size_t i = 0; i < mPlaylist->size(); ++i) {
- sp<AMessage> itemMeta;
- CHECK(mPlaylist->itemAt(
- i, NULL /* uri */, &itemMeta));
-
- int64_t itemDurationUs;
- CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
-
- mDurationUs += itemDurationUs;
- }
+status_t LiveSession::getDuration(int64_t *durationUs) const {
+ int64_t maxDurationUs = 0ll;
+ for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+ int64_t fetcherDurationUs = mFetcherInfos.valueAt(i).mDurationUs;
- mDurationFixed = mPlaylist->isComplete();
- }
+ if (fetcherDurationUs >= 0ll && fetcherDurationUs > maxDurationUs) {
+ maxDurationUs = fetcherDurationUs;
}
-
- mLastPlaylistFetchTimeUs = ALooper::GetNowUs();
}
- int32_t firstSeqNumberInPlaylist;
- if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
- "media-sequence", &firstSeqNumberInPlaylist)) {
- firstSeqNumberInPlaylist = 0;
- }
+ *durationUs = maxDurationUs;
- bool seekDiscontinuity = false;
- bool explicitDiscontinuity = false;
- bool bandwidthChanged = false;
+ return OK;
+}
- if (mSeekTimeUs >= 0) {
- if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
- size_t index = 0;
- int64_t segmentStartUs = 0;
- while (index < mPlaylist->size()) {
- sp<AMessage> itemMeta;
- CHECK(mPlaylist->itemAt(
- index, NULL /* uri */, &itemMeta));
+bool LiveSession::isSeekable() const {
+ int64_t durationUs;
+ return getDuration(&durationUs) == OK && durationUs >= 0;
+}
- int64_t itemDurationUs;
- CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+bool LiveSession::hasDynamicDuration() const {
+ return false;
+}
- if (mSeekTimeUs < segmentStartUs + itemDurationUs) {
- break;
- }
+status_t LiveSession::getTrackInfo(Parcel *reply) const {
+ return mPlaylist->getTrackInfo(reply);
+}
- segmentStartUs += itemDurationUs;
- ++index;
- }
+status_t LiveSession::selectTrack(size_t index, bool select) {
+ status_t err = mPlaylist->selectTrack(index, select);
+ if (err == OK) {
+ (new AMessage(kWhatChangeConfiguration, id()))->post();
+ }
+ return err;
+}
- if (index < mPlaylist->size()) {
- int32_t newSeqNumber = firstSeqNumberInPlaylist + index;
+void LiveSession::changeConfiguration(
+ int64_t timeUs, size_t bandwidthIndex, bool pickTrack) {
+ CHECK(!mReconfigurationInProgress);
+ mReconfigurationInProgress = true;
- ALOGI("seeking to seq no %d", newSeqNumber);
+ mPrevBandwidthIndex = bandwidthIndex;
- mSeqNumber = newSeqNumber;
+ ALOGV("changeConfiguration => timeUs:%lld us, bwIndex:%d, pickTrack:%d",
+ timeUs, bandwidthIndex, pickTrack);
- mDataSource->reset();
+ if (pickTrack) {
+ mPlaylist->pickRandomMediaItems();
+ }
- // reseting the data source will have had the
- // side effect of discarding any previously queued
- // bandwidth change discontinuity.
- // Therefore we'll need to treat these seek
- // discontinuities as involving a bandwidth change
- // even if they aren't directly.
- seekDiscontinuity = true;
- bandwidthChanged = true;
- }
- }
+ CHECK_LT(bandwidthIndex, mBandwidthItems.size());
+ const BandwidthItem &item = mBandwidthItems.itemAt(bandwidthIndex);
- mSeekTimeUs = -1;
+ uint32_t streamMask = 0;
- Mutex::Autolock autoLock(mLock);
- mSeekDone = true;
- mCondition.broadcast();
+ AString audioURI;
+ if (mPlaylist->getAudioURI(item.mPlaylistIndex, &audioURI)) {
+ streamMask |= STREAMTYPE_AUDIO;
}
- const int32_t lastSeqNumberInPlaylist =
- firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
-
- if (mSeqNumber < 0) {
- if (mPlaylist->isComplete()) {
- mSeqNumber = firstSeqNumberInPlaylist;
- } else {
- // If this is a live session, start 3 segments from the end.
- mSeqNumber = lastSeqNumberInPlaylist - 3;
- if (mSeqNumber < firstSeqNumberInPlaylist) {
- mSeqNumber = firstSeqNumberInPlaylist;
- }
- }
+ AString videoURI;
+ if (mPlaylist->getVideoURI(item.mPlaylistIndex, &videoURI)) {
+ streamMask |= STREAMTYPE_VIDEO;
}
- if (mSeqNumber < firstSeqNumberInPlaylist
- || mSeqNumber > lastSeqNumberInPlaylist) {
- if (mPrevBandwidthIndex != (ssize_t)bandwidthIndex) {
- // Go back to the previous bandwidth.
-
- ALOGI("new bandwidth does not have the sequence number "
- "we're looking for, switching back to previous bandwidth");
+ AString subtitleURI;
+ if (mPlaylist->getSubtitleURI(item.mPlaylistIndex, &subtitleURI)) {
+ streamMask |= STREAMTYPE_SUBTITLES;
+ }
- mLastPlaylistFetchTimeUs = -1;
- bandwidthIndex = mPrevBandwidthIndex;
- goto rinse_repeat;
- }
+ // Step 1, stop and discard fetchers that are no longer needed.
+ // Pause those that we'll reuse.
+ for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+ const AString &uri = mFetcherInfos.keyAt(i);
- if (!mPlaylist->isComplete() && mNumRetries < kMaxNumRetries) {
- ++mNumRetries;
+ bool discardFetcher = true;
- if (mSeqNumber > lastSeqNumberInPlaylist) {
- mLastPlaylistFetchTimeUs = -1;
- postMonitorQueue(3000000ll);
- return;
+ // If we're seeking all current fetchers are discarded.
+ if (timeUs < 0ll) {
+ if (((streamMask & STREAMTYPE_AUDIO) && uri == audioURI)
+ || ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI)
+ || ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI)) {
+ discardFetcher = false;
}
+ }
- // we've missed the boat, let's start from the lowest sequence
- // number available and signal a discontinuity.
-
- ALOGI("We've missed the boat, restarting playback.");
- mSeqNumber = lastSeqNumberInPlaylist;
- explicitDiscontinuity = true;
-
- // fall through
+ if (discardFetcher) {
+ mFetcherInfos.valueAt(i).mFetcher->stopAsync();
} else {
- ALOGE("Cannot find sequence number %d in playlist "
- "(contains %d - %d)",
- mSeqNumber, firstSeqNumberInPlaylist,
- firstSeqNumberInPlaylist + mPlaylist->size() - 1);
-
- signalEOS(ERROR_END_OF_STREAM);
- return;
+ mFetcherInfos.valueAt(i).mFetcher->pauseAsync();
}
}
- mNumRetries = 0;
-
- AString uri;
- sp<AMessage> itemMeta;
- CHECK(mPlaylist->itemAt(
- mSeqNumber - firstSeqNumberInPlaylist,
- &uri,
- &itemMeta));
-
- int32_t val;
- if (itemMeta->findInt32("discontinuity", &val) && val != 0) {
- explicitDiscontinuity = true;
+ sp<AMessage> msg = new AMessage(kWhatChangeConfiguration2, id());
+ msg->setInt32("streamMask", streamMask);
+ msg->setInt64("timeUs", timeUs);
+ if (streamMask & STREAMTYPE_AUDIO) {
+ msg->setString("audioURI", audioURI.c_str());
}
-
- int64_t range_offset, range_length;
- if (!itemMeta->findInt64("range-offset", &range_offset)
- || !itemMeta->findInt64("range-length", &range_length)) {
- range_offset = 0;
- range_length = -1;
+ if (streamMask & STREAMTYPE_VIDEO) {
+ msg->setString("videoURI", videoURI.c_str());
}
-
- ALOGV("fetching segment %d from (%d .. %d)",
- mSeqNumber, firstSeqNumberInPlaylist, lastSeqNumberInPlaylist);
-
- sp<ABuffer> buffer;
- status_t err = fetchFile(uri.c_str(), &buffer, range_offset, range_length);
- if (err != OK) {
- ALOGE("failed to fetch .ts segment at url '%s'", uri.c_str());
- signalEOS(err);
- return;
+ if (streamMask & STREAMTYPE_SUBTITLES) {
+ msg->setString("subtitleURI", subtitleURI.c_str());
}
- CHECK(buffer != NULL);
-
- err = decryptBuffer(mSeqNumber - firstSeqNumberInPlaylist, buffer);
+ // Every time a fetcher acknowledges the stopAsync or pauseAsync request
+ // we'll decrement mContinuationCounter, once it reaches zero, i.e. all
+ // fetchers have completed their asynchronous operation, we'll post
+ // mContinuation, which then is handled below in onChangeConfiguration2.
+ mContinuationCounter = mFetcherInfos.size();
+ mContinuation = msg;
- if (err != OK) {
- ALOGE("decryptBuffer failed w/ error %d", err);
-
- signalEOS(err);
- return;
+ if (mContinuationCounter == 0) {
+ msg->post();
}
+}
- if (buffer->size() == 0 || buffer->data()[0] != 0x47) {
- // Not a transport stream???
-
- ALOGE("This doesn't look like a transport stream...");
-
- mBandwidthItems.removeAt(bandwidthIndex);
+void LiveSession::onChangeConfiguration(const sp<AMessage> &msg) {
+ if (!mReconfigurationInProgress) {
+ changeConfiguration(-1ll /* timeUs */, getBandwidthIndex());
+ } else {
+ msg->post(1000000ll); // retry in 1 sec
+ }
+}
- if (mBandwidthItems.isEmpty()) {
- signalEOS(ERROR_UNSUPPORTED);
- return;
- }
+void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) {
+ mContinuation.clear();
- ALOGI("Retrying with a different bandwidth stream.");
+ // All fetchers are either suspended or have been removed now.
- mLastPlaylistFetchTimeUs = -1;
- bandwidthIndex = getBandwidthIndex();
- mPrevBandwidthIndex = bandwidthIndex;
- mSeqNumber = -1;
+ uint32_t streamMask;
+ CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
- goto rinse_repeat;
+ AString audioURI, videoURI, subtitleURI;
+ if (streamMask & STREAMTYPE_AUDIO) {
+ CHECK(msg->findString("audioURI", &audioURI));
+ ALOGV("audioURI = '%s'", audioURI.c_str());
}
-
- if ((size_t)mPrevBandwidthIndex != bandwidthIndex) {
- bandwidthChanged = true;
+ if (streamMask & STREAMTYPE_VIDEO) {
+ CHECK(msg->findString("videoURI", &videoURI));
+ ALOGV("videoURI = '%s'", videoURI.c_str());
}
-
- if (mPrevBandwidthIndex < 0) {
- // Don't signal a bandwidth change at the very beginning of
- // playback.
- bandwidthChanged = false;
+ if (streamMask & STREAMTYPE_SUBTITLES) {
+ CHECK(msg->findString("subtitleURI", &subtitleURI));
+ ALOGV("subtitleURI = '%s'", subtitleURI.c_str());
}
- if (mStartOfPlayback) {
- seekDiscontinuity = true;
- mStartOfPlayback = false;
+ // Determine which decoders to shutdown on the player side,
+ // a decoder has to be shutdown if either
+ // 1) its streamtype was active before but now longer isn't.
+ // or
+ // 2) its streamtype was already active and still is but the URI
+ // has changed.
+ uint32_t changedMask = 0;
+ if (((mStreamMask & streamMask & STREAMTYPE_AUDIO)
+ && !(audioURI == mAudioURI))
+ || (mStreamMask & ~streamMask & STREAMTYPE_AUDIO)) {
+ changedMask |= STREAMTYPE_AUDIO;
+ }
+ if (((mStreamMask & streamMask & STREAMTYPE_VIDEO)
+ && !(videoURI == mVideoURI))
+ || (mStreamMask & ~streamMask & STREAMTYPE_VIDEO)) {
+ changedMask |= STREAMTYPE_VIDEO;
}
- if (seekDiscontinuity || explicitDiscontinuity || bandwidthChanged) {
- // Signal discontinuity.
-
- ALOGI("queueing discontinuity (seek=%d, explicit=%d, bandwidthChanged=%d)",
- seekDiscontinuity, explicitDiscontinuity, bandwidthChanged);
+ if (changedMask == 0) {
+ // If nothing changed as far as the audio/video decoders
+ // are concerned we can proceed.
+ onChangeConfiguration3(msg);
+ return;
+ }
- sp<ABuffer> tmp = new ABuffer(188);
- memset(tmp->data(), 0, tmp->size());
+ // Something changed, inform the player which will shutdown the
+ // corresponding decoders and will post the reply once that's done.
+ // Handling the reply will continue executing below in
+ // onChangeConfiguration3.
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatStreamsChanged);
+ notify->setInt32("changedMask", changedMask);
- // signal a 'hard' discontinuity for explicit or bandwidthChanged.
- uint8_t type = (explicitDiscontinuity || bandwidthChanged) ? 1 : 0;
+ msg->setWhat(kWhatChangeConfiguration3);
+ msg->setTarget(id());
- if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
- // If this was a live event this made no sense since
- // we don't have access to all the segment before the current
- // one.
- int64_t segmentStartTimeUs = getSegmentStartTimeUs(mSeqNumber);
- memcpy(tmp->data() + 2, &segmentStartTimeUs, sizeof(segmentStartTimeUs));
+ notify->setMessage("reply", msg);
+ notify->post();
+}
- type |= 2;
- }
+void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
+ // All remaining fetchers are still suspended, the player has shutdown
+ // any decoders that needed it.
- tmp->data()[1] = type;
+ uint32_t streamMask;
+ CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
- mDataSource->queueBuffer(tmp);
+ AString audioURI, videoURI, subtitleURI;
+ if (streamMask & STREAMTYPE_AUDIO) {
+ CHECK(msg->findString("audioURI", &audioURI));
+ }
+ if (streamMask & STREAMTYPE_VIDEO) {
+ CHECK(msg->findString("videoURI", &videoURI));
+ }
+ if (streamMask & STREAMTYPE_SUBTITLES) {
+ CHECK(msg->findString("subtitleURI", &subtitleURI));
}
- mDataSource->queueBuffer(buffer);
+ int64_t timeUs;
+ CHECK(msg->findInt64("timeUs", &timeUs));
- mPrevBandwidthIndex = bandwidthIndex;
- ++mSeqNumber;
+ if (timeUs < 0ll) {
+ timeUs = mLastDequeuedTimeUs;
+ }
+ mRealTimeBaseUs = ALooper::GetNowUs() - timeUs;
- postMonitorQueue();
-}
+ mStreamMask = streamMask;
+ mAudioURI = audioURI;
+ mVideoURI = videoURI;
+ mSubtitleURI = subtitleURI;
-void LiveSession::signalEOS(status_t err) {
- if (mInPreparationPhase && mNotify != NULL) {
- sp<AMessage> notify = mNotify->dup();
+ // Resume all existing fetchers and assign them packet sources.
+ for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+ const AString &uri = mFetcherInfos.keyAt(i);
- notify->setInt32(
- "what",
- err == ERROR_END_OF_STREAM
- ? kWhatPrepared : kWhatPreparationFailed);
+ uint32_t resumeMask = 0;
- if (err != ERROR_END_OF_STREAM) {
- notify->setInt32("err", err);
+ sp<AnotherPacketSource> audioSource;
+ if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) {
+ audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO);
+ resumeMask |= STREAMTYPE_AUDIO;
}
- notify->post();
-
- mInPreparationPhase = false;
- }
-
- mDataSource->queueEOS(err);
-}
-
-void LiveSession::onMonitorQueue() {
- if (mSeekTimeUs >= 0
- || mDataSource->countQueuedBuffers() < kMaxNumQueuedFragments) {
- onDownloadNext();
- } else {
- if (mInPreparationPhase) {
- if (mNotify != NULL) {
- sp<AMessage> notify = mNotify->dup();
- notify->setInt32("what", kWhatPrepared);
- notify->post();
- }
-
- mInPreparationPhase = false;
+ sp<AnotherPacketSource> videoSource;
+ if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) {
+ videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO);
+ resumeMask |= STREAMTYPE_VIDEO;
}
- postMonitorQueue(1000000ll);
- }
-}
+ sp<AnotherPacketSource> subtitleSource;
+ if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) {
+ subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES);
+ resumeMask |= STREAMTYPE_SUBTITLES;
+ }
-status_t LiveSession::decryptBuffer(
- size_t playlistIndex, const sp<ABuffer> &buffer) {
- sp<AMessage> itemMeta;
- bool found = false;
- AString method;
+ CHECK_NE(resumeMask, 0u);
- for (ssize_t i = playlistIndex; i >= 0; --i) {
- AString uri;
- CHECK(mPlaylist->itemAt(i, &uri, &itemMeta));
+ ALOGV("resuming fetchers for mask 0x%08x", resumeMask);
- if (itemMeta->findString("cipher-method", &method)) {
- found = true;
- break;
- }
- }
+ streamMask &= ~resumeMask;
- if (!found) {
- method = "NONE";
+ mFetcherInfos.valueAt(i).mFetcher->startAsync(
+ audioSource, videoSource, subtitleSource);
}
- if (method == "NONE") {
- return OK;
- } else if (!(method == "AES-128")) {
- ALOGE("Unsupported cipher method '%s'", method.c_str());
- return ERROR_UNSUPPORTED;
- }
+ // streamMask now only contains the types that need a new fetcher created.
- AString keyURI;
- if (!itemMeta->findString("cipher-uri", &keyURI)) {
- ALOGE("Missing key uri");
- return ERROR_MALFORMED;
+ if (streamMask != 0) {
+ ALOGV("creating new fetchers for mask 0x%08x", streamMask);
}
- ssize_t index = mAESKeyForURI.indexOfKey(keyURI);
-
- sp<ABuffer> key;
- if (index >= 0) {
- key = mAESKeyForURI.valueAt(index);
- } else {
- key = new ABuffer(16);
-
- sp<HTTPBase> keySource =
- HTTPBase::Create(
- (mFlags & kFlagIncognito)
- ? HTTPBase::kFlagIncognito
- : 0);
+ while (streamMask != 0) {
+ StreamType streamType = (StreamType)(streamMask & ~(streamMask - 1));
- if (mUIDValid) {
- keySource->setUID(mUID);
+ AString uri;
+ switch (streamType) {
+ case STREAMTYPE_AUDIO:
+ uri = audioURI;
+ break;
+ case STREAMTYPE_VIDEO:
+ uri = videoURI;
+ break;
+ case STREAMTYPE_SUBTITLES:
+ uri = subtitleURI;
+ break;
+ default:
+ TRESPASS();
}
- status_t err =
- keySource->connect(
- keyURI.c_str(),
- mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders);
-
- if (err == OK) {
- size_t offset = 0;
- while (offset < 16) {
- ssize_t n = keySource->readAt(
- offset, key->data() + offset, 16 - offset);
- if (n <= 0) {
- err = ERROR_IO;
- break;
- }
+ sp<PlaylistFetcher> fetcher = addFetcher(uri.c_str());
+ CHECK(fetcher != NULL);
- offset += n;
- }
- }
+ sp<AnotherPacketSource> audioSource;
+ if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) {
+ audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO);
+ audioSource->clear();
- if (err != OK) {
- ALOGE("failed to fetch cipher key from '%s'.", keyURI.c_str());
- return ERROR_IO;
+ streamMask &= ~STREAMTYPE_AUDIO;
}
- mAESKeyForURI.add(keyURI, key);
- }
+ sp<AnotherPacketSource> videoSource;
+ if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) {
+ videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO);
+ videoSource->clear();
- AES_KEY aes_key;
- if (AES_set_decrypt_key(key->data(), 128, &aes_key) != 0) {
- ALOGE("failed to set AES decryption key.");
- return UNKNOWN_ERROR;
- }
-
- unsigned char aes_ivec[16];
-
- AString iv;
- if (itemMeta->findString("cipher-iv", &iv)) {
- if ((!iv.startsWith("0x") && !iv.startsWith("0X"))
- || iv.size() != 16 * 2 + 2) {
- ALOGE("malformed cipher IV '%s'.", iv.c_str());
- return ERROR_MALFORMED;
+ streamMask &= ~STREAMTYPE_VIDEO;
}
- memset(aes_ivec, 0, sizeof(aes_ivec));
- for (size_t i = 0; i < 16; ++i) {
- char c1 = tolower(iv.c_str()[2 + 2 * i]);
- char c2 = tolower(iv.c_str()[3 + 2 * i]);
- if (!isxdigit(c1) || !isxdigit(c2)) {
- ALOGE("malformed cipher IV '%s'.", iv.c_str());
- return ERROR_MALFORMED;
- }
- uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10;
- uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10;
+ sp<AnotherPacketSource> subtitleSource;
+ if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) {
+ subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES);
+ subtitleSource->clear();
- aes_ivec[i] = nibble1 << 4 | nibble2;
+ streamMask &= ~STREAMTYPE_SUBTITLES;
}
- } else {
- memset(aes_ivec, 0, sizeof(aes_ivec));
- aes_ivec[15] = mSeqNumber & 0xff;
- aes_ivec[14] = (mSeqNumber >> 8) & 0xff;
- aes_ivec[13] = (mSeqNumber >> 16) & 0xff;
- aes_ivec[12] = (mSeqNumber >> 24) & 0xff;
+
+ fetcher->startAsync(audioSource, videoSource, subtitleSource, timeUs);
}
- AES_cbc_encrypt(
- buffer->data(), buffer->data(), buffer->size(),
- &aes_key, aes_ivec, AES_DECRYPT);
+ // All fetchers have now been started, the configuration change
+ // has completed.
- // hexdump(buffer->data(), buffer->size());
+ scheduleCheckBandwidthEvent();
- size_t n = buffer->size();
- CHECK_GT(n, 0u);
+ ALOGV("XXX configuration change completed.");
- size_t pad = buffer->data()[n - 1];
+ mReconfigurationInProgress = false;
- CHECK_GT(pad, 0u);
- CHECK_LE(pad, 16u);
- CHECK_GE((size_t)n, pad);
- for (size_t i = 0; i < pad; ++i) {
- CHECK_EQ((unsigned)buffer->data()[n - 1 - i], pad);
+ if (mDisconnectReplyID != 0) {
+ finishDisconnect();
}
+}
- n -= pad;
-
- buffer->setRange(buffer->offset(), n);
-
- return OK;
+void LiveSession::scheduleCheckBandwidthEvent() {
+ sp<AMessage> msg = new AMessage(kWhatCheckBandwidth, id());
+ msg->setInt32("generation", mCheckBandwidthGeneration);
+ msg->post(10000000ll);
}
-void LiveSession::postMonitorQueue(int64_t delayUs) {
- sp<AMessage> msg = new AMessage(kWhatMonitorQueue, id());
- msg->setInt32("generation", ++mMonitorQueueGeneration);
- msg->post(delayUs);
+void LiveSession::cancelCheckBandwidthEvent() {
+ ++mCheckBandwidthGeneration;
}
-void LiveSession::onSeek(const sp<AMessage> &msg) {
- int64_t timeUs;
- CHECK(msg->findInt64("timeUs", &timeUs));
+void LiveSession::onCheckBandwidth() {
+ if (mReconfigurationInProgress) {
+ scheduleCheckBandwidthEvent();
+ return;
+ }
+
+ size_t bandwidthIndex = getBandwidthIndex();
+ if (mPrevBandwidthIndex < 0
+ || bandwidthIndex != (size_t)mPrevBandwidthIndex) {
+ changeConfiguration(-1ll /* timeUs */, bandwidthIndex);
+ }
- mSeekTimeUs = timeUs;
- postMonitorQueue();
+ // Handling the kWhatCheckBandwidth even here does _not_ automatically
+ // schedule another one on return, only an explicit call to
+ // scheduleCheckBandwidthEvent will do that.
+ // This ensures that only one configuration change is ongoing at any
+ // one time, once that completes it'll schedule another check bandwidth
+ // event.
}
-status_t LiveSession::getDuration(int64_t *durationUs) const {
- Mutex::Autolock autoLock(mLock);
- *durationUs = mDurationUs;
+void LiveSession::postPrepared(status_t err) {
+ CHECK(mInPreparationPhase);
- return OK;
-}
+ sp<AMessage> notify = mNotify->dup();
+ if (err == OK || err == ERROR_END_OF_STREAM) {
+ notify->setInt32("what", kWhatPrepared);
+ } else {
+ notify->setInt32("what", kWhatPreparationFailed);
+ notify->setInt32("err", err);
+ }
-bool LiveSession::isSeekable() const {
- int64_t durationUs;
- return getDuration(&durationUs) == OK && durationUs >= 0;
-}
+ notify->post();
-bool LiveSession::hasDynamicDuration() const {
- return !mDurationFixed;
+ mInPreparationPhase = false;
}
} // namespace android
diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/httplive/LiveSession.h
index db44a33..99b480a8 100644
--- a/media/libstagefright/include/LiveSession.h
+++ b/media/libstagefright/httplive/LiveSession.h
@@ -25,10 +25,13 @@
namespace android {
struct ABuffer;
+struct AnotherPacketSource;
struct DataSource;
+struct HTTPBase;
struct LiveDataSource;
struct M3UParser;
-struct HTTPBase;
+struct PlaylistFetcher;
+struct Parcel;
struct LiveSession : public AHandler {
enum Flags {
@@ -39,24 +42,34 @@ struct LiveSession : public AHandler {
const sp<AMessage> &notify,
uint32_t flags = 0, bool uidValid = false, uid_t uid = 0);
- sp<DataSource> getDataSource();
+ enum StreamType {
+ STREAMTYPE_AUDIO = 1,
+ STREAMTYPE_VIDEO = 2,
+ STREAMTYPE_SUBTITLES = 4,
+ };
+ status_t dequeueAccessUnit(StreamType stream, sp<ABuffer> *accessUnit);
+
+ status_t getStreamFormat(StreamType stream, sp<AMessage> *format);
- void connect(
+ void connectAsync(
const char *url,
const KeyedVector<String8, String8> *headers = NULL);
- void disconnect();
+ status_t disconnect();
// Blocks until seek is complete.
- void seekTo(int64_t timeUs);
+ status_t seekTo(int64_t timeUs);
status_t getDuration(int64_t *durationUs) const;
+ status_t getTrackInfo(Parcel *reply) const;
+ status_t selectTrack(size_t index, bool select);
bool isSeekable() const;
bool hasDynamicDuration() const;
- // Posted notification's "what" field will carry one of the following:
enum {
+ kWhatStreamsChanged,
+ kWhatError,
kWhatPrepared,
kWhatPreparationFailed,
};
@@ -67,23 +80,31 @@ protected:
virtual void onMessageReceived(const sp<AMessage> &msg);
private:
- enum {
- kMaxNumQueuedFragments = 3,
- kMaxNumRetries = 5,
- };
+ friend struct PlaylistFetcher;
enum {
- kWhatConnect = 'conn',
- kWhatDisconnect = 'disc',
- kWhatMonitorQueue = 'moni',
- kWhatSeek = 'seek',
+ kWhatConnect = 'conn',
+ kWhatDisconnect = 'disc',
+ kWhatSeek = 'seek',
+ kWhatFetcherNotify = 'notf',
+ kWhatCheckBandwidth = 'bndw',
+ kWhatChangeConfiguration = 'chC0',
+ kWhatChangeConfiguration2 = 'chC2',
+ kWhatChangeConfiguration3 = 'chC3',
+ kWhatFinishDisconnect2 = 'fin2',
};
struct BandwidthItem {
- AString mURI;
+ size_t mPlaylistIndex;
unsigned long mBandwidth;
};
+ struct FetcherInfo {
+ sp<PlaylistFetcher> mFetcher;
+ int64_t mDurationUs;
+ bool mIsPrepared;
+ };
+
sp<AMessage> mNotify;
uint32_t mFlags;
bool mUIDValid;
@@ -91,71 +112,64 @@ private:
bool mInPreparationPhase;
- sp<LiveDataSource> mDataSource;
-
sp<HTTPBase> mHTTPDataSource;
+ KeyedVector<String8, String8> mExtraHeaders;
AString mMasterURL;
- KeyedVector<String8, String8> mExtraHeaders;
Vector<BandwidthItem> mBandwidthItems;
-
- KeyedVector<AString, sp<ABuffer> > mAESKeyForURI;
-
ssize_t mPrevBandwidthIndex;
- int64_t mLastPlaylistFetchTimeUs;
+
sp<M3UParser> mPlaylist;
- int32_t mSeqNumber;
- int64_t mSeekTimeUs;
- int32_t mNumRetries;
- bool mStartOfPlayback;
-
- mutable Mutex mLock;
- Condition mCondition;
- int64_t mDurationUs;
- bool mDurationFixed; // Duration has been determined once and for all.
- bool mSeekDone;
- bool mDisconnectPending;
-
- int32_t mMonitorQueueGeneration;
-
- enum RefreshState {
- INITIAL_MINIMUM_RELOAD_DELAY,
- FIRST_UNCHANGED_RELOAD_ATTEMPT,
- SECOND_UNCHANGED_RELOAD_ATTEMPT,
- THIRD_UNCHANGED_RELOAD_ATTEMPT
- };
- RefreshState mRefreshState;
- uint8_t mPlaylistHash[16];
+ KeyedVector<AString, FetcherInfo> mFetcherInfos;
+ AString mAudioURI, mVideoURI, mSubtitleURI;
+ uint32_t mStreamMask;
+
+ KeyedVector<StreamType, sp<AnotherPacketSource> > mPacketSources;
+
+ int32_t mCheckBandwidthGeneration;
+
+ size_t mContinuationCounter;
+ sp<AMessage> mContinuation;
+
+ int64_t mLastDequeuedTimeUs;
+ int64_t mRealTimeBaseUs;
+
+ bool mReconfigurationInProgress;
+ uint32_t mDisconnectReplyID;
+
+ sp<PlaylistFetcher> addFetcher(const char *uri);
void onConnect(const sp<AMessage> &msg);
- void onDisconnect();
- void onDownloadNext();
- void onMonitorQueue();
- void onSeek(const sp<AMessage> &msg);
+ status_t onSeek(const sp<AMessage> &msg);
+ void onFinishDisconnect2();
status_t fetchFile(
const char *url, sp<ABuffer> *out,
int64_t range_offset = 0, int64_t range_length = -1);
- sp<M3UParser> fetchPlaylist(const char *url, bool *unchanged);
+ sp<M3UParser> fetchPlaylist(
+ const char *url, uint8_t *curPlaylistHash, bool *unchanged);
+
size_t getBandwidthIndex();
- status_t decryptBuffer(
- size_t playlistIndex, const sp<ABuffer> &buffer);
+ static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *);
- void postMonitorQueue(int64_t delayUs = 0);
+ void changeConfiguration(
+ int64_t timeUs, size_t bandwidthIndex, bool pickTrack = false);
+ void onChangeConfiguration(const sp<AMessage> &msg);
+ void onChangeConfiguration2(const sp<AMessage> &msg);
+ void onChangeConfiguration3(const sp<AMessage> &msg);
- bool timeToRefreshPlaylist(int64_t nowUs) const;
+ void scheduleCheckBandwidthEvent();
+ void cancelCheckBandwidthEvent();
- static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *);
+ void onCheckBandwidth();
- // Returns the media time in us of the segment specified by seqNumber.
- // This is computed by summing the durations of all segments before it.
- int64_t getSegmentStartTimeUs(int32_t seqNumber) const;
+ void finishDisconnect();
- void signalEOS(status_t err);
+ void postPrepared(status_t err);
DISALLOW_EVIL_CONSTRUCTORS(LiveSession);
};
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
index 68bbca2..243888c 100644
--- a/media/libstagefright/httplive/M3UParser.cpp
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -18,14 +18,217 @@
#define LOG_TAG "M3UParser"
#include <utils/Log.h>
-#include "include/M3UParser.h"
-
+#include "M3UParser.h"
+#include <binder/Parcel.h>
+#include <cutils/properties.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/MediaErrors.h>
+#include <media/mediaplayer.h>
namespace android {
+struct M3UParser::MediaGroup : public RefBase {
+ enum Type {
+ TYPE_AUDIO,
+ TYPE_VIDEO,
+ TYPE_SUBS,
+ };
+
+ enum FlagBits {
+ FLAG_AUTOSELECT = 1,
+ FLAG_DEFAULT = 2,
+ FLAG_FORCED = 4,
+ FLAG_HAS_LANGUAGE = 8,
+ FLAG_HAS_URI = 16,
+ };
+
+ MediaGroup(Type type);
+
+ Type type() const;
+
+ status_t addMedia(
+ const char *name,
+ const char *uri,
+ const char *language,
+ uint32_t flags);
+
+ bool getActiveURI(AString *uri) const;
+
+ void pickRandomMediaItems();
+ status_t selectTrack(size_t index, bool select);
+ void getTrackInfo(Parcel* reply) const;
+ size_t countTracks() const;
+
+protected:
+ virtual ~MediaGroup();
+
+private:
+ struct Media {
+ AString mName;
+ AString mURI;
+ AString mLanguage;
+ uint32_t mFlags;
+ };
+
+ Type mType;
+ Vector<Media> mMediaItems;
+
+ ssize_t mSelectedIndex;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MediaGroup);
+};
+
+M3UParser::MediaGroup::MediaGroup(Type type)
+ : mType(type),
+ mSelectedIndex(-1) {
+}
+
+M3UParser::MediaGroup::~MediaGroup() {
+}
+
+M3UParser::MediaGroup::Type M3UParser::MediaGroup::type() const {
+ return mType;
+}
+
+status_t M3UParser::MediaGroup::addMedia(
+ const char *name,
+ const char *uri,
+ const char *language,
+ uint32_t flags) {
+ mMediaItems.push();
+ Media &item = mMediaItems.editItemAt(mMediaItems.size() - 1);
+
+ item.mName = name;
+
+ if (uri) {
+ item.mURI = uri;
+ }
+
+ if (language) {
+ item.mLanguage = language;
+ }
+
+ item.mFlags = flags;
+
+ return OK;
+}
+
+void M3UParser::MediaGroup::pickRandomMediaItems() {
+#if 1
+ switch (mType) {
+ case TYPE_AUDIO:
+ {
+ char value[PROPERTY_VALUE_MAX];
+ if (property_get("media.httplive.audio-index", value, NULL)) {
+ char *end;
+ mSelectedIndex = strtoul(value, &end, 10);
+ CHECK(end > value && *end == '\0');
+
+ if (mSelectedIndex >= mMediaItems.size()) {
+ mSelectedIndex = mMediaItems.size() - 1;
+ }
+ } else {
+ mSelectedIndex = 0;
+ }
+ break;
+ }
+
+ case TYPE_VIDEO:
+ {
+ mSelectedIndex = 0;
+ break;
+ }
+
+ case TYPE_SUBS:
+ {
+ mSelectedIndex = -1;
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+#else
+ mSelectedIndex = (rand() * mMediaItems.size()) / RAND_MAX;
+#endif
+}
+
+status_t M3UParser::MediaGroup::selectTrack(size_t index, bool select) {
+ if (mType != TYPE_SUBS) {
+ ALOGE("only select subtitile tracks for now!");
+ return INVALID_OPERATION;
+ }
+
+ if (select) {
+ if (index >= mMediaItems.size()) {
+ ALOGE("track %d does not exist", index);
+ return INVALID_OPERATION;
+ }
+ if (mSelectedIndex == index) {
+ ALOGE("track %d already selected", index);
+ return BAD_VALUE;
+ }
+ ALOGV("selected track %d", index);
+ mSelectedIndex = index;
+ } else {
+ if (mSelectedIndex != index) {
+ ALOGE("track %d is not selected", index);
+ return BAD_VALUE;
+ }
+ ALOGV("unselected track %d", index);
+ mSelectedIndex = -1;
+ }
+
+ return OK;
+}
+
+void M3UParser::MediaGroup::getTrackInfo(Parcel* reply) const {
+ for (size_t i = 0; i < mMediaItems.size(); ++i) {
+ reply->writeInt32(2); // 2 fields
+
+ if (mType == TYPE_AUDIO) {
+ reply->writeInt32(MEDIA_TRACK_TYPE_AUDIO);
+ } else if (mType == TYPE_VIDEO) {
+ reply->writeInt32(MEDIA_TRACK_TYPE_VIDEO);
+ } else if (mType == TYPE_SUBS) {
+ reply->writeInt32(MEDIA_TRACK_TYPE_SUBTITLE);
+ } else {
+ reply->writeInt32(MEDIA_TRACK_TYPE_UNKNOWN);
+ }
+
+ const Media &item = mMediaItems.itemAt(i);
+ const char *lang = item.mLanguage.empty() ? "und" : item.mLanguage.c_str();
+ reply->writeString16(String16(lang));
+
+ if (mType == TYPE_SUBS) {
+ // TO-DO: pass in a MediaFormat instead
+ reply->writeInt32(!!(item.mFlags & MediaGroup::FLAG_AUTOSELECT));
+ reply->writeInt32(!!(item.mFlags & MediaGroup::FLAG_DEFAULT));
+ reply->writeInt32(!!(item.mFlags & MediaGroup::FLAG_FORCED));
+ }
+ }
+}
+
+size_t M3UParser::MediaGroup::countTracks() const {
+ return mMediaItems.size();
+}
+
+bool M3UParser::MediaGroup::getActiveURI(AString *uri) const {
+ for (size_t i = 0; i < mMediaItems.size(); ++i) {
+ if (mSelectedIndex >= 0 && i == (size_t)mSelectedIndex) {
+ const Media &item = mMediaItems.itemAt(i);
+
+ *uri = item.mURI;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
M3UParser::M3UParser(
const char *baseURI, const void *data, size_t size)
: mInitCheck(NO_INIT),
@@ -33,7 +236,8 @@ M3UParser::M3UParser(
mIsExtM3U(false),
mIsVariantPlaylist(false),
mIsComplete(false),
- mIsEvent(false) {
+ mIsEvent(false),
+ mSelectedIndex(-1) {
mInitCheck = parse(data, size);
}
@@ -92,6 +296,91 @@ bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) {
return true;
}
+void M3UParser::pickRandomMediaItems() {
+ for (size_t i = 0; i < mMediaGroups.size(); ++i) {
+ mMediaGroups.valueAt(i)->pickRandomMediaItems();
+ }
+}
+
+status_t M3UParser::selectTrack(size_t index, bool select) {
+ for (size_t i = 0, ii = index; i < mMediaGroups.size(); ++i) {
+ sp<MediaGroup> group = mMediaGroups.valueAt(i);
+ size_t tracks = group->countTracks();
+ if (ii < tracks) {
+ status_t err = group->selectTrack(ii, select);
+ if (err == OK) {
+ mSelectedIndex = select ? index : -1;
+ }
+ return err;
+ }
+ ii -= tracks;
+ }
+ return INVALID_OPERATION;
+}
+
+status_t M3UParser::getTrackInfo(Parcel* reply) const {
+ size_t trackCount = 0;
+ for (size_t i = 0; i < mMediaGroups.size(); ++i) {
+ trackCount += mMediaGroups.valueAt(i)->countTracks();
+ }
+ reply->writeInt32(trackCount);
+
+ for (size_t i = 0; i < mMediaGroups.size(); ++i) {
+ mMediaGroups.valueAt(i)->getTrackInfo(reply);
+ }
+ return OK;
+}
+
+ssize_t M3UParser::getSelectedIndex() const {
+ return mSelectedIndex;
+}
+
+bool M3UParser::getTypeURI(size_t index, const char *key, AString *uri) const {
+ if (!mIsVariantPlaylist) {
+ *uri = mBaseURI;
+
+ // Assume media without any more specific attribute contains
+ // audio and video, but no subtitles.
+ return !strcmp("audio", key) || !strcmp("video", key);
+ }
+
+ CHECK_LT(index, mItems.size());
+
+ sp<AMessage> meta = mItems.itemAt(index).mMeta;
+
+ AString groupID;
+ if (!meta->findString(key, &groupID)) {
+ *uri = mItems.itemAt(index).mURI;
+
+ // Assume media without any more specific attribute contains
+ // audio and video, but no subtitles.
+ return !strcmp("audio", key) || !strcmp("video", key);
+ }
+
+ sp<MediaGroup> group = mMediaGroups.valueFor(groupID);
+ if (!group->getActiveURI(uri)) {
+ return false;
+ }
+
+ if ((*uri).empty()) {
+ *uri = mItems.itemAt(index).mURI;
+ }
+
+ return true;
+}
+
+bool M3UParser::getAudioURI(size_t index, AString *uri) const {
+ return getTypeURI(index, "audio", uri);
+}
+
+bool M3UParser::getVideoURI(size_t index, AString *uri) const {
+ return getTypeURI(index, "video", uri);
+}
+
+bool M3UParser::getSubtitleURI(size_t index, AString *uri) const {
+ return getTypeURI(index, "subtitles", uri);
+}
+
static bool MakeURL(const char *baseURL, const char *url, AString *out) {
out->clear();
@@ -241,6 +530,8 @@ status_t M3UParser::parse(const void *_data, size_t size) {
segmentRangeOffset = offset + length;
}
+ } else if (line.startsWith("#EXT-X-MEDIA")) {
+ err = parseMedia(line);
}
if (err != OK) {
@@ -322,9 +613,31 @@ status_t M3UParser::parseMetaDataDuration(
return OK;
}
-// static
+// Find the next occurence of the character "what" at or after "offset",
+// but ignore occurences between quotation marks.
+// Return the index of the occurrence or -1 if not found.
+static ssize_t FindNextUnquoted(
+ const AString &line, char what, size_t offset) {
+ CHECK_NE((int)what, (int)'"');
+
+ bool quoted = false;
+ while (offset < line.size()) {
+ char c = line.c_str()[offset];
+
+ if (c == '"') {
+ quoted = !quoted;
+ } else if (c == what && !quoted) {
+ return offset;
+ }
+
+ ++offset;
+ }
+
+ return -1;
+}
+
status_t M3UParser::parseStreamInf(
- const AString &line, sp<AMessage> *meta) {
+ const AString &line, sp<AMessage> *meta) const {
ssize_t colonPos = line.find(":");
if (colonPos < 0) {
@@ -334,7 +647,7 @@ status_t M3UParser::parseStreamInf(
size_t offset = colonPos + 1;
while (offset < line.size()) {
- ssize_t end = line.find(",", offset);
+ ssize_t end = FindNextUnquoted(line, ',', offset);
if (end < 0) {
end = line.size();
}
@@ -371,33 +684,35 @@ status_t M3UParser::parseStreamInf(
*meta = new AMessage;
}
(*meta)->setInt32("bandwidth", x);
- }
- }
+ } else if (!strcasecmp("audio", key.c_str())
+ || !strcasecmp("video", key.c_str())
+ || !strcasecmp("subtitles", key.c_str())) {
+ if (val.size() < 2
+ || val.c_str()[0] != '"'
+ || val.c_str()[val.size() - 1] != '"') {
+ ALOGE("Expected quoted string for %s attribute, "
+ "got '%s' instead.",
+ key.c_str(), val.c_str());
+
+ return ERROR_MALFORMED;
+ }
- return OK;
-}
+ AString groupID(val, 1, val.size() - 2);
+ ssize_t groupIndex = mMediaGroups.indexOfKey(groupID);
-// Find the next occurence of the character "what" at or after "offset",
-// but ignore occurences between quotation marks.
-// Return the index of the occurrence or -1 if not found.
-static ssize_t FindNextUnquoted(
- const AString &line, char what, size_t offset) {
- CHECK_NE((int)what, (int)'"');
+ if (groupIndex < 0) {
+ ALOGE("Undefined media group '%s' referenced in stream info.",
+ groupID.c_str());
- bool quoted = false;
- while (offset < line.size()) {
- char c = line.c_str()[offset];
+ return ERROR_MALFORMED;
+ }
- if (c == '"') {
- quoted = !quoted;
- } else if (c == what && !quoted) {
- return offset;
+ key.tolower();
+ (*meta)->setString(key.c_str(), groupID.c_str());
}
-
- ++offset;
}
- return -1;
+ return OK;
}
// static
@@ -515,6 +830,234 @@ status_t M3UParser::parseByteRange(
return OK;
}
+status_t M3UParser::parseMedia(const AString &line) {
+ ssize_t colonPos = line.find(":");
+
+ if (colonPos < 0) {
+ return ERROR_MALFORMED;
+ }
+
+ bool haveGroupType = false;
+ MediaGroup::Type groupType = MediaGroup::TYPE_AUDIO;
+
+ bool haveGroupID = false;
+ AString groupID;
+
+ bool haveGroupLanguage = false;
+ AString groupLanguage;
+
+ bool haveGroupName = false;
+ AString groupName;
+
+ bool haveGroupAutoselect = false;
+ bool groupAutoselect = false;
+
+ bool haveGroupDefault = false;
+ bool groupDefault = false;
+
+ bool haveGroupForced = false;
+ bool groupForced = false;
+
+ bool haveGroupURI = false;
+ AString groupURI;
+
+ size_t offset = colonPos + 1;
+
+ while (offset < line.size()) {
+ ssize_t end = FindNextUnquoted(line, ',', offset);
+ if (end < 0) {
+ end = line.size();
+ }
+
+ AString attr(line, offset, end - offset);
+ attr.trim();
+
+ offset = end + 1;
+
+ ssize_t equalPos = attr.find("=");
+ if (equalPos < 0) {
+ continue;
+ }
+
+ AString key(attr, 0, equalPos);
+ key.trim();
+
+ AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
+ val.trim();
+
+ ALOGV("key=%s value=%s", key.c_str(), val.c_str());
+
+ if (!strcasecmp("type", key.c_str())) {
+ if (!strcasecmp("subtitles", val.c_str())) {
+ groupType = MediaGroup::TYPE_SUBS;
+ } else if (!strcasecmp("audio", val.c_str())) {
+ groupType = MediaGroup::TYPE_AUDIO;
+ } else if (!strcasecmp("video", val.c_str())) {
+ groupType = MediaGroup::TYPE_VIDEO;
+ } else {
+ ALOGE("Invalid media group type '%s'", val.c_str());
+ return ERROR_MALFORMED;
+ }
+
+ haveGroupType = true;
+ } else if (!strcasecmp("group-id", key.c_str())) {
+ if (val.size() < 2
+ || val.c_str()[0] != '"'
+ || val.c_str()[val.size() - 1] != '"') {
+ ALOGE("Expected quoted string for GROUP-ID, got '%s' instead.",
+ val.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ groupID.setTo(val, 1, val.size() - 2);
+ haveGroupID = true;
+ } else if (!strcasecmp("language", key.c_str())) {
+ if (val.size() < 2
+ || val.c_str()[0] != '"'
+ || val.c_str()[val.size() - 1] != '"') {
+ ALOGE("Expected quoted string for LANGUAGE, got '%s' instead.",
+ val.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ groupLanguage.setTo(val, 1, val.size() - 2);
+ haveGroupLanguage = true;
+ } else if (!strcasecmp("name", key.c_str())) {
+ if (val.size() < 2
+ || val.c_str()[0] != '"'
+ || val.c_str()[val.size() - 1] != '"') {
+ ALOGE("Expected quoted string for NAME, got '%s' instead.",
+ val.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ groupName.setTo(val, 1, val.size() - 2);
+ haveGroupName = true;
+ } else if (!strcasecmp("autoselect", key.c_str())) {
+ groupAutoselect = false;
+ if (!strcasecmp("YES", val.c_str())) {
+ groupAutoselect = true;
+ } else if (!strcasecmp("NO", val.c_str())) {
+ groupAutoselect = false;
+ } else {
+ ALOGE("Expected YES or NO for AUTOSELECT attribute, "
+ "got '%s' instead.",
+ val.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ haveGroupAutoselect = true;
+ } else if (!strcasecmp("default", key.c_str())) {
+ groupDefault = false;
+ if (!strcasecmp("YES", val.c_str())) {
+ groupDefault = true;
+ } else if (!strcasecmp("NO", val.c_str())) {
+ groupDefault = false;
+ } else {
+ ALOGE("Expected YES or NO for DEFAULT attribute, "
+ "got '%s' instead.",
+ val.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ haveGroupDefault = true;
+ } else if (!strcasecmp("forced", key.c_str())) {
+ groupForced = false;
+ if (!strcasecmp("YES", val.c_str())) {
+ groupForced = true;
+ } else if (!strcasecmp("NO", val.c_str())) {
+ groupForced = false;
+ } else {
+ ALOGE("Expected YES or NO for FORCED attribute, "
+ "got '%s' instead.",
+ val.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ haveGroupForced = true;
+ } else if (!strcasecmp("uri", key.c_str())) {
+ if (val.size() < 2
+ || val.c_str()[0] != '"'
+ || val.c_str()[val.size() - 1] != '"') {
+ ALOGE("Expected quoted string for URI, got '%s' instead.",
+ val.c_str());
+
+ return ERROR_MALFORMED;
+ }
+
+ AString tmp(val, 1, val.size() - 2);
+
+ if (!MakeURL(mBaseURI.c_str(), tmp.c_str(), &groupURI)) {
+ ALOGI("Failed to make absolute URI from '%s'.", tmp.c_str());
+ }
+
+ haveGroupURI = true;
+ }
+ }
+
+ if (!haveGroupType || !haveGroupID || !haveGroupName) {
+ ALOGE("Incomplete EXT-X-MEDIA element.");
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t flags = 0;
+ if (haveGroupAutoselect && groupAutoselect) {
+ flags |= MediaGroup::FLAG_AUTOSELECT;
+ }
+ if (haveGroupDefault && groupDefault) {
+ flags |= MediaGroup::FLAG_DEFAULT;
+ }
+ if (haveGroupForced) {
+ if (groupType != MediaGroup::TYPE_SUBS) {
+ ALOGE("The FORCED attribute MUST not be present on anything "
+ "but SUBS media.");
+
+ return ERROR_MALFORMED;
+ }
+
+ if (groupForced) {
+ flags |= MediaGroup::FLAG_FORCED;
+ }
+ }
+ if (haveGroupLanguage) {
+ flags |= MediaGroup::FLAG_HAS_LANGUAGE;
+ }
+ if (haveGroupURI) {
+ flags |= MediaGroup::FLAG_HAS_URI;
+ }
+
+ ssize_t groupIndex = mMediaGroups.indexOfKey(groupID);
+ sp<MediaGroup> group;
+
+ if (groupIndex < 0) {
+ group = new MediaGroup(groupType);
+ mMediaGroups.add(groupID, group);
+ } else {
+ group = mMediaGroups.valueAt(groupIndex);
+
+ if (group->type() != groupType) {
+ ALOGE("Attempt to put media item under group of different type "
+ "(groupType = %d, item type = %d",
+ group->type(),
+ groupType);
+
+ return ERROR_MALFORMED;
+ }
+ }
+
+ return group->addMedia(
+ groupName.c_str(),
+ haveGroupURI ? groupURI.c_str() : NULL,
+ haveGroupLanguage ? groupLanguage.c_str() : NULL,
+ flags);
+}
+
// static
status_t M3UParser::ParseInt32(const char *s, int32_t *x) {
char *end;
diff --git a/media/libstagefright/include/M3UParser.h b/media/libstagefright/httplive/M3UParser.h
index 2d2f50f..5248004 100644
--- a/media/libstagefright/include/M3UParser.h
+++ b/media/libstagefright/httplive/M3UParser.h
@@ -40,10 +40,21 @@ struct M3UParser : public RefBase {
size_t size();
bool itemAt(size_t index, AString *uri, sp<AMessage> *meta = NULL);
+ void pickRandomMediaItems();
+ status_t selectTrack(size_t index, bool select);
+ status_t getTrackInfo(Parcel* reply) const;
+ ssize_t getSelectedIndex() const;
+
+ bool getAudioURI(size_t index, AString *uri) const;
+ bool getVideoURI(size_t index, AString *uri) const;
+ bool getSubtitleURI(size_t index, AString *uri) const;
+
protected:
virtual ~M3UParser();
private:
+ struct MediaGroup;
+
struct Item {
AString mURI;
sp<AMessage> mMeta;
@@ -59,6 +70,10 @@ private:
sp<AMessage> mMeta;
Vector<Item> mItems;
+ ssize_t mSelectedIndex;
+
+ // Media groups keyed by group ID.
+ KeyedVector<AString, sp<MediaGroup> > mMediaGroups;
status_t parse(const void *data, size_t size);
@@ -68,8 +83,8 @@ private:
static status_t parseMetaDataDuration(
const AString &line, sp<AMessage> *meta, const char *key);
- static status_t parseStreamInf(
- const AString &line, sp<AMessage> *meta);
+ status_t parseStreamInf(
+ const AString &line, sp<AMessage> *meta) const;
static status_t parseCipherInfo(
const AString &line, sp<AMessage> *meta, const AString &baseURI);
@@ -78,6 +93,10 @@ private:
const AString &line, uint64_t curOffset,
uint64_t *length, uint64_t *offset);
+ status_t parseMedia(const AString &line);
+
+ bool getTypeURI(size_t index, const char *key, AString *uri) const;
+
static status_t ParseInt32(const char *s, int32_t *x);
static status_t ParseDouble(const char *s, double *x);
diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp
new file mode 100644
index 0000000..973b779
--- /dev/null
+++ b/media/libstagefright/httplive/PlaylistFetcher.cpp
@@ -0,0 +1,976 @@
+/*
+ * Copyright (C) 2012 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 "PlaylistFetcher"
+#include <utils/Log.h>
+
+#include "PlaylistFetcher.h"
+
+#include "LiveDataSource.h"
+#include "LiveSession.h"
+#include "M3UParser.h"
+
+#include "include/avc_utils.h"
+#include "include/HTTPBase.h"
+#include "include/ID3.h"
+#include "mpeg2ts/AnotherPacketSource.h"
+
+#include <media/IStreamSource.h>
+#include <media/stagefright/foundation/ABitReader.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
+
+#include <ctype.h>
+#include <openssl/aes.h>
+#include <openssl/md5.h>
+
+namespace android {
+
+// static
+const int64_t PlaylistFetcher::kMinBufferedDurationUs = 10000000ll;
+
+PlaylistFetcher::PlaylistFetcher(
+ const sp<AMessage> &notify,
+ const sp<LiveSession> &session,
+ const char *uri)
+ : mNotify(notify),
+ mSession(session),
+ mURI(uri),
+ mStreamTypeMask(0),
+ mStartTimeUs(-1ll),
+ mLastPlaylistFetchTimeUs(-1ll),
+ mSeqNumber(-1),
+ mNumRetries(0),
+ mStartup(true),
+ mNextPTSTimeUs(-1ll),
+ mMonitorQueueGeneration(0),
+ mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY),
+ mFirstPTSValid(false),
+ mAbsoluteTimeAnchorUs(0ll) {
+ memset(mPlaylistHash, 0, sizeof(mPlaylistHash));
+}
+
+PlaylistFetcher::~PlaylistFetcher() {
+}
+
+int64_t PlaylistFetcher::getSegmentStartTimeUs(int32_t seqNumber) const {
+ CHECK(mPlaylist != NULL);
+
+ int32_t firstSeqNumberInPlaylist;
+ if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
+ "media-sequence", &firstSeqNumberInPlaylist)) {
+ firstSeqNumberInPlaylist = 0;
+ }
+
+ int32_t lastSeqNumberInPlaylist =
+ firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
+
+ CHECK_GE(seqNumber, firstSeqNumberInPlaylist);
+ CHECK_LE(seqNumber, lastSeqNumberInPlaylist);
+
+ int64_t segmentStartUs = 0ll;
+ for (int32_t index = 0;
+ index < seqNumber - firstSeqNumberInPlaylist; ++index) {
+ sp<AMessage> itemMeta;
+ CHECK(mPlaylist->itemAt(
+ index, NULL /* uri */, &itemMeta));
+
+ int64_t itemDurationUs;
+ CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+
+ segmentStartUs += itemDurationUs;
+ }
+
+ return segmentStartUs;
+}
+
+bool PlaylistFetcher::timeToRefreshPlaylist(int64_t nowUs) const {
+ if (mPlaylist == NULL) {
+ CHECK_EQ((int)mRefreshState, (int)INITIAL_MINIMUM_RELOAD_DELAY);
+ return true;
+ }
+
+ int32_t targetDurationSecs;
+ CHECK(mPlaylist->meta()->findInt32("target-duration", &targetDurationSecs));
+
+ int64_t targetDurationUs = targetDurationSecs * 1000000ll;
+
+ int64_t minPlaylistAgeUs;
+
+ switch (mRefreshState) {
+ case INITIAL_MINIMUM_RELOAD_DELAY:
+ {
+ size_t n = mPlaylist->size();
+ if (n > 0) {
+ sp<AMessage> itemMeta;
+ CHECK(mPlaylist->itemAt(n - 1, NULL /* uri */, &itemMeta));
+
+ int64_t itemDurationUs;
+ CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+
+ minPlaylistAgeUs = itemDurationUs;
+ break;
+ }
+
+ // fall through
+ }
+
+ case FIRST_UNCHANGED_RELOAD_ATTEMPT:
+ {
+ minPlaylistAgeUs = targetDurationUs / 2;
+ break;
+ }
+
+ case SECOND_UNCHANGED_RELOAD_ATTEMPT:
+ {
+ minPlaylistAgeUs = (targetDurationUs * 3) / 2;
+ break;
+ }
+
+ case THIRD_UNCHANGED_RELOAD_ATTEMPT:
+ {
+ minPlaylistAgeUs = targetDurationUs * 3;
+ break;
+ }
+
+ default:
+ TRESPASS();
+ break;
+ }
+
+ return mLastPlaylistFetchTimeUs + minPlaylistAgeUs <= nowUs;
+}
+
+status_t PlaylistFetcher::decryptBuffer(
+ size_t playlistIndex, const sp<ABuffer> &buffer) {
+ sp<AMessage> itemMeta;
+ bool found = false;
+ AString method;
+
+ for (ssize_t i = playlistIndex; i >= 0; --i) {
+ AString uri;
+ CHECK(mPlaylist->itemAt(i, &uri, &itemMeta));
+
+ if (itemMeta->findString("cipher-method", &method)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ method = "NONE";
+ }
+
+ if (method == "NONE") {
+ return OK;
+ } else if (!(method == "AES-128")) {
+ ALOGE("Unsupported cipher method '%s'", method.c_str());
+ return ERROR_UNSUPPORTED;
+ }
+
+ AString keyURI;
+ if (!itemMeta->findString("cipher-uri", &keyURI)) {
+ ALOGE("Missing key uri");
+ return ERROR_MALFORMED;
+ }
+
+ ssize_t index = mAESKeyForURI.indexOfKey(keyURI);
+
+ sp<ABuffer> key;
+ if (index >= 0) {
+ key = mAESKeyForURI.valueAt(index);
+ } else {
+ status_t err = mSession->fetchFile(keyURI.c_str(), &key);
+
+ if (err != OK) {
+ ALOGE("failed to fetch cipher key from '%s'.", keyURI.c_str());
+ return ERROR_IO;
+ } else if (key->size() != 16) {
+ ALOGE("key file '%s' wasn't 16 bytes in size.", keyURI.c_str());
+ return ERROR_MALFORMED;
+ }
+
+ mAESKeyForURI.add(keyURI, key);
+ }
+
+ AES_KEY aes_key;
+ if (AES_set_decrypt_key(key->data(), 128, &aes_key) != 0) {
+ ALOGE("failed to set AES decryption key.");
+ return UNKNOWN_ERROR;
+ }
+
+ unsigned char aes_ivec[16];
+
+ AString iv;
+ if (itemMeta->findString("cipher-iv", &iv)) {
+ if ((!iv.startsWith("0x") && !iv.startsWith("0X"))
+ || iv.size() != 16 * 2 + 2) {
+ ALOGE("malformed cipher IV '%s'.", iv.c_str());
+ return ERROR_MALFORMED;
+ }
+
+ memset(aes_ivec, 0, sizeof(aes_ivec));
+ for (size_t i = 0; i < 16; ++i) {
+ char c1 = tolower(iv.c_str()[2 + 2 * i]);
+ char c2 = tolower(iv.c_str()[3 + 2 * i]);
+ if (!isxdigit(c1) || !isxdigit(c2)) {
+ ALOGE("malformed cipher IV '%s'.", iv.c_str());
+ return ERROR_MALFORMED;
+ }
+ uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10;
+ uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10;
+
+ aes_ivec[i] = nibble1 << 4 | nibble2;
+ }
+ } else {
+ memset(aes_ivec, 0, sizeof(aes_ivec));
+ aes_ivec[15] = mSeqNumber & 0xff;
+ aes_ivec[14] = (mSeqNumber >> 8) & 0xff;
+ aes_ivec[13] = (mSeqNumber >> 16) & 0xff;
+ aes_ivec[12] = (mSeqNumber >> 24) & 0xff;
+ }
+
+ AES_cbc_encrypt(
+ buffer->data(), buffer->data(), buffer->size(),
+ &aes_key, aes_ivec, AES_DECRYPT);
+
+ // hexdump(buffer->data(), buffer->size());
+
+ size_t n = buffer->size();
+ CHECK_GT(n, 0u);
+
+ size_t pad = buffer->data()[n - 1];
+
+ CHECK_GT(pad, 0u);
+ CHECK_LE(pad, 16u);
+ CHECK_GE((size_t)n, pad);
+ for (size_t i = 0; i < pad; ++i) {
+ CHECK_EQ((unsigned)buffer->data()[n - 1 - i], pad);
+ }
+
+ n -= pad;
+
+ buffer->setRange(buffer->offset(), n);
+
+ return OK;
+}
+
+void PlaylistFetcher::postMonitorQueue(int64_t delayUs) {
+ sp<AMessage> msg = new AMessage(kWhatMonitorQueue, id());
+ msg->setInt32("generation", mMonitorQueueGeneration);
+ msg->post(delayUs);
+}
+
+void PlaylistFetcher::cancelMonitorQueue() {
+ ++mMonitorQueueGeneration;
+}
+
+void PlaylistFetcher::startAsync(
+ const sp<AnotherPacketSource> &audioSource,
+ const sp<AnotherPacketSource> &videoSource,
+ const sp<AnotherPacketSource> &subtitleSource,
+ int64_t startTimeUs) {
+ sp<AMessage> msg = new AMessage(kWhatStart, id());
+
+ uint32_t streamTypeMask = 0ul;
+
+ if (audioSource != NULL) {
+ msg->setPointer("audioSource", audioSource.get());
+ streamTypeMask |= LiveSession::STREAMTYPE_AUDIO;
+ }
+
+ if (videoSource != NULL) {
+ msg->setPointer("videoSource", videoSource.get());
+ streamTypeMask |= LiveSession::STREAMTYPE_VIDEO;
+ }
+
+ if (subtitleSource != NULL) {
+ msg->setPointer("subtitleSource", subtitleSource.get());
+ streamTypeMask |= LiveSession::STREAMTYPE_SUBTITLES;
+ }
+
+ msg->setInt32("streamTypeMask", streamTypeMask);
+ msg->setInt64("startTimeUs", startTimeUs);
+ msg->post();
+}
+
+void PlaylistFetcher::pauseAsync() {
+ (new AMessage(kWhatPause, id()))->post();
+}
+
+void PlaylistFetcher::stopAsync() {
+ (new AMessage(kWhatStop, id()))->post();
+}
+
+void PlaylistFetcher::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatStart:
+ {
+ status_t err = onStart(msg);
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatStarted);
+ notify->setInt32("err", err);
+ notify->post();
+ break;
+ }
+
+ case kWhatPause:
+ {
+ onPause();
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatPaused);
+ notify->post();
+ break;
+ }
+
+ case kWhatStop:
+ {
+ onStop();
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatStopped);
+ notify->post();
+ break;
+ }
+
+ case kWhatMonitorQueue:
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mMonitorQueueGeneration) {
+ // Stale event
+ break;
+ }
+
+ onMonitorQueue();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+status_t PlaylistFetcher::onStart(const sp<AMessage> &msg) {
+ mPacketSources.clear();
+
+ uint32_t streamTypeMask;
+ CHECK(msg->findInt32("streamTypeMask", (int32_t *)&streamTypeMask));
+
+ int64_t startTimeUs;
+ CHECK(msg->findInt64("startTimeUs", &startTimeUs));
+
+ if (streamTypeMask & LiveSession::STREAMTYPE_AUDIO) {
+ void *ptr;
+ CHECK(msg->findPointer("audioSource", &ptr));
+
+ mPacketSources.add(
+ LiveSession::STREAMTYPE_AUDIO,
+ static_cast<AnotherPacketSource *>(ptr));
+ }
+
+ if (streamTypeMask & LiveSession::STREAMTYPE_VIDEO) {
+ void *ptr;
+ CHECK(msg->findPointer("videoSource", &ptr));
+
+ mPacketSources.add(
+ LiveSession::STREAMTYPE_VIDEO,
+ static_cast<AnotherPacketSource *>(ptr));
+ }
+
+ if (streamTypeMask & LiveSession::STREAMTYPE_SUBTITLES) {
+ void *ptr;
+ CHECK(msg->findPointer("subtitleSource", &ptr));
+
+ mPacketSources.add(
+ LiveSession::STREAMTYPE_SUBTITLES,
+ static_cast<AnotherPacketSource *>(ptr));
+ }
+
+ mStreamTypeMask = streamTypeMask;
+ mStartTimeUs = startTimeUs;
+
+ if (mStartTimeUs >= 0ll) {
+ mSeqNumber = -1;
+ mStartup = true;
+ }
+
+ postMonitorQueue();
+
+ return OK;
+}
+
+void PlaylistFetcher::onPause() {
+ cancelMonitorQueue();
+
+ mPacketSources.clear();
+ mStreamTypeMask = 0;
+}
+
+void PlaylistFetcher::onStop() {
+ cancelMonitorQueue();
+
+ for (size_t i = 0; i < mPacketSources.size(); ++i) {
+ mPacketSources.valueAt(i)->clear();
+ }
+
+ mPacketSources.clear();
+ mStreamTypeMask = 0;
+}
+
+void PlaylistFetcher::notifyError(status_t err) {
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatError);
+ notify->setInt32("err", err);
+ notify->post();
+}
+
+void PlaylistFetcher::queueDiscontinuity(
+ ATSParser::DiscontinuityType type, const sp<AMessage> &extra) {
+ for (size_t i = 0; i < mPacketSources.size(); ++i) {
+ mPacketSources.valueAt(i)->queueDiscontinuity(type, extra);
+ }
+}
+
+void PlaylistFetcher::onMonitorQueue() {
+ bool downloadMore = false;
+
+ status_t finalResult;
+ if (mStreamTypeMask == LiveSession::STREAMTYPE_SUBTITLES) {
+ sp<AnotherPacketSource> packetSource =
+ mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES);
+
+ int64_t bufferedDurationUs =
+ packetSource->getBufferedDurationUs(&finalResult);
+
+ downloadMore = (bufferedDurationUs < kMinBufferedDurationUs);
+ finalResult = OK;
+ } else {
+ bool first = true;
+ int64_t minBufferedDurationUs = 0ll;
+
+ for (size_t i = 0; i < mPacketSources.size(); ++i) {
+ if ((mStreamTypeMask & mPacketSources.keyAt(i)) == 0) {
+ continue;
+ }
+
+ int64_t bufferedDurationUs =
+ mPacketSources.valueAt(i)->getBufferedDurationUs(&finalResult);
+
+ if (first || bufferedDurationUs < minBufferedDurationUs) {
+ minBufferedDurationUs = bufferedDurationUs;
+ first = false;
+ }
+ }
+
+ downloadMore =
+ !first && (minBufferedDurationUs < kMinBufferedDurationUs);
+ }
+
+ if (finalResult == OK && downloadMore) {
+ onDownloadNext();
+ } else {
+ // Nothing to do yet, try again in a second.
+
+ sp<AMessage> msg = mNotify->dup();
+ msg->setInt32("what", kWhatTemporarilyDoneFetching);
+ msg->post();
+
+ postMonitorQueue(1000000ll);
+ }
+}
+
+void PlaylistFetcher::onDownloadNext() {
+ int64_t nowUs = ALooper::GetNowUs();
+
+ if (mLastPlaylistFetchTimeUs < 0ll
+ || (!mPlaylist->isComplete() && timeToRefreshPlaylist(nowUs))) {
+ bool unchanged;
+ sp<M3UParser> playlist = mSession->fetchPlaylist(
+ mURI.c_str(), mPlaylistHash, &unchanged);
+
+ if (playlist == NULL) {
+ if (unchanged) {
+ // We succeeded in fetching the playlist, but it was
+ // unchanged from the last time we tried.
+
+ if (mRefreshState != THIRD_UNCHANGED_RELOAD_ATTEMPT) {
+ mRefreshState = (RefreshState)(mRefreshState + 1);
+ }
+ } else {
+ ALOGE("failed to load playlist at url '%s'", mURI.c_str());
+ notifyError(ERROR_IO);
+ return;
+ }
+ } else {
+ mRefreshState = INITIAL_MINIMUM_RELOAD_DELAY;
+ mPlaylist = playlist;
+
+ if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
+ updateDuration();
+ }
+ }
+
+ mLastPlaylistFetchTimeUs = ALooper::GetNowUs();
+ }
+
+ int32_t firstSeqNumberInPlaylist;
+ if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
+ "media-sequence", &firstSeqNumberInPlaylist)) {
+ firstSeqNumberInPlaylist = 0;
+ }
+
+ bool seekDiscontinuity = false;
+ bool explicitDiscontinuity = false;
+
+ const int32_t lastSeqNumberInPlaylist =
+ firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
+
+ if (mSeqNumber < 0) {
+ CHECK_GE(mStartTimeUs, 0ll);
+
+ if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
+ mSeqNumber = getSeqNumberForTime(mStartTimeUs);
+ } else {
+ // If this is a live session, start 3 segments from the end.
+ mSeqNumber = lastSeqNumberInPlaylist - 3;
+ if (mSeqNumber < firstSeqNumberInPlaylist) {
+ mSeqNumber = firstSeqNumberInPlaylist;
+ }
+ }
+
+ mStartTimeUs = -1ll;
+ }
+
+ if (mSeqNumber < firstSeqNumberInPlaylist
+ || mSeqNumber > lastSeqNumberInPlaylist) {
+ if (!mPlaylist->isComplete() && mNumRetries < kMaxNumRetries) {
+ ++mNumRetries;
+
+ if (mSeqNumber > lastSeqNumberInPlaylist) {
+ mLastPlaylistFetchTimeUs = -1;
+ postMonitorQueue(3000000ll);
+ return;
+ }
+
+ // we've missed the boat, let's start from the lowest sequence
+ // number available and signal a discontinuity.
+
+ ALOGI("We've missed the boat, restarting playback.");
+ mSeqNumber = lastSeqNumberInPlaylist;
+ explicitDiscontinuity = true;
+
+ // fall through
+ } else {
+ ALOGE("Cannot find sequence number %d in playlist "
+ "(contains %d - %d)",
+ mSeqNumber, firstSeqNumberInPlaylist,
+ firstSeqNumberInPlaylist + mPlaylist->size() - 1);
+
+ notifyError(ERROR_END_OF_STREAM);
+ return;
+ }
+ }
+
+ mNumRetries = 0;
+
+ AString uri;
+ sp<AMessage> itemMeta;
+ CHECK(mPlaylist->itemAt(
+ mSeqNumber - firstSeqNumberInPlaylist,
+ &uri,
+ &itemMeta));
+
+ int32_t val;
+ if (itemMeta->findInt32("discontinuity", &val) && val != 0) {
+ explicitDiscontinuity = true;
+ }
+
+ int64_t range_offset, range_length;
+ if (!itemMeta->findInt64("range-offset", &range_offset)
+ || !itemMeta->findInt64("range-length", &range_length)) {
+ range_offset = 0;
+ range_length = -1;
+ }
+
+ ALOGV("fetching segment %d from (%d .. %d)",
+ mSeqNumber, firstSeqNumberInPlaylist, lastSeqNumberInPlaylist);
+
+ ALOGV("fetching '%s'", uri.c_str());
+
+ sp<ABuffer> buffer;
+ status_t err = mSession->fetchFile(
+ uri.c_str(), &buffer, range_offset, range_length);
+
+ if (err != OK) {
+ ALOGE("failed to fetch .ts segment at url '%s'", uri.c_str());
+ notifyError(err);
+ return;
+ }
+
+ CHECK(buffer != NULL);
+
+ err = decryptBuffer(mSeqNumber - firstSeqNumberInPlaylist, buffer);
+
+ if (err != OK) {
+ ALOGE("decryptBuffer failed w/ error %d", err);
+
+ notifyError(err);
+ return;
+ }
+
+ if (mStartup || seekDiscontinuity || explicitDiscontinuity) {
+ // Signal discontinuity.
+
+ if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
+ // If this was a live event this made no sense since
+ // we don't have access to all the segment before the current
+ // one.
+ mNextPTSTimeUs = getSegmentStartTimeUs(mSeqNumber);
+ }
+
+ if (seekDiscontinuity || explicitDiscontinuity) {
+ ALOGI("queueing discontinuity (seek=%d, explicit=%d)",
+ seekDiscontinuity, explicitDiscontinuity);
+
+ queueDiscontinuity(
+ explicitDiscontinuity
+ ? ATSParser::DISCONTINUITY_FORMATCHANGE
+ : ATSParser::DISCONTINUITY_SEEK,
+ NULL /* extra */);
+ }
+ }
+
+ err = extractAndQueueAccessUnits(buffer, itemMeta);
+
+ if (err != OK) {
+ notifyError(err);
+ return;
+ }
+
+ ++mSeqNumber;
+
+ postMonitorQueue();
+
+ mStartup = false;
+}
+
+int32_t PlaylistFetcher::getSeqNumberForTime(int64_t timeUs) const {
+ int32_t firstSeqNumberInPlaylist;
+ if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
+ "media-sequence", &firstSeqNumberInPlaylist)) {
+ firstSeqNumberInPlaylist = 0;
+ }
+
+ size_t index = 0;
+ int64_t segmentStartUs = 0;
+ while (index < mPlaylist->size()) {
+ sp<AMessage> itemMeta;
+ CHECK(mPlaylist->itemAt(
+ index, NULL /* uri */, &itemMeta));
+
+ int64_t itemDurationUs;
+ CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+
+ if (timeUs < segmentStartUs + itemDurationUs) {
+ break;
+ }
+
+ segmentStartUs += itemDurationUs;
+ ++index;
+ }
+
+ if (index >= mPlaylist->size()) {
+ index = mPlaylist->size() - 1;
+ }
+
+ return firstSeqNumberInPlaylist + index;
+}
+
+status_t PlaylistFetcher::extractAndQueueAccessUnits(
+ const sp<ABuffer> &buffer, const sp<AMessage> &itemMeta) {
+ if (buffer->size() > 0 && buffer->data()[0] == 0x47) {
+ // Let's assume this is an MPEG2 transport stream.
+
+ if ((buffer->size() % 188) != 0) {
+ ALOGE("MPEG2 transport stream is not an even multiple of 188 "
+ "bytes in length.");
+ return ERROR_MALFORMED;
+ }
+
+ if (mTSParser == NULL) {
+ mTSParser = new ATSParser;
+ }
+
+ if (mNextPTSTimeUs >= 0ll) {
+ sp<AMessage> extra = new AMessage;
+ extra->setInt64(IStreamListener::kKeyMediaTimeUs, mNextPTSTimeUs);
+
+ mTSParser->signalDiscontinuity(
+ ATSParser::DISCONTINUITY_SEEK, extra);
+
+ mNextPTSTimeUs = -1ll;
+ }
+
+ size_t offset = 0;
+ while (offset < buffer->size()) {
+ status_t err = mTSParser->feedTSPacket(buffer->data() + offset, 188);
+
+ if (err != OK) {
+ return err;
+ }
+
+ offset += 188;
+ }
+
+ for (size_t i = mPacketSources.size(); i-- > 0;) {
+ sp<AnotherPacketSource> packetSource = mPacketSources.valueAt(i);
+
+ ATSParser::SourceType type;
+ switch (mPacketSources.keyAt(i)) {
+ case LiveSession::STREAMTYPE_VIDEO:
+ type = ATSParser::VIDEO;
+ break;
+
+ case LiveSession::STREAMTYPE_AUDIO:
+ type = ATSParser::AUDIO;
+ break;
+
+ case LiveSession::STREAMTYPE_SUBTITLES:
+ {
+ ALOGE("MPEG2 Transport streams do not contain subtitles.");
+ return ERROR_MALFORMED;
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+
+ sp<AnotherPacketSource> source =
+ static_cast<AnotherPacketSource *>(
+ mTSParser->getSource(type).get());
+
+ if (source == NULL) {
+ ALOGW("MPEG2 Transport stream does not contain %s data.",
+ type == ATSParser::VIDEO ? "video" : "audio");
+
+ mStreamTypeMask &= ~mPacketSources.keyAt(i);
+ mPacketSources.removeItemsAt(i);
+ continue;
+ }
+
+ sp<ABuffer> accessUnit;
+ status_t finalResult;
+ while (source->hasBufferAvailable(&finalResult)
+ && source->dequeueAccessUnit(&accessUnit) == OK) {
+ // Note that we do NOT dequeue any discontinuities.
+
+ packetSource->queueAccessUnit(accessUnit);
+ }
+
+ if (packetSource->getFormat() == NULL) {
+ packetSource->setFormat(source->getFormat());
+ }
+ }
+
+ return OK;
+ } else if (buffer->size() >= 7 && !memcmp("WEBVTT\n", buffer->data(), 7)) {
+ if (mStreamTypeMask != LiveSession::STREAMTYPE_SUBTITLES) {
+ ALOGE("This stream only contains subtitles.");
+ return ERROR_MALFORMED;
+ }
+
+ const sp<AnotherPacketSource> packetSource =
+ mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES);
+
+ int64_t durationUs;
+ CHECK(itemMeta->findInt64("durationUs", &durationUs));
+ buffer->meta()->setInt64("timeUs", getSegmentStartTimeUs(mSeqNumber));
+ buffer->meta()->setInt64("durationUs", durationUs);
+
+ packetSource->queueAccessUnit(buffer);
+ return OK;
+ }
+
+ if (mNextPTSTimeUs >= 0ll) {
+ mFirstPTSValid = false;
+ mAbsoluteTimeAnchorUs = mNextPTSTimeUs;
+ mNextPTSTimeUs = -1ll;
+ }
+
+ // This better be an ISO 13818-7 (AAC) or ISO 13818-1 (MPEG) audio
+ // stream prefixed by an ID3 tag.
+
+ bool firstID3Tag = true;
+ uint64_t PTS = 0;
+
+ for (;;) {
+ // Make sure to skip all ID3 tags preceding the audio data.
+ // At least one must be present to provide the PTS timestamp.
+
+ ID3 id3(buffer->data(), buffer->size(), true /* ignoreV1 */);
+ if (!id3.isValid()) {
+ if (firstID3Tag) {
+ ALOGE("Unable to parse ID3 tag.");
+ return ERROR_MALFORMED;
+ } else {
+ break;
+ }
+ }
+
+ if (firstID3Tag) {
+ bool found = false;
+
+ ID3::Iterator it(id3, "PRIV");
+ while (!it.done()) {
+ size_t length;
+ const uint8_t *data = it.getData(&length);
+
+ static const char *kMatchName =
+ "com.apple.streaming.transportStreamTimestamp";
+ static const size_t kMatchNameLen = strlen(kMatchName);
+
+ if (length == kMatchNameLen + 1 + 8
+ && !strncmp((const char *)data, kMatchName, kMatchNameLen)) {
+ found = true;
+ PTS = U64_AT(&data[kMatchNameLen + 1]);
+ }
+
+ it.next();
+ }
+
+ if (!found) {
+ ALOGE("Unable to extract transportStreamTimestamp from ID3 tag.");
+ return ERROR_MALFORMED;
+ }
+ }
+
+ // skip the ID3 tag
+ buffer->setRange(
+ buffer->offset() + id3.rawSize(), buffer->size() - id3.rawSize());
+
+ firstID3Tag = false;
+ }
+
+ if (!mFirstPTSValid) {
+ mFirstPTSValid = true;
+ mFirstPTS = PTS;
+ }
+ PTS -= mFirstPTS;
+
+ int64_t timeUs = (PTS * 100ll) / 9ll + mAbsoluteTimeAnchorUs;
+
+ if (mStreamTypeMask != LiveSession::STREAMTYPE_AUDIO) {
+ ALOGW("This stream only contains audio data!");
+
+ mStreamTypeMask &= LiveSession::STREAMTYPE_AUDIO;
+
+ if (mStreamTypeMask == 0) {
+ return OK;
+ }
+ }
+
+ sp<AnotherPacketSource> packetSource =
+ mPacketSources.valueFor(LiveSession::STREAMTYPE_AUDIO);
+
+ if (packetSource->getFormat() == NULL && buffer->size() >= 7) {
+ ABitReader bits(buffer->data(), buffer->size());
+
+ // adts_fixed_header
+
+ CHECK_EQ(bits.getBits(12), 0xfffu);
+ bits.skipBits(3); // ID, layer
+ bool protection_absent = bits.getBits(1) != 0;
+
+ unsigned profile = bits.getBits(2);
+ CHECK_NE(profile, 3u);
+ unsigned sampling_freq_index = bits.getBits(4);
+ bits.getBits(1); // private_bit
+ unsigned channel_configuration = bits.getBits(3);
+ CHECK_NE(channel_configuration, 0u);
+ bits.skipBits(2); // original_copy, home
+
+ sp<MetaData> meta = MakeAACCodecSpecificData(
+ profile, sampling_freq_index, channel_configuration);
+
+ meta->setInt32(kKeyIsADTS, true);
+
+ packetSource->setFormat(meta);
+ }
+
+ int64_t numSamples = 0ll;
+ int32_t sampleRate;
+ CHECK(packetSource->getFormat()->findInt32(kKeySampleRate, &sampleRate));
+
+ size_t offset = 0;
+ while (offset < buffer->size()) {
+ const uint8_t *adtsHeader = buffer->data() + offset;
+ CHECK_LT(offset + 5, buffer->size());
+
+ unsigned aac_frame_length =
+ ((adtsHeader[3] & 3) << 11)
+ | (adtsHeader[4] << 3)
+ | (adtsHeader[5] >> 5);
+
+ CHECK_LE(offset + aac_frame_length, buffer->size());
+
+ sp<ABuffer> unit = new ABuffer(aac_frame_length);
+ memcpy(unit->data(), adtsHeader, aac_frame_length);
+
+ int64_t unitTimeUs = timeUs + numSamples * 1000000ll / sampleRate;
+ unit->meta()->setInt64("timeUs", unitTimeUs);
+
+ // Each AAC frame encodes 1024 samples.
+ numSamples += 1024;
+
+ packetSource->queueAccessUnit(unit);
+
+ offset += aac_frame_length;
+ }
+
+ return OK;
+}
+
+void PlaylistFetcher::updateDuration() {
+ int64_t durationUs = 0ll;
+ for (size_t index = 0; index < mPlaylist->size(); ++index) {
+ sp<AMessage> itemMeta;
+ CHECK(mPlaylist->itemAt(
+ index, NULL /* uri */, &itemMeta));
+
+ int64_t itemDurationUs;
+ CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+
+ durationUs += itemDurationUs;
+ }
+
+ sp<AMessage> msg = mNotify->dup();
+ msg->setInt32("what", kWhatDurationUpdate);
+ msg->setInt64("durationUs", durationUs);
+ msg->post();
+}
+
+} // namespace android
diff --git a/media/libstagefright/httplive/PlaylistFetcher.h b/media/libstagefright/httplive/PlaylistFetcher.h
new file mode 100644
index 0000000..1648e02
--- /dev/null
+++ b/media/libstagefright/httplive/PlaylistFetcher.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 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 PLAYLIST_FETCHER_H_
+
+#define PLAYLIST_FETCHER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+#include "mpeg2ts/ATSParser.h"
+#include "LiveSession.h"
+
+namespace android {
+
+struct ABuffer;
+struct AnotherPacketSource;
+struct DataSource;
+struct HTTPBase;
+struct LiveDataSource;
+struct M3UParser;
+struct String8;
+
+struct PlaylistFetcher : public AHandler {
+ enum {
+ kWhatStarted,
+ kWhatPaused,
+ kWhatStopped,
+ kWhatError,
+ kWhatDurationUpdate,
+ kWhatTemporarilyDoneFetching,
+ kWhatPrepared,
+ kWhatPreparationFailed,
+ };
+
+ PlaylistFetcher(
+ const sp<AMessage> &notify,
+ const sp<LiveSession> &session,
+ const char *uri);
+
+ sp<DataSource> getDataSource();
+
+ void startAsync(
+ const sp<AnotherPacketSource> &audioSource,
+ const sp<AnotherPacketSource> &videoSource,
+ const sp<AnotherPacketSource> &subtitleSource,
+ int64_t startTimeUs = -1ll);
+
+ void pauseAsync();
+
+ void stopAsync();
+
+protected:
+ virtual ~PlaylistFetcher();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kMaxNumRetries = 5,
+ };
+
+ enum {
+ kWhatStart = 'strt',
+ kWhatPause = 'paus',
+ kWhatStop = 'stop',
+ kWhatMonitorQueue = 'moni',
+ };
+
+ static const int64_t kMinBufferedDurationUs;
+
+ sp<AMessage> mNotify;
+ sp<LiveSession> mSession;
+ AString mURI;
+
+ uint32_t mStreamTypeMask;
+ int64_t mStartTimeUs;
+
+ KeyedVector<LiveSession::StreamType, sp<AnotherPacketSource> >
+ mPacketSources;
+
+ KeyedVector<AString, sp<ABuffer> > mAESKeyForURI;
+
+ int64_t mLastPlaylistFetchTimeUs;
+ sp<M3UParser> mPlaylist;
+ int32_t mSeqNumber;
+ int32_t mNumRetries;
+ bool mStartup;
+ int64_t mNextPTSTimeUs;
+
+ int32_t mMonitorQueueGeneration;
+
+ enum RefreshState {
+ INITIAL_MINIMUM_RELOAD_DELAY,
+ FIRST_UNCHANGED_RELOAD_ATTEMPT,
+ SECOND_UNCHANGED_RELOAD_ATTEMPT,
+ THIRD_UNCHANGED_RELOAD_ATTEMPT
+ };
+ RefreshState mRefreshState;
+
+ uint8_t mPlaylistHash[16];
+
+ sp<ATSParser> mTSParser;
+
+ bool mFirstPTSValid;
+ uint64_t mFirstPTS;
+ int64_t mAbsoluteTimeAnchorUs;
+
+ status_t decryptBuffer(
+ size_t playlistIndex, const sp<ABuffer> &buffer);
+
+ void postMonitorQueue(int64_t delayUs = 0);
+ void cancelMonitorQueue();
+
+ bool timeToRefreshPlaylist(int64_t nowUs) const;
+
+ // Returns the media time in us of the segment specified by seqNumber.
+ // This is computed by summing the durations of all segments before it.
+ int64_t getSegmentStartTimeUs(int32_t seqNumber) const;
+
+ status_t onStart(const sp<AMessage> &msg);
+ void onPause();
+ void onStop();
+ void onMonitorQueue();
+ void onDownloadNext();
+
+ status_t extractAndQueueAccessUnits(
+ const sp<ABuffer> &buffer, const sp<AMessage> &itemMeta);
+
+ void notifyError(status_t err);
+
+ void queueDiscontinuity(
+ ATSParser::DiscontinuityType type, const sp<AMessage> &extra);
+
+ int32_t getSeqNumberForTime(int64_t timeUs) const;
+
+ void updateDuration();
+
+ DISALLOW_EVIL_CONSTRUCTORS(PlaylistFetcher);
+};
+
+} // namespace android
+
+#endif // PLAYLIST_FETCHER_H_
+
diff --git a/media/libstagefright/id3/Android.mk b/media/libstagefright/id3/Android.mk
index 80a1a3a..bf6f7bb 100644
--- a/media/libstagefright/id3/Android.mk
+++ b/media/libstagefright/id3/Android.mk
@@ -21,7 +21,7 @@ LOCAL_SHARED_LIBRARIES := \
LOCAL_STATIC_LIBRARIES := \
libstagefright_id3
-LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := testid3
diff --git a/media/libstagefright/id3/ID3.cpp b/media/libstagefright/id3/ID3.cpp
index 22c2f5a..34d671a 100644
--- a/media/libstagefright/id3/ID3.cpp
+++ b/media/libstagefright/id3/ID3.cpp
@@ -30,12 +30,55 @@ namespace android {
static const size_t kMaxMetadataSize = 3 * 1024 * 1024;
+struct MemorySource : public DataSource {
+ MemorySource(const uint8_t *data, size_t size)
+ : mData(data),
+ mSize(size) {
+ }
+
+ virtual status_t initCheck() const {
+ return OK;
+ }
+
+ virtual ssize_t readAt(off64_t offset, void *data, size_t size) {
+ off64_t available = (offset >= mSize) ? 0ll : mSize - offset;
+
+ size_t copy = (available > size) ? size : available;
+ memcpy(data, mData + offset, copy);
+
+ return copy;
+ }
+
+private:
+ const uint8_t *mData;
+ size_t mSize;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MemorySource);
+};
+
ID3::ID3(const sp<DataSource> &source, bool ignoreV1)
: mIsValid(false),
mData(NULL),
mSize(0),
mFirstFrameOffset(0),
- mVersion(ID3_UNKNOWN) {
+ mVersion(ID3_UNKNOWN),
+ mRawSize(0) {
+ mIsValid = parseV2(source);
+
+ if (!mIsValid && !ignoreV1) {
+ mIsValid = parseV1(source);
+ }
+}
+
+ID3::ID3(const uint8_t *data, size_t size, bool ignoreV1)
+ : mIsValid(false),
+ mData(NULL),
+ mSize(0),
+ mFirstFrameOffset(0),
+ mVersion(ID3_UNKNOWN),
+ mRawSize(0) {
+ sp<MemorySource> source = new MemorySource(data, size);
+
mIsValid = parseV2(source);
if (!mIsValid && !ignoreV1) {
@@ -140,6 +183,7 @@ struct id3_header {
}
mSize = size;
+ mRawSize = mSize + sizeof(header);
if (source->readAt(sizeof(header), mData, mSize) != (ssize_t)mSize) {
free(mData);
@@ -313,17 +357,22 @@ bool ID3::removeUnsynchronizationV2_4(bool iTunesHack) {
}
if (flags & 2) {
- // Unsynchronization added.
+ // This file has "unsynchronization", so we have to replace occurrences
+ // of 0xff 0x00 with just 0xff in order to get the real data.
+ size_t readOffset = offset + 11;
+ size_t writeOffset = offset + 11;
for (size_t i = 0; i + 1 < dataSize; ++i) {
- if (mData[offset + 10 + i] == 0xff
- && mData[offset + 11 + i] == 0x00) {
- memmove(&mData[offset + 11 + i], &mData[offset + 12 + i],
- mSize - offset - 12 - i);
+ if (mData[readOffset - 1] == 0xff
+ && mData[readOffset] == 0x00) {
+ ++readOffset;
--mSize;
--dataSize;
}
+ mData[writeOffset++] = mData[readOffset++];
}
+ // move the remaining data following this frame
+ memmove(&mData[writeOffset], &mData[readOffset], oldSize - readOffset);
flags &= ~2;
}
@@ -505,7 +554,7 @@ void ID3::Iterator::getstring(String8 *id, bool otherdata) const {
int32_t i = n - 4;
while(--i >= 0 && *++frameData != 0) ;
int skipped = (frameData - mFrameData);
- if (skipped >= n) {
+ if (skipped >= (int)n) {
return;
}
n -= skipped;
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 2306f31..271df8e 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -25,6 +25,7 @@
#include <media/stagefright/DataSource.h>
#include <media/stagefright/OMXClient.h>
#include <media/stagefright/TimeSource.h>
+#include <media/stagefright/MetaData.h>
#include <utils/threads.h>
#include <drm/DrmManagerClient.h>
@@ -100,7 +101,7 @@ struct AwesomePlayer {
void postAudioEOS(int64_t delayUs = 0ll);
void postAudioSeekComplete();
-
+ void postAudioTearDown();
status_t dump(int fd, const Vector<String16> &args) const;
private:
@@ -168,9 +169,12 @@ private:
sp<AwesomeRenderer> mVideoRenderer;
bool mVideoRenderingStarted;
bool mVideoRendererIsPreview;
+ int32_t mMediaRenderingStartGeneration;
+ int32_t mStartGeneration;
ssize_t mActiveAudioTrackIndex;
sp<MediaSource> mAudioTrack;
+ sp<MediaSource> mOmxSource;
sp<MediaSource> mAudioSource;
AudioPlayer *mAudioPlayer;
int64_t mDurationUs;
@@ -211,7 +215,8 @@ private:
bool mAudioStatusEventPending;
sp<TimedEventQueue::Event> mVideoLagEvent;
bool mVideoLagEventPending;
-
+ sp<TimedEventQueue::Event> mAudioTearDownEvent;
+ bool mAudioTearDownEventPending;
sp<TimedEventQueue::Event> mAsyncPrepareEvent;
Condition mPreparedCondition;
bool mIsAsyncPrepare;
@@ -223,6 +228,8 @@ private:
void postStreamDoneEvent_l(status_t status);
void postCheckAudioStatusEvent(int64_t delayUs);
void postVideoLagEvent_l();
+ void postAudioTearDownEvent(int64_t delayUs);
+
status_t play_l();
MediaBuffer *mVideoBuffer;
@@ -257,6 +264,7 @@ private:
void setAudioSource(sp<MediaSource> source);
status_t initAudioDecoder();
+
void setVideoSource(sp<MediaSource> source);
status_t initVideoDecoder(uint32_t flags = 0);
@@ -273,6 +281,9 @@ private:
void abortPrepare(status_t err);
void finishAsyncPrepare_l();
void onVideoLagUpdate();
+ void onAudioTearDownEvent();
+
+ void beginPrepareAsync_l();
bool getCachedDuration_l(int64_t *durationUs, bool *eos);
@@ -285,6 +296,8 @@ private:
void finishSeekIfNecessary(int64_t videoTimeUs);
void ensureCacheIsFetching_l();
+ void notifyIfMediaStarted_l();
+ void createAudioPlayer_l();
status_t startAudioPlayer_l(bool sendErrorNotification = true);
void shutdownVideoDecoder_l();
@@ -327,6 +340,11 @@ private:
Vector<TrackStat> mTracks;
} mStats;
+ bool mOffloadAudio;
+ bool mAudioTearDown;
+ bool mAudioTearDownWasPlaying;
+ int64_t mAudioTearDownPosition;
+
status_t setVideoScalingMode(int32_t mode);
status_t setVideoScalingMode_l(int32_t mode);
status_t getTrackInfo(Parcel* reply) const;
diff --git a/media/libstagefright/include/ESDS.h b/media/libstagefright/include/ESDS.h
index 3a79951..2f40dae 100644
--- a/media/libstagefright/include/ESDS.h
+++ b/media/libstagefright/include/ESDS.h
@@ -33,6 +33,9 @@ public:
status_t getObjectTypeIndication(uint8_t *objectTypeIndication) const;
status_t getCodecSpecificInfo(const void **data, size_t *size) const;
+ status_t getCodecSpecificOffset(size_t *offset, size_t *size) const;
+ status_t getBitRate(uint32_t *brateMax, uint32_t *brateAvg) const;
+ status_t getStreamType(uint8_t *streamType) const;
private:
enum {
@@ -49,6 +52,9 @@ private:
size_t mDecoderSpecificOffset;
size_t mDecoderSpecificLength;
uint8_t mObjectTypeIndication;
+ uint8_t mStreamType;
+ uint32_t mBitRateMax;
+ uint32_t mBitRateAvg;
status_t skipDescriptorHeader(
size_t offset, size_t size,
diff --git a/media/libstagefright/include/HTTPBase.h b/media/libstagefright/include/HTTPBase.h
index c2dc351..d4b7f9f 100644
--- a/media/libstagefright/include/HTTPBase.h
+++ b/media/libstagefright/include/HTTPBase.h
@@ -59,6 +59,9 @@ struct HTTPBase : public DataSource {
static void RegisterSocketUserTag(int sockfd, uid_t uid, uint32_t kTag);
static void UnRegisterSocketUserTag(int sockfd);
+ static void RegisterSocketUserMark(int sockfd, uid_t uid);
+ static void UnRegisterSocketUserMark(int sockfd);
+
protected:
void addBandwidthMeasurement(size_t numBytes, int64_t delayUs);
diff --git a/media/libstagefright/include/ID3.h b/media/libstagefright/include/ID3.h
index 3028f56..cca83ab 100644
--- a/media/libstagefright/include/ID3.h
+++ b/media/libstagefright/include/ID3.h
@@ -36,6 +36,7 @@ struct ID3 {
};
ID3(const sp<DataSource> &source, bool ignoreV1 = false);
+ ID3(const uint8_t *data, size_t size, bool ignoreV1 = false);
~ID3();
bool isValid() const;
@@ -71,6 +72,8 @@ struct ID3 {
Iterator &operator=(const Iterator &);
};
+ size_t rawSize() const { return mRawSize; }
+
private:
bool mIsValid;
uint8_t *mData;
@@ -78,6 +81,10 @@ private:
size_t mFirstFrameOffset;
Version mVersion;
+ // size of the ID3 tag including header before any unsynchronization.
+ // only valid for IDV2+
+ size_t mRawSize;
+
bool parseV1(const sp<DataSource> &source);
bool parseV2(const sp<DataSource> &source);
void removeUnsynchronization();
diff --git a/media/libstagefright/include/MPEG2TSExtractor.h b/media/libstagefright/include/MPEG2TSExtractor.h
index fe74a42..c5e86a6 100644
--- a/media/libstagefright/include/MPEG2TSExtractor.h
+++ b/media/libstagefright/include/MPEG2TSExtractor.h
@@ -31,7 +31,6 @@ struct ATSParser;
struct DataSource;
struct MPEG2TSSource;
struct String8;
-struct LiveSession;
struct MPEG2TSExtractor : public MediaExtractor {
MPEG2TSExtractor(const sp<DataSource> &source);
@@ -44,16 +43,12 @@ struct MPEG2TSExtractor : public MediaExtractor {
virtual uint32_t flags() const;
- void setLiveSession(const sp<LiveSession> &liveSession);
- void seekTo(int64_t seekTimeUs);
-
private:
friend struct MPEG2TSSource;
mutable Mutex mLock;
sp<DataSource> mDataSource;
- sp<LiveSession> mLiveSession;
sp<ATSParser> mParser;
diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h
index 15253c3..bd5e4b9 100644
--- a/media/libstagefright/include/MPEG4Extractor.h
+++ b/media/libstagefright/include/MPEG4Extractor.h
@@ -82,6 +82,7 @@ private:
sp<DataSource> mDataSource;
status_t mInitCheck;
bool mHasVideo;
+ uint32_t mHeaderTimescale;
Track *mFirstTrack, *mLastTrack;
diff --git a/media/libstagefright/include/OMX.h b/media/libstagefright/include/OMX.h
index 24b8d98..31a5077 100644
--- a/media/libstagefright/include/OMX.h
+++ b/media/libstagefright/include/OMX.h
@@ -71,6 +71,10 @@ public:
virtual status_t storeMetaDataInBuffers(
node_id node, OMX_U32 port_index, OMX_BOOL enable);
+ virtual status_t prepareForAdaptivePlayback(
+ node_id node, OMX_U32 portIndex, OMX_BOOL enable,
+ OMX_U32 max_frame_width, OMX_U32 max_frame_height);
+
virtual status_t useBuffer(
node_id node, OMX_U32 port_index, const sp<IMemory> &params,
buffer_id *buffer);
@@ -79,6 +83,10 @@ public:
node_id node, OMX_U32 port_index,
const sp<GraphicBuffer> &graphicBuffer, buffer_id *buffer);
+ virtual status_t updateGraphicBufferInMeta(
+ node_id node, OMX_U32 port_index,
+ const sp<GraphicBuffer> &graphicBuffer, buffer_id buffer);
+
virtual status_t createInputSurface(
node_id node, OMX_U32 port_index,
sp<IGraphicBufferProducer> *bufferProducer);
@@ -109,6 +117,13 @@ public:
const char *parameter_name,
OMX_INDEXTYPE *index);
+ virtual status_t setInternalOption(
+ node_id node,
+ OMX_U32 port_index,
+ InternalOptionType type,
+ const void *data,
+ size_t size);
+
virtual void binderDied(const wp<IBinder> &the_late_who);
OMX_ERRORTYPE OnEvent(
diff --git a/media/libstagefright/include/OMXNodeInstance.h b/media/libstagefright/include/OMXNodeInstance.h
index 67aba6b..339179e 100644
--- a/media/libstagefright/include/OMXNodeInstance.h
+++ b/media/libstagefright/include/OMXNodeInstance.h
@@ -58,6 +58,10 @@ struct OMXNodeInstance {
status_t storeMetaDataInBuffers(OMX_U32 portIndex, OMX_BOOL enable);
+ status_t prepareForAdaptivePlayback(
+ OMX_U32 portIndex, OMX_BOOL enable,
+ OMX_U32 maxFrameWidth, OMX_U32 maxFrameHeight);
+
status_t useBuffer(
OMX_U32 portIndex, const sp<IMemory> &params,
OMX::buffer_id *buffer);
@@ -66,6 +70,10 @@ struct OMXNodeInstance {
OMX_U32 portIndex, const sp<GraphicBuffer> &graphicBuffer,
OMX::buffer_id *buffer);
+ status_t updateGraphicBufferInMeta(
+ OMX_U32 portIndex, const sp<GraphicBuffer> &graphicBuffer,
+ OMX::buffer_id buffer);
+
status_t createInputSurface(
OMX_U32 portIndex, sp<IGraphicBufferProducer> *bufferProducer);
@@ -96,6 +104,12 @@ struct OMXNodeInstance {
status_t getExtensionIndex(
const char *parameterName, OMX_INDEXTYPE *index);
+ status_t setInternalOption(
+ OMX_U32 portIndex,
+ IOMX::InternalOptionType type,
+ const void *data,
+ size_t size);
+
void onMessage(const omx_message &msg);
void onObserverDied(OMXMaster *master);
void onGetHandleFailed();
diff --git a/media/libstagefright/include/SoftVideoDecoderOMXComponent.h b/media/libstagefright/include/SoftVideoDecoderOMXComponent.h
new file mode 100644
index 0000000..d050fa6
--- /dev/null
+++ b/media/libstagefright/include/SoftVideoDecoderOMXComponent.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 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 SOFT_VIDEO_DECODER_OMX_COMPONENT_H_
+
+#define SOFT_VIDEO_DECODER_OMX_COMPONENT_H_
+
+#include "SimpleSoftOMXComponent.h"
+
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <media/IOMX.h>
+
+#include <utils/RefBase.h>
+#include <utils/threads.h>
+#include <utils/Vector.h>
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
+
+namespace android {
+
+struct SoftVideoDecoderOMXComponent : public SimpleSoftOMXComponent {
+ SoftVideoDecoderOMXComponent(
+ const char *name,
+ const char *componentRole,
+ OMX_VIDEO_CODINGTYPE codingType,
+ const CodecProfileLevel *profileLevels,
+ size_t numProfileLevels,
+ int32_t width,
+ int32_t height,
+ const OMX_CALLBACKTYPE *callbacks,
+ OMX_PTR appData,
+ OMX_COMPONENTTYPE **component);
+
+protected:
+ virtual void onPortEnableCompleted(OMX_U32 portIndex, bool enabled);
+ virtual void onReset();
+
+ virtual OMX_ERRORTYPE internalGetParameter(
+ OMX_INDEXTYPE index, OMX_PTR params);
+
+ virtual OMX_ERRORTYPE internalSetParameter(
+ OMX_INDEXTYPE index, const OMX_PTR params);
+
+ virtual OMX_ERRORTYPE getConfig(
+ OMX_INDEXTYPE index, OMX_PTR params);
+
+ void initPorts(OMX_U32 numInputBuffers,
+ OMX_U32 inputBufferSize,
+ OMX_U32 numOutputBuffers,
+ const char *mimeType);
+
+ virtual void updatePortDefinitions();
+
+ enum {
+ kInputPortIndex = 0,
+ kOutputPortIndex = 1,
+ kMaxPortIndex = 1,
+ };
+
+ uint32_t mWidth, mHeight;
+ uint32_t mCropLeft, mCropTop, mCropWidth, mCropHeight;
+
+ enum {
+ NONE,
+ AWAITING_DISABLED,
+ AWAITING_ENABLED
+ } mOutputPortSettingsChange;
+
+private:
+ const char *mComponentRole;
+ OMX_VIDEO_CODINGTYPE mCodingType;
+ const CodecProfileLevel *mProfileLevels;
+ size_t mNumProfileLevels;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SoftVideoDecoderOMXComponent);
+};
+
+} // namespace android
+
+#endif // SOFT_VIDEO_DECODER_OMX_COMPONENT_H_
diff --git a/media/libstagefright/include/TimedEventQueue.h b/media/libstagefright/include/TimedEventQueue.h
index 11f844c..4e49c83 100644
--- a/media/libstagefright/include/TimedEventQueue.h
+++ b/media/libstagefright/include/TimedEventQueue.h
@@ -23,6 +23,7 @@
#include <utils/List.h>
#include <utils/RefBase.h>
#include <utils/threads.h>
+#include <powermanager/IPowerManager.h>
namespace android {
@@ -57,6 +58,21 @@ struct TimedEventQueue {
Event &operator=(const Event &);
};
+ class PMDeathRecipient : public IBinder::DeathRecipient {
+ public:
+ PMDeathRecipient(TimedEventQueue *queue) : mQueue(queue) {}
+ virtual ~PMDeathRecipient() {}
+
+ // IBinder::DeathRecipient
+ virtual void binderDied(const wp<IBinder>& who);
+
+ private:
+ PMDeathRecipient(const PMDeathRecipient&);
+ PMDeathRecipient& operator = (const PMDeathRecipient&);
+
+ TimedEventQueue *mQueue;
+ };
+
TimedEventQueue();
~TimedEventQueue();
@@ -96,6 +112,8 @@ struct TimedEventQueue {
static int64_t getRealTimeUs();
+ void clearPowerManager();
+
private:
struct QueueItem {
sp<Event> event;
@@ -118,11 +136,18 @@ private:
bool mRunning;
bool mStopped;
+ sp<IPowerManager> mPowerManager;
+ sp<IBinder> mWakeLockToken;
+ const sp<PMDeathRecipient> mDeathRecipient;
+
static void *ThreadWrapper(void *me);
void threadEntry();
sp<Event> removeEventFromQueue_l(event_id id);
+ void acquireWakeLock_l();
+ void releaseWakeLock_l();
+
TimedEventQueue(const TimedEventQueue &);
TimedEventQueue &operator=(const TimedEventQueue &);
};
diff --git a/media/libstagefright/matroska/MatroskaExtractor.cpp b/media/libstagefright/matroska/MatroskaExtractor.cpp
index b304749..d260d0f 100644
--- a/media/libstagefright/matroska/MatroskaExtractor.cpp
+++ b/media/libstagefright/matroska/MatroskaExtractor.cpp
@@ -870,7 +870,9 @@ void MatroskaExtractor::addTracks() {
continue;
}
} else if (!strcmp("V_VP8", codecID)) {
- meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_VPX);
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_VP8);
+ } else if (!strcmp("V_VP9", codecID)) {
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_VP9);
} else {
ALOGW("%s is not supported.", codecID);
continue;
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index 3de3a61..3153c8b 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -32,9 +32,22 @@ const int64_t kNearEOSMarkUs = 2000000ll; // 2 secs
AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta)
: mIsAudio(false),
- mFormat(meta),
+ mFormat(NULL),
mLastQueuedTimeUs(0),
mEOSResult(OK) {
+ setFormat(meta);
+}
+
+void AnotherPacketSource::setFormat(const sp<MetaData> &meta) {
+ CHECK(mFormat == NULL);
+
+ mIsAudio = false;
+
+ if (meta == NULL) {
+ return;
+ }
+
+ mFormat = meta;
const char *mime;
CHECK(meta->findCString(kKeyMIMEType, &mime));
@@ -45,11 +58,6 @@ AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta)
}
}
-void AnotherPacketSource::setFormat(const sp<MetaData> &meta) {
- CHECK(mFormat == NULL);
- mFormat = meta;
-}
-
AnotherPacketSource::~AnotherPacketSource() {
}
@@ -152,6 +160,15 @@ void AnotherPacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
mCondition.signal();
}
+void AnotherPacketSource::clear() {
+ Mutex::Autolock autoLock(mLock);
+
+ mBuffers.clear();
+ mEOSResult = OK;
+
+ mFormat = NULL;
+}
+
void AnotherPacketSource::queueDiscontinuity(
ATSParser::DiscontinuityType type,
const sp<AMessage> &extra) {
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index 1db4068..e16cf78 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -41,6 +41,8 @@ struct AnotherPacketSource : public MediaSource {
virtual status_t read(
MediaBuffer **buffer, const ReadOptions *options = NULL);
+ void clear();
+
bool hasBufferAvailable(status_t *finalResult);
// Returns the difference between the last and the first queued
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 9f3b19c..8f9c9c8 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -504,15 +504,11 @@ int64_t ElementaryStreamQueue::fetchTimestamp(size_t size) {
if (first) {
timeUs = info->mTimestampUs;
+ first = false;
}
if (info->mLength > size) {
info->mLength -= size;
-
- if (first) {
- info->mTimestampUs = -1;
- }
-
size = 0;
} else {
size -= info->mLength;
@@ -521,7 +517,6 @@ int64_t ElementaryStreamQueue::fetchTimestamp(size_t size) {
info = NULL;
}
- first = false;
}
if (timeUs == 0ll) {
diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
index e1589b4..d449c34 100644
--- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
+++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
@@ -19,7 +19,6 @@
#include <utils/Log.h>
#include "include/MPEG2TSExtractor.h"
-#include "include/LiveSession.h"
#include "include/NuCachedSource2.h"
#include <media/stagefright/foundation/ADebug.h>
@@ -79,15 +78,7 @@ status_t MPEG2TSSource::stop() {
}
sp<MetaData> MPEG2TSSource::getFormat() {
- sp<MetaData> meta = mImpl->getFormat();
-
- int64_t durationUs;
- if (mExtractor->mLiveSession != NULL
- && mExtractor->mLiveSession->getDuration(&durationUs) == OK) {
- meta->setInt64(kKeyDuration, durationUs);
- }
-
- return meta;
+ return mImpl->getFormat();
}
status_t MPEG2TSSource::read(
@@ -97,7 +88,7 @@ status_t MPEG2TSSource::read(
int64_t seekTimeUs;
ReadOptions::SeekMode seekMode;
if (mSeekable && options && options->getSeekTo(&seekTimeUs, &seekMode)) {
- mExtractor->seekTo(seekTimeUs);
+ return ERROR_UNSUPPORTED;
}
status_t finalResult;
@@ -216,32 +207,8 @@ status_t MPEG2TSExtractor::feedMore() {
return mParser->feedTSPacket(packet, kTSPacketSize);
}
-void MPEG2TSExtractor::setLiveSession(const sp<LiveSession> &liveSession) {
- Mutex::Autolock autoLock(mLock);
-
- mLiveSession = liveSession;
-}
-
-void MPEG2TSExtractor::seekTo(int64_t seekTimeUs) {
- Mutex::Autolock autoLock(mLock);
-
- if (mLiveSession == NULL) {
- return;
- }
-
- mLiveSession->seekTo(seekTimeUs);
-}
-
uint32_t MPEG2TSExtractor::flags() const {
- Mutex::Autolock autoLock(mLock);
-
- uint32_t flags = CAN_PAUSE;
-
- if (mLiveSession != NULL && mLiveSession->isSeekable()) {
- flags |= CAN_SEEK_FORWARD | CAN_SEEK_BACKWARD | CAN_SEEK;
- }
-
- return flags;
+ return CAN_PAUSE;
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk
index a8b4939..cd912e7 100644
--- a/media/libstagefright/omx/Android.mk
+++ b/media/libstagefright/omx/Android.mk
@@ -9,6 +9,7 @@ LOCAL_SRC_FILES:= \
SimpleSoftOMXComponent.cpp \
SoftOMXComponent.cpp \
SoftOMXPlugin.cpp \
+ SoftVideoDecoderOMXComponent.cpp \
LOCAL_C_INCLUDES += \
$(TOP)/frameworks/av/media/libstagefright \
diff --git a/media/libstagefright/omx/GraphicBufferSource.cpp b/media/libstagefright/omx/GraphicBufferSource.cpp
index ef27879..cf43e94 100644
--- a/media/libstagefright/omx/GraphicBufferSource.cpp
+++ b/media/libstagefright/omx/GraphicBufferSource.cpp
@@ -18,12 +18,13 @@
//#define LOG_NDEBUG 0
#include <utils/Log.h>
-#include <GraphicBufferSource.h>
+#include "GraphicBufferSource.h"
#include <OMX_Core.h>
#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
-#include <MetadataBufferType.h>
+#include <media/hardware/MetadataBufferType.h>
#include <ui/GraphicBuffer.h>
namespace android {
@@ -36,9 +37,16 @@ GraphicBufferSource::GraphicBufferSource(OMXNodeInstance* nodeInstance,
mInitCheck(UNKNOWN_ERROR),
mNodeInstance(nodeInstance),
mExecuting(false),
+ mSuspended(false),
mNumFramesAvailable(0),
mEndOfStream(false),
- mEndOfStreamSent(false) {
+ mEndOfStreamSent(false),
+ mRepeatAfterUs(-1ll),
+ mRepeatLastFrameGeneration(0),
+ mLatestSubmittedBufferId(-1),
+ mLatestSubmittedBufferFrameNum(0),
+ mLatestSubmittedBufferUseCount(0),
+ mRepeatBufferDeferred(false) {
ALOGV("GraphicBufferSource w=%u h=%u c=%u",
bufferWidth, bufferHeight, bufferCount);
@@ -51,10 +59,9 @@ GraphicBufferSource::GraphicBufferSource(OMXNodeInstance* nodeInstance,
String8 name("GraphicBufferSource");
- mBufferQueue = new BufferQueue(true);
+ mBufferQueue = new BufferQueue();
mBufferQueue->setConsumerName(name);
mBufferQueue->setDefaultBufferSize(bufferWidth, bufferHeight);
- mBufferQueue->setSynchronousMode(true);
mBufferQueue->setConsumerUsageBits(GRALLOC_USAGE_HW_VIDEO_ENCODER |
GRALLOC_USAGE_HW_TEXTURE);
@@ -69,13 +76,10 @@ GraphicBufferSource::GraphicBufferSource(OMXNodeInstance* nodeInstance,
// reference once the ctor ends, as that would cause the refcount of 'this'
// dropping to 0 at the end of the ctor. Since all we need is a wp<...>
// that's what we create.
- wp<BufferQueue::ConsumerListener> listener;
- listener = static_cast<BufferQueue::ConsumerListener*>(this);
+ wp<BufferQueue::ConsumerListener> listener = static_cast<BufferQueue::ConsumerListener*>(this);
+ sp<BufferQueue::ProxyConsumerListener> proxy = new BufferQueue::ProxyConsumerListener(listener);
- sp<BufferQueue::ConsumerListener> proxy;
- proxy = new BufferQueue::ProxyConsumerListener(listener);
-
- mInitCheck = mBufferQueue->consumerConnect(proxy);
+ mInitCheck = mBufferQueue->consumerConnect(proxy, false);
if (mInitCheck != NO_ERROR) {
ALOGE("Error connecting to BufferQueue: %s (%d)",
strerror(-mInitCheck), mInitCheck);
@@ -126,14 +130,40 @@ void GraphicBufferSource::omxExecuting() {
if (mEndOfStream && mNumFramesAvailable == 0) {
submitEndOfInputStream_l();
}
+
+ if (mRepeatAfterUs > 0ll && mLooper == NULL) {
+ mReflector = new AHandlerReflector<GraphicBufferSource>(this);
+
+ mLooper = new ALooper;
+ mLooper->registerHandler(mReflector);
+ mLooper->start();
+
+ if (mLatestSubmittedBufferId >= 0) {
+ sp<AMessage> msg =
+ new AMessage(kWhatRepeatLastFrame, mReflector->id());
+
+ msg->setInt32("generation", ++mRepeatLastFrameGeneration);
+ msg->post(mRepeatAfterUs);
+ }
+ }
}
void GraphicBufferSource::omxLoaded(){
Mutex::Autolock autoLock(mMutex);
- ALOGV("--> loaded");
- CHECK(mExecuting);
+ if (!mExecuting) {
+ // This can happen if something failed very early.
+ ALOGW("Dropped back down to Loaded without Executing");
+ }
+
+ if (mLooper != NULL) {
+ mLooper->unregisterHandler(mReflector->id());
+ mReflector.clear();
- ALOGV("Dropped down to loaded, avail=%d eos=%d eosSent=%d",
+ mLooper->stop();
+ mLooper.clear();
+ }
+
+ ALOGV("--> loaded; avail=%d eos=%d eosSent=%d",
mNumFramesAvailable, mEndOfStream, mEndOfStreamSent);
// Codec is no longer executing. Discard all codec-related state.
@@ -206,24 +236,19 @@ void GraphicBufferSource::codecBufferEmptied(OMX_BUFFERHEADERTYPE* header) {
// Find matching entry in our cached copy of the BufferQueue slots.
// If we find a match, release that slot. If we don't, the BufferQueue
// has dropped that GraphicBuffer, and there's nothing for us to release.
- //
- // (We could store "id" in CodecBuffer and avoid the slot search.)
- int id;
- for (id = 0; id < BufferQueue::NUM_BUFFER_SLOTS; id++) {
- if (mBufferSlot[id] == NULL) {
- continue;
+ int id = codecBuffer.mBuf;
+ if (mBufferSlot[id] != NULL &&
+ mBufferSlot[id]->handle == codecBuffer.mGraphicBuffer->handle) {
+ ALOGV("cbi %d matches bq slot %d, handle=%p",
+ cbi, id, mBufferSlot[id]->handle);
+
+ if (id == mLatestSubmittedBufferId) {
+ CHECK_GT(mLatestSubmittedBufferUseCount--, 0);
+ } else {
+ mBufferQueue->releaseBuffer(id, codecBuffer.mFrameNumber,
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE);
}
-
- if (mBufferSlot[id]->handle == codecBuffer.mGraphicBuffer->handle) {
- ALOGV("cbi %d matches bq slot %d, handle=%p",
- cbi, id, mBufferSlot[id]->handle);
-
- mBufferQueue->releaseBuffer(id, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
- Fence::NO_FENCE);
- break;
- }
- }
- if (id == BufferQueue::NUM_BUFFER_SLOTS) {
+ } else {
ALOGV("codecBufferEmptied: no match for emptied buffer in cbi %d",
cbi);
}
@@ -242,13 +267,66 @@ void GraphicBufferSource::codecBufferEmptied(OMX_BUFFERHEADERTYPE* header) {
// send that.
ALOGV("buffer freed, EOS pending");
submitEndOfInputStream_l();
+ } else if (mRepeatBufferDeferred) {
+ bool success = repeatLatestSubmittedBuffer_l();
+ if (success) {
+ ALOGV("deferred repeatLatestSubmittedBuffer_l SUCCESS");
+ } else {
+ ALOGV("deferred repeatLatestSubmittedBuffer_l FAILURE");
+ }
+ mRepeatBufferDeferred = false;
}
+
return;
}
+void GraphicBufferSource::suspend(bool suspend) {
+ Mutex::Autolock autoLock(mMutex);
+
+ if (suspend) {
+ mSuspended = true;
+
+ while (mNumFramesAvailable > 0) {
+ BufferQueue::BufferItem item;
+ status_t err = mBufferQueue->acquireBuffer(&item, 0);
+
+ if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
+ // shouldn't happen.
+ ALOGW("suspend: frame was not available");
+ break;
+ } else if (err != OK) {
+ ALOGW("suspend: acquireBuffer returned err=%d", err);
+ break;
+ }
+
+ --mNumFramesAvailable;
+
+ mBufferQueue->releaseBuffer(item.mBuf, item.mFrameNumber,
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, item.mFence);
+ }
+ return;
+ }
+
+ mSuspended = false;
+
+ if (mExecuting && mNumFramesAvailable == 0 && mRepeatBufferDeferred) {
+ if (repeatLatestSubmittedBuffer_l()) {
+ ALOGV("suspend/deferred repeatLatestSubmittedBuffer_l SUCCESS");
+
+ mRepeatBufferDeferred = false;
+ } else {
+ ALOGV("suspend/deferred repeatLatestSubmittedBuffer_l FAILURE");
+ }
+ }
+}
+
bool GraphicBufferSource::fillCodecBuffer_l() {
CHECK(mExecuting && mNumFramesAvailable > 0);
+ if (mSuspended) {
+ return false;
+ }
+
int cbi = findAvailableCodecBuffer_l();
if (cbi < 0) {
// No buffers available, bail.
@@ -260,7 +338,7 @@ bool GraphicBufferSource::fillCodecBuffer_l() {
ALOGV("fillCodecBuffer_l: acquiring buffer, avail=%d",
mNumFramesAvailable);
BufferQueue::BufferItem item;
- status_t err = mBufferQueue->acquireBuffer(&item);
+ status_t err = mBufferQueue->acquireBuffer(&item, 0);
if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
// shouldn't happen
ALOGW("fillCodecBuffer_l: frame was not available");
@@ -287,18 +365,75 @@ bool GraphicBufferSource::fillCodecBuffer_l() {
mBufferSlot[item.mBuf] = item.mGraphicBuffer;
}
- err = submitBuffer_l(mBufferSlot[item.mBuf], item.mTimestamp / 1000, cbi);
+ err = submitBuffer_l(item, cbi);
if (err != OK) {
ALOGV("submitBuffer_l failed, releasing bq buf %d", item.mBuf);
- mBufferQueue->releaseBuffer(item.mBuf, EGL_NO_DISPLAY,
- EGL_NO_SYNC_KHR, Fence::NO_FENCE);
+ mBufferQueue->releaseBuffer(item.mBuf, item.mFrameNumber,
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE);
} else {
ALOGV("buffer submitted (bq %d, cbi %d)", item.mBuf, cbi);
+ setLatestSubmittedBuffer_l(item);
}
return true;
}
+bool GraphicBufferSource::repeatLatestSubmittedBuffer_l() {
+ CHECK(mExecuting && mNumFramesAvailable == 0);
+
+ if (mLatestSubmittedBufferId < 0 || mSuspended) {
+ return false;
+ }
+
+ int cbi = findAvailableCodecBuffer_l();
+ if (cbi < 0) {
+ // No buffers available, bail.
+ ALOGV("repeatLatestSubmittedBuffer_l: no codec buffers.");
+ return false;
+ }
+
+ BufferQueue::BufferItem item;
+ item.mBuf = mLatestSubmittedBufferId;
+ item.mFrameNumber = mLatestSubmittedBufferFrameNum;
+
+ status_t err = submitBuffer_l(item, cbi);
+
+ if (err != OK) {
+ return false;
+ }
+
+ ++mLatestSubmittedBufferUseCount;
+
+ return true;
+}
+
+void GraphicBufferSource::setLatestSubmittedBuffer_l(
+ const BufferQueue::BufferItem &item) {
+ ALOGV("setLatestSubmittedBuffer_l");
+
+ if (mLatestSubmittedBufferId >= 0) {
+ if (mLatestSubmittedBufferUseCount == 0) {
+ mBufferQueue->releaseBuffer(
+ mLatestSubmittedBufferId,
+ mLatestSubmittedBufferFrameNum,
+ EGL_NO_DISPLAY,
+ EGL_NO_SYNC_KHR,
+ Fence::NO_FENCE);
+ }
+ }
+
+ mLatestSubmittedBufferId = item.mBuf;
+ mLatestSubmittedBufferFrameNum = item.mFrameNumber;
+ mLatestSubmittedBufferUseCount = 1;
+ mRepeatBufferDeferred = false;
+
+ if (mReflector != NULL) {
+ sp<AMessage> msg = new AMessage(kWhatRepeatLastFrame, mReflector->id());
+ msg->setInt32("generation", ++mRepeatLastFrameGeneration);
+ msg->post(mRepeatAfterUs);
+ }
+}
+
status_t GraphicBufferSource::signalEndOfInputStream() {
Mutex::Autolock autoLock(mMutex);
ALOGV("signalEndOfInputStream: exec=%d avail=%d eos=%d",
@@ -326,11 +461,13 @@ status_t GraphicBufferSource::signalEndOfInputStream() {
return OK;
}
-status_t GraphicBufferSource::submitBuffer_l(sp<GraphicBuffer>& graphicBuffer,
- int64_t timestampUsec, int cbi) {
+status_t GraphicBufferSource::submitBuffer_l(
+ const BufferQueue::BufferItem &item, int cbi) {
ALOGV("submitBuffer_l cbi=%d", cbi);
CodecBuffer& codecBuffer(mCodecBuffers.editItemAt(cbi));
- codecBuffer.mGraphicBuffer = graphicBuffer;
+ codecBuffer.mGraphicBuffer = mBufferSlot[item.mBuf];
+ codecBuffer.mBuf = item.mBuf;
+ codecBuffer.mFrameNumber = item.mFrameNumber;
OMX_BUFFERHEADERTYPE* header = codecBuffer.mHeader;
CHECK(header->nAllocLen >= 4 + sizeof(buffer_handle_t));
@@ -342,7 +479,7 @@ status_t GraphicBufferSource::submitBuffer_l(sp<GraphicBuffer>& graphicBuffer,
status_t err = mNodeInstance->emptyDirectBuffer(header, 0,
4 + sizeof(buffer_handle_t), OMX_BUFFERFLAG_ENDOFFRAME,
- timestampUsec);
+ item.mTimestamp / 1000);
if (err != OK) {
ALOGW("WARNING: emptyDirectBuffer failed: 0x%x", err);
codecBuffer.mGraphicBuffer = NULL;
@@ -423,22 +560,30 @@ void GraphicBufferSource::onFrameAvailable() {
ALOGV("onFrameAvailable exec=%d avail=%d",
mExecuting, mNumFramesAvailable);
- if (mEndOfStream) {
- // This should only be possible if a new buffer was queued after
- // EOS was signaled, i.e. the app is misbehaving.
- ALOGW("onFrameAvailable: EOS is set, ignoring frame");
+ if (mEndOfStream || mSuspended) {
+ if (mEndOfStream) {
+ // This should only be possible if a new buffer was queued after
+ // EOS was signaled, i.e. the app is misbehaving.
+
+ ALOGW("onFrameAvailable: EOS is set, ignoring frame");
+ } else {
+ ALOGV("onFrameAvailable: suspended, ignoring frame");
+ }
BufferQueue::BufferItem item;
- status_t err = mBufferQueue->acquireBuffer(&item);
+ status_t err = mBufferQueue->acquireBuffer(&item, 0);
if (err == OK) {
- mBufferQueue->releaseBuffer(item.mBuf, EGL_NO_DISPLAY,
- EGL_NO_SYNC_KHR, item.mFence);
+ mBufferQueue->releaseBuffer(item.mBuf, item.mFrameNumber,
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, item.mFence);
}
return;
}
mNumFramesAvailable++;
+ mRepeatBufferDeferred = false;
+ ++mRepeatLastFrameGeneration;
+
if (mExecuting) {
fillCodecBuffer_l();
}
@@ -464,4 +609,51 @@ void GraphicBufferSource::onBuffersReleased() {
}
}
+status_t GraphicBufferSource::setRepeatPreviousFrameDelayUs(
+ int64_t repeatAfterUs) {
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting || repeatAfterUs <= 0ll) {
+ return INVALID_OPERATION;
+ }
+
+ mRepeatAfterUs = repeatAfterUs;
+
+ return OK;
+}
+
+void GraphicBufferSource::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRepeatLastFrame:
+ {
+ Mutex::Autolock autoLock(mMutex);
+
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mRepeatLastFrameGeneration) {
+ // stale
+ break;
+ }
+
+ if (!mExecuting || mNumFramesAvailable > 0) {
+ break;
+ }
+
+ bool success = repeatLatestSubmittedBuffer_l();
+
+ if (success) {
+ ALOGV("repeatLatestSubmittedBuffer_l SUCCESS");
+ } else {
+ ALOGV("repeatLatestSubmittedBuffer_l FAILURE");
+ mRepeatBufferDeferred = true;
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
} // namespace android
diff --git a/media/libstagefright/omx/GraphicBufferSource.h b/media/libstagefright/omx/GraphicBufferSource.h
index 562d342..244a843 100644
--- a/media/libstagefright/omx/GraphicBufferSource.h
+++ b/media/libstagefright/omx/GraphicBufferSource.h
@@ -25,6 +25,8 @@
#include <OMX_Core.h>
#include "../include/OMXNodeInstance.h"
#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <media/stagefright/foundation/ALooper.h>
namespace android {
@@ -85,6 +87,19 @@ public:
// have a codec buffer ready, we just set the mEndOfStream flag.
status_t signalEndOfInputStream();
+ // If suspend is true, all incoming buffers (including those currently
+ // in the BufferQueue) will be discarded until the suspension is lifted.
+ void suspend(bool suspend);
+
+ // Specifies the interval after which we requeue the buffer previously
+ // queued to the encoder. This is useful in the case of surface flinger
+ // providing the input surface if the resulting encoded stream is to
+ // be displayed "live". If we were not to push through the extra frame
+ // the decoder on the remote end would be unable to decode the latest frame.
+ // This API must be called before transitioning the encoder to "executing"
+ // state and once this behaviour is specified it cannot be reset.
+ status_t setRepeatPreviousFrameDelayUs(int64_t repeatAfterUs);
+
protected:
// BufferQueue::ConsumerListener interface, called when a new frame of
// data is available. If we're executing and a codec buffer is
@@ -104,6 +119,13 @@ private:
// (mGraphicBuffer == NULL) or in use by the codec.
struct CodecBuffer {
OMX_BUFFERHEADERTYPE* mHeader;
+
+ // buffer producer's frame-number for buffer
+ uint64_t mFrameNumber;
+
+ // buffer producer's buffer slot for buffer
+ int mBuf;
+
sp<GraphicBuffer> mGraphicBuffer;
};
@@ -130,13 +152,15 @@ private:
// Marks the mCodecBuffers entry as in-use, copies the GraphicBuffer
// reference into the codec buffer, and submits the data to the codec.
- status_t submitBuffer_l(sp<GraphicBuffer>& graphicBuffer,
- int64_t timestampUsec, int cbi);
+ status_t submitBuffer_l(const BufferQueue::BufferItem &item, int cbi);
// Submits an empty buffer, with the EOS flag set. Returns without
// doing anything if we don't have a codec buffer available.
void submitEndOfInputStream_l();
+ void setLatestSubmittedBuffer_l(const BufferQueue::BufferItem &item);
+ bool repeatLatestSubmittedBuffer_l();
+
// Lock, covers all member variables.
mutable Mutex mMutex;
@@ -149,6 +173,8 @@ private:
// Set by omxExecuting() / omxIdling().
bool mExecuting;
+ bool mSuspended;
+
// We consume graphic buffers from this.
sp<BufferQueue> mBufferQueue;
@@ -169,6 +195,30 @@ private:
// Tracks codec buffers.
Vector<CodecBuffer> mCodecBuffers;
+ ////
+ friend class AHandlerReflector<GraphicBufferSource>;
+
+ enum {
+ kWhatRepeatLastFrame,
+ };
+
+ int64_t mRepeatAfterUs;
+
+ sp<ALooper> mLooper;
+ sp<AHandlerReflector<GraphicBufferSource> > mReflector;
+
+ int32_t mRepeatLastFrameGeneration;
+
+ int mLatestSubmittedBufferId;
+ uint64_t mLatestSubmittedBufferFrameNum;
+ int32_t mLatestSubmittedBufferUseCount;
+
+ // The previously submitted buffer should've been repeated but
+ // no codec buffer was available at the time.
+ bool mRepeatBufferDeferred;
+
+ void onMessageReceived(const sp<AMessage> &msg);
+
DISALLOW_EVIL_CONSTRUCTORS(GraphicBufferSource);
};
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index 3987ead..84a0e10 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -331,6 +331,13 @@ status_t OMX::storeMetaDataInBuffers(
return findInstance(node)->storeMetaDataInBuffers(port_index, enable);
}
+status_t OMX::prepareForAdaptivePlayback(
+ node_id node, OMX_U32 portIndex, OMX_BOOL enable,
+ OMX_U32 maxFrameWidth, OMX_U32 maxFrameHeight) {
+ return findInstance(node)->prepareForAdaptivePlayback(
+ portIndex, enable, maxFrameWidth, maxFrameHeight);
+}
+
status_t OMX::useBuffer(
node_id node, OMX_U32 port_index, const sp<IMemory> &params,
buffer_id *buffer) {
@@ -345,6 +352,13 @@ status_t OMX::useGraphicBuffer(
port_index, graphicBuffer, buffer);
}
+status_t OMX::updateGraphicBufferInMeta(
+ node_id node, OMX_U32 port_index,
+ const sp<GraphicBuffer> &graphicBuffer, buffer_id buffer) {
+ return findInstance(node)->updateGraphicBufferInMeta(
+ port_index, graphicBuffer, buffer);
+}
+
status_t OMX::createInputSurface(
node_id node, OMX_U32 port_index,
sp<IGraphicBufferProducer> *bufferProducer) {
@@ -396,6 +410,15 @@ status_t OMX::getExtensionIndex(
parameter_name, index);
}
+status_t OMX::setInternalOption(
+ node_id node,
+ OMX_U32 port_index,
+ InternalOptionType type,
+ const void *data,
+ size_t size) {
+ return findInstance(node)->setInternalOption(port_index, type, data, size);
+}
+
OMX_ERRORTYPE OMX::OnEvent(
node_id node,
OMX_IN OMX_EVENTTYPE eEvent,
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index a9eb94f..46e5d71 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -70,6 +70,10 @@ struct BufferMeta {
header->nFilledLen);
}
+ void setGraphicBuffer(const sp<GraphicBuffer> &graphicBuffer) {
+ mGraphicBuffer = graphicBuffer;
+ }
+
private:
sp<GraphicBuffer> mGraphicBuffer;
sp<IMemory> mMem;
@@ -238,6 +242,18 @@ status_t OMXNodeInstance::freeNode(OMXMaster *master) {
status_t OMXNodeInstance::sendCommand(
OMX_COMMANDTYPE cmd, OMX_S32 param) {
+ const sp<GraphicBufferSource>& bufferSource(getGraphicBufferSource());
+ if (bufferSource != NULL
+ && cmd == OMX_CommandStateSet
+ && param == OMX_StateLoaded) {
+ // Initiating transition from Executing -> Loaded
+ // Buffers are about to be freed.
+ bufferSource->omxLoaded();
+ setGraphicBufferSource(NULL);
+
+ // fall through
+ }
+
Mutex::Autolock autoLock(mLock);
OMX_ERRORTYPE err = OMX_SendCommand(mHandle, cmd, param, NULL);
@@ -401,6 +417,40 @@ status_t OMXNodeInstance::storeMetaDataInBuffers_l(
return err;
}
+status_t OMXNodeInstance::prepareForAdaptivePlayback(
+ OMX_U32 portIndex, OMX_BOOL enable, OMX_U32 maxFrameWidth,
+ OMX_U32 maxFrameHeight) {
+ Mutex::Autolock autolock(mLock);
+
+ OMX_INDEXTYPE index;
+ OMX_STRING name = const_cast<OMX_STRING>(
+ "OMX.google.android.index.prepareForAdaptivePlayback");
+
+ OMX_ERRORTYPE err = OMX_GetExtensionIndex(mHandle, name, &index);
+ if (err != OMX_ErrorNone) {
+ ALOGW_IF(enable, "OMX_GetExtensionIndex %s failed", name);
+ return StatusFromOMXError(err);
+ }
+
+ PrepareForAdaptivePlaybackParams params;
+ params.nSize = sizeof(params);
+ params.nVersion.s.nVersionMajor = 1;
+ params.nVersion.s.nVersionMinor = 0;
+ params.nVersion.s.nRevision = 0;
+ params.nVersion.s.nStep = 0;
+
+ params.nPortIndex = portIndex;
+ params.bEnable = enable;
+ params.nMaxFrameWidth = maxFrameWidth;
+ params.nMaxFrameHeight = maxFrameHeight;
+ if ((err = OMX_SetParameter(mHandle, index, &params)) != OMX_ErrorNone) {
+ ALOGW("OMX_SetParameter failed for PrepareForAdaptivePlayback "
+ "with error %d (0x%08x)", err, err);
+ return UNKNOWN_ERROR;
+ }
+ return err;
+}
+
status_t OMXNodeInstance::useBuffer(
OMX_U32 portIndex, const sp<IMemory> &params,
OMX::buffer_id *buffer) {
@@ -554,6 +604,22 @@ status_t OMXNodeInstance::useGraphicBuffer(
return OK;
}
+status_t OMXNodeInstance::updateGraphicBufferInMeta(
+ OMX_U32 portIndex, const sp<GraphicBuffer>& graphicBuffer,
+ OMX::buffer_id buffer) {
+ Mutex::Autolock autoLock(mLock);
+
+ OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)(buffer);
+ VideoDecoderOutputMetaData *metadata =
+ (VideoDecoderOutputMetaData *)(header->pBuffer);
+ BufferMeta *bufferMeta = (BufferMeta *)(header->pAppPrivate);
+ bufferMeta->setGraphicBuffer(graphicBuffer);
+ metadata->eType = kMetadataBufferTypeGrallocSource;
+ metadata->pHandle = graphicBuffer->handle;
+
+ return OK;
+}
+
status_t OMXNodeInstance::createInputSurface(
OMX_U32 portIndex, sp<IGraphicBufferProducer> *bufferProducer) {
Mutex::Autolock autolock(mLock);
@@ -584,7 +650,8 @@ status_t OMXNodeInstance::createInputSurface(
CHECK(oerr == OMX_ErrorNone);
if (def.format.video.eColorFormat != OMX_COLOR_FormatAndroidOpaque) {
- ALOGE("createInputSurface requires AndroidOpaque color format");
+ ALOGE("createInputSurface requires COLOR_FormatSurface "
+ "(AndroidOpaque) color format");
return INVALID_OPERATION;
}
@@ -769,6 +836,47 @@ status_t OMXNodeInstance::getExtensionIndex(
return StatusFromOMXError(err);
}
+status_t OMXNodeInstance::setInternalOption(
+ OMX_U32 portIndex,
+ IOMX::InternalOptionType type,
+ const void *data,
+ size_t size) {
+ switch (type) {
+ case IOMX::INTERNAL_OPTION_SUSPEND:
+ case IOMX::INTERNAL_OPTION_REPEAT_PREVIOUS_FRAME_DELAY:
+ {
+ const sp<GraphicBufferSource> &bufferSource =
+ getGraphicBufferSource();
+
+ if (bufferSource == NULL || portIndex != kPortIndexInput) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ if (type == IOMX::INTERNAL_OPTION_SUSPEND) {
+ if (size != sizeof(bool)) {
+ return INVALID_OPERATION;
+ }
+
+ bool suspend = *(bool *)data;
+ bufferSource->suspend(suspend);
+ } else {
+ if (size != sizeof(int64_t)) {
+ return INVALID_OPERATION;
+ }
+
+ int64_t delayUs = *(int64_t *)data;
+
+ return bufferSource->setRepeatPreviousFrameDelayUs(delayUs);
+ }
+
+ return OK;
+ }
+
+ default:
+ return ERROR_UNSUPPORTED;
+ }
+}
+
void OMXNodeInstance::onMessage(const omx_message &msg) {
if (msg.type == omx_message::FILL_BUFFER_DONE) {
OMX_BUFFERHEADERTYPE *buffer =
@@ -818,16 +926,11 @@ void OMXNodeInstance::onEvent(
OMX_EVENTTYPE event, OMX_U32 arg1, OMX_U32 arg2) {
const sp<GraphicBufferSource>& bufferSource(getGraphicBufferSource());
- if (bufferSource != NULL && event == OMX_EventCmdComplete &&
- arg1 == OMX_CommandStateSet) {
- if (arg2 == OMX_StateExecuting) {
- bufferSource->omxExecuting();
- } else if (arg2 == OMX_StateLoaded) {
- // Must be shutting down -- won't have a GraphicBufferSource
- // on the way up.
- bufferSource->omxLoaded();
- setGraphicBufferSource(NULL);
- }
+ if (bufferSource != NULL
+ && event == OMX_EventCmdComplete
+ && arg1 == OMX_CommandStateSet
+ && arg2 == OMX_StateExecuting) {
+ bufferSource->omxExecuting();
}
}
diff --git a/media/libstagefright/omx/SoftOMXPlugin.cpp b/media/libstagefright/omx/SoftOMXPlugin.cpp
index b3fe98e..d6cde73 100644
--- a/media/libstagefright/omx/SoftOMXPlugin.cpp
+++ b/media/libstagefright/omx/SoftOMXPlugin.cpp
@@ -50,8 +50,9 @@ static const struct {
{ "OMX.google.mpeg4.encoder", "mpeg4enc", "video_encoder.mpeg4" },
{ "OMX.google.mp3.decoder", "mp3dec", "audio_decoder.mp3" },
{ "OMX.google.vorbis.decoder", "vorbisdec", "audio_decoder.vorbis" },
- { "OMX.google.vpx.decoder", "vpxdec", "video_decoder.vpx" },
- { "OMX.google.vpx.encoder", "vpxenc", "video_encoder.vpx" },
+ { "OMX.google.vp8.decoder", "vpxdec", "video_decoder.vp8" },
+ { "OMX.google.vp9.decoder", "vpxdec", "video_decoder.vp9" },
+ { "OMX.google.vp8.encoder", "vpxenc", "video_encoder.vp8" },
{ "OMX.google.raw.decoder", "rawdec", "audio_decoder.raw" },
{ "OMX.google.flac.encoder", "flacenc", "audio_encoder.flac" },
{ "OMX.google.gsm.decoder", "gsmdec", "audio_decoder.gsm" },
diff --git a/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp b/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp
new file mode 100644
index 0000000..08a3d42
--- /dev/null
+++ b/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2013 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 "SoftVideoDecoderOMXComponent"
+#include <utils/Log.h>
+
+#include "include/SoftVideoDecoderOMXComponent.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaDefs.h>
+
+namespace android {
+
+template<class T>
+static void InitOMXParams(T *params) {
+ params->nSize = sizeof(T);
+ params->nVersion.s.nVersionMajor = 1;
+ params->nVersion.s.nVersionMinor = 0;
+ params->nVersion.s.nRevision = 0;
+ params->nVersion.s.nStep = 0;
+}
+
+SoftVideoDecoderOMXComponent::SoftVideoDecoderOMXComponent(
+ const char *name,
+ const char *componentRole,
+ OMX_VIDEO_CODINGTYPE codingType,
+ const CodecProfileLevel *profileLevels,
+ size_t numProfileLevels,
+ int32_t width,
+ int32_t height,
+ const OMX_CALLBACKTYPE *callbacks,
+ OMX_PTR appData,
+ OMX_COMPONENTTYPE **component)
+ : SimpleSoftOMXComponent(name, callbacks, appData, component),
+ mWidth(width),
+ mHeight(height),
+ mCropLeft(0),
+ mCropTop(0),
+ mCropWidth(width),
+ mCropHeight(height),
+ mOutputPortSettingsChange(NONE),
+ mComponentRole(componentRole),
+ mCodingType(codingType),
+ mProfileLevels(profileLevels),
+ mNumProfileLevels(numProfileLevels) {
+}
+
+void SoftVideoDecoderOMXComponent::initPorts(
+ OMX_U32 numInputBuffers,
+ OMX_U32 inputBufferSize,
+ OMX_U32 numOutputBuffers,
+ const char *mimeType) {
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ InitOMXParams(&def);
+
+ def.nPortIndex = kInputPortIndex;
+ def.eDir = OMX_DirInput;
+ def.nBufferCountMin = numInputBuffers;
+ def.nBufferCountActual = def.nBufferCountMin;
+ def.nBufferSize = inputBufferSize;
+ def.bEnabled = OMX_TRUE;
+ def.bPopulated = OMX_FALSE;
+ def.eDomain = OMX_PortDomainVideo;
+ def.bBuffersContiguous = OMX_FALSE;
+ def.nBufferAlignment = 1;
+
+ def.format.video.cMIMEType = const_cast<char *>(mimeType);
+ def.format.video.pNativeRender = NULL;
+ /* size is initialized in updatePortDefinitions() */
+ def.format.video.nBitrate = 0;
+ def.format.video.xFramerate = 0;
+ def.format.video.bFlagErrorConcealment = OMX_FALSE;
+ def.format.video.eCompressionFormat = mCodingType;
+ def.format.video.eColorFormat = OMX_COLOR_FormatUnused;
+ def.format.video.pNativeWindow = NULL;
+
+ addPort(def);
+
+ def.nPortIndex = kOutputPortIndex;
+ def.eDir = OMX_DirOutput;
+ def.nBufferCountMin = numOutputBuffers;
+ def.nBufferCountActual = def.nBufferCountMin;
+ def.bEnabled = OMX_TRUE;
+ def.bPopulated = OMX_FALSE;
+ def.eDomain = OMX_PortDomainVideo;
+ def.bBuffersContiguous = OMX_FALSE;
+ def.nBufferAlignment = 2;
+
+ def.format.video.cMIMEType = const_cast<char *>("video/raw");
+ def.format.video.pNativeRender = NULL;
+ /* size is initialized in updatePortDefinitions() */
+ def.format.video.nBitrate = 0;
+ def.format.video.xFramerate = 0;
+ def.format.video.bFlagErrorConcealment = OMX_FALSE;
+ def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused;
+ def.format.video.eColorFormat = OMX_COLOR_FormatYUV420Planar;
+ def.format.video.pNativeWindow = NULL;
+
+ addPort(def);
+
+ updatePortDefinitions();
+}
+
+void SoftVideoDecoderOMXComponent::updatePortDefinitions() {
+ OMX_PARAM_PORTDEFINITIONTYPE *def = &editPortInfo(kInputPortIndex)->mDef;
+ def->format.video.nFrameWidth = mWidth;
+ def->format.video.nFrameHeight = mHeight;
+ def->format.video.nStride = def->format.video.nFrameWidth;
+ def->format.video.nSliceHeight = def->format.video.nFrameHeight;
+
+ def = &editPortInfo(kOutputPortIndex)->mDef;
+ def->format.video.nFrameWidth = mWidth;
+ def->format.video.nFrameHeight = mHeight;
+ def->format.video.nStride = def->format.video.nFrameWidth;
+ def->format.video.nSliceHeight = def->format.video.nFrameHeight;
+
+ def->nBufferSize =
+ (def->format.video.nFrameWidth *
+ def->format.video.nFrameHeight * 3) / 2;
+
+ mCropLeft = 0;
+ mCropTop = 0;
+ mCropWidth = mWidth;
+ mCropHeight = mHeight;
+}
+
+OMX_ERRORTYPE SoftVideoDecoderOMXComponent::internalGetParameter(
+ OMX_INDEXTYPE index, OMX_PTR params) {
+ switch (index) {
+ case OMX_IndexParamVideoPortFormat:
+ {
+ OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams =
+ (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params;
+
+ if (formatParams->nPortIndex > kMaxPortIndex) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (formatParams->nIndex != 0) {
+ return OMX_ErrorNoMore;
+ }
+
+ if (formatParams->nPortIndex == kInputPortIndex) {
+ formatParams->eCompressionFormat = mCodingType;
+ formatParams->eColorFormat = OMX_COLOR_FormatUnused;
+ formatParams->xFramerate = 0;
+ } else {
+ CHECK_EQ(formatParams->nPortIndex, 1u);
+
+ formatParams->eCompressionFormat = OMX_VIDEO_CodingUnused;
+ formatParams->eColorFormat = OMX_COLOR_FormatYUV420Planar;
+ formatParams->xFramerate = 0;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamVideoProfileLevelQuerySupported:
+ {
+ OMX_VIDEO_PARAM_PROFILELEVELTYPE *profileLevel =
+ (OMX_VIDEO_PARAM_PROFILELEVELTYPE *) params;
+
+ if (profileLevel->nPortIndex != kInputPortIndex) {
+ ALOGE("Invalid port index: %ld", profileLevel->nPortIndex);
+ return OMX_ErrorUnsupportedIndex;
+ }
+
+ if (index >= mNumProfileLevels) {
+ return OMX_ErrorNoMore;
+ }
+
+ profileLevel->eProfile = mProfileLevels[index].mProfile;
+ profileLevel->eLevel = mProfileLevels[index].mLevel;
+ return OMX_ErrorNone;
+ }
+
+ default:
+ return SimpleSoftOMXComponent::internalGetParameter(index, params);
+ }
+}
+
+OMX_ERRORTYPE SoftVideoDecoderOMXComponent::internalSetParameter(
+ OMX_INDEXTYPE index, const OMX_PTR params) {
+ switch (index) {
+ case OMX_IndexParamStandardComponentRole:
+ {
+ const OMX_PARAM_COMPONENTROLETYPE *roleParams =
+ (const OMX_PARAM_COMPONENTROLETYPE *)params;
+
+ if (strncmp((const char *)roleParams->cRole,
+ mComponentRole,
+ OMX_MAX_STRINGNAME_SIZE - 1)) {
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamVideoPortFormat:
+ {
+ OMX_VIDEO_PARAM_PORTFORMATTYPE *formatParams =
+ (OMX_VIDEO_PARAM_PORTFORMATTYPE *)params;
+
+ if (formatParams->nPortIndex > kMaxPortIndex) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (formatParams->nIndex != 0) {
+ return OMX_ErrorNoMore;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+ default:
+ return SimpleSoftOMXComponent::internalSetParameter(index, params);
+ }
+}
+
+OMX_ERRORTYPE SoftVideoDecoderOMXComponent::getConfig(
+ OMX_INDEXTYPE index, OMX_PTR params) {
+ switch (index) {
+ case OMX_IndexConfigCommonOutputCrop:
+ {
+ OMX_CONFIG_RECTTYPE *rectParams = (OMX_CONFIG_RECTTYPE *)params;
+
+ if (rectParams->nPortIndex != kOutputPortIndex) {
+ return OMX_ErrorUndefined;
+ }
+
+ rectParams->nLeft = mCropLeft;
+ rectParams->nTop = mCropTop;
+ rectParams->nWidth = mCropWidth;
+ rectParams->nHeight = mCropHeight;
+
+ return OMX_ErrorNone;
+ }
+
+ default:
+ return OMX_ErrorUnsupportedIndex;
+ }
+}
+
+void SoftVideoDecoderOMXComponent::onReset() {
+ mOutputPortSettingsChange = NONE;
+}
+
+void SoftVideoDecoderOMXComponent::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
+ if (portIndex != kOutputPortIndex) {
+ return;
+ }
+
+ switch (mOutputPortSettingsChange) {
+ case NONE:
+ break;
+
+ case AWAITING_DISABLED:
+ {
+ CHECK(!enabled);
+ mOutputPortSettingsChange = AWAITING_ENABLED;
+ break;
+ }
+
+ default:
+ {
+ CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED);
+ CHECK(enabled);
+ mOutputPortSettingsChange = NONE;
+ break;
+ }
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp
index 6cca8da..4bee808 100644
--- a/media/libstagefright/omx/tests/OMXHarness.cpp
+++ b/media/libstagefright/omx/tests/OMXHarness.cpp
@@ -449,7 +449,8 @@ static const char *GetMimeFromComponentRole(const char *componentRole) {
{ "video_decoder.avc", "video/avc" },
{ "video_decoder.mpeg4", "video/mp4v-es" },
{ "video_decoder.h263", "video/3gpp" },
- { "video_decoder.vpx", "video/x-vnd.on2.vp8" },
+ { "video_decoder.vp8", "video/x-vnd.on2.vp8" },
+ { "video_decoder.vp9", "video/x-vnd.on2.vp9" },
// we appear to use this as a synonym to amrnb.
{ "audio_decoder.amr", "audio/3gpp" },
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
index faac3fd..efde7a9 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -60,6 +60,7 @@ ARTSPConnection::~ARTSPConnection() {
ALOGE("Connection is still open, closing the socket.");
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(mSocket);
+ HTTPBase::UnRegisterSocketUserMark(mSocket);
}
close(mSocket);
mSocket = -1;
@@ -214,6 +215,7 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
if (mState != DISCONNECTED) {
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(mSocket);
+ HTTPBase::UnRegisterSocketUserMark(mSocket);
}
close(mSocket);
mSocket = -1;
@@ -266,6 +268,7 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
if (mUIDValid) {
HTTPBase::RegisterSocketUserTag(mSocket, mUID,
(uint32_t)*(uint32_t*) "RTSP");
+ HTTPBase::RegisterSocketUserMark(mSocket, mUID);
}
MakeSocketBlocking(mSocket, false);
@@ -295,6 +298,7 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(mSocket);
+ HTTPBase::UnRegisterSocketUserMark(mSocket);
}
close(mSocket);
mSocket = -1;
@@ -312,6 +316,7 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
void ARTSPConnection::performDisconnect() {
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(mSocket);
+ HTTPBase::UnRegisterSocketUserMark(mSocket);
}
close(mSocket);
mSocket = -1;
@@ -385,6 +390,7 @@ void ARTSPConnection::onCompleteConnection(const sp<AMessage> &msg) {
mState = DISCONNECTED;
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(mSocket);
+ HTTPBase::UnRegisterSocketUserMark(mSocket);
}
close(mSocket);
mSocket = -1;
diff --git a/media/libstagefright/rtsp/Android.mk b/media/libstagefright/rtsp/Android.mk
index 9e2724d..e77c69c 100644
--- a/media/libstagefright/rtsp/Android.mk
+++ b/media/libstagefright/rtsp/Android.mk
@@ -51,7 +51,7 @@ LOCAL_C_INCLUDES:= \
LOCAL_CFLAGS += -Wno-multichar
-LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE_TAGS := optional
LOCAL_MODULE:= rtp_test
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index 8681682..cd77aa0 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -716,7 +716,9 @@ struct MyHandler : public AHandler {
// Clear the tag
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(track->mRTPSocket);
+ HTTPBase::UnRegisterSocketUserMark(track->mRTPSocket);
HTTPBase::UnRegisterSocketUserTag(track->mRTCPSocket);
+ HTTPBase::UnRegisterSocketUserMark(track->mRTCPSocket);
}
close(track->mRTPSocket);
@@ -847,7 +849,9 @@ struct MyHandler : public AHandler {
// Clear the tag
if (mUIDValid) {
HTTPBase::UnRegisterSocketUserTag(info->mRTPSocket);
+ HTTPBase::UnRegisterSocketUserMark(info->mRTPSocket);
HTTPBase::UnRegisterSocketUserTag(info->mRTCPSocket);
+ HTTPBase::UnRegisterSocketUserMark(info->mRTCPSocket);
}
close(info->mRTPSocket);
@@ -1605,6 +1609,8 @@ private:
(uint32_t)*(uint32_t*) "RTP_");
HTTPBase::RegisterSocketUserTag(info->mRTCPSocket, mUID,
(uint32_t)*(uint32_t*) "RTP_");
+ HTTPBase::RegisterSocketUserMark(info->mRTPSocket, mUID);
+ HTTPBase::RegisterSocketUserMark(info->mRTCPSocket, mUID);
}
request.append("Transport: RTP/AVP/UDP;unicast;client_port=");
@@ -1681,6 +1687,26 @@ private:
return true;
}
+ void handleFirstAccessUnit() {
+ if (mFirstAccessUnit) {
+ sp<AMessage> msg = mNotify->dup();
+ msg->setInt32("what", kWhatConnected);
+ msg->post();
+
+ if (mSeekable) {
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ TrackInfo *info = &mTracks.editItemAt(i);
+
+ postNormalPlayTimeMapping(
+ i,
+ info->mNormalPlayTimeRTP, info->mNormalPlayTimeUs);
+ }
+ }
+
+ mFirstAccessUnit = false;
+ }
+ }
+
void onTimeUpdate(int32_t trackIndex, uint32_t rtpTime, uint64_t ntpTime) {
ALOGV("onTimeUpdate track %d, rtpTime = 0x%08x, ntpTime = 0x%016llx",
trackIndex, rtpTime, ntpTime);
@@ -1712,6 +1738,8 @@ private:
}
}
if (mAllTracksHaveTime && dataReceivedOnAllChannels()) {
+ handleFirstAccessUnit();
+
// Time is now established, lets start timestamping immediately
for (size_t i = 0; i < mTracks.size(); ++i) {
TrackInfo *trackInfo = &mTracks.editItemAt(i);
@@ -1745,23 +1773,7 @@ private:
return;
}
- if (mFirstAccessUnit) {
- sp<AMessage> msg = mNotify->dup();
- msg->setInt32("what", kWhatConnected);
- msg->post();
-
- if (mSeekable) {
- for (size_t i = 0; i < mTracks.size(); ++i) {
- TrackInfo *info = &mTracks.editItemAt(i);
-
- postNormalPlayTimeMapping(
- i,
- info->mNormalPlayTimeRTP, info->mNormalPlayTimeUs);
- }
- }
-
- mFirstAccessUnit = false;
- }
+ handleFirstAccessUnit();
TrackInfo *track = &mTracks.editItemAt(trackIndex);
diff --git a/media/libstagefright/tests/SurfaceMediaSource_test.cpp b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
index a5459fe..49ffcd6 100644
--- a/media/libstagefright/tests/SurfaceMediaSource_test.cpp
+++ b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
@@ -23,6 +23,8 @@
#include <fcntl.h>
#include <unistd.h>
+#include <GLES2/gl2.h>
+
#include <media/stagefright/SurfaceMediaSource.h>
#include <media/mediarecorder.h>
diff --git a/media/libstagefright/wifi-display/ANetworkSession.h b/media/libstagefright/wifi-display/ANetworkSession.h
deleted file mode 100644
index 7c62b29..0000000
--- a/media/libstagefright/wifi-display/ANetworkSession.h
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2012, 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_NETWORK_SESSION_H_
-
-#define A_NETWORK_SESSION_H_
-
-#include <media/stagefright/foundation/ABase.h>
-#include <utils/KeyedVector.h>
-#include <utils/RefBase.h>
-#include <utils/Thread.h>
-
-#include <netinet/in.h>
-
-namespace android {
-
-struct AMessage;
-
-// Helper class to manage a number of live sockets (datagram and stream-based)
-// on a single thread. Clients are notified about activity through AMessages.
-struct ANetworkSession : public RefBase {
- ANetworkSession();
-
- status_t start();
- status_t stop();
-
- status_t createRTSPClient(
- const char *host, unsigned port, const sp<AMessage> &notify,
- int32_t *sessionID);
-
- status_t createRTSPServer(
- const struct in_addr &addr, unsigned port,
- const sp<AMessage> &notify, int32_t *sessionID);
-
- status_t createUDPSession(
- unsigned localPort, const sp<AMessage> &notify, int32_t *sessionID);
-
- status_t createUDPSession(
- unsigned localPort,
- const char *remoteHost,
- unsigned remotePort,
- const sp<AMessage> &notify,
- int32_t *sessionID);
-
- status_t connectUDPSession(
- int32_t sessionID, const char *remoteHost, unsigned remotePort);
-
- // passive
- status_t createTCPDatagramSession(
- const struct in_addr &addr, unsigned port,
- const sp<AMessage> &notify, int32_t *sessionID);
-
- // active
- status_t createTCPDatagramSession(
- unsigned localPort,
- const char *remoteHost,
- unsigned remotePort,
- const sp<AMessage> &notify,
- int32_t *sessionID);
-
- status_t destroySession(int32_t sessionID);
-
- status_t sendRequest(
- int32_t sessionID, const void *data, ssize_t size = -1,
- bool timeValid = false, int64_t timeUs = -1ll);
-
- enum NotificationReason {
- kWhatError,
- kWhatConnected,
- kWhatClientConnected,
- kWhatData,
- kWhatDatagram,
- kWhatBinaryData,
- kWhatNetworkStall,
- };
-
-protected:
- virtual ~ANetworkSession();
-
-private:
- struct NetworkThread;
- struct Session;
-
- Mutex mLock;
- sp<Thread> mThread;
-
- int32_t mNextSessionID;
-
- int mPipeFd[2];
-
- KeyedVector<int32_t, sp<Session> > mSessions;
-
- enum Mode {
- kModeCreateUDPSession,
- kModeCreateTCPDatagramSessionPassive,
- kModeCreateTCPDatagramSessionActive,
- kModeCreateRTSPServer,
- kModeCreateRTSPClient,
- };
- status_t createClientOrServer(
- Mode mode,
- const struct in_addr *addr,
- unsigned port,
- const char *remoteHost,
- unsigned remotePort,
- const sp<AMessage> &notify,
- int32_t *sessionID);
-
- void threadLoop();
- void interrupt();
-
- static status_t MakeSocketNonBlocking(int s);
-
- DISALLOW_EVIL_CONSTRUCTORS(ANetworkSession);
-};
-
-} // namespace android
-
-#endif // A_NETWORK_SESSION_H_
diff --git a/media/libstagefright/wifi-display/Android.mk b/media/libstagefright/wifi-display/Android.mk
index 061ae89..f70454a 100644
--- a/media/libstagefright/wifi-display/Android.mk
+++ b/media/libstagefright/wifi-display/Android.mk
@@ -3,10 +3,8 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- ANetworkSession.cpp \
MediaSender.cpp \
Parameters.cpp \
- ParsedMessage.cpp \
rtp/RTPSender.cpp \
source/Converter.cpp \
source/MediaPuller.cpp \
@@ -37,26 +35,3 @@ LOCAL_MODULE:= libstagefright_wfd
LOCAL_MODULE_TAGS:= optional
include $(BUILD_SHARED_LIBRARY)
-
-################################################################################
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- wfd.cpp \
-
-LOCAL_SHARED_LIBRARIES:= \
- libbinder \
- libgui \
- libmedia \
- libstagefright \
- libstagefright_foundation \
- libstagefright_wfd \
- libutils \
- liblog \
-
-LOCAL_MODULE:= wfd
-
-LOCAL_MODULE_TAGS := debug
-
-include $(BUILD_EXECUTABLE)
diff --git a/media/libstagefright/wifi-display/MediaSender.cpp b/media/libstagefright/wifi-display/MediaSender.cpp
index 8a3566f..b1cdec0 100644
--- a/media/libstagefright/wifi-display/MediaSender.cpp
+++ b/media/libstagefright/wifi-display/MediaSender.cpp
@@ -20,16 +20,18 @@
#include "MediaSender.h"
-#include "ANetworkSession.h"
#include "rtp/RTPSender.h"
#include "source/TSPacketizer.h"
#include "include/avc_utils.h"
#include <media/IHDCP.h>
+#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ANetworkSession.h>
+#include <ui/GraphicBuffer.h>
namespace android {
@@ -341,6 +343,22 @@ void MediaSender::onSenderNotify(const sp<AMessage> &msg) {
break;
}
+ case kWhatInformSender:
+ {
+ int64_t avgLatencyUs;
+ CHECK(msg->findInt64("avgLatencyUs", &avgLatencyUs));
+
+ int64_t maxLatencyUs;
+ CHECK(msg->findInt64("maxLatencyUs", &maxLatencyUs));
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInformSender);
+ notify->setInt64("avgLatencyUs", avgLatencyUs);
+ notify->setInt64("maxLatencyUs", maxLatencyUs);
+ notify->post();
+ break;
+ }
+
default:
TRESPASS();
}
@@ -392,11 +410,36 @@ status_t MediaSender::packetizeAccessUnit(
info.mPacketizerTrackIndex, accessUnit);
}
- status_t err = mHDCP->encrypt(
- accessUnit->data(), accessUnit->size(),
- trackIndex /* streamCTR */,
- &inputCTR,
- accessUnit->data());
+ status_t err;
+ native_handle_t* handle;
+ if (accessUnit->meta()->findPointer("handle", (void**)&handle)
+ && handle != NULL) {
+ int32_t rangeLength, rangeOffset;
+ sp<AMessage> notify;
+ CHECK(accessUnit->meta()->findInt32("rangeOffset", &rangeOffset));
+ CHECK(accessUnit->meta()->findInt32("rangeLength", &rangeLength));
+ CHECK(accessUnit->meta()->findMessage("notify", &notify)
+ && notify != NULL);
+ CHECK_GE(accessUnit->size(), rangeLength);
+
+ sp<GraphicBuffer> grbuf(new GraphicBuffer(
+ rangeOffset + rangeLength, 1, HAL_PIXEL_FORMAT_Y8,
+ GRALLOC_USAGE_HW_VIDEO_ENCODER, rangeOffset + rangeLength,
+ handle, false));
+
+ err = mHDCP->encryptNative(
+ grbuf, rangeOffset, rangeLength,
+ trackIndex /* streamCTR */,
+ &inputCTR,
+ accessUnit->data());
+ notify->post();
+ } else {
+ err = mHDCP->encrypt(
+ accessUnit->data(), accessUnit->size(),
+ trackIndex /* streamCTR */,
+ &inputCTR,
+ accessUnit->data());
+ }
if (err != OK) {
ALOGE("Failed to HDCP-encrypt media data (err %d)",
diff --git a/media/libstagefright/wifi-display/MediaSender.h b/media/libstagefright/wifi-display/MediaSender.h
index 64722c5..04538ea 100644
--- a/media/libstagefright/wifi-display/MediaSender.h
+++ b/media/libstagefright/wifi-display/MediaSender.h
@@ -43,6 +43,7 @@ struct MediaSender : public AHandler {
kWhatInitDone,
kWhatError,
kWhatNetworkStall,
+ kWhatInformSender,
};
MediaSender(
diff --git a/media/libstagefright/wifi-display/ParsedMessage.h b/media/libstagefright/wifi-display/ParsedMessage.h
deleted file mode 100644
index e9a1859..0000000
--- a/media/libstagefright/wifi-display/ParsedMessage.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2012, 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 <media/stagefright/foundation/ABase.h>
-#include <media/stagefright/foundation/AString.h>
-#include <utils/KeyedVector.h>
-#include <utils/RefBase.h>
-
-namespace android {
-
-// Encapsulates an "HTTP/RTSP style" response, i.e. a status line,
-// key/value pairs making up the headers and an optional body/content.
-struct ParsedMessage : public RefBase {
- static sp<ParsedMessage> Parse(
- const char *data, size_t size, bool noMoreData, size_t *length);
-
- bool findString(const char *name, AString *value) const;
- bool findInt32(const char *name, int32_t *value) const;
-
- const char *getContent() const;
-
- void getRequestField(size_t index, AString *field) const;
- bool getStatusCode(int32_t *statusCode) const;
-
- AString debugString() const;
-
- static bool GetAttribute(const char *s, const char *key, AString *value);
-
- static bool GetInt32Attribute(
- const char *s, const char *key, int32_t *value);
-
-
-protected:
- virtual ~ParsedMessage();
-
-private:
- KeyedVector<AString, AString> mDict;
- AString mContent;
-
- ParsedMessage();
-
- ssize_t parse(const char *data, size_t size, bool noMoreData);
-
- DISALLOW_EVIL_CONSTRUCTORS(ParsedMessage);
-};
-
-} // namespace android
diff --git a/media/libstagefright/wifi-display/VideoFormats.cpp b/media/libstagefright/wifi-display/VideoFormats.cpp
index 458b163..04e02c1 100644
--- a/media/libstagefright/wifi-display/VideoFormats.cpp
+++ b/media/libstagefright/wifi-display/VideoFormats.cpp
@@ -24,7 +24,8 @@
namespace android {
-VideoFormats::config_t VideoFormats::mConfigs[][32] = {
+// static
+const VideoFormats::config_t VideoFormats::mResolutionTable[][32] = {
{
// CEA Resolutions
{ 640, 480, 60, false, 0, 0},
@@ -133,6 +134,8 @@ VideoFormats::config_t VideoFormats::mConfigs[][32] = {
};
VideoFormats::VideoFormats() {
+ memcpy(mConfigs, mResolutionTable, sizeof(mConfigs));
+
for (size_t i = 0; i < kNumResolutionTypes; ++i) {
mResolutionEnabled[i] = 0;
}
@@ -175,6 +178,29 @@ void VideoFormats::enableAll() {
}
}
+void VideoFormats::enableResolutionUpto(
+ ResolutionType type, size_t index,
+ ProfileType profile, LevelType level) {
+ size_t width, height, fps, score;
+ bool interlaced;
+ if (!GetConfiguration(type, index, &width, &height,
+ &fps, &interlaced)) {
+ ALOGE("Maximum resolution not found!");
+ return;
+ }
+ score = width * height * fps * (!interlaced + 1);
+ for (size_t i = 0; i < kNumResolutionTypes; ++i) {
+ for (size_t j = 0; j < 32; j++) {
+ if (GetConfiguration((ResolutionType)i, j,
+ &width, &height, &fps, &interlaced)
+ && score >= width * height * fps * (!interlaced + 1)) {
+ setResolutionEnabled((ResolutionType)i, j);
+ setProfileLevel((ResolutionType)i, j, profile, level);
+ }
+ }
+ }
+}
+
void VideoFormats::setResolutionEnabled(
ResolutionType type, size_t index, bool enabled) {
CHECK_LT(type, kNumResolutionTypes);
@@ -182,11 +208,56 @@ void VideoFormats::setResolutionEnabled(
if (enabled) {
mResolutionEnabled[type] |= (1ul << index);
+ mConfigs[type][index].profile = (1ul << PROFILE_CBP);
+ mConfigs[type][index].level = (1ul << LEVEL_31);
} else {
mResolutionEnabled[type] &= ~(1ul << index);
+ mConfigs[type][index].profile = 0;
+ mConfigs[type][index].level = 0;
}
}
+void VideoFormats::setProfileLevel(
+ ResolutionType type, size_t index,
+ ProfileType profile, LevelType level) {
+ CHECK_LT(type, kNumResolutionTypes);
+ CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));
+
+ mConfigs[type][index].profile = (1ul << profile);
+ mConfigs[type][index].level = (1ul << level);
+}
+
+void VideoFormats::getProfileLevel(
+ ResolutionType type, size_t index,
+ ProfileType *profile, LevelType *level) const{
+ CHECK_LT(type, kNumResolutionTypes);
+ CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));
+
+ int i, bestProfile = -1, bestLevel = -1;
+
+ for (i = 0; i < kNumProfileTypes; ++i) {
+ if (mConfigs[type][index].profile & (1ul << i)) {
+ bestProfile = i;
+ }
+ }
+
+ for (i = 0; i < kNumLevelTypes; ++i) {
+ if (mConfigs[type][index].level & (1ul << i)) {
+ bestLevel = i;
+ }
+ }
+
+ if (bestProfile == -1 || bestLevel == -1) {
+ ALOGE("Profile or level not set for resolution type %d, index %d",
+ type, index);
+ bestProfile = PROFILE_CBP;
+ bestLevel = LEVEL_31;
+ }
+
+ *profile = (ProfileType) bestProfile;
+ *level = (LevelType) bestLevel;
+}
+
bool VideoFormats::isResolutionEnabled(
ResolutionType type, size_t index) const {
CHECK_LT(type, kNumResolutionTypes);
@@ -207,7 +278,7 @@ bool VideoFormats::GetConfiguration(
return false;
}
- const config_t *config = &mConfigs[type][index];
+ const config_t *config = &mResolutionTable[type][index];
if (config->width == 0) {
return false;
@@ -251,9 +322,12 @@ bool VideoFormats::parseH264Codec(const char *spec) {
if (res[i] & (1ul << j)){
mResolutionEnabled[i] |= (1ul << j);
if (profile > mConfigs[i][j].profile) {
+ // prefer higher profile (even if level is lower)
mConfigs[i][j].profile = profile;
- if (level > mConfigs[i][j].level)
- mConfigs[i][j].level = level;
+ mConfigs[i][j].level = level;
+ } else if (profile == mConfigs[i][j].profile &&
+ level > mConfigs[i][j].level) {
+ mConfigs[i][j].level = level;
}
}
}
@@ -262,9 +336,51 @@ bool VideoFormats::parseH264Codec(const char *spec) {
return true;
}
+// static
+bool VideoFormats::GetProfileLevel(
+ ProfileType profile, LevelType level, unsigned *profileIdc,
+ unsigned *levelIdc, unsigned *constraintSet) {
+ CHECK_LT(profile, kNumProfileTypes);
+ CHECK_LT(level, kNumLevelTypes);
+
+ static const unsigned kProfileIDC[kNumProfileTypes] = {
+ 66, // PROFILE_CBP
+ 100, // PROFILE_CHP
+ };
+
+ static const unsigned kLevelIDC[kNumLevelTypes] = {
+ 31, // LEVEL_31
+ 32, // LEVEL_32
+ 40, // LEVEL_40
+ 41, // LEVEL_41
+ 42, // LEVEL_42
+ };
+
+ static const unsigned kConstraintSet[kNumProfileTypes] = {
+ 0xc0, // PROFILE_CBP
+ 0x0c, // PROFILE_CHP
+ };
+
+ if (profileIdc) {
+ *profileIdc = kProfileIDC[profile];
+ }
+
+ if (levelIdc) {
+ *levelIdc = kLevelIDC[level];
+ }
+
+ if (constraintSet) {
+ *constraintSet = kConstraintSet[profile];
+ }
+
+ return true;
+}
+
bool VideoFormats::parseFormatSpec(const char *spec) {
CHECK_EQ(kNumResolutionTypes, 3);
+ disableAll();
+
unsigned native, dummy;
unsigned res[3];
size_t size = strlen(spec);
@@ -320,8 +436,10 @@ AString VideoFormats::getFormatSpec(bool forM4Message) const {
// max-vres (none or 2 byte)
return StringPrintf(
- "%02x 00 02 02 %08x %08x %08x 00 0000 0000 00 none none",
+ "%02x 00 %02x %02x %08x %08x %08x 00 0000 0000 00 none none",
forM4Message ? 0x00 : ((mNativeIndex << 3) | mNativeType),
+ mConfigs[mNativeType][mNativeIndex].profile,
+ mConfigs[mNativeType][mNativeIndex].level,
mResolutionEnabled[0],
mResolutionEnabled[1],
mResolutionEnabled[2]);
@@ -332,7 +450,9 @@ bool VideoFormats::PickBestFormat(
const VideoFormats &sinkSupported,
const VideoFormats &sourceSupported,
ResolutionType *chosenType,
- size_t *chosenIndex) {
+ size_t *chosenIndex,
+ ProfileType *chosenProfile,
+ LevelType *chosenLevel) {
#if 0
// Support for the native format is a great idea, the spec includes
// these features, but nobody supports it and the tests don't validate it.
@@ -412,6 +532,18 @@ bool VideoFormats::PickBestFormat(
*chosenType = (ResolutionType)bestType;
*chosenIndex = bestIndex;
+ // Pick the best profile/level supported by both sink and source.
+ ProfileType srcProfile, sinkProfile;
+ LevelType srcLevel, sinkLevel;
+ sourceSupported.getProfileLevel(
+ (ResolutionType)bestType, bestIndex,
+ &srcProfile, &srcLevel);
+ sinkSupported.getProfileLevel(
+ (ResolutionType)bestType, bestIndex,
+ &sinkProfile, &sinkLevel);
+ *chosenProfile = srcProfile < sinkProfile ? srcProfile : sinkProfile;
+ *chosenLevel = srcLevel < sinkLevel ? srcLevel : sinkLevel;
+
return true;
}
diff --git a/media/libstagefright/wifi-display/VideoFormats.h b/media/libstagefright/wifi-display/VideoFormats.h
index 01de246..fd38fd1 100644
--- a/media/libstagefright/wifi-display/VideoFormats.h
+++ b/media/libstagefright/wifi-display/VideoFormats.h
@@ -69,17 +69,33 @@ struct VideoFormats {
void disableAll();
void enableAll();
+ void enableResolutionUpto(
+ ResolutionType type, size_t index,
+ ProfileType profile, LevelType level);
void setResolutionEnabled(
ResolutionType type, size_t index, bool enabled = true);
bool isResolutionEnabled(ResolutionType type, size_t index) const;
+ void setProfileLevel(
+ ResolutionType type, size_t index,
+ ProfileType profile, LevelType level);
+
+ void getProfileLevel(
+ ResolutionType type, size_t index,
+ ProfileType *profile, LevelType *level) const;
+
static bool GetConfiguration(
ResolutionType type, size_t index,
size_t *width, size_t *height, size_t *framesPerSecond,
bool *interlaced);
+ static bool GetProfileLevel(
+ ProfileType profile, LevelType level,
+ unsigned *profileIdc, unsigned *levelIdc,
+ unsigned *constraintSet);
+
bool parseFormatSpec(const char *spec);
AString getFormatSpec(bool forM4Message = false) const;
@@ -87,7 +103,9 @@ struct VideoFormats {
const VideoFormats &sinkSupported,
const VideoFormats &sourceSupported,
ResolutionType *chosenType,
- size_t *chosenIndex);
+ size_t *chosenIndex,
+ ProfileType *chosenProfile,
+ LevelType *chosenLevel);
private:
bool parseH264Codec(const char *spec);
@@ -95,7 +113,8 @@ private:
size_t mNativeIndex;
uint32_t mResolutionEnabled[kNumResolutionTypes];
- static config_t mConfigs[kNumResolutionTypes][32];
+ static const config_t mResolutionTable[kNumResolutionTypes][32];
+ config_t mConfigs[kNumResolutionTypes][32];
DISALLOW_EVIL_CONSTRUCTORS(VideoFormats);
};
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.cpp b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
index 095fd97..1887b8b 100644
--- a/media/libstagefright/wifi-display/rtp/RTPSender.cpp
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
@@ -20,11 +20,10 @@
#include "RTPSender.h"
-#include "ANetworkSession.h"
-
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ANetworkSession.h>
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/Utils.h>
@@ -767,6 +766,17 @@ status_t RTPSender::parseTSFB(const uint8_t *data, size_t size) {
}
status_t RTPSender::parseAPP(const uint8_t *data, size_t size) {
+ if (!memcmp("late", &data[8], 4)) {
+ int64_t avgLatencyUs = (int64_t)U64_AT(&data[12]);
+ int64_t maxLatencyUs = (int64_t)U64_AT(&data[20]);
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatInformSender);
+ notify->setInt64("avgLatencyUs", avgLatencyUs);
+ notify->setInt64("maxLatencyUs", maxLatencyUs);
+ notify->post();
+ }
+
return OK;
}
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.h b/media/libstagefright/wifi-display/rtp/RTPSender.h
index 7dc138a..fefcab7 100644
--- a/media/libstagefright/wifi-display/rtp/RTPSender.h
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.h
@@ -37,6 +37,7 @@ struct RTPSender : public RTPBase, public AHandler {
kWhatInitDone,
kWhatError,
kWhatNetworkStall,
+ kWhatInformSender,
};
RTPSender(
const sp<ANetworkSession> &netSession,
diff --git a/media/libstagefright/wifi-display/source/Converter.cpp b/media/libstagefright/wifi-display/source/Converter.cpp
index 5344623..753b3ec 100644
--- a/media/libstagefright/wifi-display/source/Converter.cpp
+++ b/media/libstagefright/wifi-display/source/Converter.cpp
@@ -21,6 +21,7 @@
#include "Converter.h"
#include "MediaPuller.h"
+#include "include/avc_utils.h"
#include <cutils/properties.h>
#include <gui/Surface.h>
@@ -33,6 +34,8 @@
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
+#include <arpa/inet.h>
+
#include <OMX_Video.h>
namespace android {
@@ -40,14 +43,15 @@ namespace android {
Converter::Converter(
const sp<AMessage> &notify,
const sp<ALooper> &codecLooper,
- const sp<AMessage> &format,
- bool usePCMAudio)
- : mInitCheck(NO_INIT),
- mNotify(notify),
+ const sp<AMessage> &outputFormat,
+ uint32_t flags)
+ : mNotify(notify),
mCodecLooper(codecLooper),
- mInputFormat(format),
+ mOutputFormat(outputFormat),
+ mFlags(flags),
mIsVideo(false),
- mIsPCMAudio(usePCMAudio),
+ mIsH264(false),
+ mIsPCMAudio(false),
mNeedToManuallyPrependSPSPPS(false),
mDoMoreWorkPending(false)
#if ENABLE_SILENCE_DETECTION
@@ -56,20 +60,17 @@ Converter::Converter(
#endif
,mPrevVideoBitrate(-1)
,mNumFramesToDrop(0)
+ ,mEncodingSuspended(false)
{
AString mime;
- CHECK(mInputFormat->findString("mime", &mime));
+ CHECK(mOutputFormat->findString("mime", &mime));
if (!strncasecmp("video/", mime.c_str(), 6)) {
mIsVideo = true;
- }
-
- CHECK(!usePCMAudio || !mIsVideo);
- mInitCheck = initEncoder();
-
- if (mInitCheck != OK) {
- releaseEncoder();
+ mIsH264 = !strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_AVC);
+ } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_RAW, mime.c_str())) {
+ mIsPCMAudio = true;
}
}
@@ -119,8 +120,19 @@ void Converter::shutdownAsync() {
(new AMessage(kWhatShutdown, id()))->post();
}
-status_t Converter::initCheck() const {
- return mInitCheck;
+status_t Converter::init() {
+ status_t err = initEncoder();
+
+ if (err != OK) {
+ releaseEncoder();
+ }
+
+ return err;
+}
+
+sp<IGraphicBufferProducer> Converter::getGraphicBufferProducer() {
+ CHECK(mFlags & FLAG_USE_SURFACE_INPUT);
+ return mGraphicBufferProducer;
}
size_t Converter::getInputBufferCount() const {
@@ -152,23 +164,10 @@ int32_t Converter::GetInt32Property(
}
status_t Converter::initEncoder() {
- AString inputMIME;
- CHECK(mInputFormat->findString("mime", &inputMIME));
-
AString outputMIME;
- bool isAudio = false;
- if (!strcasecmp(inputMIME.c_str(), MEDIA_MIMETYPE_AUDIO_RAW)) {
- if (mIsPCMAudio) {
- outputMIME = MEDIA_MIMETYPE_AUDIO_RAW;
- } else {
- outputMIME = MEDIA_MIMETYPE_AUDIO_AAC;
- }
- isAudio = true;
- } else if (!strcasecmp(inputMIME.c_str(), MEDIA_MIMETYPE_VIDEO_RAW)) {
- outputMIME = MEDIA_MIMETYPE_VIDEO_AVC;
- } else {
- TRESPASS();
- }
+ CHECK(mOutputFormat->findString("mime", &outputMIME));
+
+ bool isAudio = !strncasecmp(outputMIME.c_str(), "audio/", 6);
if (!mIsPCMAudio) {
mEncoder = MediaCodec::CreateByType(
@@ -179,14 +178,10 @@ status_t Converter::initEncoder() {
}
}
- mOutputFormat = mInputFormat->dup();
-
if (mIsPCMAudio) {
return OK;
}
- mOutputFormat->setString("mime", outputMIME.c_str());
-
int32_t audioBitrate = GetInt32Property("media.wfd.audio-bitrate", 128000);
int32_t videoBitrate = GetInt32Property("media.wfd.video-bitrate", 5000000);
mPrevVideoBitrate = videoBitrate;
@@ -262,6 +257,16 @@ status_t Converter::initEncoder() {
return err;
}
+ if (mFlags & FLAG_USE_SURFACE_INPUT) {
+ CHECK(mIsVideo);
+
+ err = mEncoder->createInputSurface(&mGraphicBufferProducer);
+
+ if (err != OK) {
+ return err;
+ }
+ }
+
err = mEncoder->start();
if (err != OK) {
@@ -274,7 +279,17 @@ status_t Converter::initEncoder() {
return err;
}
- return mEncoder->getOutputBuffers(&mEncoderOutputBuffers);
+ err = mEncoder->getOutputBuffers(&mEncoderOutputBuffers);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (mFlags & FLAG_USE_SURFACE_INPUT) {
+ scheduleDoMoreWork();
+ }
+
+ return OK;
}
void Converter::notifyError(status_t err) {
@@ -330,9 +345,12 @@ void Converter::onMessageReceived(const sp<AMessage> &msg) {
sp<ABuffer> accessUnit;
CHECK(msg->findBuffer("accessUnit", &accessUnit));
- if (mIsVideo && mNumFramesToDrop) {
- --mNumFramesToDrop;
- ALOGI("dropping frame.");
+ if (mNumFramesToDrop > 0 || mEncodingSuspended) {
+ if (mNumFramesToDrop > 0) {
+ --mNumFramesToDrop;
+ ALOGI("dropping frame.");
+ }
+
ReleaseMediaBufferReference(accessUnit);
break;
}
@@ -414,7 +432,7 @@ void Converter::onMessageReceived(const sp<AMessage> &msg) {
}
if (mIsVideo) {
- ALOGI("requesting IDR frame");
+ ALOGV("requesting IDR frame");
mEncoder->requestIDRFrame();
}
break;
@@ -427,8 +445,12 @@ void Converter::onMessageReceived(const sp<AMessage> &msg) {
releaseEncoder();
AString mime;
- CHECK(mInputFormat->findString("mime", &mime));
+ CHECK(mOutputFormat->findString("mime", &mime));
ALOGI("encoder (%s) shut down.", mime.c_str());
+
+ sp<AMessage> notify = mNotify->dup();
+ notify->setInt32("what", kWhatShutdownCompleted);
+ notify->post();
break;
}
@@ -438,6 +460,32 @@ void Converter::onMessageReceived(const sp<AMessage> &msg) {
break;
}
+ case kWhatReleaseOutputBuffer:
+ {
+ if (mEncoder != NULL) {
+ size_t bufferIndex;
+ CHECK(msg->findInt32("bufferIndex", (int32_t*)&bufferIndex));
+ CHECK(bufferIndex < mEncoderOutputBuffers.size());
+ mEncoder->releaseOutputBuffer(bufferIndex);
+ }
+ break;
+ }
+
+ case kWhatSuspendEncoding:
+ {
+ int32_t suspend;
+ CHECK(msg->findInt32("suspend", &suspend));
+
+ mEncodingSuspended = suspend;
+
+ if (mFlags & FLAG_USE_SURFACE_INPUT) {
+ sp<AMessage> params = new AMessage;
+ params->setInt32("drop-input-frames",suspend);
+ mEncoder->setParameters(params);
+ }
+ break;
+ }
+
default:
TRESPASS();
}
@@ -623,28 +671,46 @@ status_t Converter::feedEncoderInputBuffers() {
return OK;
}
+sp<ABuffer> Converter::prependCSD(const sp<ABuffer> &accessUnit) const {
+ CHECK(mCSD0 != NULL);
+
+ sp<ABuffer> dup = new ABuffer(accessUnit->size() + mCSD0->size());
+ memcpy(dup->data(), mCSD0->data(), mCSD0->size());
+ memcpy(dup->data() + mCSD0->size(), accessUnit->data(), accessUnit->size());
+
+ int64_t timeUs;
+ CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+ dup->meta()->setInt64("timeUs", timeUs);
+
+ return dup;
+}
+
status_t Converter::doMoreWork() {
status_t err;
- for (;;) {
- size_t bufferIndex;
- err = mEncoder->dequeueInputBuffer(&bufferIndex);
+ if (!(mFlags & FLAG_USE_SURFACE_INPUT)) {
+ for (;;) {
+ size_t bufferIndex;
+ err = mEncoder->dequeueInputBuffer(&bufferIndex);
- if (err != OK) {
- break;
+ if (err != OK) {
+ break;
+ }
+
+ mAvailEncoderInputIndices.push_back(bufferIndex);
}
- mAvailEncoderInputIndices.push_back(bufferIndex);
+ feedEncoderInputBuffers();
}
- feedEncoderInputBuffers();
-
for (;;) {
size_t bufferIndex;
size_t offset;
size_t size;
int64_t timeUs;
uint32_t flags;
+ native_handle_t* handle = NULL;
err = mEncoder->dequeueOutputBuffer(
&bufferIndex, &offset, &size, &timeUs, &flags);
@@ -667,19 +733,63 @@ status_t Converter::doMoreWork() {
notify->setInt32("what", kWhatEOS);
notify->post();
} else {
- sp<ABuffer> buffer = new ABuffer(size);
+#if 0
+ if (mIsVideo) {
+ int32_t videoBitrate = GetInt32Property(
+ "media.wfd.video-bitrate", 5000000);
+
+ setVideoBitrate(videoBitrate);
+ }
+#endif
+
+ sp<ABuffer> buffer;
+ sp<ABuffer> outbuf = mEncoderOutputBuffers.itemAt(bufferIndex);
+
+ if (outbuf->meta()->findPointer("handle", (void**)&handle) &&
+ handle != NULL) {
+ int32_t rangeLength, rangeOffset;
+ CHECK(outbuf->meta()->findInt32("rangeOffset", &rangeOffset));
+ CHECK(outbuf->meta()->findInt32("rangeLength", &rangeLength));
+ outbuf->meta()->setPointer("handle", NULL);
+
+ // MediaSender will post the following message when HDCP
+ // is done, to release the output buffer back to encoder.
+ sp<AMessage> notify(new AMessage(
+ kWhatReleaseOutputBuffer, id()));
+ notify->setInt32("bufferIndex", bufferIndex);
+
+ buffer = new ABuffer(
+ rangeLength > (int32_t)size ? rangeLength : size);
+ buffer->meta()->setPointer("handle", handle);
+ buffer->meta()->setInt32("rangeOffset", rangeOffset);
+ buffer->meta()->setInt32("rangeLength", rangeLength);
+ buffer->meta()->setMessage("notify", notify);
+ } else {
+ buffer = new ABuffer(size);
+ }
+
buffer->meta()->setInt64("timeUs", timeUs);
ALOGV("[%s] time %lld us (%.2f secs)",
mIsVideo ? "video" : "audio", timeUs, timeUs / 1E6);
- memcpy(buffer->data(),
- mEncoderOutputBuffers.itemAt(bufferIndex)->base() + offset,
- size);
+ memcpy(buffer->data(), outbuf->base() + offset, size);
if (flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) {
- mOutputFormat->setBuffer("csd-0", buffer);
+ if (!handle) {
+ if (mIsH264) {
+ mCSD0 = buffer;
+ }
+ mOutputFormat->setBuffer("csd-0", buffer);
+ }
} else {
+ if (mNeedToManuallyPrependSPSPPS
+ && mIsH264
+ && (mFlags & FLAG_PREPEND_CSD_IF_NECESSARY)
+ && IsIDR(buffer)) {
+ buffer = prependCSD(buffer);
+ }
+
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatAccessUnit);
notify->setBuffer("accessUnit", buffer);
@@ -687,7 +797,9 @@ status_t Converter::doMoreWork() {
}
}
- mEncoder->releaseOutputBuffer(bufferIndex);
+ if (!handle) {
+ mEncoder->releaseOutputBuffer(bufferIndex);
+ }
if (flags & MediaCodec::BUFFER_FLAG_EOS) {
break;
@@ -702,9 +814,18 @@ void Converter::requestIDRFrame() {
}
void Converter::dropAFrame() {
+ // Unsupported in surface input mode.
+ CHECK(!(mFlags & FLAG_USE_SURFACE_INPUT));
+
(new AMessage(kWhatDropAFrame, id()))->post();
}
+void Converter::suspendEncoding(bool suspend) {
+ sp<AMessage> msg = new AMessage(kWhatSuspendEncoding, id());
+ msg->setInt32("suspend", suspend);
+ msg->post();
+}
+
int32_t Converter::getVideoBitrate() const {
return mPrevVideoBitrate;
}
@@ -712,7 +833,7 @@ int32_t Converter::getVideoBitrate() const {
void Converter::setVideoBitrate(int32_t bitRate) {
if (mIsVideo && mEncoder != NULL && bitRate != mPrevVideoBitrate) {
sp<AMessage> params = new AMessage;
- params->setInt32("videoBitrate", bitRate);
+ params->setInt32("video-bitrate", bitRate);
mEncoder->setParameters(params);
diff --git a/media/libstagefright/wifi-display/source/Converter.h b/media/libstagefright/wifi-display/source/Converter.h
index ba297c4..5876e07 100644
--- a/media/libstagefright/wifi-display/source/Converter.h
+++ b/media/libstagefright/wifi-display/source/Converter.h
@@ -18,13 +18,12 @@
#define CONVERTER_H_
-#include "WifiDisplaySource.h"
-
#include <media/stagefright/foundation/AHandler.h>
namespace android {
struct ABuffer;
+struct IGraphicBufferProducer;
struct MediaCodec;
#define ENABLE_SILENCE_DETECTION 0
@@ -33,13 +32,25 @@ struct MediaCodec;
// media access unit of a different format.
// Right now this'll convert raw video into H.264 and raw audio into AAC.
struct Converter : public AHandler {
- Converter(
- const sp<AMessage> &notify,
- const sp<ALooper> &codecLooper,
- const sp<AMessage> &format,
- bool usePCMAudio);
+ enum {
+ kWhatAccessUnit,
+ kWhatEOS,
+ kWhatError,
+ kWhatShutdownCompleted,
+ };
+
+ enum FlagBits {
+ FLAG_USE_SURFACE_INPUT = 1,
+ FLAG_PREPEND_CSD_IF_NECESSARY = 2,
+ };
+ Converter(const sp<AMessage> &notify,
+ const sp<ALooper> &codecLooper,
+ const sp<AMessage> &outputFormat,
+ uint32_t flags = 0);
- status_t initCheck() const;
+ status_t init();
+
+ sp<IGraphicBufferProducer> getGraphicBufferProducer();
size_t getInputBufferCount() const;
@@ -52,21 +63,7 @@ struct Converter : public AHandler {
void requestIDRFrame();
void dropAFrame();
-
- enum {
- kWhatAccessUnit,
- kWhatEOS,
- kWhatError,
- };
-
- enum {
- kWhatDoMoreWork,
- kWhatRequestIDRFrame,
- kWhatShutdown,
- kWhatMediaPullerNotify,
- kWhatEncoderActivity,
- kWhatDropAFrame,
- };
+ void suspendEncoding(bool suspend);
void shutdownAsync();
@@ -75,23 +72,40 @@ struct Converter : public AHandler {
static int32_t GetInt32Property(const char *propName, int32_t defaultValue);
+ enum {
+ // MUST not conflict with private enums below.
+ kWhatMediaPullerNotify = 'pulN',
+ };
+
protected:
virtual ~Converter();
virtual void onMessageReceived(const sp<AMessage> &msg);
private:
- status_t mInitCheck;
+ enum {
+ kWhatDoMoreWork,
+ kWhatRequestIDRFrame,
+ kWhatSuspendEncoding,
+ kWhatShutdown,
+ kWhatEncoderActivity,
+ kWhatDropAFrame,
+ kWhatReleaseOutputBuffer,
+ };
+
sp<AMessage> mNotify;
sp<ALooper> mCodecLooper;
- sp<AMessage> mInputFormat;
+ sp<AMessage> mOutputFormat;
+ uint32_t mFlags;
bool mIsVideo;
+ bool mIsH264;
bool mIsPCMAudio;
- sp<AMessage> mOutputFormat;
bool mNeedToManuallyPrependSPSPPS;
sp<MediaCodec> mEncoder;
sp<AMessage> mEncoderActivityNotify;
+ sp<IGraphicBufferProducer> mGraphicBufferProducer;
+
Vector<sp<ABuffer> > mEncoderInputBuffers;
Vector<sp<ABuffer> > mEncoderOutputBuffers;
@@ -99,6 +113,8 @@ private:
List<sp<ABuffer> > mInputBufferQueue;
+ sp<ABuffer> mCSD0;
+
bool mDoMoreWorkPending;
#if ENABLE_SILENCE_DETECTION
@@ -111,6 +127,7 @@ private:
int32_t mPrevVideoBitrate;
int32_t mNumFramesToDrop;
+ bool mEncodingSuspended;
status_t initEncoder();
void releaseEncoder();
@@ -129,6 +146,8 @@ private:
static bool IsSilence(const sp<ABuffer> &accessUnit);
+ sp<ABuffer> prependCSD(const sp<ABuffer> &accessUnit) const;
+
DISALLOW_EVIL_CONSTRUCTORS(Converter);
};
diff --git a/media/libstagefright/wifi-display/source/MediaPuller.cpp b/media/libstagefright/wifi-display/source/MediaPuller.cpp
index 189bea3..7e8891d 100644
--- a/media/libstagefright/wifi-display/source/MediaPuller.cpp
+++ b/media/libstagefright/wifi-display/source/MediaPuller.cpp
@@ -93,6 +93,9 @@ void MediaPuller::onMessageReceived(const sp<AMessage> &msg) {
err = mSource->start(params.get());
} else {
err = mSource->start();
+ if (err != OK) {
+ ALOGE("source failed to start w/ err %d", err);
+ }
}
if (err == OK) {
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
index 3d7b865..286ea13 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.cpp
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
@@ -378,7 +378,9 @@ status_t WifiDisplaySource::PlaybackSession::init(
bool usePCMAudio,
bool enableVideo,
VideoFormats::ResolutionType videoResolutionType,
- size_t videoResolutionIndex) {
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType) {
sp<AMessage> notify = new AMessage(kWhatMediaSenderNotify, id());
mMediaSender = new MediaSender(mNetSession, notify);
looper()->registerHandler(mMediaSender);
@@ -390,7 +392,9 @@ status_t WifiDisplaySource::PlaybackSession::init(
usePCMAudio,
enableVideo,
videoResolutionType,
- videoResolutionIndex);
+ videoResolutionIndex,
+ videoProfileType,
+ videoLevelType);
if (err == OK) {
err = mMediaSender->initAsync(
@@ -517,7 +521,7 @@ void WifiDisplaySource::PlaybackSession::onMessageReceived(
if (mTracks.isEmpty()) {
ALOGI("Reached EOS");
}
- } else {
+ } else if (what != Converter::kWhatShutdownCompleted) {
CHECK_EQ(what, Converter::kWhatError);
status_t err;
@@ -559,6 +563,8 @@ void WifiDisplaySource::PlaybackSession::onMessageReceived(
converter->dropAFrame();
}
}
+ } else if (what == MediaSender::kWhatInformSender) {
+ onSinkFeedback(msg);
} else {
TRESPASS();
}
@@ -654,6 +660,89 @@ void WifiDisplaySource::PlaybackSession::onMessageReceived(
}
}
+void WifiDisplaySource::PlaybackSession::onSinkFeedback(const sp<AMessage> &msg) {
+ int64_t avgLatencyUs;
+ CHECK(msg->findInt64("avgLatencyUs", &avgLatencyUs));
+
+ int64_t maxLatencyUs;
+ CHECK(msg->findInt64("maxLatencyUs", &maxLatencyUs));
+
+ ALOGI("sink reports avg. latency of %lld ms (max %lld ms)",
+ avgLatencyUs / 1000ll,
+ maxLatencyUs / 1000ll);
+
+ if (mVideoTrackIndex >= 0) {
+ const sp<Track> &videoTrack = mTracks.valueFor(mVideoTrackIndex);
+ sp<Converter> converter = videoTrack->converter();
+
+ if (converter != NULL) {
+ int32_t videoBitrate =
+ Converter::GetInt32Property("media.wfd.video-bitrate", -1);
+
+ char val[PROPERTY_VALUE_MAX];
+ if (videoBitrate < 0
+ && property_get("media.wfd.video-bitrate", val, NULL)
+ && !strcasecmp("adaptive", val)) {
+ videoBitrate = converter->getVideoBitrate();
+
+ if (avgLatencyUs > 300000ll) {
+ videoBitrate *= 0.6;
+ } else if (avgLatencyUs < 100000ll) {
+ videoBitrate *= 1.1;
+ }
+ }
+
+ if (videoBitrate > 0) {
+ if (videoBitrate < 500000) {
+ videoBitrate = 500000;
+ } else if (videoBitrate > 10000000) {
+ videoBitrate = 10000000;
+ }
+
+ if (videoBitrate != converter->getVideoBitrate()) {
+ ALOGI("setting video bitrate to %d bps", videoBitrate);
+
+ converter->setVideoBitrate(videoBitrate);
+ }
+ }
+ }
+
+ sp<RepeaterSource> repeaterSource = videoTrack->repeaterSource();
+ if (repeaterSource != NULL) {
+ double rateHz =
+ Converter::GetInt32Property(
+ "media.wfd.video-framerate", -1);
+
+ char val[PROPERTY_VALUE_MAX];
+ if (rateHz < 0.0
+ && property_get("media.wfd.video-framerate", val, NULL)
+ && !strcasecmp("adaptive", val)) {
+ rateHz = repeaterSource->getFrameRate();
+
+ if (avgLatencyUs > 300000ll) {
+ rateHz *= 0.9;
+ } else if (avgLatencyUs < 200000ll) {
+ rateHz *= 1.1;
+ }
+ }
+
+ if (rateHz > 0) {
+ if (rateHz < 5.0) {
+ rateHz = 5.0;
+ } else if (rateHz > 30.0) {
+ rateHz = 30.0;
+ }
+
+ if (rateHz != repeaterSource->getFrameRate()) {
+ ALOGI("setting frame rate to %.2f Hz", rateHz);
+
+ repeaterSource->setFrameRate(rateHz);
+ }
+ }
+ }
+ }
+}
+
status_t WifiDisplaySource::PlaybackSession::setupMediaPacketizer(
bool enableAudio, bool enableVideo) {
DataSource::RegisterDefaultSniffers();
@@ -785,7 +874,9 @@ status_t WifiDisplaySource::PlaybackSession::setupPacketizer(
bool usePCMAudio,
bool enableVideo,
VideoFormats::ResolutionType videoResolutionType,
- size_t videoResolutionIndex) {
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType) {
CHECK(enableAudio || enableVideo);
if (!mMediaPath.empty()) {
@@ -794,7 +885,8 @@ status_t WifiDisplaySource::PlaybackSession::setupPacketizer(
if (enableVideo) {
status_t err = addVideoSource(
- videoResolutionType, videoResolutionIndex);
+ videoResolutionType, videoResolutionIndex, videoProfileType,
+ videoLevelType);
if (err != OK) {
return err;
@@ -810,9 +902,13 @@ status_t WifiDisplaySource::PlaybackSession::setupPacketizer(
status_t WifiDisplaySource::PlaybackSession::addSource(
bool isVideo, const sp<MediaSource> &source, bool isRepeaterSource,
- bool usePCMAudio, size_t *numInputBuffers) {
+ bool usePCMAudio, unsigned profileIdc, unsigned levelIdc,
+ unsigned constraintSet, size_t *numInputBuffers) {
CHECK(!usePCMAudio || !isVideo);
CHECK(!isRepeaterSource || isVideo);
+ CHECK(!profileIdc || isVideo);
+ CHECK(!levelIdc || isVideo);
+ CHECK(!constraintSet || isVideo);
sp<ALooper> pullLooper = new ALooper;
pullLooper->setName("pull_looper");
@@ -841,26 +937,37 @@ status_t WifiDisplaySource::PlaybackSession::addSource(
CHECK_EQ(err, (status_t)OK);
if (isVideo) {
+ format->setString("mime", MEDIA_MIMETYPE_VIDEO_AVC);
format->setInt32("store-metadata-in-buffers", true);
-
+ format->setInt32("store-metadata-in-buffers-output", (mHDCP != NULL)
+ && (mHDCP->getCaps() & HDCPModule::HDCP_CAPS_ENCRYPT_NATIVE));
format->setInt32(
"color-format", OMX_COLOR_FormatAndroidOpaque);
+ format->setInt32("profile-idc", profileIdc);
+ format->setInt32("level-idc", levelIdc);
+ format->setInt32("constraint-set", constraintSet);
+ } else {
+ format->setString(
+ "mime",
+ usePCMAudio
+ ? MEDIA_MIMETYPE_AUDIO_RAW : MEDIA_MIMETYPE_AUDIO_AAC);
}
notify = new AMessage(kWhatConverterNotify, id());
notify->setSize("trackIndex", trackIndex);
- sp<Converter> converter =
- new Converter(notify, codecLooper, format, usePCMAudio);
+ sp<Converter> converter = new Converter(notify, codecLooper, format);
+
+ looper()->registerHandler(converter);
- err = converter->initCheck();
+ err = converter->init();
if (err != OK) {
ALOGE("%s converter returned err %d", isVideo ? "video" : "audio", err);
+
+ looper()->unregisterHandler(converter->id());
return err;
}
- looper()->registerHandler(converter);
-
notify = new AMessage(Converter::kWhatMediaPullerNotify, converter->id());
notify->setSize("trackIndex", trackIndex);
@@ -905,7 +1012,9 @@ status_t WifiDisplaySource::PlaybackSession::addSource(
status_t WifiDisplaySource::PlaybackSession::addVideoSource(
VideoFormats::ResolutionType videoResolutionType,
- size_t videoResolutionIndex) {
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType) {
size_t width, height, framesPerSecond;
bool interlaced;
CHECK(VideoFormats::GetConfiguration(
@@ -916,6 +1025,14 @@ status_t WifiDisplaySource::PlaybackSession::addVideoSource(
&framesPerSecond,
&interlaced));
+ unsigned profileIdc, levelIdc, constraintSet;
+ CHECK(VideoFormats::GetProfileLevel(
+ videoProfileType,
+ videoLevelType,
+ &profileIdc,
+ &levelIdc,
+ &constraintSet));
+
sp<SurfaceMediaSource> source = new SurfaceMediaSource(width, height);
source->setUseAbsoluteTimestamps();
@@ -926,7 +1043,8 @@ status_t WifiDisplaySource::PlaybackSession::addVideoSource(
size_t numInputBuffers;
status_t err = addSource(
true /* isVideo */, videoSource, true /* isRepeaterSource */,
- false /* usePCMAudio */, &numInputBuffers);
+ false /* usePCMAudio */, profileIdc, levelIdc, constraintSet,
+ &numInputBuffers);
if (err != OK) {
return err;
@@ -949,7 +1067,8 @@ status_t WifiDisplaySource::PlaybackSession::addAudioSource(bool usePCMAudio) {
if (audioSource->initCheck() == OK) {
return addSource(
false /* isVideo */, audioSource, false /* isRepeaterSource */,
- usePCMAudio, NULL /* numInputBuffers */);
+ usePCMAudio, 0 /* profileIdc */, 0 /* levelIdc */,
+ 0 /* constraintSet */, NULL /* numInputBuffers */);
}
ALOGW("Unable to instantiate audio source");
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.h b/media/libstagefright/wifi-display/source/PlaybackSession.h
index 39086a1..5c8ee94 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.h
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.h
@@ -53,7 +53,9 @@ struct WifiDisplaySource::PlaybackSession : public AHandler {
bool usePCMAudio,
bool enableVideo,
VideoFormats::ResolutionType videoResolutionType,
- size_t videoResolutionIndex);
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType);
void destroyAsync();
@@ -130,18 +132,25 @@ private:
bool usePCMAudio,
bool enableVideo,
VideoFormats::ResolutionType videoResolutionType,
- size_t videoResolutionIndex);
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType);
status_t addSource(
bool isVideo,
const sp<MediaSource> &source,
bool isRepeaterSource,
bool usePCMAudio,
+ unsigned profileIdc,
+ unsigned levelIdc,
+ unsigned contraintSet,
size_t *numInputBuffers);
status_t addVideoSource(
VideoFormats::ResolutionType videoResolutionType,
- size_t videoResolutionIndex);
+ size_t videoResolutionIndex,
+ VideoFormats::ProfileType videoProfileType,
+ VideoFormats::LevelType videoLevelType);
status_t addAudioSource(bool usePCMAudio);
diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.cpp b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
index 2c4a373..c674700 100644
--- a/media/libstagefright/wifi-display/source/TSPacketizer.cpp
+++ b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
@@ -261,12 +261,24 @@ void TSPacketizer::Track::finalize() {
data[0] = 40; // descriptor_tag
data[1] = 4; // descriptor_length
- CHECK_GE(mCSD.size(), 1u);
- const sp<ABuffer> &sps = mCSD.itemAt(0);
- CHECK(!memcmp("\x00\x00\x00\x01", sps->data(), 4));
- CHECK_GE(sps->size(), 7u);
- // profile_idc, constraint_set*, level_idc
- memcpy(&data[2], sps->data() + 4, 3);
+ if (mCSD.size() > 0) {
+ CHECK_GE(mCSD.size(), 1u);
+ const sp<ABuffer> &sps = mCSD.itemAt(0);
+ CHECK(!memcmp("\x00\x00\x00\x01", sps->data(), 4));
+ CHECK_GE(sps->size(), 7u);
+ // profile_idc, constraint_set*, level_idc
+ memcpy(&data[2], sps->data() + 4, 3);
+ } else {
+ int32_t profileIdc, levelIdc, constraintSet;
+ CHECK(mFormat->findInt32("profile-idc", &profileIdc));
+ CHECK(mFormat->findInt32("level-idc", &levelIdc));
+ CHECK(mFormat->findInt32("constraint-set", &constraintSet));
+ CHECK_GE(profileIdc, 0u);
+ CHECK_GE(levelIdc, 0u);
+ data[2] = profileIdc; // profile_idc
+ data[3] = constraintSet; // constraint_set*
+ data[4] = levelIdc; // level_idc
+ }
// AVC_still_present=0, AVC_24_hour_picture_flag=0, reserved
data[5] = 0x3f;
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
index 22dd0b1..05e4018 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
@@ -21,7 +21,6 @@
#include "WifiDisplaySource.h"
#include "PlaybackSession.h"
#include "Parameters.h"
-#include "ParsedMessage.h"
#include "rtp/RTPSender.h"
#include <binder/IServiceManager.h>
@@ -32,6 +31,7 @@
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ParsedMessage.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/Utils.h>
@@ -73,6 +73,12 @@ WifiDisplaySource::WifiDisplaySource(
mSupportedSourceVideoFormats.setNativeResolution(
VideoFormats::RESOLUTION_CEA, 5); // 1280x720 p30
+
+ // Enable all resolutions up to 1280x720p30
+ mSupportedSourceVideoFormats.enableResolutionUpto(
+ VideoFormats::RESOLUTION_CEA, 5,
+ VideoFormats::PROFILE_CHP, // Constrained High Profile
+ VideoFormats::LEVEL_32); // Level 3.2
}
WifiDisplaySource::~WifiDisplaySource() {
@@ -164,10 +170,10 @@ void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
} else {
err = -EINVAL;
}
-
- mState = AWAITING_CLIENT_CONNECTION;
}
+ mState = AWAITING_CLIENT_CONNECTION;
+
sp<AMessage> response = new AMessage;
response->setInt32("err", err);
response->postReply(replyID);
@@ -401,7 +407,8 @@ void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
0, // height,
mUsingHDCP
? IRemoteDisplayClient::kDisplayFlagSecure
- : 0);
+ : 0,
+ 0);
} else {
size_t width, height;
@@ -420,7 +427,8 @@ void WifiDisplaySource::onMessageReceived(const sp<AMessage> &msg) {
height,
mUsingHDCP
? IRemoteDisplayClient::kDisplayFlagSecure
- : 0);
+ : 0,
+ playbackSessionID);
}
}
@@ -617,6 +625,9 @@ status_t WifiDisplaySource::sendM4(int32_t sessionID) {
chosenVideoFormat.disableAll();
chosenVideoFormat.setNativeResolution(
mChosenVideoResolutionType, mChosenVideoResolutionIndex);
+ chosenVideoFormat.setProfileLevel(
+ mChosenVideoResolutionType, mChosenVideoResolutionIndex,
+ mChosenVideoProfile, mChosenVideoLevel);
body.append(chosenVideoFormat.getFormatSpec(true /* forM4Message */));
body.append("\r\n");
@@ -729,6 +740,8 @@ status_t WifiDisplaySource::sendM16(int32_t sessionID) {
++mNextCSeq;
+ scheduleKeepAlive(sessionID);
+
return OK;
}
@@ -845,7 +858,9 @@ status_t WifiDisplaySource::onReceiveM3Response(
mSupportedSinkVideoFormats,
mSupportedSourceVideoFormats,
&mChosenVideoResolutionType,
- &mChosenVideoResolutionIndex)) {
+ &mChosenVideoResolutionIndex,
+ &mChosenVideoProfile,
+ &mChosenVideoLevel)) {
ALOGE("Sink and source share no commonly supported video "
"formats.");
@@ -864,6 +879,9 @@ status_t WifiDisplaySource::onReceiveM3Response(
ALOGI("Picked video resolution %u x %u %c%u",
width, height, interlaced ? 'i' : 'p', framesPerSecond);
+
+ ALOGI("Picked AVC profile %d, level %d",
+ mChosenVideoProfile, mChosenVideoLevel);
} else {
ALOGI("Sink doesn't support video at all.");
}
@@ -994,8 +1012,6 @@ status_t WifiDisplaySource::onReceiveM16Response(
if (mClientInfo.mPlaybackSession != NULL) {
mClientInfo.mPlaybackSession->updateLiveness();
-
- scheduleKeepAlive(sessionID);
}
return OK;
@@ -1257,7 +1273,9 @@ status_t WifiDisplaySource::onSetupRequest(
mUsingPCMAudio,
mSinkSupportsVideo,
mChosenVideoResolutionType,
- mChosenVideoResolutionIndex);
+ mChosenVideoResolutionIndex,
+ mChosenVideoProfile,
+ mChosenVideoLevel);
if (err != OK) {
looper()->unregisterHandler(playbackSession->id());
@@ -1340,7 +1358,9 @@ status_t WifiDisplaySource::onPlayRequest(
return ERROR_MALFORMED;
}
- if (mState != AWAITING_CLIENT_PLAY) {
+ if (mState != AWAITING_CLIENT_PLAY
+ && mState != PAUSED_TO_PLAYING
+ && mState != PAUSED) {
ALOGW("Received PLAY request but we're in state %d", mState);
sendErrorResponse(
@@ -1367,7 +1387,7 @@ status_t WifiDisplaySource::onPlayRequest(
return err;
}
- if (mState == PAUSED_TO_PLAYING) {
+ if (mState == PAUSED_TO_PLAYING || mPlaybackSessionEstablished) {
mState = PLAYING;
return OK;
}
@@ -1401,7 +1421,7 @@ status_t WifiDisplaySource::onPauseRequest(
ALOGI("Received PAUSE request.");
- if (mState != PLAYING_TO_PAUSED) {
+ if (mState != PLAYING_TO_PAUSED && mState != PLAYING) {
return INVALID_OPERATION;
}
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.h b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
index 44d3e4d..750265f 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.h
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
@@ -18,10 +18,10 @@
#define WIFI_DISPLAY_SOURCE_H_
-#include "ANetworkSession.h"
#include "VideoFormats.h"
#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/ANetworkSession.h>
#include <netinet/in.h>
@@ -131,6 +131,8 @@ private:
VideoFormats::ResolutionType mChosenVideoResolutionType;
size_t mChosenVideoResolutionIndex;
+ VideoFormats::ProfileType mChosenVideoProfile;
+ VideoFormats::LevelType mChosenVideoLevel;
bool mSinkSupportsAudio;
diff --git a/media/libstagefright/wifi-display/wfd.cpp b/media/libstagefright/wifi-display/wfd.cpp
deleted file mode 100644
index c947765..0000000
--- a/media/libstagefright/wifi-display/wfd.cpp
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright 2012, 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 "wfd"
-#include <utils/Log.h>
-
-#include "source/WifiDisplaySource.h"
-
-#include <binder/ProcessState.h>
-#include <binder/IServiceManager.h>
-#include <gui/ISurfaceComposer.h>
-#include <gui/SurfaceComposerClient.h>
-#include <media/AudioSystem.h>
-#include <media/IMediaPlayerService.h>
-#include <media/IRemoteDisplay.h>
-#include <media/IRemoteDisplayClient.h>
-#include <media/stagefright/DataSource.h>
-#include <media/stagefright/foundation/ADebug.h>
-#include <media/stagefright/foundation/AMessage.h>
-#include <ui/DisplayInfo.h>
-
-namespace android {
-
-static void usage(const char *me) {
- fprintf(stderr,
- "usage:\n"
- " %s -l iface[:port]\tcreate a wifi display source\n"
- " -f(ilename) \tstream media\n",
- me);
-}
-
-struct RemoteDisplayClient : public BnRemoteDisplayClient {
- RemoteDisplayClient();
-
- virtual void onDisplayConnected(
- const sp<IGraphicBufferProducer> &bufferProducer,
- uint32_t width,
- uint32_t height,
- uint32_t flags);
-
- virtual void onDisplayDisconnected();
- virtual void onDisplayError(int32_t error);
-
- void waitUntilDone();
-
-protected:
- virtual ~RemoteDisplayClient();
-
-private:
- Mutex mLock;
- Condition mCondition;
-
- bool mDone;
-
- sp<SurfaceComposerClient> mComposerClient;
- sp<IGraphicBufferProducer> mSurfaceTexture;
- sp<IBinder> mDisplayBinder;
-
- DISALLOW_EVIL_CONSTRUCTORS(RemoteDisplayClient);
-};
-
-RemoteDisplayClient::RemoteDisplayClient()
- : mDone(false) {
- mComposerClient = new SurfaceComposerClient;
- CHECK_EQ(mComposerClient->initCheck(), (status_t)OK);
-}
-
-RemoteDisplayClient::~RemoteDisplayClient() {
-}
-
-void RemoteDisplayClient::onDisplayConnected(
- const sp<IGraphicBufferProducer> &bufferProducer,
- uint32_t width,
- uint32_t height,
- uint32_t flags) {
- ALOGI("onDisplayConnected width=%u, height=%u, flags = 0x%08x",
- width, height, flags);
-
- if (bufferProducer != NULL) {
- mSurfaceTexture = bufferProducer;
- mDisplayBinder = mComposerClient->createDisplay(
- String8("foo"), false /* secure */);
-
- SurfaceComposerClient::openGlobalTransaction();
- mComposerClient->setDisplaySurface(mDisplayBinder, mSurfaceTexture);
-
- Rect layerStackRect(1280, 720); // XXX fix this.
- Rect displayRect(1280, 720);
-
- mComposerClient->setDisplayProjection(
- mDisplayBinder, 0 /* 0 degree rotation */,
- layerStackRect,
- displayRect);
-
- SurfaceComposerClient::closeGlobalTransaction();
- }
-}
-
-void RemoteDisplayClient::onDisplayDisconnected() {
- ALOGI("onDisplayDisconnected");
-
- Mutex::Autolock autoLock(mLock);
- mDone = true;
- mCondition.broadcast();
-}
-
-void RemoteDisplayClient::onDisplayError(int32_t error) {
- ALOGI("onDisplayError error=%d", error);
-
- Mutex::Autolock autoLock(mLock);
- mDone = true;
- mCondition.broadcast();
-}
-
-void RemoteDisplayClient::waitUntilDone() {
- Mutex::Autolock autoLock(mLock);
- while (!mDone) {
- mCondition.wait(mLock);
- }
-}
-
-static status_t enableAudioSubmix(bool enable) {
- status_t err = AudioSystem::setDeviceConnectionState(
- AUDIO_DEVICE_IN_REMOTE_SUBMIX,
- enable
- ? AUDIO_POLICY_DEVICE_STATE_AVAILABLE
- : AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
- NULL /* device_address */);
-
- if (err != OK) {
- return err;
- }
-
- err = AudioSystem::setDeviceConnectionState(
- AUDIO_DEVICE_OUT_REMOTE_SUBMIX,
- enable
- ? AUDIO_POLICY_DEVICE_STATE_AVAILABLE
- : AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
- NULL /* device_address */);
-
- return err;
-}
-
-static void createSource(const AString &addr, int32_t port) {
- sp<IServiceManager> sm = defaultServiceManager();
- sp<IBinder> binder = sm->getService(String16("media.player"));
- sp<IMediaPlayerService> service =
- interface_cast<IMediaPlayerService>(binder);
-
- CHECK(service.get() != NULL);
-
- enableAudioSubmix(true /* enable */);
-
- String8 iface;
- iface.append(addr.c_str());
- iface.append(StringPrintf(":%d", port).c_str());
-
- sp<RemoteDisplayClient> client = new RemoteDisplayClient;
- sp<IRemoteDisplay> display = service->listenForRemoteDisplay(client, iface);
-
- client->waitUntilDone();
-
- display->dispose();
- display.clear();
-
- enableAudioSubmix(false /* enable */);
-}
-
-static void createFileSource(
- const AString &addr, int32_t port, const char *path) {
- sp<ANetworkSession> session = new ANetworkSession;
- session->start();
-
- sp<ALooper> looper = new ALooper;
- looper->start();
-
- sp<RemoteDisplayClient> client = new RemoteDisplayClient;
- sp<WifiDisplaySource> source = new WifiDisplaySource(session, client, path);
- looper->registerHandler(source);
-
- AString iface = StringPrintf("%s:%d", addr.c_str(), port);
- CHECK_EQ((status_t)OK, source->start(iface.c_str()));
-
- client->waitUntilDone();
-
- source->stop();
-}
-
-} // namespace android
-
-int main(int argc, char **argv) {
- using namespace android;
-
- ProcessState::self()->startThreadPool();
-
- DataSource::RegisterDefaultSniffers();
-
- AString listenOnAddr;
- int32_t listenOnPort = -1;
-
- AString path;
-
- int res;
- while ((res = getopt(argc, argv, "hl:f:")) >= 0) {
- switch (res) {
- case 'f':
- {
- path = optarg;
- break;
- }
-
- case 'l':
- {
- const char *colonPos = strrchr(optarg, ':');
-
- if (colonPos == NULL) {
- listenOnAddr = optarg;
- listenOnPort = WifiDisplaySource::kWifiDisplayDefaultPort;
- } else {
- listenOnAddr.setTo(optarg, colonPos - optarg);
-
- char *end;
- listenOnPort = strtol(colonPos + 1, &end, 10);
-
- if (*end != '\0' || end == colonPos + 1
- || listenOnPort < 1 || listenOnPort > 65535) {
- fprintf(stderr, "Illegal port specified.\n");
- exit(1);
- }
- }
- break;
- }
-
- case '?':
- case 'h':
- default:
- usage(argv[0]);
- exit(1);
- }
- }
-
- if (listenOnPort >= 0) {
- if (path.empty()) {
- createSource(listenOnAddr, listenOnPort);
- } else {
- createFileSource(listenOnAddr, listenOnPort, path.c_str());
- }
-
- exit(0);
- }
-
- usage(argv[0]);
-
- return 0;
-}